-
Notifications
You must be signed in to change notification settings - Fork 14k
[RFC][BPF] Report Unreachable Behavior from IR #126858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ With the latest revision this PR passed the C/C++ code formatter. |
✅ With the latest revision this PR passed the undef deprecator. |
ae4596e
to
3526fd3
Compare
Value *RetValue = I->getReturnValue(); | ||
// PoisonValue is a special UndefValue where compiler intentionally to | ||
// poisons a value since it shouldn't be used. | ||
if (!RetValue || isa<PoisonValue>(RetValue) || !isa<UndefValue>(RetValue)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like undef is deprecated.
Do we have to check it or PoisonValue is enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some extreme case, e.g.,
int foo(void) {
int i[2];
return i[1];
}
The eventual IR will look like
define dso_local i32 @foo() #0 {
ret i32 undef
}
For such cases, checking !isa<PoisonValue>(RetValue)
is not enough, we should check !isa<UndefValue>(RetValue)
to ensure to report something wrong to user.
But such case should be really rare and it is easier for user to find out what is going on.
The prog Marc Suñé reported does not need the above check. Since upstream doesn't like to involve 'undef', I will remove the above checking and also remove the simple test which results in IR 'ret i32 undef' in the next revision.
3526fd3
to
9671d28
Compare
clang-format still does not like 'undef' in the test. Will try not to have 'undef' in IR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LLVM/Clang generally do not allow the use of backend-generated warnings, because they produce unintelligible, optimization-dependent false-positives. There are some specific exceptions, but what you're doing here is basically exactly the kind of backend warning we want to avoid.
Does BPF have any kind of support for trapping or otherwise indicating an error? There is existing support to compile unreachable to a trap instruction (TrapUnreachable), but from a quick look, it doesn't seem like there is any BPF instruction this can be mapped to?
} | ||
|
||
dbgs() << "WARNING: unreachable in func " << F.getName() | ||
<< ", due to uninitialized variable?\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dbgs() is not a suitable method to report user-visible warnings. This needs to go through DiagnosticInfo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I just updated a change to use DiagnosticInfo.
9671d28
to
437a5ec
Compare
The backend warning here is BPF specific as several conditions are used to check before issuing the warning.
No, BPF arch does not have trapping insn or another other insn similar to trap/error. I am not sure whether bpf should have trap insn or not. For a prog passing verifier, it should not trap in the kernel. Let us say we do introduce trap insn in BPF ISA, and llvm indeed inserts one which implies some path will hit 'trap' so probably we should error out at compile time. But it has slightly chance compiler may have a false positive, so I think a warning seems more appropriate. |
In my latest change, I remove the selftest due to clang-format does not like 'undef' in the IR. If anybody wants to run it at llvm/test/CodeGen/BPF, the gist link is |
Update a new revision with the following key changes:
|
943e279
to
1c19bae
Compare
} | ||
|
||
F.getContext().diagnose( | ||
DiagnosticInfoGeneric(Twine("unreachable in func ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that reporting file:line would still be useful in some cases.
If the error could say that in function FOO from line X to line Y that code was deleted as unreachable
that would help users to address the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, if debuginfo (-g) is available, the error message will be something like
error: in func repro from line 155 to the end of func that code was deleted as unreachable,
due to uninitialized variable? try -Wuninitialized?
Without debuginfo, the error message will be like
error: in func repro that code was deleted as unreachable,
due to uninitialized variable? try -Wuninitialized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error: in func repro from line 155 to the end of func that code was deleted as unreachable,
due to uninitialized variable? try -Wuninitialized?
this looks better, maybe add '' around function name like we do elsewhere and don't shorten "function" to "func" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new message looks like:
error: in function "repro" from line 155 to the end of function that code was deleted as unreachable.
due to uninitialized variable? try -Wuninitialized?
1c19bae
to
c9ba781
Compare
Marc Suñé (Isovalent, part of Cisco) reported an issue where an uninitialized variable caused generated bpf prog binary code not working as expected. The reproducer is in [1] where the flags “-Wall -Werror” are enabled, but there is no warning and compiler may take advantage of uninit variable to do aggressive optimization. In discussion [2], various approaches are discussed, e.g., improving compiler to detect undefined behavior due to uninitialized variables, trying to use ubsan (-fsanitize=undefined), and making -ftrivial-auto-var-init=zero as the bpf default flags. I tried [3] with -ftrivial-auto-var-init=zero and eventually we decided no-go since first it may introduce performance regression and second the prog may still be wrong if the prog expects a non-zero value. The ubsan apprach seems not working as well since it involves runtime callback func ([4]). The approach here is not to do complicate compiler analysis to detect whether where is undef behavior which may impact final codegen. Rather, we relies on compiler to do its normal transformation and at later IR passes stage, a BPF backend pass is inserted to check whether undef behavior is in IR or not. Note that if undef behavior indeed impacts codes, the compiler will discard those related codes with simple 'undef' or 'unreachable'. For example, for the case [1], before SCCPPass, the IR looks like ``` define dso_local i32 @repro(ptr noundef %0) #0 section "classifier" { %2 = alloca %struct.ipv6_opt_hdr, align 8 %3 = tail call i64 (ptr, i32, ...) inttoptr (i64 6 to ptr)(ptr noundef nonnull @repro.____fmt, i32 noundef 6) llvm#2 %4 = tail call ptr asm sideeffect "$0 = *(u32 *)($1 + $2)", "=r,r,i"(ptr %0, i64 76) llvm#2, !srcloc !3 %5 = ptrtoint ptr %4 to i64 %6 = trunc i64 %5 to i32 %7 = tail call i64 (ptr, i32, ...) inttoptr (i64 6 to ptr)(ptr noundef nonnull @icmp6_ndisc_validate.____fmt, i32 noundef 23) llvm#2 call void @llvm.lifetime.start.p0(i64 2, ptr nonnull %2) llvm#2 %8 = getelementptr inbounds nuw i8, ptr %2, i64 1 switch i8 undef, label %51 [ i8 59, label %56 i8 44, label %57 i8 0, label %9 i8 43, label %9 i8 51, label %9 i8 60, label %9 ] 9: ; preds = %1, %1, %1, %1 %10 = sub i32 40, %6 ... ``` Note that 'undef' is used for switch key due to one of early pass LoopFullUnrollPass. After SCCPPass: ``` efine dso_local i32 @repro(ptr noundef %0) #0 section "classifier" { %2 = alloca %struct.ipv6_opt_hdr, align 8 %3 = tail call i64 (ptr, i32, ...) inttoptr (i64 6 to ptr)(ptr noundef nonnull @repro.____fmt, i32 noundef 6) llvm#2 %4 = tail call ptr asm sideeffect "$0 = *(u32 *)($1 + $2)", "=r,r,i"(ptr %0, i64 76) llvm#2, !srcloc !3 %5 = ptrtoint ptr %4 to i64 %6 = trunc i64 %5 to i32 %7 = tail call i64 (ptr, i32, ...) inttoptr (i64 6 to ptr)(ptr noundef nonnull @icmp6_ndisc_validate.____fmt, i32 noundef 23) llvm#2 call void @llvm.lifetime.start.p0(i64 2, ptr nonnull %2) llvm#2 %8 = getelementptr inbounds nuw i8, ptr %2, i64 1 unreachable } ``` For another example, ``` $ cat t.c int foo() { int i[2]; return i[1]; } ``` Before SROAPass pass, ``` define dso_local i32 @foo() #0 { %1 = alloca [2 x i32], align 4 call void @llvm.lifetime.start.p0(i64 8, ptr %1) llvm#2 %2 = getelementptr inbounds [2 x i32], ptr %1, i64 0, i64 1 %3 = load i32, ptr %2, align 4, !tbaa !3 call void @llvm.lifetime.end.p0(i64 8, ptr %1) llvm#2 ret i32 %3 } ``` After SROAPass pass, ``` define dso_local i32 @foo() #0 { ret i32 undef } ``` Besides the above two test cases, the following three patterns are also covered: - It is possible llvm may generate codes where a default branch to 'unreachable' location. Ignore such 'unreachable' instances. See [5] or some comments in [2]. - Handle pattern like __bpf_unreachable (defined in bpf_helpers.h). - Functions with naked attribute will have 'unreachable' at the end of function. Ignore such functions. Tested with bpf selftests and there are no warnings issued. [1] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [2] https://discourse.llvm.org/t/detect-undefined-behavior-due-to-uninitialized-variables-in-bpf-programs/84116?u=yonghong-song [3] llvm#125601 [4] https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/ubsan/ubsan_interface.inc [5] https://lore.kernel.org/lkml/0bf90fc0-2287-4ce0-b810-6e383e695981@linux.dev/
Upstream does not like to check undef value and clang-format will fail due to this. Let us remove checking for returning undef value. A related test is also removed.
The test undef-sccp.ll still uses 'undef' in the IR and clang-format complains it. In this particular case, 'undef' is generated in sroa and it needs a lot of other passes to reach sccp. So let us remove this test. The test itself can be accessed in https://gist.github.com/yonghong-song/11be8603ad9422f418f60b41beded047 which can be tested in llvm/test/CodeGen/BPF directory. DiagnosticInfo() is the recommended interface for warnings comparing to dbgs().
Report an error by default. Add a new flag -bpf-disable-check-unreachable-ir to disable checking. Depend on whether debuginfo is available or not, the error message will look like in func <func> from line <line num> to the end of func that code was deleted as unreachable, due to uninitialized variable? try -Wuninitialized? or in func <func> that code was deleted as unreachable, due to uninitialized variable? try -Wuninitialized?
c9ba781
to
85bdc2c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though this is BPF-specific, it is introducing constraints on target-independent transforms: specifically, any transform which may introduce unreachable code is illegal.
As such, I think this needs an RFC to determine how to go forward. The needs to describe the general impact on transformations, and whether there are any existing transforms impacted by the constraint.
@efriedma-quic I marked this patch as RFC. I think the key thing is about when 'unreachable' is introduced today in middle end and what is the expected scenario 'unreachable' could be introduced in the future. For the 'unreachable' introduced in the future, I guess it should be okay. The BPF can be adjusted as necessary, similar to other transformations. |
On a conventional target, in general, code with undefined behavior is fine, as long as it doesn't actually execute at runtime. People write source code based on this, and transforms duplicate code based on this. And this makes diagnostics in the backend impractical. I understand that BPF operates under different constraints than conventional targets... in particular, BPF-C is a subset of C. It only allows programs that pass the BPF verifier. So maybe you don't run into exactly the same issues with late diagnostics. But, I'm not sure what the implications are here for transforms. For example, can JumpThreading introduce an "unreachable" that triggers the error here? If so, do we need to restrict JumpThreading on BPF? |
JumpThreading works for BPF target as well and the middle end optimization does not need to restrict JumpThreading for BPF target. The bpf backend pass implemented in this patch will handle such code patterns (BPF backend will do some analysis if necessary). If in the future, middle end optimization has new cases which introduce unreachable, BPF backend will try to handle handle such new patterns if necessary and this should be fine as they typically will be in the same release. So in summary, middle end optimization does not need to do any special things for BPF backend. |
cc @WenleiHe |
I was curious if there are other cases besides switch instruction, where Given this, I tend to agree that a runtime trap would be a simpler solution. E.g. something along the lines libbpf does here, where it inserts a call to unknown helper function. In kernel Verifier can be changed to report something about undefined behaviour trap to simplify debugging. BPF backend can be changed to avoid clobbering registers because of this special trap call. Just my 5 cents. |
Could you share the details about these experiments? I would like to see whether I missed anything which we should cover?
Regarding to runtime trap, I actually examined this as well before going to compiler approach. That approach is to change 'unreachable' to some newly created trap insn and later on when verifier reaches these trap insn, verification will fail. But I did a few examples and found that only limited patterns so I prefer to use the compiler approach. Note that the 'unreachable' checking is done at roughly the end of middle end optimizaiton (i.e. beginning of backend IR passes). So let us gather more examples here before considering going to bpf verifier to deal with these 'unreachable' traps. Note that there is risk that these 'unreachable' traps may be rejected by verifier but actually it may be safe because of verifier some in-precise analysis. So the best way is to do at runtime, but it has its own complication.
So my suggestion is to let us do some thorough analysis at compile time. Note that it is always better for compiler to flag something first. If eventually it turns out there are too many patterns which may have fake unreachables, then we could consider run-time approach then.
|
I did not do any experiments, tried to analyze places where One option would be to copy this transformation to x86 backend and e.g. compile Linux kernel or some other big project, to see which code patterns introduce |
Thanks. It would be great to collect some statistics. I only tried with bpf programs in kernel bpf selftests. Maybe trying with other architectures can expose more patterns. |
I also discussed with @WenleiHe a little bit about this. Another solution is to extend 'unreachable' insn in LLVM. For example, we could have 'unreachable' like below
With carried and passed from various optimization, downstream can reason about 'unreachable' insn. This will make BPF backend pass easier to only trigger error for due to undef bahavior. WDYT? Is this a reasonable approach? |
In some cases, the code has uninitialized variable and the uninitialized variable actually has an impact on the code. For example, $ cat t.c __attribute__((always_inline)) int bar(int a, int *b) { if ((*b) == 0) return a; else return 2 * a; } void tar(int); int foo(int a) { int b; return bar(a, &b); } In the above variable 'b' is uninitialized. With the following compilation flag: clang -O2 -S -emit-llvm -Wall -Werror t.c -mllvm -print-after-all The EarlyCSEPass changes the input IR define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#1 { entry: %cmp.i = icmp eq i32 undef, 0 %mul.i = shl nsw i32 %a, 1 %retval.0.i = select i1 %cmp.i, i32 %a, i32 %mul.i ret i32 %retval.0.i } to output IR define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#1 { entry: %mul.i = shl nsw i32 %a, 1 ret i32 %a } In the above, the llvm generates code without any 'undef' values. On the other hand, with the following compilation flag: clang -O2 -S -emit-llvm -Wall -Werror t.c -fsanitize=undefined -mllvm -print-after-all The SCCPPass changes the input IR define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#3 !func_sanitize !12 { entry: br i1 undef, label %bar.exit, label %if.else.i if.else.i: ; preds = %entry %0 = shl i32 %a, 1 %1 = add i32 %a, 1073741824 %2 = icmp sgt i32 %1, -1 br i1 %2, label %bar.exit, label %handler.mul_overflow.i, !prof !7, !nosanitize !6 handler.mul_overflow.i: ; preds = %if.else.i %3 = zext i32 %a to i64, !nosanitize !6 tail call void @__ubsan_handle_mul_overflow(ptr nonnull @2, i64 2, i64 %3) llvm#5, !nosanitize !6 br label %bar.exit, !nosanitize !6 bar.exit: ; preds = %entry, %if.else.i, %handler.mul_overflow.i %retval.0.i = phi i32 [ %a, %entry ], [ %0, %handler.mul_overflow.i ], [ %0, %if.else.i ] ret i32 %retval.0.i } to output IR define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#3 !func_sanitize !12 { entry: unreachable } Note that in the above example, the unitialized variable is used cross function boundary which makes frontend harder to emit error messages. And bpf prog needs to pass verifier which makes runtime sanitize not suitable. On the other hand, bpf prog typically is not that big and bpf prog also tends have quite some inlining for performance reason or to avoid kernel verification failure. So detecting and reporting impactful uninit var can help improve bpf developer productivity greatly. There are more discussion in [1] and [2]. To maximize chances to report proper unit var warnings (beyond -Wall -Werror), I would like to discuss two things related to the above example: 1. Avoid generating legal code from 'undef' code. This is needed so the 'undef' code can be carried through entire compilation. And in many cases, 'undef' is eventually transformed to 'unreachable' insn. Generating legal code (without 'undef') will prevent later catching 'undef/unreachable' cases. 2. As in discussions in [2], looks like clang-format does not like BPF Backend to check undef values. So if possible, it would be great to convert 'undef' related code to 'unreachable', e.g. in the above SCCPPass. This RFC intends to have some upstream discussion on how to achieve the above two goals. With this patch, for the following compilation flag: clang -O2 -S -emit-llvm -Wall -Werror t.c -mllvm -print-after-all The EarlyCSEPass changes the input IR define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#1 { entry: %cmp.i = icmp eq i32 undef, 0 %mul.i = shl nsw i32 %a, 1 %retval.0.i = select i1 %cmp.i, i32 %a, i32 %mul.i ret i32 %retval.0.i } to output IR define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#1 { entry: %mul.i = shl nsw i32 %a, 1 unreachable } And '%mul.i = shl nsw i32 %a, 1' is removed in SimplifyCFGPass. ; *** IR Dump After CorrelatedValuePropagationPass on foo *** ; Function Attrs: nounwind uwtable define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#1 { entry: %mul.i = shl nsw i32 %a, 1 unreachable } ; *** IR Dump After SimplifyCFGPass on foo *** ; Function Attrs: nounwind uwtable define dso_local i32 @foo(i32 noundef %a) local_unnamed_addr llvm#1 { entry: unreachable } [1] https://discourse.llvm.org/t/detect-undefined-behavior-due-to-uninitialized-variables-in-bpf-programs/84116/4?u=yonghong-song [2] llvm#126858
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. Except naked functions, the 'unreachable' insn is preserved with special encoding in bpf object file and verifier will do proper verification for the bpf prog. For naked functions, llvm middle-end generates a 'unreachable' at the end of function. Linux kernel BPF subsystem does not like it since the last insn in the function is expected to exit or jump. It is a trivial change in llvm to prevent this case in order to void linux kernel change. More specifically, for naked functions, the 'unreachable' IR will be lowered to a NOP bpf insn. For other cases, the 'unreachable' insn is replaced by a '__unreachable_helper' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern '__unreachable_helper' can be handled properly in libbpf func poison_kfunc_call(). The switch statements may generate 'unreachable' insn. I tested bpf selftests and found 4 prog files which has '__unreachable_helper' call. The following are those 4 files: test_cls_redirect.c test_cls_redirect_dynptr.c test_cls_redirect_subprogs.c user_ringbuf_success.c For the first three files, test_cls_redirect.c, test_cls_redirect_dynptr.c, and test_cls_redirect_subprogs.c, the switch statement looks like switch (verdict) { case INVALID: /* metrics have already been bumped */ return TC_ACT_SHOT; ... case ESTABLISHED: metrics->accepted_packets_total_established++; break; } In the above case, the switch statement does not 'default' case, so compiler adds one default branch with 'unreachable' insn. For user_ringbuf_success.c, the switch statement looks like switch (index % TEST_MSG_OP_NUM_OPS) { case TEST_MSG_OP_INC64: msg->operand_64 = operand_64; msg->msg_op = TEST_MSG_OP_INC64; expected_user_mutated += operand_64; break; case TEST_MSG_OP_INC32: msg->operand_32 = operand_32; msg->msg_op = TEST_MSG_OP_INC32; expected_user_mutated += operand_32; break; case TEST_MSG_OP_MUL64: msg->operand_64 = operand_64; msg->msg_op = TEST_MSG_OP_MUL64; expected_user_mutated *= operand_64; break; case TEST_MSG_OP_MUL32: msg->operand_32 = operand_32; msg->msg_op = TEST_MSG_OP_MUL32; expected_user_mutated *= operand_32; break; default: bpf_ringbuf_discard(msg, 0); err = 5; return 1; } In the above, TEST_MSG_OP_NUM_OPS equals 4 and TEST_MSG_OP{INC64,INC32,MUL64,MUL32} have values 0-3 respectively. So the default branch is actually unreachable, so compiler replace the code under default branch with 'unreachable' insn. For all the above switch statements, verifier is able to not go through branch with unreachable code (represented with a __unreachable_helper kfunc). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __unreachable_helper <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
A new approach to address this issue is in #131731 |
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a 'bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern 'bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a '__bpf_unreachable' function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The function is also present in prog btf. This way, the extern '__bpf_unreachable' can be handled properly in libbpf func poison_kfunc_call(). The name '__bpf_unreachable' is chosen to satisfy reserved identifier requirement. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the error is properly detected by verifier: func#0 @0 last insn is not an exit or jmp In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_unreachable <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping unreachable. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this case, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this case, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this patch, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this patch, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this patch, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. To keep compilation time failure, user can add an option like '-ftrap-function+<something>'. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
NOTE: not working as the symbol is generated at selectiondag stage Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this case, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
NOTE: not working as the symbol is generated at selectiondag stage Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have # define __bpf_unreachable() __builtin_trap() If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this case, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> Eventually kernel verifier will emit the following logs: 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? In another internal sched-ext bpf prog, with the patch we have bpf code: Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have ``` # define __bpf_unreachable() __builtin_trap() ``` If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this patch, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. To keep compilation time failure, user can add an option like `-ftrap-function=<something>`. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ``` ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> ``` Eventually kernel verifier will emit the following logs: ``` 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? ``` In another internal sched-ext bpf prog, with the patch we have bpf code: ``` Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> ``` The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] #126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have ``` # define __bpf_unreachable() __builtin_trap() ``` If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this patch, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. To keep compilation time failure, user can add an option like `-ftrap-function=<something>`. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ``` ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> ``` Eventually kernel verifier will emit the following logs: ``` 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? ``` In another internal sched-ext bpf prog, with the patch we have bpf code: ``` Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> ``` The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm/llvm-project#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Currently, middle-end generates 'unreachable' insn if the compiler feels the code is indeed unreachable or the code becomes invalid due to some optimizaiton (e.g. code optimization with uninitialized variables). Right now BPF backend ignores 'unreachable' insn during selectiondag lowering. For cases where 'unreachable' is due to invalid code transformation, such a signal will be missed. Later on, users needs some effort to debug it which impacts developer productivity. This patch enabled selectiondag lowering for 'unreachable' insn. Previous attempt ([1]) tries to have a backend IR pass to filter out 'unreachable' insns in a number of cases. But such pattern matching may misalign with future middle-end optimization with 'unreachable' insns. This patch takes a different approach. The 'unreachable' insn is lowered with special encoding in bpf object file and verifier will do proper verification for the bpf prog. More specifically, the 'unreachable' insn is replaced by a __bpf_trap() function. This function will be a kfunc (in ".ksyms" section) with a weak attribute, but does not have definition. The actual kfunc definition is expected to be in kernel. The __bpf_trap() extern function is also encoded in BTF. The name __bpf_trap() is chosen to satisfy reserved identifier requirement. Besides the uninitialized variable case, the builtin function '__builtin_trap' can also generate kfunc __bpf_trap(). For example in [3], we have ``` # define __bpf_unreachable() __builtin_trap() ``` If the compiler didn't remove __builtin_trap() during middle-end optimization, compilation will fail. With this patch, compilation will not fail and __builtin_trap() is converted to __bpf_trap() kfunc. The eventual failure will be in verifier instead of llvm compilation. To keep compilation time failure, user can add an option like `-ftrap-function=<something>`. I tested this patch on bpf selftests and all tests are passed. I also tried original example in [2] and the code looks like below: ``` ; { 0: bf 16 00 00 00 00 00 00 r6 = r1 ; bpf_printk("Start"); 1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll 0000000000000008: R_BPF_64_64 .rodata 3: b4 02 00 00 06 00 00 00 w2 = 0x6 4: 85 00 00 00 06 00 00 00 call 0x6 ; DEFINE_FUNC_CTX_POINTER(data) 5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c) ; bpf_printk("pre ipv6_hdrlen_offset"); 6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll 0000000000000030: R_BPF_64_64 .rodata 8: b4 02 00 00 17 00 00 00 w2 = 0x17 9: 85 00 00 00 06 00 00 00 call 0x6 10: 85 10 00 00 ff ff ff ff call -0x1 0000000000000050: R_BPF_64_32 __bpf_trap 11: 95 00 00 00 00 00 00 00 exit <END> ``` Eventually kernel verifier will emit the following logs: ``` 10: (85) call __bpf_trap#74479 unexpected __bpf_trap() due to uninitialized variable? ``` In another internal sched-ext bpf prog, with the patch we have bpf code: ``` Disassembly of section .text: 0000000000000000 <scx_storage_init_single>: ; { 0: bc 13 00 00 00 00 00 00 w3 = w1 1: b4 01 00 00 00 00 00 00 w1 = 0x0 ; const u32 zero = 0; ... 0000000000003a80 <create_dom>: ; { 1872: bc 16 00 00 00 00 00 00 w6 = w1 ; bpf_printk("dom_id %d", dom_id); 1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll 0000000000003a88: R_BPF_64_64 .rodata 1875: b4 02 00 00 0a 00 00 00 w2 = 0xa 1876: bc 63 00 00 00 00 00 00 w3 = w6 1877: 85 00 00 00 06 00 00 00 call 0x6 ; ret = scx_bpf_create_dsq(dom_id, 0); 1878: bc 61 00 00 00 00 00 00 w1 = w6 1879: b4 02 00 00 00 00 00 00 w2 = 0x0 1880: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq ; domc->node_cpumask = node_data[node_id]; 1881: 85 10 00 00 ff ff ff ff call -0x1 0000000000003ac8: R_BPF_64_32 __bpf_trap 1882: 95 00 00 00 00 00 00 00 exit <END> ``` The verifier can easily report the error too. A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable trapping for 'unreachable' or __builtin_trap. [1] llvm#126858 [2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3 [3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
Marc Suñé (Isovalent, part of Cisco) reported an issue where an uninitialized variable caused generated bpf prog binary code not working as expected. The reproducer is in [1] where the flags “-Wall -Werror” are enabled, but there is no warning and compiler may take advantage of uninit variable to do aggressive optimization.
In discussion [2], various approaches are discussed, e.g., improving compiler to detect undefined behavior due to uninitialized variables, trying to use ubsan (-fsanitize=undefined), and making -ftrivial-auto-var-init=zero as the bpf default flags.
I tried [3] with -ftrivial-auto-var-init=zero and eventually we decided no-go since first it may introduce performance regression and second the prog may still be wrong if the prog expects a non-zero value. The ubsan apprach seems not working as well since it involves runtime callback func ([4]).
The approach here is not to do complicate compiler analysis to detect whether where is undef behavior which may impact final codegen. Rather, we relies on compiler to do its normal transformation and at later IR passes stage, a BPF backend pass is inserted to check whether undef behavior is in IR or not. Note that if undef behavior indeed impacts codes, the compiler will discard those related codes with simple 'undef' or 'unreachable'.
For example, for the case [1], before SCCPPass, the IR looks like
Note that 'undef' is used for switch key due to one of early pass LoopFullUnrollPass. After SCCPPass:
Besides the above case, the following three patterns are also covered:
A bpf flag
-bpf-disable-check-unreachable-ir
is introduced to disable this checking.Tested with bpf selftests and there are no errors issued.
[1] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
[2] https://discourse.llvm.org/t/detect-undefined-behavior-due-to-uninitialized-variables-in-bpf-programs/84116?u=yonghong-song
[3] #125601
[4] https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/ubsan/ubsan_interface.inc
[5] https://lore.kernel.org/lkml/0bf90fc0-2287-4ce0-b810-6e383e695981@linux.dev/