* [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF
@ 2026-09-25 18:51 Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions Alan Maguire
` (8 more replies)
0 siblings, 9 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Support encoding information about inline functions using BTF,
in particular the libbpf interfaces added in [1].
When the "inline" BTF feature keywords is added, we also process
and encode BTF_KIND_LOC_PROTO, PARAM and LOCSEC info for inline
functions as part of CU traversal. Patches 1-3 add dwarf_loader
support for this; patch 4 then uses libbpf interfaces to add
inline info to the generated BTF. Patch 5 adds support to
the btf_loader to support recognition of inline functions in
BTF, supporting pfunct rendering of a function as inline.
Patch 6 verifies that encoding and display of inline functions
succeeds.
Inline support is gated by presence of BTF location-related
functions in libbpf, so the easiest path for testing is
to install the libbpf built from the series at [1], and
when building pahole to run
cmake -DLIBBPF_EMBEDDED=off ..
as this ensures you will run with the updated libbpf with
those interfaces (otherwise pahole auto-detects the lack
of the relevant interfaces). To verify all is working,
pahole --supported_btf_features
should show "inline".
Patches 1-3 prep the dwarf_loader to collect inline info;
patch 4 converts it to BTF; patch 5 adds the inline annotation
to pfunct output via BTF inline info; patch 6 creates an
inline site representation in the btf loader for use in patch
7 for the --inline_sites display; patch 8 tests inline display
via pfunct and patch 9 updates docs.
$ pahole -J --btf_features=default,inline \
--btf_encode_detached=/tmp/vmlinux+inline vmlinux
$ pfunct --inline_sites --elf=vmlinux /tmp/vmlinux+inline
0xffffffff8120004a [trace_initcall_start_cb+0xa, .text +0x20004a] task_pid_nr(struct task_struct * tsk [%rax])
0xffffffff8120005f [trace_initcall_start_cb+0x1f, .text +0x20005f] get_current(void)
0xffffffff81200090 [trace_initcall_level+0x0, .text +0x200090] arch_static_branch(const struct static_key * key [addr 0xffffffff83e275e8], const bool branch [const 0])
0xffffffff81200097 [trace_initcall_level+0x7, .text +0x200097] __do_trace_initcall_level(const char * level [%rdi])
...
The --elf uses ELF sections/arch info from vmlinux to render
registers, absolute addresses from base/section relative.
The --running option can be used on inline BTF and the running
kernel (kallsyms) is used for address resolution; this ensures
that kASLR is taken into account. However if kptr_restrict is
active /proc/kallsyms will show address 0 for kernel functions,
so pfunct should be run with privileges in such cases if absolute
address representations are needed.
The kbuild infrastructure will split inline info out into a
separate split BTF object, but pahole encodes inline info
together in the same BTF object here; this avoids the need to
invoke it twice during kernel builds.
[1] https://lore.kernel.org/bpf/20260924111428.75957-1-alan.maguire@oracle.com/
Alan Maguire (9):
dwarf_loader: Add parameter list to inlined expansions
dwarf_loader: Resolve inline expansion names during recoding
dwarf_loader: Collect inline expansion location data
pahole: Support inline BTF location encoding
btf_loader: Add "inline" annotation for inlined functions
btf_loader: Record inline sites in CU for BTF
pfunct: Print inline site information
tests: Validate inline BTF encoding
man-pages: Document --inline_sites option
btf_encoder.c | 391 ++++++++++++++++++++----
btf_loader.c | 96 +++++-
dwarf_loader.c | 580 +++++++++++++++++++++++++----------
dwarves.c | 35 +++
dwarves.h | 69 +++++
man-pages/pfunct.1 | 74 ++++-
pahole.c | 12 +
pfunct.c | 718 ++++++++++++++++++++++++++++++++++++++++++--
tests/btf_inline.sh | 147 +++++++++
9 files changed, 1872 insertions(+), 250 deletions(-)
create mode 100755 tests/btf_inline.sh
--
2.43.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-30 15:45 ` Jiri Olsa
2026-09-25 18:51 ` [PATCH v3 dwarves 2/9] dwarf_loader: Resolve inline expansion names during recoding Alan Maguire
` (7 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
DW_TAG_formal_parameter children of DW_TAG_inlined_subroutine are
currently skipped, so callers cannot iterate parameters that belong to a
specific inline expansion. Store those parameters directly on struct
inline_expansion instead of leaving them in the surrounding lexical block.
Add an inline_expansion parameter list, populate it while processing
inline-subroutine children, and include it in tag cleanup and
cu__for_all_tags() traversal. Recode inline-expansion parameter types via
the same parameter recoding path used for function prototypes.
Signed-off-by: Thierry Treyer <ttreyer@meta.com>
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
dwarf_loader.c | 211 ++++++++++++++++++++++++++-----------------------
dwarves.c | 34 ++++++++
dwarves.h | 6 ++
3 files changed, 153 insertions(+), 98 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 1e5363a..2165621 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -2022,6 +2022,8 @@ static struct inline_expansion *inline_expansion__new(Dwarf_Die *die, struct cu
dwarf_tag__set_attr_type(dtag, DWARF_TAG__REF_TYPE, die, DW_AT_abstract_origin, cu);
exp->ip.addr = 0;
exp->high_pc = 0;
+ exp->nr_parms = 0;
+ INIT_LIST_HEAD(&exp->parms);
if (!cu->has_addr_info)
goto out;
@@ -2559,6 +2561,7 @@ static struct tag *die__create_new_string_type(Dwarf_Die *die, struct cu *cu)
static struct tag *die__create_new_parameter(Dwarf_Die *die,
struct ftype *ftype,
struct lexblock *lexblock,
+ struct inline_expansion *exp,
struct cu *cu, struct conf_load *conf,
int param_idx)
{
@@ -2575,7 +2578,7 @@ static struct tag *die__create_new_parameter(Dwarf_Die *die,
if (add_gnu_annotation_chain(die, param_idx, conf, &(tag__function(&ftype->tag)->annots)))
return NULL;
}
- } else {
+ } else if (exp == NULL) {
/*
* DW_TAG_formal_parameters on a non DW_TAG_subprogram nor
* DW_TAG_subroutine_type tag happens sometimes, likely due to
@@ -2587,6 +2590,7 @@ static struct tag *die__create_new_parameter(Dwarf_Die *die,
*/
lexblock__add_tag(lexblock, &parm->tag);
}
+ /* Inline-expansion parameters are linked after CU-table registration. */
return &parm->tag;
}
@@ -2656,7 +2660,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, NULL, cu, conf, -1);
break;
case DW_TAG_unspecified_parameters:
ftype->unspec_parms = 1;
@@ -2946,10 +2950,14 @@ static struct tag *die__create_new_inline_expansion(Dwarf_Die *die,
struct lexblock *lexblock,
struct cu *cu, struct conf_load *conf);
-static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblock, struct cu *cu, struct conf_load *conf)
+static int die__process_inline_expansion(Dwarf_Die *die,
+ struct inline_expansion *exp,
+ struct lexblock *lexblock,
+ struct cu *cu, struct conf_load *conf)
{
Dwarf_Die child;
struct tag *tag;
+ int parm_idx = 0;
if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0)
return 0;
@@ -2957,6 +2965,7 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
die = &child;
do {
uint32_t id;
+ bool add_to_inline_expansion = false;
switch (dwarf_tag(die)) {
case DW_TAG_call_site:
@@ -2975,13 +2984,10 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
goto out_enomem;
continue;
case DW_TAG_formal_parameter:
- /*
- * Inline expansions can have their own formal
- * parameter children duplicating the abstract
- * origin's parameters. These are not needed
- * for type reconstruction — skip them.
- */
- continue;
+ tag = die__create_new_parameter(die, NULL, lexblock, exp,
+ cu, conf, parm_idx++);
+ add_to_inline_expansion = true;
+ break;
case DW_TAG_inlined_subroutine:
tag = die__create_new_inline_expansion(die, lexblock, cu, conf);
break;
@@ -3010,6 +3016,8 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
if (cu__table_add_tag(cu, tag, &id) < 0)
goto out_delete_tag;
+ if (add_to_inline_expansion)
+ inline_expansion__add_parameter(exp, tag__parameter(tag));
hash:
cu__hash(cu, tag);
struct dwarf_tag *dtag = tag__dwarf(tag);
@@ -3032,8 +3040,8 @@ static struct tag *die__create_new_inline_expansion(Dwarf_Die *die,
if (exp == NULL)
return NULL;
- if (die__process_inline_expansion(die, lexblock, cu, conf) != 0) {
- tag__free(&exp->ip.tag, cu);
+ if (die__process_inline_expansion(die, exp, lexblock, cu, conf) != 0) {
+ tag__delete(&exp->ip.tag, cu);
return NULL;
}
@@ -3115,7 +3123,8 @@ 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, NULL,
+ cu, conf, param_idx++);
break;
case DW_TAG_variable:
tag = die__create_new_variable(die, cu, conf, 0);
@@ -3562,75 +3571,103 @@ static void __tag__print_abstract_origin_not_found(struct tag *tag,
#define tag__print_abstract_origin_not_found(tag) \
__tag__print_abstract_origin_not_found(tag, __func__, __LINE__)
-static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
+static void parameter__share_state_with_abstract_origin(struct parameter *parm,
+ struct parameter *oparm)
+{
+ if (parm->has_loc)
+ oparm->has_loc = parm->has_loc;
+ if (parm->has_const_value)
+ oparm->has_const_value = parm->has_const_value;
+ if (parm->loc_const_value)
+ oparm->loc_const_value = parm->loc_const_value;
+ if (parm->loc_stack)
+ oparm->loc_stack = parm->loc_stack;
+ if (parm->loc_reg != PARAMETER_UNKNOWN_REG)
+ oparm->loc_reg = parm->loc_reg;
+ if (parm->type_byte_size != 0)
+ oparm->type_byte_size = parm->type_byte_size;
+ if (parm->passed_in_memory)
+ oparm->passed_in_memory = parm->passed_in_memory;
+ oparm->first_reg_fields |= parm->first_reg_fields;
+ oparm->second_reg_fields |= parm->second_reg_fields;
+ if (parm->true_sig_member_name && !oparm->true_sig_member_name) {
+ oparm->true_sig_member_name = parm->true_sig_member_name;
+ oparm->true_sig_type = parm->true_sig_type;
+ oparm->true_sig_type_from_types = parm->true_sig_type_from_types;
+ }
+ if (parm->optimized)
+ oparm->optimized = parm->optimized;
+ if (parm->unexpected_reg)
+ oparm->unexpected_reg = parm->unexpected_reg;
+}
+
+static void parameter__recode_dwarf_type(struct parameter *parm, struct cu *cu,
+ struct ftype *ftype)
{
- struct parameter *pos;
struct dwarf_cu *dcu = cu->priv;
- struct ftype *type = tag__ftype(tag);
-
- ftype__for_each_parameter(type, pos) {
- struct dwarf_tag *dpos = tag__dwarf(&pos->tag);
- struct parameter *opos;
- struct dwarf_tag *dtype;
+ struct dwarf_tag *dparm = tag__dwarf(&parm->tag);
+ struct dwarf_tag *dtype;
- if (dpos->type == 0) {
- if (dpos->abstract_origin == 0) {
- /* Function without parameters */
- pos->tag.type = 0;
- continue;
- }
- dtype = dwarf_cu__find_tag_by_ref(dcu, dpos, abstract_origin);
- if (dtype == NULL) {
- tag__print_abstract_origin_not_found(&pos->tag);
- continue;
- }
- opos = tag__parameter(dtag__tag(dtype));
- pos->name = opos->name;
- if (pos->idx != opos->idx)
- type->reordered_parm = 1;
- pos->tag.type = dtag__tag(dtype)->type;
- /* share location information between parameter and
- * abstract origin; if neither have location, we will
- * mark the parameter as optimized out. Also share
- * info regarding unexpected register use for
- * parameters.
- */
- if (pos->has_loc)
- opos->has_loc = pos->has_loc;
- if (pos->has_const_value)
- opos->has_const_value = pos->has_const_value;
- if (pos->loc_const_value)
- opos->loc_const_value = pos->loc_const_value;
- if (pos->loc_stack)
- opos->loc_stack = pos->loc_stack;
- if (pos->loc_reg != PARAMETER_UNKNOWN_REG)
- opos->loc_reg = pos->loc_reg;
- if (pos->type_byte_size != 0)
- opos->type_byte_size = pos->type_byte_size;
- if (pos->passed_in_memory)
- opos->passed_in_memory = pos->passed_in_memory;
- opos->first_reg_fields |= pos->first_reg_fields;
- opos->second_reg_fields |= pos->second_reg_fields;
- if (pos->true_sig_member_name && !opos->true_sig_member_name) {
- opos->true_sig_member_name = pos->true_sig_member_name;
- opos->true_sig_type = pos->true_sig_type;
- opos->true_sig_type_from_types = pos->true_sig_type_from_types;
- }
+ if (dparm->type == 0) {
+ struct parameter *oparm;
- if (pos->optimized)
- opos->optimized = pos->optimized;
- if (pos->unexpected_reg)
- opos->unexpected_reg = pos->unexpected_reg;
- continue;
+ if (dparm->abstract_origin == 0) {
+ parm->tag.type = 0;
+ return;
}
- dtype = dwarf_cu__find_type_by_ref(dcu, dpos, type);
+ dtype = dwarf_cu__find_tag_by_ref(dcu, dparm, abstract_origin);
if (dtype == NULL) {
- tag__print_type_not_found(&pos->tag);
- continue;
+ tag__print_abstract_origin_not_found(&parm->tag);
+ return;
}
- pos->tag.type = dtype->small_id;
+
+ oparm = tag__parameter(dtag__tag(dtype));
+ parm->name = oparm->name;
+ if (ftype != NULL && parm->idx != oparm->idx)
+ ftype->reordered_parm = 1;
+ parm->tag.type = dtag__tag(dtype)->type;
+ if (ftype != NULL)
+ parameter__share_state_with_abstract_origin(parm, oparm);
+ return;
+ }
+
+ dtype = dwarf_cu__find_type_by_ref(dcu, dparm, type);
+ if (dtype == NULL) {
+ tag__print_type_not_found(&parm->tag);
+ return;
+ }
+ parm->tag.type = dtype->small_id;
+}
+
+static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
+{
+ struct ftype *type = tag__ftype(tag);
+ struct parameter *pos;
+
+ ftype__for_each_parameter(type, pos)
+ parameter__recode_dwarf_type(pos, cu, type);
+}
+
+static void inline_expansion__recode_dwarf_types(struct tag *tag, struct cu *cu)
+{
+ struct dwarf_cu *dcu = cu->priv;
+ struct dwarf_tag *dtag = tag__dwarf(tag);
+ struct dwarf_tag *dtype;
+ struct tag *pos;
+
+ /* The inline expansion's abstract origin is stored as its type ref. */
+ dtype = dwarf_cu__find_tag_by_ref(dcu, dtag, type);
+ if (dtype == NULL) {
+ tag__print_type_not_found(tag);
+ tag__check_pruned_alt_ref(dcu, dtag->type, dtag->from_alt.type);
+ return;
}
+
+ ftype__recode_dwarf_types(dtag__tag(dtype), cu);
+ list_for_each_entry(pos, &tag__inline_expansion(tag)->parms, node)
+ parameter__recode_dwarf_type(tag__parameter(pos), cu, NULL);
+ tag->type = dtype->small_id;
}
static struct parameter *ftype__next_parameter(struct ftype *ftype, struct parameter *parm)
@@ -3885,23 +3922,7 @@ static void lexblock__recode_dwarf_types(struct lexblock *tag, struct cu *cu)
lexblock__recode_dwarf_types(tag__lexblock(pos), cu);
continue;
case DW_TAG_inlined_subroutine:
- if (dpos->type != 0)
- dtype = dwarf_cu__find_tag_by_ref(dcu, dpos, type);
- else
- dtype = dwarf_cu__find_tag_by_ref(dcu, dpos, abstract_origin);
- if (dtype == NULL) {
- if (dpos->type != 0) {
- tag__print_type_not_found(pos);
- tag__check_pruned_alt_ref(dcu, dpos->type,
- dpos->from_alt.type);
- } else {
- tag__print_abstract_origin_not_found(pos);
- tag__check_pruned_alt_ref(dcu, dpos->abstract_origin,
- dpos->from_alt.abstract_origin);
- }
- continue;
- }
- ftype__recode_dwarf_types(dtag__tag(dtype), cu);
+ inline_expansion__recode_dwarf_types(pos, cu);
continue;
case DW_TAG_formal_parameter:
@@ -4081,15 +4102,9 @@ static int tag__recode_dwarf_type(struct tag *tag, struct cu *cu)
case DW_TAG_namespace:
return namespace__recode_dwarf_types(tag, cu);
- /*
- * DW_TAG_inlined_subroutine uses DW_AT_abstract_origin to
- * reference the out-of-line subprogram. inline_expansion__new()
- * stores this in dtag->type (not dtag->abstract_origin) via
- * dwarf_tag__set_attr_type() with DW_AT_abstract_origin.
- * For dwz binaries, the target subprogram lives in the alt file,
- * so dtag->from_alt.type drives the lookup to dcu->alt->hash_tags.
- */
case DW_TAG_inlined_subroutine:
+ inline_expansion__recode_dwarf_types(tag, cu);
+ return 0;
case DW_TAG_imported_module:
dtype = dwarf_cu__find_tag_by_ref(cu->priv, dtag, type);
goto check_type;
diff --git a/dwarves.c b/dwarves.c
index 2164030..2bb5c18 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -203,6 +203,27 @@ void formal_parameter_pack__delete(struct formal_parameter_pack *pack, struct cu
cu__tag_free(cu, &pack->tag);
}
+static void inline_expansion__delete_tags(struct inline_expansion *exp,
+ struct cu *cu)
+{
+ struct tag *pos, *n;
+
+ list_for_each_entry_safe_reverse(pos, n, &exp->parms, node) {
+ list_del_init(&pos->node);
+ tag__delete(pos, cu);
+ }
+}
+
+static void inline_expansion__delete(struct inline_expansion *exp,
+ struct cu *cu)
+{
+ if (exp == NULL)
+ return;
+
+ inline_expansion__delete_tags(exp, cu);
+ cu__tag_free(cu, &exp->ip.tag);
+}
+
void tag__delete(struct tag *tag, struct cu *cu)
{
if (tag == NULL)
@@ -225,6 +246,8 @@ void tag__delete(struct tag *tag, struct cu *cu)
ftype__delete(tag__ftype(tag), cu); break;
case DW_TAG_subprogram:
function__delete(tag__function(tag), cu); break;
+ case DW_TAG_inlined_subroutine:
+ inline_expansion__delete(tag__inline_expansion(tag), cu); break;
case DW_TAG_lexical_block:
lexblock__delete(tag__lexblock(tag), cu); break;
case DW_TAG_GNU_template_parameter_pack:
@@ -1476,6 +1499,13 @@ void lexblock__add_tag(struct lexblock *block, struct tag *tag)
list_add_tail(&tag->node, &block->tags);
}
+void inline_expansion__add_parameter(struct inline_expansion *exp,
+ struct parameter *parm)
+{
+ ++exp->nr_parms;
+ list_add_tail(&parm->tag.node, &exp->parms);
+}
+
void lexblock__add_inline_expansion(struct lexblock *block,
struct inline_expansion *exp)
{
@@ -2059,6 +2089,10 @@ static int list__for_all_tags(struct list_head *list, struct cu *cu,
if (list__for_all_tags(&tag__lexblock(pos)->tags,
cu, iterator, cookie))
return 1;
+ } else if (pos->tag == DW_TAG_inlined_subroutine) {
+ if (list__for_all_tags(&tag__inline_expansion(pos)->parms,
+ cu, iterator, cookie))
+ return 1;
}
if (iterator(pos, cu, cookie))
diff --git a/dwarves.h b/dwarves.h
index f3453ed..f620db8 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -825,6 +825,8 @@ struct inline_expansion {
struct ip_tag ip;
size_t size;
uint64_t high_pc;
+ struct list_head parms;
+ uint16_t nr_parms;
};
static inline struct inline_expansion *
@@ -833,6 +835,10 @@ static inline struct inline_expansion *
return (struct inline_expansion *)tag;
}
+struct parameter;
+void inline_expansion__add_parameter(struct inline_expansion *exp,
+ struct parameter *parm);
+
struct label {
struct ip_tag ip;
const char *name;
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 2/9] dwarf_loader: Resolve inline expansion names during recoding
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 3/9] dwarf_loader: Collect inline expansion location data Alan Maguire
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Add a name field to inline expansions and populate it from the resolved
origin function while recoding DWARF tags.
The origin DIE may not carry a direct DW_AT_name; normal function
recoding already handles names recovered through DW_AT_specification or
DW_AT_abstract_origin. Factor that lookup into a helper and use it for
inline expansions too, so expansion names stay consistent with the
resolved function name instead of remaining NULL in those cases.
Signed-off-by: Thierry Treyer <ttreyer@meta.com>
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
dwarf_loader.c | 57 +++++++++++++++++++++++++++++++++++++-------------
dwarves.h | 1 +
2 files changed, 43 insertions(+), 15 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 2165621..13c6464 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -2015,11 +2015,19 @@ static struct inline_expansion *inline_expansion__new(Dwarf_Die *die, struct cu
if (exp != NULL) {
struct dwarf_tag *dtag = tag__dwarf(&exp->ip.tag);
+ Dwarf_Attribute attr_orig;
tag__init(&exp->ip.tag, cu, die);
dtag->decl_file = attr_string(die, DW_AT_call_file, conf);
dtag->decl_line = attr_numeric(die, DW_AT_call_line);
dwarf_tag__set_attr_type(dtag, DWARF_TAG__REF_TYPE, die, DW_AT_abstract_origin, cu);
+ if (dwarf_attr(die, DW_AT_abstract_origin, &attr_orig)) {
+ Dwarf_Die die_orig;
+
+ if (dwarf_formref_die(&attr_orig, &die_orig))
+ exp->name = attr_string(&die_orig, DW_AT_name, conf);
+ }
+
exp->ip.addr = 0;
exp->high_pc = 0;
exp->nr_parms = 0;
@@ -3571,6 +3579,32 @@ static void __tag__print_abstract_origin_not_found(struct tag *tag,
#define tag__print_abstract_origin_not_found(tag) \
__tag__print_abstract_origin_not_found(tag, __func__, __LINE__)
+static void function__recode_dwarf_name(struct tag *tag, struct cu *cu)
+{
+ struct function *fn = tag__function(tag);
+ struct dwarf_tag *dtag = tag__dwarf(tag);
+ struct dwarf_tag *dtype;
+
+ if (fn->name != 0)
+ return;
+ if (dtag->abstract_origin == 0 && dtag->specification == 0)
+ return;
+
+ dtype = dwarf_cu__find_tag_by_ref(cu->priv, dtag, abstract_origin);
+ if (dtype == NULL)
+ dtype = dwarf_cu__find_tag_by_ref(cu->priv, dtag, specification);
+ if (dtype != NULL)
+ fn->name = tag__function(dtag__tag(dtype))->name;
+ else {
+ fprintf(stderr,
+ "%s: couldn't find name for function %#llx, abstract_origin=%#llx, specification=%#llx\n",
+ __func__,
+ (unsigned long long)dtag->id,
+ (unsigned long long)dtag->abstract_origin,
+ (unsigned long long)dtag->specification);
+ }
+}
+
static void parameter__share_state_with_abstract_origin(struct parameter *parm,
struct parameter *oparm)
{
@@ -3654,6 +3688,7 @@ static void inline_expansion__recode_dwarf_types(struct tag *tag, struct cu *cu)
struct dwarf_cu *dcu = cu->priv;
struct dwarf_tag *dtag = tag__dwarf(tag);
struct dwarf_tag *dtype;
+ struct tag *origin;
struct tag *pos;
/* The inline expansion's abstract origin is stored as its type ref. */
@@ -3664,7 +3699,12 @@ static void inline_expansion__recode_dwarf_types(struct tag *tag, struct cu *cu)
return;
}
- ftype__recode_dwarf_types(dtag__tag(dtype), cu);
+ origin = dtag__tag(dtype);
+ if (tag__is_function(origin)) {
+ function__recode_dwarf_name(origin, cu);
+ tag__inline_expansion(tag)->name = function__name(tag__function(origin));
+ }
+ ftype__recode_dwarf_types(origin, cu);
list_for_each_entry(pos, &tag__inline_expansion(tag)->parms, node)
parameter__recode_dwarf_type(tag__parameter(pos), cu, NULL);
tag->type = dtype->small_id;
@@ -4056,20 +4096,7 @@ static int tag__recode_dwarf_type(struct tag *tag, struct cu *cu)
*/
return 0;
}
- dtype = dwarf_cu__find_tag_by_ref(cu->priv, dtag, abstract_origin);
- if (dtype == NULL)
- dtype = dwarf_cu__find_tag_by_ref(cu->priv, dtag, specification);
- if (dtype != NULL)
- fn->name = tag__function(dtag__tag(dtype))->name;
- else {
- fprintf(stderr,
- "%s: couldn't find name for "
- "function %#llx, abstract_origin=%#llx,"
- " specification=%#llx\n", __func__,
- (unsigned long long)dtag->id,
- (unsigned long long)dtag->abstract_origin,
- (unsigned long long)dtag->specification);
- }
+ function__recode_dwarf_name(tag, cu);
}
lexblock__recode_dwarf_types(&fn->lexblock, cu);
}
diff --git a/dwarves.h b/dwarves.h
index f620db8..ddd4fd5 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -823,6 +823,7 @@ struct ip_tag {
struct inline_expansion {
struct ip_tag ip;
+ const char *name;
size_t size;
uint64_t high_pc;
struct list_head parms;
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 3/9] dwarf_loader: Collect inline expansion location data
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 2/9] dwarf_loader: Resolve inline expansion names during recoding Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 4/9] pahole: Support inline BTF location encoding Alan Maguire
` (5 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Prepare inline expansion BTF encoding by keeping the resolved origin
function on each inline expansion and by exposing inline expansions and
their parameters.
Reuse the existing true-signature parameter location decoding instead of
adding a separate per-site location model. Store the small amount of
extra location state needed by BTF inline location encoding directly on
struct parameter: constant/address values, offsets, size, signedness and
dereference flags. This lets the encoder derive LOC_PARAM data from the
same representation used for optimized-function signature analysis.
Also fix inline expansion high_pc collection to use dwarf_highpc(), and
avoid running concrete ABI register diagnostics on abstract inline
subprograms while still allowing their type recoding to proceed.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
dwarf_loader.c | 322 ++++++++++++++++++++++++++++++++++++++++---------
dwarves.h | 38 +++++-
2 files changed, 303 insertions(+), 57 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 13c6464..49a88d5 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1730,12 +1730,123 @@ static bool dwarf_expr__has_stack_value(Dwarf_Op *expr, size_t exprlen)
return false;
}
+static uint8_t parameter__loc_size(const struct parameter *parm, const struct cu *cu)
+{
+ uint16_t size = parm->type_byte_size ?: cu->addr_size;
+
+ /* BTF_LOC_PARAM can describe values up to 16 bytes. */
+ if (size == 0 || size > 16)
+ return 0;
+ return size;
+}
+
+static bool dwarf_op__const_signed(unsigned int atom)
+{
+ switch (atom) {
+ case DW_OP_const1s:
+ case DW_OP_const2s:
+ case DW_OP_const4s:
+ case DW_OP_const8s:
+ case DW_OP_consts:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int64_t dwarf_op__snumber(const Dwarf_Op *op)
+{
+ return (int64_t)(Dwarf_Sword)op->number;
+}
+
+static uint64_t dwarf_op__const_value(const Dwarf_Op *op)
+{
+ if (dwarf_op__const_signed(op->atom))
+ return (uint64_t)dwarf_op__snumber(op);
+ return op->number;
+}
+
static void parameter__set_loc_reg(struct parameter *parm, int reg)
{
if (parm->loc_reg == PARAMETER_UNKNOWN_REG)
parm->loc_reg = reg;
}
+static void parameter__record_loc_reg(struct parameter *parm, int reg,
+ int64_t offset, bool is_deref,
+ uint8_t size)
+{
+ if (reg < DW_OP_reg0 || !size)
+ return;
+
+ parameter__set_loc_reg(parm, reg);
+
+ if (parm->loc_reg != reg)
+ return;
+ if (offset < INT32_MIN || offset > INT32_MAX)
+ return;
+
+ parm->loc_offset = offset;
+ parm->loc_deref = is_deref;
+ parm->loc_size = size;
+}
+
+static void parameter__record_loc_reg_pair(struct parameter *parm, int reg,
+ int reg2)
+{
+ uint16_t size = parm->type_byte_size;
+
+ /* LOC_PARAM supports a complete two-register value of up to 16 bytes. */
+ if (reg < DW_OP_reg0 || reg2 < DW_OP_reg0 || !size || size > 16)
+ return;
+
+ parm->loc_reg = reg;
+ parm->loc_reg2 = reg2;
+ parm->loc_offset = 0;
+ parm->loc_deref = 0;
+ parm->loc_size = size;
+}
+
+static void parameter__record_loc_const(struct parameter *parm, uint64_t value,
+ uint8_t size, bool is_signed,
+ bool is_addr)
+{
+ if (!size)
+ return;
+
+ parm->loc_const_value = 1;
+ parm->loc_value = value;
+ parm->loc_size = size;
+ parm->loc_signed = is_signed;
+ parm->loc_addr = is_addr;
+}
+
+static bool parameter__record_attr_const_value(Dwarf_Attribute *attr,
+ struct cu *cu,
+ struct parameter *parm)
+{
+ Dwarf_Sword svalue;
+ Dwarf_Word uvalue;
+
+ if (dwarf_formudata(attr, &uvalue) == 0) {
+ parm->loc_value = uvalue;
+ parm->loc_size = parameter__loc_size(parm, cu);
+ parm->loc_signed = 0;
+ parm->loc_addr = 0;
+ return true;
+ }
+
+ if (dwarf_formsdata(attr, &svalue) == 0) {
+ parm->loc_value = (uint64_t)svalue;
+ parm->loc_size = parameter__loc_size(parm, cu);
+ parm->loc_signed = 1;
+ parm->loc_addr = 0;
+ return true;
+ }
+
+ return false;
+}
+
static void parameter__set_field_bit(unsigned long *fields, int byte_offset)
{
if (byte_offset >= 0 && byte_offset < (int)(sizeof(*fields) * 8))
@@ -1795,24 +1906,43 @@ static void parameter__finish_piece_decode(struct parameter *parm, Dwarf_Die *di
static void parameter__multi_exprs(Dwarf_Op *expr, int loc_num, struct cu *cu,
size_t exprlen, struct parameter *parm)
{
+ int regs[2], nregs = 0;
+ uint64_t piece_size = 0;
+ bool reg_pieces = true;
+
switch (expr[0].atom) {
case DW_OP_lit0 ... DW_OP_lit31:
case DW_OP_constu:
case DW_OP_consts:
- if (loc_num == 0)
- parm->loc_const_value = 1;
+ if (loc_num == 0) {
+ uint64_t value = expr[0].atom;
+
+ if (expr[0].atom >= DW_OP_lit0 && expr[0].atom <= DW_OP_lit31)
+ value -= DW_OP_lit0;
+ else
+ value = dwarf_op__const_value(&expr[0]);
+ parameter__record_loc_const(parm, value,
+ parameter__loc_size(parm, cu),
+ dwarf_op__const_signed(expr[0].atom), false);
+ }
return;
}
if (parm->type_byte_size <= cu->addr_size || !cu->agg_use_two_regs) {
+ bool had_stack_value = dwarf_expr__has_stack_value(expr, exprlen);
+ uint8_t size = parameter__loc_size(parm, cu);
+
switch (expr[0].atom) {
case DW_OP_reg0 ... DW_OP_reg31:
if (loc_num == 0)
- parameter__set_loc_reg(parm, expr[0].atom);
+ parameter__record_loc_reg(parm, expr[0].atom, 0, false, size);
return;
case DW_OP_breg0 ... DW_OP_breg31:
- if (loc_num == 0 && dwarf_expr__has_stack_value(expr, exprlen))
- parameter__set_loc_reg(parm, expr[0].atom - DW_OP_breg0 + DW_OP_reg0);
+ if (loc_num == 0)
+ parameter__record_loc_reg(parm,
+ expr[0].atom - DW_OP_breg0 + DW_OP_reg0,
+ dwarf_op__snumber(&expr[0]),
+ !had_stack_value, size);
return;
default:
return;
@@ -1822,6 +1952,10 @@ static void parameter__multi_exprs(Dwarf_Op *expr, int loc_num, struct cu *cu,
int off = 0;
for (size_t i = 0; i < exprlen; i++) {
if (expr[i].atom == DW_OP_piece) {
+ if (i == 0 || !dwarf_op__is_reg(expr[i - 1].atom))
+ reg_pieces = false;
+ else
+ piece_size += expr[i].number;
int num = expr[i].number;
if (i == 0) {
@@ -1835,13 +1969,26 @@ static void parameter__multi_exprs(Dwarf_Op *expr, int loc_num, struct cu *cu,
parameter__set_field_bit(&parm->second_reg_fields, off - cu->addr_size);
off += num;
} else if (dwarf_op__is_reg(expr[i].atom)) {
- if (off < cu->addr_size || parm->loc_reg == PARAMETER_UNKNOWN_REG)
- parameter__set_loc_reg(parm, expr[i].atom);
+ int reg = expr[i].atom - DW_OP_reg0;
+
+ if (nregs < ARRAY_SIZE(regs))
+ regs[nregs++] = reg;
+ else
+ reg_pieces = false;
+ if (off < cu->addr_size || parm->loc_reg == PARAMETER_UNKNOWN_REG) {
+ uint8_t size = parameter__loc_size(parm, cu);
+
+ parameter__record_loc_reg(parm, expr[i].atom, 0, false, size);
+ }
+ } else {
+ reg_pieces = false;
}
- /* FIXME: not handling DW_OP_bregX pieces yet since we do not
- * have a use case for it yet in the Linux kernel.
- */
}
+
+ if (loc_num == 0 && reg_pieces && nregs == ARRAY_SIZE(regs) &&
+ piece_size == parm->type_byte_size)
+ parameter__record_loc_reg_pair(parm, DW_OP_reg0 + regs[0],
+ DW_OP_reg0 + regs[1]);
}
static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *conf,
@@ -1857,46 +2004,90 @@ static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *
libdw__lock_lock();
while ((offset = __dwarf_getlocations(attr, offset, &base, &start, &end, &expr, &exprlen)) > 0) {
- bool had_stack_value;
+ bool had_stack_value = false, had_deref = false;
+ uint8_t size = 0;
loc_num++;
if (exprlen == 0)
continue;
+ /*
+ * Expressions can have form
+ * DW_OP_fbreg -40, DW_OP_stack_value ; or
+ * DW_OP_fbreg -40, DW_OP_deref[_size], DW_OP_stack_value
+ *
+ * In the absence of the stack value, breg/fbreg are
+ * implicitly dereferences; with it they are non-dereference
+ * calculations of register + offset. So below we mark
+ * dereferences as either explicit (from DW_OP_deref*)
+ * or implicit (!had_stack_value for fbreg/breg).
+ */
had_stack_value = expr[exprlen - 1].atom == DW_OP_stack_value;
- if (exprlen == 2 && had_stack_value)
+ if (had_stack_value)
exprlen--;
+ if (exprlen > 0) {
+ had_deref = expr[exprlen - 1].atom == DW_OP_deref ||
+ expr[exprlen - 1].atom == DW_OP_deref_size;
+ if (had_deref)
+ exprlen--;
+ }
+ if (exprlen == 0)
+ continue;
if (exprlen != 1) {
parameter__multi_exprs(expr, loc_num, cu, exprlen, parm);
continue;
}
+ size = parameter__loc_size(parm, cu);
+
switch (expr->atom) {
case DW_OP_reg0 ... DW_OP_reg31:
if (loc_num == 0)
- parameter__set_loc_reg(parm, expr->atom);
+ parameter__record_loc_reg(parm, expr->atom, 0, false, size);
break;
case DW_OP_breg0 ... DW_OP_breg31:
- if (loc_num == 0 && had_stack_value)
- parameter__set_loc_reg(parm, expr->atom - DW_OP_breg0 + DW_OP_reg0);
+ if (loc_num == 0)
+ parameter__record_loc_reg(parm,
+ expr->atom - DW_OP_breg0 + DW_OP_reg0,
+ dwarf_op__snumber(expr),
+ had_deref || !had_stack_value,
+ size);
break;
case DW_OP_fbreg:
- if (loc_num == 0)
+ if (loc_num == 0 && size) {
+ int64_t loc_offset = dwarf_op__snumber(expr);
+
parm->loc_stack = 1;
+ if (loc_offset >= INT32_MIN && loc_offset <= INT32_MAX)
+ parm->loc_offset = (int32_t)loc_offset;
+ parm->loc_size = size;
+ parm->loc_deref = had_deref || !had_stack_value;
+ }
break;
case DW_OP_lit0 ... DW_OP_lit31:
- case DW_OP_constu:
- case DW_OP_consts:
if (loc_num == 0)
- parm->loc_const_value = 1;
+ parameter__record_loc_const(parm, expr->atom - DW_OP_lit0,
+ size, false, false);
+ break;
+ case DW_OP_const1u ... DW_OP_consts:
+ if (loc_num == 0)
+ parameter__record_loc_const(parm, dwarf_op__const_value(expr),
+ size,
+ dwarf_op__const_signed(expr->atom),
+ false);
+ break;
+ case DW_OP_addr:
+ if (loc_num == 0)
+ parameter__record_loc_const(parm, expr->number, cu->addr_size,
+ false, true);
break;
case DW_OP_entry_value:
case DW_OP_GNU_entry_value:
if (dwarf_getlocation_attr(attr, expr, &entry_attr) == 0 &&
dwarf_getlocation(&entry_attr, &entry_ops, &entry_len) == 0 &&
entry_len == 1 && dwarf_op__is_reg(entry_ops->atom))
- parameter__set_loc_reg(parm, entry_ops->atom);
+ parameter__record_loc_reg(parm, entry_ops->atom, 0, false, size);
break;
}
}
@@ -1906,7 +2097,8 @@ static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *
}
static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf,
- struct ftype *ftype, int param_idx)
+ struct ftype *ftype, int param_idx,
+ bool decode_location)
{
struct parameter *parm = tag__alloc(cu, sizeof(*parm));
@@ -1917,7 +2109,8 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu, struct co
parm->name = attr_string(die, DW_AT_name, conf);
parm->idx = param_idx;
parm->loc_reg = PARAMETER_UNKNOWN_REG;
- if (!ftype)
+ parm->loc_reg2 = PARAMETER_UNKNOWN_REG;
+ if (!decode_location)
return parm;
parm->type_byte_size = get_type_byte_size(die, cu);
@@ -1958,6 +2151,8 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu, struct co
* ftype__recode_dwarf_types() below for how this is handled.
*/
parm->has_const_value = dwarf_attr(die, DW_AT_const_value, &attr) != NULL;
+ if (parm->has_const_value)
+ parameter__record_attr_const_value(&attr, cu, parm);
parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) != NULL;
if (parm->has_loc)
parameter__decode_location(&attr, conf, cu, die, parm);
@@ -1980,7 +2175,7 @@ static int formal_parameter_pack__load_params(struct formal_parameter_pack *pack
continue;
}
- struct parameter *param = parameter__new(die, cu, conf, NULL, -1);
+ struct parameter *param = parameter__new(die, cu, conf, NULL, -1, false);
if (param == NULL)
return -1;
@@ -2021,10 +2216,12 @@ static struct inline_expansion *inline_expansion__new(Dwarf_Die *die, struct cu
dtag->decl_file = attr_string(die, DW_AT_call_file, conf);
dtag->decl_line = attr_numeric(die, DW_AT_call_line);
dwarf_tag__set_attr_type(dtag, DWARF_TAG__REF_TYPE, die, DW_AT_abstract_origin, cu);
+ exp->name = NULL;
if (dwarf_attr(die, DW_AT_abstract_origin, &attr_orig)) {
Dwarf_Die die_orig;
- if (dwarf_formref_die(&attr_orig, &die_orig))
+ if (dwarf_formref_die(&attr_orig, &die_orig) &&
+ dwarf_tag(&die_orig) == DW_TAG_subprogram)
exp->name = attr_string(&die_orig, DW_AT_name, conf);
}
@@ -2038,7 +2235,7 @@ static struct inline_expansion *inline_expansion__new(Dwarf_Die *die, struct cu
if (dwarf_lowpc(die, &exp->ip.addr))
exp->ip.addr = 0;
- if (dwarf_lowpc(die, &exp->high_pc))
+ if (dwarf_highpc(die, &exp->high_pc))
exp->high_pc = 0;
exp->size = exp->high_pc - exp->ip.addr;
@@ -2573,7 +2770,8 @@ static struct tag *die__create_new_parameter(Dwarf_Die *die,
struct cu *cu, struct conf_load *conf,
int param_idx)
{
- struct parameter *parm = parameter__new(die, cu, conf, ftype, param_idx);
+ struct parameter *parm = parameter__new(die, cu, conf, ftype, param_idx,
+ ftype != NULL || exp != NULL);
if (parm == NULL)
return NULL;
@@ -3618,6 +3816,17 @@ static void parameter__share_state_with_abstract_origin(struct parameter *parm,
oparm->loc_stack = parm->loc_stack;
if (parm->loc_reg != PARAMETER_UNKNOWN_REG)
oparm->loc_reg = parm->loc_reg;
+ if (parm->loc_reg2 != PARAMETER_UNKNOWN_REG)
+ oparm->loc_reg2 = parm->loc_reg2;
+ if (parm->has_const_value || parm->loc_const_value || parm->loc_stack ||
+ parm->loc_reg != PARAMETER_UNKNOWN_REG) {
+ oparm->loc_offset = parm->loc_offset;
+ oparm->loc_value = parm->loc_value;
+ oparm->loc_size = parm->loc_size;
+ oparm->loc_deref = parm->loc_deref;
+ oparm->loc_addr = parm->loc_addr;
+ oparm->loc_signed = parm->loc_signed;
+ }
if (parm->type_byte_size != 0)
oparm->type_byte_size = parm->type_byte_size;
if (parm->passed_in_memory)
@@ -3690,22 +3899,28 @@ static void inline_expansion__recode_dwarf_types(struct tag *tag, struct cu *cu)
struct dwarf_tag *dtype;
struct tag *origin;
struct tag *pos;
+ struct inline_expansion *exp = tag__inline_expansion(tag);
+
+ exp->function = NULL;
/* The inline expansion's abstract origin is stored as its type ref. */
dtype = dwarf_cu__find_tag_by_ref(dcu, dtag, type);
if (dtype == NULL) {
tag__print_type_not_found(tag);
tag__check_pruned_alt_ref(dcu, dtag->type, dtag->from_alt.type);
+ exp->name = NULL;
return;
}
origin = dtag__tag(dtype);
if (tag__is_function(origin)) {
function__recode_dwarf_name(origin, cu);
- tag__inline_expansion(tag)->name = function__name(tag__function(origin));
- }
+ exp->function = tag__function(origin);
+ exp->name = function__name(exp->function);
+ } else
+ exp->name = NULL;
ftype__recode_dwarf_types(origin, cu);
- list_for_each_entry(pos, &tag__inline_expansion(tag)->parms, node)
+ list_for_each_entry(pos, &exp->parms, node)
parameter__recode_dwarf_type(tag__parameter(pos), cu, NULL);
tag->type = dtype->small_id;
}
@@ -4214,37 +4429,32 @@ static int cu__resolve_func_ret_types_optimized(struct cu *cu, struct conf_load
continue;
fn = tag__function(tag);
- function__analyze_parameter_locations(fn, cu, conf);
-
- /* mark function as optimized if parameter is, or
- * if parameter does not have a location; at this
- * point location presence has been marked in
- * abstract origins for cases where a parameter
- * location is not stored in the original function
- * parameter tag.
- *
- * Also mark functions which, due to optimization,
- * use an unexpected register for a parameter.
- * Exception is functions which have a struct
- * as a parameter, as multiple registers may
- * be used to represent it, throwing off register
- * to parameter mapping.
+ /* Abstract inline subprograms describe source signatures; their
+ * concrete locations belong to DW_TAG_inlined_subroutine children.
*/
- ftype__for_each_parameter(&fn->proto, pos) {
- if (pos->optimized || !pos->has_loc)
- fn->proto.optimized_parms = 1;
+ if (!function__inlined(fn)) {
+ function__analyze_parameter_locations(fn, cu, conf);
- if (pos->unexpected_reg)
- has_unexpected_reg = true;
- }
- if (has_unexpected_reg) {
+ /* Mark functions with optimized or location-less parameters, and
+ * flag unexpected registers unless a struct parameter explains
+ * the multi-register ABI representation.
+ */
ftype__for_each_parameter(&fn->proto, pos) {
- has_struct_param = param__is_struct(cu, &pos->tag);
- if (has_struct_param)
- break;
+ if (pos->optimized || !pos->has_loc)
+ fn->proto.optimized_parms = 1;
+
+ if (pos->unexpected_reg)
+ has_unexpected_reg = true;
+ }
+ if (has_unexpected_reg) {
+ ftype__for_each_parameter(&fn->proto, pos) {
+ has_struct_param = param__is_struct(cu, &pos->tag);
+ if (has_struct_param)
+ break;
+ }
+ if (!has_struct_param)
+ fn->proto.unexpected_reg = 1;
}
- if (!has_struct_param)
- fn->proto.unexpected_reg = 1;
}
if (tag == NULL || tag->type != 0)
diff --git a/dwarves.h b/dwarves.h
index ddd4fd5..b6ddb50 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -472,6 +472,19 @@ bool languages__cu_filtered(struct languages *languages, struct cu *cu, bool ver
continue; \
else
+/**
+ * cu__for_each_inline_expansion - iterate thru all inline expansions
+ * @cu: struct cu instance to iterate
+ * @id: uint32_t tag id
+ * @pos: struct inline_expansion iterator
+ */
+#define cu__for_each_inline_expansion(cu, id, pos) \
+ for (id = 0; id < cu->tags_table.nr_entries; ++id) \
+ if (!tag__is_inline_expansion(cu->tags_table.entries[id]) || \
+ !(pos = tag__inline_expansion(cu->tags_table.entries[id]))) \
+ continue; \
+ else
+
/**
* cu__for_each_namespace - iterate thru all the global namespace tags
* @cu: struct cu instance to iterate
@@ -602,6 +615,11 @@ static inline bool tag__is_constant(const struct tag *tag)
return tag->tag == DW_TAG_constant;
}
+static inline bool tag__is_inline_expansion(const struct tag *tag)
+{
+ return tag && tag->tag == DW_TAG_inlined_subroutine;
+}
+
static inline bool tag__is_volatile(const struct tag *tag)
{
return tag->tag == DW_TAG_volatile_type;
@@ -821,12 +839,16 @@ struct ip_tag {
uint64_t addr;
};
+struct function;
+struct parameter;
+
struct inline_expansion {
struct ip_tag ip;
const char *name;
size_t size;
uint64_t high_pc;
struct list_head parms;
+ struct function *function;
uint16_t nr_parms;
};
@@ -836,10 +858,17 @@ static inline struct inline_expansion *
return (struct inline_expansion *)tag;
}
-struct parameter;
void inline_expansion__add_parameter(struct inline_expansion *exp,
struct parameter *parm);
+/**
+ * inline_expansion__for_each_parameter - iterate thru all the parameters
+ * @ie: struct inline_expansion instance to iterate
+ * @pos: struct parameter iterator
+ */
+#define inline_expansion__for_each_parameter(ie, pos) \
+ list_for_each_entry(pos, &(ie)->parms, tag.node)
+
struct label {
struct ip_tag ip;
const char *name;
@@ -955,12 +984,19 @@ struct parameter {
unsigned long first_reg_fields;
unsigned long second_reg_fields;
int loc_reg;
+ int loc_reg2;
+ int32_t loc_offset;
+ uint64_t loc_value;
uint16_t type_byte_size;
+ uint8_t loc_size;
uint8_t true_sig_type_from_types:1;
uint8_t true_sig_type_from_alt:1;
uint8_t has_const_value:1;
uint8_t loc_const_value:1;
uint8_t loc_stack:1;
+ uint8_t loc_deref:1;
+ uint8_t loc_addr:1;
+ uint8_t loc_signed:1;
uint8_t optimized:1;
uint8_t unexpected_reg:1;
uint8_t has_loc:1;
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 4/9] pahole: Support inline BTF location encoding
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
` (2 preceding siblings ...)
2026-09-25 18:51 ` [PATCH v3 dwarves 3/9] dwarf_loader: Collect inline expansion location data Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 5/9] btf_loader: Add "inline" annotation for inlined functions Alan Maguire
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Add support for emitting BTF location information for inline
expansions when --btf_features=inline is enabled.
Encode inline parameters as BTF_KIND_LOC_PARAM records, collect them
in BTF_KIND_LOC_PROTO records, and associate those with inline
functions through BTF_KIND_LOCSEC records named "inline<section>".
Support constant, register, frame-base-register, dereferenced
base-register, and two-register aggregate parameter locations.
Offsets are marked signed, and the frame-base register uses the
BTF_LOC_PARAM_FBREG sentinel value. LOC_PARAMs that specify
an address have that address normalized to the base address of
the object (kernel or module).
Use DWFL to resolve an inline expansion address to its executable ELF
section and section-relative offset, including for ET_REL inputs.
Expose inline encoding as a non-default BTF feature and enable inline
DIE loading when it is selected. Omit the feature support when
the linked libbpf does not provide the required location APIs.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
btf_encoder.c | 391 +++++++++++++++++++++++++++++++++++++++++++-------
dwarves.h | 15 ++
pahole.c | 12 ++
3 files changed, 366 insertions(+), 52 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index d6b9be6..b4aa382 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -43,6 +43,13 @@
#define BTF_FASTCALL_TAG "bpf_fastcall"
#define BPF_ARENA_ATTR "address_space(1)"
+#define BTF_LOC_PARAM_SIGNED 0x1
+#define BTF_LOC_PARAM_CONST 0x2
+#define BTF_LOC_PARAM_ADDR 0x4
+#define BTF_LOC_PARAM_REG 0x8
+#define BTF_LOC_PARAM_DEREF 0x10
+#define BTF_LOC_PARAM_OFFSET 0x20
+
/* kfunc flags, see include/linux/btf.h in the kernel source */
#define KF_FASTCALL (1 << 12)
#define KF_ARENA_RET (1 << 13)
@@ -77,6 +84,23 @@ struct btf_encoder_func_annot {
int16_t component_idx;
};
+struct loc_param {
+ uint32_t flags;
+ uint32_t values[2];
+ uint8_t nvalues;
+ uint8_t size;
+};
+
+struct loc {
+ char *name;
+ struct loc_param *params;
+ uint16_t nparams;
+ int func_id;
+ int loc_proto_id;
+ uint32_t section_idx;
+ uint32_t section_offset;
+};
+
struct elf_function_sym {
const char *name;
uint64_t addr;
@@ -86,6 +110,7 @@ struct elf_function_sym {
struct btf_encoder_func_state {
struct elf_function *elf;
struct elf_function_sym *sym;
+ struct loc *loc;
uint64_t addr;
uint32_t type_id_off;
uint16_t nr_parms;
@@ -103,6 +128,12 @@ struct btf_encoder_func_state {
struct btf_encoder_func_annot *annots;
};
+struct btf_encoder_func_states {
+ struct btf_encoder_func_state *array;
+ int cnt;
+ int cap;
+};
+
struct elf_function {
char *name;
struct elf_function_sym *syms;
@@ -116,6 +147,7 @@ struct elf_secinfo {
uint64_t addr;
const char *name;
uint64_t sz;
+ uint64_t flags;
uint32_t type;
bool include;
struct gobuffer secinfo;
@@ -175,12 +207,10 @@ struct btf_encoder {
uint32_t btf_id_skipped;
struct elf_secinfo *secinfo;
size_t seccnt;
+ uint64_t addr_base;
int encode_vars;
- struct {
- struct btf_encoder_func_state *array;
- int cnt;
- int cap;
- } func_states;
+ struct btf_encoder_func_states func_states;
+ struct btf_encoder_func_states loc_states;
/* This is a list of elf_functions tables, one per ELF.
* Multiple ELF modules can be processed in one pahole run,
* so we have to store elf_functions tables per ELF.
@@ -188,6 +218,8 @@ struct btf_encoder {
struct list_head elf_functions_list;
};
+static int btf_encoder__add_locsec(struct btf_encoder *encoder);
+
/* Half open interval representing range of addresses containing kfuncs */
struct btf_kfunc_set_range {
uint64_t start;
@@ -324,6 +356,9 @@ static const char * const btf_kind_str[] = {
[BTF_KIND_DECL_TAG] = "DECL_TAG",
[BTF_KIND_TYPE_TAG] = "TYPE_TAG",
[BTF_KIND_ENUM64] = "ENUM64",
+ [BTF_KIND_LOC_PARAM] = "LOC_PARAM",
+ [BTF_KIND_LOC_PROTO] = "LOC_PROTO",
+ [BTF_KIND_LOCSEC] = "LOCSEC",
};
static const char *btf__printable_name(const struct btf *btf, uint32_t offset)
@@ -938,8 +973,10 @@ static int32_t btf_encoder__add_func_proto_for_state(struct btf_encoder *encoder
* name string memory, so make a temporary copy.
*/
strncpy(tmp_name, name, sizeof(tmp_name) - 1);
+ tmp_name[sizeof(tmp_name) - 1] = '\0';
- if (btf_encoder__add_func_param(encoder, tmp_name, p->type_id, is_last))
+ type_id = p->type_id;
+ if (btf_encoder__add_func_param(encoder, tmp_name, type_id, is_last))
return -1;
}
@@ -1216,31 +1253,57 @@ static bool funcs__match(struct btf_encoder *encoder,
return true;
}
-static struct btf_encoder_func_state *btf_encoder__alloc_func_state(struct btf_encoder *encoder)
+static struct btf_encoder_func_state *
+btf_encoder__alloc_func_state(struct btf_encoder_func_states *func_states)
{
struct btf_encoder_func_state *state, *tmp;
- if (encoder->func_states.cnt >= encoder->func_states.cap) {
+ if (func_states->cnt >= func_states->cap) {
/* We only need to grow to accommodate duplicate
* function declarations across different CUs, so the
* rate of the array growth shouldn't be high.
*/
- encoder->func_states.cap += 64;
+ func_states->cap += 64;
- tmp = realloc(encoder->func_states.array, sizeof(*tmp) * encoder->func_states.cap);
+ tmp = realloc(func_states->array, sizeof(*tmp) * func_states->cap);
if (!tmp)
return NULL;
- encoder->func_states.array = tmp;
+ func_states->array = tmp;
}
- state = &encoder->func_states.array[encoder->func_states.cnt++];
+ state = &func_states->array[func_states->cnt++];
memset(state, 0, sizeof(*state));
return state;
}
+static void loc__delete(struct loc *loc)
+{
+ if (!loc)
+ return;
+ free(loc->name);
+ free(loc->params);
+ free(loc);
+}
+
+static void btf_encoder_func_state__clear(struct btf_encoder_func_state *state)
+{
+ free(state->parms);
+ free(state->annots);
+ loc__delete(state->loc);
+ memset(state, 0, sizeof(*state));
+}
+
+static void btf_encoder__drop_func_state(struct btf_encoder_func_states *func_states,
+ struct btf_encoder_func_state *state)
+{
+ btf_encoder_func_state__clear(state);
+ if (func_states->cnt > 0 && state == &func_states->array[func_states->cnt - 1])
+ func_states->cnt--;
+}
+
static bool str_contains_suffix(const char *str, const char * const *suffixes, size_t nr_suffixes)
{
const char *suffix = strchr(str, '.');
@@ -1298,9 +1361,12 @@ static bool elf_function__has_ambiguous_address(struct elf_function *func)
return false;
}
-static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct function *fn, struct elf_function *func)
+static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct function *fn,
+ struct elf_function *func, struct loc *loc)
{
- struct btf_encoder_func_state *state = btf_encoder__alloc_func_state(encoder);
+ struct btf_encoder_func_states *func_states = loc ? &encoder->loc_states :
+ &encoder->func_states;
+ struct btf_encoder_func_state *state = btf_encoder__alloc_func_state(func_states);
struct ftype *ftype = &fn->proto;
struct btf *btf = encoder->btf;
struct llvm_annotation *annot;
@@ -1313,6 +1379,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
state->addr = function__addr(fn);
state->elf = func;
+ state->loc = loc;
state->nr_parms = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0);
state->ret_type_id = btf_encoder__tag_type(encoder, ftype->tag.type);
if (state->nr_parms > 0) {
@@ -1322,7 +1389,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
goto out;
}
}
- if (encoder->true_signature && state->addr) {
+ if (encoder->true_signature && state->addr && func) {
int i;
for (i = 0; i < func->sym_cnt; i++) {
@@ -1344,7 +1411,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
state->uncertain_parm_loc = ftype->uncertain_parm_loc;
state->reordered_parm = ftype->reordered_parm;
state->signature_changed = ftype->signature_changed;
- if (state->addr) {
+ if (state->addr && func) {
for (int i = 0; i < func->sym_cnt; i++) {
if (state->addr != func->syms[i].addr)
continue;
@@ -1359,11 +1426,12 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
char *final_name = NULL;
/* No location info/optimized + reordered means optimized out. */
- if (ftype->reordered_parm && (!param->has_loc || param->optimized)) {
+ if (!loc && ftype->reordered_parm && (!param->has_loc || param->optimized)) {
state->nr_parms--;
continue;
}
- if (encoder->true_signature && ftype->signature_changed && param->optimized) {
+ if (!loc && encoder->true_signature && ftype->signature_changed &&
+ param->optimized) {
state->nr_parms--;
continue;
}
@@ -1399,6 +1467,9 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
if (ftype->unspec_parms)
state->parms[param_idx].type_id = 0;
+ if (loc)
+ return 0;
+
list_for_each_entry(annot, &fn->annots, node)
state->nr_annots++;
if (state->nr_annots) {
@@ -1422,21 +1493,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
}
return 0;
out:
- /*
- * state is an interior pointer into func_states.array (returned by
- * btf_encoder__alloc_func_state), not a standalone allocation.
- * Calling free(state) here was heap corruption.
- *
- * Since no further func_state allocations happen between the alloc
- * at the top of this function and this error path, state is always
- * the last element (index cnt-1), so decrementing cnt releases it.
- * If this invariant ever changes (e.g. nested alloc calls are added),
- * this cleanup must be revised.
- */
- zfree(&state->annots);
- zfree(&state->parms);
- memset(state, 0, sizeof(*state));
- encoder->func_states.cnt--;
+ btf_encoder__drop_func_state(func_states, state);
return err;
}
@@ -1600,21 +1657,14 @@ static int saved_functions_combine(struct btf_encoder *encoder,
return 0;
}
-static void btf_encoder__delete_saved_funcs(struct btf_encoder *encoder)
+static void btf_encoder__delete_func_states(struct btf_encoder_func_states *func_states)
{
- struct btf_encoder_func_state *state;
-
- for (int i = 0; i < encoder->func_states.cnt; i++) {
- state = &encoder->func_states.array[i];
- free(state->parms);
- free(state->annots);
- }
-
- free(encoder->func_states.array);
-
- encoder->func_states.array = NULL;
- encoder->func_states.cnt = 0;
- encoder->func_states.cap = 0;
+ for (int i = 0; i < func_states->cnt; i++)
+ btf_encoder_func_state__clear(&func_states->array[i]);
+ free(func_states->array);
+ func_states->array = NULL;
+ func_states->cnt = 0;
+ func_states->cap = 0;
}
/*
@@ -1784,7 +1834,7 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
}
out:
- btf_encoder__delete_saved_funcs(encoder);
+ btf_encoder__delete_func_states(&encoder->func_states);
return err;
}
@@ -2466,6 +2516,11 @@ int btf_encoder__encode(struct btf_encoder *encoder, struct conf_load *conf)
err = btf_encoder__add_saved_funcs(encoder, conf->skip_encoding_btf_inconsistent_proto);
if (err < 0)
return err;
+ if (encoder->loc_states.cnt) {
+ err = btf_encoder__add_locsec(encoder);
+ if (err)
+ return err;
+ }
for (shndx = 1; shndx < encoder->seccnt; shndx++)
if (gobuffer__size(&encoder->secinfo[shndx].secinfo))
@@ -2675,7 +2730,7 @@ static size_t get_elf_section(struct btf_encoder *encoder, uint64_t addr)
* values. Prefixes should be added sparingly, and it should be objectively
* obvious that they are not useful.
*/
-static bool filter_variable_name(const char *name)
+static bool filter_name(const char *name)
{
static const struct { char *s; size_t len; } skip[] = {
#define X(str) {str, sizeof(str) - 1}
@@ -2783,7 +2838,7 @@ static int btf_encoder__encode_cu_variables(struct btf_encoder *encoder)
if (!name)
continue;
- if (filter_variable_name(name))
+ if (filter_name(name))
continue;
/* A 0 address may be in a "discard" section; DWARF provides
@@ -2972,6 +3027,7 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
}
bool found_percpu = false;
+ uint64_t addr_base = UINT64_MAX;
for (shndx = 0; shndx < encoder->seccnt; shndx++) {
const char *secname = NULL;
Elf_Scn *sec = elf_section_by_idx(cu->elf, &shdr, shndx, &secname);
@@ -2979,8 +3035,11 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
goto out_delete;
encoder->secinfo[shndx].addr = shdr.sh_addr;
encoder->secinfo[shndx].sz = shdr.sh_size;
+ encoder->secinfo[shndx].flags = shdr.sh_flags;
encoder->secinfo[shndx].name = secname;
encoder->secinfo[shndx].type = shdr.sh_type;
+ if ((shdr.sh_flags & SHF_ALLOC) && shdr.sh_addr < addr_base)
+ addr_base = shdr.sh_addr;
if (encoder->encode_vars & BTF_VAR_GLOBAL)
encoder->secinfo[shndx].include = true;
@@ -2992,6 +3051,8 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
}
}
+ encoder->addr_base = addr_base == UINT64_MAX ? 0 : addr_base;
+
if (!found_percpu && encoder->verbose)
printf("%s: '%s' doesn't have '%s' section\n", __func__, cu->filename, PERCPU_SECTION);
@@ -3030,7 +3091,8 @@ void btf_encoder__delete(struct btf_encoder *encoder)
elf_functions_list__clear(&encoder->elf_functions_list);
- btf_encoder__delete_saved_funcs(encoder);
+ btf_encoder__delete_func_states(&encoder->func_states);
+ btf_encoder__delete_func_states(&encoder->loc_states);
free(encoder);
}
@@ -3067,6 +3129,220 @@ static bool ftype__has_uncertain_arg_loc(struct cu *cu, struct ftype *ftype)
return false;
}
+#define BTF_LOC_PARAM_FBREG 0xffffffff
+
+static int btf_encoder__add_loc_param(struct parameter *param, uint64_t base,
+ struct loc_param *lp)
+{
+ uint8_t size = param->loc_size ?: sizeof(uint64_t);
+ uint64_t value;
+
+ memset(lp, 0, sizeof(*lp));
+ if ((param->loc_const_value || param->has_const_value) && param->loc_size) {
+ /* The loader currently retains at most one 64-bit constant value. */
+ if (size > sizeof(value))
+ return 0;
+ value = param->loc_value;
+ lp->size = size;
+ lp->flags = BTF_LOC_PARAM_CONST;
+ if (param->loc_addr) {
+ lp->flags |= BTF_LOC_PARAM_ADDR;
+ if (value >= base)
+ value -= base;
+ }
+ if (param->loc_signed)
+ lp->flags |= BTF_LOC_PARAM_SIGNED;
+ lp->values[lp->nvalues++] = value;
+ if (lp->size > sizeof(uint32_t))
+ lp->values[lp->nvalues++] = value >> 32;
+ return 1;
+ }
+ if (param->loc_reg >= DW_OP_reg0 && param->loc_reg2 >= DW_OP_reg0) {
+ lp->size = size;
+ lp->flags = BTF_LOC_PARAM_REG;
+ lp->values[lp->nvalues++] = param->loc_reg - DW_OP_reg0;
+ lp->values[lp->nvalues++] = param->loc_reg2 - DW_OP_reg0;
+ return 1;
+ }
+ if (param->loc_reg >= DW_OP_reg0) {
+ lp->size = size;
+ lp->flags = BTF_LOC_PARAM_REG;
+ if (param->loc_deref)
+ lp->flags |= BTF_LOC_PARAM_DEREF;
+ lp->values[lp->nvalues++] = param->loc_reg - DW_OP_reg0;
+ if (param->loc_offset) {
+ lp->flags |= BTF_LOC_PARAM_OFFSET | BTF_LOC_PARAM_SIGNED;
+ lp->values[lp->nvalues++] = param->loc_offset;
+ }
+ return 1;
+ }
+ if (param->loc_stack) {
+ lp->size = size;
+ lp->flags = BTF_LOC_PARAM_REG;
+ if (param->loc_deref)
+ lp->flags |= BTF_LOC_PARAM_DEREF;
+ lp->values[lp->nvalues++] = BTF_LOC_PARAM_FBREG;
+ if (param->loc_offset) {
+ lp->flags |= BTF_LOC_PARAM_OFFSET | BTF_LOC_PARAM_SIGNED;
+ lp->values[lp->nvalues++] = param->loc_offset;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static int btf_encoder__add_inline_expansion(struct btf_encoder *encoder,
+ struct inline_expansion *ie)
+{
+ struct ftype *ftype;
+ struct loc *loc;
+ struct parameter *param;
+ Dwarf_Addr section_offset, bias;
+ Elf_Scn *section;
+ size_t section_idx;
+ int err;
+
+ /* DWFL accounts for ET_REL relocations and identifies the actual
+ * executable section, unlike a raw DWARF address comparison.
+ */
+ section_offset = ie->ip.addr;
+ section = dwfl_module_address_section(encoder->cu->dwfl, §ion_offset, &bias);
+ section_idx = section ? elf_ndxscn(section) : 0;
+ if (!ie->function || !ie->name || !section_idx || section_idx >= encoder->seccnt ||
+ !(encoder->secinfo[section_idx].flags & SHF_EXECINSTR) ||
+ section_offset > UINT32_MAX)
+ return 0;
+ ftype = &ie->function->proto;
+ loc = calloc(1, sizeof(*loc));
+ if (!loc)
+ return -ENOMEM;
+ loc->name = strdup(ie->name);
+ if (!loc->name) {
+ err = -ENOMEM;
+ goto out;
+ }
+ loc->section_idx = section_idx;
+ loc->section_offset = section_offset;
+ loc->nparams = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0);
+ loc->params = calloc(loc->nparams ? loc->nparams : 1, sizeof(*loc->params));
+ if (!loc->params) {
+ err = -ENOMEM;
+ goto out;
+ }
+ inline_expansion__for_each_parameter(ie, param) {
+ if (param->idx < loc->nparams)
+ btf_encoder__add_loc_param(param, encoder->addr_base,
+ &loc->params[param->idx]);
+ }
+ err = btf_encoder__save_func(encoder, ie->function, NULL, loc);
+ loc = NULL; /* save_func owns loc after this point, including on error */
+ if (!err)
+ return 0;
+out:
+ loc__delete(loc);
+ return err;
+}
+
+static int btf_encoder__loc_state_cmp(const void *a, const void *b)
+{
+ const struct btf_encoder_func_state *sa = a;
+ const struct btf_encoder_func_state *sb = b;
+ const struct loc *la = sa->loc;
+ const struct loc *lb = sb->loc;
+
+ if (la->section_idx != lb->section_idx)
+ return la->section_idx < lb->section_idx ? -1 : 1;
+ if (la->section_offset != lb->section_offset)
+ return la->section_offset < lb->section_offset ? -1 : 1;
+ return 0;
+}
+
+static int btf_encoder__add_locsec(struct btf_encoder *encoder)
+{
+ struct btf_encoder_func_state *states = encoder->loc_states.array;
+ uint32_t section_idx = UINT32_MAX;
+ int err, id;
+
+ if (!encoder->loc_states.cnt)
+ return 0;
+ if (!btf__add_loc_param || !btf__add_loc_param_value || !btf__add_loc_proto ||
+ !btf__add_loc_proto_param || !btf__add_locsec || !btf__add_locsec_loc)
+ return -ENOTSUP;
+ qsort(states, encoder->loc_states.cnt, sizeof(*states), btf_encoder__loc_state_cmp);
+ for (int i = 0; i < encoder->loc_states.cnt; i++) {
+ struct loc *loc = states[i].loc;
+ int *param_ids = calloc(loc->nparams ? loc->nparams : 1, sizeof(*param_ids));
+ int proto;
+
+ if (!param_ids)
+ return -ENOMEM;
+ proto = btf_encoder__add_func_proto_for_state(encoder, &states[i]);
+ if (proto < 0) {
+ free(param_ids);
+ return proto;
+ }
+ loc->func_id = btf__add_func(encoder->btf, loc->name, BTF_FUNC_STATIC, proto);
+ if (loc->func_id < 0) {
+ free(param_ids);
+ return loc->func_id;
+ }
+ for (uint16_t j = 0; j < loc->nparams; j++) {
+ struct loc_param *p = &loc->params[j];
+
+ /* A zero id in LOC_PROTO represents unavailable location data. */
+ if (!p->size)
+ continue;
+ param_ids[j] = btf__add_loc_param(encoder->btf, p->size, p->flags);
+ if (param_ids[j] < 0) {
+ err = param_ids[j];
+ free(param_ids);
+ return err;
+ }
+ for (uint8_t k = 0; k < p->nvalues; k++) {
+ err = btf__add_loc_param_value(encoder->btf, p->values[k]);
+ if (err < 0) {
+ free(param_ids);
+ return err;
+ }
+ }
+ }
+ loc->loc_proto_id = btf__add_loc_proto(encoder->btf);
+ if (loc->loc_proto_id < 0) {
+ free(param_ids);
+ return loc->loc_proto_id;
+ }
+ for (uint16_t j = 0; j < loc->nparams; j++) {
+ err = btf__add_loc_proto_param(encoder->btf, param_ids[j]);
+ if (err < 0) {
+ free(param_ids);
+ return err;
+ }
+ }
+ free(param_ids);
+ }
+ for (int i = 0; i < encoder->loc_states.cnt; i++) {
+ struct loc *loc = states[i].loc;
+
+ if (loc->section_idx != section_idx) {
+ char *name;
+
+ section_idx = loc->section_idx;
+ if (asprintf(&name, "inline%s", encoder->secinfo[section_idx].name) < 0)
+ return -ENOMEM;
+ id = btf__add_locsec(encoder->btf, name);
+ free(name);
+ if (id < 0)
+ return id;
+ }
+ err = btf__add_locsec_loc(encoder->btf, loc->func_id,
+ loc->loc_proto_id, loc->section_offset);
+ if (err < 0)
+ return err;
+ }
+ btf_encoder__delete_func_states(&encoder->loc_states);
+ return 0;
+}
+
/*
* Pre-compute the actual BTF type id for every DWARF core_id.
*
@@ -3144,6 +3420,7 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
struct elf_functions *funcs;
uint32_t core_id;
struct function *fn;
+ struct inline_expansion *ie;
struct tag *pos;
int err = 0;
@@ -3349,11 +3626,21 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
if (ftype__has_uncertain_arg_loc(cu, &fn->proto))
fn->proto.uncertain_parm_loc = 1;
- err = btf_encoder__save_func(encoder, fn, func);
+ err = btf_encoder__save_func(encoder, fn, func, NULL);
if (err)
goto out;
}
+ if (conf_load->btf_gen_inlines) {
+ cu__for_each_inline_expansion(cu, core_id, ie) {
+ if (!ie->name || !ie->function || filter_name(ie->name))
+ continue;
+ err = btf_encoder__add_inline_expansion(encoder, ie);
+ if (err)
+ goto out;
+ }
+ }
+
if (encoder->encode_vars)
err = btf_encoder__encode_cu_variables(encoder);
diff --git a/dwarves.h b/dwarves.h
index b6ddb50..746f94e 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -46,6 +46,13 @@ enum load_steal_kind {
LSK__ABORT,
};
+/* For older libbpf may need to define BTF location data kinds. */
+#ifndef BTF_KIND_LOC_PARAM
+#define BTF_KIND_LOC_PARAM 20
+#define BTF_KIND_LOC_PROTO 21
+#define BTF_KIND_LOCSEC 22
+#endif
+
struct btf_new_opts;
/*
@@ -58,6 +65,13 @@ __weak extern int btf__add_enum64_value(struct btf *btf, const char *name, __u64
__weak extern int btf__add_type_attr(struct btf *btf, const char *value, int ref_type_id);
__weak extern int btf__distill_base(const struct btf *src_btf, struct btf **new_base_btf, struct btf **new_split_btf);
__weak extern struct btf *btf__new_empty_opts(struct btf_new_opts *opts);
+__weak extern int btf__add_loc_param(struct btf *btf, __u32 size, __u32 flags);
+__weak extern int btf__add_loc_param_value(struct btf *btf, __u32 value);
+__weak extern int btf__add_loc_proto(struct btf *btf);
+__weak extern int btf__add_loc_proto_param(struct btf *btf, __u32 id);
+__weak extern int btf__add_locsec(struct btf *btf, const char *name);
+__weak extern int btf__add_locsec_loc(struct btf *btf, __u32 func,
+ __u32 loc_proto, __u32 offset);
/*
* BTF combines all the types into one big CU using btf_dedup(), so for something
@@ -104,6 +118,7 @@ struct conf_load {
bool reproducible_build;
bool btf_decl_tag_kfuncs;
bool btf_gen_distilled_base;
+ bool btf_gen_inlines;
bool btf_attributes;
bool true_signature;
uint8_t hashtable_bits;
diff --git a/pahole.c b/pahole.c
index b2047c1..5c05b97 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1217,6 +1217,13 @@ static bool layout_check(void)
return btf__new_empty_opts != NULL;
}
+static bool location_check(void)
+{
+ return btf__add_loc_param && btf__add_loc_param_value &&
+ btf__add_loc_proto && btf__add_loc_proto_param &&
+ btf__add_locsec && btf__add_locsec_loc;
+}
+
struct btf_feature {
const char *name;
const char *option_alias;
@@ -1244,6 +1251,7 @@ struct btf_feature {
attributes_check),
BTF_NON_DEFAULT_FEATURE(true_signature, true_signature, false),
BTF_NON_DEFAULT_FEATURE_CHECK(layout, btf_gen_layout, false, layout_check),
+ BTF_NON_DEFAULT_FEATURE_CHECK(inline, btf_gen_inlines, false, location_check),
BTF_NON_DEFAULT_FEATURE(force_cu_merging, force_cu_merging, false),
};
@@ -3754,6 +3762,10 @@ try_sole_arg_as_class_names:
}
}
+ if (conf_load.btf_gen_inlines) {
+ /* BTF encoding normally omits inline DIEs; inline BTF needs them. */
+ conf_load.ignore_inline_expansions = false;
+ }
err = cus__load_files(cus, &conf_load, argv + remaining);
if (err != 0) {
if (class_name == NULL && !btf_encode && !ctf_encode) {
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 5/9] btf_loader: Add "inline" annotation for inlined functions
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
` (3 preceding siblings ...)
2026-09-25 18:51 ` [PATCH v3 dwarves 4/9] pahole: Support inline BTF location encoding Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 6/9] btf_loader: Record inline sites in CU for BTF Alan Maguire
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
For a function that is inlined in BTF (signified by a LOCSEC entry
pointing at its FUNC) mark it "inline". This is already done for
DWARF.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
btf_loader.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 52 insertions(+), 1 deletion(-)
diff --git a/btf_loader.c b/btf_loader.c
index 66ffd1a..4dea882 100644
--- a/btf_loader.c
+++ b/btf_loader.c
@@ -30,6 +30,12 @@
#include "dutil.h"
#include "dwarves.h"
+struct btf_locsec_entry {
+ uint32_t func;
+ uint32_t loc_proto;
+ uint32_t offset;
+};
+
static const char *cu__btf_str(struct cu *cu, uint32_t offset)
{
return offset ? btf__str_by_offset(cu->priv, offset) : NULL;
@@ -670,9 +676,54 @@ static int btf__load_types(struct btf *btf, struct cu *cu)
return 0;
}
+static bool btf__has_location_support(void)
+{
+ return btf__add_loc_param && btf__add_loc_param_value &&
+ btf__add_loc_proto && btf__add_loc_proto_param &&
+ btf__add_locsec && btf__add_locsec_loc;
+}
+
+static void btf__mark_inline_functions(struct btf *btf, struct cu *cu)
+{
+ const struct btf_type *type_ptr;
+ uint32_t type_index, type;
+
+ if (!btf__has_location_support())
+ return;
+
+ for (type_index = 1; type_index < btf__type_cnt(btf); type_index++) {
+ const struct btf_locsec_entry *entries;
+ const char *name;
+ uint16_t i;
+
+ type_ptr = btf__type_by_id(btf, type_index);
+ type = btf_kind(type_ptr);
+ if (type != BTF_KIND_LOCSEC)
+ continue;
+
+ name = cu__btf_str(cu, type_ptr->name_off);
+ /* LOCSECs are named "inline<section>", e.g. "inline.text". */
+ if (!name || !strstarts(name, "inline"))
+ continue;
+
+ entries = (const struct btf_locsec_entry *)(type_ptr + 1);
+ for (i = 0; i < btf_vlen(type_ptr); i++) {
+ struct tag *tag = cu__function(cu, entries[i].func);
+
+ if (tag)
+ tag__function(tag)->inlined = DW_INL_declared_inlined;
+ }
+ }
+}
+
static int btf__load_sections(struct btf *btf, struct cu *cu)
{
- return btf__load_types(btf, cu);
+ int err = btf__load_types(btf, cu);
+
+ if (!err)
+ btf__mark_inline_functions(btf, cu);
+
+ return err;
}
static uint32_t class__infer_alignment(const struct conf_load *conf,
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 6/9] btf_loader: Record inline sites in CU for BTF
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
` (4 preceding siblings ...)
2026-09-25 18:51 ` [PATCH v3 dwarves 5/9] btf_loader: Add "inline" annotation for inlined functions Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 7/9] pfunct: Print inline site information Alan Maguire
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Add BTF inline sites to the CU representation.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
btf_loader.c | 47 +++++++++++++++++++++++++++++++++++++++++++----
dwarves.c | 1 +
dwarves.h | 11 +++++++++++
3 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/btf_loader.c b/btf_loader.c
index 4dea882..821a6d8 100644
--- a/btf_loader.c
+++ b/btf_loader.c
@@ -25,8 +25,6 @@
#include <bpf/libbpf.h>
#include <zlib.h>
-#include <gelf.h>
-
#include "dutil.h"
#include "dwarves.h"
@@ -36,6 +34,28 @@ struct btf_locsec_entry {
uint32_t offset;
};
+static void btf__add_inline_site(struct cu *cu, const char *section_name,
+ const struct btf_locsec_entry *entry)
+{
+ struct tag *tag = cu__function(cu, entry->func);
+ struct btf_inline_site *site;
+
+ if (!tag || !section_name)
+ return;
+ site = zalloc(sizeof(*site));
+ if (!site)
+ return;
+ site->section_name = strdup(section_name);
+ if (!site->section_name) {
+ free(site);
+ return;
+ }
+ site->function = tag__function(tag);
+ site->section_offset = entry->offset;
+ site->loc_proto = entry->loc_proto;
+ list_add_tail(&site->node, &cu->btf_inline_sites);
+}
+
static const char *cu__btf_str(struct cu *cu, uint32_t offset)
{
return offset ? btf__str_by_offset(cu->priv, offset) : NULL;
@@ -647,6 +667,12 @@ static int btf__load_types(struct btf *btf, struct cu *cu)
* loaded.
*/
break;
+ case BTF_KIND_LOC_PARAM:
+ case BTF_KIND_LOC_PROTO:
+ case BTF_KIND_LOCSEC:
+ /* LOCSEC is decoded after the ordinary BTF types are loaded. */
+ err = 0;
+ break;
default:
fprintf(stderr, "BTF: idx: %d, Unknown kind %d\n", type_index, type);
fflush(stderr);
@@ -694,7 +720,7 @@ static void btf__mark_inline_functions(struct btf *btf, struct cu *cu)
for (type_index = 1; type_index < btf__type_cnt(btf); type_index++) {
const struct btf_locsec_entry *entries;
const char *name;
- uint16_t i;
+ uint32_t i, nr_entries;
type_ptr = btf__type_by_id(btf, type_index);
type = btf_kind(type_ptr);
@@ -706,12 +732,18 @@ static void btf__mark_inline_functions(struct btf *btf, struct cu *cu)
if (!name || !strstarts(name, "inline"))
continue;
+ /* LOCSEC uses the extended 24-bit vlen introduced with the
+ * location BTF kinds. Older libbpf headers still expose the
+ * original 16-bit btf_vlen() helper.
+ */
+ nr_entries = type_ptr->info & 0x00ffffff;
entries = (const struct btf_locsec_entry *)(type_ptr + 1);
- for (i = 0; i < btf_vlen(type_ptr); i++) {
+ for (i = 0; i < nr_entries; i++) {
struct tag *tag = cu__function(cu, entries[i].func);
if (tag)
tag__function(tag)->inlined = DW_INL_declared_inlined;
+ btf__add_inline_site(cu, name + strlen("inline"), &entries[i]);
}
}
}
@@ -851,6 +883,13 @@ static int cu__fixup_btf_bitfields(const struct conf_load *conf, struct cu *cu)
static void btf__cu_delete(struct cu *cu)
{
+ struct btf_inline_site *site, *n;
+
+ list_for_each_entry_safe(site, n, &cu->btf_inline_sites, node) {
+ list_del(&site->node);
+ free(site->section_name);
+ free(site);
+ }
btf__free(cu->priv);
cu->priv = NULL;
}
diff --git a/dwarves.c b/dwarves.c
index 2bb5c18..fe4f5e4 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -756,6 +756,7 @@ struct cu *cu__new(const char *name, uint8_t addr_size,
cu->dfops = NULL;
INIT_LIST_HEAD(&cu->tags);
INIT_LIST_HEAD(&cu->tool_list);
+ INIT_LIST_HEAD(&cu->btf_inline_sites);
INIT_LIST_HEAD(&cu->node);
cu->addr_size = addr_size;
diff --git a/dwarves.h b/dwarves.h
index 746f94e..ba7dd77 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -292,12 +292,23 @@ struct debug_fmt_ops {
bool has_alignment_info;
};
+/* A BTF LOCSEC record describing one inline expansion. */
+struct function;
+struct btf_inline_site {
+ struct list_head node;
+ struct function *function;
+ char *section_name;
+ uint32_t section_offset;
+ uint32_t loc_proto;
+};
+
#define ARCH_MAX_REGISTER_PARAMS 8
struct cu {
struct list_head node;
struct list_head tags;
struct list_head tool_list; /* To be used by tools such as ctracer */
+ struct list_head btf_inline_sites;
struct ptr_table types_table;
struct ptr_table functions_table;
struct ptr_table tags_table;
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 7/9] pfunct: Print inline site information
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
` (5 preceding siblings ...)
2026-09-25 18:51 ` [PATCH v3 dwarves 6/9] btf_loader: Record inline sites in CU for BTF Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 8/9] tests: Validate inline BTF encoding Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 9/9] man-pages: Document --inline_sites option Alan Maguire
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Print information about inline sites using the --inline_sites
option, BTF only for now. Dumps list of form
0x392c50 [.text +0x392c50] cpumask_test_cpu(int cpu [*(fbreg-40)], const struct cpumask * cpumask [unavailable])
The address is either absolute (where address info is available
and we can compute it) or relative via BTF location info.
Following is the annotated function signature (excluding return
value since these are inline entry points). Unavailable parameters
are marked as such.
Where ELF info is available, arch-specific registers are used, otherwise
we fall back to register numbers (reg0, reg1, ...).
Supports specifying a function name via -f, i.e.
$ pfunct --inline_sites -f ip_send_skb /sys/kernel/btf/vmlinux.inline
0x11a86bc [.text +0x11a86bc] ip_send_skb(struct net * net [reg3], struct sk_buff * skb [reg0])
If ELF info is available, it is used to show containing function;
it can also be supplied by --elf path2elf if BTF is raw as in
/sys/kernel/btf . If available, relative addresses are converted
to absolute from section base, registers are ABI-specific and containing
function displayed:
$ pfunct --inline_sites -f ip_send_skb --elf vmlinux /sys/kernel/btf/vmlinux.inline
0xffffffff821a86bc [ip_push_pending_frames+0x2c, .text +0x11a86bc] ip_send_skb(struct net * net [%rbx], struct sk_buff * skb [%rax])
We can also use the running kernel to derive current base/section addresses;
using --running [optional_module_name]. However, it is necessary
to run via sudo if kptr_restrict is set if absolute addresses and
containing functions are needed, otherwise addresses in /proc/kallsyms
are zeroed and not available:
$ pfunct --inline_sites -f ip_send_skb --running /sys/kernel/btf/vmlinux.inline
0x11a86bc [.text +0x11a86bc] ip_send_skb(struct net * net [%rbx], struct sk_buff * skb [%rax])
$ sudo pfunct --inline_sites -f ip_send_skb --running /sys/kernel/btf/vmlinux.inline
0xffffffffa9da86bc [ip_push_pending_frames+0x2c, .text +0x11a86bc] ip_send_skb(struct net * net [%rbx], struct sk_buff * skb [%rax])
The --running representation will account for kASLR, where the base
address is shifted with respect to the ELF-reported address; contrast
the --running reported address above and that reported with the --elf
option.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: Codex (GPT 5.6)
---
pfunct.c | 718 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 689 insertions(+), 29 deletions(-)
diff --git a/pfunct.c b/pfunct.c
index 1dc88a5..b86fa97 100644
--- a/pfunct.c
+++ b/pfunct.c
@@ -7,6 +7,8 @@
*/
#include <argp.h>
+#include <dirent.h>
+#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -39,15 +41,446 @@ static uint64_t addr;
static char *class_name;
static char *function_name;
static const char *base_btf_file;
+static const char *elf_filename;
+static const char *running_object;
+static bool show_inline_sites;
static struct conf_fprintf conf;
+#define BTF_LOC_PARAM_SIGNED 0x1
+#define BTF_LOC_PARAM_CONST 0x2
+#define BTF_LOC_PARAM_ADDR 0x4
+#define BTF_LOC_PARAM_REG 0x8
+#define BTF_LOC_PARAM_DEREF 0x10
+#define BTF_LOC_PARAM_OFFSET 0x20
+#define BTF_LOC_PARAM_FBREG 0xffffffffU
+
static struct conf_load conf_load = {
.conf_fprintf = &conf,
};
static struct languages languages;
+struct elf_register_names {
+ Dwfl *dwfl;
+ Dwfl_Module *dwfl_module;
+ char *names[32];
+};
+
+struct elf_symtab_ctx {
+ int fd;
+ Elf *elf;
+ struct elf_symtab *symtab;
+ struct elf_register_names regs;
+};
+
+struct elf_function {
+ const char *name;
+ uint64_t addr;
+ uint64_t size;
+};
+
+struct elf_functions {
+ struct elf_function *entries;
+ uint32_t nr_entries;
+ bool owns_names;
+};
+
+struct running_section {
+ char *name;
+ uint64_t addr;
+};
+
+struct running_object_ctx {
+ bool vmlinux;
+ uint64_t addr_base;
+ struct running_section *sections;
+ uint32_t nr_sections;
+ struct elf_functions functions;
+ struct elf_register_names regs;
+};
+
+static int elf_register_names__cache_name(void *arg, int regno,
+ const char *setname __maybe_unused,
+ const char *prefix,
+ const char *regname,
+ int bits __maybe_unused,
+ int type __maybe_unused)
+{
+ struct elf_register_names *regs = arg;
+
+ if (regno < 0 || regno >= (int)ARRAY_SIZE(regs->names) || !regname)
+ return DWARF_CB_OK;
+ if (asprintf(®s->names[regno], "%s%s", prefix ?: "", regname) < 0)
+ return DWARF_CB_ABORT;
+ return DWARF_CB_OK;
+}
+
+static void elf_register_names__init(struct elf_register_names *regs,
+ const char *filename)
+{
+ static const Dwfl_Callbacks callbacks = {
+ .section_address = dwfl_offline_section_address,
+ };
+ int fd;
+
+ regs->dwfl = dwfl_begin(&callbacks);
+ if (!regs->dwfl)
+ return;
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto out_dwfl_end;
+ regs->dwfl_module = dwfl_report_offline(regs->dwfl, filename, filename, fd);
+ if (!regs->dwfl_module)
+ goto out_dwfl_end;
+ if (dwfl_report_end(regs->dwfl, NULL, NULL))
+ goto out_dwfl_end;
+ dwfl_module_register_names(regs->dwfl_module, elf_register_names__cache_name, regs);
+ return;
+
+out_dwfl_end:
+ dwfl_end(regs->dwfl);
+ regs->dwfl = NULL;
+ regs->dwfl_module = NULL;
+}
+
+static void elf_register_names__exit(struct elf_register_names *regs)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(regs->names); i++)
+ free(regs->names[i]);
+ if (regs->dwfl)
+ dwfl_end(regs->dwfl);
+}
+
+static uint64_t elf__addr_base(Elf *elf)
+{
+ Elf_Scn *scn = NULL;
+ GElf_Shdr shdr;
+ uint64_t addr_base = UINT64_MAX;
+
+ while ((scn = elf_nextscn(elf, scn)) != NULL) {
+ if (!gelf_getshdr(scn, &shdr))
+ continue;
+ if ((shdr.sh_flags & SHF_ALLOC) && shdr.sh_addr < addr_base)
+ addr_base = shdr.sh_addr;
+ }
+
+ return addr_base;
+}
+
+static int elf_symtab__init(struct elf_symtab_ctx *ctx, const char *filename)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->fd = -1;
+ ctx->elf = NULL;
+ ctx->symtab = NULL;
+ ctx->fd = open(filename, O_RDONLY);
+ if (ctx->fd < 0)
+ return -1;
+ if (elf_version(EV_CURRENT) == EV_NONE) {
+ fprintf(stderr, "%s: cannot set libelf version.\n", __func__);
+ goto out_close;
+ }
+ ctx->elf = elf_begin(ctx->fd, ELF_C_READ_MMAP, NULL);
+ if (!ctx->elf) {
+ fprintf(stderr, "%s: cannot read %s ELF file.\n", __func__, filename);
+ goto out_close;
+ }
+ ctx->symtab = elf_symtab__new(symtab_name, ctx->elf);
+ elf_register_names__init(&ctx->regs, filename);
+ return 0;
+
+ elf_end(ctx->elf);
+ ctx->elf = NULL;
+out_close:
+ close(ctx->fd);
+ ctx->fd = -1;
+ return -1;
+}
+
+static void elf_symtab__exit(struct elf_symtab_ctx *ctx)
+{
+ elf_register_names__exit(&ctx->regs);
+ elf_symtab__delete(ctx->symtab);
+ if (ctx->elf)
+ elf_end(ctx->elf);
+ if (ctx->fd >= 0)
+ close(ctx->fd);
+}
+
+static int elf_function__cmp(const void *a, const void *b)
+{
+ const struct elf_function *fa = a, *fb = b;
+
+ if (fa->addr != fb->addr)
+ return fa->addr < fb->addr ? -1 : 1;
+ if (fa->size != fb->size)
+ return fa->size < fb->size ? -1 : 1;
+ return strcmp(fa->name, fb->name);
+}
+
+static int elf_functions__collect(struct elf_functions *functions,
+ const struct elf_symtab *symtab)
+{
+ uint32_t index;
+ GElf_Sym sym;
+
+ functions->entries = calloc(elf_symtab__nr_symbols(symtab),
+ sizeof(*functions->entries));
+ if (!functions->entries && elf_symtab__nr_symbols(symtab))
+ return -1;
+ elf_symtab__for_each_symbol(symtab, index, sym) {
+ struct elf_function *function;
+
+ if (elf_sym__type(&sym) != STT_FUNC || !elf_sym__size(&sym) ||
+ elf_sym__section(&sym) == SHN_UNDEF || !*elf_sym__name(&sym, symtab))
+ continue;
+ function = &functions->entries[functions->nr_entries++];
+ function->name = elf_sym__name(&sym, symtab);
+ function->addr = elf_sym__value(&sym);
+ function->size = elf_sym__size(&sym);
+ }
+ qsort(functions->entries, functions->nr_entries,
+ sizeof(*functions->entries), elf_function__cmp);
+ return 0;
+}
+
+static void elf_functions__exit(struct elf_functions *functions)
+{
+ if (functions->owns_names)
+ for (uint32_t i = 0; i < functions->nr_entries; i++)
+ free((char *)functions->entries[i].name);
+ free(functions->entries);
+}
+
+static const struct elf_function *
+elf_functions__find(const struct elf_functions *functions, uint64_t addr)
+{
+ const struct elf_function *best = NULL;
+ uint64_t start_addr;
+ uint32_t left = 0, right = functions->nr_entries;
+
+ while (left < right) {
+ uint32_t mid = left + (right - left) / 2;
+
+ if (functions->entries[mid].addr <= addr)
+ left = mid + 1;
+ else
+ right = mid;
+ }
+ if (left == 0)
+ return NULL;
+ --left;
+ while (left > 0 && functions->entries[left - 1].addr == functions->entries[left].addr)
+ --left;
+ start_addr = functions->entries[left].addr;
+ for (; left < functions->nr_entries &&
+ functions->entries[left].addr == start_addr; left++) {
+ const struct elf_function *function = &functions->entries[left];
+
+ if (addr - function->addr < function->size &&
+ (!best || function->size < best->size))
+ best = function;
+ if (left + 1 == functions->nr_entries || functions->entries[left + 1].addr > addr)
+ break;
+ }
+ return best;
+}
+
+static int running_object__collect_functions(struct running_object_ctx *ctx,
+ const char *module)
+{
+ struct elf_functions *functions = &ctx->functions;
+ FILE *fp;
+ char line[512], name[256], module_name[256], type;
+ unsigned long long addr;
+
+ fp = fopen("/proc/kallsyms", "r");
+ if (!fp)
+ return -1;
+ while (fgets(line, sizeof(line), fp)) {
+ struct elf_function *function;
+ int fields;
+
+ module_name[0] = '\0';
+ fields = sscanf(line, "%llx %c %255s %255s", &addr, &type, name,
+ module_name);
+ if (fields < 3 || !addr || !strchr("TtWw", type))
+ continue;
+ if (module) {
+ size_t len = strlen(module_name);
+
+ if (fields != 4 || len < 3 || module_name[0] != '[' ||
+ module_name[len - 1] != ']' ||
+ strncmp(module_name + 1, module, len - 2) != 0 ||
+ module[len - 2] != '\0')
+ continue;
+ } else if (fields == 4) {
+ continue;
+ }
+ function = realloc(functions->entries,
+ (functions->nr_entries + 1) * sizeof(*functions->entries));
+ if (!function)
+ goto out_err;
+ functions->entries = function;
+ function = &functions->entries[functions->nr_entries];
+ function->name = strdup(name);
+ if (!function->name)
+ goto out_err;
+ function->addr = addr;
+ function->size = 0;
+ functions->nr_entries++;
+ }
+ fclose(fp);
+ functions->owns_names = true;
+ qsort(functions->entries, functions->nr_entries,
+ sizeof(*functions->entries), elf_function__cmp);
+ for (uint32_t i = 0; i < functions->nr_entries;) {
+ uint32_t next = i + 1;
+
+ while (next < functions->nr_entries &&
+ functions->entries[next].addr == functions->entries[i].addr)
+ next++;
+ if (next < functions->nr_entries)
+ for (uint32_t j = i; j < next; j++)
+ functions->entries[j].size =
+ functions->entries[next].addr - functions->entries[j].addr;
+ i = next;
+ }
+ return 0;
+
+out_err:
+ fclose(fp);
+ functions->owns_names = true;
+ elf_functions__exit(functions);
+ return -1;
+}
+
+static void running_object__exit(struct running_object_ctx *ctx)
+{
+ elf_functions__exit(&ctx->functions);
+ elf_register_names__exit(&ctx->regs);
+ for (uint32_t i = 0; i < ctx->nr_sections; i++)
+ free(ctx->sections[i].name);
+ free(ctx->sections);
+}
+
+static uint64_t running_object__section_addr(const struct running_object_ctx *ctx,
+ const char *name)
+{
+ if (ctx->vmlinux)
+ return strcmp(name, ".text") == 0 ? ctx->addr_base : 0;
+ for (uint32_t i = 0; i < ctx->nr_sections; i++)
+ if (strcmp(ctx->sections[i].name, name) == 0)
+ return ctx->sections[i].addr;
+ return 0;
+}
+
+static int running_object__init_vmlinux(struct running_object_ctx *ctx)
+{
+ FILE *fp;
+ char line[512], name[256], type;
+ unsigned long long addr;
+
+ fp = fopen("/proc/kallsyms", "r");
+ if (!fp)
+ return -1;
+ while (fgets(line, sizeof(line), fp)) {
+ if (sscanf(line, "%llx %c %255s", &addr, &type, name) != 3)
+ continue;
+ if (strcmp(name, "_stext") == 0) {
+ ctx->vmlinux = true;
+ ctx->addr_base = addr;
+ fclose(fp);
+ /* kptr_restrict may be active */
+ if (addr == 0)
+ return 0;
+ return running_object__collect_functions(ctx, NULL);
+ }
+ }
+ fclose(fp);
+ return -1;
+}
+
+static int running_object__init_module(struct running_object_ctx *ctx, const char *module)
+{
+ char path[PATH_MAX];
+ struct dirent *entry;
+ DIR *dir;
+
+ if (strchr(module, '/'))
+ return -1;
+ if (snprintf(path, sizeof(path), "/sys/module/%s/sections",
+ module) >= (int)sizeof(path))
+ return -1;
+ dir = opendir(path);
+ if (!dir)
+ return -1;
+ while ((entry = readdir(dir)) != NULL) {
+ struct running_section *section;
+ FILE *fp;
+ unsigned long long addr;
+
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0)
+ continue;
+ if (snprintf(path, sizeof(path), "/sys/module/%s/sections/%s", module,
+ entry->d_name) >= (int)sizeof(path))
+ continue;
+ fp = fopen(path, "r");
+ if (!fp)
+ continue;
+ if (fscanf(fp, "%llx", &addr) != 1) {
+ fclose(fp);
+ continue;
+ }
+ fclose(fp);
+ /* kptr_restrict */
+ if (addr == 0) {
+ closedir(dir);
+ return 0;
+ }
+ section = realloc(ctx->sections,
+ (ctx->nr_sections + 1) * sizeof(*ctx->sections));
+ if (!section)
+ goto out_err;
+ ctx->sections = section;
+ section = &ctx->sections[ctx->nr_sections];
+ section->name = strdup(entry->d_name);
+ if (!section->name)
+ goto out_err;
+ section->addr = addr;
+ if (!ctx->addr_base || addr < ctx->addr_base)
+ ctx->addr_base = addr;
+ ctx->nr_sections++;
+ }
+ closedir(dir);
+ if (!ctx->nr_sections)
+ return -1;
+ return running_object__collect_functions(ctx, module);
+
+out_err:
+ closedir(dir);
+ running_object__exit(ctx);
+ return -1;
+}
+
+static int running_object__init(struct running_object_ctx *ctx, const char *object)
+{
+ int err;
+
+ memset(ctx, 0, sizeof(*ctx));
+ if (strcmp(object, "vmlinux") == 0)
+ err = running_object__init_vmlinux(ctx);
+ else
+ err = running_object__init_module(ctx, object);
+ if (!err) {
+ /* For --running, we can use pfunct ELF itself. */
+ elf_register_names__init(&ctx->regs, "/proc/self/exe");
+ }
+ return err;
+}
+
struct fn_stats {
struct list_head node;
struct tag *tag;
@@ -429,33 +862,186 @@ static int cu_function_iterator(struct cu *cu, void *cookie __maybe_unused)
return 0;
}
-static int elf_symtab__show(char *filename)
+static const char *btf_reg_name(uint32_t reg, const struct elf_register_names *regs,
+ char *buf, size_t len)
{
- int fd = open(filename, O_RDONLY), err = -1;
- if (fd < 0)
- return -1;
+ if (reg == BTF_LOC_PARAM_FBREG)
+ return "fbreg";
+ if (regs && reg < ARRAY_SIZE(regs->names) && regs->names[reg])
+ return regs->names[reg];
+ snprintf(buf, len, "reg%u", reg);
+ return buf;
+}
- if (elf_version(EV_CURRENT) == EV_NONE) {
- fprintf(stderr, "%s: cannot set libelf version.\n", __func__);
- goto out_close;
+static bool btf_inline_site__fprintf_loc(const struct btf_type *type,
+ const struct btf_inline_site *site,
+ uint64_t object_addr_base,
+ const struct elf_register_names *regs, FILE *fp)
+{
+ const uint32_t *values = (const uint32_t *)(type + 1);
+ uint32_t flags = *values++;
+ uint64_t value;
+ char regbuf[16];
+ char regbuf2[16];
+ const char *reg;
+
+ if (flags & BTF_LOC_PARAM_CONST) {
+ value = values[0];
+ if (type->size > sizeof(values[0]))
+ value |= (uint64_t)values[1] << 32;
+ if (flags & BTF_LOC_PARAM_ADDR) {
+ fputs("addr ", fp);
+ if (object_addr_base != UINT64_MAX)
+ fprintf(fp, "%#llx",
+ (unsigned long long)(object_addr_base + value));
+ else
+ fprintf(fp, "%s+%#llx", site->section_name,
+ (unsigned long long)value);
+ return true;
+ }
+ fputs("const ", fp);
+ if (flags & BTF_LOC_PARAM_SIGNED) {
+ if (type->size && type->size < sizeof(value)) {
+ unsigned int bits = type->size * 8;
+
+ if (value & (1ULL << (bits - 1)))
+ value |= ~0ULL << bits;
+ }
+ fprintf(fp, "%lld", (long long)value);
+ } else
+ fprintf(fp, "%#llx", (unsigned long long)value);
+ return true;
}
+ if (!(flags & BTF_LOC_PARAM_REG))
+ return false;
+ reg = btf_reg_name(values[0], regs, regbuf, sizeof(regbuf));
+ if (flags & BTF_LOC_PARAM_DEREF)
+ fprintf(fp, "*(%s", reg);
+ else
+ fputs(reg, fp);
+ if (flags & BTF_LOC_PARAM_OFFSET)
+ fprintf(fp, "%+d", (int32_t)values[1]);
+ else if (flags == BTF_LOC_PARAM_REG && btf_vlen(type) == 2)
+ fprintf(fp, ", %s", btf_reg_name(values[1], regs, regbuf2, sizeof(regbuf2)));
+ if (flags & BTF_LOC_PARAM_DEREF)
+ fputc(')', fp);
+ return true;
+}
- Elf *elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
- if (elf == NULL) {
- fprintf(stderr, "%s: cannot read %s ELF file.\n",
- __func__, filename);
- goto out_close;
+static void btf_inline_site__fprintf(const struct btf_inline_site *site,
+ const struct cu *cu,
+ const struct elf_symtab_ctx *elf_symtab,
+ const struct elf_register_names *regs,
+ const struct elf_functions *elf_functions,
+ const struct running_object_ctx *running, FILE *fp)
+{
+ const struct btf_type *proto;
+ const uint32_t *param_ids;
+ struct ftype *ftype;
+ struct parameter *param;
+ uint16_t i = 0;
+ uint64_t section_addr = 0;
+ uint64_t object_addr_base = UINT64_MAX;
+ const struct elf_function *containing_function = NULL;
+
+ if (!site->function)
+ return;
+ proto = btf__type_by_id(cu->priv, site->loc_proto);
+ ftype = tag__ftype(cu__type(cu, site->function->proto.tag.type));
+ if (!proto || btf_kind(proto) != BTF_KIND_LOC_PROTO || !ftype)
+ return;
+ param_ids = (const uint32_t *)(proto + 1);
+ if (running) {
+ section_addr = running_object__section_addr(running, site->section_name);
+ if (section_addr)
+ object_addr_base = running->addr_base;
}
+ if (elf_symtab) {
+ GElf_Shdr shdr;
+ size_t index;
- GElf_Ehdr ehdr;
- if (gelf_getehdr(elf, &ehdr) == NULL) {
- fprintf(stderr, "%s: cannot get elf header.\n", __func__);
- goto out_elf_end;
+ if (elf_section_by_name(elf_symtab->elf, &shdr, site->section_name, &index))
+ section_addr = shdr.sh_addr;
+ object_addr_base = elf__addr_base(elf_symtab->elf);
+ }
+ if (elf_functions && section_addr)
+ containing_function = elf_functions__find(elf_functions,
+ section_addr + site->section_offset);
+ /* Raw or detached BTF has no ELF section VMA; its LOCSEC offset is
+ * still useful and is the best address available in that case.
+ */
+ fprintf(fp, "%#llx [",
+ (unsigned long long)(section_addr ?
+ section_addr + site->section_offset : site->section_offset));
+ if (containing_function)
+ fprintf(fp, "%s+0x%llx, ", containing_function->name,
+ (unsigned long long)(section_addr + site->section_offset -
+ containing_function->addr));
+ fprintf(fp, "%s +%#x] %s(", site->section_name, site->section_offset,
+ function__name(site->function));
+ ftype__for_each_parameter(ftype, param) {
+ struct tag *type = cu__type(cu, param->tag.type);
+ const struct btf_type *location = NULL;
+ char typebuf[128];
+
+ if (i)
+ fputs(", ", fp);
+ fputs(tag__name(type, cu, typebuf, sizeof(typebuf), &conf), fp);
+ if (!conf.no_parm_names && parameter__name(param))
+ fprintf(fp, " %s", parameter__name(param));
+ if (i < btf_vlen(proto))
+ location = btf__type_by_id(cu->priv, param_ids[i]);
+ fputs(" [", fp);
+ if (!location || btf_kind(location) != BTF_KIND_LOC_PARAM ||
+ !btf_inline_site__fprintf_loc(location, site, object_addr_base,
+ regs, fp))
+ fputs("unavailable", fp);
+ fputc(']', fp);
+ ++i;
}
+ if (ftype->unspec_parms) {
+ if (i)
+ fputs(", ", fp);
+ fputs("...", fp);
+ } else if (i == 0) {
+ fputs("void", fp);
+ }
+ fputs(")\n", fp);
+}
+
+struct btf_inline_sites {
+ const struct elf_symtab_ctx *elf_symtab;
+ const struct elf_register_names *regs;
+ const struct elf_functions *elf_functions;
+ const struct running_object_ctx *running;
+};
+
+static int cu_btf_inline_sites_iterator(struct cu *cu, void *cookie)
+{
+ struct btf_inline_site *site;
+ const struct btf_inline_sites *inline_sites = cookie;
+
+ list_for_each_entry(site, &cu->btf_inline_sites, node)
+ if (!function_name ||
+ strcmp(function__name(site->function), function_name) == 0)
+ btf_inline_site__fprintf(site, cu, inline_sites->elf_symtab,
+ inline_sites->regs,
+ inline_sites->elf_functions, inline_sites->running,
+ stdout);
+ return 0;
+}
+
+static int elf_symtab__show(char *filename)
+{
+ struct elf_symtab_ctx ctx;
+ struct elf_symtab *symtab;
+ int err = -1;
- struct elf_symtab *symtab = elf_symtab__new(symtab_name, elf);
- if (symtab == NULL)
- goto out_elf_end;
+ if (elf_symtab__init(&ctx, filename) || !ctx.symtab) {
+ elf_symtab__exit(&ctx);
+ return -1;
+ }
+ symtab = ctx.symtab;
GElf_Sym sym;
uint32_t index;
@@ -488,12 +1074,8 @@ static int elf_symtab__show(char *filename)
elf_sym__size(&sym));
}
- elf_symtab__delete(symtab);
err = 0;
-out_elf_end:
- elf_end(elf);
-out_close:
- close(fd);
+ elf_symtab__exit(&ctx);
return err;
}
@@ -548,6 +1130,8 @@ ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version;
#define ARGP_compile 302
#define ARGP_devel_version 303
#define ARGP_btf_base 304
+#define ARGP_elf 305
+#define ARGP_running 306
static const struct argp_option pfunct__options[] = {
{
@@ -595,6 +1179,19 @@ static const struct argp_option pfunct__options[] = {
.arg = "PATH",
.doc = "Path to the base BTF file for split BTF input",
},
+ {
+ .name = "elf",
+ .key = ARGP_elf,
+ .arg = "PATH",
+ .doc = "ELF file used to resolve BTF inline-site addresses and containers",
+ },
+ {
+ .name = "running",
+ .key = ARGP_running,
+ .arg = "OBJECT",
+ .flags = OPTION_ARG_OPTIONAL,
+ .doc = "Resolve inline sites against running vmlinux or module OBJECT (Default vmlinux)",
+ },
{
.key = 'g',
.name = "goto_labels",
@@ -615,6 +1212,11 @@ static const struct argp_option pfunct__options[] = {
.name = "inline_expansions",
.doc = "show inline expansions",
},
+ {
+ .key = 'j',
+ .name = "inline_sites",
+ .doc = "show BTF inline sites and parameter locations",
+ },
{
.key = 'I',
.name = "inline_expansions_stats",
@@ -725,6 +1327,10 @@ static error_t pfunct__options_parser(int key, char *arg,
case 'i': show_inline_expansions = verbose = 1;
conf_load.extra_dbg_info = true;
conf_load.get_addr_info = true; break;
+ case 'j': show_inline_sites = true;
+ if (conf_load.format_path == NULL)
+ conf_load.format_path = "btf";
+ break;
case 'I': formatter = fn_stats_inline_exps_fmtr;
conf_load.get_addr_info = true; break;
case 'l': conf.show_decl_info = 1;
@@ -738,6 +1344,8 @@ static error_t pfunct__options_parser(int key, char *arg,
conf_load.get_addr_info = true; break;
case ARGP_symtab: symtab_name = arg ?: ".symtab"; break;
case ARGP_btf_base: base_btf_file = arg; break;
+ case ARGP_elf: elf_filename = arg; break;
+ case ARGP_running: running_object = arg ?: "vmlinux"; break;
case ARGP_no_parm_names: conf.no_parm_names = 1; break;
case ARGP_compile:
expand_types = true;
@@ -768,6 +1376,12 @@ static struct argp pfunct__argp = {
int main(int argc, char *argv[])
{
int err, remaining, rc = EXIT_FAILURE;
+ struct elf_symtab_ctx elf_symtab;
+ struct elf_functions elf_functions = {};
+ struct btf_inline_sites inline_sites = {};
+ struct running_object_ctx running = {};
+ bool have_inline_elf = false;
+ bool have_running = false;
if (argp_parse(&pfunct__argp, argc, argv, 0, &remaining, NULL) ||
(remaining == argc && class_name == NULL && function_name == NULL)) {
@@ -788,6 +1402,11 @@ int main(int argc, char *argv[])
fputs("pfunct: insufficient memory\n", stderr);
goto out;
}
+ if (running_object && (elf_filename || !show_inline_sites)) {
+ fputs("pfunct: --running is only supported with --inline_sites and without --elf\n",
+ stderr);
+ goto out_dwarves_exit;
+ }
dwarves__resolve_cacheline_size(&conf_load, 0);
@@ -795,9 +1414,8 @@ int main(int argc, char *argv[])
if (base_btf_file == NULL) {
const char *filename = argv[remaining];
- if (filename &&
- strstarts(filename, "/sys/kernel/btf/") &&
- strstr(filename, "/vmlinux") == NULL)
+ if (filename && strstarts(filename, "/sys/kernel/btf/") &&
+ strcmp(filename, vmlinux_path__btf_filename()) != 0)
base_btf_file = vmlinux_path__btf_filename();
}
@@ -820,7 +1438,8 @@ int main(int argc, char *argv[])
goto out_dwarves_exit;
}
- if (function_name || (!function_name && show_all_matches) || class_name)
+ if ((function_name && !show_inline_sites) ||
+ (!function_name && show_all_matches) || class_name)
conf_load.steal = pfunct_stealer;
try_sole_arg_as_function_name:
@@ -844,6 +1463,38 @@ try_sole_arg_as_function_name:
bool is_btf = conf_load.format_path && strcasecmp(conf_load.format_path, "btf") == 0;
cus__for_each_cu(cus, cu_unique_iterator, is_btf ? (void *)1 : NULL, NULL);
+ if (running_object) {
+ if (running_object__init(&running, running_object)) {
+ fprintf(stderr, "pfunct: cannot resolve running object '%s'\n",
+ running_object);
+ goto out_elf_symtab_exit;
+ }
+ have_running = true;
+ inline_sites.running = &running;
+ inline_sites.regs = &running.regs;
+ inline_sites.elf_functions = &running.functions;
+ } else if (show_inline_sites && (elf_filename || argv[remaining])) {
+ const char *inline_elf_filename = elf_filename ?: argv[remaining];
+
+ if (elf_symtab__init(&elf_symtab, inline_elf_filename)) {
+ if (!elf_filename)
+ goto no_inline_elf;
+ elf_functions__exit(&elf_functions);
+ elf_symtab__exit(&elf_symtab);
+ goto out_cus_delete;
+ }
+ have_inline_elf = true;
+ if (elf_symtab.symtab &&
+ elf_functions__collect(&elf_functions, elf_symtab.symtab)) {
+ elf_functions__exit(&elf_functions);
+ elf_symtab__exit(&elf_symtab);
+ goto out_cus_delete;
+ }
+ inline_sites.elf_symtab = &elf_symtab;
+ inline_sites.regs = &elf_symtab.regs;
+ inline_sites.elf_functions = &elf_functions;
+ }
+no_inline_elf:
if (addr) {
struct cu *cu;
@@ -852,17 +1503,26 @@ try_sole_arg_as_function_name:
if (f == NULL) {
fprintf(stderr, "pfunct: No function found at %#llx!\n",
(unsigned long long)addr);
- goto out_cus_delete;
+ goto out_elf_symtab_exit;
}
function__show(f, cu);
} else if (show_total_inline_expansion_stats)
print_total_inline_stats();
+ else if (show_inline_sites)
+ cus__for_each_cu(cus, cu_btf_inline_sites_iterator, &inline_sites, NULL);
else if (expand_types)
cus__for_each_cu(cus, cu_function_iterator, NULL, NULL);
else if (function_name == NULL)
print_fn_stats(formatter);
rc = EXIT_SUCCESS;
+out_elf_symtab_exit:
+ if (have_running)
+ running_object__exit(&running);
+ if (have_inline_elf) {
+ elf_functions__exit(&elf_functions);
+ elf_symtab__exit(&elf_symtab);
+ }
out_cus_delete:
cus__delete(cus);
fn_stats__delete_list();
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 8/9] tests: Validate inline BTF encoding
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
` (6 preceding siblings ...)
2026-09-25 18:51 ` [PATCH v3 dwarves 7/9] pfunct: Print inline site information Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 9/9] man-pages: Document --inline_sites option Alan Maguire
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Exercise nested inline calls, constant arguments, no-argument inline
functions, and repeated inline call sites. Verify that ordinary wrapper
function BTF remains present, inline functions are annotated by pfunct,
and when bpftool is available, LOCSEC contains multiple inline sites.
Also verify that pfunct:
- lists the ordinary and inline BTF functions;
- prints inline-site parameter locations from detached raw BTF using
the regN fallback;
- uses an explicitly supplied ELF file to resolve register names and
containing functions; and
- automatically uses ELF information when BTF is embedded in the ELF.
Use a single-register inline parameter rather than asserting ABI-specific
register numbers or aggregate calling conventions, allowing the test to
run across supported architectures.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: Codex (GPT-5.6)
---
tests/btf_inline.sh | 147 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
create mode 100755 tests/btf_inline.sh
diff --git a/tests/btf_inline.sh b/tests/btf_inline.sh
new file mode 100755
index 0000000..a782a65
--- /dev/null
+++ b/tests/btf_inline.sh
@@ -0,0 +1,147 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+
+script_dir=$(dirname "$0")
+source "$script_dir/test_lib.sh"
+
+outdir=$(make_tmpdir)
+trap cleanup EXIT
+
+title_log "Validation of additive inline BTF encoding."
+
+CC=${CC:-gcc}
+if ! command -v "$CC" >/dev/null 2>&1; then
+ info_log "skip: gcc not available"
+ test_skip
+fi
+
+if ! pahole --supported_btf_features | tr ',' '\n' | grep -Fxq inline; then
+ info_log "skip: pahole was built without BTF location support"
+ test_skip
+fi
+
+src=${outdir}/btf_inline.c
+obj=${outdir}/btf_inline
+btf=${outdir}/btf_inline.btf
+
+cat > "$src" <<'EOF'
+/* Keep the inline functions small enough to guarantee inlining at -O2. */
+static inline int scale(int value, int factor)
+{
+ return value * factor;
+}
+
+static inline int add_bias(int value, int bias)
+{
+ return scale(value, 2) + bias;
+}
+
+static volatile int inline_bias;
+
+static inline int add_global(void)
+{
+ return inline_bias;
+}
+
+static inline int combine(int value, int bias)
+{
+ return add_bias(value, bias) + add_global();
+}
+
+__attribute__((noinline)) int wrapper_once(int value, int bias)
+{
+ return combine(value, bias);
+}
+
+__attribute__((noinline)) int wrapper_twice(int value, int bias)
+{
+ return combine(value, bias) + combine(bias, value);
+}
+
+int main(void)
+{
+ return wrapper_once(1, 3) + wrapper_twice(2, 4);
+}
+EOF
+
+"$CC" -g -O2 -o "$obj" "$src"
+if [[ $? -ne 0 ]]; then
+ error_log "Could not compile $src"
+ test_fail
+fi
+
+if ! pahole --btf_features=default,inline --btf_encode_detached="$btf" "$obj"; then
+ error_log "Could not encode inline BTF for $obj"
+ test_fail
+fi
+
+# Inline location BTF augments the ordinary BTF_KIND_FUNC records for the
+# non-inline wrappers. It must cover nested calls, a constant parameter, a
+# no-argument inline function, and multiple call sites of the same inline.
+if ! pfunct --all --format_path=btf "$btf" 2>/dev/null | grep -Fq "int wrapper_once(int value, int bias);"; then
+ error_log "wrapper_once() is missing from inline BTF"
+ test_fail
+fi
+
+if ! pfunct --all --format_path=btf "$btf" 2>/dev/null | grep -Fq "int wrapper_twice(int value, int bias);"; then
+ error_log "wrapper_twice() is missing from inline BTF"
+ test_fail
+fi
+
+if ! pfunct --all --format_path=btf "$btf" 2>/dev/null | grep -Fq "inline int scale(int value, int factor);"; then
+ error_log "scale() inline location is missing from BTF"
+ test_fail
+fi
+
+if ! pfunct --all --format_path=btf "$btf" 2>/dev/null | grep -Fq "inline int add_bias(int value, int bias);"; then
+ error_log "add_bias() inline location is missing from BTF"
+ test_fail
+fi
+
+if ! pfunct --all --format_path=btf "$btf" 2>/dev/null | grep -Fq "inline int add_global(void);"; then
+ error_log "add_global() inline location is missing from BTF"
+ test_fail
+fi
+
+if ! pfunct --all --format_path=btf "$btf" 2>/dev/null | grep -Fq "inline int combine(int value, int bias);"; then
+ error_log "combine() inline location is missing from BTF"
+ test_fail
+fi
+
+if ! inline_sites=$(pfunct --inline_sites "$btf" 2>/dev/null) ||
+ ! grep -Eq 'scale\(int value \[reg[0-9]+\], int factor \[const 0x2\]\)' <<<"$inline_sites"; then
+ error_log "pfunct did not render a fallback register name for an inline parameter"
+ test_fail
+fi
+
+if ! inline_sites=$(pfunct --inline_sites --elf "$obj" "$btf" 2>/dev/null) ||
+ ! grep -Eq 'scale\(int value \[[^][,[:space:]]+\], int factor \[const 0x2\]\)' <<<"$inline_sites" ||
+ grep -Eq 'scale\(int value \[reg[0-9]+\], int factor \[const 0x2\]\)' <<<"$inline_sites" ||
+ ! grep -Eq '\[wrapper_once\+0x[0-9a-f]+, ' <<<"$inline_sites"; then
+ error_log "pfunct did not resolve inline-site registers and containing functions through ELF"
+ test_fail
+fi
+
+if command -v bpftool >/dev/null 2>&1; then
+ locsec_vlen=$(bpftool btf dump file "$btf" format raw 2>/dev/null |
+ sed -n 's/.*LOCSEC .*vlen=\([0-9][0-9]*\).*/\1/p')
+ if [[ -z "$locsec_vlen" || "$locsec_vlen" -lt 2 ]]; then
+ error_log "multiple inline call sites are missing from LOCSEC"
+ test_fail
+ fi
+fi
+
+if ! pahole -J --btf_features=default,inline "$obj"; then
+ error_log "Could not encode embedded inline BTF for $obj"
+ test_fail
+fi
+
+if ! inline_sites=$(pfunct --inline_sites "$obj" 2>/dev/null) ||
+ ! grep -Eq 'scale\(int value \[[^][,[:space:]]+\], int factor \[const 0x2\]\)' <<<"$inline_sites" ||
+ grep -Eq 'scale\(int value \[reg[0-9]+\], int factor \[const 0x2\]\)' <<<"$inline_sites" ||
+ ! grep -Eq '\[wrapper_once\+0x[0-9a-f]+, ' <<<"$inline_sites"; then
+ error_log "pfunct did not resolve inline-site registers and containing functions from embedded ELF BTF"
+ test_fail
+fi
+
+test_pass
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 dwarves 9/9] man-pages: Document --inline_sites option
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
` (7 preceding siblings ...)
2026-09-25 18:51 ` [PATCH v3 dwarves 8/9] tests: Validate inline BTF encoding Alan Maguire
@ 2026-09-25 18:51 ` Alan Maguire
8 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-09-25 18:51 UTC (permalink / raw)
To: acme, ast, andrii, eddyz87, qmo
Cc: dwarves, jolsa, daniel, ihor.solodrai, yonghong.song, song,
martin.lau, memxor, emil, bpf, nsc, puranjay, yatsenko,
Alan Maguire
Ensure it is documented along with -f file, --elf path2elf
and --running and include example.
Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
man-pages/pfunct.1 | 74 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 72 insertions(+), 2 deletions(-)
diff --git a/man-pages/pfunct.1 b/man-pages/pfunct.1
index 5ce203b..9935f9d 100644
--- a/man-pages/pfunct.1
+++ b/man-pages/pfunct.1
@@ -1,6 +1,6 @@
.\" Man page for pfunct
.\" Licensed under version 2 of the GNU General Public License.
-.TH pfunct 1 "September 2, 2026" "dwarves" "dwarves"
+.TH pfunct 1 "September 20, 2026" "dwarves" "dwarves"
.\"
.SH NAME
pfunct \- Shows function information in debug information.
@@ -82,7 +82,9 @@ Show just external functions (DWARF only).
.TP
.B \-f,\-\-function=FUNCTION
-Show just FUNCTION.
+Show just FUNCTION. When used with
+.B \-j,\-\-inline_sites,
+show only sites for the specified inlined function.
.TP
.B \-F, \-\-format_path
@@ -96,6 +98,16 @@ Use PATH as the base BTF when loading split BTF. If no format path is given,
this selects BTF loading. This option is not needed for kernel module BTF read
from /sys/kernel/btf/MODULE, where the vmlinux BTF is selected automatically.
+.TP
+.B \-\-running[=OBJECT]
+Resolve BTF inline-site addresses against the running kernel instead of an ELF
+companion. OBJECT is vmlinux by default, or the name of a loaded module.
+For vmlinux, only .text inline sites are shown, using _stext from
+/proc/kallsyms. For a module, section bases are read from
+/sys/module/OBJECT/sections. Address visibility is subject to normal kernel
+permissions, including kptr_restrict. When symbol addresses are visible,
+pfunct also uses /proc/kallsyms to identify the containing function.
+
.TP
.B \-g,\-\-goto_labels
Show number of goto labels (DWARF only).
@@ -112,6 +124,34 @@ Not declared inline, inlined by compiler (DWARF only).
.B \-i, \-\-inline_expansions
Show inline expansion stats (DWARF only).
+.TP
+.B \-j, \-\-inline_sites
+Show BTF inline sites encoded in LOCSEC records. If no
+.B \-\-format_path
+is specified, this option automatically selects BTF input.
+
+Each site includes its address, section-relative offset, inlined function
+prototype, and parameter locations. Parameter locations are shown as
+registers, dereferenced registers, constants, addresses, or
+.B unavailable
+when location information is absent. Without ELF information, registers use
+the generic
+.B regN
+notation and addresses are section-relative. When the input is an ELF file
+with embedded BTF, its ELF information is used automatically. For raw or
+detached BTF, use
+.B \-\-elf
+to resolve absolute addresses, enclosing ELF functions, and
+architecture-specific register names.
+
+.TP
+.B \-\-elf=PATH
+Use PATH as the ELF companion for
+.B \-j,\-\-inline_sites.
+Its section addresses, symbol table, and register names are used to resolve
+detached or raw BTF inline sites to absolute addresses, their containing
+functions, and architecture-specific register names.
+
.TP
.B \-l, \-\-decl_info
Show source code info.
@@ -204,6 +244,36 @@ $ pfunct --btf_base=vmlinux.btf --all module.btf
\&...
.fi
+.P
+Show inline BTF sites from a detached BTF file, resolving each site to the
+containing function in its ELF companion:
+.PP
+.nf
+$ pfunct --inline_sites --elf ./program ./program.btf
+0x401120 [wrapper+0x0, .text +0x100] add_one(int value [%rdi])
+\&...
+.fi
+
+.P
+For an ELF file with embedded BTF, the ELF companion is inferred from the
+input, so no separate
+.B \-\-elf
+option is needed:
+.PP
+.nf
+$ pfunct --inline_sites ./program
+0x401120 [wrapper+0x0, .text +0x100] add_one(int value [%rdi])
+\&...
+.fi
+
+.P
+Limit inline-site output to the inlined function named add_one:
+.PP
+.nf
+$ pfunct --inline_sites --elf ./program -f add_one ./program.btf
+\&...
+.fi
+
.P
Show functions that were not declared with "inline" but were inlined:
.PP
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions
2026-09-25 18:51 ` [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions Alan Maguire
@ 2026-09-30 15:45 ` Jiri Olsa
2026-10-02 11:02 ` Alan Maguire
0 siblings, 1 reply; 12+ messages in thread
From: Jiri Olsa @ 2026-09-30 15:45 UTC (permalink / raw)
To: Alan Maguire
Cc: acme, ast, andrii, eddyz87, qmo, dwarves, daniel, ihor.solodrai,
yonghong.song, song, martin.lau, memxor, emil, bpf, nsc, puranjay,
yatsenko
On Fri, Sep 25, 2026 at 07:51:50PM +0100, Alan Maguire wrote:
SNIP
> -static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblock, struct cu *cu, struct conf_load *conf)
> +static int die__process_inline_expansion(Dwarf_Die *die,
> + struct inline_expansion *exp,
> + struct lexblock *lexblock,
> + struct cu *cu, struct conf_load *conf)
> {
> Dwarf_Die child;
> struct tag *tag;
> + int parm_idx = 0;
>
> if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0)
> return 0;
> @@ -2957,6 +2965,7 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
> die = &child;
> do {
> uint32_t id;
> + bool add_to_inline_expansion = false;
>
> switch (dwarf_tag(die)) {
> case DW_TAG_call_site:
> @@ -2975,13 +2984,10 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
> goto out_enomem;
> continue;
> case DW_TAG_formal_parameter:
> - /*
> - * Inline expansions can have their own formal
> - * parameter children duplicating the abstract
> - * origin's parameters. These are not needed
> - * for type reconstruction — skip them.
> - */
> - continue;
> + tag = die__create_new_parameter(die, NULL, lexblock, exp,
> + cu, conf, parm_idx++);
> + add_to_inline_expansion = true;
> + break;
> case DW_TAG_inlined_subroutine:
> tag = die__create_new_inline_expansion(die, lexblock, cu, conf);
> break;
> @@ -3010,6 +3016,8 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
>
> if (cu__table_add_tag(cu, tag, &id) < 0)
> goto out_delete_tag;
> + if (add_to_inline_expansion)
could we just check (tag->tag == DW_TAG_formal_parameter) instead the
new bool?
> + inline_expansion__add_parameter(exp, tag__parameter(tag));
> hash:
> cu__hash(cu, tag);
> struct dwarf_tag *dtag = tag__dwarf(tag);
> @@ -3032,8 +3040,8 @@ static struct tag *die__create_new_inline_expansion(Dwarf_Die *die,
> if (exp == NULL)
> return NULL;
>
> - if (die__process_inline_expansion(die, lexblock, cu, conf) != 0) {
> - tag__free(&exp->ip.tag, cu);
> + if (die__process_inline_expansion(die, exp, lexblock, cu, conf) != 0) {
> + tag__delete(&exp->ip.tag, cu);
> return NULL;
> }
>
> @@ -3115,7 +3123,8 @@ 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, NULL,
> + cu, conf, param_idx++);
> break;
> case DW_TAG_variable:
> tag = die__create_new_variable(die, cu, conf, 0);
> @@ -3562,75 +3571,103 @@ static void __tag__print_abstract_origin_not_found(struct tag *tag,
> #define tag__print_abstract_origin_not_found(tag) \
> __tag__print_abstract_origin_not_found(tag, __func__, __LINE__)
>
> -static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
> +static void parameter__share_state_with_abstract_origin(struct parameter *parm,
> + struct parameter *oparm)
> +{
> + if (parm->has_loc)
> + oparm->has_loc = parm->has_loc;
> + if (parm->has_const_value)
> + oparm->has_const_value = parm->has_const_value;
> + if (parm->loc_const_value)
> + oparm->loc_const_value = parm->loc_const_value;
> + if (parm->loc_stack)
> + oparm->loc_stack = parm->loc_stack;
> + if (parm->loc_reg != PARAMETER_UNKNOWN_REG)
> + oparm->loc_reg = parm->loc_reg;
> + if (parm->type_byte_size != 0)
> + oparm->type_byte_size = parm->type_byte_size;
> + if (parm->passed_in_memory)
> + oparm->passed_in_memory = parm->passed_in_memory;
> + oparm->first_reg_fields |= parm->first_reg_fields;
> + oparm->second_reg_fields |= parm->second_reg_fields;
> + if (parm->true_sig_member_name && !oparm->true_sig_member_name) {
> + oparm->true_sig_member_name = parm->true_sig_member_name;
> + oparm->true_sig_type = parm->true_sig_type;
> + oparm->true_sig_type_from_types = parm->true_sig_type_from_types;
> + }
> + if (parm->optimized)
> + oparm->optimized = parm->optimized;
> + if (parm->unexpected_reg)
> + oparm->unexpected_reg = parm->unexpected_reg;
> +}
> +
> +static void parameter__recode_dwarf_type(struct parameter *parm, struct cu *cu,
> + struct ftype *ftype)
could this be added in separate change? seems like it could
ease up the readability a bit
> {
> - struct parameter *pos;
> struct dwarf_cu *dcu = cu->priv;
> - struct ftype *type = tag__ftype(tag);
> -
> - ftype__for_each_parameter(type, pos) {
> - struct dwarf_tag *dpos = tag__dwarf(&pos->tag);
> - struct parameter *opos;
> - struct dwarf_tag *dtype;
> + struct dwarf_tag *dparm = tag__dwarf(&parm->tag);
> + struct dwarf_tag *dtype;
>
> - if (dpos->type == 0) {
> - if (dpos->abstract_origin == 0) {
> - /* Function without parameters */
> - pos->tag.type = 0;
> - continue;
> - }
> - dtype = dwarf_cu__find_tag_by_ref(dcu, dpos, abstract_origin);
> - if (dtype == NULL) {
> - tag__print_abstract_origin_not_found(&pos->tag);
> - continue;
> - }
> - opos = tag__parameter(dtag__tag(dtype));
> - pos->name = opos->name;
> - if (pos->idx != opos->idx)
> - type->reordered_parm = 1;
> - pos->tag.type = dtag__tag(dtype)->type;
> - /* share location information between parameter and
> - * abstract origin; if neither have location, we will
> - * mark the parameter as optimized out. Also share
> - * info regarding unexpected register use for
> - * parameters.
> - */
> - if (pos->has_loc)
> - opos->has_loc = pos->has_loc;
> - if (pos->has_const_value)
> - opos->has_const_value = pos->has_const_value;
> - if (pos->loc_const_value)
> - opos->loc_const_value = pos->loc_const_value;
> - if (pos->loc_stack)
> - opos->loc_stack = pos->loc_stack;
> - if (pos->loc_reg != PARAMETER_UNKNOWN_REG)
> - opos->loc_reg = pos->loc_reg;
> - if (pos->type_byte_size != 0)
> - opos->type_byte_size = pos->type_byte_size;
> - if (pos->passed_in_memory)
> - opos->passed_in_memory = pos->passed_in_memory;
> - opos->first_reg_fields |= pos->first_reg_fields;
> - opos->second_reg_fields |= pos->second_reg_fields;
> - if (pos->true_sig_member_name && !opos->true_sig_member_name) {
> - opos->true_sig_member_name = pos->true_sig_member_name;
> - opos->true_sig_type = pos->true_sig_type;
> - opos->true_sig_type_from_types = pos->true_sig_type_from_types;
> - }
> + if (dparm->type == 0) {
> + struct parameter *oparm;
>
> - if (pos->optimized)
> - opos->optimized = pos->optimized;
> - if (pos->unexpected_reg)
> - opos->unexpected_reg = pos->unexpected_reg;
> - continue;
> + if (dparm->abstract_origin == 0) {
> + parm->tag.type = 0;
> + return;
> }
>
> - dtype = dwarf_cu__find_type_by_ref(dcu, dpos, type);
> + dtype = dwarf_cu__find_tag_by_ref(dcu, dparm, abstract_origin);
> if (dtype == NULL) {
> - tag__print_type_not_found(&pos->tag);
> - continue;
> + tag__print_abstract_origin_not_found(&parm->tag);
> + return;
> }
> - pos->tag.type = dtype->small_id;
> +
> + oparm = tag__parameter(dtag__tag(dtype));
> + parm->name = oparm->name;
> + if (ftype != NULL && parm->idx != oparm->idx)
> + ftype->reordered_parm = 1;
looks like we could end up with wrong argument index if the dwarf
info is missing for the argument, my agent is suggesting this fix
it gets me over 16k changes like this:
-0xffffffff8100e322 [__show_trace_log_lvl+0xb2, .text +0xe322] get_stack_pointer(struct task_struct * task [const 0], struct pt_regs * regs [%r14])
+0xffffffff8100e322 [__show_trace_log_lvl+0xb2, .text +0xe322] get_stack_pointer(struct task_struct * task [%r14], struct pt_regs * regs [const 0])
I tried to validate one against dwarf info and it seems
to be correct with the fix ;-)
thanks,
jirak
---
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 49a88d526d9e..d0fb8fda08cd 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -3867,8 +3867,15 @@ static void parameter__recode_dwarf_type(struct parameter *parm, struct cu *cu,
oparm = tag__parameter(dtag__tag(dtype));
parm->name = oparm->name;
- if (ftype != NULL && parm->idx != oparm->idx)
- ftype->reordered_parm = 1;
+ if (ftype != NULL) {
+ if (parm->idx != oparm->idx)
+ ftype->reordered_parm = 1;
+ } else {
+ /* Inline-site parameters may omit or reorder children;
+ * use the abstract origin's position as the slot.
+ */
+ parm->idx = oparm->idx;
+ }
parm->tag.type = dtag__tag(dtype)->type;
if (ftype != NULL)
parameter__share_state_with_abstract_origin(parm, oparm);
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions
2026-09-30 15:45 ` Jiri Olsa
@ 2026-10-02 11:02 ` Alan Maguire
0 siblings, 0 replies; 12+ messages in thread
From: Alan Maguire @ 2026-10-02 11:02 UTC (permalink / raw)
To: Jiri Olsa
Cc: acme, ast, andrii, eddyz87, qmo, dwarves, daniel, ihor.solodrai,
yonghong.song, song, martin.lau, memxor, emil, bpf, nsc, puranjay,
yatsenko
On 30/09/2026 16:45, Jiri Olsa wrote:
> On Fri, Sep 25, 2026 at 07:51:50PM +0100, Alan Maguire wrote:
>
> SNIP
>
>> -static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblock, struct cu *cu, struct conf_load *conf)
>> +static int die__process_inline_expansion(Dwarf_Die *die,
>> + struct inline_expansion *exp,
>> + struct lexblock *lexblock,
>> + struct cu *cu, struct conf_load *conf)
>> {
>> Dwarf_Die child;
>> struct tag *tag;
>> + int parm_idx = 0;
>>
>> if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0)
>> return 0;
>> @@ -2957,6 +2965,7 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
>> die = &child;
>> do {
>> uint32_t id;
>> + bool add_to_inline_expansion = false;
>>
>> switch (dwarf_tag(die)) {
>> case DW_TAG_call_site:
>> @@ -2975,13 +2984,10 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
>> goto out_enomem;
>> continue;
>> case DW_TAG_formal_parameter:
>> - /*
>> - * Inline expansions can have their own formal
>> - * parameter children duplicating the abstract
>> - * origin's parameters. These are not needed
>> - * for type reconstruction — skip them.
>> - */
>> - continue;
>> + tag = die__create_new_parameter(die, NULL, lexblock, exp,
>> + cu, conf, parm_idx++);
>> + add_to_inline_expansion = true;
>> + break;
>> case DW_TAG_inlined_subroutine:
>> tag = die__create_new_inline_expansion(die, lexblock, cu, conf);
>> break;
>> @@ -3010,6 +3016,8 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
>>
>> if (cu__table_add_tag(cu, tag, &id) < 0)
>> goto out_delete_tag;
>> + if (add_to_inline_expansion)
>
> could we just check (tag->tag == DW_TAG_formal_parameter) instead the
> new bool?
>
yep, will fix.
>
>> + inline_expansion__add_parameter(exp, tag__parameter(tag));
>> hash:
>> cu__hash(cu, tag);
>> struct dwarf_tag *dtag = tag__dwarf(tag);
>> @@ -3032,8 +3040,8 @@ static struct tag *die__create_new_inline_expansion(Dwarf_Die *die,
>> if (exp == NULL)
>> return NULL;
>>
>> - if (die__process_inline_expansion(die, lexblock, cu, conf) != 0) {
>> - tag__free(&exp->ip.tag, cu);
>> + if (die__process_inline_expansion(die, exp, lexblock, cu, conf) != 0) {
>> + tag__delete(&exp->ip.tag, cu);
>> return NULL;
>> }
>>
>> @@ -3115,7 +3123,8 @@ 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, NULL,
>> + cu, conf, param_idx++);
>> break;
>> case DW_TAG_variable:
>> tag = die__create_new_variable(die, cu, conf, 0);
>> @@ -3562,75 +3571,103 @@ static void __tag__print_abstract_origin_not_found(struct tag *tag,
>> #define tag__print_abstract_origin_not_found(tag) \
>> __tag__print_abstract_origin_not_found(tag, __func__, __LINE__)
>>
>> -static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
>> +static void parameter__share_state_with_abstract_origin(struct parameter *parm,
>> + struct parameter *oparm)
>> +{
>> + if (parm->has_loc)
>> + oparm->has_loc = parm->has_loc;
>> + if (parm->has_const_value)
>> + oparm->has_const_value = parm->has_const_value;
>> + if (parm->loc_const_value)
>> + oparm->loc_const_value = parm->loc_const_value;
>> + if (parm->loc_stack)
>> + oparm->loc_stack = parm->loc_stack;
>> + if (parm->loc_reg != PARAMETER_UNKNOWN_REG)
>> + oparm->loc_reg = parm->loc_reg;
>> + if (parm->type_byte_size != 0)
>> + oparm->type_byte_size = parm->type_byte_size;
>> + if (parm->passed_in_memory)
>> + oparm->passed_in_memory = parm->passed_in_memory;
>> + oparm->first_reg_fields |= parm->first_reg_fields;
>> + oparm->second_reg_fields |= parm->second_reg_fields;
>> + if (parm->true_sig_member_name && !oparm->true_sig_member_name) {
>> + oparm->true_sig_member_name = parm->true_sig_member_name;
>> + oparm->true_sig_type = parm->true_sig_type;
>> + oparm->true_sig_type_from_types = parm->true_sig_type_from_types;
>> + }
>> + if (parm->optimized)
>> + oparm->optimized = parm->optimized;
>> + if (parm->unexpected_reg)
>> + oparm->unexpected_reg = parm->unexpected_reg;
>> +}
>> +
>> +static void parameter__recode_dwarf_type(struct parameter *parm, struct cu *cu,
>> + struct ftype *ftype)
>
> could this be added in separate change? seems like it could
> ease up the readability a bit
>
ok so a separate patch to rework recoding/sharing with abstract origin?
Sure.
>> {
>> - struct parameter *pos;
>> struct dwarf_cu *dcu = cu->priv;
>> - struct ftype *type = tag__ftype(tag);
>> -
>> - ftype__for_each_parameter(type, pos) {
>> - struct dwarf_tag *dpos = tag__dwarf(&pos->tag);
>> - struct parameter *opos;
>> - struct dwarf_tag *dtype;
>> + struct dwarf_tag *dparm = tag__dwarf(&parm->tag);
>> + struct dwarf_tag *dtype;
>>
>> - if (dpos->type == 0) {
>> - if (dpos->abstract_origin == 0) {
>> - /* Function without parameters */
>> - pos->tag.type = 0;
>> - continue;
>> - }
>> - dtype = dwarf_cu__find_tag_by_ref(dcu, dpos, abstract_origin);
>> - if (dtype == NULL) {
>> - tag__print_abstract_origin_not_found(&pos->tag);
>> - continue;
>> - }
>> - opos = tag__parameter(dtag__tag(dtype));
>> - pos->name = opos->name;
>> - if (pos->idx != opos->idx)
>> - type->reordered_parm = 1;
>> - pos->tag.type = dtag__tag(dtype)->type;
>> - /* share location information between parameter and
>> - * abstract origin; if neither have location, we will
>> - * mark the parameter as optimized out. Also share
>> - * info regarding unexpected register use for
>> - * parameters.
>> - */
>> - if (pos->has_loc)
>> - opos->has_loc = pos->has_loc;
>> - if (pos->has_const_value)
>> - opos->has_const_value = pos->has_const_value;
>> - if (pos->loc_const_value)
>> - opos->loc_const_value = pos->loc_const_value;
>> - if (pos->loc_stack)
>> - opos->loc_stack = pos->loc_stack;
>> - if (pos->loc_reg != PARAMETER_UNKNOWN_REG)
>> - opos->loc_reg = pos->loc_reg;
>> - if (pos->type_byte_size != 0)
>> - opos->type_byte_size = pos->type_byte_size;
>> - if (pos->passed_in_memory)
>> - opos->passed_in_memory = pos->passed_in_memory;
>> - opos->first_reg_fields |= pos->first_reg_fields;
>> - opos->second_reg_fields |= pos->second_reg_fields;
>> - if (pos->true_sig_member_name && !opos->true_sig_member_name) {
>> - opos->true_sig_member_name = pos->true_sig_member_name;
>> - opos->true_sig_type = pos->true_sig_type;
>> - opos->true_sig_type_from_types = pos->true_sig_type_from_types;
>> - }
>> + if (dparm->type == 0) {
>> + struct parameter *oparm;
>>
>> - if (pos->optimized)
>> - opos->optimized = pos->optimized;
>> - if (pos->unexpected_reg)
>> - opos->unexpected_reg = pos->unexpected_reg;
>> - continue;
>> + if (dparm->abstract_origin == 0) {
>> + parm->tag.type = 0;
>> + return;
>> }
>>
>> - dtype = dwarf_cu__find_type_by_ref(dcu, dpos, type);
>> + dtype = dwarf_cu__find_tag_by_ref(dcu, dparm, abstract_origin);
>> if (dtype == NULL) {
>> - tag__print_type_not_found(&pos->tag);
>> - continue;
>> + tag__print_abstract_origin_not_found(&parm->tag);
>> + return;
>> }
>> - pos->tag.type = dtype->small_id;
>> +
>> + oparm = tag__parameter(dtag__tag(dtype));
>> + parm->name = oparm->name;
>> + if (ftype != NULL && parm->idx != oparm->idx)
>> + ftype->reordered_parm = 1;
>
> looks like we could end up with wrong argument index if the dwarf
> info is missing for the argument, my agent is suggesting this fix
>
> it gets me over 16k changes like this:
>
> -0xffffffff8100e322 [__show_trace_log_lvl+0xb2, .text +0xe322] get_stack_pointer(struct task_struct * task [const 0], struct pt_regs * regs [%r14])
> +0xffffffff8100e322 [__show_trace_log_lvl+0xb2, .text +0xe322] get_stack_pointer(struct task_struct * task [%r14], struct pt_regs * regs [const 0])
>
> I tried to validate one against dwarf info and it seems
> to be correct with the fix ;-)
>
thanks for catching this! So the solution is something like this
oparm = tag__parameter(dtag__tag(dtype));
parm->name = oparm->name;
- if (ftype != NULL && parm->idx != oparm->idx)
- ftype->reordered_parm = 1;
+ if (ftype != NULL) {
+ if (parm->idx != oparm->idx)
+ ftype->reordered_parm = 1;
+ } else {
+ parm->idx = oparm->idx;
+ }
parm->tag.type = dtag__tag(dtype)->type;
to ensure that we map to source parameter order for inlines, right?
Thanks for reviewing!
Alan
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-02 11:03 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-25 18:51 [PATCH v3 dwarves 0/9] Encoding of inline functions using BTF Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 1/9] dwarf_loader: Add parameter list to inlined expansions Alan Maguire
2026-09-30 15:45 ` Jiri Olsa
2026-10-02 11:02 ` Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 2/9] dwarf_loader: Resolve inline expansion names during recoding Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 3/9] dwarf_loader: Collect inline expansion location data Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 4/9] pahole: Support inline BTF location encoding Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 5/9] btf_loader: Add "inline" annotation for inlined functions Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 6/9] btf_loader: Record inline sites in CU for BTF Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 7/9] pfunct: Print inline site information Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 8/9] tests: Validate inline BTF encoding Alan Maguire
2026-09-25 18:51 ` [PATCH v3 dwarves 9/9] man-pages: Document --inline_sites option Alan Maguire
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox