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)
+	}
+}