Skip to content

Commit acd6e64

Browse files
committed
program: move jump and call fixup out of asm package
Jumps and calls may have to be fixed up, if their target isn't known at compile time. This happens when a user uses the asm package to write "inline" BPF programs, and when using function calls in C derived BPF. The jump and call targets have to be expressed in units of raw BPF instructions. Since some asm.Instructions have to be encoded in two BPF instructions there is no 1:1 mapping. Figuring out the raw instruction offset therefore involves iterating all instructions and keeping a running count. Due to this the fixup currently happens during instruction marshalling. This is a bit of a hack, but so far allowed to keep the details of raw instruction offsets contained to the asm package. With BPF CO-RE this doesn't work too well anymore: the fixup we have to do is also based on raw BPF instruction offsets. Cramming this into instruction marshalling seems like the wrong thing to do, and is probably not possible due to import cycles between the asm and btf package. Introduce a RawInstructionOffset type and an InstructionIterator to encapsulate the raw offset handling, and move jump and call fixup to a function in the main package.
1 parent 9b4cff7 commit acd6e64

File tree

4 files changed

+116
-67
lines changed

4 files changed

+116
-67
lines changed

asm/instruction.go

+58-63
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import (
1212
// InstructionSize is the size of a BPF instruction in bytes
1313
const InstructionSize = 8
1414

15+
// RawInstructionOffset is an offset in units of raw BPF instructions.
16+
type RawInstructionOffset uint64
17+
18+
// Bytes returns the offset of an instruction in bytes.
19+
func (rio RawInstructionOffset) Bytes() uint64 {
20+
return uint64(rio) * InstructionSize
21+
}
22+
1523
// Instruction is a single eBPF instruction.
1624
type Instruction struct {
1725
OpCode OpCode
@@ -155,6 +163,13 @@ func (ins *Instruction) isLoadFromMap() bool {
155163
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
156164
}
157165

166+
// IsFunctionCall returns true if the instruction calls another BPF function.
167+
//
168+
// This is not the same thing as a BPF helper call.
169+
func (ins *Instruction) IsFunctionCall() bool {
170+
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall
171+
}
172+
158173
// Format implements fmt.Formatter.
159174
func (ins Instruction) Format(f fmt.State, c rune) {
160175
if c != 'v' {
@@ -310,28 +325,6 @@ func (insns Instructions) ReferenceOffsets() map[string][]int {
310325
return offsets
311326
}
312327

313-
func (insns Instructions) marshalledOffsets() (map[string]int, error) {
314-
symbols := make(map[string]int)
315-
316-
marshalledPos := 0
317-
for _, ins := range insns {
318-
currentPos := marshalledPos
319-
marshalledPos += ins.OpCode.marshalledInstructions()
320-
321-
if ins.Symbol == "" {
322-
continue
323-
}
324-
325-
if _, ok := symbols[ins.Symbol]; ok {
326-
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
327-
}
328-
329-
symbols[ins.Symbol] = currentPos
330-
}
331-
332-
return symbols, nil
333-
}
334-
335328
// Format implements fmt.Formatter.
336329
//
337330
// You can control indentation of symbols by
@@ -370,65 +363,67 @@ func (insns Instructions) Format(f fmt.State, c rune) {
370363
symIndent = strings.Repeat(" ", symPadding)
371364
}
372365

373-
// Figure out how many digits we need to represent the highest
374-
// offset.
375-
highestOffset := 0
376-
for _, ins := range insns {
377-
highestOffset += ins.OpCode.marshalledInstructions()
378-
}
366+
// Guess how many digits we need at most, by assuming that all instructions
367+
// are double wide.
368+
highestOffset := len(insns) * 2
379369
offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset))))
380370

381-
offset := 0
382-
for _, ins := range insns {
383-
if ins.Symbol != "" {
384-
fmt.Fprintf(f, "%s%s:\n", symIndent, ins.Symbol)
371+
iter := insns.Iterate()
372+
for iter.Next() {
373+
if iter.Ins.Symbol != "" {
374+
fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol)
385375
}
386-
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, offset, ins)
387-
offset += ins.OpCode.marshalledInstructions()
376+
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
388377
}
389378

390379
return
391380
}
392381

393382
// Marshal encodes a BPF program into the kernel format.
394383
func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
395-
absoluteOffsets, err := insns.marshalledOffsets()
396-
if err != nil {
397-
return err
398-
}
399-
400-
num := 0
401384
for i, ins := range insns {
402-
switch {
403-
case ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall && ins.Constant == -1:
404-
// Rewrite bpf to bpf call
405-
offset, ok := absoluteOffsets[ins.Reference]
406-
if !ok {
407-
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
408-
}
409-
410-
ins.Constant = int64(offset - num - 1)
411-
412-
case ins.OpCode.Class() == JumpClass && ins.Offset == -1:
413-
// Rewrite jump to label
414-
offset, ok := absoluteOffsets[ins.Reference]
415-
if !ok {
416-
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
417-
}
418-
419-
ins.Offset = int16(offset - num - 1)
420-
}
421-
422-
n, err := ins.Marshal(w, bo)
385+
_, err := ins.Marshal(w, bo)
423386
if err != nil {
424387
return fmt.Errorf("instruction %d: %w", i, err)
425388
}
426-
427-
num += int(n / InstructionSize)
428389
}
429390
return nil
430391
}
431392

393+
// Iterate allows iterating a BPF program while keeping track of
394+
// various offsets.
395+
//
396+
// Modifying the instruction slice will lead to undefined behaviour.
397+
func (insns Instructions) Iterate() *InstructionIterator {
398+
return &InstructionIterator{insns: insns}
399+
}
400+
401+
// InstructionIterator iterates over a BPF program.
402+
type InstructionIterator struct {
403+
insns Instructions
404+
// The instruction in question.
405+
Ins *Instruction
406+
// The index of the instruction in the original instruction slice.
407+
Index int
408+
// The offset of the instruction in raw BPF instructions. This accounts
409+
// for double-wide instructions.
410+
Offset RawInstructionOffset
411+
}
412+
413+
// Next returns true as long as there are any instructions remaining.
414+
func (iter *InstructionIterator) Next() bool {
415+
if len(iter.insns) == 0 {
416+
return false
417+
}
418+
419+
if iter.Ins != nil {
420+
iter.Offset += RawInstructionOffset(iter.Ins.OpCode.rawInstructions())
421+
}
422+
iter.Ins = &iter.insns[0]
423+
iter.insns = iter.insns[1:]
424+
return true
425+
}
426+
432427
type bpfInstruction struct {
433428
OpCode OpCode
434429
Registers bpfRegisters

asm/opcode.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ type OpCode uint8
6666
// InvalidOpCode is returned by setters on OpCode
6767
const InvalidOpCode OpCode = 0xff
6868

69-
// marshalledInstructions returns the number of BPF instructions required
69+
// rawInstructions returns the number of BPF instructions required
7070
// to encode this opcode.
71-
func (op OpCode) marshalledInstructions() int {
72-
if op == LoadImmOp(DWord) {
71+
func (op OpCode) rawInstructions() int {
72+
if op.isDWordLoad() {
7373
return 2
7474
}
7575
return 1

linker.go

+47
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,50 @@ func needSection(insns, section asm.Instructions) (bool, error) {
8484
// None of the functions in the section are called.
8585
return false, nil
8686
}
87+
88+
func fixupJumpsAndCalls(insns asm.Instructions) error {
89+
symbolOffsets := make(map[string]asm.RawInstructionOffset)
90+
iter := insns.Iterate()
91+
for iter.Next() {
92+
ins := iter.Ins
93+
94+
if ins.Symbol == "" {
95+
continue
96+
}
97+
98+
if _, ok := symbolOffsets[ins.Symbol]; ok {
99+
return fmt.Errorf("duplicate symbol %s", ins.Symbol)
100+
}
101+
102+
symbolOffsets[ins.Symbol] = iter.Offset
103+
}
104+
105+
iter = insns.Iterate()
106+
for iter.Next() {
107+
i := iter.Index
108+
offset := iter.Offset
109+
ins := iter.Ins
110+
111+
switch {
112+
case ins.IsFunctionCall() && ins.Constant == -1:
113+
// Rewrite bpf to bpf call
114+
callOffset, ok := symbolOffsets[ins.Reference]
115+
if !ok {
116+
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
117+
}
118+
119+
ins.Constant = int64(callOffset - offset - 1)
120+
121+
case ins.OpCode.Class() == asm.JumpClass && ins.Offset == -1:
122+
// Rewrite jump to label
123+
jumpOffset, ok := symbolOffsets[ins.Reference]
124+
if !ok {
125+
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
126+
}
127+
128+
ins.Offset = int16(jumpOffset - offset - 1)
129+
}
130+
}
131+
132+
return nil
133+
}

prog.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,15 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
208208
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
209209
}
210210

211+
insns := make(asm.Instructions, len(spec.Instructions))
212+
copy(insns, spec.Instructions)
213+
214+
if err := fixupJumpsAndCalls(insns); err != nil {
215+
return nil, err
216+
}
217+
211218
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
212-
err := spec.Instructions.Marshal(buf, internal.NativeEndian)
219+
err := insns.Marshal(buf, internal.NativeEndian)
213220
if err != nil {
214221
return nil, err
215222
}

0 commit comments

Comments
 (0)