* [PATCH dwarves 0/4] Improve BTF concrete function accuracy
@ 2026-01-13 13:13 Alan Maguire
2026-01-13 13:13 ` [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters Alan Maguire
` (4 more replies)
0 siblings, 5 replies; 19+ messages in thread
From: Alan Maguire @ 2026-01-13 13:13 UTC (permalink / raw)
To: yonghong.song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Alan Maguire
This series brings together a few solutions to issues we have
with accuracy of BTF function representation at the binary level.
The first patch detects mismatches between concrete (binary)
and abstract (source-level) function signatures as a means
of either excluding them or providing a "true" function signature.
Patch 2 is from Yonghong's LLVM true function signature series,
and helps for patch 3 which adds GCC true function signature
support for optimized functions; with that support, we use
binary-level signatures for .isra, .constprop functions and
represent them with their "." suffixes as BTF_KIND_FUNC
names. This allows for fentry attach to such functions, and
the "." suffix is an indicator of signature modification.
The feature is guarded by a default-off BTF feature because
older kernels did not support a "." in a function name.
Patch 4 is Matt's patch to favour the strong function
over the associated weak declaration. The other patches
are important prerequisites for this as the patch selects
the binary-level function (with a lowpc value), and in
the case of optimized functions we were often selecting
the .isra function with optimized-out parameters. Because
pahole did not previously detect this correctly we ended
up with functions with signatures having reordered parameters.
Patches 1-3 help avoid this by better detecting optimized-out
function parameters.
With these patches in place, ~20 functions are omitted from
vmlinux BTF; all these are "."-suffixed functions which
we were not noticing had optimized-out parameters.
Experimenting with adding true_signature to BTF features
we end up adding approximately 500 .isra and .constprop
functions to vmlinux BTF.
The true function signature support here will also hopefully
help pave the way for Yonghong's work on the LLVM side.
Alan Maguire (2):
dwarf_loader/btf_encoder: Detect reordered parameters
btf_encoder: Add true_signature feature support for "."-suffixed
functions
Matt Bobrowski (1):
btf_encoder: Prefer strong function definitions for BTF generation
Yonghong Song (1):
btf_encoder: Refactor elf_functions__new() with struct btf_encoder as
argument
btf_encoder.c | 142 ++++++++++++++++++++++++++++++++++++++++++-------
dwarf_loader.c | 5 +-
dwarves.h | 3 ++
pahole.c | 1 +
4 files changed, 131 insertions(+), 20 deletions(-)
--
2.43.5
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters
2026-01-13 13:13 [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
@ 2026-01-13 13:13 ` Alan Maguire
2026-01-20 16:07 ` Yonghong Song
2026-01-13 13:13 ` [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Alan Maguire
` (3 subsequent siblings)
4 siblings, 1 reply; 19+ messages in thread
From: Alan Maguire @ 2026-01-13 13:13 UTC (permalink / raw)
To: yonghong.song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Alan Maguire
When encoding concrete instances of optimized functions it is possible
parameters get reordered, often due to a parameter being optimized out;
in such cases the order of abstract origin references to the abstract
function is different, and the parameters that are optimized out
usually appear after all the non-optimized parameters with no
DW_AT_location information [1].
As an example consider
static void __blkcg_rstat_flush(struct blkcg *blkcg, int cpu);
It has - as expected - an abstract representation as follows:
<1><6392a2d>: Abbrev Number: 47 (DW_TAG_subprogram)
<6392a2e> DW_AT_name : (indirect string, offset: 0x261e25): __blkcg_rstat_flush
<6392a32> DW_AT_decl_file : 1
<6392a33> DW_AT_decl_line : 1043
<6392a35> DW_AT_decl_column : 13
<6392a36> DW_AT_prototyped : 1
<6392a36> DW_AT_inline : 1 (inlined)
<6392a37> DW_AT_sibling : <0x6392bac>
<2><6392a3b>: Abbrev Number: 38 (DW_TAG_formal_parameter)
<6392a3c> DW_AT_name : (indirect string, offset: 0xa7a9f): blkcg
<6392a40> DW_AT_decl_file : 1
<6392a41> DW_AT_decl_line : 1043
<6392a43> DW_AT_decl_column : 47
<6392a44> DW_AT_type : <0x638b611>
<2><6392a48>: Abbrev Number: 20 (DW_TAG_formal_parameter)
<6392a49> DW_AT_name : cpu
<6392a4d> DW_AT_decl_file : 1
<6392a4e> DW_AT_decl_line : 1043
<6392a50> DW_AT_decl_column : 58
<6392a51> DW_AT_type : <0x6377f8f>
However the concrete representation after optimization becomes:
ffffffff8186d180 t __blkcg_rstat_flush.isra.0
and has a concrete representation with parameter order switched:
<1><6399661>: Abbrev Number: 110 (DW_TAG_subprogram)
<6399662> DW_AT_abstract_origin: <0x6392a2d>
<6399666> DW_AT_low_pc : 0xffffffff8186d180
<639966e> DW_AT_high_pc : 0x169
<6399676> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
<6399678> DW_AT_GNU_all_call_sites: 1
<6399678> DW_AT_sibling : <0x6399a8a>
<2><639967c>: Abbrev Number: 4 (DW_TAG_formal_parameter)
<639967d> DW_AT_abstract_origin: <0x6392a48>
<6399681> DW_AT_location : 0x1fe21fb (location list)
<6399685> DW_AT_GNU_locviews: 0x1fe21f5
<2><63996e4>: Abbrev Number: 4 (DW_TAG_formal_parameter)
<63996e5> DW_AT_abstract_origin: <0x6392a3b>
<63996e9> DW_AT_location : 0x1fe2387 (location list)
<63996ed> DW_AT_GNU_locviews: 0x1fe2385
In other words we end up with
static void __blkcg_rstat_flush.isra(int cpu, struct blkcg *blkcg);
We are not detecting cases like this in pahole, so we need to
catch it to exclude such cases since they could lead to incorrect
fentry attachment.
Future work around true function signatures will allow such functions
with their "." suffixes, but even for such cases it is good to
detect the reordering.
In practice we just end up excluding a few more .isra/.constprop
functions which we cannot fentry-attach by name anyway; see [2] for an
example list from CI.
[1] https://lore.kernel.org/bpf/101b74c9-949a-4bf4-a766-a5343b70bdd2@oracle.com/
[2] https://github.com/alan-maguire/dwarves/actions/runs/20031993822
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
btf_encoder.c | 29 ++++++++++++++++++++---------
dwarf_loader.c | 5 +++--
dwarves.h | 2 ++
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index b37ee7f..2c3cef9 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -87,6 +87,7 @@ struct btf_encoder_func_state {
uint8_t unexpected_reg:1;
uint8_t inconsistent_proto:1;
uint8_t uncertain_parm_loc:1;
+ uint8_t reordered_parm:1;
uint8_t ambiguous_addr:1;
int ret_type_id;
struct btf_encoder_func_parm *parms;
@@ -1272,6 +1273,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
state->unexpected_reg = ftype->unexpected_reg;
state->optimized_parms = ftype->optimized_parms;
state->uncertain_parm_loc = ftype->uncertain_parm_loc;
+ state->reordered_parm = ftype->reordered_parm;
ftype__for_each_parameter(ftype, param) {
const char *name = parameter__name(param) ?: "";
@@ -1441,7 +1443,7 @@ static int saved_functions_combine(struct btf_encoder *encoder,
struct btf_encoder_func_state *a,
struct btf_encoder_func_state *b)
{
- uint8_t optimized, unexpected, inconsistent, uncertain_parm_loc;
+ uint8_t optimized, unexpected, inconsistent, uncertain_parm_loc, reordered_parm;
if (a->elf != b->elf)
return 1;
@@ -1450,12 +1452,14 @@ static int saved_functions_combine(struct btf_encoder *encoder,
unexpected = a->unexpected_reg | b->unexpected_reg;
inconsistent = a->inconsistent_proto | b->inconsistent_proto;
uncertain_parm_loc = a->uncertain_parm_loc | b->uncertain_parm_loc;
- if (!unexpected && !inconsistent && !funcs__match(encoder, a, b))
+ reordered_parm = a->reordered_parm | b->reordered_parm;
+ if (!unexpected && !inconsistent && !reordered_parm && !funcs__match(encoder, a, b))
inconsistent = 1;
a->optimized_parms = b->optimized_parms = optimized;
a->unexpected_reg = b->unexpected_reg = unexpected;
a->inconsistent_proto = b->inconsistent_proto = inconsistent;
a->uncertain_parm_loc = b->uncertain_parm_loc = uncertain_parm_loc;
+ a->reordered_parm = b->reordered_parm = reordered_parm;
return 0;
}
@@ -1493,7 +1497,7 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
for (i = 0; i < nr_saved_fns; i = j) {
struct btf_encoder_func_state *state = &saved_fns[i];
- bool add_to_btf = !skip_encoding_inconsistent_proto;
+ char *skip_reason = NULL;
/* Compare across sorted functions that match by name/prefix;
* share inconsistent/unexpected reg state between them.
@@ -1509,14 +1513,21 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
* unexpected register use, multiple inconsistent prototypes or
* uncertain parameters location
*/
- add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto && !state->uncertain_parm_loc && !state->elf->ambiguous_addr;
-
+ if (state->unexpected_reg)
+ skip_reason = "unexpected register usage for parameter\n";
+ if (skip_encoding_inconsistent_proto && state->inconsistent_proto)
+ skip_reason = "inconsistet prototype\n";
if (state->uncertain_parm_loc)
- btf_encoder__log_func_skip(encoder, saved_fns[i].elf,
- "uncertain parameter location\n",
- 0, 0);
+ skip_reason = "uncertain parameter location\n";
+ if (state->reordered_parm)
+ skip_reason = "reordered parameters\n";
+ if (state->elf->ambiguous_addr)
+ skip_reason = "ambiguous address\n";
- if (add_to_btf) {
+ if (skip_reason) {
+ btf_encoder__log_func_skip(encoder, saved_fns[i].elf,
+ skip_reason, 0, 0);
+ } else {
if (is_kfunc_state(state))
err = btf_encoder__add_bpf_kfunc(encoder, state);
else
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 77aab8a..16fb7be 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1262,7 +1262,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
tag__init(&parm->tag, cu, die);
parm->name = attr_string(die, DW_AT_name, conf);
-
+ parm->idx = param_idx;
if (param_idx >= cu->nr_register_params || param_idx < 0)
return parm;
/* Parameters which use DW_AT_abstract_origin to point at
@@ -2636,6 +2636,8 @@ static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
}
opos = tag__parameter(dtag__tag(dtype));
pos->name = opos->name;
+ if (pos->idx != opos->idx)
+ type->reordered_parm = 1;
pos->tag.type = dtag__tag(dtype)->type;
/* share location information between parameter and
* abstract origin; if neither have location, we will
@@ -2838,7 +2840,6 @@ static int tag__recode_dwarf_type(struct tag *tag, struct cu *cu)
lexblock__recode_dwarf_types(&fn->lexblock, cu);
}
/* Fall thru */
-
case DW_TAG_subroutine_type:
ftype__recode_dwarf_types(tag, cu);
/* Fall thru, for the function return type */
diff --git a/dwarves.h b/dwarves.h
index 21d4166..78bedf5 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -944,6 +944,7 @@ struct parameter {
uint8_t optimized:1;
uint8_t unexpected_reg:1;
uint8_t has_loc:1;
+ uint8_t idx;
};
static inline struct parameter *tag__parameter(const struct tag *tag)
@@ -1023,6 +1024,7 @@ struct ftype {
uint8_t processed:1;
uint8_t inconsistent_proto:1;
uint8_t uncertain_parm_loc:1;
+ uint8_t reordered_parm:1;
struct list_head template_type_params;
struct list_head template_value_params;
struct template_parameter_pack *template_parameter_pack;
--
2.43.5
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument
2026-01-13 13:13 [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
2026-01-13 13:13 ` [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters Alan Maguire
@ 2026-01-13 13:13 ` Alan Maguire
2026-01-13 18:32 ` Ihor Solodrai
2026-01-13 13:13 ` [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions Alan Maguire
` (2 subsequent siblings)
4 siblings, 1 reply; 19+ messages in thread
From: Alan Maguire @ 2026-01-13 13:13 UTC (permalink / raw)
To: yonghong.song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
From: Yonghong Song <yonghong.song@linux.dev>
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 2c3cef9..5bc61cb 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;
@@ -1552,7 +1554,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.43.5
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-13 13:13 [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
2026-01-13 13:13 ` [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters Alan Maguire
2026-01-13 13:13 ` [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Alan Maguire
@ 2026-01-13 13:13 ` Alan Maguire
2026-01-14 16:15 ` Yonghong Song
2026-01-20 17:53 ` Yonghong Song
2026-01-13 13:13 ` [PATCH dwarves 4/4] btf_encoder: Prefer strong function definitions for BTF generation Alan Maguire
2026-01-20 9:52 ` [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
4 siblings, 2 replies; 19+ messages in thread
From: Alan Maguire @ 2026-01-13 13:13 UTC (permalink / raw)
To: yonghong.song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Alan Maguire
Currently we collate function information by name and add functions
provided there are no inconsistencies across various representations.
For true_signature support - where we wish to add the real signature
of a function even if it differs from source level - we need to do
a few things:
1. For "."-suffixed functions, we need to match from DWARF->ELF;
we can do this via the address associated with the function.
In doing this, we can then be confident that the debug info
for foo.isra.0 is the right info for the function at that
address.
2. When adding saved functions we need to look for such cases
and provided they do not violate other constraints around BTF
representation - unexpected reg usage for function, uncertain
parameter location or ambiguous address - we add them with
their "."-suffixed name. The latter can be used as a signal
that the function is transformed from the original.
Doing this adds 500 functions to BTF. These are traceable with
their "."-suffix names and because we have excluded ambiguous
address cases we know exactly which function address they refer
to.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
dwarves.h | 1 +
pahole.c | 1 +
3 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 5bc61cb..01fd469 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
int16_t component_idx;
};
+struct elf_function_sym {
+ const char *name;
+ uint64_t addr;
+};
+
/* state used to do later encoding of saved functions */
struct btf_encoder_func_state {
struct elf_function *elf;
+ struct elf_function_sym *sym;
+ uint64_t addr;
uint32_t type_id_off;
uint16_t nr_parms;
uint16_t nr_annots;
@@ -94,11 +101,6 @@ struct btf_encoder_func_state {
struct btf_encoder_func_annot *annots;
};
-struct elf_function_sym {
- const char *name;
- uint64_t addr;
-};
-
struct elf_function {
char *name;
struct elf_function_sym *syms;
@@ -145,7 +147,8 @@ struct btf_encoder {
skip_encoding_decl_tag,
tag_kfuncs,
gen_distilled_base,
- encode_attributes;
+ encode_attributes,
+ true_signature;
uint32_t array_index_id;
struct elf_secinfo *secinfo;
size_t seccnt;
@@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
goto out;
}
}
+ if (encoder->true_signature && fn->lexblock.ip.addr) {
+ int i;
+
+ for (i = 0; i < func->sym_cnt; i++) {
+ if (fn->lexblock.ip.addr != func->syms[i].addr)
+ continue;
+ /* Only need to record address for '.'-suffixed
+ * functions, since we only currently need true
+ * signatures for them.
+ */
+ if (!strchr(func->syms[i].name, '.'))
+ continue;
+ state->sym = &func->syms[i];
+ break;
+ }
+ }
state->inconsistent_proto = ftype->inconsistent_proto;
state->unexpected_reg = ftype->unexpected_reg;
state->optimized_parms = ftype->optimized_parms;
state->uncertain_parm_loc = ftype->uncertain_parm_loc;
state->reordered_parm = ftype->reordered_parm;
ftype__for_each_parameter(ftype, param) {
- const char *name = parameter__name(param) ?: "";
+ const char *name;
+ /* No location info + reordered means optimized out. */
+ if (ftype->reordered_parm && !param->has_loc)
+ continue;
+ name = parameter__name(param) ?: "";
str_off = btf__add_str(btf, name);
if (str_off < 0) {
err = str_off;
@@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
name = func->name;
+ if (encoder->true_signature && state->sym)
+ name = state->sym->name;
+
if (btf_fnproto_id >= 0)
btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
name, false);
@@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
j++;
+ /* Add true signatures for case where we have an exact
+ * symbol match by address from DWARF->ELF and have a
+ * "." suffixed name.
+ */
+ if (encoder->true_signature) {
+ int k;
+
+ for (k = i; k < nr_saved_fns; k++) {
+ struct btf_encoder_func_state *true_state = &saved_fns[k];
+
+ if (state->elf != true_state->elf)
+ break;
+ if (!true_state->sym)
+ continue;
+ /* Unexpected reg, uncertain parm loc and
+ * ambiguous address mean we cannot trust fentry.
+ */
+ if (true_state->unexpected_reg ||
+ true_state->uncertain_parm_loc ||
+ true_state->ambiguous_addr)
+ continue;
+ err = btf_encoder__add_func(encoder, true_state);
+ if (err < 0)
+ goto out;
+ break;
+ }
+ }
+
+ /* True symbol that was handled above; skip. */
+ if (state->sym)
+ continue;
+
/* do not exclude functions with optimized-out parameters; they
* may still be _called_ with the right parameter values, they
* just do not _use_ them. Only exclude functions with
@@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
encoder->verbose = verbose;
encoder->has_index_type = false;
encoder->need_index_type = false;
diff --git a/dwarves.h b/dwarves.h
index 78bedf5..d7c6474 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -101,6 +101,7 @@ struct conf_load {
bool btf_decl_tag_kfuncs;
bool btf_gen_distilled_base;
bool btf_attributes;
+ bool true_signature;
uint8_t hashtable_bits;
uint8_t max_hashtable_bits;
uint16_t kabi_prefix_len;
diff --git a/pahole.c b/pahole.c
index ef01e58..02a0d19 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1234,6 +1234,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
--
2.43.5
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH dwarves 4/4] btf_encoder: Prefer strong function definitions for BTF generation
2026-01-13 13:13 [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
` (2 preceding siblings ...)
2026-01-13 13:13 ` [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions Alan Maguire
@ 2026-01-13 13:13 ` Alan Maguire
2026-01-20 17:54 ` Yonghong Song
2026-01-20 9:52 ` [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
4 siblings, 1 reply; 19+ messages in thread
From: Alan Maguire @ 2026-01-13 13:13 UTC (permalink / raw)
To: yonghong.song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
From: Matt Bobrowski <mattbobrowski@google.com>
Currently, when a function has both a weak and a strong definition
across different compilation units (CUs), the BTF encoder arbitrarily
selects one to generate the BTF entry. This selection fundamentally is
dependent on the order in which pahole processes the CUs.
This indifference often leads to a mismatch where the generated BTF
reflects the weak definition's prototype, even though the linker
selected the strong definition for the final vmlinux binary.
A notable example described in [0] involving function
bpf_lsm_mmap_file(). Both weak and strong definitions exist,
distinguished only by parameter names (e.g., file vs
file__nullable). While the strong definition is linked into the
vmlinux object, the generated BTF contained the prototype for the weak
definition. This causes issues for BPF verifier (e.g., __nullable
annotation semantics), or tools relying on accurate type information.
To fix this, ensure the BTF encoder selects the function definition
corresponding to the actual code linked into the binary. This is
achieved by comparing the DWARF function address (DW_AT_low_pc) with
the ELF symbol address (st_value). Only the DWARF entry for the strong
definition will match the final resolved ELF symbol address.
[0] https://lore.kernel.org/all/aVJY9H-e83T7ivT4@google.com/
Link: https://lore.kernel.org/all/aVJY9H-e83T7ivT4@google.com/
Signed-off-by: Matt Bobrowski <mattbobrowski@google.com>
---
btf_encoder.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/btf_encoder.c b/btf_encoder.c
index 01fd469..26ccbbe 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1264,6 +1264,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
if (!state)
return -ENOMEM;
+ state->addr = function__addr(fn);
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;
@@ -1509,6 +1510,29 @@ static void btf_encoder__delete_saved_funcs(struct btf_encoder *encoder)
encoder->func_states.cap = 0;
}
+static struct btf_encoder_func_state *btf_encoder__select_canonical_state(struct btf_encoder_func_state *combined_states,
+ int combined_cnt)
+{
+ int i, j;
+
+ /*
+ * The same elf_function is shared amongst combined functions,
+ * as per saved_functions_combine().
+ */
+ struct elf_function *elf = combined_states[0].elf;
+
+ for (i = 0; i < combined_cnt; i++) {
+ struct btf_encoder_func_state *state = &combined_states[i];
+
+ for (j = 0; j < elf->sym_cnt; j++) {
+ if (state->addr == elf->syms[j].addr)
+ return state;
+ }
+ }
+
+ return &combined_states[0];
+}
+
static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_encoding_inconsistent_proto)
{
struct btf_encoder_func_state *saved_fns = encoder->func_states.array;
@@ -1588,6 +1612,16 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
btf_encoder__log_func_skip(encoder, saved_fns[i].elf,
skip_reason, 0, 0);
} else {
+ /*
+ * We're to add the current function within
+ * BTF. Although, from all functions that have
+ * possibly been combined via
+ * saved_functions_combine(), ensure to only
+ * select and emit BTF for the most canonical
+ * function definition.
+ */
+ if (j - i > 1)
+ state = btf_encoder__select_canonical_state(state, j - i);
if (is_kfunc_state(state))
err = btf_encoder__add_bpf_kfunc(encoder, state);
else
--
2.43.5
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument
2026-01-13 13:13 ` [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Alan Maguire
@ 2026-01-13 18:32 ` Ihor Solodrai
2026-01-13 18:57 ` Yonghong Song
2026-01-13 20:59 ` Alan Maguire
0 siblings, 2 replies; 19+ messages in thread
From: Ihor Solodrai @ 2026-01-13 18:32 UTC (permalink / raw)
To: Alan Maguire, yonghong.song, mattbobrowski
Cc: eddyz87, jolsa, andrii, ast, dwarves, bpf
On 1/13/26 5:13 AM, Alan Maguire wrote:
> From: Yonghong Song <yonghong.song@linux.dev>
>
> 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 2c3cef9..5bc61cb 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)
Hi Alan, Yonghong,
I assume "future use" refers to this patch:
https://lore.kernel.org/dwarves/20251130040350.2636774-1-yonghong.song@linux.dev/
Do I understand correctly that you're passing btf_encoder here in
order to detect that the `encoder->dotted_true_signature` feature flag
is set? If so, I think this is a bit of an overkill.
How about just store the flag in struct elf_functions, pass it to the
elf_functions__new() directly and set it there:
funcs->elf = elf;
funcs->dotted_true_signature = dotted_true_signature; // <--
err = elf_functions__collect(funcs);
if (err < 0)
goto out_delete;
And even then, it doesn't feel right to me that the contents of the
*ELF* functions table changes based on a feature flag. But we are
discarding the suffixes currently, so I understand why this was done.
Taking a step back, I remember Yonghong mentioned some pushback both
from LLVM and DWARF side regarding the introduction of true signatures
to DWARF data. Is there a feasible path forward landing all that?
I haven't followed this work in detail, so apologies if I missed
anything. Just want to have a high-level understanding of the
situation.
Thank you!
> {
> struct elf_functions *funcs;
> + Elf *elf;
> int err;
>
> + elf = encoder->cu->elf;
> funcs = calloc(1, sizeof(*funcs));
> if (!funcs) {
> err = -ENOMEM;
> @@ -1552,7 +1554,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);
> }
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument
2026-01-13 18:32 ` Ihor Solodrai
@ 2026-01-13 18:57 ` Yonghong Song
2026-01-13 20:59 ` Alan Maguire
1 sibling, 0 replies; 19+ messages in thread
From: Yonghong Song @ 2026-01-13 18:57 UTC (permalink / raw)
To: Ihor Solodrai, Alan Maguire, mattbobrowski
Cc: eddyz87, jolsa, andrii, ast, dwarves, bpf
On 1/13/26 10:32 AM, Ihor Solodrai wrote:
> On 1/13/26 5:13 AM, Alan Maguire wrote:
>> From: Yonghong Song <yonghong.song@linux.dev>
>>
>> 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 2c3cef9..5bc61cb 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)
> Hi Alan, Yonghong,
>
> I assume "future use" refers to this patch:
> https://lore.kernel.org/dwarves/20251130040350.2636774-1-yonghong.song@linux.dev/
>
> Do I understand correctly that you're passing btf_encoder here in
> order to detect that the `encoder->dotted_true_signature` feature flag
> is set? If so, I think this is a bit of an overkill.
>
> How about just store the flag in struct elf_functions, pass it to the
> elf_functions__new() directly and set it there:
>
> funcs->elf = elf;
> funcs->dotted_true_signature = dotted_true_signature; // <--
> err = elf_functions__collect(funcs);
> if (err < 0)
> goto out_delete;
>
> And even then, it doesn't feel right to me that the contents of the
> *ELF* functions table changes based on a feature flag. But we are
> discarding the suffixes currently, so I understand why this was done.
>
> Taking a step back, I remember Yonghong mentioned some pushback both
> from LLVM and DWARF side regarding the introduction of true signatures
> to DWARF data. Is there a feasible path forward landing all that?
Yes. My previous dwarf format (https://github.com/llvm/llvm-project/pull/165310)
gets resistance from llvm esp. dwarf community.
There are two possible solutions going forward:
1. use existing dwarf data (e.g. locations, etc) to extract true signatures.
2. generate vmlinux BTF directly from compiler and make sure true signatures
are encoded in that BTF. Currently gcc is able to generate BTF but
do not have changed signatures.
The second approach is more complicated so I prefer to try option 1 first.
With option 1, I think pahole already has lots of checking for various
inconsistency or ambiguity. But llvm generated dwarf may have some
difference from gcc generated dwarf. For example, for function
__blkcg_rstat_flush() in patch 1, gcc has abstract origin for that function,
but clang does not have it in dwarf. I will need to sort out
these things.
>
> I haven't followed this work in detail, so apologies if I missed
> anything. Just want to have a high-level understanding of the
> situation.
>
> Thank you!
>
>
>> {
>> struct elf_functions *funcs;
>> + Elf *elf;
>> int err;
>>
>> + elf = encoder->cu->elf;
>> funcs = calloc(1, sizeof(*funcs));
>> if (!funcs) {
>> err = -ENOMEM;
>> @@ -1552,7 +1554,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);
>> }
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument
2026-01-13 18:32 ` Ihor Solodrai
2026-01-13 18:57 ` Yonghong Song
@ 2026-01-13 20:59 ` Alan Maguire
1 sibling, 0 replies; 19+ messages in thread
From: Alan Maguire @ 2026-01-13 20:59 UTC (permalink / raw)
To: Ihor Solodrai, yonghong.song, mattbobrowski
Cc: eddyz87, jolsa, andrii, ast, dwarves, bpf
On 13/01/2026 18:32, Ihor Solodrai wrote:
> On 1/13/26 5:13 AM, Alan Maguire wrote:
>> From: Yonghong Song <yonghong.song@linux.dev>
>>
>> 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 2c3cef9..5bc61cb 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)
>
> Hi Alan, Yonghong,
>
> I assume "future use" refers to this patch:
> https://lore.kernel.org/dwarves/20251130040350.2636774-1-yonghong.song@linux.dev/
>
> Do I understand correctly that you're passing btf_encoder here in
> order to detect that the `encoder->dotted_true_signature` feature flag
> is set? If so, I think this is a bit of an overkill.
>
hi Ihor, good catch; actually I think it makes sense to drop this patch until
we need it. At one point I was using the encoder in this series but it's not
needed right now, so let's wait and see if/when we need it later. Thanks!
Alan
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-13 13:13 ` [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions Alan Maguire
@ 2026-01-14 16:15 ` Yonghong Song
2026-01-14 16:55 ` Alan Maguire
2026-01-20 17:53 ` Yonghong Song
1 sibling, 1 reply; 19+ messages in thread
From: Yonghong Song @ 2026-01-14 16:15 UTC (permalink / raw)
To: Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 1/13/26 5:13 AM, Alan Maguire wrote:
> Currently we collate function information by name and add functions
> provided there are no inconsistencies across various representations.
>
> For true_signature support - where we wish to add the real signature
> of a function even if it differs from source level - we need to do
> a few things:
>
> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
> we can do this via the address associated with the function.
> In doing this, we can then be confident that the debug info
> for foo.isra.0 is the right info for the function at that
> address.
>
> 2. When adding saved functions we need to look for such cases
> and provided they do not violate other constraints around BTF
> representation - unexpected reg usage for function, uncertain
> parameter location or ambiguous address - we add them with
> their "."-suffixed name. The latter can be used as a signal
> that the function is transformed from the original.
>
> Doing this adds 500 functions to BTF. These are traceable with
> their "."-suffix names and because we have excluded ambiguous
> address cases we know exactly which function address they refer
> to.
>
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> ---
> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
> dwarves.h | 1 +
> pahole.c | 1 +
> 3 files changed, 68 insertions(+), 7 deletions(-)
>
> diff --git a/btf_encoder.c b/btf_encoder.c
> index 5bc61cb..01fd469 100644
> --- a/btf_encoder.c
> +++ b/btf_encoder.c
> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
> int16_t component_idx;
> };
>
> +struct elf_function_sym {
> + const char *name;
> + uint64_t addr;
> +};
> +
> /* state used to do later encoding of saved functions */
> struct btf_encoder_func_state {
> struct elf_function *elf;
> + struct elf_function_sym *sym;
> + uint64_t addr;
> uint32_t type_id_off;
> uint16_t nr_parms;
> uint16_t nr_annots;
> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
> struct btf_encoder_func_annot *annots;
> };
>
> -struct elf_function_sym {
> - const char *name;
> - uint64_t addr;
> -};
> -
> struct elf_function {
> char *name;
> struct elf_function_sym *syms;
> @@ -145,7 +147,8 @@ struct btf_encoder {
> skip_encoding_decl_tag,
> tag_kfuncs,
> gen_distilled_base,
> - encode_attributes;
> + encode_attributes,
> + true_signature;
> uint32_t array_index_id;
> struct elf_secinfo *secinfo;
> size_t seccnt;
> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
> goto out;
> }
> }
> + if (encoder->true_signature && fn->lexblock.ip.addr) {
> + int i;
> +
> + for (i = 0; i < func->sym_cnt; i++) {
> + if (fn->lexblock.ip.addr != func->syms[i].addr)
> + continue;
> + /* Only need to record address for '.'-suffixed
> + * functions, since we only currently need true
> + * signatures for them.
> + */
> + if (!strchr(func->syms[i].name, '.'))
> + continue;
> + state->sym = &func->syms[i];
> + break;
> + }
> + }
> state->inconsistent_proto = ftype->inconsistent_proto;
> state->unexpected_reg = ftype->unexpected_reg;
> state->optimized_parms = ftype->optimized_parms;
> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
> state->reordered_parm = ftype->reordered_parm;
> ftype__for_each_parameter(ftype, param) {
> - const char *name = parameter__name(param) ?: "";
> + const char *name;
>
> + /* No location info + reordered means optimized out. */
> + if (ftype->reordered_parm && !param->has_loc)
> + continue;
> + name = parameter__name(param) ?: "";
> str_off = btf__add_str(btf, name);
> if (str_off < 0) {
> err = str_off;
> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>
> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
> name = func->name;
> + if (encoder->true_signature && state->sym)
> + name = state->sym->name;
> +
> if (btf_fnproto_id >= 0)
> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
> name, false);
> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
> j++;
>
> + /* Add true signatures for case where we have an exact
> + * symbol match by address from DWARF->ELF and have a
> + * "." suffixed name.
> + */
> + if (encoder->true_signature) {
> + int k;
> +
> + for (k = i; k < nr_saved_fns; k++) {
> + struct btf_encoder_func_state *true_state = &saved_fns[k];
> +
> + if (state->elf != true_state->elf)
> + break;
> + if (!true_state->sym)
> + continue;
> + /* Unexpected reg, uncertain parm loc and
> + * ambiguous address mean we cannot trust fentry.
> + */
> + if (true_state->unexpected_reg ||
> + true_state->uncertain_parm_loc ||
> + true_state->ambiguous_addr)
> + continue;
> + err = btf_encoder__add_func(encoder, true_state);
> + if (err < 0)
> + goto out;
> + break;
> + }
> + }
> +
> + /* True symbol that was handled above; skip. */
> + if (state->sym)
> + continue;
> +
> /* do not exclude functions with optimized-out parameters; they
> * may still be _called_ with the right parameter values, they
> * just do not _use_ them. Only exclude functions with
> @@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
> encoder->verbose = verbose;
> encoder->has_index_type = false;
> encoder->need_index_type = false;
> diff --git a/dwarves.h b/dwarves.h
> index 78bedf5..d7c6474 100644
> --- a/dwarves.h
> +++ b/dwarves.h
> @@ -101,6 +101,7 @@ struct conf_load {
> bool btf_decl_tag_kfuncs;
> bool btf_gen_distilled_base;
> bool btf_attributes;
> + bool true_signature;
> uint8_t hashtable_bits;
> uint8_t max_hashtable_bits;
> uint16_t kabi_prefix_len;
> diff --git a/pahole.c b/pahole.c
> index ef01e58..02a0d19 100644
> --- a/pahole.c
> +++ b/pahole.c
> @@ -1234,6 +1234,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
Currently, in pahole, when checking whether signature has changed during
optimization or not, we only check parameters.
But compiler optimization may optimize away return value and such
information is not available in dwarf.
For example,
$ cat test.c
#include <stdio.h>
unsigned tar(int a);
__attribute__((noinline)) static int foo(int a, int b)
{
return tar(a) + tar(a + 1);
}
__attribute__((noinline)) int bar(int a)
{
foo(a, 1);
return 0;
}
In this particular case, the return value of foo() is actually not used
and the compiler will optimize it away with returning void (at least
for llvm).
$ /opt/rh/gcc-toolset-15/root/usr/bin/gcc -O2 -g -c test.c
$ llvm-dwarfdump test.o
...
0x000000d9: DW_TAG_subprogram
DW_AT_name ("foo")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
DW_AT_decl_line (3)
DW_AT_decl_column (38)
DW_AT_prototyped (true)
DW_AT_type (0x0000005d "int")
DW_AT_inline (DW_INL_inlined)
DW_AT_sibling (0x000000fb)
0x000000ea: DW_TAG_formal_parameter
DW_AT_name ("a")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
DW_AT_decl_line (3)
DW_AT_decl_column (46)
DW_AT_type (0x0000005d "int")
0x000000f2: DW_TAG_formal_parameter
DW_AT_name ("b")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
DW_AT_decl_line (3)
DW_AT_decl_column (53)
DW_AT_type (0x0000005d "int")
0x000000fa: NULL
0x000000fb: DW_TAG_subprogram
DW_AT_abstract_origin (0x000000d9 "foo")
DW_AT_low_pc (0x0000000000000000)
DW_AT_high_pc (0x0000000000000011)
DW_AT_frame_base (DW_OP_call_frame_cfa)
DW_AT_call_all_calls (true)
0x00000112: DW_TAG_formal_parameter
DW_AT_abstract_origin (0x000000ea "a")
DW_AT_location (0x00000026:
[0x0000000000000000, 0x0000000000000007): DW_OP_reg5 RDI
[0x0000000000000007, 0x000000000000000c): DW_OP_reg3 RBX
[0x000000000000000c, 0x0000000000000010): DW_OP_breg5 RDI-1, DW_OP_stack_value
[0x0000000000000010, 0x0000000000000011): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
DW_AT_GNU_locviews (0x0000001e)
0x0000011f: DW_TAG_formal_parameter
DW_AT_abstract_origin (0x000000f2 "b")
DW_AT_const_value (0x01)
...
Assembly code:
0000000000000000 <foo.constprop.0.isra.0>:
0: 53 pushq %rbx
1: 89 fb movl %edi, %ebx
3: e8 00 00 00 00 callq 0x8 <foo.constprop.0.isra.0+0x8>
8: 8d 7b 01 leal 0x1(%rbx), %edi
b: 5b popq %rbx
c: e9 00 00 00 00 jmp 0x11 <foo.constprop.0.isra.0+0x11>
11: 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
1c: 0f 1f 40 00 nopl (%rax)
0000000000000020 <bar>:
20: 48 83 ec 08 subq $0x8, %rsp
24: e8 d7 ff ff ff callq 0x0 <foo.constprop.0.isra.0>
29: 31 c0 xorl %eax, %eax
2b: 48 83 c4 08 addq $0x8, %rsp
2f: c3 retq
$ clang -O2 -g -c test.c
$ llvm-dwarfdump test.o
...
0x0000004e: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000022)
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/deadret/test.c")
DW_AT_decl_line (3)
DW_AT_prototyped (true)
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x00000096 "int")
0x0000005e: DW_TAG_formal_parameter
DW_AT_location (indexed (0x1) loclist = 0x00000022:
[0x0000000000000010, 0x0000000000000018): DW_OP_reg5 RDI
[0x0000000000000018, 0x000000000000001a): DW_OP_reg3 RBX
[0x000000000000001a, 0x0000000000000022): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
DW_AT_name ("a")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x00000096 "int")
0x00000067: DW_TAG_formal_parameter
DW_AT_name ("b")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x00000096 "int")
...
Assembly code:
0000000000000000 <bar>:
0: 50 pushq %rax
1: e8 0a 00 00 00 callq 0x10 <foo>
6: 31 c0 xorl %eax, %eax
8: 59 popq %rcx
9: c3 retq
a: 66 0f 1f 44 00 00 nopw (%rax,%rax)
0000000000000010 <foo>:
10: 53 pushq %rbx
11: 89 fb movl %edi, %ebx
13: e8 00 00 00 00 callq 0x18 <foo+0x8>
18: ff c3 incl %ebx
1a: 89 df movl %ebx, %edi
1c: 5b popq %rbx
1d: e9 00 00 00 00 jmp 0x22 <foo+0x12>
The compiler knows whether the return type has changed or not.
Unfortunately the information is not available in dwarf. So
BTF will encode source level return type even if the actual
return type could be void due to optimization.
This is not perfect but at least it is an improvement
for true signature. But it would be great if llvm/gcc
side can coordinate to propose something in compiler/dwarf
to encode return type change as well. In llvm,
AFAIK, the only return type change will be
'original non-void type' -> 'void type'.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-14 16:15 ` Yonghong Song
@ 2026-01-14 16:55 ` Alan Maguire
2026-01-14 18:22 ` David Faust
0 siblings, 1 reply; 19+ messages in thread
From: Alan Maguire @ 2026-01-14 16:55 UTC (permalink / raw)
To: Yonghong Song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Jose E. Marchesi, David Faust
On 14/01/2026 16:15, Yonghong Song wrote:
>
>
> On 1/13/26 5:13 AM, Alan Maguire wrote:
>> Currently we collate function information by name and add functions
>> provided there are no inconsistencies across various representations.
>>
>> For true_signature support - where we wish to add the real signature
>> of a function even if it differs from source level - we need to do
>> a few things:
>>
>> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
>> we can do this via the address associated with the function.
>> In doing this, we can then be confident that the debug info
>> for foo.isra.0 is the right info for the function at that
>> address.
>>
>> 2. When adding saved functions we need to look for such cases
>> and provided they do not violate other constraints around BTF
>> representation - unexpected reg usage for function, uncertain
>> parameter location or ambiguous address - we add them with
>> their "."-suffixed name. The latter can be used as a signal
>> that the function is transformed from the original.
>>
>> Doing this adds 500 functions to BTF. These are traceable with
>> their "."-suffix names and because we have excluded ambiguous
>> address cases we know exactly which function address they refer
>> to.
>>
>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>> ---
>> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
>> dwarves.h | 1 +
>> pahole.c | 1 +
>> 3 files changed, 68 insertions(+), 7 deletions(-)
>>
>> diff --git a/btf_encoder.c b/btf_encoder.c
>> index 5bc61cb..01fd469 100644
>> --- a/btf_encoder.c
>> +++ b/btf_encoder.c
>> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
>> int16_t component_idx;
>> };
>> +struct elf_function_sym {
>> + const char *name;
>> + uint64_t addr;
>> +};
>> +
>> /* state used to do later encoding of saved functions */
>> struct btf_encoder_func_state {
>> struct elf_function *elf;
>> + struct elf_function_sym *sym;
>> + uint64_t addr;
>> uint32_t type_id_off;
>> uint16_t nr_parms;
>> uint16_t nr_annots;
>> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
>> struct btf_encoder_func_annot *annots;
>> };
>> -struct elf_function_sym {
>> - const char *name;
>> - uint64_t addr;
>> -};
>> -
>> struct elf_function {
>> char *name;
>> struct elf_function_sym *syms;
>> @@ -145,7 +147,8 @@ struct btf_encoder {
>> skip_encoding_decl_tag,
>> tag_kfuncs,
>> gen_distilled_base,
>> - encode_attributes;
>> + encode_attributes,
>> + true_signature;
>> uint32_t array_index_id;
>> struct elf_secinfo *secinfo;
>> size_t seccnt;
>> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>> goto out;
>> }
>> }
>> + if (encoder->true_signature && fn->lexblock.ip.addr) {
>> + int i;
>> +
>> + for (i = 0; i < func->sym_cnt; i++) {
>> + if (fn->lexblock.ip.addr != func->syms[i].addr)
>> + continue;
>> + /* Only need to record address for '.'-suffixed
>> + * functions, since we only currently need true
>> + * signatures for them.
>> + */
>> + if (!strchr(func->syms[i].name, '.'))
>> + continue;
>> + state->sym = &func->syms[i];
>> + break;
>> + }
>> + }
>> state->inconsistent_proto = ftype->inconsistent_proto;
>> state->unexpected_reg = ftype->unexpected_reg;
>> state->optimized_parms = ftype->optimized_parms;
>> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
>> state->reordered_parm = ftype->reordered_parm;
>> ftype__for_each_parameter(ftype, param) {
>> - const char *name = parameter__name(param) ?: "";
>> + const char *name;
>> + /* No location info + reordered means optimized out. */
>> + if (ftype->reordered_parm && !param->has_loc)
>> + continue;
>> + name = parameter__name(param) ?: "";
>> str_off = btf__add_str(btf, name);
>> if (str_off < 0) {
>> err = str_off;
>> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
>> name = func->name;
>> + if (encoder->true_signature && state->sym)
>> + name = state->sym->name;
>> +
>> if (btf_fnproto_id >= 0)
>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>> name, false);
>> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
>> j++;
>> + /* Add true signatures for case where we have an exact
>> + * symbol match by address from DWARF->ELF and have a
>> + * "." suffixed name.
>> + */
>> + if (encoder->true_signature) {
>> + int k;
>> +
>> + for (k = i; k < nr_saved_fns; k++) {
>> + struct btf_encoder_func_state *true_state = &saved_fns[k];
>> +
>> + if (state->elf != true_state->elf)
>> + break;
>> + if (!true_state->sym)
>> + continue;
>> + /* Unexpected reg, uncertain parm loc and
>> + * ambiguous address mean we cannot trust fentry.
>> + */
>> + if (true_state->unexpected_reg ||
>> + true_state->uncertain_parm_loc ||
>> + true_state->ambiguous_addr)
>> + continue;
>> + err = btf_encoder__add_func(encoder, true_state);
>> + if (err < 0)
>> + goto out;
>> + break;
>> + }
>> + }
>> +
>> + /* True symbol that was handled above; skip. */
>> + if (state->sym)
>> + continue;
>> +
>> /* do not exclude functions with optimized-out parameters; they
>> * may still be _called_ with the right parameter values, they
>> * just do not _use_ them. Only exclude functions with
>> @@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
>> encoder->verbose = verbose;
>> encoder->has_index_type = false;
>> encoder->need_index_type = false;
>> diff --git a/dwarves.h b/dwarves.h
>> index 78bedf5..d7c6474 100644
>> --- a/dwarves.h
>> +++ b/dwarves.h
>> @@ -101,6 +101,7 @@ struct conf_load {
>> bool btf_decl_tag_kfuncs;
>> bool btf_gen_distilled_base;
>> bool btf_attributes;
>> + bool true_signature;
>> uint8_t hashtable_bits;
>> uint8_t max_hashtable_bits;
>> uint16_t kabi_prefix_len;
>> diff --git a/pahole.c b/pahole.c
>> index ef01e58..02a0d19 100644
>> --- a/pahole.c
>> +++ b/pahole.c
>> @@ -1234,6 +1234,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
>
> Currently, in pahole, when checking whether signature has changed during
> optimization or not, we only check parameters.
>
> But compiler optimization may optimize away return value and such
> information is not available in dwarf.
>
> For example,
>
> $ cat test.c
> #include <stdio.h>
> unsigned tar(int a);
> __attribute__((noinline)) static int foo(int a, int b)
> {
> return tar(a) + tar(a + 1);
> }
> __attribute__((noinline)) int bar(int a)
> {
> foo(a, 1);
> return 0;
> }
>
> In this particular case, the return value of foo() is actually not used
> and the compiler will optimize it away with returning void (at least
> for llvm).
>
> $ /opt/rh/gcc-toolset-15/root/usr/bin/gcc -O2 -g -c test.c
> $ llvm-dwarfdump test.o
> ...
> 0x000000d9: DW_TAG_subprogram
> DW_AT_name ("foo")
> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
> DW_AT_decl_line (3)
> DW_AT_decl_column (38)
> DW_AT_prototyped (true)
> DW_AT_type (0x0000005d "int")
> DW_AT_inline (DW_INL_inlined)
> DW_AT_sibling (0x000000fb)
> 0x000000ea: DW_TAG_formal_parameter
> DW_AT_name ("a")
> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
> DW_AT_decl_line (3)
> DW_AT_decl_column (46)
> DW_AT_type (0x0000005d "int")
> 0x000000f2: DW_TAG_formal_parameter
> DW_AT_name ("b")
> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
> DW_AT_decl_line (3)
> DW_AT_decl_column (53)
> DW_AT_type (0x0000005d "int")
>
> 0x000000fa: NULL
>
> 0x000000fb: DW_TAG_subprogram
> DW_AT_abstract_origin (0x000000d9 "foo")
> DW_AT_low_pc (0x0000000000000000)
> DW_AT_high_pc (0x0000000000000011)
> DW_AT_frame_base (DW_OP_call_frame_cfa)
> DW_AT_call_all_calls (true)
>
> 0x00000112: DW_TAG_formal_parameter
> DW_AT_abstract_origin (0x000000ea "a")
> DW_AT_location (0x00000026:
> [0x0000000000000000, 0x0000000000000007): DW_OP_reg5 RDI
> [0x0000000000000007, 0x000000000000000c): DW_OP_reg3 RBX
> [0x000000000000000c, 0x0000000000000010): DW_OP_breg5 RDI-1, DW_OP_stack_value
> [0x0000000000000010, 0x0000000000000011): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
> DW_AT_GNU_locviews (0x0000001e)
>
> 0x0000011f: DW_TAG_formal_parameter
> DW_AT_abstract_origin (0x000000f2 "b")
> DW_AT_const_value (0x01)
> ...
>
> Assembly code:
> 0000000000000000 <foo.constprop.0.isra.0>:
> 0: 53 pushq %rbx
> 1: 89 fb movl %edi, %ebx
> 3: e8 00 00 00 00 callq 0x8 <foo.constprop.0.isra.0+0x8>
> 8: 8d 7b 01 leal 0x1(%rbx), %edi
> b: 5b popq %rbx
> c: e9 00 00 00 00 jmp 0x11 <foo.constprop.0.isra.0+0x11>
> 11: 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
> 1c: 0f 1f 40 00 nopl (%rax)
>
> 0000000000000020 <bar>:
> 20: 48 83 ec 08 subq $0x8, %rsp
> 24: e8 d7 ff ff ff callq 0x0 <foo.constprop.0.isra.0>
> 29: 31 c0 xorl %eax, %eax
> 2b: 48 83 c4 08 addq $0x8, %rsp
> 2f: c3 retq
>
> $ clang -O2 -g -c test.c
> $ llvm-dwarfdump test.o
> ...
> 0x0000004e: DW_TAG_subprogram
> DW_AT_low_pc (0x0000000000000010)
> DW_AT_high_pc (0x0000000000000022)
> 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/deadret/test.c")
> DW_AT_decl_line (3)
> DW_AT_prototyped (true)
> DW_AT_calling_convention (DW_CC_nocall)
> DW_AT_type (0x00000096 "int")
>
> 0x0000005e: DW_TAG_formal_parameter
> DW_AT_location (indexed (0x1) loclist = 0x00000022:
> [0x0000000000000010, 0x0000000000000018): DW_OP_reg5 RDI
> [0x0000000000000018, 0x000000000000001a): DW_OP_reg3 RBX
> [0x000000000000001a, 0x0000000000000022): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
> DW_AT_name ("a")
> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
> DW_AT_decl_line (3)
> DW_AT_type (0x00000096 "int")
>
> 0x00000067: DW_TAG_formal_parameter
> DW_AT_name ("b")
> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
> DW_AT_decl_line (3)
> DW_AT_type (0x00000096 "int")
> ...
> Assembly code:encs
> 0000000000000000 <bar>:
> 0: 50 pushq %rax
> 1: e8 0a 00 00 00 callq 0x10 <foo>
> 6: 31 c0 xorl %eax, %eax
> 8: 59 popq %rcx
> 9: c3 retq
> a: 66 0f 1f 44 00 00 nopw (%rax,%rax)
>
> 0000000000000010 <foo>:
> 10: 53 pushq %rbx
> 11: 89 fb movl %edi, %ebx
> 13: e8 00 00 00 00 callq 0x18 <foo+0x8>
> 18: ff c3 incl %ebx
> 1a: 89 df movl %ebx, %edi
> 1c: 5b popq %rbx
> 1d: e9 00 00 00 00 jmp 0x22 <foo+0x12>
>
>
> The compiler knows whether the return type has changed or not.
> Unfortunately the information is not available in dwarf. So
> BTF will encode source level return type even if the actual
> return type could be void due to optimization.
>
> This is not perfect but at least it is an improvement
> for true signature. But it would be great if llvm/gcc
> side can coordinate to propose something in compiler/dwarf
> to encode return type change as well. In llvm,
> AFAIK, the only return type change will be
> 'original non-void type' -> 'void type'.
>
Yeah, we dug into this a bit on the gcc side with David's help and it
appears the only mechanism used seems to be abstract origin reference
unfortunately. It seems to me that in theory the compiler could encode
the actual type for return types and any parameters that change type
from the abstract to concrete representation, and we could end up with
a mix of abstract origin refererences for the types that don't change and
non-abstract for the types that do.
David, Jose, I'm wondering if the information is available to gcc to do
that at late DWARF encoding time? Thanks!
Alan
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-14 16:55 ` Alan Maguire
@ 2026-01-14 18:22 ` David Faust
2026-01-15 3:27 ` Yonghong Song
2026-01-15 18:38 ` Yonghong Song
0 siblings, 2 replies; 19+ messages in thread
From: David Faust @ 2026-01-14 18:22 UTC (permalink / raw)
To: Alan Maguire, Yonghong Song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Jose E. Marchesi
On 1/14/26 08:55, Alan Maguire wrote:
> On 14/01/2026 16:15, Yonghong Song wrote:
>>
>>
>> On 1/13/26 5:13 AM, Alan Maguire wrote:
>>> Currently we collate function information by name and add functions
>>> provided there are no inconsistencies across various representations.
>>>
>>> For true_signature support - where we wish to add the real signature
>>> of a function even if it differs from source level - we need to do
>>> a few things:
>>>
>>> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
>>> we can do this via the address associated with the function.
>>> In doing this, we can then be confident that the debug info
>>> for foo.isra.0 is the right info for the function at that
>>> address.
>>>
>>> 2. When adding saved functions we need to look for such cases
>>> and provided they do not violate other constraints around BTF
>>> representation - unexpected reg usage for function, uncertain
>>> parameter location or ambiguous address - we add them with
>>> their "."-suffixed name. The latter can be used as a signal
>>> that the function is transformed from the original.
>>>
>>> Doing this adds 500 functions to BTF. These are traceable with
>>> their "."-suffix names and because we have excluded ambiguous
>>> address cases we know exactly which function address they refer
>>> to.
>>>
>>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>>> ---
>>> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
>>> dwarves.h | 1 +
>>> pahole.c | 1 +
>>> 3 files changed, 68 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/btf_encoder.c b/btf_encoder.c
>>> index 5bc61cb..01fd469 100644
>>> --- a/btf_encoder.c
>>> +++ b/btf_encoder.c
>>> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
>>> int16_t component_idx;
>>> };
>>> +struct elf_function_sym {
>>> + const char *name;
>>> + uint64_t addr;
>>> +};
>>> +
>>> /* state used to do later encoding of saved functions */
>>> struct btf_encoder_func_state {
>>> struct elf_function *elf;
>>> + struct elf_function_sym *sym;
>>> + uint64_t addr;
>>> uint32_t type_id_off;
>>> uint16_t nr_parms;
>>> uint16_t nr_annots;
>>> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
>>> struct btf_encoder_func_annot *annots;
>>> };
>>> -struct elf_function_sym {
>>> - const char *name;
>>> - uint64_t addr;
>>> -};
>>> -
>>> struct elf_function {
>>> char *name;
>>> struct elf_function_sym *syms;
>>> @@ -145,7 +147,8 @@ struct btf_encoder {
>>> skip_encoding_decl_tag,
>>> tag_kfuncs,
>>> gen_distilled_base,
>>> - encode_attributes;
>>> + encode_attributes,
>>> + true_signature;
>>> uint32_t array_index_id;
>>> struct elf_secinfo *secinfo;
>>> size_t seccnt;
>>> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>>> goto out;
>>> }
>>> }
>>> + if (encoder->true_signature && fn->lexblock.ip.addr) {
>>> + int i;
>>> +
>>> + for (i = 0; i < func->sym_cnt; i++) {
>>> + if (fn->lexblock.ip.addr != func->syms[i].addr)
>>> + continue;
>>> + /* Only need to record address for '.'-suffixed
>>> + * functions, since we only currently need true
>>> + * signatures for them.
>>> + */
>>> + if (!strchr(func->syms[i].name, '.'))
>>> + continue;
>>> + state->sym = &func->syms[i];
>>> + break;
>>> + }
>>> + }
>>> state->inconsistent_proto = ftype->inconsistent_proto;
>>> state->unexpected_reg = ftype->unexpected_reg;
>>> state->optimized_parms = ftype->optimized_parms;
>>> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
>>> state->reordered_parm = ftype->reordered_parm;
>>> ftype__for_each_parameter(ftype, param) {
>>> - const char *name = parameter__name(param) ?: "";
>>> + const char *name;
>>> + /* No location info + reordered means optimized out. */
>>> + if (ftype->reordered_parm && !param->has_loc)
>>> + continue;
>>> + name = parameter__name(param) ?: "";
>>> str_off = btf__add_str(btf, name);
>>> if (str_off < 0) {
>>> err = str_off;
>>> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>>> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
>>> name = func->name;
>>> + if (encoder->true_signature && state->sym)
>>> + name = state->sym->name;
>>> +
>>> if (btf_fnproto_id >= 0)
>>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>>> name, false);
>>> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>>> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
>>> j++;
>>> + /* Add true signatures for case where we have an exact
>>> + * symbol match by address from DWARF->ELF and have a
>>> + * "." suffixed name.
>>> + */
>>> + if (encoder->true_signature) {
>>> + int k;
>>> +
>>> + for (k = i; k < nr_saved_fns; k++) {
>>> + struct btf_encoder_func_state *true_state = &saved_fns[k];
>>> +
>>> + if (state->elf != true_state->elf)
>>> + break;
>>> + if (!true_state->sym)
>>> + continue;
>>> + /* Unexpected reg, uncertain parm loc and
>>> + * ambiguous address mean we cannot trust fentry.
>>> + */
>>> + if (true_state->unexpected_reg ||
>>> + true_state->uncertain_parm_loc ||
>>> + true_state->ambiguous_addr)
>>> + continue;
>>> + err = btf_encoder__add_func(encoder, true_state);
>>> + if (err < 0)
>>> + goto out;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + /* True symbol that was handled above; skip. */
>>> + if (state->sym)
>>> + continue;
>>> +
>>> /* do not exclude functions with optimized-out parameters; they
>>> * may still be _called_ with the right parameter values, they
>>> * just do not _use_ them. Only exclude functions with
>>> @@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
>>> encoder->verbose = verbose;
>>> encoder->has_index_type = false;
>>> encoder->need_index_type = false;
>>> diff --git a/dwarves.h b/dwarves.h
>>> index 78bedf5..d7c6474 100644
>>> --- a/dwarves.h
>>> +++ b/dwarves.h
>>> @@ -101,6 +101,7 @@ struct conf_load {
>>> bool btf_decl_tag_kfuncs;
>>> bool btf_gen_distilled_base;
>>> bool btf_attributes;
>>> + bool true_signature;
>>> uint8_t hashtable_bits;
>>> uint8_t max_hashtable_bits;
>>> uint16_t kabi_prefix_len;
>>> diff --git a/pahole.c b/pahole.c
>>> index ef01e58..02a0d19 100644
>>> --- a/pahole.c
>>> +++ b/pahole.c
>>> @@ -1234,6 +1234,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
>>
>> Currently, in pahole, when checking whether signature has changed during
>> optimization or not, we only check parameters.
>>
>> But compiler optimization may optimize away return value and such
>> information is not available in dwarf.
>>
>> For example,
>>
>> $ cat test.c
>> #include <stdio.h>
>> unsigned tar(int a);
>> __attribute__((noinline)) static int foo(int a, int b)
>> {
>> return tar(a) + tar(a + 1);
>> }
>> __attribute__((noinline)) int bar(int a)
>> {
>> foo(a, 1);
>> return 0;
>> }
>>
>> In this particular case, the return value of foo() is actually not used
>> and the compiler will optimize it away with returning void (at least
>> for llvm).
>>
>> $ /opt/rh/gcc-toolset-15/root/usr/bin/gcc -O2 -g -c test.c
>> $ llvm-dwarfdump test.o
>> ...
>> 0x000000d9: DW_TAG_subprogram
>> DW_AT_name ("foo")
>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>> DW_AT_decl_line (3)
>> DW_AT_decl_column (38)
>> DW_AT_prototyped (true)
>> DW_AT_type (0x0000005d "int")
>> DW_AT_inline (DW_INL_inlined)
>> DW_AT_sibling (0x000000fb)
>> 0x000000ea: DW_TAG_formal_parameter
>> DW_AT_name ("a")
>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>> DW_AT_decl_line (3)
>> DW_AT_decl_column (46)
>> DW_AT_type (0x0000005d "int")
>> 0x000000f2: DW_TAG_formal_parameter
>> DW_AT_name ("b")
>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>> DW_AT_decl_line (3)
>> DW_AT_decl_column (53)
>> DW_AT_type (0x0000005d "int")
>>
>> 0x000000fa: NULL
>>
>> 0x000000fb: DW_TAG_subprogram
>> DW_AT_abstract_origin (0x000000d9 "foo")
>> DW_AT_low_pc (0x0000000000000000)
>> DW_AT_high_pc (0x0000000000000011)
>> DW_AT_frame_base (DW_OP_call_frame_cfa)
>> DW_AT_call_all_calls (true)
>>
>> 0x00000112: DW_TAG_formal_parameter
>> DW_AT_abstract_origin (0x000000ea "a")
>> DW_AT_location (0x00000026:
>> [0x0000000000000000, 0x0000000000000007): DW_OP_reg5 RDI
>> [0x0000000000000007, 0x000000000000000c): DW_OP_reg3 RBX
>> [0x000000000000000c, 0x0000000000000010): DW_OP_breg5 RDI-1, DW_OP_stack_value
>> [0x0000000000000010, 0x0000000000000011): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>> DW_AT_GNU_locviews (0x0000001e)
>>
>> 0x0000011f: DW_TAG_formal_parameter
>> DW_AT_abstract_origin (0x000000f2 "b")
>> DW_AT_const_value (0x01)
>> ...
>>
>> Assembly code:
>> 0000000000000000 <foo.constprop.0.isra.0>:
>> 0: 53 pushq %rbx
>> 1: 89 fb movl %edi, %ebx
>> 3: e8 00 00 00 00 callq 0x8 <foo.constprop.0.isra.0+0x8>
>> 8: 8d 7b 01 leal 0x1(%rbx), %edi
>> b: 5b popq %rbx
>> c: e9 00 00 00 00 jmp 0x11 <foo.constprop.0.isra.0+0x11>
>> 11: 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
>> 1c: 0f 1f 40 00 nopl (%rax)
>>
>> 0000000000000020 <bar>:
>> 20: 48 83 ec 08 subq $0x8, %rsp
>> 24: e8 d7 ff ff ff callq 0x0 <foo.constprop.0.isra.0>
>> 29: 31 c0 xorl %eax, %eax
>> 2b: 48 83 c4 08 addq $0x8, %rsp
>> 2f: c3 retq
>>
>> $ clang -O2 -g -c test.c
>> $ llvm-dwarfdump test.o
>> ...
>> 0x0000004e: DW_TAG_subprogram
>> DW_AT_low_pc (0x0000000000000010)
>> DW_AT_high_pc (0x0000000000000022)
>> 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/deadret/test.c")
>> DW_AT_decl_line (3)
>> DW_AT_prototyped (true)
>> DW_AT_calling_convention (DW_CC_nocall)
>> DW_AT_type (0x00000096 "int")
>>
>> 0x0000005e: DW_TAG_formal_parameter
>> DW_AT_location (indexed (0x1) loclist = 0x00000022:
>> [0x0000000000000010, 0x0000000000000018): DW_OP_reg5 RDI
>> [0x0000000000000018, 0x000000000000001a): DW_OP_reg3 RBX
>> [0x000000000000001a, 0x0000000000000022): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>> DW_AT_name ("a")
>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>> DW_AT_decl_line (3)
>> DW_AT_type (0x00000096 "int")
>>
>> 0x00000067: DW_TAG_formal_parameter
>> DW_AT_name ("b")
>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>> DW_AT_decl_line (3)
>> DW_AT_type (0x00000096 "int")
>> ...
>> Assembly code:encs
>> 0000000000000000 <bar>:
>> 0: 50 pushq %rax
>> 1: e8 0a 00 00 00 callq 0x10 <foo>
>> 6: 31 c0 xorl %eax, %eax
>> 8: 59 popq %rcx
>> 9: c3 retq
>> a: 66 0f 1f 44 00 00 nopw (%rax,%rax)
>>
>> 0000000000000010 <foo>:
>> 10: 53 pushq %rbx
>> 11: 89 fb movl %edi, %ebx
>> 13: e8 00 00 00 00 callq 0x18 <foo+0x8>
>> 18: ff c3 incl %ebx
>> 1a: 89 df movl %ebx, %edi
>> 1c: 5b popq %rbx
>> 1d: e9 00 00 00 00 jmp 0x22 <foo+0x12>
>>
>>
>> The compiler knows whether the return type has changed or not.
>> Unfortunately the information is not available in dwarf. So
>> BTF will encode source level return type even if the actual
>> return type could be void due to optimization.
>>
>> This is not perfect but at least it is an improvement
>> for true signature. But it would be great if llvm/gcc
>> side can coordinate to propose something in compiler/dwarf
>> to encode return type change as well. In llvm,
>> AFAIK, the only return type change will be
>> 'original non-void type' -> 'void type'.
>>
>
> Yeah, we dug into this a bit on the gcc side with David's help and it
> appears the only mechanism used seems to be abstract origin reference
> unfortunately. It seems to me that in theory the compiler could encode
> the actual type for return types and any parameters that change type
> from the abstract to concrete representation, and we could end up with
> a mix of abstract origin refererences for the types that don't change and
> non-abstract for the types that do.
>
> David, Jose, I'm wondering if the information is available to gcc to do
> that at late DWARF encoding time? Thanks!
Yes, at least to some degree. For non-inlined cases, I don't currently know
of a case where we do not have the information. For inlined cases I am
less confident that it's still available.
I spent some time looking at this last year, thinking that we could at least
use this to improve gcc-emitted BTF with the final signatures for non-inline
funcs, even if there is no perfect way to encode it in DWARF.
For example, I have:
__attribute__((noinline))
static int callee (struct tcphdr *tcp, int x, int y)
{...}
In this case both the return value and the param 'x' are dropped by
optimizations.
At late DWARF time we have a function_decl node for the optimized version
'callee.constprop.isra' which has a return type of void and arg type list
reflecting the remaining two parameters. We can also get a pointer to
the original pre-optimized decl to compare against. i.e. we have both:
callee.constprop.isra
return type: void
arg types: struct tcphdr*, int, void
callee
return type: int
arg types: struct tcphdr*, int, int, void
This also extends to more complicated cases where IPA-SRA splits
aggregate parameters into individual pieces (e.g. split a pass-by-value
struct into only passing the relevant fields which are used).
I think some of these transformations are not always reflected in DWARF.
For a simpler case like the one here, it is currently reflected in DWARF
via the abstract_origin subprogram DIE for callee in which only the
remaining two parameters have concrete AT_location.
(Although IMO it is rather ambiguous whether the abstract_origin DIE's
lack of AT_type means "return type is unchanged" or "return type is void")
>
> Alan
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-14 18:22 ` David Faust
@ 2026-01-15 3:27 ` Yonghong Song
2026-01-15 18:38 ` Yonghong Song
1 sibling, 0 replies; 19+ messages in thread
From: Yonghong Song @ 2026-01-15 3:27 UTC (permalink / raw)
To: David Faust, Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Jose E. Marchesi
On 1/14/26 10:22 AM, David Faust wrote:
>
> On 1/14/26 08:55, Alan Maguire wrote:
>> On 14/01/2026 16:15, Yonghong Song wrote:
>>>
>>> On 1/13/26 5:13 AM, Alan Maguire wrote:
>>>> Currently we collate function information by name and add functions
>>>> provided there are no inconsistencies across various representations.
>>>>
>>>> For true_signature support - where we wish to add the real signature
>>>> of a function even if it differs from source level - we need to do
>>>> a few things:
>>>>
>>>> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
>>>> we can do this via the address associated with the function.
>>>> In doing this, we can then be confident that the debug info
>>>> for foo.isra.0 is the right info for the function at that
>>>> address.
>>>>
>>>> 2. When adding saved functions we need to look for such cases
>>>> and provided they do not violate other constraints around BTF
>>>> representation - unexpected reg usage for function, uncertain
>>>> parameter location or ambiguous address - we add them with
>>>> their "."-suffixed name. The latter can be used as a signal
>>>> that the function is transformed from the original.
>>>>
>>>> Doing this adds 500 functions to BTF. These are traceable with
>>>> their "."-suffix names and because we have excluded ambiguous
>>>> address cases we know exactly which function address they refer
>>>> to.
>>>>
>>>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>>>> ---
>>>> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
>>>> dwarves.h | 1 +
>>>> pahole.c | 1 +
>>>> 3 files changed, 68 insertions(+), 7 deletions(-)
>>>>
>>>> diff --git a/btf_encoder.c b/btf_encoder.c
>>>> index 5bc61cb..01fd469 100644
>>>> --- a/btf_encoder.c
>>>> +++ b/btf_encoder.c
>>>> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
>>>> int16_t component_idx;
>>>> };
>>>> +struct elf_function_sym {
>>>> + const char *name;
>>>> + uint64_t addr;
>>>> +};
>>>> +
>>>> /* state used to do later encoding of saved functions */
>>>> struct btf_encoder_func_state {
>>>> struct elf_function *elf;
>>>> + struct elf_function_sym *sym;
>>>> + uint64_t addr;
>>>> uint32_t type_id_off;
>>>> uint16_t nr_parms;
>>>> uint16_t nr_annots;
>>>> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
>>>> struct btf_encoder_func_annot *annots;
>>>> };
>>>> -struct elf_function_sym {
>>>> - const char *name;
>>>> - uint64_t addr;
>>>> -};
>>>> -
>>>> struct elf_function {
>>>> char *name;
>>>> struct elf_function_sym *syms;
>>>> @@ -145,7 +147,8 @@ struct btf_encoder {
>>>> skip_encoding_decl_tag,
>>>> tag_kfuncs,
>>>> gen_distilled_base,
>>>> - encode_attributes;
>>>> + encode_attributes,
>>>> + true_signature;
>>>> uint32_t array_index_id;
>>>> struct elf_secinfo *secinfo;
>>>> size_t seccnt;
>>>> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>>>> goto out;
>>>> }
>>>> }
>>>> + if (encoder->true_signature && fn->lexblock.ip.addr) {
>>>> + int i;
>>>> +
>>>> + for (i = 0; i < func->sym_cnt; i++) {
>>>> + if (fn->lexblock.ip.addr != func->syms[i].addr)
>>>> + continue;
>>>> + /* Only need to record address for '.'-suffixed
>>>> + * functions, since we only currently need true
>>>> + * signatures for them.
>>>> + */
>>>> + if (!strchr(func->syms[i].name, '.'))
>>>> + continue;
>>>> + state->sym = &func->syms[i];
>>>> + break;
>>>> + }
>>>> + }
>>>> state->inconsistent_proto = ftype->inconsistent_proto;
>>>> state->unexpected_reg = ftype->unexpected_reg;
>>>> state->optimized_parms = ftype->optimized_parms;
>>>> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
>>>> state->reordered_parm = ftype->reordered_parm;
>>>> ftype__for_each_parameter(ftype, param) {
>>>> - const char *name = parameter__name(param) ?: "";
>>>> + const char *name;
>>>> + /* No location info + reordered means optimized out. */
>>>> + if (ftype->reordered_parm && !param->has_loc)
>>>> + continue;
>>>> + name = parameter__name(param) ?: "";
>>>> str_off = btf__add_str(btf, name);
>>>> if (str_off < 0) {
>>>> err = str_off;
>>>> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>>>> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
>>>> name = func->name;
>>>> + if (encoder->true_signature && state->sym)
>>>> + name = state->sym->name;
>>>> +
>>>> if (btf_fnproto_id >= 0)
>>>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>>>> name, false);
>>>> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>>>> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
>>>> j++;
>>>> + /* Add true signatures for case where we have an exact
>>>> + * symbol match by address from DWARF->ELF and have a
>>>> + * "." suffixed name.
>>>> + */
>>>> + if (encoder->true_signature) {
>>>> + int k;
>>>> +
>>>> + for (k = i; k < nr_saved_fns; k++) {
>>>> + struct btf_encoder_func_state *true_state = &saved_fns[k];
>>>> +
>>>> + if (state->elf != true_state->elf)
>>>> + break;
>>>> + if (!true_state->sym)
>>>> + continue;
>>>> + /* Unexpected reg, uncertain parm loc and
>>>> + * ambiguous address mean we cannot trust fentry.
>>>> + */
>>>> + if (true_state->unexpected_reg ||
>>>> + true_state->uncertain_parm_loc ||
>>>> + true_state->ambiguous_addr)
>>>> + continue;
>>>> + err = btf_encoder__add_func(encoder, true_state);
>>>> + if (err < 0)
>>>> + goto out;
>>>> + break;
>>>> + }
>>>> + }
>>>> +
>>>> + /* True symbol that was handled above; skip. */
>>>> + if (state->sym)
>>>> + continue;
>>>> +
>>>> /* do not exclude functions with optimized-out parameters; they
>>>> * may still be _called_ with the right parameter values, they
>>>> * just do not _use_ them. Only exclude functions with
>>>> @@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
>>>> encoder->verbose = verbose;
>>>> encoder->has_index_type = false;
>>>> encoder->need_index_type = false;
>>>> diff --git a/dwarves.h b/dwarves.h
>>>> index 78bedf5..d7c6474 100644
>>>> --- a/dwarves.h
>>>> +++ b/dwarves.h
>>>> @@ -101,6 +101,7 @@ struct conf_load {
>>>> bool btf_decl_tag_kfuncs;
>>>> bool btf_gen_distilled_base;
>>>> bool btf_attributes;
>>>> + bool true_signature;
>>>> uint8_t hashtable_bits;
>>>> uint8_t max_hashtable_bits;
>>>> uint16_t kabi_prefix_len;
>>>> diff --git a/pahole.c b/pahole.c
>>>> index ef01e58..02a0d19 100644
>>>> --- a/pahole.c
>>>> +++ b/pahole.c
>>>> @@ -1234,6 +1234,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
>>> Currently, in pahole, when checking whether signature has changed during
>>> optimization or not, we only check parameters.
>>>
>>> But compiler optimization may optimize away return value and such
>>> information is not available in dwarf.
>>>
>>> For example,
>>>
>>> $ cat test.c
>>> #include <stdio.h>
>>> unsigned tar(int a);
>>> __attribute__((noinline)) static int foo(int a, int b)
>>> {
>>> return tar(a) + tar(a + 1);
>>> }
>>> __attribute__((noinline)) int bar(int a)
>>> {
>>> foo(a, 1);
>>> return 0;
>>> }
>>>
>>> In this particular case, the return value of foo() is actually not used
>>> and the compiler will optimize it away with returning void (at least
>>> for llvm).
>>>
>>> $ /opt/rh/gcc-toolset-15/root/usr/bin/gcc -O2 -g -c test.c
>>> $ llvm-dwarfdump test.o
>>> ...
>>> 0x000000d9: DW_TAG_subprogram
>>> DW_AT_name ("foo")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_decl_column (38)
>>> DW_AT_prototyped (true)
>>> DW_AT_type (0x0000005d "int")
>>> DW_AT_inline (DW_INL_inlined)
>>> DW_AT_sibling (0x000000fb)
>>> 0x000000ea: DW_TAG_formal_parameter
>>> DW_AT_name ("a")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_decl_column (46)
>>> DW_AT_type (0x0000005d "int")
>>> 0x000000f2: DW_TAG_formal_parameter
>>> DW_AT_name ("b")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_decl_column (53)
>>> DW_AT_type (0x0000005d "int")
>>>
>>> 0x000000fa: NULL
>>>
>>> 0x000000fb: DW_TAG_subprogram
>>> DW_AT_abstract_origin (0x000000d9 "foo")
>>> DW_AT_low_pc (0x0000000000000000)
>>> DW_AT_high_pc (0x0000000000000011)
>>> DW_AT_frame_base (DW_OP_call_frame_cfa)
>>> DW_AT_call_all_calls (true)
>>>
>>> 0x00000112: DW_TAG_formal_parameter
>>> DW_AT_abstract_origin (0x000000ea "a")
>>> DW_AT_location (0x00000026:
>>> [0x0000000000000000, 0x0000000000000007): DW_OP_reg5 RDI
>>> [0x0000000000000007, 0x000000000000000c): DW_OP_reg3 RBX
>>> [0x000000000000000c, 0x0000000000000010): DW_OP_breg5 RDI-1, DW_OP_stack_value
>>> [0x0000000000000010, 0x0000000000000011): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>>> DW_AT_GNU_locviews (0x0000001e)
>>>
>>> 0x0000011f: DW_TAG_formal_parameter
>>> DW_AT_abstract_origin (0x000000f2 "b")
>>> DW_AT_const_value (0x01)
>>> ...
>>>
>>> Assembly code:
>>> 0000000000000000 <foo.constprop.0.isra.0>:
>>> 0: 53 pushq %rbx
>>> 1: 89 fb movl %edi, %ebx
>>> 3: e8 00 00 00 00 callq 0x8 <foo.constprop.0.isra.0+0x8>
>>> 8: 8d 7b 01 leal 0x1(%rbx), %edi
>>> b: 5b popq %rbx
>>> c: e9 00 00 00 00 jmp 0x11 <foo.constprop.0.isra.0+0x11>
>>> 11: 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
>>> 1c: 0f 1f 40 00 nopl (%rax)
>>>
>>> 0000000000000020 <bar>:
>>> 20: 48 83 ec 08 subq $0x8, %rsp
>>> 24: e8 d7 ff ff ff callq 0x0 <foo.constprop.0.isra.0>
>>> 29: 31 c0 xorl %eax, %eax
>>> 2b: 48 83 c4 08 addq $0x8, %rsp
>>> 2f: c3 retq
>>>
>>> $ clang -O2 -g -c test.c
>>> $ llvm-dwarfdump test.o
>>> ...
>>> 0x0000004e: DW_TAG_subprogram
>>> DW_AT_low_pc (0x0000000000000010)
>>> DW_AT_high_pc (0x0000000000000022)
>>> 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/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_prototyped (true)
>>> DW_AT_calling_convention (DW_CC_nocall)
>>> DW_AT_type (0x00000096 "int")
>>>
>>> 0x0000005e: DW_TAG_formal_parameter
>>> DW_AT_location (indexed (0x1) loclist = 0x00000022:
>>> [0x0000000000000010, 0x0000000000000018): DW_OP_reg5 RDI
>>> [0x0000000000000018, 0x000000000000001a): DW_OP_reg3 RBX
>>> [0x000000000000001a, 0x0000000000000022): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>>> DW_AT_name ("a")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_type (0x00000096 "int")
>>>
>>> 0x00000067: DW_TAG_formal_parameter
>>> DW_AT_name ("b")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_type (0x00000096 "int")
>>> ...
>>> Assembly code:encs
>>> 0000000000000000 <bar>:
>>> 0: 50 pushq %rax
>>> 1: e8 0a 00 00 00 callq 0x10 <foo>
>>> 6: 31 c0 xorl %eax, %eax
>>> 8: 59 popq %rcx
>>> 9: c3 retq
>>> a: 66 0f 1f 44 00 00 nopw (%rax,%rax)
>>>
>>> 0000000000000010 <foo>:
>>> 10: 53 pushq %rbx
>>> 11: 89 fb movl %edi, %ebx
>>> 13: e8 00 00 00 00 callq 0x18 <foo+0x8>
>>> 18: ff c3 incl %ebx
>>> 1a: 89 df movl %ebx, %edi
>>> 1c: 5b popq %rbx
>>> 1d: e9 00 00 00 00 jmp 0x22 <foo+0x12>
>>>
>>>
>>> The compiler knows whether the return type has changed or not.
>>> Unfortunately the information is not available in dwarf. So
>>> BTF will encode source level return type even if the actual
>>> return type could be void due to optimization.
>>>
>>> This is not perfect but at least it is an improvement
>>> for true signature. But it would be great if llvm/gcc
>>> side can coordinate to propose something in compiler/dwarf
>>> to encode return type change as well. In llvm,
>>> AFAIK, the only return type change will be
>>> 'original non-void type' -> 'void type'.
>>>
>> Yeah, we dug into this a bit on the gcc side with David's help and it
>> appears the only mechanism used seems to be abstract origin reference
>> unfortunately. It seems to me that in theory the compiler could encode
>> the actual type for return types and any parameters that change type
>> from the abstract to concrete representation, and we could end up with
>> a mix of abstract origin refererences for the types that don't change and
>> non-abstract for the types that do.
>>
>> David, Jose, I'm wondering if the information is available to gcc to do
>> that at late DWARF encoding time? Thanks!
> Yes, at least to some degree. For non-inlined cases, I don't currently know
> of a case where we do not have the information. For inlined cases I am
> less confident that it's still available.
>
> I spent some time looking at this last year, thinking that we could at least
> use this to improve gcc-emitted BTF with the final signatures for non-inline
> funcs, even if there is no perfect way to encode it in DWARF.
>
> For example, I have:
>
> __attribute__((noinline))
> static int callee (struct tcphdr *tcp, int x, int y)
> {...}
>
> In this case both the return value and the param 'x' are dropped by
> optimizations.
>
> At late DWARF time we have a function_decl node for the optimized version
> 'callee.constprop.isra' which has a return type of void and arg type list
> reflecting the remaining two parameters. We can also get a pointer to
> the original pre-optimized decl to compare against. i.e. we have both:
>
> callee.constprop.isra
> return type: void
> arg types: struct tcphdr*, int, void
> callee
> return type: int
> arg types: struct tcphdr*, int, int, void
So gcc is able to encode both functions since their func names
are different and both can be in dwarf. The pahole can find out
callee.constprop.isra is matching the same function name in
kallsyms and pahole will be able to encode callee.constprop.isra
function in vmlinux BTF.
But unfortunately, llvm does not have this luxury since llvm
will not allow suffixes even after function signature gets
changed. So only the original signature is encoded in dwarf.
Let us say we could recover true signatures through locations.
From llvm side,
DW_AT_calling_convention (DW_CC_nocall)
can help indicate there is a signature change. But there is
no way to find whether return type is changed or not if
some parameters are changed/removed. Any suggestion?
>
> This also extends to more complicated cases where IPA-SRA splits
> aggregate parameters into individual pieces (e.g. split a pass-by-value
> struct into only passing the relevant fields which are used).
> I think some of these transformations are not always reflected in DWARF.
>
> For a simpler case like the one here, it is currently reflected in DWARF
> via the abstract_origin subprogram DIE for callee in which only the
> remaining two parameters have concrete AT_location.
> (Although IMO it is rather ambiguous whether the abstract_origin DIE's
> lack of AT_type means "return type is unchanged" or "return type is void")
>
>
>> Alan
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-14 18:22 ` David Faust
2026-01-15 3:27 ` Yonghong Song
@ 2026-01-15 18:38 ` Yonghong Song
1 sibling, 0 replies; 19+ messages in thread
From: Yonghong Song @ 2026-01-15 18:38 UTC (permalink / raw)
To: David Faust, Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf,
Jose E. Marchesi
On 1/14/26 10:22 AM, David Faust wrote:
>
> On 1/14/26 08:55, Alan Maguire wrote:
>> On 14/01/2026 16:15, Yonghong Song wrote:
>>>
>>> On 1/13/26 5:13 AM, Alan Maguire wrote:
>>>> Currently we collate function information by name and add functions
>>>> provided there are no inconsistencies across various representations.
>>>>
>>>> For true_signature support - where we wish to add the real signature
>>>> of a function even if it differs from source level - we need to do
>>>> a few things:
>>>>
>>>> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
>>>> we can do this via the address associated with the function.
>>>> In doing this, we can then be confident that the debug info
>>>> for foo.isra.0 is the right info for the function at that
>>>> address.
>>>>
>>>> 2. When adding saved functions we need to look for such cases
>>>> and provided they do not violate other constraints around BTF
>>>> representation - unexpected reg usage for function, uncertain
>>>> parameter location or ambiguous address - we add them with
>>>> their "."-suffixed name. The latter can be used as a signal
>>>> that the function is transformed from the original.
>>>>
>>>> Doing this adds 500 functions to BTF. These are traceable with
>>>> their "."-suffix names and because we have excluded ambiguous
>>>> address cases we know exactly which function address they refer
>>>> to.
>>>>
>>>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>>>> ---
>>>> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
>>>> dwarves.h | 1 +
>>>> pahole.c | 1 +
>>>> 3 files changed, 68 insertions(+), 7 deletions(-)
>>>>
>>>> diff --git a/btf_encoder.c b/btf_encoder.c
>>>> index 5bc61cb..01fd469 100644
>>>> --- a/btf_encoder.c
>>>> +++ b/btf_encoder.c
>>>> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
>>>> int16_t component_idx;
>>>> };
>>>> +struct elf_function_sym {
>>>> + const char *name;
>>>> + uint64_t addr;
>>>> +};
>>>> +
>>>> /* state used to do later encoding of saved functions */
>>>> struct btf_encoder_func_state {
>>>> struct elf_function *elf;
>>>> + struct elf_function_sym *sym;
>>>> + uint64_t addr;
>>>> uint32_t type_id_off;
>>>> uint16_t nr_parms;
>>>> uint16_t nr_annots;
>>>> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
>>>> struct btf_encoder_func_annot *annots;
>>>> };
>>>> -struct elf_function_sym {
>>>> - const char *name;
>>>> - uint64_t addr;
>>>> -};
>>>> -
>>>> struct elf_function {
>>>> char *name;
>>>> struct elf_function_sym *syms;
>>>> @@ -145,7 +147,8 @@ struct btf_encoder {
>>>> skip_encoding_decl_tag,
>>>> tag_kfuncs,
>>>> gen_distilled_base,
>>>> - encode_attributes;
>>>> + encode_attributes,
>>>> + true_signature;
>>>> uint32_t array_index_id;
>>>> struct elf_secinfo *secinfo;
>>>> size_t seccnt;
>>>> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>>>> goto out;
>>>> }
>>>> }
>>>> + if (encoder->true_signature && fn->lexblock.ip.addr) {
>>>> + int i;
>>>> +
>>>> + for (i = 0; i < func->sym_cnt; i++) {
>>>> + if (fn->lexblock.ip.addr != func->syms[i].addr)
>>>> + continue;
>>>> + /* Only need to record address for '.'-suffixed
>>>> + * functions, since we only currently need true
>>>> + * signatures for them.
>>>> + */
>>>> + if (!strchr(func->syms[i].name, '.'))
>>>> + continue;
>>>> + state->sym = &func->syms[i];
>>>> + break;
>>>> + }
>>>> + }
>>>> state->inconsistent_proto = ftype->inconsistent_proto;
>>>> state->unexpected_reg = ftype->unexpected_reg;
>>>> state->optimized_parms = ftype->optimized_parms;
>>>> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
>>>> state->reordered_parm = ftype->reordered_parm;
>>>> ftype__for_each_parameter(ftype, param) {
>>>> - const char *name = parameter__name(param) ?: "";
>>>> + const char *name;
>>>> + /* No location info + reordered means optimized out. */
>>>> + if (ftype->reordered_parm && !param->has_loc)
>>>> + continue;
>>>> + name = parameter__name(param) ?: "";
>>>> str_off = btf__add_str(btf, name);
>>>> if (str_off < 0) {
>>>> err = str_off;
>>>> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>>>> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
>>>> name = func->name;
>>>> + if (encoder->true_signature && state->sym)
>>>> + name = state->sym->name;
>>>> +
>>>> if (btf_fnproto_id >= 0)
>>>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>>>> name, false);
>>>> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>>>> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
>>>> j++;
>>>> + /* Add true signatures for case where we have an exact
>>>> + * symbol match by address from DWARF->ELF and have a
>>>> + * "." suffixed name.
>>>> + */
>>>> + if (encoder->true_signature) {
>>>> + int k;
>>>> +
>>>> + for (k = i; k < nr_saved_fns; k++) {
>>>> + struct btf_encoder_func_state *true_state = &saved_fns[k];
>>>> +
>>>> + if (state->elf != true_state->elf)
>>>> + break;
>>>> + if (!true_state->sym)
>>>> + continue;
>>>> + /* Unexpected reg, uncertain parm loc and
>>>> + * ambiguous address mean we cannot trust fentry.
>>>> + */
>>>> + if (true_state->unexpected_reg ||
>>>> + true_state->uncertain_parm_loc ||
>>>> + true_state->ambiguous_addr)
>>>> + continue;
>>>> + err = btf_encoder__add_func(encoder, true_state);
>>>> + if (err < 0)
>>>> + goto out;
>>>> + break;
>>>> + }
>>>> + }
>>>> +
>>>> + /* True symbol that was handled above; skip. */
>>>> + if (state->sym)
>>>> + continue;
>>>> +
>>>> /* do not exclude functions with optimized-out parameters; they
>>>> * may still be _called_ with the right parameter values, they
>>>> * just do not _use_ them. Only exclude functions with
>>>> @@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
>>>> encoder->verbose = verbose;
>>>> encoder->has_index_type = false;
>>>> encoder->need_index_type = false;
>>>> diff --git a/dwarves.h b/dwarves.h
>>>> index 78bedf5..d7c6474 100644
>>>> --- a/dwarves.h
>>>> +++ b/dwarves.h
>>>> @@ -101,6 +101,7 @@ struct conf_load {
>>>> bool btf_decl_tag_kfuncs;
>>>> bool btf_gen_distilled_base;
>>>> bool btf_attributes;
>>>> + bool true_signature;
>>>> uint8_t hashtable_bits;
>>>> uint8_t max_hashtable_bits;
>>>> uint16_t kabi_prefix_len;
>>>> diff --git a/pahole.c b/pahole.c
>>>> index ef01e58..02a0d19 100644
>>>> --- a/pahole.c
>>>> +++ b/pahole.c
>>>> @@ -1234,6 +1234,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
>>> Currently, in pahole, when checking whether signature has changed during
>>> optimization or not, we only check parameters.
>>>
>>> But compiler optimization may optimize away return value and such
>>> information is not available in dwarf.
>>>
>>> For example,
>>>
>>> $ cat test.c
>>> #include <stdio.h>
>>> unsigned tar(int a);
>>> __attribute__((noinline)) static int foo(int a, int b)
>>> {
>>> return tar(a) + tar(a + 1);
>>> }
>>> __attribute__((noinline)) int bar(int a)
>>> {
>>> foo(a, 1);
>>> return 0;
>>> }
>>>
>>> In this particular case, the return value of foo() is actually not used
>>> and the compiler will optimize it away with returning void (at least
>>> for llvm).
>>>
>>> $ /opt/rh/gcc-toolset-15/root/usr/bin/gcc -O2 -g -c test.c
>>> $ llvm-dwarfdump test.o
>>> ...
>>> 0x000000d9: DW_TAG_subprogram
>>> DW_AT_name ("foo")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_decl_column (38)
>>> DW_AT_prototyped (true)
>>> DW_AT_type (0x0000005d "int")
>>> DW_AT_inline (DW_INL_inlined)
>>> DW_AT_sibling (0x000000fb)
>>> 0x000000ea: DW_TAG_formal_parameter
>>> DW_AT_name ("a")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_decl_column (46)
>>> DW_AT_type (0x0000005d "int")
>>> 0x000000f2: DW_TAG_formal_parameter
>>> DW_AT_name ("b")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_decl_column (53)
>>> DW_AT_type (0x0000005d "int")
>>>
>>> 0x000000fa: NULL
>>>
>>> 0x000000fb: DW_TAG_subprogram
>>> DW_AT_abstract_origin (0x000000d9 "foo")
>>> DW_AT_low_pc (0x0000000000000000)
>>> DW_AT_high_pc (0x0000000000000011)
>>> DW_AT_frame_base (DW_OP_call_frame_cfa)
>>> DW_AT_call_all_calls (true)
>>>
>>> 0x00000112: DW_TAG_formal_parameter
>>> DW_AT_abstract_origin (0x000000ea "a")
>>> DW_AT_location (0x00000026:
>>> [0x0000000000000000, 0x0000000000000007): DW_OP_reg5 RDI
>>> [0x0000000000000007, 0x000000000000000c): DW_OP_reg3 RBX
>>> [0x000000000000000c, 0x0000000000000010): DW_OP_breg5 RDI-1, DW_OP_stack_value
>>> [0x0000000000000010, 0x0000000000000011): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>>> DW_AT_GNU_locviews (0x0000001e)
>>>
>>> 0x0000011f: DW_TAG_formal_parameter
>>> DW_AT_abstract_origin (0x000000f2 "b")
>>> DW_AT_const_value (0x01)
>>> ...
>>>
>>> Assembly code:
>>> 0000000000000000 <foo.constprop.0.isra.0>:
>>> 0: 53 pushq %rbx
>>> 1: 89 fb movl %edi, %ebx
>>> 3: e8 00 00 00 00 callq 0x8 <foo.constprop.0.isra.0+0x8>
>>> 8: 8d 7b 01 leal 0x1(%rbx), %edi
>>> b: 5b popq %rbx
>>> c: e9 00 00 00 00 jmp 0x11 <foo.constprop.0.isra.0+0x11>
>>> 11: 66 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:(%rax,%rax)
>>> 1c: 0f 1f 40 00 nopl (%rax)
>>>
>>> 0000000000000020 <bar>:
>>> 20: 48 83 ec 08 subq $0x8, %rsp
>>> 24: e8 d7 ff ff ff callq 0x0 <foo.constprop.0.isra.0>
>>> 29: 31 c0 xorl %eax, %eax
>>> 2b: 48 83 c4 08 addq $0x8, %rsp
>>> 2f: c3 retq
>>>
>>> $ clang -O2 -g -c test.c
>>> $ llvm-dwarfdump test.o
>>> ...
>>> 0x0000004e: DW_TAG_subprogram
>>> DW_AT_low_pc (0x0000000000000010)
>>> DW_AT_high_pc (0x0000000000000022)
>>> 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/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_prototyped (true)
>>> DW_AT_calling_convention (DW_CC_nocall)
>>> DW_AT_type (0x00000096 "int")
>>>
>>> 0x0000005e: DW_TAG_formal_parameter
>>> DW_AT_location (indexed (0x1) loclist = 0x00000022:
>>> [0x0000000000000010, 0x0000000000000018): DW_OP_reg5 RDI
>>> [0x0000000000000018, 0x000000000000001a): DW_OP_reg3 RBX
>>> [0x000000000000001a, 0x0000000000000022): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>>> DW_AT_name ("a")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_type (0x00000096 "int")
>>>
>>> 0x00000067: DW_TAG_formal_parameter
>>> DW_AT_name ("b")
>>> DW_AT_decl_file ("/home/yhs/tests/sig-change/deadret/test.c")
>>> DW_AT_decl_line (3)
>>> DW_AT_type (0x00000096 "int")
>>> ...
>>> Assembly code:encs
>>> 0000000000000000 <bar>:
>>> 0: 50 pushq %rax
>>> 1: e8 0a 00 00 00 callq 0x10 <foo>
>>> 6: 31 c0 xorl %eax, %eax
>>> 8: 59 popq %rcx
>>> 9: c3 retq
>>> a: 66 0f 1f 44 00 00 nopw (%rax,%rax)
>>>
>>> 0000000000000010 <foo>:
>>> 10: 53 pushq %rbx
>>> 11: 89 fb movl %edi, %ebx
>>> 13: e8 00 00 00 00 callq 0x18 <foo+0x8>
>>> 18: ff c3 incl %ebx
>>> 1a: 89 df movl %ebx, %edi
>>> 1c: 5b popq %rbx
>>> 1d: e9 00 00 00 00 jmp 0x22 <foo+0x12>
>>>
>>>
>>> The compiler knows whether the return type has changed or not.
>>> Unfortunately the information is not available in dwarf. So
>>> BTF will encode source level return type even if the actual
>>> return type could be void due to optimization.
>>>
>>> This is not perfect but at least it is an improvement
>>> for true signature. But it would be great if llvm/gcc
>>> side can coordinate to propose something in compiler/dwarf
>>> to encode return type change as well. In llvm,
>>> AFAIK, the only return type change will be
>>> 'original non-void type' -> 'void type'.
>>>
>> Yeah, we dug into this a bit on the gcc side with David's help and it
>> appears the only mechanism used seems to be abstract origin reference
>> unfortunately. It seems to me that in theory the compiler could encode
>> the actual type for return types and any parameters that change type
>> from the abstract to concrete representation, and we could end up with
>> a mix of abstract origin refererences for the types that don't change and
>> non-abstract for the types that do.
>>
>> David, Jose, I'm wondering if the information is available to gcc to do
>> that at late DWARF encoding time? Thanks!
> Yes, at least to some degree. For non-inlined cases, I don't currently know
> of a case where we do not have the information. For inlined cases I am
> less confident that it's still available.
>
> I spent some time looking at this last year, thinking that we could at least
> use this to improve gcc-emitted BTF with the final signatures for non-inline
> funcs, even if there is no perfect way to encode it in DWARF.
>
> For example, I have:
>
> __attribute__((noinline))
> static int callee (struct tcphdr *tcp, int x, int y)
> {...}
>
> In this case both the return value and the param 'x' are dropped by
> optimizations.
>
> At late DWARF time we have a function_decl node for the optimized version
> 'callee.constprop.isra' which has a return type of void and arg type list
> reflecting the remaining two parameters. We can also get a pointer to
> the original pre-optimized decl to compare against. i.e. we have both:
>
> callee.constprop.isra
> return type: void
> arg types: struct tcphdr*, int, void
> callee
> return type: int
> arg types: struct tcphdr*, int, int, void
>
> This also extends to more complicated cases where IPA-SRA splits
> aggregate parameters into individual pieces (e.g. split a pass-by-value
> struct into only passing the relevant fields which are used).
> I think some of these transformations are not always reflected in DWARF.
>
> For a simpler case like the one here, it is currently reflected in DWARF
> via the abstract_origin subprogram DIE for callee in which only the
> remaining two parameters have concrete AT_location.
> (Although IMO it is rather ambiguous whether the abstract_origin DIE's
> lack of AT_type means "return type is unchanged" or "return type is void")
David Blaikie from Google provided a link
https://dwarfstd.org/issues/221105.1.html
which tries to provide return value locations. This might be useful
to find proper return types. The intention of this new addition
is for dwarf 6.
So we could temporarily keep the return type as the source for now.
When dwarf 6 is implemented by gcc/clang, we could use return value
location to recover return type.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 0/4] Improve BTF concrete function accuracy
2026-01-13 13:13 [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
` (3 preceding siblings ...)
2026-01-13 13:13 ` [PATCH dwarves 4/4] btf_encoder: Prefer strong function definitions for BTF generation Alan Maguire
@ 2026-01-20 9:52 ` Alan Maguire
4 siblings, 0 replies; 19+ messages in thread
From: Alan Maguire @ 2026-01-20 9:52 UTC (permalink / raw)
To: yonghong.song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 13/01/2026 13:13, Alan Maguire wrote:
> This series brings together a few solutions to issues we have
> with accuracy of BTF function representation at the binary level.
>
> The first patch detects mismatches between concrete (binary)
> and abstract (source-level) function signatures as a means
> of either excluding them or providing a "true" function signature.
>
> Patch 2 is from Yonghong's LLVM true function signature series,
> and helps for patch 3 which adds GCC true function signature
> support for optimized functions; with that support, we use
> binary-level signatures for .isra, .constprop functions and
> represent them with their "." suffixes as BTF_KIND_FUNC
> names. This allows for fentry attach to such functions, and
> the "." suffix is an indicator of signature modification.
> The feature is guarded by a default-off BTF feature because
> older kernels did not support a "." in a function name.
>
> Patch 4 is Matt's patch to favour the strong function
> over the associated weak declaration. The other patches
> are important prerequisites for this as the patch selects
> the binary-level function (with a lowpc value), and in
> the case of optimized functions we were often selecting
> the .isra function with optimized-out parameters. Because
> pahole did not previously detect this correctly we ended
> up with functions with signatures having reordered parameters.
>
> Patches 1-3 help avoid this by better detecting optimized-out
> function parameters.
>
> With these patches in place, ~20 functions are omitted from
> vmlinux BTF; all these are "."-suffixed functions which
> we were not noticing had optimized-out parameters.
>
> Experimenting with adding true_signature to BTF features
> we end up adding approximately 500 .isra and .constprop
> functions to vmlinux BTF.
>
> The true function signature support here will also hopefully
> help pave the way for Yonghong's work on the LLVM side.
>
hi folks, I'd like to land this series (patches 1, 3 and 4;
patch 2 isn't needed right now) soon so we have Matt's
fix in place; if anyone has cycles to further look at the patches
or test it to ensure the vmlinux and module BTF generated doesn't
cause problems, that would be great. Thanks!
Alan
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters
2026-01-13 13:13 ` [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters Alan Maguire
@ 2026-01-20 16:07 ` Yonghong Song
0 siblings, 0 replies; 19+ messages in thread
From: Yonghong Song @ 2026-01-20 16:07 UTC (permalink / raw)
To: Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 1/13/26 5:13 AM, Alan Maguire wrote:
> When encoding concrete instances of optimized functions it is possible
> parameters get reordered, often due to a parameter being optimized out;
> in such cases the order of abstract origin references to the abstract
> function is different, and the parameters that are optimized out
> usually appear after all the non-optimized parameters with no
> DW_AT_location information [1].
>
> As an example consider
>
> static void __blkcg_rstat_flush(struct blkcg *blkcg, int cpu);
>
> It has - as expected - an abstract representation as follows:
>
> <1><6392a2d>: Abbrev Number: 47 (DW_TAG_subprogram)
> <6392a2e> DW_AT_name : (indirect string, offset: 0x261e25): __blkcg_rstat_flush
> <6392a32> DW_AT_decl_file : 1
> <6392a33> DW_AT_decl_line : 1043
> <6392a35> DW_AT_decl_column : 13
> <6392a36> DW_AT_prototyped : 1
> <6392a36> DW_AT_inline : 1 (inlined)
> <6392a37> DW_AT_sibling : <0x6392bac>
> <2><6392a3b>: Abbrev Number: 38 (DW_TAG_formal_parameter)
> <6392a3c> DW_AT_name : (indirect string, offset: 0xa7a9f): blkcg
> <6392a40> DW_AT_decl_file : 1
> <6392a41> DW_AT_decl_line : 1043
> <6392a43> DW_AT_decl_column : 47
> <6392a44> DW_AT_type : <0x638b611>
> <2><6392a48>: Abbrev Number: 20 (DW_TAG_formal_parameter)
> <6392a49> DW_AT_name : cpu
> <6392a4d> DW_AT_decl_file : 1
> <6392a4e> DW_AT_decl_line : 1043
> <6392a50> DW_AT_decl_column : 58
> <6392a51> DW_AT_type : <0x6377f8f>
>
> However the concrete representation after optimization becomes:
>
> ffffffff8186d180 t __blkcg_rstat_flush.isra.0
>
> and has a concrete representation with parameter order switched:
>
> <1><6399661>: Abbrev Number: 110 (DW_TAG_subprogram)
> <6399662> DW_AT_abstract_origin: <0x6392a2d>
> <6399666> DW_AT_low_pc : 0xffffffff8186d180
> <639966e> DW_AT_high_pc : 0x169
> <6399676> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
> <6399678> DW_AT_GNU_all_call_sites: 1
> <6399678> DW_AT_sibling : <0x6399a8a>
> <2><639967c>: Abbrev Number: 4 (DW_TAG_formal_parameter)
> <639967d> DW_AT_abstract_origin: <0x6392a48>
> <6399681> DW_AT_location : 0x1fe21fb (location list)
> <6399685> DW_AT_GNU_locviews: 0x1fe21f5
> <2><63996e4>: Abbrev Number: 4 (DW_TAG_formal_parameter)
> <63996e5> DW_AT_abstract_origin: <0x6392a3b>
> <63996e9> DW_AT_location : 0x1fe2387 (location list)
> <63996ed> DW_AT_GNU_locviews: 0x1fe2385
>
> In other words we end up with
>
> static void __blkcg_rstat_flush.isra(int cpu, struct blkcg *blkcg);
>
> We are not detecting cases like this in pahole, so we need to
> catch it to exclude such cases since they could lead to incorrect
> fentry attachment.
>
> Future work around true function signatures will allow such functions
> with their "." suffixes, but even for such cases it is good to
> detect the reordering.
>
> In practice we just end up excluding a few more .isra/.constprop
> functions which we cannot fentry-attach by name anyway; see [2] for an
> example list from CI.
>
> [1] https://lore.kernel.org/bpf/101b74c9-949a-4bf4-a766-a5343b70bdd2@oracle.com/
> [2] https://github.com/alan-maguire/dwarves/actions/runs/20031993822
>
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Acked-by: Yonghong Song <yonghong.song@linux.dev>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-13 13:13 ` [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions Alan Maguire
2026-01-14 16:15 ` Yonghong Song
@ 2026-01-20 17:53 ` Yonghong Song
2026-01-22 18:21 ` Alan Maguire
1 sibling, 1 reply; 19+ messages in thread
From: Yonghong Song @ 2026-01-20 17:53 UTC (permalink / raw)
To: Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 1/13/26 5:13 AM, Alan Maguire wrote:
> Currently we collate function information by name and add functions
> provided there are no inconsistencies across various representations.
>
> For true_signature support - where we wish to add the real signature
> of a function even if it differs from source level - we need to do
> a few things:
>
> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
> we can do this via the address associated with the function.
> In doing this, we can then be confident that the debug info
> for foo.isra.0 is the right info for the function at that
> address.
>
> 2. When adding saved functions we need to look for such cases
> and provided they do not violate other constraints around BTF
> representation - unexpected reg usage for function, uncertain
> parameter location or ambiguous address - we add them with
> their "."-suffixed name. The latter can be used as a signal
> that the function is transformed from the original.
>
> Doing this adds 500 functions to BTF. These are traceable with
> their "."-suffix names and because we have excluded ambiguous
> address cases we know exactly which function address they refer
> to.
>
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> ---
> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
> dwarves.h | 1 +
> pahole.c | 1 +
> 3 files changed, 68 insertions(+), 7 deletions(-)
>
> diff --git a/btf_encoder.c b/btf_encoder.c
> index 5bc61cb..01fd469 100644
> --- a/btf_encoder.c
> +++ b/btf_encoder.c
> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
> int16_t component_idx;
> };
>
> +struct elf_function_sym {
> + const char *name;
> + uint64_t addr;
> +};
> +
> /* state used to do later encoding of saved functions */
> struct btf_encoder_func_state {
> struct elf_function *elf;
> + struct elf_function_sym *sym;
> + uint64_t addr;
> uint32_t type_id_off;
> uint16_t nr_parms;
> uint16_t nr_annots;
> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
> struct btf_encoder_func_annot *annots;
> };
>
> -struct elf_function_sym {
> - const char *name;
> - uint64_t addr;
> -};
> -
> struct elf_function {
> char *name;
> struct elf_function_sym *syms;
> @@ -145,7 +147,8 @@ struct btf_encoder {
> skip_encoding_decl_tag,
> tag_kfuncs,
> gen_distilled_base,
> - encode_attributes;
> + encode_attributes,
> + true_signature;
> uint32_t array_index_id;
> struct elf_secinfo *secinfo;
> size_t seccnt;
> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
> goto out;
> }
> }
> + if (encoder->true_signature && fn->lexblock.ip.addr) {
> + int i;
> +
> + for (i = 0; i < func->sym_cnt; i++) {
> + if (fn->lexblock.ip.addr != func->syms[i].addr)
> + continue;
> + /* Only need to record address for '.'-suffixed
> + * functions, since we only currently need true
> + * signatures for them.
> + */
> + if (!strchr(func->syms[i].name, '.'))
> + continue;
> + state->sym = &func->syms[i];
> + break;
> + }
> + }
> state->inconsistent_proto = ftype->inconsistent_proto;
> state->unexpected_reg = ftype->unexpected_reg;
> state->optimized_parms = ftype->optimized_parms;
> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
> state->reordered_parm = ftype->reordered_parm;
> ftype__for_each_parameter(ftype, param) {
> - const char *name = parameter__name(param) ?: "";
> + const char *name;
>
> + /* No location info + reordered means optimized out. */
> + if (ftype->reordered_parm && !param->has_loc)
> + continue;
> + name = parameter__name(param) ?: "";
> str_off = btf__add_str(btf, name);
> if (str_off < 0) {
> err = str_off;
> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>
> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
> name = func->name;
> + if (encoder->true_signature && state->sym)
> + name = state->sym->name;
> +
> if (btf_fnproto_id >= 0)
> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
> name, false);
> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
> j++;
>
> + /* Add true signatures for case where we have an exact
> + * symbol match by address from DWARF->ELF and have a
> + * "." suffixed name.
> + */
> + if (encoder->true_signature) {
> + int k;
> +
> + for (k = i; k < nr_saved_fns; k++) {
> + struct btf_encoder_func_state *true_state = &saved_fns[k];
> +
> + if (state->elf != true_state->elf)
> + break;
> + if (!true_state->sym)
> + continue;
> + /* Unexpected reg, uncertain parm loc and
> + * ambiguous address mean we cannot trust fentry.
> + */
> + if (true_state->unexpected_reg ||
> + true_state->uncertain_parm_loc ||
> + true_state->ambiguous_addr)
> + continue;
> + err = btf_encoder__add_func(encoder, true_state);
> + if (err < 0)
> + goto out;
> + break;
> + }
> + }
> +
> + /* True symbol that was handled above; skip. */
> + if (state->sym)
> + continue;
I did an experiment with the following code:
$ cat test.c
struct t { int a; };
__attribute__((noinline)) char *tar(struct t *a, struct t *d) { if (a->a == d->a) return (char *)10; else return (char *)0; }
__attribute__((noinline)) static char * foo(struct t *a, int b, struct t *d)
{
return tar(a, d);
}
__attribute__((noinline)) char *bar(struct t *a, struct t *d)
{
return foo(a, 1, d);
}
struct t p1, p2;
int main() {
return !!bar(&p1, &p2);
}
and compiled with gcc11:
$ gcc -O2 -g test.c
I hacked btf_encoder.c with true_signature is all on and with
$ pahole -JV ./a.out
...
btf_encoder__new: './a.out' doesn't have '.data..percpu' section
File ./a.out:
[1] STRUCT t size=4
a type_id=2 bits_offset=0
[2] INT int size=4 nr_bits=32 encoding=SIGNED
[3] PTR (anon) type_id=4
[4] INT char size=1 nr_bits=8 encoding=SIGNED
[5] PTR (anon) type_id=1
search cu 'test.c' for percpu global variables.
[6] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
[7] FUNC bar type_id=6
[8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d, vararg)
[9] FUNC foo.constprop.0 type_id=8
foo : skipping BTF encoding of function due to reordered parameters
[10] FUNC_PROTO (anon) return=2 args=(void)
[11] FUNC main type_id=10
[12] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
[13] FUNC tar type_id=12
There are two issues.
First in btf_encoder__add_saved_funcs(), it is possible below
+ /* True symbol that was handled above; skip. */
+ if (state->sym)
+ continue;
state->sym is false.
But one of true_state->sym in the above loop could be true.
So if btf_encoder__add_func(encoder, true_state) is successful,
we should continue in the above regardless state->sym null or not.
This will remove the warning:
foo : skipping BTF encoding of function due to reordered parameters
Second, we have foo.constprop.0 func proto encoding:
[8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d, vararg)
The last argument 'vararg' should not be there since the
optimized out argument is already gone.
> +
> /* do not exclude functions with optimized-out parameters; they
> * may still be _called_ with the right parameter values, they
> * just do not _use_ them. Only exclude functions with
> @@ -2585,6 +2643,7 @@ 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->true_signature = conf_load->true_signature;
> encoder->verbose = verbose;
> encoder->has_index_type = false;
> encoder->need_index_type = false;
> diff --git a/dwarves.h b/dwarves.h
> index 78bedf5..d7c6474 100644
> --- a/dwarves.h
> +++ b/dwarves.h
> @@ -101,6 +101,7 @@ struct conf_load {
> bool btf_decl_tag_kfuncs;
> bool btf_gen_distilled_base;
> bool btf_attributes;
> + bool true_signature;
> uint8_t hashtable_bits;
> uint8_t max_hashtable_bits;
> uint16_t kabi_prefix_len;
> diff --git a/pahole.c b/pahole.c
> index ef01e58..02a0d19 100644
> --- a/pahole.c
> +++ b/pahole.c
> @@ -1234,6 +1234,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
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 4/4] btf_encoder: Prefer strong function definitions for BTF generation
2026-01-13 13:13 ` [PATCH dwarves 4/4] btf_encoder: Prefer strong function definitions for BTF generation Alan Maguire
@ 2026-01-20 17:54 ` Yonghong Song
0 siblings, 0 replies; 19+ messages in thread
From: Yonghong Song @ 2026-01-20 17:54 UTC (permalink / raw)
To: Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 1/13/26 5:13 AM, Alan Maguire wrote:
> From: Matt Bobrowski <mattbobrowski@google.com>
>
> Currently, when a function has both a weak and a strong definition
> across different compilation units (CUs), the BTF encoder arbitrarily
> selects one to generate the BTF entry. This selection fundamentally is
> dependent on the order in which pahole processes the CUs.
>
> This indifference often leads to a mismatch where the generated BTF
> reflects the weak definition's prototype, even though the linker
> selected the strong definition for the final vmlinux binary.
>
> A notable example described in [0] involving function
> bpf_lsm_mmap_file(). Both weak and strong definitions exist,
> distinguished only by parameter names (e.g., file vs
> file__nullable). While the strong definition is linked into the
> vmlinux object, the generated BTF contained the prototype for the weak
> definition. This causes issues for BPF verifier (e.g., __nullable
> annotation semantics), or tools relying on accurate type information.
>
> To fix this, ensure the BTF encoder selects the function definition
> corresponding to the actual code linked into the binary. This is
> achieved by comparing the DWARF function address (DW_AT_low_pc) with
> the ELF symbol address (st_value). Only the DWARF entry for the strong
> definition will match the final resolved ELF symbol address.
>
> [0] https://lore.kernel.org/all/aVJY9H-e83T7ivT4@google.com/
>
> Link: https://lore.kernel.org/all/aVJY9H-e83T7ivT4@google.com/
> Signed-off-by: Matt Bobrowski <mattbobrowski@google.com>
Acked-by: Yonghong Song <yonghong.song@linux.dev>
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-20 17:53 ` Yonghong Song
@ 2026-01-22 18:21 ` Alan Maguire
2026-01-22 18:36 ` Yonghong Song
0 siblings, 1 reply; 19+ messages in thread
From: Alan Maguire @ 2026-01-22 18:21 UTC (permalink / raw)
To: Yonghong Song, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 20/01/2026 17:53, Yonghong Song wrote:
>
>
> On 1/13/26 5:13 AM, Alan Maguire wrote:
>> Currently we collate function information by name and add functions
>> provided there are no inconsistencies across various representations.
>>
>> For true_signature support - where we wish to add the real signature
>> of a function even if it differs from source level - we need to do
>> a few things:
>>
>> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
>> we can do this via the address associated with the function.
>> In doing this, we can then be confident that the debug info
>> for foo.isra.0 is the right info for the function at that
>> address.
>>
>> 2. When adding saved functions we need to look for such cases
>> and provided they do not violate other constraints around BTF
>> representation - unexpected reg usage for function, uncertain
>> parameter location or ambiguous address - we add them with
>> their "."-suffixed name. The latter can be used as a signal
>> that the function is transformed from the original.
>>
>> Doing this adds 500 functions to BTF. These are traceable with
>> their "."-suffix names and because we have excluded ambiguous
>> address cases we know exactly which function address they refer
>> to.
>>
>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>> ---
>> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
>> dwarves.h | 1 +
>> pahole.c | 1 +
>> 3 files changed, 68 insertions(+), 7 deletions(-)
>>
>> diff --git a/btf_encoder.c b/btf_encoder.c
>> index 5bc61cb..01fd469 100644
>> --- a/btf_encoder.c
>> +++ b/btf_encoder.c
>> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
>> int16_t component_idx;
>> };
>> +struct elf_function_sym {
>> + const char *name;
>> + uint64_t addr;
>> +};
>> +
>> /* state used to do later encoding of saved functions */
>> struct btf_encoder_func_state {
>> struct elf_function *elf;
>> + struct elf_function_sym *sym;
>> + uint64_t addr;
>> uint32_t type_id_off;
>> uint16_t nr_parms;
>> uint16_t nr_annots;
>> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
>> struct btf_encoder_func_annot *annots;
>> };
>> -struct elf_function_sym {
>> - const char *name;
>> - uint64_t addr;
>> -};
>> -
>> struct elf_function {
>> char *name;
>> struct elf_function_sym *syms;
>> @@ -145,7 +147,8 @@ struct btf_encoder {
>> skip_encoding_decl_tag,
>> tag_kfuncs,
>> gen_distilled_base,
>> - encode_attributes;
>> + encode_attributes,
>> + true_signature;
>> uint32_t array_index_id;
>> struct elf_secinfo *secinfo;
>> size_t seccnt;
>> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>> goto out;
>> }
>> }
>> + if (encoder->true_signature && fn->lexblock.ip.addr) {
>> + int i;
>> +
>> + for (i = 0; i < func->sym_cnt; i++) {
>> + if (fn->lexblock.ip.addr != func->syms[i].addr)
>> + continue;
>> + /* Only need to record address for '.'-suffixed
>> + * functions, since we only currently need true
>> + * signatures for them.
>> + */
>> + if (!strchr(func->syms[i].name, '.'))
>> + continue;
>> + state->sym = &func->syms[i];
>> + break;
>> + }
>> + }
>> state->inconsistent_proto = ftype->inconsistent_proto;
>> state->unexpected_reg = ftype->unexpected_reg;
>> state->optimized_parms = ftype->optimized_parms;
>> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
>> state->reordered_parm = ftype->reordered_parm;
>> ftype__for_each_parameter(ftype, param) {
>> - const char *name = parameter__name(param) ?: "";
>> + const char *name;
>> + /* No location info + reordered means optimized out. */
>> + if (ftype->reordered_parm && !param->has_loc)
>> + continue;
>> + name = parameter__name(param) ?: "";
>> str_off = btf__add_str(btf, name);
>> if (str_off < 0) {
>> err = str_off;
>> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
>> name = func->name;
>> + if (encoder->true_signature && state->sym)
>> + name = state->sym->name;
>> +
>> if (btf_fnproto_id >= 0)
>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>> name, false);
>> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
>> j++;
>> + /* Add true signatures for case where we have an exact
>> + * symbol match by address from DWARF->ELF and have a
>> + * "." suffixed name.
>> + */
>> + if (encoder->true_signature) {
>> + int k;
>> +
>> + for (k = i; k < nr_saved_fns; k++) {
>> + struct btf_encoder_func_state *true_state = &saved_fns[k];
>> +
>> + if (state->elf != true_state->elf)
>> + break;
>> + if (!true_state->sym)
>> + continue;
>> + /* Unexpected reg, uncertain parm loc and
>> + * ambiguous address mean we cannot trust fentry.
>> + */
>> + if (true_state->unexpected_reg ||
>> + true_state->uncertain_parm_loc ||
>> + true_state->ambiguous_addr)
>> + continue;
>> + err = btf_encoder__add_func(encoder, true_state);
>> + if (err < 0)
>> + goto out;
>> + break;
>> + }
>> + }
>> +
>> + /* True symbol that was handled above; skip. */
>> + if (state->sym)
>> + continue;
>
> I did an experiment with the following code:
>
> $ cat test.c
> struct t { int a; };
> __attribute__((noinline)) char *tar(struct t *a, struct t *d) { if (a->a == d->a) return (char *)10; else return (char *)0; }
> __attribute__((noinline)) static char * foo(struct t *a, int b, struct t *d)
> {
> return tar(a, d);
> }
> __attribute__((noinline)) char *bar(struct t *a, struct t *d)
> {
> return foo(a, 1, d);
> }
>
> struct t p1, p2;
> int main() {
> return !!bar(&p1, &p2);
> }
>
> and compiled with gcc11:
> $ gcc -O2 -g test.c
>
> I hacked btf_encoder.c with true_signature is all on and with
> $ pahole -JV ./a.out
> ...
> btf_encoder__new: './a.out' doesn't have '.data..percpu' section
> File ./a.out:
> [1] STRUCT t size=4
> a type_id=2 bits_offset=0
> [2] INT int size=4 nr_bits=32 encoding=SIGNED
> [3] PTR (anon) type_id=4
> [4] INT char size=1 nr_bits=8 encoding=SIGNED
> [5] PTR (anon) type_id=1
> search cu 'test.c' for percpu global variables.
> [6] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
> [7] FUNC bar type_id=6
> [8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d, vararg)
> [9] FUNC foo.constprop.0 type_id=8
> foo : skipping BTF encoding of function due to reordered parameters
> [10] FUNC_PROTO (anon) return=2 args=(void)
> [11] FUNC main type_id=10
> [12] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
> [13] FUNC tar type_id=12
>
> There are two issues.
>
> First in btf_encoder__add_saved_funcs(), it is possible below
> + /* True symbol that was handled above; skip. */
> + if (state->sym)
> + continue;
> state->sym is false.
> But one of true_state->sym in the above loop could be true.
> So if btf_encoder__add_func(encoder, true_state) is successful,
> we should continue in the above regardless state->sym null or not.
> This will remove the warning:
> foo : skipping BTF encoding of function due to reordered parameters
>
thanks, will fix this.
> Second, we have foo.constprop.0 func proto encoding:
> [8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d, vararg)
> The last argument 'vararg' should not be there since the
> optimized out argument is already gone.
>
The problem here was both
1. we weren't dealing with const values; and
2. when a parameter was marked optimized we weren't reducing the number
of params, so ended up with an extra 0-type param on the end,
resembling a vararg.
Fixing both of these we get:
$ pahole -J --verbose --btf_features=+true_signature a.out
btf_encoder__new: 'a.out' doesn't have '.data..percpu' section
File a.out:
[1] STRUCT t size=4
a type_id=2 bits_offset=0
[2] INT int size=4 nr_bits=32 encoding=SIGNED
[3] PTR (anon) type_id=4
[4] INT char size=1 nr_bits=8 encoding=SIGNED
[5] PTR (anon) type_id=1
search cu 'true_test.c' for percpu global variables.
[6] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
[7] FUNC bar type_id=6
[8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
[9] FUNC foo.constprop.0 type_id=8
[10] FUNC_PROTO (anon) return=2 args=(void)
[11] FUNC main type_id=10
[12] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
[13] FUNC tar type_id=12
...which I _think_ is right.
I'll retest and respin; was wondering if it'd be okay to incorporate
the above into a selftest, as it's really useful? Thanks!
Alan
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions
2026-01-22 18:21 ` Alan Maguire
@ 2026-01-22 18:36 ` Yonghong Song
0 siblings, 0 replies; 19+ messages in thread
From: Yonghong Song @ 2026-01-22 18:36 UTC (permalink / raw)
To: Alan Maguire, mattbobrowski
Cc: eddyz87, ihor.solodrai, jolsa, andrii, ast, dwarves, bpf
On 1/22/26 10:21 AM, Alan Maguire wrote:
> On 20/01/2026 17:53, Yonghong Song wrote:
>>
>> On 1/13/26 5:13 AM, Alan Maguire wrote:
>>> Currently we collate function information by name and add functions
>>> provided there are no inconsistencies across various representations.
>>>
>>> For true_signature support - where we wish to add the real signature
>>> of a function even if it differs from source level - we need to do
>>> a few things:
>>>
>>> 1. For "."-suffixed functions, we need to match from DWARF->ELF;
>>> we can do this via the address associated with the function.
>>> In doing this, we can then be confident that the debug info
>>> for foo.isra.0 is the right info for the function at that
>>> address.
>>>
>>> 2. When adding saved functions we need to look for such cases
>>> and provided they do not violate other constraints around BTF
>>> representation - unexpected reg usage for function, uncertain
>>> parameter location or ambiguous address - we add them with
>>> their "."-suffixed name. The latter can be used as a signal
>>> that the function is transformed from the original.
>>>
>>> Doing this adds 500 functions to BTF. These are traceable with
>>> their "."-suffix names and because we have excluded ambiguous
>>> address cases we know exactly which function address they refer
>>> to.
>>>
>>> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
>>> ---
>>> btf_encoder.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-----
>>> dwarves.h | 1 +
>>> pahole.c | 1 +
>>> 3 files changed, 68 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/btf_encoder.c b/btf_encoder.c
>>> index 5bc61cb..01fd469 100644
>>> --- a/btf_encoder.c
>>> +++ b/btf_encoder.c
>>> @@ -77,9 +77,16 @@ struct btf_encoder_func_annot {
>>> int16_t component_idx;
>>> };
>>> +struct elf_function_sym {
>>> + const char *name;
>>> + uint64_t addr;
>>> +};
>>> +
>>> /* state used to do later encoding of saved functions */
>>> struct btf_encoder_func_state {
>>> struct elf_function *elf;
>>> + struct elf_function_sym *sym;
>>> + uint64_t addr;
>>> uint32_t type_id_off;
>>> uint16_t nr_parms;
>>> uint16_t nr_annots;
>>> @@ -94,11 +101,6 @@ struct btf_encoder_func_state {
>>> struct btf_encoder_func_annot *annots;
>>> };
>>> -struct elf_function_sym {
>>> - const char *name;
>>> - uint64_t addr;
>>> -};
>>> -
>>> struct elf_function {
>>> char *name;
>>> struct elf_function_sym *syms;
>>> @@ -145,7 +147,8 @@ struct btf_encoder {
>>> skip_encoding_decl_tag,
>>> tag_kfuncs,
>>> gen_distilled_base,
>>> - encode_attributes;
>>> + encode_attributes,
>>> + true_signature;
>>> uint32_t array_index_id;
>>> struct elf_secinfo *secinfo;
>>> size_t seccnt;
>>> @@ -1271,14 +1274,34 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>>> goto out;
>>> }
>>> }
>>> + if (encoder->true_signature && fn->lexblock.ip.addr) {
>>> + int i;
>>> +
>>> + for (i = 0; i < func->sym_cnt; i++) {
>>> + if (fn->lexblock.ip.addr != func->syms[i].addr)
>>> + continue;
>>> + /* Only need to record address for '.'-suffixed
>>> + * functions, since we only currently need true
>>> + * signatures for them.
>>> + */
>>> + if (!strchr(func->syms[i].name, '.'))
>>> + continue;
>>> + state->sym = &func->syms[i];
>>> + break;
>>> + }
>>> + }
>>> state->inconsistent_proto = ftype->inconsistent_proto;
>>> state->unexpected_reg = ftype->unexpected_reg;
>>> state->optimized_parms = ftype->optimized_parms;
>>> state->uncertain_parm_loc = ftype->uncertain_parm_loc;
>>> state->reordered_parm = ftype->reordered_parm;
>>> ftype__for_each_parameter(ftype, param) {
>>> - const char *name = parameter__name(param) ?: "";
>>> + const char *name;
>>> + /* No location info + reordered means optimized out. */
>>> + if (ftype->reordered_parm && !param->has_loc)
>>> + continue;
>>> + name = parameter__name(param) ?: "";
>>> str_off = btf__add_str(btf, name);
>>> if (str_off < 0) {
>>> err = str_off;
>>> @@ -1367,6 +1390,9 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>>> btf_fnproto_id = btf_encoder__add_func_proto_for_state(encoder, state);
>>> name = func->name;
>>> + if (encoder->true_signature && state->sym)
>>> + name = state->sym->name;
>>> +
>>> if (btf_fnproto_id >= 0)
>>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>>> name, false);
>>> @@ -1509,6 +1535,38 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>>> while (j < nr_saved_fns && saved_functions_combine(encoder, &saved_fns[i], &saved_fns[j]) == 0)
>>> j++;
>>> + /* Add true signatures for case where we have an exact
>>> + * symbol match by address from DWARF->ELF and have a
>>> + * "." suffixed name.
>>> + */
>>> + if (encoder->true_signature) {
>>> + int k;
>>> +
>>> + for (k = i; k < nr_saved_fns; k++) {
>>> + struct btf_encoder_func_state *true_state = &saved_fns[k];
>>> +
>>> + if (state->elf != true_state->elf)
>>> + break;
>>> + if (!true_state->sym)
>>> + continue;
>>> + /* Unexpected reg, uncertain parm loc and
>>> + * ambiguous address mean we cannot trust fentry.
>>> + */
>>> + if (true_state->unexpected_reg ||
>>> + true_state->uncertain_parm_loc ||
>>> + true_state->ambiguous_addr)
>>> + continue;
>>> + err = btf_encoder__add_func(encoder, true_state);
>>> + if (err < 0)
>>> + goto out;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + /* True symbol that was handled above; skip. */
>>> + if (state->sym)
>>> + continue;
>> I did an experiment with the following code:
>>
>> $ cat test.c
>> struct t { int a; };
>> __attribute__((noinline)) char *tar(struct t *a, struct t *d) { if (a->a == d->a) return (char *)10; else return (char *)0; }
>> __attribute__((noinline)) static char * foo(struct t *a, int b, struct t *d)
>> {
>> return tar(a, d);
>> }
>> __attribute__((noinline)) char *bar(struct t *a, struct t *d)
>> {
>> return foo(a, 1, d);
>> }
>>
>> struct t p1, p2;
>> int main() {
>> return !!bar(&p1, &p2);
>> }
>>
>> and compiled with gcc11:
>> $ gcc -O2 -g test.c
>>
>> I hacked btf_encoder.c with true_signature is all on and with
>> $ pahole -JV ./a.out
>> ...
>> btf_encoder__new: './a.out' doesn't have '.data..percpu' section
>> File ./a.out:
>> [1] STRUCT t size=4
>> a type_id=2 bits_offset=0
>> [2] INT int size=4 nr_bits=32 encoding=SIGNED
>> [3] PTR (anon) type_id=4
>> [4] INT char size=1 nr_bits=8 encoding=SIGNED
>> [5] PTR (anon) type_id=1
>> search cu 'test.c' for percpu global variables.
>> [6] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
>> [7] FUNC bar type_id=6
>> [8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d, vararg)
>> [9] FUNC foo.constprop.0 type_id=8
>> foo : skipping BTF encoding of function due to reordered parameters
>> [10] FUNC_PROTO (anon) return=2 args=(void)
>> [11] FUNC main type_id=10
>> [12] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
>> [13] FUNC tar type_id=12
>>
>> There are two issues.
>>
>> First in btf_encoder__add_saved_funcs(), it is possible below
>> + /* True symbol that was handled above; skip. */
>> + if (state->sym)
>> + continue;
>> state->sym is false.
>> But one of true_state->sym in the above loop could be true.
>> So if btf_encoder__add_func(encoder, true_state) is successful,
>> we should continue in the above regardless state->sym null or not.
>> This will remove the warning:
>> foo : skipping BTF encoding of function due to reordered parameters
>>
> thanks, will fix this.
>
>> Second, we have foo.constprop.0 func proto encoding:
>> [8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d, vararg)
>> The last argument 'vararg' should not be there since the
>> optimized out argument is already gone.
>>
> The problem here was both
>
> 1. we weren't dealing with const values; and
> 2. when a parameter was marked optimized we weren't reducing the number
> of params, so ended up with an extra 0-type param on the end,
> resembling a vararg.
>
> Fixing both of these we get:
>
> $ pahole -J --verbose --btf_features=+true_signature a.out
> btf_encoder__new: 'a.out' doesn't have '.data..percpu' section
> File a.out:
> [1] STRUCT t size=4
> a type_id=2 bits_offset=0
> [2] INT int size=4 nr_bits=32 encoding=SIGNED
> [3] PTR (anon) type_id=4
> [4] INT char size=1 nr_bits=8 encoding=SIGNED
> [5] PTR (anon) type_id=1
> search cu 'true_test.c' for percpu global variables.
> [6] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
> [7] FUNC bar type_id=6
> [8] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
> [9] FUNC foo.constprop.0 type_id=8
> [10] FUNC_PROTO (anon) return=2 args=(void)
> [11] FUNC main type_id=10
> [12] FUNC_PROTO (anon) return=3 args=(5 a, 5 d)
> [13] FUNC tar type_id=12
>
> ...which I _think_ is right.
>
> I'll retest and respin; was wondering if it'd be okay to incorporate
> the above into a selftest, as it's really useful? Thanks!
Looks like current pahole tests (at pahole/tests directory) are all
simple and using python. But true signature might be worth to have
some tests at pahole as it is unlikely that kernel/bpf will validate it.
But having these selftests can be a separate patch.
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-01-22 18:37 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-13 13:13 [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
2026-01-13 13:13 ` [PATCH dwarves 1/4] dwarf_loader/btf_encoder: Detect reordered parameters Alan Maguire
2026-01-20 16:07 ` Yonghong Song
2026-01-13 13:13 ` [PATCH dwarves 2/4] btf_encoder: Refactor elf_functions__new() with struct btf_encoder as argument Alan Maguire
2026-01-13 18:32 ` Ihor Solodrai
2026-01-13 18:57 ` Yonghong Song
2026-01-13 20:59 ` Alan Maguire
2026-01-13 13:13 ` [PATCH dwarves 3/4] btf_encoder: Add true_signature feature support for "."-suffixed functions Alan Maguire
2026-01-14 16:15 ` Yonghong Song
2026-01-14 16:55 ` Alan Maguire
2026-01-14 18:22 ` David Faust
2026-01-15 3:27 ` Yonghong Song
2026-01-15 18:38 ` Yonghong Song
2026-01-20 17:53 ` Yonghong Song
2026-01-22 18:21 ` Alan Maguire
2026-01-22 18:36 ` Yonghong Song
2026-01-13 13:13 ` [PATCH dwarves 4/4] btf_encoder: Prefer strong function definitions for BTF generation Alan Maguire
2026-01-20 17:54 ` Yonghong Song
2026-01-20 9:52 ` [PATCH dwarves 0/4] Improve BTF concrete function accuracy Alan Maguire
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox