Extended Berkeley Packet Filter (eBPF) assembler and virtual machine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

256 lines
6.0 KiB

package ebpf
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
type Instruction interface {
// Assemble assembles the Instruction into a RawInstruction.
Assemble() (RawInstruction, error)
// String returns the assembly.
String() string
}
// A RawInstruction is a raw BPF virtual machine instruction.
type RawInstruction struct {
// Operation to execute.
Op Opcode
Dst, Src Register
Offset int16
Immediate uint64
}
// Assemble implements the Instruction Assemble method.
func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
// Disassemble parses ri into an Instruction and returns it. If ri is
// not recognized by this package, ri itself is returned.
func (ri RawInstruction) Disassemble() Instruction {
// log.Printf("op class %#02x", ri.Op&opMaskClass)
switch class := ri.Op & opMaskClass; class {
case opClassLoad, opClassLoadX:
var size uint8
switch ri.Op & opMaskSize {
case sizeWord:
size = 4
case sizeDoubleWord:
size = 8
case sizeHalfWord:
size = 2
case sizeByte:
size = 1
default:
return ri
}
switch ri.Op & opMaskMode {
case modeImmediate: // 0x00
if class == opClassLoad {
if size != 4 && size != 8 {
return ri
}
return LoadConstant{Dst: ri.Dst, Size: size, Value: ri.Immediate}
}
case modeAbsolute: // 0x20
if class == opClassLoad {
return LoadAbsolute{
Dst: ri.Dst,
Src: ri.Src,
Value: ri.Immediate,
Size: size,
}
}
case modeIndirect: // 0x40
if class == opClassLoad {
return LoadIndirect{
Dst: ri.Dst,
Src: ri.Src,
Value: ri.Immediate,
Size: size,
}
}
case modeMemory: // 0x60
if class == opClassLoadX {
return LoadRegister{
Dst: ri.Dst,
Src: ri.Src,
Offset: ri.Offset,
Size: size,
}
}
case modeExclusiveAdd: // 0xc0
}
case opClassStore, opClassStoreX:
var size uint8
switch ri.Op & opMaskSize {
case sizeWord:
size = 4
case sizeDoubleWord:
size = 8
case sizeHalfWord:
size = 2
case sizeByte:
size = 1
default:
return ri
}
switch ri.Op & opMaskMode {
case modeMemory: // 0x60
if class == opClassStore {
return StoreImmediate{
Dst: ri.Dst,
Offset: ri.Offset,
Value: uint32(ri.Immediate),
Size: size,
}
}
return StoreRegister{
Dst: ri.Dst,
Src: ri.Src,
Offset: ri.Offset,
Size: size,
}
}
case opClassALU:
switch op := ALUOp(ri.Op & opMaskOperand); op {
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor, ALUOpMove, ALUOpArithmicShiftRight:
switch ri.Op & opMaskSource {
case aluSourceImmediate:
return ALUOpConstant{
Op: op,
Dst: ri.Dst,
Value: uint32(ri.Immediate),
}
case aluSourceX:
return ALUOpRegister{
Op: op,
Dst: ri.Dst,
Src: ri.Src,
}
}
case aluOpNegate:
return Negate{ri.Dst}
case aluOpByteSwap:
var byteOrder binary.ByteOrder = binary.LittleEndian
if ri.Op&opMaskSource == aluSourceX {
byteOrder = binary.BigEndian
}
return ByteSwap{
Dst: ri.Dst,
ByteOrder: byteOrder,
Size: uint8(ri.Immediate),
}
}
case opClassALU64:
switch op := ALUOp(ri.Op & opMaskOperand); op {
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor, ALUOpMove, ALUOpArithmicShiftRight:
switch ri.Op & opMaskSource {
case aluSourceImmediate:
return ALU64OpConstant{
Op: op,
Dst: ri.Dst,
Value: uint32(ri.Immediate),
}
case aluSourceX:
return ALU64OpRegister{
Op: op,
Dst: ri.Dst,
Src: ri.Src,
}
}
case aluOpNegate:
return Negate64{ri.Dst}
}
case opClassJump:
switch op := ri.Op & 0xf0; op {
case jumpOp:
return Jump{uint16(ri.Offset)}
case jumpOpEqual, jumpOpGreater, jumpOpGreaterOrEqual, jumpOpSet, jumpOpNotEqual,
jumpOpSignedGreater, jumpOpSignedGreaterOrEqual, jumpOpLess, jumpOpLessOrEqual,
jumpOpSignedLess, jumpOpSignedLessOrEqual:
cond := jumpOpToTest(op)
if ri.Op&0x08 == jumpSourceImmediate {
return JumpIf{
Cond: cond,
Dst: ri.Dst,
Value: uint32(ri.Immediate),
Offset: uint16(ri.Offset),
}
}
return JumpIfX{
Cond: cond,
Dst: ri.Dst,
Src: ri.Src,
Offset: uint16(ri.Offset),
}
case jumpOpExit:
return Exit{}
case jumpOpCall:
return Call(ri.Immediate)
}
}
return ri
}
func (ri RawInstruction) String() string {
return fmt.Sprintf("# invalid opcode = %#02x, class = %#02x", ri.Op, ri.Op&opMaskClass)
}
// Disassemble a slice of bytes containing an eBPF program.
func Disassemble(data []byte) (Program, error) {
if l := len(data); l%8 != 0 {
return nil, fmt.Errorf("ebpf: raw data size %d does not align with 64-bit boundary", l)
}
return DisassembleReader(bytes.NewBuffer(data))
}
// DisassembleReader disassembles an eBPF program from reader using the default byte order.
func DisassembleReader(r io.Reader) (Program, error) {
return DisassembleReaderOrder(r, binary.LittleEndian)
}
type marshaledInstruction struct {
Op Opcode
DstSrc uint8
Offset int16
Immediate uint32
}
// DisassembleReaderOrder is like DisassembleReader with a custom byte order.
func DisassembleReaderOrder(r io.Reader, byteOrder binary.ByteOrder) (Program, error) {
var (
out Program
)
for {
var load marshaledInstruction
if err := binary.Read(r, byteOrder, &load); err == io.EOF {
break
} else if err != nil {
return nil, err
}
ri := RawInstruction{
Op: load.Op,
Dst: Register(load.DstSrc & 0x0f),
Src: Register(load.DstSrc >> 4),
Offset: load.Offset,
Immediate: uint64(load.Immediate),
}
if ri.Op == 0x18 {
// LDDW takes two opcodes
var next marshaledInstruction
if err := binary.Read(r, byteOrder, &next); err != nil {
return nil, err
}
ri.Immediate |= uint64(next.Immediate) << 32
}
out = append(out, ri.Disassemble())
}
return out, nil
}