github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/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 "github.com/go-quicktest/qt" 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, qt.Equals(ins.MapPtr(), 123)) 114 qt.Assert(t, qt.Equals(ins.mapOffset(), 321)) 115 116 qt.Assert(t, qt.IsNil(ins.RewriteMapPtr(-1))) 117 qt.Assert(t, qt.Equals(ins.MapPtr(), -1)) 118 119 qt.Assert(t, qt.IsNil(ins.RewriteMapPtr(1))) 120 qt.Assert(t, qt.Equals(ins.MapPtr(), 1)) 121 122 // mapOffset should be unchanged after rewriting the pointer. 123 qt.Assert(t, qt.Equals(ins.mapOffset(), 321)) 124 125 qt.Assert(t, qt.IsNil(ins.RewriteMapOffset(123))) 126 qt.Assert(t, qt.Equals(ins.mapOffset(), 123)) 127 128 // MapPtr should be unchanged. 129 qt.Assert(t, qt.Equals(ins.MapPtr(), 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 changing 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 // Setting metadata should copy Instruction and modify the metadata pointer 308 // of the new object without touching the old Instruction. 309 310 // Reference 311 ins := Ja.Label("my_func") 312 ins2 := ins.WithReference("my_func2") 313 314 qt.Assert(t, qt.Equals(ins.Reference(), "my_func"), qt.Commentf("WithReference updated ins")) 315 qt.Assert(t, qt.Equals(ins2.Reference(), "my_func2"), qt.Commentf("WithReference didn't update ins2")) 316 317 // Symbol 318 ins = Ja.Label("").WithSymbol("my_sym") 319 ins2 = ins.WithSymbol("my_sym2") 320 321 qt.Assert(t, qt.Equals(ins.Symbol(), "my_sym"), qt.Commentf("WithSymbol updated ins")) 322 qt.Assert(t, qt.Equals(ins2.Symbol(), "my_sym2"), qt.Commentf("WithSymbol didn't update ins2")) 323 324 // Map 325 ins = LoadMapPtr(R1, 0) 326 ins2 = ins 327 328 testMap := testFDer(1) 329 qt.Assert(t, qt.IsNil(ins2.AssociateMap(testMap)), qt.Commentf("failed to associate map with ins2")) 330 331 qt.Assert(t, qt.IsNil(ins.Map()), qt.Commentf("AssociateMap updated ins")) 332 qt.Assert(t, qt.Equals[FDer](ins2.Map(), testMap), qt.Commentf("AssociateMap didn't update ins2")) 333 } 334 335 type testFDer int 336 337 func (t testFDer) FD() int { 338 return int(t) 339 } 340 341 func TestISAv4(t *testing.T) { 342 rawInsns := []byte{ 343 0xd7, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // r1 = bswap16 r1 344 0xd7, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // r2 = bswap32 r2 345 0xd7, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // r3 = bswap64 r3 346 347 0x91, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = *(s8 *)(r4 + 0x0) 348 0x89, 0x52, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // r2 = *(s16 *)(r5 + 0x4) 349 0x81, 0x63, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // r3 = *(s32 *)(r6 + 0x8) 350 351 0x91, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = *(s8 *)(r4 + 0x0) 352 0x89, 0x52, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // r2 = *(s16 *)(r5 + 0x4) 353 354 0xbf, 0x41, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = (s8)r4 355 0xbf, 0x52, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // r2 = (s16)r5 356 0xbf, 0x63, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, // r3 = (s32)r6 357 358 0xbc, 0x31, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // w1 = (s8)w3 359 0xbc, 0x42, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // w2 = (s16)w4 360 361 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, // gotol +3 362 363 0x3f, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 s/= r3 364 0x9f, 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // r2 s%= r4 365 366 0x3c, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // w1 s/= w3 367 0x9c, 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // w2 s%= w4 368 } 369 370 var insns Instructions 371 err := insns.Unmarshal(bytes.NewReader(rawInsns), binary.LittleEndian) 372 if err != nil { 373 t.Fatal(err) 374 } 375 376 lines := []string{ 377 "BSwap16 dst: r1 ", 378 "BSwap32 dst: r2 ", 379 "BSwap64 dst: r3 ", 380 "LdXMemSXB dst: r1 src: r4 off: 0 imm: 0", 381 "LdXMemSXH dst: r2 src: r5 off: 4 imm: 0", 382 "LdXMemSXW dst: r3 src: r6 off: 8 imm: 0", 383 "LdXMemSXB dst: r1 src: r4 off: 0 imm: 0", 384 "LdXMemSXH dst: r2 src: r5 off: 4 imm: 0", 385 "MovSX8Reg dst: r1 src: r4", 386 "MovSX16Reg dst: r2 src: r5", 387 "MovSX32Reg dst: r3 src: r6", 388 "MovSX8Reg32 dst: r1 src: r3", 389 "MovSX16Reg32 dst: r2 src: r4", 390 "Ja32 imm: 3", 391 "SDivReg dst: r1 src: r3", 392 "SModReg dst: r2 src: r4", 393 "SDivReg32 dst: r1 src: r3", 394 "SModReg32 dst: r2 src: r4", 395 } 396 397 for i, ins := range insns { 398 if want, got := lines[i], fmt.Sprint(ins); want != got { 399 t.Errorf("Expected %q, got %q", want, got) 400 } 401 } 402 403 // Marshal and unmarshal again to make sure the instructions are 404 // still valid. 405 var buf bytes.Buffer 406 err = insns.Marshal(&buf, binary.LittleEndian) 407 if err != nil { 408 t.Fatal(err) 409 } 410 411 if !bytes.Equal(buf.Bytes(), rawInsns) { 412 t.Error("Expected instructions to be equal after marshalling") 413 } 414 } 415 416 func TestLongJumpPatching(t *testing.T) { 417 insns := Instructions{ 418 LongJump("exit"), 419 Xor.Reg(R0, R0), 420 Xor.Reg(R0, R0), 421 Xor.Reg(R0, R0), 422 Return().WithSymbol("exit"), 423 } 424 425 err := insns.encodeFunctionReferences() 426 if err != nil { 427 t.Fatal(err) 428 } 429 430 if insns[0].Constant != 3 { 431 t.Errorf("Expected offset to be 3, got %d", insns[1].Constant) 432 } 433 }