bpf: support JumpIf on RegX instead of K
Add a JumpIfX instruction which implements conditional jumps using
RegA and RegX. This is in addition to the pre-existing JumpIf
instruction which uses RegA and K.
This instruction / addressing mode is not mentionned in the original BPF
paper, but is supported by tools like bpf_asm, and has recently been
added to the kernel's filter.txt.
Simplify some of the parsing logic, and add a separate helper for
checking for "fake" JumpIfs.
Add JumpIfX support to the BPF vm.
Update testdata with JumpIfX instructions, and add tests
for both the assembler/disassembler and vm.
Fixes golang/go#27814
Change-Id: I0c3f6ac7eb5b4cd4d9c5af8784ee2e8d25195a0a
GitHub-Last-Rev: 39a88165b2d3253c37db4b0e303d862b60dc37c9
GitHub-Pull-Request: golang/net#20
Reviewed-on: https://go-review.googlesource.com/c/136895
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/bpf/constants.go b/bpf/constants.go
index b89ca35..12f3ee8 100644
--- a/bpf/constants.go
+++ b/bpf/constants.go
@@ -38,6 +38,7 @@
type JumpTest uint16
// Supported operators for conditional jumps.
+// K can be RegX for JumpIfX
const (
// K == A
JumpEqual JumpTest = iota
@@ -134,12 +135,9 @@
opMaskLoadDest = 0x01
opMaskLoadWidth = 0x18
opMaskLoadMode = 0xe0
- // opClsALU
- opMaskOperandSrc = 0x08
- opMaskOperator = 0xf0
- // opClsJump
- opMaskJumpConst = 0x0f
- opMaskJumpCond = 0xf0
+ // opClsALU & opClsJump
+ opMaskOperand = 0x08
+ opMaskOperator = 0xf0
)
const (
@@ -192,15 +190,21 @@
opLoadWidth1
)
-// Operator defined by ALUOp*
+// Operand for ALU and Jump instructions
+type opOperand uint16
+// Supported operand sources.
const (
- opALUSrcConstant uint16 = iota << 3
- opALUSrcX
+ opOperandConstant opOperand = iota << 3
+ opOperandX
)
+// An jumpOp is a conditional jump condition.
+type jumpOp uint16
+
+// Supported jump conditions.
const (
- opJumpAlways = iota << 4
+ opJumpAlways jumpOp = iota << 4
opJumpEqual
opJumpGT
opJumpGE
diff --git a/bpf/instructions.go b/bpf/instructions.go
index f9dc0e8..3cffcaa 100644
--- a/bpf/instructions.go
+++ b/bpf/instructions.go
@@ -89,10 +89,14 @@
case opClsALU:
switch op := ALUOp(ri.Op & opMaskOperator); op {
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
- if ri.Op&opMaskOperandSrc != 0 {
+ 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
}
- return ALUOpConstant{Op: op, Val: ri.K}
case aluOpNeg:
return NegateA{}
default:
@@ -100,63 +104,18 @@
}
case opClsJump:
- if ri.Op&opMaskJumpConst != opClsJump {
- return ri
- }
- switch ri.Op & opMaskJumpCond {
+ switch op := jumpOp(ri.Op & opMaskOperator); op {
case opJumpAlways:
return Jump{Skip: ri.K}
- case opJumpEqual:
- if ri.Jt == 0 {
- return JumpIf{
- Cond: JumpNotEqual,
- Val: ri.K,
- SkipTrue: ri.Jf,
- SkipFalse: 0,
- }
- }
- return JumpIf{
- Cond: JumpEqual,
- Val: ri.K,
- SkipTrue: ri.Jt,
- SkipFalse: ri.Jf,
- }
- case opJumpGT:
- if ri.Jt == 0 {
- return JumpIf{
- Cond: JumpLessOrEqual,
- Val: ri.K,
- SkipTrue: ri.Jf,
- SkipFalse: 0,
- }
- }
- return JumpIf{
- Cond: JumpGreaterThan,
- Val: ri.K,
- SkipTrue: ri.Jt,
- SkipFalse: ri.Jf,
- }
- case opJumpGE:
- if ri.Jt == 0 {
- return JumpIf{
- Cond: JumpLessThan,
- Val: ri.K,
- SkipTrue: ri.Jf,
- SkipFalse: 0,
- }
- }
- return JumpIf{
- Cond: JumpGreaterOrEqual,
- Val: ri.K,
- SkipTrue: ri.Jt,
- SkipFalse: ri.Jf,
- }
- case opJumpSet:
- return JumpIf{
- Cond: JumpBitsSet,
- Val: ri.K,
- SkipTrue: ri.Jt,
- SkipFalse: ri.Jf,
+ 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
@@ -187,6 +146,41 @@
}
}
+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
@@ -413,7 +407,7 @@
// Assemble implements the Instruction Assemble method.
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
return RawInstruction{
- Op: opClsALU | opALUSrcConstant | uint16(a.Op),
+ Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op),
K: a.Val,
}, nil
}
@@ -454,7 +448,7 @@
// Assemble implements the Instruction Assemble method.
func (a ALUOpX) Assemble() (RawInstruction, error) {
return RawInstruction{
- Op: opClsALU | opALUSrcX | uint16(a.Op),
+ Op: opClsALU | uint16(opOperandX) | uint16(a.Op),
}, nil
}
@@ -509,7 +503,7 @@
// Assemble implements the Instruction Assemble method.
func (a Jump) Assemble() (RawInstruction, error) {
return RawInstruction{
- Op: opClsJump | opJumpAlways,
+ Op: opClsJump | uint16(opJumpAlways),
K: a.Skip,
}, nil
}
@@ -530,11 +524,39 @@
// 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 uint16
+ cond jumpOp
flip bool
)
- switch a.Cond {
+ switch test {
case JumpEqual:
cond = opJumpEqual
case JumpNotEqual:
@@ -552,63 +574,63 @@
case JumpBitsNotSet:
cond, flip = opJumpSet, true
default:
- return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", a.Cond)
+ return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test)
}
- jt, jf := a.SkipTrue, a.SkipFalse
+ jt, jf := skipTrue, skipFalse
if flip {
jt, jf = jf, jt
}
return RawInstruction{
- Op: opClsJump | cond,
+ Op: opClsJump | uint16(cond) | uint16(operand),
Jt: jt,
Jf: jf,
- K: a.Val,
+ K: k,
}, nil
}
-// String returns the instruction in assembler notation.
-func (a JumpIf) String() string {
- switch a.Cond {
+// 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(a, "jeq", "jneq")
+ return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq")
// K != A
case JumpNotEqual:
- return fmt.Sprintf("jneq #%d,%d", a.Val, a.SkipTrue)
+ return fmt.Sprintf("jneq %s,%d", operand, skipTrue)
// K > A
case JumpGreaterThan:
- return conditionalJump(a, "jgt", "jle")
+ return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle")
// K < A
case JumpLessThan:
- return fmt.Sprintf("jlt #%d,%d", a.Val, a.SkipTrue)
+ return fmt.Sprintf("jlt %s,%d", operand, skipTrue)
// K >= A
case JumpGreaterOrEqual:
- return conditionalJump(a, "jge", "jlt")
+ return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt")
// K <= A
case JumpLessOrEqual:
- return fmt.Sprintf("jle #%d,%d", a.Val, a.SkipTrue)
+ return fmt.Sprintf("jle %s,%d", operand, skipTrue)
// K & A != 0
case JumpBitsSet:
- if a.SkipFalse > 0 {
- return fmt.Sprintf("jset #%d,%d,%d", a.Val, a.SkipTrue, a.SkipFalse)
+ if skipFalse > 0 {
+ return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse)
}
- return fmt.Sprintf("jset #%d,%d", a.Val, a.SkipTrue)
+ 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 JumpIf{Cond: JumpBitsSet, SkipTrue: a.SkipFalse, SkipFalse: a.SkipTrue, Val: a.Val}.String()
+ return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue)
default:
- return fmt.Sprintf("unknown instruction: %#v", a)
+ return fmt.Sprintf("unknown JumpTest %#v", cond)
}
}
-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)
+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 #%d,%d", positiveJump, inst.Val, inst.SkipTrue)
+ return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue)
}
- return fmt.Sprintf("%s #%d,%d", negativeJump, inst.Val, inst.SkipFalse)
+ return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse)
}
// RetA exits the BPF program, returning the value of register A.
diff --git a/bpf/instructions_test.go b/bpf/instructions_test.go
index dde474a..69b25c5 100644
--- a/bpf/instructions_test.go
+++ b/bpf/instructions_test.go
@@ -64,14 +64,22 @@
NegateA{},
- Jump{Skip: 10},
- JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
- JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
- JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
- JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
- JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
- JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
- JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
+ Jump{Skip: 17},
+ JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 15, SkipFalse: 16},
+ JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 15},
+ JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 14},
+ JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 13},
+ JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 11, SkipFalse: 12},
+ JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 10, SkipFalse: 11},
+ JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 9, SkipFalse: 10},
+
+ JumpIfX{Cond: JumpEqual, SkipTrue: 8, SkipFalse: 9},
+ JumpIfX{Cond: JumpNotEqual, SkipTrue: 8},
+ JumpIfX{Cond: JumpLessThan, SkipTrue: 7},
+ JumpIfX{Cond: JumpLessOrEqual, SkipTrue: 6},
+ JumpIfX{Cond: JumpGreaterThan, SkipTrue: 4, SkipFalse: 5},
+ JumpIfX{Cond: JumpGreaterOrEqual, SkipTrue: 3, SkipFalse: 4},
+ JumpIfX{Cond: JumpBitsSet, SkipTrue: 2, SkipFalse: 3},
TAX{},
TXA{},
@@ -487,7 +495,67 @@
},
{
instruction: JumpIf{Cond: 0xffff, Val: 42, SkipTrue: 1, SkipFalse: 2},
- assembler: "unknown instruction: bpf.JumpIf{Cond:0xffff, Val:0x2a, SkipTrue:0x1, SkipFalse:0x2}",
+ assembler: "unknown JumpTest 0xffff",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpEqual, SkipTrue: 8, SkipFalse: 9},
+ assembler: "jeq x,8,9",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpEqual, SkipTrue: 8},
+ assembler: "jeq x,8",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpEqual, SkipFalse: 8},
+ assembler: "jneq x,8",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpNotEqual, SkipTrue: 8},
+ assembler: "jneq x,8",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpLessThan, SkipTrue: 7},
+ assembler: "jlt x,7",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpLessOrEqual, SkipTrue: 6},
+ assembler: "jle x,6",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpGreaterThan, SkipTrue: 4, SkipFalse: 5},
+ assembler: "jgt x,4,5",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpGreaterThan, SkipTrue: 4},
+ assembler: "jgt x,4",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpGreaterOrEqual, SkipTrue: 3, SkipFalse: 4},
+ assembler: "jge x,3,4",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpGreaterOrEqual, SkipTrue: 3},
+ assembler: "jge x,3",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpBitsSet, SkipTrue: 2, SkipFalse: 3},
+ assembler: "jset x,2,3",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpBitsSet, SkipTrue: 2},
+ assembler: "jset x,2",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpBitsNotSet, SkipTrue: 2, SkipFalse: 3},
+ assembler: "jset x,3,2",
+ },
+ {
+ instruction: JumpIfX{Cond: JumpBitsNotSet, SkipTrue: 2},
+ assembler: "jset x,0,2",
+ },
+ {
+ instruction: JumpIfX{Cond: 0xffff, SkipTrue: 1, SkipFalse: 2},
+ assembler: "unknown JumpTest 0xffff",
},
{
instruction: TAX{},
diff --git a/bpf/testdata/all_instructions.bpf b/bpf/testdata/all_instructions.bpf
index f871440..929eb25 100644
--- a/bpf/testdata/all_instructions.bpf
+++ b/bpf/testdata/all_instructions.bpf
@@ -1 +1 @@
-50,0 0 0 42,1 0 0 42,96 0 0 3,97 0 0 3,48 0 0 42,40 0 0 42,32 0 0 42,80 0 0 42,72 0 0 42,64 0 0 42,177 0 0 42,128 0 0 0,32 0 0 4294963200,32 0 0 4294963204,32 0 0 4294963256,2 0 0 3,3 0 0 3,4 0 0 42,20 0 0 42,36 0 0 42,52 0 0 42,68 0 0 42,84 0 0 42,100 0 0 42,116 0 0 42,148 0 0 42,164 0 0 42,12 0 0 0,28 0 0 0,44 0 0 0,60 0 0 0,76 0 0 0,92 0 0 0,108 0 0 0,124 0 0 0,156 0 0 0,172 0 0 0,132 0 0 0,5 0 0 10,21 8 9 42,21 0 8 42,53 0 7 42,37 0 6 42,37 4 5 42,53 3 4 42,69 2 3 42,7 0 0 0,135 0 0 0,22 0 0 0,6 0 0 0,
+57,0 0 0 42,1 0 0 42,96 0 0 3,97 0 0 3,48 0 0 42,40 0 0 42,32 0 0 42,80 0 0 42,72 0 0 42,64 0 0 42,177 0 0 42,128 0 0 0,32 0 0 4294963200,32 0 0 4294963204,32 0 0 4294963256,2 0 0 3,3 0 0 3,4 0 0 42,20 0 0 42,36 0 0 42,52 0 0 42,68 0 0 42,84 0 0 42,100 0 0 42,116 0 0 42,148 0 0 42,164 0 0 42,12 0 0 0,28 0 0 0,44 0 0 0,60 0 0 0,76 0 0 0,92 0 0 0,108 0 0 0,124 0 0 0,156 0 0 0,172 0 0 0,132 0 0 0,5 0 0 17,21 15 16 42,21 0 15 42,53 0 14 42,37 0 13 42,37 11 12 42,53 10 11 42,69 9 10 42,29 8 9 0,29 0 8 0,61 0 7 0,45 0 6 0,45 4 5 0,61 3 4 0,77 2 3 0,7 0 0 0,135 0 0 0,22 0 0 0,6 0 0 42,
diff --git a/bpf/testdata/all_instructions.txt b/bpf/testdata/all_instructions.txt
index 3045501..8e4d758 100644
--- a/bpf/testdata/all_instructions.txt
+++ b/bpf/testdata/all_instructions.txt
@@ -1,6 +1,6 @@
# This filter is compiled to all_instructions.bpf by the `bpf_asm`
# tool, which can be found in the linux kernel source tree under
-# tools/net.
+# tools/bpf.
# Load immediate
ld #42
@@ -60,7 +60,7 @@
# !A
neg
-# Jumps
+# Jump A <op> constant
ja end
jeq #42,prev,end
jne #42,end
@@ -70,6 +70,15 @@
jge #42,prev,end
jset #42,prev,end
+# Jump A <op> X
+jeq x,prev,end
+jne x,end
+jlt x,end
+jle x,end
+jgt x,prev,end
+jge x,prev,end
+jset x,prev,end
+
# Register transfers
tax
txa
diff --git a/bpf/vm.go b/bpf/vm.go
index 4c656f1..73f57f1 100644
--- a/bpf/vm.go
+++ b/bpf/vm.go
@@ -35,6 +35,13 @@
if check <= int(ins.SkipFalse) {
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
}
+ case JumpIfX:
+ if check <= int(ins.SkipTrue) {
+ return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
+ }
+ if check <= int(ins.SkipFalse) {
+ return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
+ }
// Check for division or modulus by zero
case ALUOpConstant:
if ins.Val != 0 {
@@ -109,6 +116,9 @@
case JumpIf:
jump := jumpIf(ins, regA)
i += jump
+ case JumpIfX:
+ jump := jumpIfX(ins, regA, regX)
+ i += jump
case LoadAbsolute:
regA, ok = loadAbsolute(ins, in)
case LoadConstant:
diff --git a/bpf/vm_instructions.go b/bpf/vm_instructions.go
index 516f946..f0d2e55 100644
--- a/bpf/vm_instructions.go
+++ b/bpf/vm_instructions.go
@@ -55,34 +55,41 @@
}
}
-func jumpIf(ins JumpIf, value uint32) int {
- var ok bool
- inV := uint32(ins.Val)
+func jumpIf(ins JumpIf, regA uint32) int {
+ return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, ins.Val)
+}
- switch ins.Cond {
+func jumpIfX(ins JumpIfX, regA uint32, regX uint32) int {
+ return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, regX)
+}
+
+func jumpIfCommon(cond JumpTest, skipTrue, skipFalse uint8, regA uint32, value uint32) int {
+ var ok bool
+
+ switch cond {
case JumpEqual:
- ok = value == inV
+ ok = regA == value
case JumpNotEqual:
- ok = value != inV
+ ok = regA != value
case JumpGreaterThan:
- ok = value > inV
+ ok = regA > value
case JumpLessThan:
- ok = value < inV
+ ok = regA < value
case JumpGreaterOrEqual:
- ok = value >= inV
+ ok = regA >= value
case JumpLessOrEqual:
- ok = value <= inV
+ ok = regA <= value
case JumpBitsSet:
- ok = (value & inV) != 0
+ ok = (regA & value) != 0
case JumpBitsNotSet:
- ok = (value & inV) == 0
+ ok = (regA & value) == 0
}
if ok {
- return int(ins.SkipTrue)
+ return int(skipTrue)
}
- return int(ins.SkipFalse)
+ return int(skipFalse)
}
func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
diff --git a/bpf/vm_jump_test.go b/bpf/vm_jump_test.go
index e0a3a98..77a2d47 100644
--- a/bpf/vm_jump_test.go
+++ b/bpf/vm_jump_test.go
@@ -83,6 +83,32 @@
}
}
+func TestVMJumpIfXTrueOutOfProgram(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.JumpIfX{
+ Cond: bpf.JumpEqual,
+ SkipTrue: 2,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot jump 2 instructions in true case; jumping past program bounds" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMJumpIfXFalseOutOfProgram(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.JumpIfX{
+ Cond: bpf.JumpEqual,
+ SkipFalse: 3,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot jump 3 instructions in false case; jumping past program bounds" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
func TestVMJumpIfEqual(t *testing.T) {
vm, done, err := testVM(t, []bpf.Instruction{
bpf.LoadAbsolute{
@@ -378,3 +404,323 @@
want, got)
}
}
+
+func TestVMJumpIfXEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 1,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpEqual,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXNotEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 1,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpNotEqual,
+ SkipFalse: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXGreaterThan(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 0x00010202,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpGreaterThan,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXLessThan(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 0xff010203,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpLessThan,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXGreaterOrEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 0x00010203,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpGreaterOrEqual,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXLessOrEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 0xff010203,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpLessOrEqual,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXBitsSet(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 0x1122,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpBitsSet,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 10,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x01, 0x02,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfXBitsNotSet(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 0x1221,
+ },
+ bpf.JumpIfX{
+ Cond: bpf.JumpBitsNotSet,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 10,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x01, 0x02,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}