* [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF
@ 2026-03-05 22:54 Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr Yonghong Song
` (8 more replies)
0 siblings, 9 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:54 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
Current vmlinux BTF encoding is based on the source level signatures.
But the compiler may do some optimization and changed the signature.
If the user tried with source level signature, their initial implementation
may have wrong results and then the user need to check what is the
problem and work around it, e.g. through kprobe since kprobe does not
need vmlinux BTF.
Majority of changed signatures are due to dead argument elimination.
The following is a more complex one. The original source signature:
typedef struct {
union {
void *kernel;
void __user *user;
};
bool is_kernel : 1;
} sockptr_t;
typedef sockptr_t bpfptr_t;
static int map_create(union bpf_attr *attr, bpfptr_t uattr) { ... }
After compiler optimization, the signature becomes:
static int map_create(union bpf_attr *attr, bool uattr__is_kernel) { ... }
In the above, uattr__is_kernel corresponds to 'is_kernel' field in sockptr_t.
This makes it easier for developers to understand what changed.
The new signature needs to properly follow ABI specification based on
locations. Otherwise, that signature should be discarded. For example,
0x0242f1f7: DW_TAG_subprogram
DW_AT_name ("memblock_find_in_range")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f22e: DW_TAG_formal_parameter
DW_AT_location (indexed (0x14a) loclist = 0x005595bc:
[0xffffffff87a000f9, 0xffffffff87a00178): DW_OP_reg5 RDI
[0xffffffff87a00178, 0xffffffff87a001be): DW_OP_reg14 R14
[0xffffffff87a001be, 0xffffffff87a001c7): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff87a001c7, 0xffffffff87a00214): DW_OP_reg14 R14)
DW_AT_name ("start")
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f239: DW_TAG_formal_parameter
DW_AT_location (indexed (0x14b) loclist = 0x005595e6:
[0xffffffff87a000f9, 0xffffffff87a00175): DW_OP_reg4 RSI
[0xffffffff87a00175, 0xffffffff87a001b8): DW_OP_reg3 RBX
[0xffffffff87a001b8, 0xffffffff87a001c7): DW_OP_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value
[0xffffffff87a001c7, 0xffffffff87a00214): DW_OP_reg3 RBX)
DW_AT_name ("end")
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f245: DW_TAG_formal_parameter
DW_AT_location (indexed (0x14c) loclist = 0x00559610:
[0xffffffff87a001e3, 0xffffffff87a001ef): DW_OP_breg4 RSI+0)
DW_AT_name ("size")
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f250: DW_TAG_formal_parameter
DW_AT_const_value (4096)
DW_AT_name ("align")
DW_AT_type (0x0242decc "phys_addr_t")
...
The third argument should correspond to RDX for x86_64. But the location suggests that
the parameter value is stored in the address with 'RSI + 0'. It is not clear whether
the parameter value is stored in RDEX or not. So we have to discard this funciton in
vmlinux BTF to avoid incorrect true signatures.
For llvm, any function having
DW_AT_calling_convention (DW_CC_nocall)
in dwarf DW_TAG_subprogram will indicate that this function has signature changed.
I did experiment with latest bpf-next. There are 69103 kernel functions and 875
kernel functions have signature changed. A series of patches are intended to
ensure true signatures are properly represented. Eventually, only 17 functions
cannot have true signatures due to locations.
For the patch set, Patch 1 introduced usage of DW_AT_calling_convention, which
can precisely identify which function has signature changed. This can filter
majority of functions where their signature won't change.
Patches 2 to 7 tried to find functions with true signature.
Patch 8 enables to btf encoder to properly generate BTF.
Patch 9 includes a few tests.
Yonghong Song (9):
dwarf_loader: Reduce parameter checking with clang
DW_AT_calling_convention attr
dwarf_loader: Handle signatures with dead arguments
dwarf_loader: Refactor initial ret -1 to be macro PARM_DEFAULT_FAIL
dwarf_laoder: Handle locations with DW_OP_fbreg
dwarf_loader: Change exprlen checking condition in parameter__reg()
dwarf_loader: Detect optimized parameters with locations having
constant values
dwarf_loader: Handle expression lists
btf_encoder: Handle optimized parameter properly
tests: Add a few clang true signature tests
btf_encoder.c | 11 +-
dwarf_loader.c | 375 +++++++++++++++++-
dwarves.h | 3 +
tests/true_signatures/clang_parm_aggregate.sh | 83 ++++
tests/true_signatures/clang_parm_optimized.sh | 95 +++++
.../clang_parm_optimized_stack.sh | 95 +++++
.../gcc_true_signatures.sh | 0
7 files changed, 638 insertions(+), 24 deletions(-)
create mode 100755 tests/true_signatures/clang_parm_aggregate.sh
create mode 100755 tests/true_signatures/clang_parm_optimized.sh
create mode 100755 tests/true_signatures/clang_parm_optimized_stack.sh
rename tests/{ => true_signatures}/gcc_true_signatures.sh (100%)
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-19 12:32 ` Jiri Olsa
2026-03-05 22:55 ` [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments Yonghong Song
` (7 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
Currently every function is checked for its parameters to identify whether
the signature changed or not. If signature indeed changed, pahole may do
some adjustment for parameters for true signatures.
In clang, any function with the following attribute
DW_AT_calling_convention (DW_CC_nocall)
indicates this function having signature changed.
pahole can take advantage of this to avoid parameter checking if
DW_AT_calling_convention is not DW_CC_nocall.
But more importantly, DW_CC_nocall can identify signature-changed functions
and parameters can be checked one-after-another to create the true
signatures. Otherwise, it takes more effort to identify whether a
function has signature changed or not. For example, for funciton
__bpf_kfunc static void bbr_main(struct sock *sk, u32 ack, int flag,
const struct rate_sample *rs) { ... }
and bbr_main() is a callback function in
.cong_control = bbr_main
in 'struct tcp_congestion_ops tcp_bbr_cong_ops'.
In the above bbr_main(...), parameter 'ack' and 'flag' are not used.
The following are some details:
0x0a713b8d: DW_TAG_formal_parameter
DW_AT_location (indexed (0x28) loclist = 0x0166d452:
[0xffffffff83e77fd9, 0xffffffff83e78016): DW_OP_reg5 RDI
...
DW_AT_name ("sk")
DW_AT_type (0x0a6f5b2b "sock *")
...
0x0a713b98: DW_TAG_formal_parameter
DW_AT_name ("ack")
DW_AT_type (0x0a6f58fd "u32")
...
0x0a713ba2: DW_TAG_formal_parameter
DW_AT_name ("flag")
DW_AT_type (0x0a6f57d1 "int")
...
0x0a713bac: DW_TAG_formal_parameter
DW_AT_location (indexed (0x29) loclist = 0x0166d4a8:
[0xffffffff83e77fd9, 0xffffffff83e78016): DW_OP_reg2 RCX
...
DW_AT_name ("rs")
DW_AT_type (0x0a710da5 "const rate_sample *")
DW_AT_decl_line (1027)
Some analysis for the above dwarf can conclude that the 'ark' and 'flag'
may be related to RSI and RDX, considering the last one is RCX. Basically this
requires all parameters are available to collectively decide whether the
true signature can be found or not. In such case, DW_CC_nocall can make things
easier as parameter can be checked one after another.
For a clang built bpf-next kernel, in non-LTO setup, the number of kernel functions
is 69103 and the number of signature changed functions is 875, based on
DW_AT_calling_convention (DW_CC_nocall)
indication.
Among 875 signature changed functions, after this patch, 495 functions
can have proper true signatures, mostly due to simple dead argument
elimination. The number of remaining functions, which cannot get the
true signature, is 379. They will be addressed in the subsequent commits.
In llvm23, I implemented [1] which added DW_CC_nocall for ArgumentPromotion pass.
This compiler pass can add additional DW_CC_nocall cases for the following
compilation:
- Flag -O3 or FullLTO
So once llvm23 available, we may have more DW_CC_nocall cases, hence more
potential true signatures if the kernel is built with -O3 or
with FullLTO (CONFIG_LTO_CLANG_FULL).
[1] https://github.com/llvm/llvm-project/pull/178973
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 71 +++++++++++++++++++++++++++++++++++++++++++-------
dwarves.h | 2 ++
2 files changed, 63 insertions(+), 10 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 16fb7be..610b69e 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1190,6 +1190,10 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
return ret;
}
+struct func_info {
+ bool signature_changed;
+};
+
/* For DW_AT_location 'attr':
* - if first location is DW_OP_regXX with expected number, return the register;
* otherwise save the register for later return
@@ -1252,7 +1256,8 @@ out:
}
static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
- struct conf_load *conf, int param_idx)
+ struct conf_load *conf, int param_idx,
+ struct func_info *info)
{
struct parameter *parm = tag__alloc(cu, sizeof(*parm));
@@ -1265,6 +1270,8 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
parm->idx = param_idx;
if (param_idx >= cu->nr_register_params || param_idx < 0)
return parm;
+ if (cu->producer_clang && !info->signature_changed)
+ return parm;
/* Parameters which use DW_AT_abstract_origin to point at
* the original parameter definition (with no name in the DIE)
* are the result of later DWARF generation during compilation
@@ -1337,7 +1344,7 @@ static int formal_parameter_pack__load_params(struct formal_parameter_pack *pack
continue;
}
- struct parameter *param = parameter__new(die, cu, conf, -1);
+ struct parameter *param = parameter__new(die, cu, conf, -1, NULL);
if (param == NULL)
return -1;
@@ -1502,6 +1509,29 @@ static struct ftype *ftype__new(Dwarf_Die *die, struct cu *cu)
return ftype;
}
+static bool function__signature_changed(struct function *func, Dwarf_Die *die)
+{
+ /* The inlined DW_TAG_subprogram typically has the original source type for
+ * abstract origin of a concrete function with address range, inlined subroutine,
+ * or call site.
+ */
+ if (func->inlined)
+ return false;
+
+ if (!func->abstract_origin)
+ return attr_numeric(die, DW_AT_calling_convention) == DW_CC_nocall;
+
+ Dwarf_Attribute attr;
+ if (dwarf_attr(die, DW_AT_abstract_origin, &attr)) {
+ Dwarf_Die origin;
+ if (dwarf_formref_die(&attr, &origin))
+ return attr_numeric(&origin, DW_AT_calling_convention) == DW_CC_nocall;
+ }
+
+ /* This should not happen */
+ return false;
+}
+
static struct function *function__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
{
struct function *func = tag__alloc(cu, sizeof(*func));
@@ -1527,6 +1557,7 @@ static struct function *function__new(Dwarf_Die *die, struct cu *cu, struct conf
func->cu_total_size_inline_expansions = 0;
func->cu_total_nr_inline_expansions = 0;
func->priv = NULL;
+ func->signature_changed = function__signature_changed(func, die);
}
return func;
@@ -1800,9 +1831,9 @@ static struct tag *die__create_new_parameter(Dwarf_Die *die,
struct ftype *ftype,
struct lexblock *lexblock,
struct cu *cu, struct conf_load *conf,
- int param_idx)
+ int param_idx, struct func_info *info)
{
- struct parameter *parm = parameter__new(die, cu, conf, param_idx);
+ struct parameter *parm = parameter__new(die, cu, conf, param_idx, info);
if (parm == NULL)
return NULL;
@@ -1889,7 +1920,7 @@ static struct tag *die__create_new_subroutine_type(Dwarf_Die *die,
tag__print_not_supported(die);
continue;
case DW_TAG_formal_parameter:
- tag = die__create_new_parameter(die, ftype, NULL, cu, conf, -1);
+ tag = die__create_new_parameter(die, ftype, NULL, cu, conf, -1, NULL);
break;
case DW_TAG_unspecified_parameters:
ftype->unspec_parms = 1;
@@ -2118,7 +2149,8 @@ out_enomem:
}
static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
- struct lexblock *lexblock, struct cu *cu, struct conf_load *conf);
+ struct lexblock *lexblock, struct cu *cu, struct conf_load *conf,
+ struct func_info *info);
static int die__create_new_lexblock(Dwarf_Die *die,
struct cu *cu, struct lexblock *father, struct conf_load *conf)
@@ -2126,7 +2158,7 @@ static int die__create_new_lexblock(Dwarf_Die *die,
struct lexblock *lexblock = lexblock__new(die, cu);
if (lexblock != NULL) {
- if (die__process_function(die, NULL, lexblock, cu, conf) != 0)
+ if (die__process_function(die, NULL, lexblock, cu, conf, NULL) != 0)
goto out_delete;
}
if (father != NULL)
@@ -2246,7 +2278,8 @@ static struct tag *die__create_new_inline_expansion(Dwarf_Die *die,
}
static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
- struct lexblock *lexblock, struct cu *cu, struct conf_load *conf)
+ struct lexblock *lexblock, struct cu *cu, struct conf_load *conf,
+ struct func_info *info)
{
int param_idx = 0;
Dwarf_Die child;
@@ -2320,7 +2353,7 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
continue;
}
case DW_TAG_formal_parameter:
- tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++);
+ tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++, info);
break;
case DW_TAG_variable:
tag = die__create_new_variable(die, cu, conf, 0);
@@ -2391,9 +2424,10 @@ out_enomem:
static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
{
struct function *function = function__new(die, cu, conf);
+ struct func_info info = { function->signature_changed };
if (function != NULL &&
- die__process_function(die, &function->proto, &function->lexblock, cu, conf) != 0) {
+ die__process_function(die, &function->proto, &function->lexblock, cu, conf, &info) != 0) {
function__delete(function, cu);
function = NULL;
}
@@ -3045,6 +3079,21 @@ static unsigned long long dwarf_tag__orig_id(const struct tag *tag,
return cu->extra_dbg_info ? dtag->id : 0;
}
+static bool attr_producer_clang(Dwarf_Die *die)
+{
+ Dwarf_Attribute attr;
+ const char *producer;
+
+ if (dwarf_attr(die, DW_AT_producer, &attr) == NULL)
+ return false;
+
+ producer = dwarf_formstring(&attr);
+ if (!producer)
+ return false;
+
+ return !!strstr(producer, "clang");
+}
+
struct debug_fmt_ops dwarf__ops;
static int die__process(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
@@ -3082,6 +3131,7 @@ static int die__process(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
}
cu->language = attr_numeric(die, DW_AT_language);
+ cu->producer_clang = attr_producer_clang(die);
if (conf->early_cu_filter)
cu = conf->early_cu_filter(cu);
@@ -3841,6 +3891,7 @@ static int cus__merge_and_process_cu(struct cus *cus, struct conf_load *conf,
cu->priv = dcu;
cu->dfops = &dwarf__ops;
cu->language = attr_numeric(cu_die, DW_AT_language);
+ cu->producer_clang = attr_producer_clang(cu_die);
cus__add(cus, cu);
}
diff --git a/dwarves.h b/dwarves.h
index d7c6474..ad33828 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -302,6 +302,7 @@ struct cu {
uint8_t has_addr_info:1;
uint8_t uses_global_strings:1;
uint8_t little_endian:1;
+ uint8_t producer_clang:1;
uint8_t nr_register_params;
int register_params[ARCH_MAX_REGISTER_PARAMS];
int functions_saved;
@@ -1095,6 +1096,7 @@ struct function {
uint8_t virtuality:2; /* DW_VIRTUALITY_{none,virtual,pure_virtual} */
uint8_t declaration:1;
uint8_t btf:1;
+ uint8_t signature_changed:1;
int32_t vtable_entry;
struct list_head vtable_node;
struct list_head annots;
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-19 18:55 ` Alan Maguire
2026-03-05 22:55 ` [PATCH dwarves 3/9] dwarf_loader: Refactor initial ret -1 to be macro PARM_DEFAULT_FAIL Yonghong Song
` (6 subsequent siblings)
8 siblings, 1 reply; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
For llvm dwarf, the dead argument may be in the middle of
DW_TAG_subprogram. So we introduce skip_idx in order to
match expected registers properly.
For example:
0x00042897: DW_TAG_subprogram
DW_AT_name ("create_dev")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x0002429a "int")
...
0x000428ab: DW_TAG_formal_parameter
DW_AT_name ("name")
DW_AT_type (0x000242ed "char *")
...
0x000428b5: DW_TAG_formal_parameter
DW_AT_location (indexed (0x3f) loclist = 0x000027f8:
[0xffffffff87681370, 0xffffffff8768137a): DW_OP_reg5 RDI
[0xffffffff8768137a, 0xffffffff87681392): DW_OP_reg3 RBX
[0xffffffff87681392, 0xffffffff876813ae): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
DW_AT_name ("dev")
DW_AT_type (0x00026859 "dev_t")
...
With skip_idx, we can identify that the second original argument
'dev' becomes the first one after optimization.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 27 +++++++++++++++++++++++----
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 610b69e..1ced5e2 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1192,6 +1192,7 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
struct func_info {
bool signature_changed;
+ int skip_idx;
};
/* For DW_AT_location 'attr':
@@ -1264,13 +1265,28 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
if (parm != NULL) {
bool has_const_value;
Dwarf_Attribute attr;
+ int reg_idx;
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)
+ if (param_idx < 0)
return parm;
- if (cu->producer_clang && !info->signature_changed)
+ if (!cu->producer_clang && param_idx >= cu->nr_register_params)
+ return parm;
+ if (cu->producer_clang) {
+ if (!info->signature_changed)
+ return parm;
+ /* if true_signature is not enabled, mark parameter as
+ * unexpected_reg since there is a skipped parameter before.
+ */
+ if (!conf->true_signature && info->skip_idx) {
+ parm->unexpected_reg = 1;
+ return parm;
+ }
+ }
+ reg_idx = param_idx - info->skip_idx;
+ if (reg_idx >= cu->nr_register_params)
return parm;
/* Parameters which use DW_AT_abstract_origin to point at
* the original parameter definition (with no name in the DIE)
@@ -1309,7 +1325,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) != NULL;
if (parm->has_loc) {
- int expected_reg = cu->register_params[param_idx];
+ int expected_reg = cu->register_params[reg_idx];
int actual_reg = parameter__reg(&attr, expected_reg);
if (actual_reg < 0)
@@ -1322,8 +1338,11 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
* contents.
*/
parm->unexpected_reg = 1;
- } else if (has_const_value) {
+ } else if (!cu->producer_clang && has_const_value) {
+ parm->optimized = 1;
+ } else if (cu->producer_clang) {
parm->optimized = 1;
+ info->skip_idx++;
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 3/9] dwarf_loader: Refactor initial ret -1 to be macro PARM_DEFAULT_FAIL
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 4/9] dwarf_laoder: Handle locations with DW_OP_fbreg Yonghong Song
` (5 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
Later on, More macro return values will be implemented to make
code easier to understand.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 1ced5e2..7da3926 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1195,12 +1195,14 @@ struct func_info {
int skip_idx;
};
+#define PARM_DEFAULT_FAIL -1
+
/* For DW_AT_location 'attr':
* - if first location is DW_OP_regXX with expected number, return the register;
* otherwise save the register for later return
* - if location DW_OP_entry_value(DW_OP_regXX) with expected number is in the
* list, return the register; otherwise save register for later return
- * - otherwise if no register was found for locations, return -1.
+ * - otherwise if no register was found for locations, return PARM_DEFAULT_FAIL.
*/
static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
{
@@ -1210,7 +1212,7 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
size_t exprlen, entry_len;
ptrdiff_t offset = 0;
int loc_num = -1;
- int ret = -1;
+ int ret = PARM_DEFAULT_FAIL;
/* use libdw__lock as dwarf_getlocation(s) has concurrency issues
* when libdw is not compiled with experimental --enable-thread-safety
@@ -1328,7 +1330,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
int expected_reg = cu->register_params[reg_idx];
int actual_reg = parameter__reg(&attr, expected_reg);
- if (actual_reg < 0)
+ if (actual_reg == PARM_DEFAULT_FAIL)
parm->optimized = 1;
else if (expected_reg >= 0 && expected_reg != actual_reg)
/* mark parameters that use an unexpected
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 4/9] dwarf_laoder: Handle locations with DW_OP_fbreg
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
` (2 preceding siblings ...)
2026-03-05 22:55 ` [PATCH dwarves 3/9] dwarf_loader: Refactor initial ret -1 to be macro PARM_DEFAULT_FAIL Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 5/9] dwarf_loader: Change exprlen checking condition in parameter__reg() Yonghong Song
` (4 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
DW_OP_fbreg means the parameter value will be stored on the
stack. So the corresponding parameter register is not used.
For example:
0x071f7717: DW_TAG_subprogram
DW_AT_name ("jent_health_failure")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x071f7626 "unsigned int")
...
0x071f7728: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg -8)
DW_AT_name ("ec")
DW_AT_type (0x071f7ab6 "rand_data *")
...
0x071f7734: NULL
In the above, the parameter 'ec' type is a pointer so it perfectly fits
into a register. But the location uses 'DW_OP_fbreg -8' which prevents
from generating a function with true signatures.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 7da3926..3d9b626 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1196,6 +1196,7 @@ struct func_info {
};
#define PARM_DEFAULT_FAIL -1
+#define PARM_FBREG_FAIL -2
/* For DW_AT_location 'attr':
* - if first location is DW_OP_regXX with expected number, return the register;
@@ -1240,6 +1241,15 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
if (ret == expected_reg)
goto out;
break;
+ case DW_OP_fbreg:
+ /* The locaiton like
+ * DW_AT_location (DW_OP_fbreg +<num>)
+ * indicates that the parameter is on the stack. But it is possible
+ * that the parameter can fit in register(s). So conservatively
+ * mark this parameter not suitable for true signatures.
+ */
+ ret = PARM_FBREG_FAIL;
+ break;
/* match DW_OP_entry_value(DW_OP_regXX) at any location */
case DW_OP_entry_value:
case DW_OP_GNU_entry_value:
@@ -1332,7 +1342,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
if (actual_reg == PARM_DEFAULT_FAIL)
parm->optimized = 1;
- else if (expected_reg >= 0 && expected_reg != actual_reg)
+ else if (actual_reg == PARM_FBREG_FAIL || (expected_reg >= 0 && expected_reg != actual_reg))
/* mark parameters that use an unexpected
* register to hold a parameter; these will
* be problematic for users of BTF as they
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 5/9] dwarf_loader: Change exprlen checking condition in parameter__reg()
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
` (3 preceding siblings ...)
2026-03-05 22:55 ` [PATCH dwarves 4/9] dwarf_laoder: Handle locations with DW_OP_fbreg Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 6/9] dwarf_loader: Detect optimized parameters with locations having constant values Yonghong Song
` (3 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
The change does not change any functionalities. But it allows
DW_OP_stack_value preserved in longer location list for future
parameter checking.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 3d9b626..684115d 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1226,7 +1226,7 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
* DW_OP_stack_value instructs interpreter to pop current value from
* DWARF expression evaluation stack, and thus is not important here.
*/
- if (exprlen > 1 && expr[exprlen - 1].atom == DW_OP_stack_value)
+ if (exprlen == 2 && expr[exprlen - 1].atom == DW_OP_stack_value)
exprlen--;
if (exprlen != 1)
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 6/9] dwarf_loader: Detect optimized parameters with locations having constant values
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
` (4 preceding siblings ...)
2026-03-05 22:55 ` [PATCH dwarves 5/9] dwarf_loader: Change exprlen checking condition in parameter__reg() Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 7/9] dwarf_loader: Handle expression lists Yonghong Song
` (2 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
The following is an example:
0x00899a78: DW_TAG_subprogram
DW_AT_low_pc (0xffffffff879be480)
DW_AT_high_pc (0xffffffff879beac4)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_call_all_calls (true)
DW_AT_name ("split_mem_range")
DW_AT_decl_file ("/home/yhs/work/bpf-next/arch/x86/mm/init.c")
DW_AT_decl_line (401)
DW_AT_prototyped (true)
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x008861cb "int")
0x00899a8c: DW_TAG_formal_parameter
DW_AT_location (indexed (0x3e) loclist = 0x000b2195:
[0xffffffff879be485, 0xffffffff879be49a): DW_OP_reg5 RDI
[0xffffffff879be49a, 0xffffffff879beac4): DW_OP_breg7 RSP+0)
DW_AT_name ("mr")
DW_AT_decl_file ("/home/yhs/work/bpf-next/arch/x86/mm/init.c")
DW_AT_decl_line (401)
DW_AT_type (0x00899c88 "map_range *")
0x00899a98: DW_TAG_formal_parameter
DW_AT_location (indexed (0x41) loclist = 0x000b21d4:
[0xffffffff879be480, 0xffffffff879be554): DW_OP_consts +0, DW_OP_stack_value
[0xffffffff879be554, 0xffffffff879be56d): DW_OP_consts +1, DW_OP_stack_value
[0xffffffff879be56d, 0xffffffff879be572): DW_OP_reg2 RCX
[0xffffffff879be572, 0xffffffff879be638): DW_OP_breg7 RSP+8
[0xffffffff879be638, 0xffffffff879be63d): DW_OP_reg0 RAX
[0xffffffff879be63d, 0xffffffff879be6ef): DW_OP_breg7 RSP+8
[0xffffffff879be6ef, 0xffffffff879be6f5): DW_OP_reg14 R14
[0xffffffff879be6f5, 0xffffffff879be6fd): DW_OP_breg7 RSP+8
[0xffffffff879be6fd, 0xffffffff879be879): DW_OP_reg14 R14
[0xffffffff879be879, 0xffffffff879be931): DW_OP_reg12 R12
[0xffffffff879be955, 0xffffffff879be961): DW_OP_reg14 R14
[0xffffffff879be961, 0xffffffff879be966): DW_OP_reg12 R12
[0xffffffff879be966, 0xffffffff879be976): DW_OP_reg14 R14
[0xffffffff879be976, 0xffffffff879be9df): DW_OP_reg12 R12
[0xffffffff879be9df, 0xffffffff879be9e5): DW_OP_reg14 R14
[0xffffffff879be9fc, 0xffffffff879bea24): DW_OP_consts +0, DW_OP_stack_value
[0xffffffff879bea24, 0xffffffff879bea74): DW_OP_breg7 RSP+8
[0xffffffff879bea74, 0xffffffff879beac4): DW_OP_reg14 R14)
DW_AT_name ("nr_range")
DW_AT_decl_file ("/home/yhs/work/bpf-next/arch/x86/mm/init.c")
DW_AT_decl_line (401)
DW_AT_type (0x008861cb "int")
0x00899aa4: DW_TAG_formal_parameter
DW_AT_location (indexed (0x3f) loclist = 0x000b21a7:
[0xffffffff879be485, 0xffffffff879be4a4): DW_OP_reg4 RSI
[0xffffffff879be4a4, 0xffffffff879be4e6): DW_OP_reg12 R12
[0xffffffff879be4e6, 0xffffffff879beac4): DW_OP_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value)
DW_AT_name ("start")
DW_AT_decl_file ("/home/yhs/work/bpf-next/arch/x86/mm/init.c")
DW_AT_decl_line (402)
DW_AT_type (0x008861cf "unsigned long")
0x00899ab0: DW_TAG_formal_parameter
DW_AT_location (indexed (0x40) loclist = 0x000b21c2:
[0xffffffff879be485, 0xffffffff879be4a9): DW_OP_reg1 RDX
[0xffffffff879be4a9, 0xffffffff879beac4): DW_OP_breg7 RSP+32)
DW_AT_name ("end")
DW_AT_decl_file ("/home/yhs/work/bpf-next/arch/x86/mm/init.c")
DW_AT_decl_line (403)
DW_AT_type (0x008861cf "unsigned long")
The parameter 'nr_range' is a constant and won't consume any ABI register.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 50 ++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 44 insertions(+), 6 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 684115d..712a957 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1197,6 +1197,21 @@ struct func_info {
#define PARM_DEFAULT_FAIL -1
#define PARM_FBREG_FAIL -2
+#define PARM_OPTIMIZED_CLANG -3
+#define PARM_CONTINUE -4
+
+static int parameter__multi_exprs(Dwarf_Op *expr, int loc_num) {
+ switch (expr[0].atom) {
+ case DW_OP_lit0 ... DW_OP_lit31:
+ case DW_OP_constu:
+ case DW_OP_consts:
+ if (loc_num != 0)
+ break;
+ return PARM_OPTIMIZED_CLANG;
+ }
+
+ return PARM_CONTINUE;
+}
/* For DW_AT_location 'attr':
* - if first location is DW_OP_regXX with expected number, return the register;
@@ -1205,7 +1220,7 @@ struct func_info {
* list, return the register; otherwise save register for later return
* - otherwise if no register was found for locations, return PARM_DEFAULT_FAIL.
*/
-static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
+static int parameter__reg(Dwarf_Attribute *attr, int expected_reg, struct cu *cu, struct conf_load *conf)
{
Dwarf_Addr base, start, end;
Dwarf_Op *expr, *entry_ops;
@@ -1229,8 +1244,17 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
if (exprlen == 2 && expr[exprlen - 1].atom == DW_OP_stack_value)
exprlen--;
- if (exprlen != 1)
- continue;
+ if (exprlen != 1) {
+ if (!cu->producer_clang || !conf->true_signature)
+ continue;
+
+ int res;
+ res = parameter__multi_exprs(expr, loc_num);
+ if (res == PARM_CONTINUE)
+ continue;
+ ret = res;
+ goto out;
+ }
switch (expr->atom) {
/* match DW_OP_regXX at first location */
@@ -1250,6 +1274,16 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg)
*/
ret = PARM_FBREG_FAIL;
break;
+ case DW_OP_lit0 ... DW_OP_lit31:
+ case DW_OP_constu:
+ case DW_OP_consts:
+ if (cu->producer_clang && conf->true_signature) {
+ if (loc_num != 0)
+ break;
+ ret = PARM_OPTIMIZED_CLANG;
+ goto out;
+ }
+ break;
/* match DW_OP_entry_value(DW_OP_regXX) at any location */
case DW_OP_entry_value:
case DW_OP_GNU_entry_value:
@@ -1338,11 +1372,14 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
if (parm->has_loc) {
int expected_reg = cu->register_params[reg_idx];
- int actual_reg = parameter__reg(&attr, expected_reg);
+ int actual_reg = parameter__reg(&attr, expected_reg, cu, conf);
- if (actual_reg == PARM_DEFAULT_FAIL)
+ if (actual_reg == PARM_DEFAULT_FAIL) {
parm->optimized = 1;
- else if (actual_reg == PARM_FBREG_FAIL || (expected_reg >= 0 && expected_reg != actual_reg))
+ } else if (actual_reg == PARM_OPTIMIZED_CLANG) {
+ parm->optimized = 1;
+ info->skip_idx++;
+ } else if (actual_reg == PARM_FBREG_FAIL || (expected_reg >= 0 && expected_reg != actual_reg)) {
/* mark parameters that use an unexpected
* register to hold a parameter; these will
* be problematic for users of BTF as they
@@ -1350,6 +1387,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
* contents.
*/
parm->unexpected_reg = 1;
+ }
} else if (!cu->producer_clang && has_const_value) {
parm->optimized = 1;
} else if (cu->producer_clang) {
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 7/9] dwarf_loader: Handle expression lists
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
` (5 preceding siblings ...)
2026-03-05 22:55 ` [PATCH dwarves 6/9] dwarf_loader: Detect optimized parameters with locations having constant values Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 8/9] btf_encoder: Handle optimized parameter properly Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 9/9] tests: Add a few clang true signature tests Yonghong Song
8 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
The corresponding type is checked for the parameter.
If the parameter size is less or equal to size of long,
the argument should match the corresponding ABI register.
For example:
0x0aba0808: DW_TAG_subprogram
DW_AT_name ("addrconf_ifdown")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x0ab7d8e9 "int")
...
0x0aba082b: DW_TAG_formal_parameter
DW_AT_location (indexed (0x32b) loclist = 0x016eabcd:
[0xffffffff83f6fef9, 0xffffffff83f6ff98): DW_OP_reg5 RDI
[0xffffffff83f6ff98, 0xffffffff83f70080): DW_OP_reg12 R12
[0xffffffff83f70080, 0xffffffff83f70111): DW_OP_breg7 RSP+112
[0xffffffff83f70111, 0xffffffff83f7014f): DW_OP_reg12 R12
[0xffffffff83f7014f, 0xffffffff83f7123c): DW_OP_breg7 RSP+112
[0xffffffff83f7123c, 0xffffffff83f7128c): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff83f7128c, 0xffffffff83f712a9): DW_OP_reg12 R12
[0xffffffff83f712a9, 0xffffffff83f712cd): DW_OP_breg7 RSP+112
[0xffffffff83f712cd, 0xffffffff83f712d2): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff83f712d2, 0xffffffff83f713dd): DW_OP_breg7 RSP+112)
DW_AT_name ("dev")
DW_AT_type (0x0ab7cb7d "net_device *")
...
0x0aba0836: DW_TAG_formal_parameter
DW_AT_location (indexed (0x32c) loclist = 0x016eac39:
[0xffffffff83f6fef9, 0xffffffff83f6ff15): DW_OP_breg4 RSI+0, DW_OP_constu 0xffffffff, DW_OP_and, DW_OP_convert (0x0ab7b571) "DW_ATE_unsigned_1", DW_OP_convert (0x0ab7b576) "DW_ATE_unsigned_8", DW_OP_stack_value
[0xffffffff83f6ff15, 0xffffffff83f7127c): DW_OP_breg7 RSP+36, DW_OP_deref_size 0x4, DW_OP_convert (0x0ab7b571) "DW_ATE_unsigned_1", DW_OP_convert (0x0ab7b576) "DW_ATE_unsigned_8", DW_OP_stack_value
[0xffffffff83f7128c, 0xffffffff83f713dd): DW_OP_breg7 RSP+36, DW_OP_deref_size 0x4, DW_OP_convert (0x0ab7b571) "DW_ATE_unsigned_1", DW_OP_convert (0x0ab7b576) "DW_ATE_unsigned_8", DW_OP_stack_value)
DW_AT_name ("unregister")
DW_AT_type (0x0ab7c933 "bool")
...
The parameter 'unregister' is the second argument which matches ABI register RSI.
So the function "addrconf_ifdown" signature is valid.
If the parameter size is '2 x size_of_long', more handling is necessary, e.g., below:
0x0a01e174: DW_TAG_subprogram
DW_AT_name ("check_zeroed_sockptr")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x09fead35 "int")
...
0x0a01e187: DW_TAG_formal_parameter
DW_AT_location (indexed (0x5b6) loclist = 0x0157f03f:
[0xffffffff83c941c0, 0xffffffff83c941c4): DW_OP_reg5 RDI, DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1
[0xffffffff83c941c4, 0xffffffff83c941cc): DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1
[0xffffffff83c941e1, 0xffffffff83c941e4): DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1)
DW_AT_name ("src")
DW_AT_type (0x09ff832d "sockptr_t")
...
0x0a01e193: DW_TAG_formal_parameter
DW_AT_const_value (64)
DW_AT_name ("offset")
DW_AT_type (0x09fee984 "size_t")
...
0x0a01e19e: DW_TAG_formal_parameter
DW_AT_location (indexed (0x5b7) loclist = 0x0157f06b:
[0xffffffff83c941c0, 0xffffffff83c941d1): DW_OP_reg1 RDX
[0xffffffff83c941d1, 0xffffffff83c941e1): DW_OP_entry_value(DW_OP_reg1 RDX), DW_OP_stack_value
[0xffffffff83c941e1, 0xffffffff83c941e9): DW_OP_reg1 RDX)
DW_AT_name ("size")
DW_AT_type (0x09fee984 "size_t")
...
The first parameter 'src' will take two ABI registers. This patch correctly detects such a pattern
to construct the true signature.
However, it is possible that only one 'size_of_long' is used from '2 x size_of_long'. For example
0x019520c6: DW_TAG_subprogram
DW_AT_name ("map_create")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x01934b29 "int")
...
0x01952111: DW_TAG_formal_parameter
DW_AT_location (indexed (0x31b) loclist = 0x0034fa0f:
[0xffffffff81892345, 0xffffffff8189237c): DW_OP_reg5 RDI
[0xffffffff8189237c, 0xffffffff818923bd): DW_OP_reg3 RBX
[0xffffffff818923bd, 0xffffffff818923d4): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff818923d4, 0xffffffff81892dcb): DW_OP_reg3 RBX
[0xffffffff81892df3, 0xffffffff81892e01): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff81892e01, 0xffffffff818932a9): DW_OP_reg3 RBX)
DW_AT_name ("attr")
DW_AT_type (0x01934d17 "bpf_attr *")
...
0x0195211d: DW_TAG_formal_parameter
DW_AT_location (indexed (0x31a) loclist = 0x0034f9dc:
[0xffffffff81892345, 0xffffffff81892357): DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1
[0xffffffff81892357, 0xffffffff81892f02): DW_OP_piece 0x8, DW_OP_breg7 RSP+20, DW_OP_deref_size 0x4, DW_OP_stack_value, DW_OP_piece 0x1
[0xffffffff81892f07, 0xffffffff818932a9): DW_OP_piece 0x8, DW_OP_breg7 RSP+20, DW_OP_deref_size 0x4, DW_OP_stack_value, DW_OP_piece 0x1)
DW_AT_name ("uattr")
DW_AT_type (0x019512ab "bpfptr_t")
...
For parameter 'uattr', only second half of parameter is used. For such cases,
the name and the type is changed in pahole and eventually going to vmlinux btf.
[55697] FUNC_PROTO '(anon)' ret_type_id=106780 vlen=2
'attr' type_id=455
'uattr__is_kernel' type_id=82014
[82014] TYPEDEF 'bool' type_id=67434
[113251] FUNC 'map_create' type_id=55697 linkage=static
You can see the new parameter name is 'uattr__is_kernel' and the type is 'bool'.
This makes thing easier for users to get the true signature.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
dwarf_loader.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++--
dwarves.h | 1 +
2 files changed, 219 insertions(+), 7 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 712a957..3a9b2c0 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1100,6 +1100,16 @@ static void arch__set_register_params(const GElf_Ehdr *ehdr, struct cu *cu)
}
}
+static bool arch__agg_use_two_regs(const GElf_Ehdr *ehdr)
+{
+ switch (ehdr->e_machine) {
+ case EM_S390:
+ return false;
+ default:
+ return true;
+ }
+}
+
static struct template_type_param *template_type_param__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
{
struct template_type_param *ttparm = tag__alloc(cu, sizeof(*ttparm));
@@ -1199,8 +1209,79 @@ struct func_info {
#define PARM_FBREG_FAIL -2
#define PARM_OPTIMIZED_CLANG -3
#define PARM_CONTINUE -4
+#define PARM_TWO_ADDR_LEN -5
+#define PARM_TO_BE_IMPROVED -6
+
+static int __get_type_byte_size(Dwarf_Die *die) {
+ Dwarf_Attribute attr;
+ if (dwarf_attr(die, DW_AT_type, &attr) == NULL)
+ return 0;
+
+ Dwarf_Die type_die;
+ if (dwarf_formref_die(&attr, &type_die) == NULL)
+ return 0;
+
+ uint64_t bsize = attr_numeric(&type_die, DW_AT_byte_size);
+ if (bsize == 0)
+ return __get_type_byte_size(&type_die);
+
+ return bsize;
+}
+
+static int get_type_byte_size(Dwarf_Die *die) {
+ int byte_size = 0;
+
+ Dwarf_Attribute attr;
+ if (dwarf_attr(die, DW_AT_abstract_origin, &attr)) {
+ Dwarf_Die origin;
+ if (dwarf_formref_die(&attr, &origin))
+ byte_size = __get_type_byte_size(&origin);
+ } else {
+ byte_size = __get_type_byte_size(die);
+ }
+ return byte_size;
+}
+
+/* Traverse the parameter type until finding the member type which has expected
+ * struct type offset.
+*/
+static Dwarf_Die *get_member_with_offset(Dwarf_Die *die, int offset, Dwarf_Die *member_die) {
+ Dwarf_Attribute attr;
+ if (dwarf_attr(die, DW_AT_type, &attr) == NULL)
+ return NULL;
+
+ Dwarf_Die type_die;
+ if (dwarf_formref_die(&attr, &type_die) == NULL)
+ return NULL;
+
+ uint64_t bsize = attr_numeric(&type_die, DW_AT_byte_size);
+ if (bsize == 0)
+ return get_member_with_offset(&type_die, offset, member_die);
+
+ if (dwarf_tag(&type_die) != DW_TAG_structure_type)
+ return NULL;
-static int parameter__multi_exprs(Dwarf_Op *expr, int loc_num) {
+ if (!dwarf_haschildren(&type_die) || dwarf_child(&type_die, member_die) != 0)
+ return NULL;
+ do {
+ if (dwarf_tag(member_die) != DW_TAG_member)
+ continue;
+
+ int off = attr_numeric(member_die, DW_AT_data_bit_offset);
+ if (off == offset * 8)
+ return member_die;
+ } while (dwarf_siblingof(member_die, member_die) == 0);
+
+ return NULL;
+}
+
+/* For two address length case, lower_half and upper_half represents the parameter.
+ * The lower_half and upper_half accumulates field information across possible multiple
+ * location lists.
+ */
+static int parameter__multi_exprs(Dwarf_Op *expr, int loc_num, struct cu *cu, size_t exprlen,
+ Dwarf_Die *die, int expected_reg, int byte_size,
+ unsigned long *lower_half, unsigned long *upper_half, int *ret) {
switch (expr[0].atom) {
case DW_OP_lit0 ... DW_OP_lit31:
case DW_OP_constu:
@@ -1210,9 +1291,119 @@ static int parameter__multi_exprs(Dwarf_Op *expr, int loc_num) {
return PARM_OPTIMIZED_CLANG;
}
+ if (byte_size <= cu->addr_size || !cu->agg_use_two_regs) {
+ switch (expr[0].atom) {
+ case DW_OP_reg0 ... DW_OP_reg31:
+ if (loc_num != 0)
+ break;
+ *ret = expr[0].atom;
+ if (*ret == expected_reg)
+ return *ret;
+ break;
+ case DW_OP_breg0 ... DW_OP_breg31:
+ if (loc_num != 0)
+ break;
+ bool has_op_stack_value = false;
+ for (int i = 1; i < exprlen; i++) {
+ if (expr[i].atom == DW_OP_stack_value) {
+ has_op_stack_value = true;
+ break;
+ }
+ }
+ if (!has_op_stack_value)
+ break;
+ /* The existence of DW_OP_stack_value means that
+ * DW_OP_bregX register is used as value.
+ */
+ *ret = expr[0].atom - DW_OP_breg0 + DW_OP_reg0;
+ if (*ret == expected_reg)
+ return *ret;
+ }
+ } else {
+ /* cu->addr * 2 */
+ int off = 0;
+ for (int i = 0; i < exprlen; i++) {
+ if (expr[i].atom == DW_OP_piece) {
+ int num = expr[i].number;
+ if (i == 0) {
+ off = num;
+ continue;
+ }
+ if (off < cu->addr_size) (*lower_half) |= (1 << off);
+ else (*upper_half) |= (1 << (off - cu->addr_size));
+ off += num;
+ } else if (expr[i].atom >= DW_OP_reg0 && expr[i].atom <= DW_OP_reg31) {
+ if (off < cu->addr_size)
+ *ret = expr[i].atom;
+ else if (*ret < 0)
+ *ret = expr[i].atom;
+ }
+ /* FIXME: not handling DW_OP_bregX yet since we do not have
+ * a use case for it yet for linux kernel.
+ */
+ }
+ }
+
return PARM_CONTINUE;
}
+/* The lower_half and upper_half, computed in parameter__multiple_exprs(), are handled here.
+ */
+static int parameter__handle_two_addr_len(int expected_reg, unsigned long lower_half, unsigned long upper_half,
+ int ret, Dwarf_Die *die, struct conf_load *conf, struct cu *cu,
+ struct parameter *parm) {
+ if (!lower_half && !upper_half)
+ return ret;
+
+ if (ret != expected_reg)
+ return ret;
+
+ if (!conf->true_signature)
+ return PARM_DEFAULT_FAIL;
+
+ /* Both halfs are used based on dwarf */
+ if (lower_half && upper_half)
+ return PARM_TWO_ADDR_LEN;
+
+ /* FIXME: parm->name may be NULL due to abstract origin. We do not want to
+ * update abstract origin as the type in abstract origin may be used
+ * in some other places. We could remove abstract origin in this parameter
+ * and add name and type in parameter itself. Right now, for current bpf-next
+ * repo, we do not have instances below where parm->name is NULL for x86_64 arch.
+ */
+ if (!parm->name)
+ return PARM_TO_BE_IMPROVED;
+
+ /* FIXME: Only support single field now so we can have a good parameter name and
+ * type for it.
+ */
+ if (__builtin_popcountll(lower_half) >= 2 || __builtin_popcountll(upper_half) >= 2)
+ return PARM_TO_BE_IMPROVED;
+
+ int field_offset;
+ if (__builtin_popcountll(lower_half) == 1)
+ field_offset = __builtin_ctzll(lower_half);
+ else
+ field_offset = cu->addr_size + __builtin_ctzll(upper_half);
+
+ /* FIXME: Only struct type is supported. */
+ Dwarf_Die member_die;
+ if (!get_member_with_offset(die, field_offset, &member_die))
+ return PARM_TO_BE_IMPROVED;
+
+ const char *member_name = attr_string(&member_die, DW_AT_name, conf);
+ int len = sizeof(parm->name) + sizeof(member_name) + 3;
+ char *new_name = malloc(len);
+ sprintf(new_name, "%s__%s", parm->name, member_name);
+ parm->name = new_name;
+
+ struct tag *tag = &parm->tag;
+ struct dwarf_tag *dtag = tag__dwarf(tag);
+ dwarf_tag__set_attr_type(dtag, type, &member_die, DW_AT_type);
+
+ return ret;
+}
+
/* For DW_AT_location 'attr':
* - if first location is DW_OP_regXX with expected number, return the register;
* otherwise save the register for later return
@@ -1220,15 +1411,18 @@ static int parameter__multi_exprs(Dwarf_Op *expr, int loc_num) {
* list, return the register; otherwise save register for later return
* - otherwise if no register was found for locations, return PARM_DEFAULT_FAIL.
*/
-static int parameter__reg(Dwarf_Attribute *attr, int expected_reg, struct cu *cu, struct conf_load *conf)
+static int parameter__reg(Dwarf_Attribute *attr, int expected_reg, struct cu *cu, struct conf_load *conf,
+ Dwarf_Die *die, struct parameter *parm, struct func_info *info)
{
Dwarf_Addr base, start, end;
Dwarf_Op *expr, *entry_ops;
Dwarf_Attribute entry_attr;
size_t exprlen, entry_len;
ptrdiff_t offset = 0;
+ int byte_size = 0;
int loc_num = -1;
int ret = PARM_DEFAULT_FAIL;
+ unsigned long lower_half = 0, upper_half = 0;
/* use libdw__lock as dwarf_getlocation(s) has concurrency issues
* when libdw is not compiled with experimental --enable-thread-safety
@@ -1248,8 +1442,15 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg, struct cu *cu
if (!cu->producer_clang || !conf->true_signature)
continue;
+ if (!byte_size)
+ byte_size = get_type_byte_size(die);
+ /* This should not happen. */
+ if (!byte_size)
+ return PARM_DEFAULT_FAIL;
+
int res;
- res = parameter__multi_exprs(expr, loc_num);
+ res = parameter__multi_exprs(expr, loc_num, cu, exprlen, die, expected_reg,
+ byte_size, &lower_half, &upper_half, &ret);
if (res == PARM_CONTINUE)
continue;
ret = res;
@@ -1297,6 +1498,10 @@ static int parameter__reg(Dwarf_Attribute *attr, int expected_reg, struct cu *cu
break;
}
}
+
+ ret = parameter__handle_two_addr_len(expected_reg, lower_half, upper_half,
+ ret, die, conf, cu, parm);
+
out:
pthread_mutex_unlock(&libdw__lock);
return ret;
@@ -1332,8 +1537,6 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
}
}
reg_idx = param_idx - info->skip_idx;
- if (reg_idx >= cu->nr_register_params)
- return parm;
/* Parameters which use DW_AT_abstract_origin to point at
* the original parameter definition (with no name in the DIE)
* are the result of later DWARF generation during compilation
@@ -1371,15 +1574,22 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) != NULL;
if (parm->has_loc) {
+ if (reg_idx >= cu->nr_register_params)
+ return parm;
+
int expected_reg = cu->register_params[reg_idx];
- int actual_reg = parameter__reg(&attr, expected_reg, cu, conf);
+ int actual_reg = parameter__reg(&attr, expected_reg, cu, conf, die, parm, info);
if (actual_reg == PARM_DEFAULT_FAIL) {
parm->optimized = 1;
} else if (actual_reg == PARM_OPTIMIZED_CLANG) {
parm->optimized = 1;
info->skip_idx++;
- } else if (actual_reg == PARM_FBREG_FAIL || (expected_reg >= 0 && expected_reg != actual_reg)) {
+ } else if (actual_reg == PARM_TWO_ADDR_LEN) {
+ /* account for parameter with two registers */
+ info->skip_idx--;
+ } else if (actual_reg == PARM_FBREG_FAIL || actual_reg == PARM_TO_BE_IMPROVED ||
+ (expected_reg >= 0 && expected_reg != actual_reg)) {
/* mark parameters that use an unexpected
* register to hold a parameter; these will
* be problematic for users of BTF as they
@@ -3419,6 +3629,7 @@ static int cu__set_common(struct cu *cu, struct conf_load *conf,
cu->little_endian = ehdr.e_ident[EI_DATA] == ELFDATA2LSB;
cu->nr_register_params = arch__nr_register_params(&ehdr);
+ cu->agg_use_two_regs = arch__agg_use_two_regs(&ehdr);
arch__set_register_params(&ehdr, cu);
return 0;
}
diff --git a/dwarves.h b/dwarves.h
index ad33828..b7bae87 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -303,6 +303,7 @@ struct cu {
uint8_t uses_global_strings:1;
uint8_t little_endian:1;
uint8_t producer_clang:1;
+ uint8_t agg_use_two_regs:1; /* An aggregate like {long a; long b;} */
uint8_t nr_register_params;
int register_params[ARCH_MAX_REGISTER_PARAMS];
int functions_saved;
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 8/9] btf_encoder: Handle optimized parameter properly
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
` (6 preceding siblings ...)
2026-03-05 22:55 ` [PATCH dwarves 7/9] dwarf_loader: Handle expression lists Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 9/9] tests: Add a few clang true signature tests Yonghong Song
8 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
Ensure to skip optimized parameter so btf can generate
proper true signatures.
In the first patch of the patch set, with DW_CC_nocall filtering, 379 functions
have signature changed but not checked properly. With a series of improvement,
eventually only 17 functions remain and unfortunately these functions cannot
be converted to true signatures due to locations. For example,
0x0242f1f7: DW_TAG_subprogram
DW_AT_name ("memblock_find_in_range")
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f22e: DW_TAG_formal_parameter
DW_AT_location (indexed (0x14a) loclist = 0x005595bc:
[0xffffffff87a000f9, 0xffffffff87a00178): DW_OP_reg5 RDI
[0xffffffff87a00178, 0xffffffff87a001be): DW_OP_reg14 R14
[0xffffffff87a001be, 0xffffffff87a001c7): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff87a001c7, 0xffffffff87a00214): DW_OP_reg14 R14)
DW_AT_name ("start")
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f239: DW_TAG_formal_parameter
DW_AT_location (indexed (0x14b) loclist = 0x005595e6:
[0xffffffff87a000f9, 0xffffffff87a00175): DW_OP_reg4 RSI
[0xffffffff87a00175, 0xffffffff87a001b8): DW_OP_reg3 RBX
[0xffffffff87a001b8, 0xffffffff87a001c7): DW_OP_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value
[0xffffffff87a001c7, 0xffffffff87a00214): DW_OP_reg3 RBX)
DW_AT_name ("end")
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f245: DW_TAG_formal_parameter
DW_AT_location (indexed (0x14c) loclist = 0x00559610:
[0xffffffff87a001e3, 0xffffffff87a001ef): DW_OP_breg4 RSI+0)
DW_AT_name ("size")
DW_AT_type (0x0242decc "phys_addr_t")
...
0x0242f250: DW_TAG_formal_parameter
DW_AT_const_value (4096)
DW_AT_name ("align")
DW_AT_type (0x0242decc "phys_addr_t")
...
The third parameter 'size' is not from RDX. Hence, true signature is not possible for this function.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
btf_encoder.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index aa7cd1c..738a4cc 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1257,15 +1257,19 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
struct btf *btf = encoder->btf;
struct llvm_annotation *annot;
struct parameter *param;
- uint8_t param_idx = 0;
+ uint8_t param_idx = 0, skip_idx = 0;
int str_off, err = 0;
if (!state)
return -ENOMEM;
+ ftype__for_each_parameter(ftype, param) {
+ if (param->optimized) skip_idx++;
+ }
+
state->addr = function__addr(fn);
state->elf = func;
- state->nr_parms = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0);
+ state->nr_parms = ftype->nr_parms - skip_idx + (ftype->unspec_parms ? 1 : 0);
state->ret_type_id = ftype->tag.type == 0 ? 0 : encoder->type_id_off + ftype->tag.type;
if (state->nr_parms > 0) {
state->parms = zalloc(state->nr_parms * sizeof(*state->parms));
@@ -1303,6 +1307,9 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
state->nr_parms--;
continue;
}
+ if (encoder->cu->producer_clang && param->optimized)
+ continue;
+
name = parameter__name(param) ?: "";
str_off = btf__add_str(btf, name);
if (str_off < 0) {
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH dwarves 9/9] tests: Add a few clang true signature tests
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
` (7 preceding siblings ...)
2026-03-05 22:55 ` [PATCH dwarves 8/9] btf_encoder: Handle optimized parameter properly Yonghong Song
@ 2026-03-05 22:55 ` Yonghong Song
2026-03-19 18:48 ` Alan Maguire
8 siblings, 1 reply; 17+ messages in thread
From: Yonghong Song @ 2026-03-05 22:55 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
Three tests are added.
Test 1: clang_parm_optimized.sh
BTF: char * foo(struct t * a, struct t * d);
DWARF: char * foo(struct t * a, int b, struct t * d);
where parameber 'b' is unused.
Test 2: clang_parm_optimized_stack.sh
BTF: char * foo(struct t * a, struct t * d);
DWARF: char * foo(struct t * a, int b1, int b2, int b3, int b4, int b5, int b6, struct t * d);
where parameters 'b1' to 'b6' are unused.
Test 3: clang_parm_aggregate.sh
BTF: long foo(long a__f1, struct t b, int i);
DWARF: long foo(struct t a, struct t b, int i);
where the 'struct t' definition is 'struct t { long f1; long f2; };', and
a.f2 is not used in the function.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
tests/true_signatures/clang_parm_aggregate.sh | 83 ++++++++++++++++
tests/true_signatures/clang_parm_optimized.sh | 95 +++++++++++++++++++
.../clang_parm_optimized_stack.sh | 95 +++++++++++++++++++
.../gcc_true_signatures.sh | 0
4 files changed, 273 insertions(+)
create mode 100755 tests/true_signatures/clang_parm_aggregate.sh
create mode 100755 tests/true_signatures/clang_parm_optimized.sh
create mode 100755 tests/true_signatures/clang_parm_optimized_stack.sh
rename tests/{ => true_signatures}/gcc_true_signatures.sh (100%)
diff --git a/tests/true_signatures/clang_parm_aggregate.sh b/tests/true_signatures/clang_parm_aggregate.sh
new file mode 100755
index 0000000..6d92701
--- /dev/null
+++ b/tests/true_signatures/clang_parm_aggregate.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+
+outdir=
+
+fail()
+{
+ # Do not remove test dir; might be useful for analysis
+ trap - EXIT
+ if [[ -d "$outdir" ]]; then
+ echo "Test data is in $outdir"
+ fi
+ exit 1
+}
+
+cleanup()
+{
+ rm ${outdir}/*
+ rmdir $outdir
+}
+
+outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
+
+trap cleanup EXIT
+
+echo -n "Validation of BTF encoding of true_signatures: "
+
+clang_true="${outdir}/clang_true"
+CC=$(which clang 2>/dev/null)
+
+if [[ -z "$CC" ]]; then
+ echo "skip: clang not available"
+ exit 2
+fi
+
+cat > ${clang_true}.c << EOF
+struct t { long f1; long f2; };
+__attribute__((noinline)) static long foo(struct t a, struct t b, int i)
+{
+ return a.f1 + b.f1 + b.f2 + i;
+}
+
+struct t p1, p2;
+int i;
+int main()
+{
+ return (int)foo(p1, p2, i);
+}
+EOF
+
+CFLAGS="$CFLAGS -g -O2"
+${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
+if [[ $? -ne 0 ]]; then
+ echo "Could not compile ${clang_true}.c" >& 2
+ exit 1
+fi
+LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
+if [[ $? -ne 0 ]]; then
+ echo "Could not encode BTF for $clang_true"
+ exit 1
+fi
+
+btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
+if [[ -z "$btf_optimized" ]]; then
+ echo "skip: no optimizations applied."
+ exit 2
+fi
+
+btf_cmp=$btf_optimized
+dwarf=$(pfunct --all $clang_true |grep "foo")
+
+test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
+
+if [[ "$btf_cmp" == "$dwarf" ]]; then
+ echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
+ exit 1
+else
+ echo ""
+ echo " BTF: $btf_optimized"
+ echo " DWARF: $dwarf"
+fi
+echo "Ok"
+exit 0
diff --git a/tests/true_signatures/clang_parm_optimized.sh b/tests/true_signatures/clang_parm_optimized.sh
new file mode 100755
index 0000000..3022e2b
--- /dev/null
+++ b/tests/true_signatures/clang_parm_optimized.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+
+outdir=
+
+fail()
+{
+ # Do not remove test dir; might be useful for analysis
+ trap - EXIT
+ if [[ -d "$outdir" ]]; then
+ echo "Test data is in $outdir"
+ fi
+ exit 1
+}
+
+cleanup()
+{
+ rm ${outdir}/*
+ rmdir $outdir
+}
+
+outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
+
+trap cleanup EXIT
+
+echo -n "Validation of BTF encoding of true_signatures: "
+
+clang_true="${outdir}/clang_true"
+CC=$(which clang 2>/dev/null)
+
+if [[ -z "$CC" ]]; then
+ echo "skip: clang not available"
+ exit 2
+fi
+
+cat > ${clang_true}.c << EOF
+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);
+}
+EOF
+
+CFLAGS="$CFLAGS -g -O2"
+${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
+if [[ $? -ne 0 ]]; then
+ echo "Could not compile ${clang_true}.c" >& 2
+ exit 1
+fi
+LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
+if [[ $? -ne 0 ]]; then
+ echo "Could not encode BTF for $clang_true"
+ exit 1
+fi
+
+btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
+if [[ -z "$btf_optimized" ]]; then
+ echo "skip: no optimizations applied."
+ exit 2
+fi
+
+btf_cmp=$btf_optimized
+dwarf=$(pfunct --all $clang_true |grep "foo")
+
+test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
+
+if [[ "$btf_cmp" == "$dwarf" ]]; then
+ echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
+ exit 1
+else
+ echo ""
+ echo " BTF: $btf_optimized"
+ echo " DWARF: $dwarf"
+fi
+echo "Ok"
+exit 0
diff --git a/tests/true_signatures/clang_parm_optimized_stack.sh b/tests/true_signatures/clang_parm_optimized_stack.sh
new file mode 100755
index 0000000..1abb96d
--- /dev/null
+++ b/tests/true_signatures/clang_parm_optimized_stack.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+
+outdir=
+
+fail()
+{
+ # Do not remove test dir; might be useful for analysis
+ trap - EXIT
+ if [[ -d "$outdir" ]]; then
+ echo "Test data is in $outdir"
+ fi
+ exit 1
+}
+
+cleanup()
+{
+ rm ${outdir}/*
+ rmdir $outdir
+}
+
+outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
+
+trap cleanup EXIT
+
+echo -n "Validation of BTF encoding of true_signatures: "
+
+clang_true="${outdir}/clang_true"
+CC=$(which clang 2>/dev/null)
+
+if [[ -z "$CC" ]]; then
+ echo "skip: clang not available"
+ exit 2
+fi
+
+cat > ${clang_true}.c << EOF
+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 b1, int b2, int b3, int b4, int b5, int b6, struct t *d)
+{
+ return tar(a, d);
+}
+
+__attribute__((noinline)) char *bar(struct t *a, struct t *d)
+{
+ return foo(a, 1, 2, 3, 4, 5, 6, d);
+}
+
+struct t p1, p2;
+int main()
+{
+ return !!bar(&p1, &p2);
+}
+EOF
+
+CFLAGS="$CFLAGS -g -O2"
+${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
+if [[ $? -ne 0 ]]; then
+ echo "Could not compile ${clang_true}.c" >& 2
+ exit 1
+fi
+LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
+if [[ $? -ne 0 ]]; then
+ echo "Could not encode BTF for $clang_true"
+ exit 1
+fi
+
+btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
+if [[ -z "$btf_optimized" ]]; then
+ echo "skip: no optimizations applied."
+ exit 2
+fi
+
+btf_cmp=$btf_optimized
+dwarf=$(pfunct --all $clang_true |grep "foo")
+
+test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
+
+if [[ "$btf_cmp" == "$dwarf" ]]; then
+ echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
+ exit 1
+else
+ echo ""
+ echo " BTF: $btf_optimized"
+ echo " DWARF: $dwarf"
+fi
+echo "Ok"
+exit 0
diff --git a/tests/gcc_true_signatures.sh b/tests/true_signatures/gcc_true_signatures.sh
similarity index 100%
rename from tests/gcc_true_signatures.sh
rename to tests/true_signatures/gcc_true_signatures.sh
--
2.47.3
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr
2026-03-05 22:55 ` [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr Yonghong Song
@ 2026-03-19 12:32 ` Jiri Olsa
2026-03-19 17:31 ` Yonghong Song
0 siblings, 1 reply; 17+ messages in thread
From: Jiri Olsa @ 2026-03-19 12:32 UTC (permalink / raw)
To: Yonghong Song
Cc: Alan Maguire, Arnaldo Carvalho de Melo, dwarves,
Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On Thu, Mar 05, 2026 at 02:55:00PM -0800, Yonghong Song wrote:
SNIP
> ---
> dwarf_loader.c | 71 +++++++++++++++++++++++++++++++++++++++++++-------
> dwarves.h | 2 ++
> 2 files changed, 63 insertions(+), 10 deletions(-)
>
> diff --git a/dwarf_loader.c b/dwarf_loader.c
> index 16fb7be..610b69e 100644
> --- a/dwarf_loader.c
> +++ b/dwarf_loader.c
> @@ -1190,6 +1190,10 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
> return ret;
> }
>
> +struct func_info {
> + bool signature_changed;
> +};
> +
> /* For DW_AT_location 'attr':
> * - if first location is DW_OP_regXX with expected number, return the register;
> * otherwise save the register for later return
> @@ -1252,7 +1256,8 @@ out:
> }
>
> static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
> - struct conf_load *conf, int param_idx)
> + struct conf_load *conf, int param_idx,
> + struct func_info *info)
> {
> struct parameter *parm = tag__alloc(cu, sizeof(*parm));
>
> @@ -1265,6 +1270,8 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
> parm->idx = param_idx;
> if (param_idx >= cu->nr_register_params || param_idx < 0)
> return parm;
> + if (cu->producer_clang && !info->signature_changed)
> + return parm;
should you check for info != NULL?
> /* Parameters which use DW_AT_abstract_origin to point at
> * the original parameter definition (with no name in the DIE)
> * are the result of later DWARF generation during compilation
> @@ -1337,7 +1344,7 @@ static int formal_parameter_pack__load_params(struct formal_parameter_pack *pack
> continue;
> }
>
> - struct parameter *param = parameter__new(die, cu, conf, -1);
> + struct parameter *param = parameter__new(die, cu, conf, -1, NULL);
>
> if (param == NULL)
> return -1;
> @@ -1502,6 +1509,29 @@ static struct ftype *ftype__new(Dwarf_Die *die, struct cu *cu)
> return ftype;
> }
>
SNIP
> int param_idx = 0;
> Dwarf_Die child;
> @@ -2320,7 +2353,7 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
> continue;
> }
> case DW_TAG_formal_parameter:
> - tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++);
> + tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++, info);
> break;
> case DW_TAG_variable:
> tag = die__create_new_variable(die, cu, conf, 0);
> @@ -2391,9 +2424,10 @@ out_enomem:
> static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
> {
> struct function *function = function__new(die, cu, conf);
> + struct func_info info = { function->signature_changed };
would it be easier to add signature_changed to struct ftype or pass
function pointer directly to die__process_function ?
(after reading the rest of the patches, seems like func_info is needed
anyway, so maybe not worth it)
>
> if (function != NULL &&
> - die__process_function(die, &function->proto, &function->lexblock, cu, conf) != 0) {
> + die__process_function(die, &function->proto, &function->lexblock, cu, conf, &info) != 0) {
> function__delete(function, cu);
> function = NULL;
> }
> @@ -3045,6 +3079,21 @@ static unsigned long long dwarf_tag__orig_id(const struct tag *tag,
> return cu->extra_dbg_info ? dtag->id : 0;
> }
>
> +static bool attr_producer_clang(Dwarf_Die *die)
> +{
> + Dwarf_Attribute attr;
> + const char *producer;
> +
> + if (dwarf_attr(die, DW_AT_producer, &attr) == NULL)
> + return false;
> +
> + producer = dwarf_formstring(&attr);
> + if (!producer)
> + return false;
> +
nit you could call attr_string(die, DW_AT_producer, NULL) to get the string
jirka
> + return !!strstr(producer, "clang");
> +}
> +
SNIP
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr
2026-03-19 12:32 ` Jiri Olsa
@ 2026-03-19 17:31 ` Yonghong Song
0 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-19 17:31 UTC (permalink / raw)
To: Jiri Olsa
Cc: Alan Maguire, Arnaldo Carvalho de Melo, dwarves,
Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On 3/19/26 5:32 AM, Jiri Olsa wrote:
> On Thu, Mar 05, 2026 at 02:55:00PM -0800, Yonghong Song wrote:
>
> SNIP
>
>> ---
>> dwarf_loader.c | 71 +++++++++++++++++++++++++++++++++++++++++++-------
>> dwarves.h | 2 ++
>> 2 files changed, 63 insertions(+), 10 deletions(-)
>>
>> diff --git a/dwarf_loader.c b/dwarf_loader.c
>> index 16fb7be..610b69e 100644
>> --- a/dwarf_loader.c
>> +++ b/dwarf_loader.c
>> @@ -1190,6 +1190,10 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
>> return ret;
>> }
>>
>> +struct func_info {
>> + bool signature_changed;
>> +};
>> +
>> /* For DW_AT_location 'attr':
>> * - if first location is DW_OP_regXX with expected number, return the register;
>> * otherwise save the register for later return
>> @@ -1252,7 +1256,8 @@ out:
>> }
>>
>> static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> - struct conf_load *conf, int param_idx)
>> + struct conf_load *conf, int param_idx,
>> + struct func_info *info)
>> {
>> struct parameter *parm = tag__alloc(cu, sizeof(*parm));
>>
>> @@ -1265,6 +1270,8 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> parm->idx = param_idx;
>> if (param_idx >= cu->nr_register_params || param_idx < 0)
>> return parm;
>> + if (cu->producer_clang && !info->signature_changed)
>> + return parm;
> should you check for info != NULL?
We should be okay. I audited that if info is NULL, then param_idx must be < 0.
>
>> /* Parameters which use DW_AT_abstract_origin to point at
>> * the original parameter definition (with no name in the DIE)
>> * are the result of later DWARF generation during compilation
>> @@ -1337,7 +1344,7 @@ static int formal_parameter_pack__load_params(struct formal_parameter_pack *pack
>> continue;
>> }
>>
>> - struct parameter *param = parameter__new(die, cu, conf, -1);
>> + struct parameter *param = parameter__new(die, cu, conf, -1, NULL);
>>
>> if (param == NULL)
>> return -1;
>> @@ -1502,6 +1509,29 @@ static struct ftype *ftype__new(Dwarf_Die *die, struct cu *cu)
>> return ftype;
>> }
>>
> SNIP
>
>> int param_idx = 0;
>> Dwarf_Die child;
>> @@ -2320,7 +2353,7 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
>> continue;
>> }
>> case DW_TAG_formal_parameter:
>> - tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++);
>> + tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++, info);
>> break;
>> case DW_TAG_variable:
>> tag = die__create_new_variable(die, cu, conf, 0);
>> @@ -2391,9 +2424,10 @@ out_enomem:
>> static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
>> {
>> struct function *function = function__new(die, cu, conf);
>> + struct func_info info = { function->signature_changed };
> would it be easier to add signature_changed to struct ftype or pass
> function pointer directly to die__process_function ?
>
> (after reading the rest of the patches, seems like func_info is needed
> anyway, so maybe not worth it)
Right, there are additional fields in func_info struct.
>
>>
>> if (function != NULL &&
>> - die__process_function(die, &function->proto, &function->lexblock, cu, conf) != 0) {
>> + die__process_function(die, &function->proto, &function->lexblock, cu, conf, &info) != 0) {
>> function__delete(function, cu);
>> function = NULL;
>> }
>> @@ -3045,6 +3079,21 @@ static unsigned long long dwarf_tag__orig_id(const struct tag *tag,
>> return cu->extra_dbg_info ? dtag->id : 0;
>> }
>>
>> +static bool attr_producer_clang(Dwarf_Die *die)
>> +{
>> + Dwarf_Attribute attr;
>> + const char *producer;
>> +
>> + if (dwarf_attr(die, DW_AT_producer, &attr) == NULL)
>> + return false;
>> +
>> + producer = dwarf_formstring(&attr);
>> + if (!producer)
>> + return false;
>> +
> nit you could call attr_string(die, DW_AT_producer, NULL) to get the string
Good point. Will fix it in the next revision.
>
> jirka
>
>> + return !!strstr(producer, "clang");
>> +}
>> +
> SNIP
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 9/9] tests: Add a few clang true signature tests
2026-03-05 22:55 ` [PATCH dwarves 9/9] tests: Add a few clang true signature tests Yonghong Song
@ 2026-03-19 18:48 ` Alan Maguire
2026-03-20 4:52 ` Yonghong Song
0 siblings, 1 reply; 17+ messages in thread
From: Alan Maguire @ 2026-03-19 18:48 UTC (permalink / raw)
To: Yonghong Song, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On 05/03/2026 22:55, Yonghong Song wrote:
> Three tests are added.
>
> Test 1: clang_parm_optimized.sh
> BTF: char * foo(struct t * a, struct t * d);
> DWARF: char * foo(struct t * a, int b, struct t * d);
> where parameber 'b' is unused.
>
> Test 2: clang_parm_optimized_stack.sh
> BTF: char * foo(struct t * a, struct t * d);
> DWARF: char * foo(struct t * a, int b1, int b2, int b3, int b4, int b5, int b6, struct t * d);
> where parameters 'b1' to 'b6' are unused.
>
> Test 3: clang_parm_aggregate.sh
> BTF: long foo(long a__f1, struct t b, int i);
> DWARF: long foo(struct t a, struct t b, int i);
> where the 'struct t' definition is 'struct t { long f1; long f2; };', and
> a.f2 is not used in the function.
>
> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
tests look good but we need to ensure they are run by the ./tests script
so that they will be run in CI. also take a look at Bruce's work to
add common test functions [1], will probably simplify aspects of the tests.
Thanks!
Alan
[1] https://lore.kernel.org/dwarves/20260309205643.1985134-2-bruce.mcculloch@oracle.com/
> ---
> tests/true_signatures/clang_parm_aggregate.sh | 83 ++++++++++++++++
> tests/true_signatures/clang_parm_optimized.sh | 95 +++++++++++++++++++
> .../clang_parm_optimized_stack.sh | 95 +++++++++++++++++++
> .../gcc_true_signatures.sh | 0
> 4 files changed, 273 insertions(+)
> create mode 100755 tests/true_signatures/clang_parm_aggregate.sh
> create mode 100755 tests/true_signatures/clang_parm_optimized.sh
> create mode 100755 tests/true_signatures/clang_parm_optimized_stack.sh
> rename tests/{ => true_signatures}/gcc_true_signatures.sh (100%)
>
> diff --git a/tests/true_signatures/clang_parm_aggregate.sh b/tests/true_signatures/clang_parm_aggregate.sh
> new file mode 100755
> index 0000000..6d92701
> --- /dev/null
> +++ b/tests/true_signatures/clang_parm_aggregate.sh
> @@ -0,0 +1,83 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +outdir=
> +
> +fail()
> +{
> + # Do not remove test dir; might be useful for analysis
> + trap - EXIT
> + if [[ -d "$outdir" ]]; then
> + echo "Test data is in $outdir"
> + fi
> + exit 1
> +}
> +
> +cleanup()
> +{
> + rm ${outdir}/*
> + rmdir $outdir
> +}
> +
> +outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
> +
> +trap cleanup EXIT
> +
> +echo -n "Validation of BTF encoding of true_signatures: "
> +
> +clang_true="${outdir}/clang_true"
> +CC=$(which clang 2>/dev/null)
> +
> +if [[ -z "$CC" ]]; then
> + echo "skip: clang not available"
> + exit 2
> +fi
> +
> +cat > ${clang_true}.c << EOF
> +struct t { long f1; long f2; };
> +__attribute__((noinline)) static long foo(struct t a, struct t b, int i)
> +{
> + return a.f1 + b.f1 + b.f2 + i;
> +}
> +
> +struct t p1, p2;
> +int i;
> +int main()
> +{
> + return (int)foo(p1, p2, i);
> +}
> +EOF
> +
> +CFLAGS="$CFLAGS -g -O2"
> +${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
> +if [[ $? -ne 0 ]]; then
> + echo "Could not compile ${clang_true}.c" >& 2
> + exit 1
> +fi
> +LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
> +if [[ $? -ne 0 ]]; then
> + echo "Could not encode BTF for $clang_true"
> + exit 1
> +fi
> +
> +btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
> +if [[ -z "$btf_optimized" ]]; then
> + echo "skip: no optimizations applied."
> + exit 2
> +fi
> +
> +btf_cmp=$btf_optimized
> +dwarf=$(pfunct --all $clang_true |grep "foo")
> +
> +test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
> +
> +if [[ "$btf_cmp" == "$dwarf" ]]; then
> + echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
> + exit 1
> +else
> + echo ""
> + echo " BTF: $btf_optimized"
> + echo " DWARF: $dwarf"
> +fi
> +echo "Ok"
> +exit 0
> diff --git a/tests/true_signatures/clang_parm_optimized.sh b/tests/true_signatures/clang_parm_optimized.sh
> new file mode 100755
> index 0000000..3022e2b
> --- /dev/null
> +++ b/tests/true_signatures/clang_parm_optimized.sh
> @@ -0,0 +1,95 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +outdir=
> +
> +fail()
> +{
> + # Do not remove test dir; might be useful for analysis
> + trap - EXIT
> + if [[ -d "$outdir" ]]; then
> + echo "Test data is in $outdir"
> + fi
> + exit 1
> +}
> +
> +cleanup()
> +{
> + rm ${outdir}/*
> + rmdir $outdir
> +}
> +
> +outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
> +
> +trap cleanup EXIT
> +
> +echo -n "Validation of BTF encoding of true_signatures: "
> +
> +clang_true="${outdir}/clang_true"
> +CC=$(which clang 2>/dev/null)
> +
> +if [[ -z "$CC" ]]; then
> + echo "skip: clang not available"
> + exit 2
> +fi
> +
> +cat > ${clang_true}.c << EOF
> +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);
> +}
> +EOF
> +
> +CFLAGS="$CFLAGS -g -O2"
> +${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
> +if [[ $? -ne 0 ]]; then
> + echo "Could not compile ${clang_true}.c" >& 2
> + exit 1
> +fi
> +LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
> +if [[ $? -ne 0 ]]; then
> + echo "Could not encode BTF for $clang_true"
> + exit 1
> +fi
> +
> +btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
> +if [[ -z "$btf_optimized" ]]; then
> + echo "skip: no optimizations applied."
> + exit 2
> +fi
> +
> +btf_cmp=$btf_optimized
> +dwarf=$(pfunct --all $clang_true |grep "foo")
> +
> +test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
> +
> +if [[ "$btf_cmp" == "$dwarf" ]]; then
> + echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
> + exit 1
> +else
> + echo ""
> + echo " BTF: $btf_optimized"
> + echo " DWARF: $dwarf"
> +fi
> +echo "Ok"
> +exit 0
> diff --git a/tests/true_signatures/clang_parm_optimized_stack.sh b/tests/true_signatures/clang_parm_optimized_stack.sh
> new file mode 100755
> index 0000000..1abb96d
> --- /dev/null
> +++ b/tests/true_signatures/clang_parm_optimized_stack.sh
> @@ -0,0 +1,95 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +outdir=
> +
> +fail()
> +{
> + # Do not remove test dir; might be useful for analysis
> + trap - EXIT
> + if [[ -d "$outdir" ]]; then
> + echo "Test data is in $outdir"
> + fi
> + exit 1
> +}
> +
> +cleanup()
> +{
> + rm ${outdir}/*
> + rmdir $outdir
> +}
> +
> +outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
> +
> +trap cleanup EXIT
> +
> +echo -n "Validation of BTF encoding of true_signatures: "
> +
> +clang_true="${outdir}/clang_true"
> +CC=$(which clang 2>/dev/null)
> +
> +if [[ -z "$CC" ]]; then
> + echo "skip: clang not available"
> + exit 2
> +fi
> +
> +cat > ${clang_true}.c << EOF
> +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 b1, int b2, int b3, int b4, int b5, int b6, struct t *d)
> +{
> + return tar(a, d);
> +}
> +
> +__attribute__((noinline)) char *bar(struct t *a, struct t *d)
> +{
> + return foo(a, 1, 2, 3, 4, 5, 6, d);
> +}
> +
> +struct t p1, p2;
> +int main()
> +{
> + return !!bar(&p1, &p2);
> +}
> +EOF
> +
> +CFLAGS="$CFLAGS -g -O2"
> +${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
> +if [[ $? -ne 0 ]]; then
> + echo "Could not compile ${clang_true}.c" >& 2
> + exit 1
> +fi
> +LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
> +if [[ $? -ne 0 ]]; then
> + echo "Could not encode BTF for $clang_true"
> + exit 1
> +fi
> +
> +btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
> +if [[ -z "$btf_optimized" ]]; then
> + echo "skip: no optimizations applied."
> + exit 2
> +fi
> +
> +btf_cmp=$btf_optimized
> +dwarf=$(pfunct --all $clang_true |grep "foo")
> +
> +test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
> +
> +if [[ "$btf_cmp" == "$dwarf" ]]; then
> + echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
> + exit 1
> +else
> + echo ""
> + echo " BTF: $btf_optimized"
> + echo " DWARF: $dwarf"
> +fi
> +echo "Ok"
> +exit 0
> diff --git a/tests/gcc_true_signatures.sh b/tests/true_signatures/gcc_true_signatures.sh
> similarity index 100%
> rename from tests/gcc_true_signatures.sh
> rename to tests/true_signatures/gcc_true_signatures.sh
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments
2026-03-05 22:55 ` [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments Yonghong Song
@ 2026-03-19 18:55 ` Alan Maguire
2026-03-20 5:00 ` Yonghong Song
0 siblings, 1 reply; 17+ messages in thread
From: Alan Maguire @ 2026-03-19 18:55 UTC (permalink / raw)
To: Yonghong Song, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On 05/03/2026 22:55, Yonghong Song wrote:
> For llvm dwarf, the dead argument may be in the middle of
> DW_TAG_subprogram. So we introduce skip_idx in order to
> match expected registers properly.
>
> For example:
> 0x00042897: DW_TAG_subprogram
> DW_AT_name ("create_dev")
> DW_AT_calling_convention (DW_CC_nocall)
> DW_AT_type (0x0002429a "int")
> ...
>
> 0x000428ab: DW_TAG_formal_parameter
> DW_AT_name ("name")
> DW_AT_type (0x000242ed "char *")
> ...
>
> 0x000428b5: DW_TAG_formal_parameter
> DW_AT_location (indexed (0x3f) loclist = 0x000027f8:
> [0xffffffff87681370, 0xffffffff8768137a): DW_OP_reg5 RDI
> [0xffffffff8768137a, 0xffffffff87681392): DW_OP_reg3 RBX
> [0xffffffff87681392, 0xffffffff876813ae): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
> DW_AT_name ("dev")
> DW_AT_type (0x00026859 "dev_t")
> ...
>
> With skip_idx, we can identify that the second original argument
> 'dev' becomes the first one after optimization.
>
> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
> ---
> dwarf_loader.c | 27 +++++++++++++++++++++++----
> 1 file changed, 23 insertions(+), 4 deletions(-)
>
> diff --git a/dwarf_loader.c b/dwarf_loader.c
> index 610b69e..1ced5e2 100644
> --- a/dwarf_loader.c
> +++ b/dwarf_loader.c
> @@ -1192,6 +1192,7 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
>
> struct func_info {
> bool signature_changed;
> + int skip_idx;
> };
>
> /* For DW_AT_location 'attr':
> @@ -1264,13 +1265,28 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
> if (parm != NULL) {
> bool has_const_value;
> Dwarf_Attribute attr;
> + int reg_idx;
>
> 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)
> + if (param_idx < 0)
> return parm;
> - if (cu->producer_clang && !info->signature_changed)
> + if (!cu->producer_clang && param_idx >= cu->nr_register_params)
> + return parm;
> + if (cu->producer_clang) {
> + if (!info->signature_changed)
> + return parm;
> + /* if true_signature is not enabled, mark parameter as
> + * unexpected_reg since there is a skipped parameter before.
> + */
> + if (!conf->true_signature && info->skip_idx) {
> + parm->unexpected_reg = 1;
> + return parm;
> + }
> + }
> + reg_idx = param_idx - info->skip_idx;
> + if (reg_idx >= cu->nr_register_params)
> return parm;
> /* Parameters which use DW_AT_abstract_origin to point at
> * the original parameter definition (with no name in the DIE)
> @@ -1309,7 +1325,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
> parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) != NULL;
>
> if (parm->has_loc) {
> - int expected_reg = cu->register_params[param_idx];
> + int expected_reg = cu->register_params[reg_idx];
> int actual_reg = parameter__reg(&attr, expected_reg);
>
> if (actual_reg < 0)
> @@ -1322,8 +1338,11 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
> * contents.
> */
> parm->unexpected_reg = 1;
> - } else if (has_const_value) {
> + } else if (!cu->producer_clang && has_const_value) {
> + parm->optimized = 1;
> + } else if (cu->producer_clang) {
> parm->optimized = 1;
> + info->skip_idx++;
> }
> }
>
In [1] (dwarf_loader/btf_encoder: Detect reordered parameters)
we detect reordered/missing parameters for gcc by comparing abstract origin to concrete representation
and mark any such functions by setting the reordered_parm bitfield in the encoder func state. In this
approach reordered encompasses both missing and actual reordering; in practice it's always the former, but
the DWARF representation was confusing because it had the actually-used parameters (with location info)
followed by the unused ones (without location info). Before the above commit we were treating these
function signatures incorrectly as valid leading to wrong functions signatures.
All of this is to say: should we mark with reordered_parm instead? The reason I suggest this is that
btf_encoder__save_func() has handling for skipping functions without locations with reordered_parm set;
maybe we could find a way to unify that across clang/gcc cases?
Now in the clang case, I guess reordered is a poor description; for gcc it really means "reordered as
compared to abstract origin". If we could find a better way to describe/encompass both cases that would
be ideal I think. The terminology used in the gcc true signature code isn't great today.
[1] https://git.kernel.org/pub/scm/devel/pahole/pahole.git/commit/?id=109e5e4554f663e5f12fb634bc54cceb710aec61
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 9/9] tests: Add a few clang true signature tests
2026-03-19 18:48 ` Alan Maguire
@ 2026-03-20 4:52 ` Yonghong Song
0 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-20 4:52 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On 3/19/26 11:48 AM, Alan Maguire wrote:
> On 05/03/2026 22:55, Yonghong Song wrote:
>> Three tests are added.
>>
>> Test 1: clang_parm_optimized.sh
>> BTF: char * foo(struct t * a, struct t * d);
>> DWARF: char * foo(struct t * a, int b, struct t * d);
>> where parameber 'b' is unused.
>>
>> Test 2: clang_parm_optimized_stack.sh
>> BTF: char * foo(struct t * a, struct t * d);
>> DWARF: char * foo(struct t * a, int b1, int b2, int b3, int b4, int b5, int b6, struct t * d);
>> where parameters 'b1' to 'b6' are unused.
>>
>> Test 3: clang_parm_aggregate.sh
>> BTF: long foo(long a__f1, struct t b, int i);
>> DWARF: long foo(struct t a, struct t b, int i);
>> where the 'struct t' definition is 'struct t { long f1; long f2; };', and
>> a.f2 is not used in the function.
>>
>> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
> tests look good but we need to ensure they are run by the ./tests script
> so that they will be run in CI. also take a look at Bruce's work to
> add common test functions [1], will probably simplify aspects of the tests.
Okay. The [1] is recently added. I will follow the convention in the next revision.
>
> Thanks!
>
> Alan
>
> [1] https://lore.kernel.org/dwarves/20260309205643.1985134-2-bruce.mcculloch@oracle.com/
>
>> ---
>> tests/true_signatures/clang_parm_aggregate.sh | 83 ++++++++++++++++
>> tests/true_signatures/clang_parm_optimized.sh | 95 +++++++++++++++++++
>> .../clang_parm_optimized_stack.sh | 95 +++++++++++++++++++
>> .../gcc_true_signatures.sh | 0
>> 4 files changed, 273 insertions(+)
>> create mode 100755 tests/true_signatures/clang_parm_aggregate.sh
>> create mode 100755 tests/true_signatures/clang_parm_optimized.sh
>> create mode 100755 tests/true_signatures/clang_parm_optimized_stack.sh
>> rename tests/{ => true_signatures}/gcc_true_signatures.sh (100%)
>>
>> diff --git a/tests/true_signatures/clang_parm_aggregate.sh b/tests/true_signatures/clang_parm_aggregate.sh
>> new file mode 100755
>> index 0000000..6d92701
>> --- /dev/null
>> +++ b/tests/true_signatures/clang_parm_aggregate.sh
>> @@ -0,0 +1,83 @@
>> +#!/bin/bash
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +outdir=
>> +
>> +fail()
>> +{
>> + # Do not remove test dir; might be useful for analysis
>> + trap - EXIT
>> + if [[ -d "$outdir" ]]; then
>> + echo "Test data is in $outdir"
>> + fi
>> + exit 1
>> +}
>> +
>> +cleanup()
>> +{
>> + rm ${outdir}/*
>> + rmdir $outdir
>> +}
>> +
>> +outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
>> +
>> +trap cleanup EXIT
>> +
>> +echo -n "Validation of BTF encoding of true_signatures: "
>> +
>> +clang_true="${outdir}/clang_true"
>> +CC=$(which clang 2>/dev/null)
>> +
>> +if [[ -z "$CC" ]]; then
>> + echo "skip: clang not available"
>> + exit 2
>> +fi
>> +
>> +cat > ${clang_true}.c << EOF
>> +struct t { long f1; long f2; };
>> +__attribute__((noinline)) static long foo(struct t a, struct t b, int i)
>> +{
>> + return a.f1 + b.f1 + b.f2 + i;
>> +}
>> +
>> +struct t p1, p2;
>> +int i;
>> +int main()
>> +{
>> + return (int)foo(p1, p2, i);
>> +}
>> +EOF
>> +
>> +CFLAGS="$CFLAGS -g -O2"
>> +${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
>> +if [[ $? -ne 0 ]]; then
>> + echo "Could not compile ${clang_true}.c" >& 2
>> + exit 1
>> +fi
>> +LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
>> +if [[ $? -ne 0 ]]; then
>> + echo "Could not encode BTF for $clang_true"
>> + exit 1
>> +fi
>> +
>> +btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
>> +if [[ -z "$btf_optimized" ]]; then
>> + echo "skip: no optimizations applied."
>> + exit 2
>> +fi
>> +
>> +btf_cmp=$btf_optimized
>> +dwarf=$(pfunct --all $clang_true |grep "foo")
>> +
>> +test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
>> +
>> +if [[ "$btf_cmp" == "$dwarf" ]]; then
>> + echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
>> + exit 1
>> +else
>> + echo ""
>> + echo " BTF: $btf_optimized"
>> + echo " DWARF: $dwarf"
>> +fi
>> +echo "Ok"
>> +exit 0
>> diff --git a/tests/true_signatures/clang_parm_optimized.sh b/tests/true_signatures/clang_parm_optimized.sh
>> new file mode 100755
>> index 0000000..3022e2b
>> --- /dev/null
>> +++ b/tests/true_signatures/clang_parm_optimized.sh
>> @@ -0,0 +1,95 @@
>> +#!/bin/bash
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +outdir=
>> +
>> +fail()
>> +{
>> + # Do not remove test dir; might be useful for analysis
>> + trap - EXIT
>> + if [[ -d "$outdir" ]]; then
>> + echo "Test data is in $outdir"
>> + fi
>> + exit 1
>> +}
>> +
>> +cleanup()
>> +{
>> + rm ${outdir}/*
>> + rmdir $outdir
>> +}
>> +
>> +outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
>> +
>> +trap cleanup EXIT
>> +
>> +echo -n "Validation of BTF encoding of true_signatures: "
>> +
>> +clang_true="${outdir}/clang_true"
>> +CC=$(which clang 2>/dev/null)
>> +
>> +if [[ -z "$CC" ]]; then
>> + echo "skip: clang not available"
>> + exit 2
>> +fi
>> +
>> +cat > ${clang_true}.c << EOF
>> +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);
>> +}
>> +EOF
>> +
>> +CFLAGS="$CFLAGS -g -O2"
>> +${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
>> +if [[ $? -ne 0 ]]; then
>> + echo "Could not compile ${clang_true}.c" >& 2
>> + exit 1
>> +fi
>> +LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
>> +if [[ $? -ne 0 ]]; then
>> + echo "Could not encode BTF for $clang_true"
>> + exit 1
>> +fi
>> +
>> +btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
>> +if [[ -z "$btf_optimized" ]]; then
>> + echo "skip: no optimizations applied."
>> + exit 2
>> +fi
>> +
>> +btf_cmp=$btf_optimized
>> +dwarf=$(pfunct --all $clang_true |grep "foo")
>> +
>> +test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
>> +
>> +if [[ "$btf_cmp" == "$dwarf" ]]; then
>> + echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
>> + exit 1
>> +else
>> + echo ""
>> + echo " BTF: $btf_optimized"
>> + echo " DWARF: $dwarf"
>> +fi
>> +echo "Ok"
>> +exit 0
>> diff --git a/tests/true_signatures/clang_parm_optimized_stack.sh b/tests/true_signatures/clang_parm_optimized_stack.sh
>> new file mode 100755
>> index 0000000..1abb96d
>> --- /dev/null
>> +++ b/tests/true_signatures/clang_parm_optimized_stack.sh
>> @@ -0,0 +1,95 @@
>> +#!/bin/bash
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +outdir=
>> +
>> +fail()
>> +{
>> + # Do not remove test dir; might be useful for analysis
>> + trap - EXIT
>> + if [[ -d "$outdir" ]]; then
>> + echo "Test data is in $outdir"
>> + fi
>> + exit 1
>> +}
>> +
>> +cleanup()
>> +{
>> + rm ${outdir}/*
>> + rmdir $outdir
>> +}
>> +
>> +outdir=$(mktemp -d /tmp/clang_true.sh.XXXXXX)
>> +
>> +trap cleanup EXIT
>> +
>> +echo -n "Validation of BTF encoding of true_signatures: "
>> +
>> +clang_true="${outdir}/clang_true"
>> +CC=$(which clang 2>/dev/null)
>> +
>> +if [[ -z "$CC" ]]; then
>> + echo "skip: clang not available"
>> + exit 2
>> +fi
>> +
>> +cat > ${clang_true}.c << EOF
>> +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 b1, int b2, int b3, int b4, int b5, int b6, struct t *d)
>> +{
>> + return tar(a, d);
>> +}
>> +
>> +__attribute__((noinline)) char *bar(struct t *a, struct t *d)
>> +{
>> + return foo(a, 1, 2, 3, 4, 5, 6, d);
>> +}
>> +
>> +struct t p1, p2;
>> +int main()
>> +{
>> + return !!bar(&p1, &p2);
>> +}
>> +EOF
>> +
>> +CFLAGS="$CFLAGS -g -O2"
>> +${CC} ${CFLAGS} -o $clang_true ${clang_true}.c
>> +if [[ $? -ne 0 ]]; then
>> + echo "Could not compile ${clang_true}.c" >& 2
>> + exit 1
>> +fi
>> +LLVM_OBJCOPY=objcopy pahole -J --btf_features=+true_signature $clang_true
>> +if [[ $? -ne 0 ]]; then
>> + echo "Could not encode BTF for $clang_true"
>> + exit 1
>> +fi
>> +
>> +btf_optimized=$(pfunct --all --format_path=btf $clang_true |grep "foo")
>> +if [[ -z "$btf_optimized" ]]; then
>> + echo "skip: no optimizations applied."
>> + exit 2
>> +fi
>> +
>> +btf_cmp=$btf_optimized
>> +dwarf=$(pfunct --all $clang_true |grep "foo")
>> +
>> +test -n "$VERBOSE" && printf "\nBTF: $btf_optimized DWARF: $dwarf \n"
>> +
>> +if [[ "$btf_cmp" == "$dwarf" ]]; then
>> + echo "BTF and DWARF signatures should be different and they are not: BTF: $btf_optimized ; DWARF $dwarf"
>> + exit 1
>> +else
>> + echo ""
>> + echo " BTF: $btf_optimized"
>> + echo " DWARF: $dwarf"
>> +fi
>> +echo "Ok"
>> +exit 0
>> diff --git a/tests/gcc_true_signatures.sh b/tests/true_signatures/gcc_true_signatures.sh
>> similarity index 100%
>> rename from tests/gcc_true_signatures.sh
>> rename to tests/true_signatures/gcc_true_signatures.sh
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments
2026-03-19 18:55 ` Alan Maguire
@ 2026-03-20 5:00 ` Yonghong Song
2026-03-20 19:20 ` Yonghong Song
0 siblings, 1 reply; 17+ messages in thread
From: Yonghong Song @ 2026-03-20 5:00 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On 3/19/26 11:55 AM, Alan Maguire wrote:
> On 05/03/2026 22:55, Yonghong Song wrote:
>> For llvm dwarf, the dead argument may be in the middle of
>> DW_TAG_subprogram. So we introduce skip_idx in order to
>> match expected registers properly.
>>
>> For example:
>> 0x00042897: DW_TAG_subprogram
>> DW_AT_name ("create_dev")
>> DW_AT_calling_convention (DW_CC_nocall)
>> DW_AT_type (0x0002429a "int")
>> ...
>>
>> 0x000428ab: DW_TAG_formal_parameter
>> DW_AT_name ("name")
>> DW_AT_type (0x000242ed "char *")
>> ...
>>
>> 0x000428b5: DW_TAG_formal_parameter
>> DW_AT_location (indexed (0x3f) loclist = 0x000027f8:
>> [0xffffffff87681370, 0xffffffff8768137a): DW_OP_reg5 RDI
>> [0xffffffff8768137a, 0xffffffff87681392): DW_OP_reg3 RBX
>> [0xffffffff87681392, 0xffffffff876813ae): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>> DW_AT_name ("dev")
>> DW_AT_type (0x00026859 "dev_t")
>> ...
>>
>> With skip_idx, we can identify that the second original argument
>> 'dev' becomes the first one after optimization.
>>
>> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
>> ---
>> dwarf_loader.c | 27 +++++++++++++++++++++++----
>> 1 file changed, 23 insertions(+), 4 deletions(-)
>>
>> diff --git a/dwarf_loader.c b/dwarf_loader.c
>> index 610b69e..1ced5e2 100644
>> --- a/dwarf_loader.c
>> +++ b/dwarf_loader.c
>> @@ -1192,6 +1192,7 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
>>
>> struct func_info {
>> bool signature_changed;
>> + int skip_idx;
>> };
>>
>> /* For DW_AT_location 'attr':
>> @@ -1264,13 +1265,28 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> if (parm != NULL) {
>> bool has_const_value;
>> Dwarf_Attribute attr;
>> + int reg_idx;
>>
>> 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)
>> + if (param_idx < 0)
>> return parm;
>> - if (cu->producer_clang && !info->signature_changed)
>> + if (!cu->producer_clang && param_idx >= cu->nr_register_params)
>> + return parm;
>> + if (cu->producer_clang) {
>> + if (!info->signature_changed)
>> + return parm;
>> + /* if true_signature is not enabled, mark parameter as
>> + * unexpected_reg since there is a skipped parameter before.
>> + */
>> + if (!conf->true_signature && info->skip_idx) {
>> + parm->unexpected_reg = 1;
>> + return parm;
>> + }
>> + }
>> + reg_idx = param_idx - info->skip_idx;
>> + if (reg_idx >= cu->nr_register_params)
>> return parm;
>> /* Parameters which use DW_AT_abstract_origin to point at
>> * the original parameter definition (with no name in the DIE)
>> @@ -1309,7 +1325,7 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) != NULL;
>>
>> if (parm->has_loc) {
>> - int expected_reg = cu->register_params[param_idx];
>> + int expected_reg = cu->register_params[reg_idx];
>> int actual_reg = parameter__reg(&attr, expected_reg);
>>
>> if (actual_reg < 0)
>> @@ -1322,8 +1338,11 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> * contents.
>> */
>> parm->unexpected_reg = 1;
>> - } else if (has_const_value) {
>> + } else if (!cu->producer_clang && has_const_value) {
>> + parm->optimized = 1;
>> + } else if (cu->producer_clang) {
>> parm->optimized = 1;
>> + info->skip_idx++;
>> }
>> }
>>
> In [1] (dwarf_loader/btf_encoder: Detect reordered parameters)
>
> we detect reordered/missing parameters for gcc by comparing abstract origin to concrete representation
> and mark any such functions by setting the reordered_parm bitfield in the encoder func state. In this
> approach reordered encompasses both missing and actual reordering; in practice it's always the former, but
> the DWARF representation was confusing because it had the actually-used parameters (with location info)
> followed by the unused ones (without location info). Before the above commit we were treating these
> function signatures incorrectly as valid leading to wrong functions signatures.
>
> All of this is to say: should we mark with reordered_parm instead? The reason I suggest this is that
> btf_encoder__save_func() has handling for skipping functions without locations with reordered_parm set;
> maybe we could find a way to unify that across clang/gcc cases?
For v2, I focused on clang side. I have not studied such unification yet. I think it is possible
since ultimately both clang and gcc tries to skip some parameters.
>
> Now in the clang case, I guess reordered is a poor description; for gcc it really means "reordered as
> compared to abstract origin". If we could find a better way to describe/encompass both cases that would
> be ideal I think. The terminology used in the gcc true signature code isn't great today.
Agree that we want *common* names to represent both gcc and clang for skipped parameters.
BTW, please review v2 instead:
https://lore.kernel.org/bpf/20260309153215.1917033-1-yonghong.song@linux.dev/
Thanks!
>
> [1] https://git.kernel.org/pub/scm/devel/pahole/pahole.git/commit/?id=109e5e4554f663e5f12fb634bc54cceb710aec61
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments
2026-03-20 5:00 ` Yonghong Song
@ 2026-03-20 19:20 ` Yonghong Song
0 siblings, 0 replies; 17+ messages in thread
From: Yonghong Song @ 2026-03-20 19:20 UTC (permalink / raw)
To: Alan Maguire, Arnaldo Carvalho de Melo, dwarves
Cc: Alexei Starovoitov, Andrii Nakryiko, bpf, kernel-team
On 3/19/26 10:00 PM, Yonghong Song wrote:
>
>
> On 3/19/26 11:55 AM, Alan Maguire wrote:
>> On 05/03/2026 22:55, Yonghong Song wrote:
>>> For llvm dwarf, the dead argument may be in the middle of
>>> DW_TAG_subprogram. So we introduce skip_idx in order to
>>> match expected registers properly.
>>>
>>> For example:
>>> 0x00042897: DW_TAG_subprogram
>>> DW_AT_name ("create_dev")
>>> DW_AT_calling_convention (DW_CC_nocall)
>>> DW_AT_type (0x0002429a "int")
>>> ...
>>>
>>> 0x000428ab: DW_TAG_formal_parameter
>>> DW_AT_name ("name")
>>> DW_AT_type (0x000242ed "char *")
>>> ...
>>>
>>> 0x000428b5: DW_TAG_formal_parameter
>>> DW_AT_location (indexed (0x3f) loclist =
>>> 0x000027f8:
>>> [0xffffffff87681370, 0xffffffff8768137a):
>>> DW_OP_reg5 RDI
>>> [0xffffffff8768137a, 0xffffffff87681392):
>>> DW_OP_reg3 RBX
>>> [0xffffffff87681392, 0xffffffff876813ae):
>>> DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value)
>>> DW_AT_name ("dev")
>>> DW_AT_type (0x00026859 "dev_t")
>>> ...
>>>
>>> With skip_idx, we can identify that the second original argument
>>> 'dev' becomes the first one after optimization.
>>>
>>> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
>>> ---
>>> dwarf_loader.c | 27 +++++++++++++++++++++++----
>>> 1 file changed, 23 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/dwarf_loader.c b/dwarf_loader.c
>>> index 610b69e..1ced5e2 100644
>>> --- a/dwarf_loader.c
>>> +++ b/dwarf_loader.c
>>> @@ -1192,6 +1192,7 @@ static ptrdiff_t
>>> __dwarf_getlocations(Dwarf_Attribute *attr,
>>> struct func_info {
>>> bool signature_changed;
>>> + int skip_idx;
>>> };
>>> /* For DW_AT_location 'attr':
>>> @@ -1264,13 +1265,28 @@ static struct parameter
>>> *parameter__new(Dwarf_Die *die, struct cu *cu,
>>> if (parm != NULL) {
>>> bool has_const_value;
>>> Dwarf_Attribute attr;
>>> + int reg_idx;
>>> 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)
>>> + if (param_idx < 0)
>>> return parm;
>>> - if (cu->producer_clang && !info->signature_changed)
>>> + if (!cu->producer_clang && param_idx >=
>>> cu->nr_register_params)
>>> + return parm;
>>> + if (cu->producer_clang) {
>>> + if (!info->signature_changed)
>>> + return parm;
>>> + /* if true_signature is not enabled, mark parameter as
>>> + * unexpected_reg since there is a skipped parameter
>>> before.
>>> + */
>>> + if (!conf->true_signature && info->skip_idx) {
>>> + parm->unexpected_reg = 1;
>>> + return parm;
>>> + }
>>> + }
>>> + reg_idx = param_idx - info->skip_idx;
>>> + if (reg_idx >= cu->nr_register_params)
>>> return parm;
>>> /* Parameters which use DW_AT_abstract_origin to point at
>>> * the original parameter definition (with no name in the
>>> DIE)
>>> @@ -1309,7 +1325,7 @@ static struct parameter
>>> *parameter__new(Dwarf_Die *die, struct cu *cu,
>>> parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) !=
>>> NULL;
>>> if (parm->has_loc) {
>>> - int expected_reg = cu->register_params[param_idx];
>>> + int expected_reg = cu->register_params[reg_idx];
>>> int actual_reg = parameter__reg(&attr, expected_reg);
>>> if (actual_reg < 0)
>>> @@ -1322,8 +1338,11 @@ static struct parameter
>>> *parameter__new(Dwarf_Die *die, struct cu *cu,
>>> * contents.
>>> */
>>> parm->unexpected_reg = 1;
>>> - } else if (has_const_value) {
>>> + } else if (!cu->producer_clang && has_const_value) {
>>> + parm->optimized = 1;
>>> + } else if (cu->producer_clang) {
>>> parm->optimized = 1;
>>> + info->skip_idx++;
>>> }
>>> }
>> In [1] (dwarf_loader/btf_encoder: Detect reordered parameters)
>>
>> we detect reordered/missing parameters for gcc by comparing abstract
>> origin to concrete representation
>> and mark any such functions by setting the reordered_parm bitfield in
>> the encoder func state. In this
>> approach reordered encompasses both missing and actual reordering; in
>> practice it's always the former, but
>> the DWARF representation was confusing because it had the
>> actually-used parameters (with location info)
>> followed by the unused ones (without location info). Before the above
>> commit we were treating these
>> function signatures incorrectly as valid leading to wrong functions
>> signatures.
>>
>> All of this is to say: should we mark with reordered_parm instead?
>> The reason I suggest this is that
>> btf_encoder__save_func() has handling for skipping functions without
>> locations with reordered_parm set;
>> maybe we could find a way to unify that across clang/gcc cases?
>
> For v2, I focused on clang side. I have not studied such unification
> yet. I think it is possible
> since ultimately both clang and gcc tries to skip some parameters.
>
>>
>> Now in the clang case, I guess reordered is a poor description; for
>> gcc it really means "reordered as
>> compared to abstract origin". If we could find a better way to
>> describe/encompass both cases that would
>> be ideal I think. The terminology used in the gcc true signature code
>> isn't great today.
>
> Agree that we want *common* names to represent both gcc and clang for
> skipped parameters.
> BTW, please review v2 instead:
> https://lore.kernel.org/bpf/20260309153215.1917033-1-yonghong.song@linux.dev/
>
Jiri, Alan,
Thanks for the previous review. I just updated with v3:
https://lore.kernel.org/bpf/20260320190917.1970524-1-yonghong.song@linux.dev/
which addressed the issue to simplify getting cu->producer_clang, rewrite tests
based on latest convention, and try to use info->signature_changed instead of cu->producer_clang
to avoid clear separation between gcc and clang.
Please review v3.
Thanks!
>
> Thanks!
>
>>
>> [1]
>> https://git.kernel.org/pub/scm/devel/pahole/pahole.git/commit/?id=109e5e4554f663e5f12fb634bc54cceb710aec61
>
>
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-03-20 19:21 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-05 22:54 [PATCH dwarves 0/9] pahole: Encode true signatures in kernel BTF Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 1/9] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr Yonghong Song
2026-03-19 12:32 ` Jiri Olsa
2026-03-19 17:31 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 2/9] dwarf_loader: Handle signatures with dead arguments Yonghong Song
2026-03-19 18:55 ` Alan Maguire
2026-03-20 5:00 ` Yonghong Song
2026-03-20 19:20 ` Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 3/9] dwarf_loader: Refactor initial ret -1 to be macro PARM_DEFAULT_FAIL Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 4/9] dwarf_laoder: Handle locations with DW_OP_fbreg Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 5/9] dwarf_loader: Change exprlen checking condition in parameter__reg() Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 6/9] dwarf_loader: Detect optimized parameters with locations having constant values Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 7/9] dwarf_loader: Handle expression lists Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 8/9] btf_encoder: Handle optimized parameter properly Yonghong Song
2026-03-05 22:55 ` [PATCH dwarves 9/9] tests: Add a few clang true signature tests Yonghong Song
2026-03-19 18:48 ` Alan Maguire
2026-03-20 4:52 ` Yonghong Song
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox