浏览代码

bpf: implement fmt.Stringer for BPF instructions

Fixes golang/go#18538

Change-Id: Ic0627352f96ad5fa138633d1e1ccfaf76294d621
Reviewed-on: https://go-review.googlesource.com/35171
Run-TryBot: Matt Layher <mdlayher@gmail.com>
Reviewed-by: Matt Layher <mdlayher@gmail.com>
Reviewed-by: David Anderson <dave@natulte.net>
Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Lucas Bremgartner 9 年之前
父节点
当前提交
0ab64c594a
共有 2 个文件被更改,包括 594 次插入0 次删除
  1. 243 0
      bpf/instructions.go
  2. 351 0
      bpf/instructions_test.go

+ 243 - 0
bpf/instructions.go

@@ -198,6 +198,18 @@ func (a LoadConstant) Assemble() (RawInstruction, error) {
 	return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
 }
 
+// String returns the the instruction in assembler notation.
+func (a LoadConstant) String() string {
+	switch a.Dst {
+	case RegA:
+		return fmt.Sprintf("ld #%d", a.Val)
+	case RegX:
+		return fmt.Sprintf("ldx #%d", a.Val)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // LoadScratch loads scratch[N] into register Dst.
 type LoadScratch struct {
 	Dst Register
@@ -212,6 +224,18 @@ func (a LoadScratch) Assemble() (RawInstruction, error) {
 	return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
 }
 
+// String returns the the instruction in assembler notation.
+func (a LoadScratch) String() string {
+	switch a.Dst {
+	case RegA:
+		return fmt.Sprintf("ld M[%d]", a.N)
+	case RegX:
+		return fmt.Sprintf("ldx M[%d]", a.N)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // LoadAbsolute loads packet[Off:Off+Size] as an integer value into
 // register A.
 type LoadAbsolute struct {
@@ -224,6 +248,23 @@ func (a LoadAbsolute) Assemble() (RawInstruction, error) {
 	return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
 }
 
+// String returns the the instruction in assembler notation.
+func (a LoadAbsolute) String() string {
+	switch a.Size {
+	case 1: // byte
+		return fmt.Sprintf("ldb [%d]", a.Off)
+	case 2: // half word
+		return fmt.Sprintf("ldh [%d]", a.Off)
+	case 4: // word
+		if a.Off > extOffset+0xffffffff {
+			return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
+		}
+		return fmt.Sprintf("ld [%d]", a.Off)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
 // into register A.
 type LoadIndirect struct {
@@ -236,6 +277,20 @@ func (a LoadIndirect) Assemble() (RawInstruction, error) {
 	return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
 }
 
+// String returns the the instruction in assembler notation.
+func (a LoadIndirect) String() string {
+	switch a.Size {
+	case 1: // byte
+		return fmt.Sprintf("ldb [x + %d]", a.Off)
+	case 2: // half word
+		return fmt.Sprintf("ldh [x + %d]", a.Off)
+	case 4: // word
+		return fmt.Sprintf("ld [x + %d]", a.Off)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
 // by 4 and stores the result in register X.
 //
@@ -251,6 +306,11 @@ func (a LoadMemShift) Assemble() (RawInstruction, error) {
 	return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
 }
 
+// String returns the the instruction in assembler notation.
+func (a LoadMemShift) String() string {
+	return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
+}
+
 // LoadExtension invokes a linux-specific extension and stores the
 // result in register A.
 type LoadExtension struct {
@@ -265,6 +325,46 @@ func (a LoadExtension) Assemble() (RawInstruction, error) {
 	return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
 }
 
+// String returns the the instruction in assembler notation.
+func (a LoadExtension) String() string {
+	switch a.Num {
+	case ExtLen:
+		return "ld #len"
+	case ExtProto:
+		return "ld #proto"
+	case ExtType:
+		return "ld #type"
+	case ExtPayloadOffset:
+		return "ld #poff"
+	case ExtInterfaceIndex:
+		return "ld #ifidx"
+	case ExtNetlinkAttr:
+		return "ld #nla"
+	case ExtNetlinkAttrNested:
+		return "ld #nlan"
+	case ExtMark:
+		return "ld #mark"
+	case ExtQueue:
+		return "ld #queue"
+	case ExtLinkLayerType:
+		return "ld #hatype"
+	case ExtRXHash:
+		return "ld #rxhash"
+	case ExtCPUID:
+		return "ld #cpu"
+	case ExtVLANTag:
+		return "ld #vlan_tci"
+	case ExtVLANTagPresent:
+		return "ld #vlan_avail"
+	case ExtVLANProto:
+		return "ld #vlan_tpid"
+	case ExtRand:
+		return "ld #rand"
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // StoreScratch stores register Src into scratch[N].
 type StoreScratch struct {
 	Src Register
@@ -292,6 +392,18 @@ func (a StoreScratch) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a StoreScratch) String() string {
+	switch a.Src {
+	case RegA:
+		return fmt.Sprintf("st M[%d]", a.N)
+	case RegX:
+		return fmt.Sprintf("stx M[%d]", a.N)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // ALUOpConstant executes A = A <Op> Val.
 type ALUOpConstant struct {
 	Op  ALUOp
@@ -306,6 +418,34 @@ func (a ALUOpConstant) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a ALUOpConstant) String() string {
+	switch a.Op {
+	case ALUOpAdd:
+		return fmt.Sprintf("add #%d", a.Val)
+	case ALUOpSub:
+		return fmt.Sprintf("sub #%d", a.Val)
+	case ALUOpMul:
+		return fmt.Sprintf("mul #%d", a.Val)
+	case ALUOpDiv:
+		return fmt.Sprintf("div #%d", a.Val)
+	case ALUOpMod:
+		return fmt.Sprintf("mod #%d", a.Val)
+	case ALUOpAnd:
+		return fmt.Sprintf("and #%d", a.Val)
+	case ALUOpOr:
+		return fmt.Sprintf("or #%d", a.Val)
+	case ALUOpXor:
+		return fmt.Sprintf("xor #%d", a.Val)
+	case ALUOpShiftLeft:
+		return fmt.Sprintf("lsh #%d", a.Val)
+	case ALUOpShiftRight:
+		return fmt.Sprintf("rsh #%d", a.Val)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // ALUOpX executes A = A <Op> X
 type ALUOpX struct {
 	Op ALUOp
@@ -318,6 +458,34 @@ func (a ALUOpX) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a ALUOpX) String() string {
+	switch a.Op {
+	case ALUOpAdd:
+		return "add x"
+	case ALUOpSub:
+		return "sub x"
+	case ALUOpMul:
+		return "mul x"
+	case ALUOpDiv:
+		return "div x"
+	case ALUOpMod:
+		return "mod x"
+	case ALUOpAnd:
+		return "and x"
+	case ALUOpOr:
+		return "or x"
+	case ALUOpXor:
+		return "xor x"
+	case ALUOpShiftLeft:
+		return "lsh x"
+	case ALUOpShiftRight:
+		return "rsh x"
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
 // NegateA executes A = -A.
 type NegateA struct{}
 
@@ -328,6 +496,11 @@ func (a NegateA) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a NegateA) String() string {
+	return fmt.Sprintf("neg")
+}
+
 // Jump skips the following Skip instructions in the program.
 type Jump struct {
 	Skip uint32
@@ -341,6 +514,11 @@ func (a Jump) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a Jump) String() string {
+	return fmt.Sprintf("ja %d", a.Skip)
+}
+
 // JumpIf skips the following Skip instructions in the program if A
 // <Cond> Val is true.
 type JumpIf struct {
@@ -388,6 +566,51 @@ func (a JumpIf) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a JumpIf) String() string {
+	switch a.Cond {
+	// K == A
+	case JumpEqual:
+		return conditionalJump(a, "jeq", "jneq")
+	// K != A
+	case JumpNotEqual:
+		return fmt.Sprintf("jneq #%d,%d", a.Val, a.SkipTrue)
+	// K > A
+	case JumpGreaterThan:
+		return conditionalJump(a, "jgt", "jle")
+	// K < A
+	case JumpLessThan:
+		return fmt.Sprintf("jlt #%d,%d", a.Val, a.SkipTrue)
+	// K >= A
+	case JumpGreaterOrEqual:
+		return conditionalJump(a, "jge", "jlt")
+	// K <= A
+	case JumpLessOrEqual:
+		return fmt.Sprintf("jle #%d,%d", a.Val, a.SkipTrue)
+	// K & A != 0
+	case JumpBitsSet:
+		if a.SkipFalse > 0 {
+			return fmt.Sprintf("jset #%d,%d,%d", a.Val, a.SkipTrue, a.SkipFalse)
+		}
+		return fmt.Sprintf("jset #%d,%d", a.Val, a.SkipTrue)
+	// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
+	case JumpBitsNotSet:
+		return JumpIf{Cond: JumpBitsSet, SkipTrue: a.SkipFalse, SkipFalse: a.SkipTrue, Val: a.Val}.String()
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+func conditionalJump(inst JumpIf, positiveJump, negativeJump string) string {
+	if inst.SkipTrue > 0 {
+		if inst.SkipFalse > 0 {
+			return fmt.Sprintf("%s #%d,%d,%d", positiveJump, inst.Val, inst.SkipTrue, inst.SkipFalse)
+		}
+		return fmt.Sprintf("%s #%d,%d", positiveJump, inst.Val, inst.SkipTrue)
+	}
+	return fmt.Sprintf("%s #%d,%d", negativeJump, inst.Val, inst.SkipFalse)
+}
+
 // RetA exits the BPF program, returning the value of register A.
 type RetA struct{}
 
@@ -398,6 +621,11 @@ func (a RetA) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a RetA) String() string {
+	return fmt.Sprintf("ret a")
+}
+
 // RetConstant exits the BPF program, returning a constant value.
 type RetConstant struct {
 	Val uint32
@@ -411,6 +639,11 @@ func (a RetConstant) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a RetConstant) String() string {
+	return fmt.Sprintf("ret #%d", a.Val)
+}
+
 // TXA copies the value of register X to register A.
 type TXA struct{}
 
@@ -421,6 +654,11 @@ func (a TXA) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a TXA) String() string {
+	return fmt.Sprintf("txa")
+}
+
 // TAX copies the value of register A to register X.
 type TAX struct{}
 
@@ -431,6 +669,11 @@ func (a TAX) Assemble() (RawInstruction, error) {
 	}, nil
 }
 
+// String returns the the instruction in assembler notation.
+func (a TAX) String() string {
+	return fmt.Sprintf("tax")
+}
+
 func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
 	var (
 		cls uint16

+ 351 - 0
bpf/instructions_test.go

@@ -5,6 +5,7 @@
 package bpf
 
 import (
+	"fmt"
 	"io/ioutil"
 	"reflect"
 	"strconv"
@@ -172,3 +173,353 @@ func TestAsmDisasm(t *testing.T) {
 		}
 	}
 }
+
+type InvalidInstruction struct{}
+
+func (a InvalidInstruction) Assemble() (RawInstruction, error) {
+	return RawInstruction{}, fmt.Errorf("Invalid Instruction")
+}
+
+func (a InvalidInstruction) String() string {
+	return fmt.Sprintf("unknown instruction: %#v", a)
+}
+
+func TestString(t *testing.T) {
+	testCases := []struct {
+		instruction Instruction
+		assembler   string
+	}{
+		{
+			instruction: LoadConstant{Dst: RegA, Val: 42},
+			assembler:   "ld #42",
+		},
+		{
+			instruction: LoadConstant{Dst: RegX, Val: 42},
+			assembler:   "ldx #42",
+		},
+		{
+			instruction: LoadConstant{Dst: 0xffff, Val: 42},
+			assembler:   "unknown instruction: bpf.LoadConstant{Dst:0xffff, Val:0x2a}",
+		},
+		{
+			instruction: LoadScratch{Dst: RegA, N: 3},
+			assembler:   "ld M[3]",
+		},
+		{
+			instruction: LoadScratch{Dst: RegX, N: 3},
+			assembler:   "ldx M[3]",
+		},
+		{
+			instruction: LoadScratch{Dst: 0xffff, N: 3},
+			assembler:   "unknown instruction: bpf.LoadScratch{Dst:0xffff, N:3}",
+		},
+		{
+			instruction: LoadAbsolute{Off: 42, Size: 1},
+			assembler:   "ldb [42]",
+		},
+		{
+			instruction: LoadAbsolute{Off: 42, Size: 2},
+			assembler:   "ldh [42]",
+		},
+		{
+			instruction: LoadAbsolute{Off: 42, Size: 4},
+			assembler:   "ld [42]",
+		},
+		{
+			instruction: LoadAbsolute{Off: 42, Size: -1},
+			assembler:   "unknown instruction: bpf.LoadAbsolute{Off:0x2a, Size:-1}",
+		},
+		{
+			instruction: LoadIndirect{Off: 42, Size: 1},
+			assembler:   "ldb [x + 42]",
+		},
+		{
+			instruction: LoadIndirect{Off: 42, Size: 2},
+			assembler:   "ldh [x + 42]",
+		},
+		{
+			instruction: LoadIndirect{Off: 42, Size: 4},
+			assembler:   "ld [x + 42]",
+		},
+		{
+			instruction: LoadIndirect{Off: 42, Size: -1},
+			assembler:   "unknown instruction: bpf.LoadIndirect{Off:0x2a, Size:-1}",
+		},
+		{
+			instruction: LoadMemShift{Off: 42},
+			assembler:   "ldx 4*([42]&0xf)",
+		},
+		{
+			instruction: LoadExtension{Num: ExtLen},
+			assembler:   "ld #len",
+		},
+		{
+			instruction: LoadExtension{Num: ExtProto},
+			assembler:   "ld #proto",
+		},
+		{
+			instruction: LoadExtension{Num: ExtType},
+			assembler:   "ld #type",
+		},
+		{
+			instruction: LoadExtension{Num: ExtPayloadOffset},
+			assembler:   "ld #poff",
+		},
+		{
+			instruction: LoadExtension{Num: ExtInterfaceIndex},
+			assembler:   "ld #ifidx",
+		},
+		{
+			instruction: LoadExtension{Num: ExtNetlinkAttr},
+			assembler:   "ld #nla",
+		},
+		{
+			instruction: LoadExtension{Num: ExtNetlinkAttrNested},
+			assembler:   "ld #nlan",
+		},
+		{
+			instruction: LoadExtension{Num: ExtMark},
+			assembler:   "ld #mark",
+		},
+		{
+			instruction: LoadExtension{Num: ExtQueue},
+			assembler:   "ld #queue",
+		},
+		{
+			instruction: LoadExtension{Num: ExtLinkLayerType},
+			assembler:   "ld #hatype",
+		},
+		{
+			instruction: LoadExtension{Num: ExtRXHash},
+			assembler:   "ld #rxhash",
+		},
+		{
+			instruction: LoadExtension{Num: ExtCPUID},
+			assembler:   "ld #cpu",
+		},
+		{
+			instruction: LoadExtension{Num: ExtVLANTag},
+			assembler:   "ld #vlan_tci",
+		},
+		{
+			instruction: LoadExtension{Num: ExtVLANTagPresent},
+			assembler:   "ld #vlan_avail",
+		},
+		{
+			instruction: LoadExtension{Num: ExtVLANProto},
+			assembler:   "ld #vlan_tpid",
+		},
+		{
+			instruction: LoadExtension{Num: ExtRand},
+			assembler:   "ld #rand",
+		},
+		{
+			instruction: LoadAbsolute{Off: 0xfffff038, Size: 4},
+			assembler:   "ld #rand",
+		},
+		{
+			instruction: LoadExtension{Num: 0xfff},
+			assembler:   "unknown instruction: bpf.LoadExtension{Num:4095}",
+		},
+		{
+			instruction: StoreScratch{Src: RegA, N: 3},
+			assembler:   "st M[3]",
+		},
+		{
+			instruction: StoreScratch{Src: RegX, N: 3},
+			assembler:   "stx M[3]",
+		},
+		{
+			instruction: StoreScratch{Src: 0xffff, N: 3},
+			assembler:   "unknown instruction: bpf.StoreScratch{Src:0xffff, N:3}",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpAdd, Val: 42},
+			assembler:   "add #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpSub, Val: 42},
+			assembler:   "sub #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpMul, Val: 42},
+			assembler:   "mul #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpDiv, Val: 42},
+			assembler:   "div #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpOr, Val: 42},
+			assembler:   "or #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpAnd, Val: 42},
+			assembler:   "and #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpShiftLeft, Val: 42},
+			assembler:   "lsh #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpShiftRight, Val: 42},
+			assembler:   "rsh #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpMod, Val: 42},
+			assembler:   "mod #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: ALUOpXor, Val: 42},
+			assembler:   "xor #42",
+		},
+		{
+			instruction: ALUOpConstant{Op: 0xffff, Val: 42},
+			assembler:   "unknown instruction: bpf.ALUOpConstant{Op:0xffff, Val:0x2a}",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpAdd},
+			assembler:   "add x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpSub},
+			assembler:   "sub x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpMul},
+			assembler:   "mul x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpDiv},
+			assembler:   "div x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpOr},
+			assembler:   "or x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpAnd},
+			assembler:   "and x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpShiftLeft},
+			assembler:   "lsh x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpShiftRight},
+			assembler:   "rsh x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpMod},
+			assembler:   "mod x",
+		},
+		{
+			instruction: ALUOpX{Op: ALUOpXor},
+			assembler:   "xor x",
+		},
+		{
+			instruction: ALUOpX{Op: 0xffff},
+			assembler:   "unknown instruction: bpf.ALUOpX{Op:0xffff}",
+		},
+		{
+			instruction: NegateA{},
+			assembler:   "neg",
+		},
+		{
+			instruction: Jump{Skip: 10},
+			assembler:   "ja 10",
+		},
+		{
+			instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
+			assembler:   "jeq #42,8,9",
+		},
+		{
+			instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8},
+			assembler:   "jeq #42,8",
+		},
+		{
+			instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipFalse: 8},
+			assembler:   "jneq #42,8",
+		},
+		{
+			instruction: JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
+			assembler:   "jneq #42,8",
+		},
+		{
+			instruction: JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
+			assembler:   "jlt #42,7",
+		},
+		{
+			instruction: JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
+			assembler:   "jle #42,6",
+		},
+		{
+			instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
+			assembler:   "jgt #42,4,5",
+		},
+		{
+			instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4},
+			assembler:   "jgt #42,4",
+		},
+		{
+			instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
+			assembler:   "jge #42,3,4",
+		},
+		{
+			instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3},
+			assembler:   "jge #42,3",
+		},
+		{
+			instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
+			assembler:   "jset #42,2,3",
+		},
+		{
+			instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2},
+			assembler:   "jset #42,2",
+		},
+		{
+			instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
+			assembler:   "jset #42,3,2",
+		},
+		{
+			instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2},
+			assembler:   "jset #42,0,2",
+		},
+		{
+			instruction: JumpIf{Cond: 0xffff, Val: 42, SkipTrue: 1, SkipFalse: 2},
+			assembler:   "unknown instruction: bpf.JumpIf{Cond:0xffff, Val:0x2a, SkipTrue:0x1, SkipFalse:0x2}",
+		},
+		{
+			instruction: TAX{},
+			assembler:   "tax",
+		},
+		{
+			instruction: TXA{},
+			assembler:   "txa",
+		},
+		{
+			instruction: RetA{},
+			assembler:   "ret a",
+		},
+		{
+			instruction: RetConstant{Val: 42},
+			assembler:   "ret #42",
+		},
+		// Invalid instruction
+		{
+			instruction: InvalidInstruction{},
+			assembler:   "unknown instruction: bpf.InvalidInstruction{}",
+		},
+	}
+
+	for _, testCase := range testCases {
+		if input, ok := testCase.instruction.(fmt.Stringer); ok {
+			got := input.String()
+			if got != testCase.assembler {
+				t.Errorf("String did not return expected assembler notation, expected: %s, got: %s", testCase.assembler, got)
+			}
+		} else {
+			t.Errorf("Instruction %#v is not a fmt.Stringer", testCase.instruction)
+		}
+	}
+}