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  }