github.com/kubeshark/ebpf@v0.9.2/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 // You can use format flags to change the way an eBPF 181 // program is stringified. 182 func ExampleInstructions_Format() { 183 184 insns := Instructions{ 185 FnMapLookupElem.Call().WithSymbol("my_func").WithSource(Comment("bpf_map_lookup_elem()")), 186 LoadImm(R0, 42, DWord).WithSource(Comment("abc = 42")), 187 Return(), 188 } 189 190 fmt.Println("Default format:") 191 fmt.Printf("%v\n", insns) 192 193 fmt.Println("Don't indent instructions:") 194 fmt.Printf("%.0v\n", insns) 195 196 fmt.Println("Indent using spaces:") 197 fmt.Printf("% v\n", insns) 198 199 fmt.Println("Control symbol indentation:") 200 fmt.Printf("%2v\n", insns) 201 202 // Output: Default format: 203 // my_func: 204 // ; bpf_map_lookup_elem() 205 // 0: Call FnMapLookupElem 206 // ; abc = 42 207 // 1: LdImmDW dst: r0 imm: 42 208 // 3: Exit 209 // 210 // Don't indent instructions: 211 // my_func: 212 // ; bpf_map_lookup_elem() 213 // 0: Call FnMapLookupElem 214 // ; abc = 42 215 // 1: LdImmDW dst: r0 imm: 42 216 // 3: Exit 217 // 218 // Indent using spaces: 219 // my_func: 220 // ; bpf_map_lookup_elem() 221 // 0: Call FnMapLookupElem 222 // ; abc = 42 223 // 1: LdImmDW dst: r0 imm: 42 224 // 3: Exit 225 // 226 // Control symbol indentation: 227 // my_func: 228 // ; bpf_map_lookup_elem() 229 // 0: Call FnMapLookupElem 230 // ; abc = 42 231 // 1: LdImmDW dst: r0 imm: 42 232 // 3: Exit 233 } 234 235 func TestReadSrcDst(t *testing.T) { 236 testSrcDstProg := []byte{ 237 // on little-endian: r0 = r1 238 // on big-endian: be: r1 = r0 239 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 240 } 241 242 testcases := []struct { 243 bo binary.ByteOrder 244 dst, src Register 245 }{ 246 {binary.BigEndian, R1, R0}, 247 {binary.LittleEndian, R0, R1}, 248 } 249 250 for _, tc := range testcases { 251 t.Run(tc.bo.String(), func(t *testing.T) { 252 var ins Instruction 253 _, err := ins.Unmarshal(bytes.NewReader(testSrcDstProg), tc.bo) 254 if err != nil { 255 t.Fatal(err) 256 } 257 if ins.Dst != tc.dst { 258 t.Errorf("Expected destination to be %v, got %v", tc.dst, ins.Dst) 259 } 260 if ins.Src != tc.src { 261 t.Errorf("Expected source to be %v, got %v", tc.src, ins.Src) 262 } 263 }) 264 } 265 } 266 267 func TestInstructionIterator(t *testing.T) { 268 insns := Instructions{ 269 LoadImm(R0, 0, Word), 270 LoadImm(R0, 0, DWord), 271 Return(), 272 } 273 offsets := []RawInstructionOffset{0, 1, 3} 274 275 iter := insns.Iterate() 276 for i := 0; i < len(insns); i++ { 277 if !iter.Next() { 278 t.Fatalf("Expected %dth call to Next to return true", i) 279 } 280 281 if iter.Ins == nil { 282 t.Errorf("Expected iter.Ins to be non-nil") 283 } 284 if iter.Index != i { 285 t.Errorf("Expected iter.Index to be %d, got %d", i, iter.Index) 286 } 287 if iter.Offset != offsets[i] { 288 t.Errorf("Expected iter.Offset to be %d, got %d", offsets[i], iter.Offset) 289 } 290 } 291 } 292 293 func TestMetadataCopyOnWrite(t *testing.T) { 294 c := qt.New(t) 295 296 // Setting metadata should copy Instruction and modify the metadata pointer 297 // of the new object without touching the old Instruction. 298 299 // Reference 300 ins := Ja.Label("my_func") 301 ins2 := ins.WithReference("my_func2") 302 303 c.Assert(ins.Reference(), qt.Equals, "my_func", qt.Commentf("WithReference updated ins")) 304 c.Assert(ins2.Reference(), qt.Equals, "my_func2", qt.Commentf("WithReference didn't update ins2")) 305 306 // Symbol 307 ins = Ja.Label("").WithSymbol("my_sym") 308 ins2 = ins.WithSymbol("my_sym2") 309 310 c.Assert(ins.Symbol(), qt.Equals, "my_sym", qt.Commentf("WithSymbol updated ins")) 311 c.Assert(ins2.Symbol(), qt.Equals, "my_sym2", qt.Commentf("WithSymbol didn't update ins2")) 312 313 // Map 314 ins = LoadMapPtr(R1, 0) 315 ins2 = ins 316 317 testMap := testFDer(1) 318 c.Assert(ins2.AssociateMap(testMap), qt.IsNil, qt.Commentf("failed to associate map with ins2")) 319 320 c.Assert(ins.Map(), qt.IsNil, qt.Commentf("AssociateMap updated ins")) 321 c.Assert(ins2.Map(), qt.Equals, testMap, qt.Commentf("AssociateMap didn't update ins2")) 322 } 323 324 type testFDer int 325 326 func (t testFDer) FD() int { 327 return int(t) 328 }