github.com/cilium/ebpf@v0.10.0/asm/instruction_test.go (about) 1 package asm 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "io" 10 "math" 11 "testing" 12 13 qt "github.com/frankban/quicktest" 14 ) 15 16 var test64bitImmProg = []byte{ 17 // r0 = math.MinInt32 - 1 18 0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x7f, 19 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 20 } 21 22 func TestRead64bitImmediate(t *testing.T) { 23 var ins Instruction 24 n, err := ins.Unmarshal(bytes.NewReader(test64bitImmProg), binary.LittleEndian) 25 if err != nil { 26 t.Fatal(err) 27 } 28 if want := uint64(InstructionSize * 2); n != want { 29 t.Errorf("Expected %d bytes to be read, got %d", want, n) 30 } 31 32 if c := ins.Constant; c != math.MinInt32-1 { 33 t.Errorf("Expected immediate to be %v, got %v", int64(math.MinInt32)-1, c) 34 } 35 } 36 37 func BenchmarkRead64bitImmediate(b *testing.B) { 38 r := &bytes.Reader{} 39 for i := 0; i < b.N; i++ { 40 r.Reset(test64bitImmProg) 41 42 var ins Instruction 43 if _, err := ins.Unmarshal(r, binary.LittleEndian); err != nil { 44 b.Fatal(err) 45 } 46 } 47 } 48 49 func TestWrite64bitImmediate(t *testing.T) { 50 insns := Instructions{ 51 LoadImm(R0, math.MinInt32-1, DWord), 52 } 53 54 var buf bytes.Buffer 55 if err := insns.Marshal(&buf, binary.LittleEndian); err != nil { 56 t.Fatal(err) 57 } 58 59 if prog := buf.Bytes(); !bytes.Equal(prog, test64bitImmProg) { 60 t.Errorf("Marshalled program does not match:\n%s", hex.Dump(prog)) 61 } 62 } 63 64 func BenchmarkWrite64BitImmediate(b *testing.B) { 65 ins := LoadImm(R0, math.MinInt32-1, DWord) 66 67 var buf bytes.Buffer 68 for i := 0; i < b.N; i++ { 69 buf.Reset() 70 71 if _, err := ins.Marshal(&buf, binary.LittleEndian); err != nil { 72 b.Fatal(err) 73 } 74 } 75 } 76 77 func TestUnmarshalInstructions(t *testing.T) { 78 r := bytes.NewReader(test64bitImmProg) 79 80 var insns Instructions 81 if err := insns.Unmarshal(r, binary.LittleEndian); err != nil { 82 t.Fatal(err) 83 } 84 85 // Unmarshaling into the same Instructions multiple times replaces 86 // the instruction stream. 87 r.Reset(test64bitImmProg) 88 if err := insns.Unmarshal(r, binary.LittleEndian); err != nil { 89 t.Fatal(err) 90 } 91 92 if len(insns) != 1 { 93 t.Fatalf("Expected one instruction, got %d", len(insns)) 94 } 95 } 96 97 func TestSignedJump(t *testing.T) { 98 insns := Instructions{ 99 JSGT.Imm(R0, -1, "foo"), 100 } 101 102 insns[0].Offset = 1 103 104 err := insns.Marshal(io.Discard, binary.LittleEndian) 105 if err != nil { 106 t.Error("Can't marshal signed jump:", err) 107 } 108 } 109 110 func TestInstructionRewriteMapConstant(t *testing.T) { 111 ins := LoadMapValue(R0, 123, 321) 112 113 qt.Assert(t, ins.MapPtr(), qt.Equals, 123) 114 qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(321)) 115 116 qt.Assert(t, ins.RewriteMapPtr(-1), qt.IsNil) 117 qt.Assert(t, ins.MapPtr(), qt.Equals, -1) 118 119 qt.Assert(t, ins.RewriteMapPtr(1), qt.IsNil) 120 qt.Assert(t, ins.MapPtr(), qt.Equals, 1) 121 122 // mapOffset should be unchanged after rewriting the pointer. 123 qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(321)) 124 125 qt.Assert(t, ins.RewriteMapOffset(123), qt.IsNil) 126 qt.Assert(t, ins.mapOffset(), qt.Equals, uint32(123)) 127 128 // MapPtr should be unchanged. 129 qt.Assert(t, ins.MapPtr(), qt.Equals, 1) 130 131 ins = Mov.Imm(R1, 32) 132 if err := ins.RewriteMapPtr(1); err == nil { 133 t.Error("RewriteMapPtr rewriting bogus instruction") 134 } 135 if err := ins.RewriteMapOffset(1); err == nil { 136 t.Error("RewriteMapOffset rewriting bogus instruction") 137 } 138 } 139 140 func TestInstructionLoadMapValue(t *testing.T) { 141 ins := LoadMapValue(R0, 1, 123) 142 if !ins.IsLoadFromMap() { 143 t.Error("isLoadFromMap returns false") 144 } 145 if fd := ins.mapFd(); fd != 1 { 146 t.Error("Expected map fd to be 1, got", fd) 147 } 148 if off := ins.mapOffset(); off != 123 { 149 t.Fatal("Expected map offset to be 123 after changin the pointer, got", off) 150 } 151 } 152 153 func TestInstructionsRewriteMapPtr(t *testing.T) { 154 insns := Instructions{ 155 LoadMapPtr(R1, 0).WithReference("good"), 156 Return(), 157 } 158 159 if err := insns.RewriteMapPtr("good", 1); err != nil { 160 t.Fatal(err) 161 } 162 163 if insns[0].Constant != 1 { 164 t.Error("Constant should be 1, have", insns[0].Constant) 165 } 166 167 if err := insns.RewriteMapPtr("good", 2); err != nil { 168 t.Fatal(err) 169 } 170 171 if insns[0].Constant != 2 { 172 t.Error("Constant should be 2, have", insns[0].Constant) 173 } 174 175 if err := insns.RewriteMapPtr("bad", 1); !errors.Is(err, ErrUnreferencedSymbol) { 176 t.Error("Rewriting unreferenced map doesn't return appropriate error") 177 } 178 } 179 180 func TestInstructionWithMetadata(t *testing.T) { 181 ins := LoadImm(R0, 123, DWord).WithSymbol("abc") 182 ins2 := LoadImm(R0, 567, DWord).WithMetadata(ins.Metadata) 183 184 if want, got := "abc", ins2.Symbol(); want != got { 185 t.Fatalf("unexpected Symbol value on ins2: want: %s, got: %s", want, got) 186 } 187 188 if want, got := ins.Metadata, ins2.Metadata; want != got { 189 t.Fatal("expected ins and isn2 Metadata to match") 190 } 191 } 192 193 // You can use format flags to change the way an eBPF 194 // program is stringified. 195 func ExampleInstructions_Format() { 196 197 insns := Instructions{ 198 FnMapLookupElem.Call().WithSymbol("my_func").WithSource(Comment("bpf_map_lookup_elem()")), 199 LoadImm(R0, 42, DWord).WithSource(Comment("abc = 42")), 200 Return(), 201 } 202 203 fmt.Println("Default format:") 204 fmt.Printf("%v\n", insns) 205 206 fmt.Println("Don't indent instructions:") 207 fmt.Printf("%.0v\n", insns) 208 209 fmt.Println("Indent using spaces:") 210 fmt.Printf("% v\n", insns) 211 212 fmt.Println("Control symbol indentation:") 213 fmt.Printf("%2v\n", insns) 214 215 // Output: Default format: 216 // my_func: 217 // ; bpf_map_lookup_elem() 218 // 0: Call FnMapLookupElem 219 // ; abc = 42 220 // 1: LdImmDW dst: r0 imm: 42 221 // 3: Exit 222 // 223 // Don't indent instructions: 224 // my_func: 225 // ; bpf_map_lookup_elem() 226 // 0: Call FnMapLookupElem 227 // ; abc = 42 228 // 1: LdImmDW dst: r0 imm: 42 229 // 3: Exit 230 // 231 // Indent using spaces: 232 // my_func: 233 // ; bpf_map_lookup_elem() 234 // 0: Call FnMapLookupElem 235 // ; abc = 42 236 // 1: LdImmDW dst: r0 imm: 42 237 // 3: Exit 238 // 239 // Control symbol indentation: 240 // my_func: 241 // ; bpf_map_lookup_elem() 242 // 0: Call FnMapLookupElem 243 // ; abc = 42 244 // 1: LdImmDW dst: r0 imm: 42 245 // 3: Exit 246 } 247 248 func TestReadSrcDst(t *testing.T) { 249 testSrcDstProg := []byte{ 250 // on little-endian: r0 = r1 251 // on big-endian: be: r1 = r0 252 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 253 } 254 255 testcases := []struct { 256 bo binary.ByteOrder 257 dst, src Register 258 }{ 259 {binary.BigEndian, R1, R0}, 260 {binary.LittleEndian, R0, R1}, 261 } 262 263 for _, tc := range testcases { 264 t.Run(tc.bo.String(), func(t *testing.T) { 265 var ins Instruction 266 _, err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo) 267 if err != nil { 268 t.Fatal(err) 269 } 270 if ins.Dst != tc.dst { 271 t.Errorf("Expected destination to be %v, got %v", tc.dst, ins.Dst) 272 } 273 if ins.Src != tc.src { 274 t.Errorf("Expected source to be %v, got %v", tc.src, ins.Src) 275 } 276 }) 277 } 278 } 279 280 func TestInstructionIterator(t *testing.T) { 281 insns := Instructions{ 282 LoadImm(R0, 0, Word), 283 LoadImm(R0, 0, DWord), 284 Return(), 285 } 286 offsets := []RawInstructionOffset{0, 1, 3} 287 288 iter := insns.Iterate() 289 for i := 0; i < len(insns); i++ { 290 if !iter.Next() { 291 t.Fatalf("Expected %dth call to Next to return true", i) 292 } 293 294 if iter.Ins == nil { 295 t.Errorf("Expected iter.Ins to be non-nil") 296 } 297 if iter.Index != i { 298 t.Errorf("Expected iter.Index to be %d, got %d", i, iter.Index) 299 } 300 if iter.Offset != offsets[i] { 301 t.Errorf("Expected iter.Offset to be %d, got %d", offsets[i], iter.Offset) 302 } 303 } 304 } 305 306 func TestMetadataCopyOnWrite(t *testing.T) { 307 c := qt.New(t) 308 309 // Setting metadata should copy Instruction and modify the metadata pointer 310 // of the new object without touching the old Instruction. 311 312 // Reference 313 ins := Ja.Label("my_func") 314 ins2 := ins.WithReference("my_func2") 315 316 c.Assert(ins.Reference(), qt.Equals, "my_func", qt.Commentf("WithReference updated ins")) 317 c.Assert(ins2.Reference(), qt.Equals, "my_func2", qt.Commentf("WithReference didn't update ins2")) 318 319 // Symbol 320 ins = Ja.Label("").WithSymbol("my_sym") 321 ins2 = ins.WithSymbol("my_sym2") 322 323 c.Assert(ins.Symbol(), qt.Equals, "my_sym", qt.Commentf("WithSymbol updated ins")) 324 c.Assert(ins2.Symbol(), qt.Equals, "my_sym2", qt.Commentf("WithSymbol didn't update ins2")) 325 326 // Map 327 ins = LoadMapPtr(R1, 0) 328 ins2 = ins 329 330 testMap := testFDer(1) 331 c.Assert(ins2.AssociateMap(testMap), qt.IsNil, qt.Commentf("failed to associate map with ins2")) 332 333 c.Assert(ins.Map(), qt.IsNil, qt.Commentf("AssociateMap updated ins")) 334 c.Assert(ins2.Map(), qt.Equals, testMap, qt.Commentf("AssociateMap didn't update ins2")) 335 } 336 337 type testFDer int 338 339 func (t testFDer) FD() int { 340 return int(t) 341 }