* [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf
@ 2025-11-11 17:04 Yonghong Song
2025-11-11 17:04 ` [PATCH dwarves 1/3] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Yonghong Song
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Yonghong Song @ 2025-11-11 17:04 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust,
Jose E . Marchesi, kernel-team
Current vmlinux BTF encoding is based on the source level signatures.
But the compiler may do some optimization and changed the signature.
If the user tried with source level signature, their initial implementation
may have wrong results and then the user need to check what is the
problem and work around it, e.g. through kprobe since kprobe does not
need vmlinux BTF.
The following is a concrete example for [1].
The original source signature:
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) { ... }
After compiler optimization, the signature becomes:
static int map_create(union bpf_attr *attr, bool uattr__coerce1) { ... }
In the above, uattr__coerce1 corresponds to 'is_kernel' field in sockptr_t.
Here, the suffix '__coerce1' refers to the second 64bit value in
sockptr_t. The first 64bit value will be '__coerce0' if that value
is used instead.
To do proper tracing, it would be good for the users to know the
changed signature. With the actual signature, both kprobe and fentry
should work as usual. This can avoid user surprise and improve
developer productivity.
The llvm compiler patch [1] collects true signature and encoded those
functions in dwarf. pahole will process these functions and
replace old signtures with true signatures. Additionally,
new functions (e.g., foo.llvm.<hash>) can be encoded in
vmlinux BTF as well.
Patches 1/2 are refactor patches. Patch 3 has the detailed explanation
in commit message and implements the logic to encode replaced or new
signatures to vmlinux BTF. Please see Patch 3 for details.
[1] https://github.com/llvm/llvm-project/pull/165310
Yonghong Song (3):
btf_encoder: Refactor elf_functions__new() with struct btf_encoder as
argument
bpf_encoder: Refactor a helper elf_function__check_and_push_sym()
pahole: Replace or add functions with true signatures in btf
btf_encoder.c | 79 +++++++++++++++++++++++++++++++++++---------
dwarf_loader.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++
dwarves.h | 4 +++
pahole.c | 9 +++++
4 files changed, 165 insertions(+), 16 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH dwarves 1/3] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument 2025-11-11 17:04 [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf Yonghong Song @ 2025-11-11 17:04 ` Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 2/3] bpf_encoder: Refactor a helper elf_function__check_and_push_sym() Yonghong Song ` (2 subsequent siblings) 3 siblings, 0 replies; 8+ messages in thread From: Yonghong Song @ 2025-11-11 17:04 UTC (permalink / raw) To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, kernel-team For elf_functions__new(), replace original argument 'Elf *elf' with 'struct btf_encoder *encoder' for future use. Signed-off-by: Yonghong Song <yonghong.song@linux.dev> --- btf_encoder.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/btf_encoder.c b/btf_encoder.c index 03bc3c7..1c69577 100644 --- a/btf_encoder.c +++ b/btf_encoder.c @@ -187,11 +187,13 @@ static inline void elf_functions__delete(struct elf_functions *funcs) static int elf_functions__collect(struct elf_functions *functions); -struct elf_functions *elf_functions__new(Elf *elf) +struct elf_functions *elf_functions__new(struct btf_encoder *encoder) { struct elf_functions *funcs; + Elf *elf; int err; + elf = encoder->cu->elf; funcs = calloc(1, sizeof(*funcs)); if (!funcs) { err = -ENOMEM; @@ -1509,7 +1511,7 @@ static struct elf_functions *btf_encoder__elf_functions(struct btf_encoder *enco funcs = elf_functions__find(encoder->cu->elf, &encoder->elf_functions_list); if (!funcs) { - funcs = elf_functions__new(encoder->cu->elf); + funcs = elf_functions__new(encoder); if (funcs) list_add(&funcs->node, &encoder->elf_functions_list); } -- 2.47.3 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH dwarves 2/3] bpf_encoder: Refactor a helper elf_function__check_and_push_sym() 2025-11-11 17:04 [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 1/3] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Yonghong Song @ 2025-11-11 17:04 ` Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 3/3] pahole: Replace or add functions with true signatures in btf Yonghong Song 2025-11-13 16:45 ` [PATCH dwarves 0/3] " Alan Maguire 3 siblings, 0 replies; 8+ messages in thread From: Yonghong Song @ 2025-11-11 17:04 UTC (permalink / raw) To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, kernel-team Inside elf_functions__collect(), refactor portion of the code to be a helper elf_function__check_and_push_sym(). Such a helper will be used in the next patch. Signed-off-by: Yonghong Song <yonghong.song@linux.dev> --- btf_encoder.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/btf_encoder.c b/btf_encoder.c index 1c69577..d28af58 100644 --- a/btf_encoder.c +++ b/btf_encoder.c @@ -2181,10 +2181,22 @@ static inline int elf_function__push_sym(struct elf_function *func, struct elf_f return 0; } +static int elf_function__check_and_push_sym(struct elf_function *func, const char *sym_name, int st_value) +{ + struct elf_function_sym func_sym; + + if (!func->name) + return -ENOMEM; + + func_sym.name = sym_name; + func_sym.addr = st_value; + + return elf_function__push_sym(func, &func_sym); +} + static int elf_functions__collect(struct elf_functions *functions) { uint32_t nr_symbols = elf_symtab__nr_symbols(functions->symtab); - struct elf_function_sym func_sym; struct elf_function *func, *tmp; const char *sym_name, *suffix; Elf32_Word sym_sec_idx; @@ -2224,15 +2236,7 @@ static int elf_functions__collect(struct elf_functions *functions) else func->name = strdup(sym_name); - if (!func->name) { - err = -ENOMEM; - goto out_free; - } - - func_sym.name = sym_name; - func_sym.addr = sym.st_value; - - err = elf_function__push_sym(func, &func_sym); + err = elf_function__check_and_push_sym(func, sym_name, sym.st_value); if (err) goto out_free; -- 2.47.3 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH dwarves 3/3] pahole: Replace or add functions with true signatures in btf 2025-11-11 17:04 [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 1/3] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 2/3] bpf_encoder: Refactor a helper elf_function__check_and_push_sym() Yonghong Song @ 2025-11-11 17:04 ` Yonghong Song 2025-11-13 16:45 ` [PATCH dwarves 0/3] " Alan Maguire 3 siblings, 0 replies; 8+ messages in thread From: Yonghong Song @ 2025-11-11 17:04 UTC (permalink / raw) To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, kernel-team Overview ======== The llvm pull request ([1]) is able to emit dwarf data for changed-signature and new functions. The new dwarf format has tag DW_TAG_inlined_subroutine and directly under CU. There is no other change for existing dwarf. See further down examples. When building linux kernel with [1], additional functions will be added to dwarf: - A function with signature changed compared to source. For example, original function foo(int a, int b) is optimized too foo(int b). The foo(int b) will be added to dwarf and the original foo(int a, int b) is not touched. - A function generated by the compiler which typically will have '.' in the function name, for example, in thin-lto mode, a function could be foo.llvm.<hash>. A new feature 'true_signature' is introduced in pahole. Only if 'true_signature' is enabled by pahole in linux kernel, pahole will either replace the old function with new function for correct signature in vmlinux/module btf, or add new functions to the btf like foo.llvm.<hash>. Below are some more detailed illustration of new dwarf format, how compiler controls what to generate, and how newly added functions (e.g., foo.llvm.<hash>) could be used, etc. Added Dwarf Contents ==================== The following are some examples from linux kernel. Example 1 --------- 0x000411f0: DW_TAG_inlined_subroutine DW_AT_name ("split_fs_names") DW_AT_type (0x00023368 "int") DW_AT_artificial (true) DW_AT_specification (0x00040d1f "split_fs_names") 0x000411fc: DW_TAG_formal_parameter DW_AT_name ("page") DW_AT_type (0x000233bb "char *") 0x00041204: NULL vmlinux BTF: [131534] FUNC_PROTO '(anon)' ret_type_id=14 vlen=1 'page' type_id=100 [131535] FUNC 'split_fs_names' type_id=131534 linkage=static Signature changed due to optimization. The source signature is static int __init split_fs_names(char *page, size_t size) { ... } The vmlinux BTF will encode 'int split_fs_names(char *page)'. Example 2 --------- 0x01886077: DW_TAG_inlined_subroutine DW_AT_name ("map_create") DW_AT_type (0x01854182 "int") DW_AT_artificial (true) DW_AT_specification (0x01870e96 "map_create") 0x01886083: DW_TAG_formal_parameter DW_AT_name ("attr") DW_AT_type (0x01854370 "bpf_attr *") 0x0188608b: DW_TAG_formal_parameter DW_AT_name ("uattr__coerce1") DW_AT_type (0x01855b14 "bool") 0x01886093: NULL vmlinux BTF: [119] TYPEDEF 'bool' type_id=120 [120] INT '_Bool' size=1 bits_offset=0 nr_bits=8 encoding=BOOL ... [106699] FUNC_PROTO '(anon)' ret_type_id=14 vlen=2 'attr' type_id=707 'uattr__coerce1' type_id=119 [106700] FUNC 'map_create' type_id=106699 linkage=static Signature changed due to optimization. The source signature is static int map_create(union bpf_attr *attr, bpfptr_t uattr) { ... } Example 3 --------- 0x0060dd56: DW_TAG_inlined_subroutine DW_AT_name ("enable_step.llvm.569499763459584554") DW_AT_artificial (true) DW_AT_specification (0x0060d396 "enable_step") 0x0060dd5d: DW_TAG_formal_parameter DW_AT_name ("child") DW_AT_type (0x006046b9 "task_struct *") 0x0060dd64: DW_TAG_formal_parameter DW_AT_name ("block") DW_AT_type (0x00605b99 "bool") 0x0060dd6b: NULL [90683] FUNC_PROTO '(anon)' ret_type_id=0 vlen=2 'child' type_id=321 'block' type_id=45 [90684] FUNC 'enable_step' type_id=90683 linkage=static [90685] FUNC 'enable_step.llvm.569499763459584554' type_id=90683 linkage=static No signature change. But the thin-lto promoted the function enable_step() to global, hence enable_step.llvm.<hash>. The existing dwarf should already have 'enable_step' so the above puts 'enable_step.llvm.569499763459584554' as the name. Example 4 --------- 0x00b920fb: DW_TAG_inlined_subroutine DW_AT_name ("__ioremap_caller") DW_AT_linkage_name ("__ioremap_caller.llvm.766076626088609549") DW_AT_type (0x00b83dd6 "void *") DW_AT_artificial (true) DW_AT_specification (0x00b8420d "__ioremap_caller") 0x00b92109: DW_TAG_formal_parameter DW_AT_name ("phys_addr") DW_AT_type (0x00b850b2 "resource_size_t") 0x00b92111: DW_TAG_formal_parameter DW_AT_name ("size") DW_AT_type (0x00b841dd "unsigned long") 0x00b92119: DW_TAG_formal_parameter DW_AT_name ("pcm") DW_AT_type (0x00b84562 "page_cache_mode") 0x00b92121: DW_TAG_formal_parameter DW_AT_name ("caller") DW_AT_type (0x00b83dd6 "void *") 0x00b92129: NULL The corresponding vmlinux BTF: [60222] FUNC_PROTO '(anon)' ret_type_id=17 vlen=4 'phys_addr' type_id=56 'size' type_id=15 'pcm' type_id=5633 'caller' type_id=17 [60223] FUNC '__ioremap_caller' type_id=60222 linkage=static [60224] FUNC '__ioremap_caller.llvm.766076626088609549' type_id=60222 linkage=static For this one, the signature and func name changed for __ioremap_caller(). The original signature is static void __iomem * __ioremap_caller(resource_size_t phys_addr, unsigned long size, enum page_cache_mode pcm, void *caller, bool encrypted) { ... } Build Linux Kernel ================== The following kernel patch is needed to support true signatures: diff --git a/Makefile b/Makefile index 088565edc911..a35dd86c7639 100644 --- a/Makefile +++ b/Makefile @@ -1002,6 +1002,11 @@ endif ifdef CONFIG_LTO_CLANG ifdef CONFIG_LTO_CLANG_THIN CC_FLAGS_LTO := -flto=thin -fsplit-lto-unit +ifeq ($(call test-ge, $(CONFIG_PAHOLE_VERSION), 130),y) +ifeq ($(call clang-min-version, 220000),y) +KBUILD_LDFLAGS += $(call ld-option, -mllvm -enable-changed-func-dbinfo) +endif +endif else CC_FLAGS_LTO := -flto endif @@ -1009,6 +1014,12 @@ CC_FLAGS_LTO += -fvisibility=hidden # Limit inlining across translation units to reduce binary size KBUILD_LDFLAGS += -mllvm -import-instr-limit=5 +else +ifeq ($(call test-ge, $(CONFIG_PAHOLE_VERSION), 130),y) +ifeq ($(call clang-min-version, 220000),y) +KBUILD_CFLAGS += -mllvm -enable-changed-func-dbinfo +endif +endif endif ifdef CONFIG_LTO diff --git a/scripts/Makefile.btf b/scripts/Makefile.btf index db76335dd917..2f66cc0bc2b5 100644 --- a/scripts/Makefile.btf +++ b/scripts/Makefile.btf @@ -23,7 +23,7 @@ else # Switch to using --btf_features for v1.26 and later. pahole-flags-$(call test-ge, $(pahole-ver), 126) = -j$(JOBS) --btf_features=encode_force,var,float,enum64,decl_tag,type_tag,optimized_func,consistent_func,decl_tag_kfuncs -pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes +pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes --btf_features=true_signature Option to avoid import <foo>.<...> functions -------------------------------------------- There is a debate whether we should really introduce dwarf function like <foo>.llvm.<hash> into BTF. Current pahole searches kallsyms and for symbol like <foo>.llvm.<hash>, only <foo> is represented as an elf symbol. This typically is true as if <foo>.llvm.<hash> symbol exists, symbol <foo> should not exist. In such cases, one symbol corresponds to one dwarf function entry should work. However, in llvm, there is a pass FunctionSpecializer which can do specialization of original functions ([1]). For example, there is a static function foo(a, b, c, d) and it is used in multiple call sites in the same function. It is possible the llvm may generate the following clones: foo.specialized.1(a, b, c) foo.specialized.2(a, b, d) foo.specialized.3(b, c, d) The default maximum number of specialization is 3. If the original function foo() is not used, then the function foo() will be removed. In such cases, we probably should import foo.<...>() functions if we want to trace those functions. In [1], I give one example where print_cpu.specialized.1(...) is created. But I agree that this is a corner case. Not sure whether gcc has similar situation or not. Anyway, the llvm option "-mllvm -skip-dotted-func" can be used to skip all functions with <foo>.<...>. The following is the related build flag: KBUILD_LDFLAGS += $(call ld-option, -mllvm -enable-changed-func-dbinfo -mllvm -skip-dotted-func) KBUILD_CFLAGS += -mllvm -enable-changed-func-dbinfo -mllvm -skip-dotted-func In this case, the function like __ioremap_caller.llvm.766076626088609549 will not appear in generated new dwarfs. Remarks ======= The llvm patch [1] supports remarks which help dump out some debug info, e.g., why a particular function is skipped or generated in special way. The below kernel build option can enable remarks: KBUILD_LDFLAGS += $(call ld-option, -mllvm -enable-changed-func-dbinfo \ --opt-remarks-filename --opt-remarks-passes emit-changed-func-debuginfo) KBUILD_CFLAGS += -mllvm -enable-changed-func-dbinfo -fsave-optimization-record \ -foptimization-record-passes=emit-changed-func-debuginfo For non-lto mode, the remark files will be in the same directory as the corresponding object file. For lto mode, the remark files will be in the build directory as invoked by ld.lld. The following command can help find any non-empty remark file which can help debugging. find . -type f -name '*.opt.yaml.*' -size +0c Currently, there are no remarks generated. The following is a remark example in the commit message in [1]. $ cat test.opt.yaml --- !Passed Pass: emit-changed-func-debuginfo Name: FindNoDIVariable DebugLoc: { File: test.c, Line: 1, Column: 0 } Function: callee Args: - String: 'create a new int type ' - ArgName: '' - String: '(' - ArgIndex: '0' - String: ')' ... Some Statictics =============== Comparing with and without dotted functions: ------------------------------------------- without -mllvm -enable-changed-func-dbinfo: non-lto: 66051 functions lto: 66227 functions additional functions with -mllvm -enable-changed-func-dbinfo: non-lto: 894 new DW_TAG_inlined_subroutine entries lto: 2993 ... additional functions with -mllvm -enable-changed-func-dbinfo -mllvm -skip-dotted-func non-lto: 894 ... lto: 1152 ... So the option '-mllvm -skip-dotted-func' does filter out lots of funcitons like <foo>.llvm.<hash>. vmlinux BTF size: ---------------- size non-lto without new signatures: 0x623a4a non-lto with default new signatures: 0x6274c7 non-lto with additional -skip-dotted-func: 0x6274c7 lto without new signatures: 0x63386e lto with default new signatures: 0x651281 lto with additional -skip-dotted-func: 0x63776b For non-lto build, the vmlinux BTF increases 14973 bytes compared to the build without default new signatures. For lto build, the vmlinux BTF increases 121363 bytes compared to the build without default new signatures. If enabling -skip-dotted-func, the increase will be 16125 bytes. So looks like the BTF increase should not be a concern here. Should We Import Functions with Dot? ==================================== Current pahole removed ".<...>" suffix in kallsyms so the function name can match the existing dwarf. This way, the BTF can be generated based on dwarf subprogram entries. In [1], function with dot (e.g., foo.llvm.<hash>) is allowed to be in dwarf. This paves the way to have such functions in kernel BTF as well. Previously, fentry/fexit for function enable_step.llvm.569499763459584554() won't work since in vmlinux BTF, the name is 'enable_step'. But 'enable_step' is not in kallsyms, so bpf program load will fail. Now we can have a function entry with name enable_step.llvm.569499763459584554 in BTF and the name matches with kernel, so fentry/fexit can be successful although this needs a little bit user work: skel = fentry_lto__open(); if (!ASSERT_OK_PTR(skel, "fentry_lto__open")) return; /* The fentry target is 'start_thread_common' or 'start_thread_common.llvm.<hash>' */ /* User need to find out whether either or them exist in ksyms. * Let start_thread_common.llvm.<hash> exists. */ prog = skel->progs.test1; bpf_program__set_attach_target(prog, 0, "start_thread_common.llvm.<hash>"); err = fentry_lto__load(skel); if (!ASSERT_OK(err, "fentry_lto__load")) goto out; But there are alternative solutions to handle the above. Solution 1: In [2] which utilize the kprobe_multi with option 'unique_match'. It should work in most cases except something like functions foobar() and foo.llvm.<hash>(). In such cases, unique_match 'foo*' will not work since it will match both functions. But maybe regex could be 'foo.llvm.*'? Solution 2: With function name 'foo', the kernel can search both 'foo' and 'foo.<...>' in kallsyms. If any of two symbols is uniquely available, the kernel just picks it and proceed as normal. Linux Kernel BPF Selftest Resutls ================================= Tried with both non-lto and lto mode, there are no additional selftest failures compared to without this patch set. Future Work =========== We need gcc side to generate true signatures as well with ideally the same dwarf format. [1] https://github.com/llvm/llvm-project/pull/165310 [2] https://lore.kernel.org/bpf/20250109174023.3368432-1-yonghong.song@linux.dev/ Signed-off-by: Yonghong Song <yonghong.song@linux.dev> --- btf_encoder.c | 55 +++++++++++++++++++++++++++---- dwarf_loader.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ dwarves.h | 4 +++ pahole.c | 9 +++++ 4 files changed, 150 insertions(+), 7 deletions(-) diff --git a/btf_encoder.c b/btf_encoder.c index d28af58..0f9a4e3 100644 --- a/btf_encoder.c +++ b/btf_encoder.c @@ -89,6 +89,7 @@ struct btf_encoder_func_state { uint8_t inconsistent_proto:1; uint8_t uncertain_parm_loc:1; uint8_t ambiguous_addr:1; + uint8_t true_signature:1; int ret_type_id; struct btf_encoder_func_parm *parms; struct btf_encoder_func_annot *annots; @@ -145,11 +146,13 @@ struct btf_encoder { skip_encoding_decl_tag, tag_kfuncs, gen_distilled_base, - encode_attributes; + encode_attributes, + dotted_true_signature; uint32_t array_index_id; struct elf_secinfo *secinfo; size_t seccnt; int encode_vars; + int nr_true_signature_funcs; struct { struct btf_encoder_func_state *array; int cnt; @@ -185,7 +188,7 @@ static inline void elf_functions__delete(struct elf_functions *funcs) free(funcs); } -static int elf_functions__collect(struct elf_functions *functions); +static int elf_functions__collect(struct btf_encoder *encoder, struct elf_functions *functions); struct elf_functions *elf_functions__new(struct btf_encoder *encoder) { @@ -207,7 +210,7 @@ struct elf_functions *elf_functions__new(struct btf_encoder *encoder) } funcs->elf = elf; - err = elf_functions__collect(funcs); + err = elf_functions__collect(encoder, funcs); if (err < 0) goto out_delete; @@ -1250,6 +1253,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi state->elf = func; state->nr_parms = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0); state->ret_type_id = ftype->tag.type == 0 ? 0 : encoder->type_id_off + ftype->tag.type; + state->true_signature = ftype->true_signature; if (state->nr_parms > 0) { state->parms = zalloc(state->nr_parms * sizeof(*state->parms)); if (!state->parms) { @@ -1409,7 +1413,20 @@ static int saved_functions_cmp(const void *_a, const void *_b) const struct btf_encoder_func_state *a = _a; const struct btf_encoder_func_state *b = _b; - return elf_function__name_cmp(a->elf, b->elf); + int ret = strcmp(a->elf->name, b->elf->name); + if (ret) + return ret; + + /* Two identical names, if one has true_signture attribute, make sure the + * one has true_attirube in earlier place. This way, the other function + * will be filtered out during later btf generation. + */ + if (a->true_signature) + return -1; + else if (b->true_signature) + return 1; + + return 0; } static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_encoder_func_state *b) @@ -1419,6 +1436,10 @@ static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_ if (a->elf != b->elf) return 1; + /* Skip if any of the func states represents a true_signature function. */ + if (a->true_signature || b->true_signature) + return 0; + optimized = a->optimized_parms | b->optimized_parms; unexpected = a->unexpected_reg | b->unexpected_reg; inconsistent = a->inconsistent_proto | b->inconsistent_proto; @@ -2194,9 +2215,10 @@ static int elf_function__check_and_push_sym(struct elf_function *func, const cha return elf_function__push_sym(func, &func_sym); } -static int elf_functions__collect(struct elf_functions *functions) +static int elf_functions__collect(struct btf_encoder *encoder, struct elf_functions *functions) { uint32_t nr_symbols = elf_symtab__nr_symbols(functions->symtab); + bool dotted_true_signature = encoder->dotted_true_signature; struct elf_function *func, *tmp; const char *sym_name, *suffix; Elf32_Word sym_sec_idx; @@ -2204,10 +2226,10 @@ static int elf_functions__collect(struct elf_functions *functions) uint32_t core_id; GElf_Sym sym; - /* We know that number of functions is less than number of symbols, + /* We know that number of functions is less than number of symbols plus number of true signature funcs, * so we can overallocate temporarily. */ - functions->entries = calloc(nr_symbols, sizeof(*functions->entries)); + functions->entries = calloc(nr_symbols + encoder->nr_true_signature_funcs, sizeof(*functions->entries)); if (!functions->entries) { err = -ENOMEM; goto out_free; @@ -2241,6 +2263,23 @@ static int elf_functions__collect(struct elf_functions *functions) goto out_free; functions->cnt++; + + /* If the dwarf true signature addition does not have any function with dot, + * do nothing. Otherwise, if the suffix is not NULL, i.e, it has '.' in the + * function, proceed to record this elf function so later BTF encoding + * can succeed. + */ + if (!dotted_true_signature || !suffix) + continue; + + func = &functions->entries[functions->cnt]; + func->name = strdup(sym_name); + + err = elf_function__check_and_push_sym(func, sym_name, sym.st_value); + if (err) + goto out_free; + + functions->cnt++; } /* At this point functions->entries is an unordered array of elf_function @@ -2546,6 +2585,8 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam encoder->tag_kfuncs = conf_load->btf_decl_tag_kfuncs; encoder->gen_distilled_base = conf_load->btf_gen_distilled_base; encoder->encode_attributes = conf_load->btf_attributes; + encoder->dotted_true_signature = conf_load->dotted_true_signature; + encoder->nr_true_signature_funcs = conf_load->nr_true_signature_funcs; encoder->verbose = verbose; encoder->has_index_type = false; encoder->need_index_type = false; diff --git a/dwarf_loader.c b/dwarf_loader.c index 79be3f5..1c1cfb2 100644 --- a/dwarf_loader.c +++ b/dwarf_loader.c @@ -537,6 +537,19 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die) INIT_LIST_HEAD(&tag->node); } +static void tag__init_true_signature(struct tag *tag, struct cu *cu, Dwarf_Die *die) +{ + struct dwarf_tag *dtag = tag__dwarf(tag); + + tag->tag = DW_TAG_subprogram; + dtag->id = dwarf_dieoffset(die); + dwarf_tag__set_attr_type(dtag, type, die, DW_AT_type); + tag->recursivity_level = 0; + tag->attributes = NULL; + + INIT_LIST_HEAD(&tag->node); +} + static struct tag *tag__new(Dwarf_Die *die, struct cu *cu) { struct tag *tag = tag__alloc(cu, sizeof(*tag)); @@ -1487,6 +1500,22 @@ static void ftype__init(struct ftype *ftype, Dwarf_Die *die, struct cu *cu) ftype->template_parameter_pack = NULL; } +static void ftype__init_true_signature(struct ftype *ftype, Dwarf_Die *die, struct cu *cu) +{ +#ifndef NDEBUG + const uint16_t tag = dwarf_tag(die); + assert(tag == DW_TAG_inlined_subroutine); +#endif + tag__init_true_signature(&ftype->tag, cu, die); + INIT_LIST_HEAD(&ftype->parms); + INIT_LIST_HEAD(&ftype->template_type_params); + INIT_LIST_HEAD(&ftype->template_value_params); + ftype->nr_parms = 0; + ftype->unspec_parms = 0; + ftype->template_parameter_pack = NULL; + ftype->true_signature = 1; +} + static struct ftype *ftype__new(Dwarf_Die *die, struct cu *cu) { struct ftype *ftype = tag__alloc(cu, sizeof(*ftype)); @@ -2468,9 +2497,69 @@ static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu, return tag; } +static int die__process_inline_subroutine(Dwarf_Die *die, struct cu *cu, struct conf_load *conf, const char *name) +{ + struct function *function = tag__alloc(cu, sizeof(*function)); + + if (function != NULL) { + ftype__init_true_signature(&function->proto, die, cu); + lexblock__init(&function->lexblock, cu, die); + function->name = name; + tag__set_spec(&function->proto.tag, die); + INIT_LIST_HEAD(&function->annots); + function->cu_total_size_inline_expansions = 0; + function->cu_total_nr_inline_expansions = 0; + function->priv = NULL; + } + + if (function != NULL && + die__process_function(die, &function->proto, &function->lexblock, cu, conf) != 0) { + function__delete(function, cu); + function = NULL; + } + + struct tag *tag = function ? &function->proto.tag : NULL; + if (tag == NULL) + return -ENOMEM; + + uint32_t id = 0; + tag->top_level = 1; + cu__add_tag(cu, tag, &id); + cu__hash(cu, tag); + struct dwarf_tag *dtag = tag__dwarf(tag); + dtag->small_id = id; + + conf->nr_true_signature_funcs++; + if (!conf->dotted_true_signature && !!strchr(name, '.')) + conf->dotted_true_signature = true; + + return 0; +} + static int die__process_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *conf) { do { + // special process DW_TAG_inlined_subroutine + if (conf->true_signature && dwarf_tag(die) == DW_TAG_inlined_subroutine) { + const char *name; + int ret; + + name = attr_string(die, DW_AT_name, conf); + ret = die__process_inline_subroutine(die, cu, conf, name); + if (ret) + return ret; + + name = attr_string(die, DW_AT_linkage_name, conf); + if (!name) + continue; + + ret = die__process_inline_subroutine(die, cu, conf, name); + if (ret) + return ret; + + continue; + } + struct tag *tag = die__process_tag(die, cu, 1, conf); if (tag == NULL) return -ENOMEM; diff --git a/dwarves.h b/dwarves.h index 21d4166..dfd43e1 100644 --- a/dwarves.h +++ b/dwarves.h @@ -79,6 +79,7 @@ struct conf_load { void *cookie; char *format_path; int nr_jobs; + int nr_true_signature_funcs; bool extra_dbg_info; bool use_obstack; bool fixup_silly_bitfields; @@ -101,6 +102,8 @@ struct conf_load { bool btf_decl_tag_kfuncs; bool btf_gen_distilled_base; bool btf_attributes; + bool true_signature; + bool dotted_true_signature; uint8_t hashtable_bits; uint8_t max_hashtable_bits; uint16_t kabi_prefix_len; @@ -1023,6 +1026,7 @@ struct ftype { uint8_t processed:1; uint8_t inconsistent_proto:1; uint8_t uncertain_parm_loc:1; + uint8_t true_signature:1; struct list_head template_type_params; struct list_head template_value_params; struct template_parameter_pack *template_parameter_pack; diff --git a/pahole.c b/pahole.c index ef01e58..b81d03a 100644 --- a/pahole.c +++ b/pahole.c @@ -1153,6 +1153,7 @@ ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version; #define ARG_padding 348 #define ARGP_with_embedded_flexible_array 349 #define ARGP_btf_attributes 350 +#define ARGP_true_signature 351 /* --btf_features=feature1[,feature2,..] allows us to specify * a list of requested BTF features or "default" to enable all default @@ -1234,6 +1235,7 @@ struct btf_feature { BTF_NON_DEFAULT_FEATURE(global_var, encode_btf_global_vars, false), BTF_NON_DEFAULT_FEATURE_CHECK(attributes, btf_attributes, false, attributes_check), + BTF_NON_DEFAULT_FEATURE(true_signature, true_signature, false), }; #define BTF_MAX_FEATURE_STR 1024 @@ -1817,6 +1819,11 @@ static const struct argp_option pahole__options[] = { .key = ARGP_btf_attributes, .doc = "Allow generation of attributes in BTF. Attributes are the type tags and decl tags with the kind_flag set to 1.", }, + { + .name = "true_signature", + .key = ARGP_true_signature, + .doc = "Replace existing functions and add new functions with true signatures.", + }, { .name = NULL, } @@ -2013,6 +2020,8 @@ static error_t pahole__options_parser(int key, char *arg, parse_btf_features(arg, true); break; case ARGP_btf_attributes: conf_load.btf_attributes = true; break; + case ARGP_true_signature: + conf_load.true_signature = true; break; default: return ARGP_ERR_UNKNOWN; } -- 2.47.3 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf 2025-11-11 17:04 [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf Yonghong Song ` (2 preceding siblings ...) 2025-11-11 17:04 ` [PATCH dwarves 3/3] pahole: Replace or add functions with true signatures in btf Yonghong Song @ 2025-11-13 16:45 ` Alan Maguire 2025-11-13 17:36 ` Alexei Starovoitov 3 siblings, 1 reply; 8+ messages in thread From: Alan Maguire @ 2025-11-13 16:45 UTC (permalink / raw) To: Yonghong Song, Arnaldo Carvalho de Melo, dwarves Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, kernel-team On 11/11/2025 17:04, Yonghong Song wrote: > Current vmlinux BTF encoding is based on the source level signatures. > But the compiler may do some optimization and changed the signature. > If the user tried with source level signature, their initial implementation > may have wrong results and then the user need to check what is the > problem and work around it, e.g. through kprobe since kprobe does not > need vmlinux BTF. > > The following is a concrete example for [1]. > The original source signature: > 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) { ... } > After compiler optimization, the signature becomes: > static int map_create(union bpf_attr *attr, bool uattr__coerce1) { ... } > > In the above, uattr__coerce1 corresponds to 'is_kernel' field in sockptr_t. > Here, the suffix '__coerce1' refers to the second 64bit value in > sockptr_t. The first 64bit value will be '__coerce0' if that value > is used instead. > > To do proper tracing, it would be good for the users to know the > changed signature. With the actual signature, both kprobe and fentry > should work as usual. This can avoid user surprise and improve > developer productivity. > > The llvm compiler patch [1] collects true signature and encoded those > functions in dwarf. pahole will process these functions and > replace old signtures with true signatures. Additionally, > new functions (e.g., foo.llvm.<hash>) can be encoded in > vmlinux BTF as well. > > Patches 1/2 are refactor patches. Patch 3 has the detailed explanation > in commit message and implements the logic to encode replaced or new > signatures to vmlinux BTF. Please see Patch 3 for details. > Thanks for sending the series Yonghong! I think the thing we need to discuss at a high level is this; what is the proposed relationship between source code and BTF function encoding? The approach we have taken thus far is to use source level as the basis for encoding, and as part of that we attempt to identify cases where the source-level expectations are violated by the compiled (optimized) code. We currently do not encode those cases as in the case of optimized-out parameters, source-level expectations of parameter position could lead to bad behaviour. There are probably cases we miss in this, but that is the intent at least. There are however cases where .isra-suffixed functions retain the expected parameter representations; in such cases we encode with the prefix name ("foo" not "foo.isra.0") as DWARF does. So in moving away from that, I think we need to make a clear decision and have handling in place. My practical worry is that users trying to write BPF progs cannot easily predict if a parameter is optimized out and so on, so it's hard to write stable BPF programs for such signatures. Less of a problem if using a high-level tracer I suppose. The approach I had been thinking about was to utilize BTF location information for such cases, but the RFC [1] didn't get around to implementing the support. So the idea would be have location info with parameter types and locations, but because we don't encode a function fentry can't be used (but kprobes still could as for inline sites). So under that scheme the foo.llvm.hash functions could still be called "foo" since we have address information for the sites we can match foo to foo.llvm.hash. Anyway I'd appreciate other perspectives here. We have implicitly tied BTF function encoding thus for to source-level representation for reasons of fentry safety, but we could potentially move away from that. Doing so though would I think at a minimum require machinery for fentry safety to preserved, but we could find other ways to flag this in the BTF function representation potentially. Thanks! Alan [1] https://lore.kernel.org/bpf/20251024073328.370457-1-alan.maguire@oracle.com/ > [1] https://github.com/llvm/llvm-project/pull/165310 > > Yonghong Song (3): > btf_encoder: Refactor elf_functions__new() with struct btf_encoder as > argument > bpf_encoder: Refactor a helper elf_function__check_and_push_sym() > pahole: Replace or add functions with true signatures in btf > > btf_encoder.c | 79 +++++++++++++++++++++++++++++++++++--------- > dwarf_loader.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ > dwarves.h | 4 +++ > pahole.c | 9 +++++ > 4 files changed, 165 insertions(+), 16 deletions(-) > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf 2025-11-13 16:45 ` [PATCH dwarves 0/3] " Alan Maguire @ 2025-11-13 17:36 ` Alexei Starovoitov 2025-11-14 15:57 ` Alan Maguire 0 siblings, 1 reply; 8+ messages in thread From: Alexei Starovoitov @ 2025-11-13 17:36 UTC (permalink / raw) To: Alan Maguire Cc: Yonghong Song, Arnaldo Carvalho de Melo, dwarves, Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, Kernel Team On Thu, Nov 13, 2025 at 8:45 AM Alan Maguire <alan.maguire@oracle.com> wrote: > > On 11/11/2025 17:04, Yonghong Song wrote: > > Current vmlinux BTF encoding is based on the source level signatures. > > But the compiler may do some optimization and changed the signature. > > If the user tried with source level signature, their initial implementation > > may have wrong results and then the user need to check what is the > > problem and work around it, e.g. through kprobe since kprobe does not > > need vmlinux BTF. > > > > The following is a concrete example for [1]. > > The original source signature: > > 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) { ... } > > After compiler optimization, the signature becomes: > > static int map_create(union bpf_attr *attr, bool uattr__coerce1) { ... } > > > > In the above, uattr__coerce1 corresponds to 'is_kernel' field in sockptr_t. > > Here, the suffix '__coerce1' refers to the second 64bit value in > > sockptr_t. The first 64bit value will be '__coerce0' if that value > > is used instead. > > > > To do proper tracing, it would be good for the users to know the > > changed signature. With the actual signature, both kprobe and fentry > > should work as usual. This can avoid user surprise and improve > > developer productivity. > > > > The llvm compiler patch [1] collects true signature and encoded those > > functions in dwarf. pahole will process these functions and > > replace old signtures with true signatures. Additionally, > > new functions (e.g., foo.llvm.<hash>) can be encoded in > > vmlinux BTF as well. > > > > Patches 1/2 are refactor patches. Patch 3 has the detailed explanation > > in commit message and implements the logic to encode replaced or new > > signatures to vmlinux BTF. Please see Patch 3 for details. > > > > > Thanks for sending the series Yonghong! I think the thing we need to > discuss at a high level is this; what is the proposed relationship > between source code and BTF function encoding? The approach we have > taken thus far is to use source level as the basis for encoding, and as > part of that we attempt to identify cases where the source-level > expectations are violated by the compiled (optimized) code. We currently > do not encode those cases as in the case of optimized-out parameters, > source-level expectations of parameter position could lead to bad > behaviour. There are probably cases we miss in this, but that is the > intent at least. > > There are however cases where .isra-suffixed functions retain the > expected parameter representations; in such cases we encode with the > prefix name ("foo" not "foo.isra.0") as DWARF does. > > So in moving away from that, I think we need to make a clear decision > and have handling in place. My practical worry is that users trying to > write BPF progs cannot easily predict if a parameter is optimized out > and so on, so it's hard to write stable BPF programs for such > signatures. Less of a problem if using a high-level tracer I suppose. > > The approach I had been thinking about was to utilize BTF location > information for such cases, but the RFC [1] didn't get around to > implementing the support. So the idea would be have location info with > parameter types and locations, but because we don't encode a function > fentry can't be used (but kprobes still could as for inline sites). So > under that scheme the foo.llvm.hash functions could still be called > "foo" since we have address information for the sites we can match foo > to foo.llvm.hash. > > Anyway I'd appreciate other perspectives here. We have implicitly tied > BTF function encoding thus for to source-level representation for > reasons of fentry safety, but we could potentially move away from that. > Doing so though would I think at a minimum require machinery for fentry > safety to preserved, but we could find other ways to flag this in the > BTF function representation potentially. Thanks! Looks like we have a big disconnect here. To me BTF was never about the source, but about vmlinux final binary. Compile flags, configs change both types and functions significantly. For types it's easy to see in the vmlinux BTF how they got transformed from the original types in the source. Some source types disappear altogether. Similar situation with functions. They mutate. Partial inling, function renames are all part of the same category. BTF has to describe the final result, so that tracers/users can actually debug/introspect the kernel they have and not an abstract kernel source. pahole was conservative and removed functions that don't match BTF. loc* set is going to bring back these functions into BTF with their arguments. True signature support is complementary and mandatory part to loc* set. We need both. Compiler has to store the true signature in dwarf and pahole has to pass it to BTF along with location of arguments and actual name of function symbol table. Re: whether to strip .llvm or not, I think it's better to keep BTF matching symbol table which is kallsyms. If it has .llvm suffix in kallsyms it should have the same name in BTF. Tracing tools can attach with "func_name.*" pattern. libbpf already supports it. And thanks to BTF the fentry prog should match what is true kernel function signature. What was the source signature is secondary. The users cannot write their progs based on source, since such source code doesn't exist in the binary, so nothing to trace. While true signature with actual parameters is traceable. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf 2025-11-13 17:36 ` Alexei Starovoitov @ 2025-11-14 15:57 ` Alan Maguire 2025-11-14 20:11 ` Alexei Starovoitov 0 siblings, 1 reply; 8+ messages in thread From: Alan Maguire @ 2025-11-14 15:57 UTC (permalink / raw) To: Alexei Starovoitov Cc: Yonghong Song, Arnaldo Carvalho de Melo, dwarves, Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, Kernel Team On 13/11/2025 17:36, Alexei Starovoitov wrote: > On Thu, Nov 13, 2025 at 8:45 AM Alan Maguire <alan.maguire@oracle.com> wrote: >> >> On 11/11/2025 17:04, Yonghong Song wrote: >>> Current vmlinux BTF encoding is based on the source level signatures. >>> But the compiler may do some optimization and changed the signature. >>> If the user tried with source level signature, their initial implementation >>> may have wrong results and then the user need to check what is the >>> problem and work around it, e.g. through kprobe since kprobe does not >>> need vmlinux BTF. >>> >>> The following is a concrete example for [1]. >>> The original source signature: >>> 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) { ... } >>> After compiler optimization, the signature becomes: >>> static int map_create(union bpf_attr *attr, bool uattr__coerce1) { ... } >>> >>> In the above, uattr__coerce1 corresponds to 'is_kernel' field in sockptr_t. >>> Here, the suffix '__coerce1' refers to the second 64bit value in >>> sockptr_t. The first 64bit value will be '__coerce0' if that value >>> is used instead. >>> >>> To do proper tracing, it would be good for the users to know the >>> changed signature. With the actual signature, both kprobe and fentry >>> should work as usual. This can avoid user surprise and improve >>> developer productivity. >>> >>> The llvm compiler patch [1] collects true signature and encoded those >>> functions in dwarf. pahole will process these functions and >>> replace old signtures with true signatures. Additionally, >>> new functions (e.g., foo.llvm.<hash>) can be encoded in >>> vmlinux BTF as well. >>> >>> Patches 1/2 are refactor patches. Patch 3 has the detailed explanation >>> in commit message and implements the logic to encode replaced or new >>> signatures to vmlinux BTF. Please see Patch 3 for details. >>> >> >> >> Thanks for sending the series Yonghong! I think the thing we need to >> discuss at a high level is this; what is the proposed relationship >> between source code and BTF function encoding? The approach we have >> taken thus far is to use source level as the basis for encoding, and as >> part of that we attempt to identify cases where the source-level >> expectations are violated by the compiled (optimized) code. We currently >> do not encode those cases as in the case of optimized-out parameters, >> source-level expectations of parameter position could lead to bad >> behaviour. There are probably cases we miss in this, but that is the >> intent at least. >> >> There are however cases where .isra-suffixed functions retain the >> expected parameter representations; in such cases we encode with the >> prefix name ("foo" not "foo.isra.0") as DWARF does. >> >> So in moving away from that, I think we need to make a clear decision >> and have handling in place. My practical worry is that users trying to >> write BPF progs cannot easily predict if a parameter is optimized out >> and so on, so it's hard to write stable BPF programs for such >> signatures. Less of a problem if using a high-level tracer I suppose. >> >> The approach I had been thinking about was to utilize BTF location >> information for such cases, but the RFC [1] didn't get around to >> implementing the support. So the idea would be have location info with >> parameter types and locations, but because we don't encode a function >> fentry can't be used (but kprobes still could as for inline sites). So >> under that scheme the foo.llvm.hash functions could still be called >> "foo" since we have address information for the sites we can match foo >> to foo.llvm.hash. >> >> Anyway I'd appreciate other perspectives here. We have implicitly tied >> BTF function encoding thus for to source-level representation for >> reasons of fentry safety, but we could potentially move away from that. >> Doing so though would I think at a minimum require machinery for fentry >> safety to preserved, but we could find other ways to flag this in the >> BTF function representation potentially. Thanks! > > Looks like we have a big disconnect here. > To me BTF was never about the source, but about vmlinux final binary. > Compile flags, configs change both types and functions significantly. > For types it's easy to see in the vmlinux BTF how they got transformed > from the original types in the source. Some source types disappear > altogether. Similar situation with functions. They mutate. > Partial inling, function renames are all part of the same category. > BTF has to describe the final result, so that tracers/users can > actually debug/introspect the kernel they have and not an abstract > kernel source. pahole was conservative and removed functions that > don't match BTF. loc* set is going to bring back these functions > into BTF with their arguments. True signature support is complementary > and mandatory part to loc* set. We need both. Compiler has to > store the true signature in dwarf and pahole has to pass it to BTF > along with location of arguments and actual name of function symbol table. > I don't object to having a representation tied to the final binary; however I will say there is huge value in _knowing_ things changed from source to final representation. Now if we encode function names with '.' suffixes that is one way of knowing, and it may be enough, but I think we should think about mechanisms to ease overall developer experience in that new world. When I write a BPF program for fentry(), it seems to me to be deeply inconsistent that I can make it work across multiple kernel versions from a data structure perspective via CO-RE while also having to worry about the risk of compiler optimizations transforming or eliminating function parameters. It is a step forward in some ways that we can trace such functions at all, but I still think we will need a better story there. For example it is often the case that a BPF program only uses one parameter from a function signature; if we don't access transformed or eliminated parameters, can the verifier accept the fentry() even if the signature doesn't exactly match? We don't need to add these things today, but I think it would be good to discuss some of the consequences and how we would possibly handle them. > Re: whether to strip .llvm or not, I think it's better to keep BTF > matching symbol table which is kallsyms. If it has .llvm suffix in kallsyms > it should have the same name in BTF. Tracing tools can attach > with "func_name.*" pattern. libbpf already supports it. > And thanks to BTF the fentry prog should match what is true > kernel function signature. What was the source signature is secondary. > The users cannot write their progs based on source, since such > source code doesn't exist in the binary, so nothing to trace. > While true signature with actual parameters is traceable. Yeah I think if we are passing through the changed function signatures we definitely need a way to know such changes happened; the "." suffix will tell us that. Alan ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf 2025-11-14 15:57 ` Alan Maguire @ 2025-11-14 20:11 ` Alexei Starovoitov 0 siblings, 0 replies; 8+ messages in thread From: Alexei Starovoitov @ 2025-11-14 20:11 UTC (permalink / raw) To: Alan Maguire Cc: Yonghong Song, Arnaldo Carvalho de Melo, dwarves, Alexei Starovoitov, Andrii Nakryiko, bpf, David Faust, Jose E . Marchesi, Kernel Team On Fri, Nov 14, 2025 at 7:57 AM Alan Maguire <alan.maguire@oracle.com> wrote: > > On 13/11/2025 17:36, Alexei Starovoitov wrote: > > On Thu, Nov 13, 2025 at 8:45 AM Alan Maguire <alan.maguire@oracle.com> wrote: > >> > >> On 11/11/2025 17:04, Yonghong Song wrote: > >>> Current vmlinux BTF encoding is based on the source level signatures. > >>> But the compiler may do some optimization and changed the signature. > >>> If the user tried with source level signature, their initial implementation > >>> may have wrong results and then the user need to check what is the > >>> problem and work around it, e.g. through kprobe since kprobe does not > >>> need vmlinux BTF. > >>> > >>> The following is a concrete example for [1]. > >>> The original source signature: > >>> 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) { ... } > >>> After compiler optimization, the signature becomes: > >>> static int map_create(union bpf_attr *attr, bool uattr__coerce1) { ... } > >>> > >>> In the above, uattr__coerce1 corresponds to 'is_kernel' field in sockptr_t. > >>> Here, the suffix '__coerce1' refers to the second 64bit value in > >>> sockptr_t. The first 64bit value will be '__coerce0' if that value > >>> is used instead. > >>> > >>> To do proper tracing, it would be good for the users to know the > >>> changed signature. With the actual signature, both kprobe and fentry > >>> should work as usual. This can avoid user surprise and improve > >>> developer productivity. > >>> > >>> The llvm compiler patch [1] collects true signature and encoded those > >>> functions in dwarf. pahole will process these functions and > >>> replace old signtures with true signatures. Additionally, > >>> new functions (e.g., foo.llvm.<hash>) can be encoded in > >>> vmlinux BTF as well. > >>> > >>> Patches 1/2 are refactor patches. Patch 3 has the detailed explanation > >>> in commit message and implements the logic to encode replaced or new > >>> signatures to vmlinux BTF. Please see Patch 3 for details. > >>> > >> > >> > >> Thanks for sending the series Yonghong! I think the thing we need to > >> discuss at a high level is this; what is the proposed relationship > >> between source code and BTF function encoding? The approach we have > >> taken thus far is to use source level as the basis for encoding, and as > >> part of that we attempt to identify cases where the source-level > >> expectations are violated by the compiled (optimized) code. We currently > >> do not encode those cases as in the case of optimized-out parameters, > >> source-level expectations of parameter position could lead to bad > >> behaviour. There are probably cases we miss in this, but that is the > >> intent at least. > >> > >> There are however cases where .isra-suffixed functions retain the > >> expected parameter representations; in such cases we encode with the > >> prefix name ("foo" not "foo.isra.0") as DWARF does. > >> > >> So in moving away from that, I think we need to make a clear decision > >> and have handling in place. My practical worry is that users trying to > >> write BPF progs cannot easily predict if a parameter is optimized out > >> and so on, so it's hard to write stable BPF programs for such > >> signatures. Less of a problem if using a high-level tracer I suppose. > >> > >> The approach I had been thinking about was to utilize BTF location > >> information for such cases, but the RFC [1] didn't get around to > >> implementing the support. So the idea would be have location info with > >> parameter types and locations, but because we don't encode a function > >> fentry can't be used (but kprobes still could as for inline sites). So > >> under that scheme the foo.llvm.hash functions could still be called > >> "foo" since we have address information for the sites we can match foo > >> to foo.llvm.hash. > >> > >> Anyway I'd appreciate other perspectives here. We have implicitly tied > >> BTF function encoding thus for to source-level representation for > >> reasons of fentry safety, but we could potentially move away from that. > >> Doing so though would I think at a minimum require machinery for fentry > >> safety to preserved, but we could find other ways to flag this in the > >> BTF function representation potentially. Thanks! > > > > Looks like we have a big disconnect here. > > To me BTF was never about the source, but about vmlinux final binary. > > Compile flags, configs change both types and functions significantly. > > For types it's easy to see in the vmlinux BTF how they got transformed > > from the original types in the source. Some source types disappear > > altogether. Similar situation with functions. They mutate. > > Partial inling, function renames are all part of the same category. > > BTF has to describe the final result, so that tracers/users can > > actually debug/introspect the kernel they have and not an abstract > > kernel source. pahole was conservative and removed functions that > > don't match BTF. loc* set is going to bring back these functions > > into BTF with their arguments. True signature support is complementary > > and mandatory part to loc* set. We need both. Compiler has to > > store the true signature in dwarf and pahole has to pass it to BTF > > along with location of arguments and actual name of function symbol table. > > > > I don't object to having a representation tied to the final binary; > however I will say there is huge value in _knowing_ things changed from > source to final representation. Now if we encode function names with '.' > suffixes that is one way of knowing, and it may be enough, Right. llvm will use '.llvm' suffix to indicate the change, and imo it's enough of the signal. I don't think we need to store original func signature in BTF. It feels just a waste of BTF space, since tracers cannot use it for anything. It's like a historical artifact. Nice to know, but it's there in dwarf for people interested in history. > but I think > we should think about mechanisms to ease overall developer experience in > that new world. and that's exactly the purpose of Yonghong's llvm patches. Without them the dwarf is missing the final function signature. They will improve your loc* coverage too. > When I write a BPF program for fentry(), it seems to me to be deeply > inconsistent that I can make it work across multiple kernel versions > from a data structure perspective via CO-RE while also having to worry > about the risk of compiler optimizations transforming or eliminating > function parameters. It is a step forward in some ways that we can trace > such functions at all, but I still think we will need a better story > there. Quality of debug info in compilers is an afterthought, sadly. All optimizations are trying to preserve it as much as possible, of course, but "shrug, it was optimized out" is deployed too often as well. > For example it is often the case that a BPF program only uses one > parameter from a function signature; if we don't access transformed or > eliminated parameters, can the verifier accept the fentry() even if the > signature doesn't exactly match? We don't need to add these things > today, but I think it would be good to discuss some of the consequences > and how we would possibly handle them. Yeah. We can relax the verifier. Menglong had patches for multi-attach-fentry where it solved the case where an argument with the same type is present in multiple kernel functions. In that case we can have one fentry prog attach to all of them and access only that arg. > > > Re: whether to strip .llvm or not, I think it's better to keep BTF > > matching symbol table which is kallsyms. If it has .llvm suffix in kallsyms > > it should have the same name in BTF. Tracing tools can attach > > with "func_name.*" pattern. libbpf already supports it. > > And thanks to BTF the fentry prog should match what is true > > kernel function signature. What was the source signature is secondary. > > The users cannot write their progs based on source, since such > > source code doesn't exist in the binary, so nothing to trace. > > While true signature with actual parameters is traceable. > > Yeah I think if we are passing through the changed function signatures > we definitely need a way to know such changes happened; the "." suffix > will tell us that. yep. gcc needs to be improved too, since it will add a suffix, but won't update dwarf sufficiently. I think it will only strip args of location info, but won't update func signature. While llvm now will store new actual signature and update locations. Note the whole thing mainly affects LTO builds. There are lots of .llvm funcs in them. Kernel doesn't support gcc lto yet. ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-11-14 20:11 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-11-11 17:04 [PATCH dwarves 0/3] pahole: Replace or add functions with true signatures in btf Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 1/3] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 2/3] bpf_encoder: Refactor a helper elf_function__check_and_push_sym() Yonghong Song 2025-11-11 17:04 ` [PATCH dwarves 3/3] pahole: Replace or add functions with true signatures in btf Yonghong Song 2025-11-13 16:45 ` [PATCH dwarves 0/3] " Alan Maguire 2025-11-13 17:36 ` Alexei Starovoitov 2025-11-14 15:57 ` Alan Maguire 2025-11-14 20:11 ` Alexei Starovoitov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox