Skip to content

Commit b0447dd

Browse files
committed
asm: support direct value access for maps
Linux added support for directly loading a value from a map at a known offset in d8eca5bbb2be ("bpf: implement lookup-free direct value access for maps"). This allows to skip the call to bpf_map_lookup_elem and is a building block for data sections. Fix RewriteMapPtr to preserve any existing offset, and add a RewriteMapOffset function.
1 parent f5942f5 commit b0447dd

File tree

6 files changed

+130
-18
lines changed

6 files changed

+130
-18
lines changed

asm/instruction.go

+50-6
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,52 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
103103

104104
// RewriteMapPtr changes an instruction to use a new map fd.
105105
//
106-
// Returns an error if the fd is invalid, or the instruction
107-
// is incorrect.
106+
// Returns an error if the instruction doesn't load a map.
108107
func (ins *Instruction) RewriteMapPtr(fd int) error {
109108
if !ins.OpCode.isDWordLoad() {
110109
return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode)
111110
}
112111

113-
if fd < 0 {
114-
return xerrors.New("invalid fd")
112+
if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
113+
return xerrors.New("not a load from a map")
115114
}
116115

117-
ins.Src = R1
118-
ins.Constant = int64(fd)
116+
// Preserve the offset value for direct map loads.
117+
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
118+
rawFd := uint64(uint32(fd))
119+
ins.Constant = int64(offset | rawFd)
119120
return nil
120121
}
121122

123+
func (ins *Instruction) mapPtr() uint32 {
124+
return uint32(uint64(ins.Constant) & math.MaxUint32)
125+
}
126+
127+
// RewriteMapOffset changes the offset of a direct load from a map.
128+
//
129+
// Returns an error if the instruction is not a direct load.
130+
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
131+
if !ins.OpCode.isDWordLoad() {
132+
return xerrors.Errorf("%s is not a 64 bit load", ins.OpCode)
133+
}
134+
135+
if ins.Src != PseudoMapValue {
136+
return xerrors.New("not a direct load from a map")
137+
}
138+
139+
fd := uint64(ins.Constant) & math.MaxUint32
140+
ins.Constant = int64(uint64(offset)<<32 | fd)
141+
return nil
142+
}
143+
144+
func (ins *Instruction) mapOffset() uint32 {
145+
return uint32(uint64(ins.Constant) >> 32)
146+
}
147+
148+
func (ins *Instruction) isLoadFromMap() bool {
149+
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
150+
}
151+
122152
// Format implements fmt.Formatter.
123153
func (ins Instruction) Format(f fmt.State, c rune) {
124154
if c != 'v' {
@@ -139,6 +169,19 @@ func (ins Instruction) Format(f fmt.State, c rune) {
139169
return
140170
}
141171

172+
if ins.isLoadFromMap() {
173+
fd := int32(ins.mapPtr())
174+
switch ins.Src {
175+
case PseudoMapFD:
176+
fmt.Fprintf(f, "LoadMapPtr dst: %s fd: %d", ins.Dst, fd)
177+
178+
case PseudoMapValue:
179+
fmt.Fprintf(f, "LoadMapValue dst: %s, fd: %d off: %d", ins.Dst, fd, ins.mapOffset())
180+
}
181+
182+
goto ref
183+
}
184+
142185
fmt.Fprintf(f, "%v ", op)
143186
switch cls := op.Class(); cls {
144187
case LdClass, LdXClass, StClass, StXClass:
@@ -183,6 +226,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
183226
}
184227
}
185228

229+
ref:
186230
if ins.Reference != "" {
187231
fmt.Fprintf(f, " <%s>", ins.Reference)
188232
}

asm/instruction_test.go

+37-5
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,50 @@ func TestSignedJump(t *testing.T) {
5959
}
6060
}
6161

62-
func TestInstructionRewriteMapPtr(t *testing.T) {
62+
func TestInstructionRewriteMapConstant(t *testing.T) {
6363
ins := LoadMapPtr(R2, 0)
64+
ins.Src = R2
65+
6466
if err := ins.RewriteMapPtr(1); err != nil {
65-
t.Fatal("Can't rewrite map pointer")
67+
t.Fatal("Can't rewrite map pointer:", err)
68+
}
69+
if ins.mapPtr() != 1 {
70+
t.Fatal("Expected map ptr to be 1, got", ins.Constant)
71+
}
72+
73+
if err := ins.RewriteMapOffset(123); err != nil {
74+
t.Fatal("Can't rewrite map offset:", err)
75+
}
76+
if fd := ins.mapPtr(); fd != 1 {
77+
t.Fatal("Expected map ptr to be 1 after changing the offset, got", fd)
78+
}
79+
80+
if err := ins.RewriteMapPtr(2); err != nil {
81+
t.Fatal("Can't rewrite map pointer:", err)
6682
}
67-
if ins.Constant != 1 {
68-
t.Error("Expected Constant to be 1, got", ins.Constant)
83+
if off := ins.mapOffset(); off != 123 {
84+
t.Fatal("Expected map offset to be 123 after changin the pointer, got", off)
6985
}
7086

7187
ins = Mov.Imm(R1, 32)
7288
if err := ins.RewriteMapPtr(1); err == nil {
73-
t.Error("Allows rewriting bogus instruction")
89+
t.Error("RewriteMapPtr rewriting bogus instruction")
90+
}
91+
if err := ins.RewriteMapOffset(1); err == nil {
92+
t.Error("RewriteMapOffset rewriting bogus instruction")
93+
}
94+
}
95+
96+
func TestInstructionLoadMapValue(t *testing.T) {
97+
ins := LoadMapValue(R0, 1, 123)
98+
if !ins.isLoadFromMap() {
99+
t.Error("isLoadFromMap returns false")
100+
}
101+
if fd := ins.mapPtr(); fd != 1 {
102+
t.Error("Expected map fd to be 1, got", fd)
103+
}
104+
if off := ins.mapOffset(); off != 123 {
105+
t.Fatal("Expected map offset to be 123 after changin the pointer, got", off)
74106
}
75107
}
76108

asm/load_store.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,26 @@ func LoadMapPtr(dst Register, fd int) Instruction {
110110
return Instruction{
111111
OpCode: LoadImmOp(DWord),
112112
Dst: dst,
113-
Src: R1,
113+
Src: PseudoMapFD,
114114
Constant: int64(fd),
115115
}
116116
}
117117

118+
// LoadMapValue stores a pointer to the value at a certain offset of a map.
119+
func LoadMapValue(dst Register, fd int, offset uint32) Instruction {
120+
if fd < 0 {
121+
return Instruction{OpCode: InvalidOpCode}
122+
}
123+
124+
fdAndOffset := (uint64(offset) << 32) | uint64(uint32(fd))
125+
return Instruction{
126+
OpCode: LoadImmOp(DWord),
127+
Dst: dst,
128+
Src: PseudoMapValue,
129+
Constant: int64(fdAndOffset),
130+
}
131+
}
132+
118133
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
119134
func LoadIndOp(size Size) OpCode {
120135
return OpCode(LdClass).SetMode(IndMode).SetSize(size)

asm/register.go

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ const (
3333
RFP = R10
3434
)
3535

36+
// Pseudo registers used by 64bit loads
37+
const (
38+
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
39+
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
40+
)
41+
3642
func (r Register) String() string {
3743
v := uint8(r)
3844
if v == 10 {

collection.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package ebpf
22

33
import (
4+
"math"
5+
46
"github.com/cilium/ebpf/asm"
7+
"github.com/cilium/ebpf/internal"
58
"github.com/cilium/ebpf/internal/btf"
69
"golang.org/x/xerrors"
710
)
@@ -153,21 +156,27 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
153156

154157
// Rewrite any reference to a valid map.
155158
for i := range progSpec.Instructions {
156-
var (
157-
ins = &progSpec.Instructions[i]
158-
m = maps[ins.Reference]
159-
)
159+
ins := &progSpec.Instructions[i]
160160

161-
if ins.Reference == "" || m == nil {
161+
if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
162162
continue
163163
}
164164

165-
if ins.Src == asm.R1 {
165+
if uint32(ins.Constant) != math.MaxUint32 {
166166
// Don't overwrite maps already rewritten, users can
167167
// rewrite programs in the spec themselves
168168
continue
169169
}
170170

171+
m := maps[ins.Reference]
172+
if m == nil {
173+
return nil, xerrors.Errorf("program %s: missing map %s", progName, ins.Reference)
174+
}
175+
176+
fd := m.FD()
177+
if fd < 0 {
178+
return nil, xerrors.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
179+
}
171180
if err := ins.RewriteMapPtr(m.FD()); err != nil {
172181
return nil, xerrors.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
173182
}

elf_reader.go

+6
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations m
231231

232232
ins.Symbol = symbols[offset]
233233
ins.Reference = relocations[offset]
234+
if ins.OpCode == asm.LoadImmOp(asm.DWord) && ins.Reference != "" {
235+
ins.Src = asm.R1
236+
if err := ins.RewriteMapPtr(-1); err != nil {
237+
return nil, 0, err
238+
}
239+
}
234240

235241
insns = append(insns, ins)
236242
offset += n

0 commit comments

Comments
 (0)