On Thu, Dec 4, 2025 at 8:55 AM David Blaikie <[email protected]> wrote: > > Thanks for reaching out, > > I think, at least for myself, there's still a lot of assumed knowledge about > how BPF works that makes this proposal/question hard to follow. > > Ideally, it'd be good to have enough information to motivate the discussion > without having to dig too far into the BPF implementation details... but I > know that's a hard balance.
Fair point. I will describe bpf use cases in detail below. > > Could you walk through in more detail what BPF tracing is doing, and how it > conflicts with cases where the source signature doesn't match the optimized > signature? It'd be good to see more clearly how the assumption that they do > match causes problems along the way - there might be other solutions to that > problem. bpf tracing, which I referred in the proposal, is to trace functions in final vmlinux binary. The below is a link to bpf selftest: https://github.com/torvalds/linux/tree/master/tools/testing/selftests/bpf/progs and below fentry/fexit shows associated bpf programs have fentry/fexit attachment type. Here attachment means the bpf prog will attach to a particular kernel function and the prog will run before or after the function body itself with the same signature. $ grep fentry *.c | grep SEC access_map_in_map.c:SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") access_map_in_map.c:SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") atomic_bounds.c:SEC("fentry/bpf_fentry_test1") bloom_filter_bench.c:SEC("fentry/" SYS_PREFIX "sys_getpgid") bloom_filter_bench.c:SEC("fentry/" SYS_PREFIX "sys_getpgid") bloom_filter_bench.c:SEC("fentry/" SYS_PREFIX "sys_getpgid") ... test_d_path.c:SEC("fentry/filp_close") ... $ grep fexit *.c | grep SEC bpf_mod_race.c:SEC("fexit/do_init_module") bpf_mod_race.c:SEC("fexit/btf_try_get_module") cgrp_ls_attach_cgroup.c:SEC("fexit/inet_stream_connect") core_kern.c:SEC("fexit/eth_type_trans") dynptr_fail.c:SEC("fexit/skb_tx_error") ... In the above fentry and fexit is the bpf program attach type to functions. The following are two concrete examples for fentry and fexit. SEC("fentry/filp_close") int BPF_PROG(prog_close, struct file *file, void *id) { pid_t pid = bpf_get_current_pid_tgid() >> 32; __u32 cnt = cnt_close; int ret; called_close = 1; if (pid != my_pid) return 0; if (cnt >= MAX_FILES) return 0; ret = bpf_d_path(&file->f_path, paths_close[cnt], MAX_PATH_LEN); rets_close[cnt] = ret; cnt_close++; return 0; } SEC("fexit/inet_stream_connect") int BPF_PROG(update_cookie_tracing, struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags) { struct socket_cookie *p; if (uaddr->sa_family != AF_INET6) return 0; p = bpf_cgrp_storage_get(&socket_cookies, sock->sk->sk_cgrp_data.cgroup, 0, 0); if (!p) return 0; if (p->cookie_key != bpf_get_socket_cookie(sock->sk)) return 0; p->cookie_value |= 0xF0; return 0; } For prog "prog_close" with section "fentry/filp_close", the prog will run right before the entry of kernel function filp_close(). // In fs/open.c of the linux repo. int filp_close(struct file *filp, fl_owner_t id) { int retval; retval = filp_flush(filp, id); fput_close(filp); return retval; } EXPORT_SYMBOL(filp_close); The bpf prog takes the same signature as the kernel function. int BPF_PROG(prog_close, struct file *file, void *id) In include/linux/fs.h, we have typedef void *fl_owner_t; This is to ensure bpf prog gets expected result. The same for fexit example. If the kernel function signature is changed but it is not encoded in vmlinux BTF (vmlinux BTF is generated from dwarf with pahole). Then we may have the following case. For example, we have below source-level kernel function: typedef struct { union { void *kernel; void __user *user; }; bool is_kernel : 1; } sockptr_t; typedef sockptr_t bpfptr_t; static int map_create(union bpf_attr *attr, bpfptr_t uattr) { ... } If the user wants to trace map_create, it will do something like SEC("fentry/map_create") int BPF_PROG(trace_map_create, union bpf_attr *attr, bpfptr_t uattr) { ... } Note that in the above uattr has 16 bytes and it will consume two registers for x86. But compiler optimization did change map_create() signature to static int map_create(union bpf_attr *attr, bool is_kernel) since for 'bpfptr_t uattr', only 'is_kernel' is used in the function. For this case, SEC("fentry/map_create") int BPF_PROG(trace_map_create, union bpf_attr *attr, bpfptr_t uattr) { ... } will generate incorrect result for bpf prog since when bpf prog tries to use uattr.is_kernel but uattr.is_kernel could be a garbage. If we can encode the true signature like SEC("fentry/map_create") int BPF_PROG(trace_map_create, union bpf_attr *attr, bool is_kernel) { ... } then everything should be okay. Similarly, true signature is also useful to fexit (see above) and also useful for kprobes. Although kprobes does not require signatures but it still map registers to source-level signature at this point. Hopefully the above use case can show why we need true signatures in dwarf. The vmlinux BTF is generated from dwarf by pahole. > > On Thu, Dec 4, 2025 at 8:37 AM Y Song <[email protected]> wrote: >> >> Hi, >> >> Recently I have been working on to generate true signatures >> to dwarf in LLVM ([1]). Currently, the dwarf DISubprogram >> encodes the source-level signature. But compiler optimization >> may modify signatures, e.g., from >> static int foo(int a, int b, int c) { ... } >> to >> static int foo(int a, int c) { ... } >> where the parameter 'int b' is removed by the compiler. >> >> In most cases signature won't change after >> compiler optimization, but some function signatures >> indeed get changed. Encoding those changed signatures in >> dwarf can help users easily identify true signature and >> improve productivity. >> >> Motivation >> ========== >> >> My particular use case is for bpf-based linux kernel >> tracing. When tracing a kernel function, the user would like >> to know the actual signature. This is critical. >> >> For example, if the actual signature is >> static int foo(int a, int c) { ... } >> and the source signature is >> static int foo(int a, int b, int c) { ... } >> >> If users trace function foo() with signature >> int foo(int a, int b, int c); >> then user may get incorrect result as the >> above source-level parameter 'int b' actually takes >> the value from true-signature 'int c', and the source-level >> parameter 'int c' actually takes a garbage value. >> >> In this case, true signature is essential for better and >> correct tracing. >> >> The link [2] shows how true signature may be used in pahole >> to generate vmlinux BTF which has true signatures. >> >> The link [3] has some history and discussion about what >> kind of dwarf format we should take. >> >> Proposed Format >> =============== >> >> Currently, with [1] the proposed format is >> >> $ clang -O2 -c -g test.c -mllvm -enable-changed-func-dbinfo >> $ llvm-dwarfdump test.o >> 0x0000000c: DW_TAG_compile_unit >> ... >> 0x0000005c: DW_TAG_subprogram >> DW_AT_low_pc (0x0000000000000010) >> DW_AT_high_pc (0x0000000000000015) >> DW_AT_frame_base (DW_OP_reg7 RSP) >> DW_AT_call_all_calls (true) >> DW_AT_name ("foo") >> DW_AT_decl_file >> ("/home/yhs/tests/sig-change/deadarg/test.c") >> DW_AT_decl_line (3) >> DW_AT_prototyped (true) >> DW_AT_calling_convention (DW_CC_nocall) >> DW_AT_type (0x000000b1 "char *") >> >> 0x0000006c: DW_TAG_formal_parameter >> DW_AT_location (DW_OP_reg5 RDI) >> DW_AT_name ("a") >> DW_AT_decl_file >> ("/home/yhs/tests/sig-change/deadarg/test.c") >> DW_AT_decl_line (3) >> DW_AT_type (0x000000ba "t *") >> >> 0x00000076: DW_TAG_formal_parameter >> DW_AT_name ("b") >> DW_AT_decl_file >> ("/home/yhs/tests/sig-change/deadarg/test.c") >> DW_AT_decl_line (3) >> DW_AT_type (0x000000ce "int") >> >> 0x0000007e: DW_TAG_formal_parameter >> DW_AT_location (DW_OP_reg4 RSI) >> DW_AT_name ("d") >> DW_AT_decl_file >> ("/home/yhs/tests/sig-change/deadarg/test.c") >> DW_AT_decl_line (3) >> DW_AT_type (0x000000ba "t *") >> >> 0x00000088: DW_TAG_call_site >> ... >> >> 0x0000009d: NULL >> ... >> 0x000000d2: DW_TAG_inlined_subroutine >> DW_AT_name ("foo") >> DW_AT_type (0x000000b1 "char *") >> DW_AT_artificial (true) >> DW_AT_specification (0x0000005c "foo") >> >> 0x000000dc: DW_TAG_formal_parameter >> DW_AT_name ("a") >> DW_AT_type (0x000000ba "t *") >> >> 0x000000e2: DW_TAG_formal_parameter >> DW_AT_name ("d") >> DW_AT_type (0x000000ba "t *") >> >> 0x000000e8: NULL >> >> Basically immediately under tag DW_TAG_compile_unit, tag >> DW_TAG_inlined_subroutine >> encodes the true signature. The DW_AT_specification will refer to >> the actual DISubprogram. The format has been agreed with >> Jose Marchesi and David Faust from gcc. >> >> The following is another example: >> >> $ clang -O2 -c -g test.c -mllvm -enable-changed-func-dbinfo >> $ llvm-dwarfdump test.o >> ... >> 0x0000004e: DW_TAG_subprogram >> DW_AT_low_pc (0x0000000000000010) >> DW_AT_high_pc (0x0000000000000015) >> DW_AT_frame_base (DW_OP_reg7 RSP) >> DW_AT_call_all_calls (true) >> DW_AT_name ("foo") >> DW_AT_decl_file >> ("/home/yhs/tests/sig-change/struct/test.c") >> DW_AT_decl_line (2) >> DW_AT_prototyped (true) >> DW_AT_calling_convention (DW_CC_nocall) >> DW_AT_type (0x0000006d "long") >> >> 0x0000005e: DW_TAG_formal_parameter >> DW_AT_location (DW_OP_piece 0x8, DW_OP_reg5 >> RDI, DW_OP_piece 0x8) >> DW_AT_name ("arg") >> DW_AT_decl_file >> ("/home/yhs/tests/sig-change/struct/test.c") >> DW_AT_decl_line (2) >> DW_AT_type (0x00000099 "t") >> >> 0x0000006c: NULL >> ... >> 0x00000088: DW_TAG_inlined_subroutine >> DW_AT_name ("foo") >> DW_AT_type (0x0000006d "long") >> DW_AT_artificial (true) >> DW_AT_specification (0x0000004e "foo") >> >> 0x00000092: DW_TAG_formal_parameter >> DW_AT_name ("b") >> DW_AT_type (0x0000006d "long") >> >> 0x00000098: NULL >> >> In this case, the source-level parameter 'arg' is a 16-byte struct >> 'struct t {long a; long b;};'. But function only uses the second field >> of the struct, so the true signature is 'long foo(long b)'. With >> true signature, users can easily use it to construct the bpf-based >> tracing program. >> >> For the above 'DW_TAG_inlined_subroutine' format is not in dwarf >> standard. Suggested by Orlando Cazalet-Hyams and Jeremy Morse (from llvm >> side), >> I would like to get some feedback/opinion from dwarf community about how to >> encode changed signatures in dwarf in a better way. >> >> Any suggestions are welcome! >> >> [1] https://github.com/llvm/llvm-project/pull/165310 >> [2] >> https://lore.kernel.org/bpf/[email protected]/ >> [3] >> https://discourse.llvm.org/t/rfc-identify-func-signature-change-in-llvm-compiled-kernel-image/82609 -- Dwarf-discuss mailing list [email protected] https://lists.dwarfstd.org/mailman/listinfo/dwarf-discuss
