github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/asm/opcode.go (about)

     1  package asm
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  //go:generate go run golang.org/x/tools/cmd/stringer@latest -output opcode_string.go -type=Class
     9  
    10  // Class of operations
    11  //
    12  //	msb      lsb
    13  //	+---+--+---+
    14  //	|  ??  |CLS|
    15  //	+---+--+---+
    16  type Class uint8
    17  
    18  const classMask OpCode = 0x07
    19  
    20  const (
    21  	// LdClass loads immediate values into registers.
    22  	// Also used for non-standard load operations from cBPF.
    23  	LdClass Class = 0x00
    24  	// LdXClass loads memory into registers.
    25  	LdXClass Class = 0x01
    26  	// StClass stores immediate values to memory.
    27  	StClass Class = 0x02
    28  	// StXClass stores registers to memory.
    29  	StXClass Class = 0x03
    30  	// ALUClass describes arithmetic operators.
    31  	ALUClass Class = 0x04
    32  	// JumpClass describes jump operators.
    33  	JumpClass Class = 0x05
    34  	// Jump32Class describes jump operators with 32-bit comparisons.
    35  	// Requires kernel 5.1.
    36  	Jump32Class Class = 0x06
    37  	// ALU64Class describes arithmetic operators in 64-bit mode.
    38  	ALU64Class Class = 0x07
    39  )
    40  
    41  // IsLoad checks if this is either LdClass or LdXClass.
    42  func (cls Class) IsLoad() bool {
    43  	return cls == LdClass || cls == LdXClass
    44  }
    45  
    46  // IsStore checks if this is either StClass or StXClass.
    47  func (cls Class) IsStore() bool {
    48  	return cls == StClass || cls == StXClass
    49  }
    50  
    51  func (cls Class) isLoadOrStore() bool {
    52  	return cls.IsLoad() || cls.IsStore()
    53  }
    54  
    55  // IsALU checks if this is either ALUClass or ALU64Class.
    56  func (cls Class) IsALU() bool {
    57  	return cls == ALUClass || cls == ALU64Class
    58  }
    59  
    60  // IsJump checks if this is either JumpClass or Jump32Class.
    61  func (cls Class) IsJump() bool {
    62  	return cls == JumpClass || cls == Jump32Class
    63  }
    64  
    65  func (cls Class) isJumpOrALU() bool {
    66  	return cls.IsJump() || cls.IsALU()
    67  }
    68  
    69  // OpCode represents a single operation.
    70  // It is not a 1:1 mapping to real eBPF opcodes.
    71  //
    72  // The encoding varies based on a 3-bit Class:
    73  //
    74  //	7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    75  //	           ???           | CLS
    76  //
    77  // For ALUClass and ALUCLass32:
    78  //
    79  //	7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    80  //	           OPC         |S| CLS
    81  //
    82  // For LdClass, LdXclass, StClass and StXClass:
    83  //
    84  //	7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    85  //	        0      | MDE |SIZ| CLS
    86  //
    87  // For JumpClass, Jump32Class:
    88  //
    89  //	7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    90  //	        0      |  OPC  |S| CLS
    91  type OpCode uint16
    92  
    93  // InvalidOpCode is returned by setters on OpCode
    94  const InvalidOpCode OpCode = 0xffff
    95  
    96  // bpfOpCode returns the actual BPF opcode.
    97  func (op OpCode) bpfOpCode() (byte, error) {
    98  	const opCodeMask = 0xff
    99  
   100  	if !valid(op, opCodeMask) {
   101  		return 0, fmt.Errorf("invalid opcode %x", op)
   102  	}
   103  
   104  	return byte(op & opCodeMask), nil
   105  }
   106  
   107  // rawInstructions returns the number of BPF instructions required
   108  // to encode this opcode.
   109  func (op OpCode) rawInstructions() int {
   110  	if op.IsDWordLoad() {
   111  		return 2
   112  	}
   113  	return 1
   114  }
   115  
   116  func (op OpCode) IsDWordLoad() bool {
   117  	return op == LoadImmOp(DWord)
   118  }
   119  
   120  // Class returns the class of operation.
   121  func (op OpCode) Class() Class {
   122  	return Class(op & classMask)
   123  }
   124  
   125  // Mode returns the mode for load and store operations.
   126  func (op OpCode) Mode() Mode {
   127  	if !op.Class().isLoadOrStore() {
   128  		return InvalidMode
   129  	}
   130  	return Mode(op & modeMask)
   131  }
   132  
   133  // Size returns the size for load and store operations.
   134  func (op OpCode) Size() Size {
   135  	if !op.Class().isLoadOrStore() {
   136  		return InvalidSize
   137  	}
   138  	return Size(op & sizeMask)
   139  }
   140  
   141  // Source returns the source for branch and ALU operations.
   142  func (op OpCode) Source() Source {
   143  	if !op.Class().isJumpOrALU() || op.ALUOp() == Swap {
   144  		return InvalidSource
   145  	}
   146  	return Source(op & sourceMask)
   147  }
   148  
   149  // ALUOp returns the ALUOp.
   150  func (op OpCode) ALUOp() ALUOp {
   151  	if !op.Class().IsALU() {
   152  		return InvalidALUOp
   153  	}
   154  	return ALUOp(op & aluMask)
   155  }
   156  
   157  // Endianness returns the Endianness for a byte swap instruction.
   158  func (op OpCode) Endianness() Endianness {
   159  	if op.ALUOp() != Swap {
   160  		return InvalidEndian
   161  	}
   162  	return Endianness(op & endianMask)
   163  }
   164  
   165  // JumpOp returns the JumpOp.
   166  // Returns InvalidJumpOp if it doesn't encode a jump.
   167  func (op OpCode) JumpOp() JumpOp {
   168  	if !op.Class().IsJump() {
   169  		return InvalidJumpOp
   170  	}
   171  
   172  	jumpOp := JumpOp(op & jumpMask)
   173  
   174  	// Some JumpOps are only supported by JumpClass, not Jump32Class.
   175  	if op.Class() == Jump32Class && (jumpOp == Exit || jumpOp == Call) {
   176  		return InvalidJumpOp
   177  	}
   178  
   179  	return jumpOp
   180  }
   181  
   182  // SetMode sets the mode on load and store operations.
   183  //
   184  // Returns InvalidOpCode if op is of the wrong class.
   185  func (op OpCode) SetMode(mode Mode) OpCode {
   186  	if !op.Class().isLoadOrStore() || !valid(OpCode(mode), modeMask) {
   187  		return InvalidOpCode
   188  	}
   189  	return (op & ^modeMask) | OpCode(mode)
   190  }
   191  
   192  // SetSize sets the size on load and store operations.
   193  //
   194  // Returns InvalidOpCode if op is of the wrong class.
   195  func (op OpCode) SetSize(size Size) OpCode {
   196  	if !op.Class().isLoadOrStore() || !valid(OpCode(size), sizeMask) {
   197  		return InvalidOpCode
   198  	}
   199  	return (op & ^sizeMask) | OpCode(size)
   200  }
   201  
   202  // SetSource sets the source on jump and ALU operations.
   203  //
   204  // Returns InvalidOpCode if op is of the wrong class.
   205  func (op OpCode) SetSource(source Source) OpCode {
   206  	if !op.Class().isJumpOrALU() || !valid(OpCode(source), sourceMask) {
   207  		return InvalidOpCode
   208  	}
   209  	return (op & ^sourceMask) | OpCode(source)
   210  }
   211  
   212  // SetALUOp sets the ALUOp on ALU operations.
   213  //
   214  // Returns InvalidOpCode if op is of the wrong class.
   215  func (op OpCode) SetALUOp(alu ALUOp) OpCode {
   216  	if !op.Class().IsALU() || !valid(OpCode(alu), aluMask) {
   217  		return InvalidOpCode
   218  	}
   219  	return (op & ^aluMask) | OpCode(alu)
   220  }
   221  
   222  // SetJumpOp sets the JumpOp on jump operations.
   223  //
   224  // Returns InvalidOpCode if op is of the wrong class.
   225  func (op OpCode) SetJumpOp(jump JumpOp) OpCode {
   226  	if !op.Class().IsJump() || !valid(OpCode(jump), jumpMask) {
   227  		return InvalidOpCode
   228  	}
   229  
   230  	newOp := (op & ^jumpMask) | OpCode(jump)
   231  
   232  	// Check newOp is legal.
   233  	if newOp.JumpOp() == InvalidJumpOp {
   234  		return InvalidOpCode
   235  	}
   236  
   237  	return newOp
   238  }
   239  
   240  func (op OpCode) String() string {
   241  	var f strings.Builder
   242  
   243  	switch class := op.Class(); {
   244  	case class.isLoadOrStore():
   245  		f.WriteString(strings.TrimSuffix(class.String(), "Class"))
   246  
   247  		mode := op.Mode()
   248  		f.WriteString(strings.TrimSuffix(mode.String(), "Mode"))
   249  
   250  		switch op.Size() {
   251  		case DWord:
   252  			f.WriteString("DW")
   253  		case Word:
   254  			f.WriteString("W")
   255  		case Half:
   256  			f.WriteString("H")
   257  		case Byte:
   258  			f.WriteString("B")
   259  		}
   260  
   261  	case class.IsALU():
   262  		if op.ALUOp() == Swap && op.Class() == ALU64Class {
   263  			// B to make BSwap, uncontitional byte swap
   264  			f.WriteString("B")
   265  		}
   266  
   267  		f.WriteString(op.ALUOp().String())
   268  
   269  		if op.ALUOp() == Swap {
   270  			if op.Class() == ALUClass {
   271  				// Width for Endian is controlled by Constant
   272  				f.WriteString(op.Endianness().String())
   273  			}
   274  		} else {
   275  			f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
   276  
   277  			if class == ALUClass {
   278  				f.WriteString("32")
   279  			}
   280  		}
   281  
   282  	case class.IsJump():
   283  		f.WriteString(op.JumpOp().String())
   284  
   285  		if class == Jump32Class {
   286  			f.WriteString("32")
   287  		}
   288  
   289  		if jop := op.JumpOp(); jop != Exit && jop != Call && jop != Ja {
   290  			f.WriteString(strings.TrimSuffix(op.Source().String(), "Source"))
   291  		}
   292  
   293  	default:
   294  		fmt.Fprintf(&f, "OpCode(%#x)", uint8(op))
   295  	}
   296  
   297  	return f.String()
   298  }
   299  
   300  // valid returns true if all bits in value are covered by mask.
   301  func valid(value, mask OpCode) bool {
   302  	return value & ^mask == 0
   303  }