Skip to content

Commit 2d07fba

Browse files
authored
Improvements to dependency tracking (#69)
So far dependency tracking in bpfvv relied only on interpretation of individual instructions. For example, `r1 = r6` is parsed as ALU instruction that *reads* r6 and *writes* to r1. BPF verifier often prints relevant values for instructions, and bpfvv of course parses them to build an array of BpfState objects. This change expands the dependency tracking to include information about value changes, as reported by the verifier. For example: 26: (bf) r6 = r0 ; R0_w=map_value_or_null(id=1,map=bpfj_pod_map,ks=4,vs=1040) R6_w=map_value_or_null(id=1,map=bpfj_pod_map,ks=4,vs=1040) ... 29: (15) if r6 == 0x0 goto pc+27 ; R6=map_value(map=bpfj_pod_map,ks=4,vs=1040) Note that the value of r6 has changed from `map_value_or_null` to `map_value`, even though there was no actual writes to r6 in the instruction stream. It is correct however, because in this trace verifier is exploring a branch where r6 is not equal to 0, and so it's value (as interpreted by the verifier) did actually change. In bpfvv, we can notice such value changes and take them into account when calculating dependencies. This also has an additional benefit of indirect stack access tracking, at least in simple cases, such as: 525: (bf) r1 = r10 ; frame2: R1_w=fp0 R10=fp0 526: (07) r1 += -24 ; frame2: R1_w=fp-24 ... 529: (7b) *(u64 *)(r1 +0) = r8 ; frame2: R1_w=fp-24 R8=scalar(id=102) fp-24_w=scalar(id=102) ... 999: (79) r6 = *(u64 *)(r10 -24) ; With this change, the user can now see the dependency of instruction at 999 on 529, even though stack access is indirect.
1 parent d0744e1 commit 2d07fba

File tree

4 files changed

+71
-23
lines changed

4 files changed

+71
-23
lines changed

src/__snapshots__/App.test.tsx.snap

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,17 @@ exports[`App renders the log visualizer when text is pasted 1`] = `
276276
</td>
277277
</tr>
278278
<tr
279-
class="effect-read"
279+
class="effect-write"
280280
>
281281
<td>
282282
r1
283283
</td>
284284
<td>
285285
<span>
286+
ctx()
287+
288+
-&gt;
289+
286290
0
287291
</span>
288292
</td>
@@ -338,13 +342,16 @@ exports[`App renders the log visualizer when text is pasted 1`] = `
338342
</td>
339343
</tr>
340344
<tr
341-
class="effect-read"
345+
class="effect-write"
342346
>
343347
<td>
344348
r7
345349
</td>
346350
<td>
347351
<span>
352+
353+
-&gt;
354+
348355
map_value(off=0,ks=4,vs=2808,imm=0)
349356
</span>
350357
</td>

src/analyzer.test.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,41 @@ ERROR: Loading BPF object(s) failed.
185185
});
186186
});
187187

188+
describe("processes indirect stack access correctly", () => {
189+
const sampleLog = `
190+
525: (bf) r1 = r10 ; R1_w=fp0 R10=fp0
191+
526: (07) r1 += -24 ; R1_w=fp-24
192+
527: (79) r2 = *(u64 *)(r10 -56) ; R2_w=0 R10=fp0 fp-56=0
193+
528: (0f) r1 += r2
194+
529: (7b) *(u64 *)(r1 +0) = r8 ; R1_w=fp-24 R8=scalar(id=102) fp-24_w=scalar(id=102)
195+
900: (bf) r2 = r10 ; R2_w=fp0 R10=fp0
196+
901: (07) r2 += -24 ; R2_w=fp-24
197+
902: (79) r6 = *(u64 *)(r2 +0) ; R2=fp-24 R6_w=scalar(id=102) fp-24=scalar(id=102)
198+
`;
199+
const bpfStates = bpfStatesFromLog(sampleLog);
200+
it("*(u64 *)(r1 +0) = r8", () => {
201+
const s = bpfStates[4];
202+
expect(s.frame).toBe(0);
203+
expect(s.idx).toBe(4);
204+
expect(s.pc).toBe(529);
205+
expect(s.values.get("fp-24")).toMatchObject({
206+
value: "scalar(id=102)",
207+
effect: Effect.WRITE,
208+
});
209+
});
210+
211+
it("r6 = *(u64 *)(r2 +0)", () => {
212+
const s = bpfStates[7];
213+
expect(s.frame).toBe(0);
214+
expect(s.idx).toBe(7);
215+
expect(s.pc).toBe(902);
216+
expect(s.values.get("fp-24")).toMatchObject({
217+
value: "scalar(id=102)",
218+
});
219+
expect(s.lastKnownWrites.get("fp-24")).toBe(4);
220+
});
221+
});
222+
188223
const verifierLogFragmentWithASubprogramCall = `
189224
702: (bf) r1 = r7 ; frame0: R1_w=map_value(off=0,ks=4,vs=2808,imm=0) R7=map_value(off=0,ks=4,vs=2808,imm=0)
190225
703: (bf) r2 = r6 ; frame0: R2_w=rcu_ptr_task_struct(off=0,imm=0) R6=rcu_ptr_task_struct(off=0,imm=0)
@@ -215,10 +250,7 @@ to caller at 705:
215250
expect(s.frame).toBe(0);
216251
expect(s.idx).toBe(0);
217252
expect(s.pc).toBe(702);
218-
expect(s.values.get("r7")).toMatchObject({
219-
value: r1Value,
220-
effect: Effect.READ,
221-
});
253+
expect(s.values.get("r7")?.value).toBe(r1Value);
222254
expect(s.values.get("r1")).toMatchObject({
223255
value: r1Value,
224256
effect: Effect.WRITE,
@@ -230,10 +262,7 @@ to caller at 705:
230262
expect(s.frame).toBe(0);
231263
expect(s.idx).toBe(1);
232264
expect(s.pc).toBe(703);
233-
expect(s.values.get("r6")).toMatchObject({
234-
value: r2Value,
235-
effect: Effect.READ,
236-
});
265+
expect(s.values.get("r6")?.value).toBe(r2Value);
237266
expect(s.values.get("r2")).toMatchObject({
238267
value: r2Value,
239268
effect: Effect.WRITE,

src/analyzer.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ export function makeValue(
8585
prevValue: string = "",
8686
): BpfValue {
8787
const ret: BpfValue = { value, effect };
88-
// @Hack display fp0 as fp-0
89-
if (ret.value === "fp0") ret.value = "fp-0";
9088
if (prevValue) ret.prevValue = prevValue;
9189
return ret;
9290
}
@@ -97,7 +95,7 @@ export function initialBpfState(): BpfState {
9795
values.set(`r${i}`, { value: "", effect: Effect.NONE });
9896
}
9997
values.set("r1", makeValue("ctx()"));
100-
values.set("r10", makeValue("fp0"));
98+
values.set("r10", makeValue("fp-0"));
10199
let lastKnownWrites = new Map<string, number>();
102100
lastKnownWrites.set("r1", 0);
103101
lastKnownWrites.set("r10", 0);
@@ -156,7 +154,7 @@ function pushStackFrame(
156154
for (const r of ["r0", ...BPF_CALLEE_SAVED_REGS]) {
157155
values.set(r, { value: "", effect: Effect.WRITE });
158156
}
159-
values.set("r10", makeValue("fp0"));
157+
values.set("r10", makeValue("fp-0"));
160158

161159
let lastKnownWrites = new Map<string, number>();
162160
for (const r of BPF_SCRATCH_REGS) {
@@ -245,10 +243,18 @@ function nextBpfState(
245243
newState.lastKnownWrites.set(id, line.idx);
246244
}
247245

248-
// verifier reported values
246+
// If verifier reported a particular expr, it overrides any values we may have computed so far
247+
// This means, for example, that conditions can be UPDATEs
249248
for (const expr of line.bpfStateExprs) {
250-
const effect = effects.get(expr.id) || Effect.NONE;
251249
const val: BpfValue | undefined = newState.values.get(expr.id);
250+
let effect = effects.get(expr.id) || Effect.NONE;
251+
if (!val) {
252+
effect = Effect.WRITE;
253+
newState.lastKnownWrites.set(expr.id, line.idx);
254+
} else if (expr.value !== val.value && effect !== Effect.WRITE) {
255+
effect = Effect.UPDATE;
256+
newState.lastKnownWrites.set(expr.id, line.idx);
257+
}
252258
newState.values.set(
253259
expr.id,
254260
makeValue(expr.value, effect, val?.prevValue || ""),
@@ -409,7 +415,7 @@ export function getMemSlotDependencies(
409415

410416
deps = getMemSlotDependencies(verifierLogState, prevDepIdx, memSlotId);
411417
}
412-
} else if (nReads === 1) {
418+
} else if (nReads === 1 && depIdx !== bpfState.idx) {
413419
deps = getMemSlotDependencies(verifierLogState, depIdx, depIns.reads[0]);
414420
}
415421

src/parser.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,9 @@ const parseBpfStateExpr = (
275275
}
276276
i++;
277277
}
278-
const expr = {
279-
id,
280-
value: str.substring(equalsIndex + 1, i),
281-
rawKey: key,
282-
};
278+
let value = str.substring(equalsIndex + 1, i);
279+
if (value === "fp0") value = "fp-0"; // normalize fp0 to fp-0
280+
const expr = { id, value, rawKey: key };
283281
return { expr, rest: str.substring(i) };
284282
};
285283

@@ -645,6 +643,14 @@ function parseConditionalJmp(
645643
const target = jmpTarget.match[2];
646644
rest = consumeSpaces(jmpTarget.rest);
647645

646+
const reads = [];
647+
if (leftOp.op.type !== OperandType.IMM) {
648+
reads.push(leftOp.op.id);
649+
}
650+
if (rightOp.op.type !== OperandType.IMM) {
651+
reads.push(rightOp.op.id);
652+
}
653+
648654
const ins: BpfConditionalJmpInstruction = {
649655
kind: BpfInstructionKind.JMP,
650656
jmpKind: BpfJmpKind.CONDITIONAL_GOTO,
@@ -655,7 +661,7 @@ function parseConditionalJmp(
655661
op: operator,
656662
right: rightOp.op,
657663
},
658-
reads: [leftOp.op.id, rightOp.op.id],
664+
reads,
659665
writes: [], // technically goto writes pc, but we don't care about it (?)
660666
};
661667
return { ins, rest };

0 commit comments

Comments
 (0)