...

Source file src/github.com/cilium/ebpf/asm/instruction_test.go

Documentation: github.com/cilium/ebpf/asm

     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  }
   329  

View as plain text