Skip to content

Commit 49bb7ce

Browse files
arthurfabrebradfitz
authored andcommitted
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: 39a8816 GitHub-Pull-Request: #20 Reviewed-on: https://go-review.googlesource.com/c/136895 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
1 parent 146acd2 commit 49bb7ce

File tree

8 files changed

+587
-121
lines changed

8 files changed

+587
-121
lines changed

bpf/constants.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
type JumpTest uint16
3939

4040
// Supported operators for conditional jumps.
41+
// K can be RegX for JumpIfX
4142
const (
4243
// K == A
4344
JumpEqual JumpTest = iota
@@ -134,12 +135,9 @@ const (
134135
opMaskLoadDest = 0x01
135136
opMaskLoadWidth = 0x18
136137
opMaskLoadMode = 0xe0
137-
// opClsALU
138-
opMaskOperandSrc = 0x08
139-
opMaskOperator = 0xf0
140-
// opClsJump
141-
opMaskJumpConst = 0x0f
142-
opMaskJumpCond = 0xf0
138+
// opClsALU & opClsJump
139+
opMaskOperand = 0x08
140+
opMaskOperator = 0xf0
143141
)
144142

145143
const (
@@ -192,15 +190,21 @@ const (
192190
opLoadWidth1
193191
)
194192

195-
// Operator defined by ALUOp*
193+
// Operand for ALU and Jump instructions
194+
type opOperand uint16
196195

196+
// Supported operand sources.
197197
const (
198-
opALUSrcConstant uint16 = iota << 3
199-
opALUSrcX
198+
opOperandConstant opOperand = iota << 3
199+
opOperandX
200200
)
201201

202+
// An jumpOp is a conditional jump condition.
203+
type jumpOp uint16
204+
205+
// Supported jump conditions.
202206
const (
203-
opJumpAlways = iota << 4
207+
opJumpAlways jumpOp = iota << 4
204208
opJumpEqual
205209
opJumpGT
206210
opJumpGE

bpf/instructions.go

Lines changed: 108 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -89,74 +89,33 @@ func (ri RawInstruction) Disassemble() Instruction {
8989
case opClsALU:
9090
switch op := ALUOp(ri.Op & opMaskOperator); op {
9191
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
92-
if ri.Op&opMaskOperandSrc != 0 {
92+
switch operand := opOperand(ri.Op & opMaskOperand); operand {
93+
case opOperandX:
9394
return ALUOpX{Op: op}
95+
case opOperandConstant:
96+
return ALUOpConstant{Op: op, Val: ri.K}
97+
default:
98+
return ri
9499
}
95-
return ALUOpConstant{Op: op, Val: ri.K}
96100
case aluOpNeg:
97101
return NegateA{}
98102
default:
99103
return ri
100104
}
101105

102106
case opClsJump:
103-
if ri.Op&opMaskJumpConst != opClsJump {
104-
return ri
105-
}
106-
switch ri.Op & opMaskJumpCond {
107+
switch op := jumpOp(ri.Op & opMaskOperator); op {
107108
case opJumpAlways:
108109
return Jump{Skip: ri.K}
109-
case opJumpEqual:
110-
if ri.Jt == 0 {
111-
return JumpIf{
112-
Cond: JumpNotEqual,
113-
Val: ri.K,
114-
SkipTrue: ri.Jf,
115-
SkipFalse: 0,
116-
}
117-
}
118-
return JumpIf{
119-
Cond: JumpEqual,
120-
Val: ri.K,
121-
SkipTrue: ri.Jt,
122-
SkipFalse: ri.Jf,
123-
}
124-
case opJumpGT:
125-
if ri.Jt == 0 {
126-
return JumpIf{
127-
Cond: JumpLessOrEqual,
128-
Val: ri.K,
129-
SkipTrue: ri.Jf,
130-
SkipFalse: 0,
131-
}
132-
}
133-
return JumpIf{
134-
Cond: JumpGreaterThan,
135-
Val: ri.K,
136-
SkipTrue: ri.Jt,
137-
SkipFalse: ri.Jf,
138-
}
139-
case opJumpGE:
140-
if ri.Jt == 0 {
141-
return JumpIf{
142-
Cond: JumpLessThan,
143-
Val: ri.K,
144-
SkipTrue: ri.Jf,
145-
SkipFalse: 0,
146-
}
147-
}
148-
return JumpIf{
149-
Cond: JumpGreaterOrEqual,
150-
Val: ri.K,
151-
SkipTrue: ri.Jt,
152-
SkipFalse: ri.Jf,
153-
}
154-
case opJumpSet:
155-
return JumpIf{
156-
Cond: JumpBitsSet,
157-
Val: ri.K,
158-
SkipTrue: ri.Jt,
159-
SkipFalse: ri.Jf,
110+
case opJumpEqual, opJumpGT, opJumpGE, opJumpSet:
111+
cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf)
112+
switch operand := opOperand(ri.Op & opMaskOperand); operand {
113+
case opOperandX:
114+
return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse}
115+
case opOperandConstant:
116+
return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse}
117+
default:
118+
return ri
160119
}
161120
default:
162121
return ri
@@ -187,6 +146,41 @@ func (ri RawInstruction) Disassemble() Instruction {
187146
}
188147
}
189148

149+
func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) {
150+
var test JumpTest
151+
152+
// Decode "fake" jump conditions that don't appear in machine code
153+
// Ensures the Assemble -> Disassemble stage recreates the same instructions
154+
// See https://github.com/golang/go/issues/18470
155+
if skipTrue == 0 {
156+
switch op {
157+
case opJumpEqual:
158+
test = JumpNotEqual
159+
case opJumpGT:
160+
test = JumpLessOrEqual
161+
case opJumpGE:
162+
test = JumpLessThan
163+
case opJumpSet:
164+
test = JumpBitsNotSet
165+
}
166+
167+
return test, skipFalse, 0
168+
}
169+
170+
switch op {
171+
case opJumpEqual:
172+
test = JumpEqual
173+
case opJumpGT:
174+
test = JumpGreaterThan
175+
case opJumpGE:
176+
test = JumpGreaterOrEqual
177+
case opJumpSet:
178+
test = JumpBitsSet
179+
}
180+
181+
return test, skipTrue, skipFalse
182+
}
183+
190184
// LoadConstant loads Val into register Dst.
191185
type LoadConstant struct {
192186
Dst Register
@@ -413,7 +407,7 @@ type ALUOpConstant struct {
413407
// Assemble implements the Instruction Assemble method.
414408
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
415409
return RawInstruction{
416-
Op: opClsALU | opALUSrcConstant | uint16(a.Op),
410+
Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op),
417411
K: a.Val,
418412
}, nil
419413
}
@@ -454,7 +448,7 @@ type ALUOpX struct {
454448
// Assemble implements the Instruction Assemble method.
455449
func (a ALUOpX) Assemble() (RawInstruction, error) {
456450
return RawInstruction{
457-
Op: opClsALU | opALUSrcX | uint16(a.Op),
451+
Op: opClsALU | uint16(opOperandX) | uint16(a.Op),
458452
}, nil
459453
}
460454

@@ -509,7 +503,7 @@ type Jump struct {
509503
// Assemble implements the Instruction Assemble method.
510504
func (a Jump) Assemble() (RawInstruction, error) {
511505
return RawInstruction{
512-
Op: opClsJump | opJumpAlways,
506+
Op: opClsJump | uint16(opJumpAlways),
513507
K: a.Skip,
514508
}, nil
515509
}
@@ -530,11 +524,39 @@ type JumpIf struct {
530524

531525
// Assemble implements the Instruction Assemble method.
532526
func (a JumpIf) Assemble() (RawInstruction, error) {
527+
return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse)
528+
}
529+
530+
// String returns the instruction in assembler notation.
531+
func (a JumpIf) String() string {
532+
return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse)
533+
}
534+
535+
// JumpIfX skips the following Skip instructions in the program if A
536+
// <Cond> X is true.
537+
type JumpIfX struct {
538+
Cond JumpTest
539+
SkipTrue uint8
540+
SkipFalse uint8
541+
}
542+
543+
// Assemble implements the Instruction Assemble method.
544+
func (a JumpIfX) Assemble() (RawInstruction, error) {
545+
return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse)
546+
}
547+
548+
// String returns the instruction in assembler notation.
549+
func (a JumpIfX) String() string {
550+
return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse)
551+
}
552+
553+
// jumpToRaw assembles a jump instruction into a RawInstruction
554+
func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) {
533555
var (
534-
cond uint16
556+
cond jumpOp
535557
flip bool
536558
)
537-
switch a.Cond {
559+
switch test {
538560
case JumpEqual:
539561
cond = opJumpEqual
540562
case JumpNotEqual:
@@ -552,63 +574,63 @@ func (a JumpIf) Assemble() (RawInstruction, error) {
552574
case JumpBitsNotSet:
553575
cond, flip = opJumpSet, true
554576
default:
555-
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", a.Cond)
577+
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test)
556578
}
557-
jt, jf := a.SkipTrue, a.SkipFalse
579+
jt, jf := skipTrue, skipFalse
558580
if flip {
559581
jt, jf = jf, jt
560582
}
561583
return RawInstruction{
562-
Op: opClsJump | cond,
584+
Op: opClsJump | uint16(cond) | uint16(operand),
563585
Jt: jt,
564586
Jf: jf,
565-
K: a.Val,
587+
K: k,
566588
}, nil
567589
}
568590

569-
// String returns the instruction in assembler notation.
570-
func (a JumpIf) String() string {
571-
switch a.Cond {
591+
// jumpToString converts a jump instruction to assembler notation
592+
func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string {
593+
switch cond {
572594
// K == A
573595
case JumpEqual:
574-
return conditionalJump(a, "jeq", "jneq")
596+
return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq")
575597
// K != A
576598
case JumpNotEqual:
577-
return fmt.Sprintf("jneq #%d,%d", a.Val, a.SkipTrue)
599+
return fmt.Sprintf("jneq %s,%d", operand, skipTrue)
578600
// K > A
579601
case JumpGreaterThan:
580-
return conditionalJump(a, "jgt", "jle")
602+
return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle")
581603
// K < A
582604
case JumpLessThan:
583-
return fmt.Sprintf("jlt #%d,%d", a.Val, a.SkipTrue)
605+
return fmt.Sprintf("jlt %s,%d", operand, skipTrue)
584606
// K >= A
585607
case JumpGreaterOrEqual:
586-
return conditionalJump(a, "jge", "jlt")
608+
return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt")
587609
// K <= A
588610
case JumpLessOrEqual:
589-
return fmt.Sprintf("jle #%d,%d", a.Val, a.SkipTrue)
611+
return fmt.Sprintf("jle %s,%d", operand, skipTrue)
590612
// K & A != 0
591613
case JumpBitsSet:
592-
if a.SkipFalse > 0 {
593-
return fmt.Sprintf("jset #%d,%d,%d", a.Val, a.SkipTrue, a.SkipFalse)
614+
if skipFalse > 0 {
615+
return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse)
594616
}
595-
return fmt.Sprintf("jset #%d,%d", a.Val, a.SkipTrue)
617+
return fmt.Sprintf("jset %s,%d", operand, skipTrue)
596618
// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
597619
case JumpBitsNotSet:
598-
return JumpIf{Cond: JumpBitsSet, SkipTrue: a.SkipFalse, SkipFalse: a.SkipTrue, Val: a.Val}.String()
620+
return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue)
599621
default:
600-
return fmt.Sprintf("unknown instruction: %#v", a)
622+
return fmt.Sprintf("unknown JumpTest %#v", cond)
601623
}
602624
}
603625

604-
func conditionalJump(inst JumpIf, positiveJump, negativeJump string) string {
605-
if inst.SkipTrue > 0 {
606-
if inst.SkipFalse > 0 {
607-
return fmt.Sprintf("%s #%d,%d,%d", positiveJump, inst.Val, inst.SkipTrue, inst.SkipFalse)
626+
func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string {
627+
if skipTrue > 0 {
628+
if skipFalse > 0 {
629+
return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse)
608630
}
609-
return fmt.Sprintf("%s #%d,%d", positiveJump, inst.Val, inst.SkipTrue)
631+
return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue)
610632
}
611-
return fmt.Sprintf("%s #%d,%d", negativeJump, inst.Val, inst.SkipFalse)
633+
return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse)
612634
}
613635

614636
// RetA exits the BPF program, returning the value of register A.

0 commit comments

Comments
 (0)