// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package bpf import "fmt" // An Instruction is one instruction executed by the BPF virtual // machine. type Instruction interface { // Assemble assembles the Instruction into a RawInstruction. Assemble() (RawInstruction, error) } // A RawInstruction is a raw BPF virtual machine instruction. type RawInstruction struct { // Operation to execute. Op uint16 // For conditional jump instructions, the number of instructions // to skip if the condition is true/false. Jt uint8 Jf uint8 // Constant parameter. The meaning depends on the Op. K uint32 } // 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 { switch ri.Op & opMaskCls { case opClsLoadA, opClsLoadX: reg := Register(ri.Op & opMaskLoadDest) sz := 0 switch ri.Op & opMaskLoadWidth { case opLoadWidth4: sz = 4 case opLoadWidth2: sz = 2 case opLoadWidth1: sz = 1 default: return ri } switch ri.Op & opMaskLoadMode { case opAddrModeImmediate: if sz != 4 { return ri } return LoadConstant{Dst: reg, Val: ri.K} case opAddrModeScratch: if sz != 4 || ri.K > 15 { return ri } return LoadScratch{Dst: reg, N: int(ri.K)} case opAddrModeAbsolute: if ri.K > extOffset+0xffffffff { return LoadExtension{Num: Extension(-extOffset + ri.K)} } return LoadAbsolute{Size: sz, Off: ri.K} case opAddrModeIndirect: return LoadIndirect{Size: sz, Off: ri.K} case opAddrModePacketLen: if sz != 4 { return ri } return LoadExtension{Num: ExtLen} case opAddrModeMemShift: return LoadMemShift{Off: ri.K} default: return ri } case opClsStoreA: if ri.Op != opClsStoreA || ri.K > 15 { return ri } return StoreScratch{Src: RegA, N: int(ri.K)} case opClsStoreX: if ri.Op != opClsStoreX || ri.K > 15 { return ri } return StoreScratch{Src: RegX, N: int(ri.K)} case opClsALU: switch op := ALUOp(ri.Op & opMaskOperator); op { case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor: switch operand := opOperand(ri.Op & opMaskOperand); operand { case opOperandX: return ALUOpX{Op: op} case opOperandConstant: return ALUOpConstant{Op: op, Val: ri.K} default: return ri } case aluOpNeg: return NegateA{} default: return ri } case opClsJump: switch op := jumpOp(ri.Op & opMaskOperator); op { case opJumpAlways: return Jump{Skip: ri.K} case opJumpEqual, opJumpGT, opJumpGE, opJumpSet: cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf) switch operand := opOperand(ri.Op & opMaskOperand); operand { case opOperandX: return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse} case opOperandConstant: return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse} default: return ri } default: return ri } case opClsReturn: switch ri.Op { case opClsReturn | opRetSrcA: return RetA{} case opClsReturn | opRetSrcConstant: return RetConstant{Val: ri.K} default: return ri } case opClsMisc: switch ri.Op { case opClsMisc | opMiscTAX: return TAX{} case opClsMisc | opMiscTXA: return TXA{} default: return ri } default: panic("unreachable") // switch is exhaustive on the bit pattern } } func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) { var test JumpTest // Decode "fake" jump conditions that don't appear in machine code // Ensures the Assemble -> Disassemble stage recreates the same instructions // See https://github.com/golang/go/issues/18470 if skipTrue == 0 { switch op { case opJumpEqual: test = JumpNotEqual case opJumpGT: test = JumpLessOrEqual case opJumpGE: test = JumpLessThan case opJumpSet: test = JumpBitsNotSet } return test, skipFalse, 0 } switch op { case opJumpEqual: test = JumpEqual case opJumpGT: test = JumpGreaterThan case opJumpGE: test = JumpGreaterOrEqual case opJumpSet: test = JumpBitsSet } return test, skipTrue, skipFalse } // LoadConstant loads Val into register Dst. type LoadConstant struct { Dst Register Val uint32 } // Assemble implements the Instruction Assemble method. func (a LoadConstant) Assemble() (RawInstruction, error) { return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val) } // String returns 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 N int // 0-15 } // Assemble implements the Instruction Assemble method. func (a LoadScratch) Assemble() (RawInstruction, error) { if a.N < 0 || a.N > 15 { return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N) } return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N)) } // String returns 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 { Off uint32 Size int // 1, 2 or 4 } // Assemble implements the Instruction Assemble method. func (a LoadAbsolute) Assemble() (RawInstruction, error) { return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off) } // String returns 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 { Off uint32 Size int // 1, 2 or 4 } // Assemble implements the Instruction Assemble method. func (a LoadIndirect) Assemble() (RawInstruction, error) { return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off) } // String returns 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. // // This instruction is mainly useful to load into X the length of an // IPv4 packet header in a single instruction, rather than have to do // the arithmetic on the header's first byte by hand. type LoadMemShift struct { Off uint32 } // Assemble implements the Instruction Assemble method. func (a LoadMemShift) Assemble() (RawInstruction, error) { return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off) } // String returns 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 { Num Extension } // Assemble implements the Instruction Assemble method. func (a LoadExtension) Assemble() (RawInstruction, error) { if a.Num == ExtLen { return assembleLoad(RegA, 4, opAddrModePacketLen, 0) } return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num)) } // String returns 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 N int // 0-15 } // Assemble implements the Instruction Assemble method. func (a StoreScratch) Assemble() (RawInstruction, error) { if a.N < 0 || a.N > 15 { return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N) } var op uint16 switch a.Src { case RegA: op = opClsStoreA case RegX: op = opClsStoreX default: return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src) } return RawInstruction{ Op: op, K: uint32(a.N), }, nil } // String returns 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 Val uint32 } // Assemble implements the Instruction Assemble method. func (a ALUOpConstant) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op), K: a.Val, }, nil } // String returns 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 } // Assemble implements the Instruction Assemble method. func (a ALUOpX) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsALU | uint16(opOperandX) | uint16(a.Op), }, nil } // String returns 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{} // Assemble implements the Instruction Assemble method. func (a NegateA) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsALU | uint16(aluOpNeg), }, nil } // String returns 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 } // Assemble implements the Instruction Assemble method. func (a Jump) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsJump | uint16(opJumpAlways), K: a.Skip, }, nil } // String returns 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 { Cond JumpTest Val uint32 SkipTrue uint8 SkipFalse uint8 } // Assemble implements the Instruction Assemble method. func (a JumpIf) Assemble() (RawInstruction, error) { return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse) } // String returns the instruction in assembler notation. func (a JumpIf) String() string { return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse) } // JumpIfX skips the following Skip instructions in the program if A // <Cond> X is true. type JumpIfX struct { Cond JumpTest SkipTrue uint8 SkipFalse uint8 } // Assemble implements the Instruction Assemble method. func (a JumpIfX) Assemble() (RawInstruction, error) { return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse) } // String returns the instruction in assembler notation. func (a JumpIfX) String() string { return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse) } // jumpToRaw assembles a jump instruction into a RawInstruction func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) { var ( cond jumpOp flip bool ) switch test { case JumpEqual: cond = opJumpEqual case JumpNotEqual: cond, flip = opJumpEqual, true case JumpGreaterThan: cond = opJumpGT case JumpLessThan: cond, flip = opJumpGE, true case JumpGreaterOrEqual: cond = opJumpGE case JumpLessOrEqual: cond, flip = opJumpGT, true case JumpBitsSet: cond = opJumpSet case JumpBitsNotSet: cond, flip = opJumpSet, true default: return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test) } jt, jf := skipTrue, skipFalse if flip { jt, jf = jf, jt } return RawInstruction{ Op: opClsJump | uint16(cond) | uint16(operand), Jt: jt, Jf: jf, K: k, }, nil } // jumpToString converts a jump instruction to assembler notation func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string { switch cond { // K == A case JumpEqual: return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq") // K != A case JumpNotEqual: return fmt.Sprintf("jneq %s,%d", operand, skipTrue) // K > A case JumpGreaterThan: return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle") // K < A case JumpLessThan: return fmt.Sprintf("jlt %s,%d", operand, skipTrue) // K >= A case JumpGreaterOrEqual: return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt") // K <= A case JumpLessOrEqual: return fmt.Sprintf("jle %s,%d", operand, skipTrue) // K & A != 0 case JumpBitsSet: if skipFalse > 0 { return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse) } return fmt.Sprintf("jset %s,%d", operand, skipTrue) // K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips case JumpBitsNotSet: return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue) default: return fmt.Sprintf("unknown JumpTest %#v", cond) } } func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string { if skipTrue > 0 { if skipFalse > 0 { return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse) } return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue) } return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse) } // RetA exits the BPF program, returning the value of register A. type RetA struct{} // Assemble implements the Instruction Assemble method. func (a RetA) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsReturn | opRetSrcA, }, nil } // String returns 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 } // Assemble implements the Instruction Assemble method. func (a RetConstant) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsReturn | opRetSrcConstant, K: a.Val, }, nil } // String returns 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{} // Assemble implements the Instruction Assemble method. func (a TXA) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsMisc | opMiscTXA, }, nil } // String returns 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{} // Assemble implements the Instruction Assemble method. func (a TAX) Assemble() (RawInstruction, error) { return RawInstruction{ Op: opClsMisc | opMiscTAX, }, nil } // String returns 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 sz uint16 ) switch dst { case RegA: cls = opClsLoadA case RegX: cls = opClsLoadX default: return RawInstruction{}, fmt.Errorf("invalid target register %v", dst) } switch loadSize { case 1: sz = opLoadWidth1 case 2: sz = opLoadWidth2 case 4: sz = opLoadWidth4 default: return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz) } return RawInstruction{ Op: cls | sz | mode, K: k, }, nil }