From: Ihor Solodrai <ihor.solodrai@linux.dev>
To: Alan Maguire <alan.maguire@oracle.com>,
olsajiri@gmail.com, dwarves@vger.kernel.org
Cc: bpf@vger.kernel.org, acme@kernel.org, andrii@kernel.org,
ast@kernel.org, eddyz87@gmail.com, menglong8.dong@gmail.com,
song@kernel.org, yonghong.song@linux.dev, kernel-team@meta.com
Subject: Re: [PATCH dwarves v2] btf_encoder: group all function ELF syms by function name
Date: Thu, 31 Jul 2025 09:51:21 -0700 [thread overview]
Message-ID: <5fe3af2b-a12e-40d7-9f9d-d7c26d98cf14@linux.dev> (raw)
In-Reply-To: <79a329ef-9bb3-454e-9135-731f2fd51951@oracle.com>
On 7/31/25 7:16 AM, Alan Maguire wrote:
> On 29/07/2025 03:03, Ihor Solodrai wrote:
>> btf_encoder collects function ELF symbols into a table, which is later
>> used for processing DWARF data and determining whether a function can
>> be added to BTF.
>>
>> So far the ELF symbol name was used as a key for search in this table,
>> and a search by prefix match was attempted in cases when ELF symbol
>> name has a compiler-generated suffix.
>>
>> This implementation has bugs [1][2], causing some functions to be
>> inappropriately excluded from (or included into) BTF.
>>
>> Rework the implementation of the ELF functions table. Use a name of a
>> function without any suffix - symbol name before the first occurrence
>> of '.' - as a key. This way btf_encoder__find_function() always
>> returns a valid elf_function object (or NULL).
>>
>> Collect an array of symbol name + address pairs from GElf_Sym for each
>> elf_function when building the elf_functions table.
>>
>> Introduce ambiguous_addr flag to the btf_encoder_func_state. It is set
>> when the function is saved by examining the array of ELF symbols in
>> elf_function__has_ambiguous_address(). It tests whether there is only
>> one unique address for this function name, taking into account that
>> some addresses associated with it are not relevant:
>> * ".cold" suffix indicates a piece of hot/cold split
>> * ".part" suffix indicates a piece of partial inline
>>
>> When inspecting symbol name we have to search for any occurrence of
>> the target suffix, as opposed to testing the entire suffix, or the end
>> of a string. This is because suffixes may be combined by the compiler,
>> for example producing ".isra0.cold", and the conclusion will be
>> incorrect.
>>
>> In saved_functions_combine() check ambiguous_addr when deciding
>> whether a function should be included in BTF.
>>
>> Successful CI run: https://github.com/acmel/dwarves/pull/68/checks
>>
>> I manually spot checked some of the ~200 functions from vmlinux (BPF
>> CI-like kconfig) that are now excluded: all of those that I checked
>> had multiple addresses, and some where static functions from different
>> files with the same name.
>>
>> [1] https://lore.kernel.org/bpf/2f8c792e-9675-4385-b1cb-10266c72bd45@linux.dev/
>> [2] https://lore.kernel.org/dwarves/6b4fda90fbf8f6aeeb2732bbfb6e81ba5669e2f3@linux.dev/
>>
>> v1: https://lore.kernel.org/dwarves/98f41eaf6dd364745013650d58c5f254a592221c@linux.dev/
>> Signed-off-by: Ihor Solodrai <isolodrai@meta.com>
>
> Thanks for doing this Ihor! Apologies for just thinking of this now, but
> why don't we filter out the .cold and .part functions earlier, not even
> adding them to the ELF functions list? Something like this on top of
> your patch:
>
> $ git diff
> diff --git a/btf_encoder.c b/btf_encoder.c
> index 0aa94ae..f61db0f 100644
> --- a/btf_encoder.c
> +++ b/btf_encoder.c
> @@ -1188,27 +1188,6 @@ static struct btf_encoder_func_state
> *btf_encoder__alloc_func_state(struct btf_e
> return state;
> }
>
> -/* some "." suffixes do not correspond to real functions;
> - * - .part for partial inline
> - * - .cold for rarely-used codepath extracted for better code locality
> - */
> -static bool str_contains_non_fn_suffix(const char *str) {
> - static const char *skip[] = {
> - ".cold",
> - ".part"
> - };
> - char *suffix = strchr(str, '.');
> - int i;
> -
> - if (!suffix)
> - return false;
> - for (i = 0; i < ARRAY_SIZE(skip); i++) {
> - if (strstr(suffix, skip[i]))
> - return true;
> - }
> - return false;
> -}
> -
> static bool elf_function__has_ambiguous_address(struct elf_function
> *func) {
> struct elf_function_sym *sym;
> uint64_t addr;
> @@ -1219,12 +1198,10 @@ static bool
> elf_function__has_ambiguous_address(struct elf_function *func) {
> addr = 0;
> for (int i = 0; i < func->sym_cnt; i++) {
> sym = &func->syms[i];
> - if (!str_contains_non_fn_suffix(sym->name)) {
> - if (addr && addr != sym->addr)
> - return true;
> - else
> + if (addr && addr != sym->addr)
> + return true;
> + else
> addr = sym->addr;
> - }
> }
>
>
> return false;
> @@ -2208,9 +2185,12 @@ static int elf_functions__collect(struct
> elf_functions *functions)
> func = &functions->entries[functions->cnt];
>
> suffix = strchr(sym_name, '.');
> - if (suffix)
> + if (suffix) {
> + if (strstr(suffix, ".part") ||
> + strstr(suffix, ".cold"))
> + continue;
> func->name = strndup(sym_name, suffix - sym_name);
> - else
> + } else
> func->name = strdup(sym_name);
>
> if (!func->name) {
>
> I think that would work and saves later string comparisons, what do you
> think?
My thinking was that in the future pahole may want to use the
information about those symbols when generating BTF, such as for
(partial) inline support. But that is a stretch, as those potential
features are complicated.
And it would be easy to add the suffixed symbols back when
necessary. So will change in v3.
>
>> ---
>> btf_encoder.c | 250 ++++++++++++++++++++++++++++++++------------------
>> 1 file changed, 162 insertions(+), 88 deletions(-)
>>
>> diff --git a/btf_encoder.c b/btf_encoder.c
>> index 0bc2334..0aa94ae 100644
>> --- a/btf_encoder.c
>> +++ b/btf_encoder.c
>> @@ -87,16 +87,22 @@ struct btf_encoder_func_state {
>> uint8_t optimized_parms:1;
>> uint8_t unexpected_reg:1;
>> uint8_t inconsistent_proto:1;
>> + uint8_t ambiguous_addr:1;
>> int ret_type_id;
>> struct btf_encoder_func_parm *parms;
>> struct btf_encoder_func_annot *annots;
>> };
>>
>> +struct elf_function_sym {
>> + const char *name;
>> + uint64_t addr;
>> +};
>> +
>> struct elf_function {
>> - const char *name;
>> - char *alias;
>> - size_t prefixlen;
>> - bool kfunc;
>> + char *name;
>> + struct elf_function_sym *syms;
>> + uint8_t sym_cnt;
>> + uint8_t kfunc:1;
>> uint32_t kfunc_flags;
>> };
>>
>> @@ -115,7 +121,6 @@ struct elf_functions {
>> struct elf_symtab *symtab;
>> struct elf_function *entries;
>> int cnt;
>> - int suffix_cnt; /* number of .isra, .part etc */
>> };
>>
>> /*
>> @@ -161,10 +166,18 @@ struct btf_kfunc_set_range {
>> uint64_t end;
>> };
>>
>> +static inline void elf_function__free_content(struct elf_function *func) {
>> + free(func->name);
>> + if (func->sym_cnt)
>> + free(func->syms);
>> + memset(func, 0, sizeof(*func));
>> +}
>> +
>> static inline void elf_functions__delete(struct elf_functions *funcs)
>> {
>> - for (int i = 0; i < funcs->cnt; i++)
>> - free(funcs->entries[i].alias);
>> + for (int i = 0; i < funcs->cnt; i++) {
>> + elf_function__free_content(&funcs->entries[i]);
>> + }
>> free(funcs->entries);
>> elf_symtab__delete(funcs->symtab);
>> list_del(&funcs->node);
>> @@ -981,8 +994,7 @@ static void btf_encoder__log_func_skip(struct btf_encoder *encoder, struct elf_f
>>
>> if (!encoder->verbose)
>> return;
>> - printf("%s (%s): skipping BTF encoding of function due to ",
>> - func->alias ?: func->name, func->name);
>> + printf("%s : skipping BTF encoding of function due to ", func->name);
>> va_start(ap, fmt);
>> vprintf(fmt, ap);
>> va_end(ap);
>> @@ -1176,6 +1188,48 @@ static struct btf_encoder_func_state *btf_encoder__alloc_func_state(struct btf_e
>> return state;
>> }
>>
>> +/* some "." suffixes do not correspond to real functions;
>> + * - .part for partial inline
>> + * - .cold for rarely-used codepath extracted for better code locality
>> + */
>> +static bool str_contains_non_fn_suffix(const char *str) {
>> + static const char *skip[] = {
>> + ".cold",
>> + ".part"
>> + };
>> + char *suffix = strchr(str, '.');
>> + int i;
>> +
>> + if (!suffix)
>> + return false;
>> + for (i = 0; i < ARRAY_SIZE(skip); i++) {
>> + if (strstr(suffix, skip[i]))
>> + return true;
>> + }
>> + return false;
>> +}
>> +
>> +static bool elf_function__has_ambiguous_address(struct elf_function *func) {
>> + struct elf_function_sym *sym;
>> + uint64_t addr;
>> +
>> + if (func->sym_cnt <= 1)
>> + return false;
>> +
>> + addr = 0;
>> + for (int i = 0; i < func->sym_cnt; i++) {
>> + sym = &func->syms[i];
>> + if (!str_contains_non_fn_suffix(sym->name)) {
>> + if (addr && addr != sym->addr)
>> + return true;
>> + else
>> + addr = sym->addr;
>> + }
>> + }
>> +
>> + return false;
>> +}
>> +
>> static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct function *fn, struct elf_function *func)
>> {
>> struct btf_encoder_func_state *state = btf_encoder__alloc_func_state(encoder);
>> @@ -1191,6 +1245,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
>>
>> state->encoder = encoder;
>> state->elf = func;
>> + state->ambiguous_addr = elf_function__has_ambiguous_address(func);
>> state->nr_parms = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0);
>> state->ret_type_id = ftype->tag.type == 0 ? 0 : encoder->type_id_off + ftype->tag.type;
>> if (state->nr_parms > 0) {
>> @@ -1294,7 +1349,7 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>> int err;
>>
>> btf_fnproto_id = btf_encoder__add_func_proto(encoder, NULL, state);
>> - name = func->alias ?: func->name;
>> + name = func->name;
>> if (btf_fnproto_id >= 0)
>> btf_fn_id = btf_encoder__add_ref_type(encoder, BTF_KIND_FUNC, btf_fnproto_id,
>> name, false);
>> @@ -1338,48 +1393,39 @@ static int32_t btf_encoder__add_func(struct btf_encoder *encoder,
>> return 0;
>> }
>>
>> -static int functions_cmp(const void *_a, const void *_b)
>> +static int elf_function__name_cmp(const void *_a, const void *_b)
>> {
>> const struct elf_function *a = _a;
>> const struct elf_function *b = _b;
>>
>> - /* if search key allows prefix match, verify target has matching
>> - * prefix len and prefix matches.
>> - */
>> - if (a->prefixlen && a->prefixlen == b->prefixlen)
>> - return strncmp(a->name, b->name, b->prefixlen);
>> return strcmp(a->name, b->name);
>> }
>>
>> -#ifndef max
>> -#define max(x, y) ((x) < (y) ? (y) : (x))
>> -#endif
>> -
>> static int saved_functions_cmp(const void *_a, const void *_b)
>> {
>> const struct btf_encoder_func_state *a = _a;
>> const struct btf_encoder_func_state *b = _b;
>>
>> - return functions_cmp(a->elf, b->elf);
>> + return elf_function__name_cmp(a->elf, b->elf);
>> }
>>
>> static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_encoder_func_state *b)
>> {
>> - uint8_t optimized, unexpected, inconsistent;
>> - int ret;
>> + uint8_t optimized, unexpected, inconsistent, ambiguous_addr;
>> +
>> + if (a->elf != b->elf)
>> + return 1;
>>
>> - ret = strncmp(a->elf->name, b->elf->name,
>> - max(a->elf->prefixlen, b->elf->prefixlen));
>> - if (ret != 0)
>> - return ret;
>> optimized = a->optimized_parms | b->optimized_parms;
>> unexpected = a->unexpected_reg | b->unexpected_reg;
>> inconsistent = a->inconsistent_proto | b->inconsistent_proto;
>> - if (!unexpected && !inconsistent && !funcs__match(a, b))
>> + ambiguous_addr = a->ambiguous_addr | b->ambiguous_addr;
>> + if (!unexpected && !inconsistent && !ambiguous_addr && !funcs__match(a, b))
>> inconsistent = 1;
>> a->optimized_parms = b->optimized_parms = optimized;
>> a->unexpected_reg = b->unexpected_reg = unexpected;
>> a->inconsistent_proto = b->inconsistent_proto = inconsistent;
>> + a->ambiguous_addr = b->ambiguous_addr = ambiguous_addr;
>>
>> return 0;
>> }
>> @@ -1432,7 +1478,7 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
>> * just do not _use_ them. Only exclude functions with
>> * unexpected register use or multiple inconsistent prototypes.
>> */
>> - add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto;
>> + add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto && !state->ambiguous_addr;
>>
>> if (add_to_btf) {
>> err = btf_encoder__add_func(state->encoder, state);
>> @@ -1447,32 +1493,6 @@ out:
>> return err;
>> }
>>
>> -static void elf_functions__collect_function(struct elf_functions *functions, GElf_Sym *sym)
>> -{
>> - struct elf_function *func;
>> - const char *name;
>> -
>> - if (elf_sym__type(sym) != STT_FUNC)
>> - return;
>> -
>> - name = elf_sym__name(sym, functions->symtab);
>> - if (!name)
>> - return;
>> -
>> - func = &functions->entries[functions->cnt];
>> - func->name = name;
>> - if (strchr(name, '.')) {
>> - const char *suffix = strchr(name, '.');
>> -
>> - functions->suffix_cnt++;
>> - func->prefixlen = suffix - name;
>> - } else {
>> - func->prefixlen = strlen(name);
>> - }
>> -
>> - functions->cnt++;
>> -}
>> -
>> static struct elf_functions *btf_encoder__elf_functions(struct btf_encoder *encoder)
>> {
>> struct elf_functions *funcs = NULL;
>> @@ -1490,13 +1510,12 @@ static struct elf_functions *btf_encoder__elf_functions(struct btf_encoder *enco
>> return funcs;
>> }
>>
>> -static struct elf_function *btf_encoder__find_function(const struct btf_encoder *encoder,
>> - const char *name, size_t prefixlen)
>> +static struct elf_function *btf_encoder__find_function(const struct btf_encoder *encoder, const char *name)
>> {
>> struct elf_functions *funcs = elf_functions__find(encoder->cu->elf, &encoder->elf_functions_list);
>> - struct elf_function key = { .name = name, .prefixlen = prefixlen };
>> + struct elf_function key = { .name = (char*)name };
>>
>> - return bsearch(&key, funcs->entries, funcs->cnt, sizeof(key), functions_cmp);
>> + return bsearch(&key, funcs->entries, funcs->cnt, sizeof(key), elf_function__name_cmp);
>> }
>>
>> static bool btf_name_char_ok(char c, bool first)
>> @@ -2060,7 +2079,7 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
>> continue;
>> }
>>
>> - elf_fn = btf_encoder__find_function(encoder, func, 0);
>> + elf_fn = btf_encoder__find_function(encoder, func);
>> if (elf_fn) {
>> elf_fn->kfunc = true;
>> elf_fn->kfunc_flags = pair->flags;
>> @@ -2135,14 +2154,34 @@ int btf_encoder__encode(struct btf_encoder *encoder, struct conf_load *conf)
>> return err;
>> }
>>
>> +static inline int elf_function__push_sym(struct elf_function *func, struct elf_function_sym *sym) {
>> + struct elf_function_sym *tmp;
>> +
>> + if (func->sym_cnt)
>> + tmp = realloc(func->syms, (func->sym_cnt + 1) * sizeof(func->syms[0]));
>> + else
>> + tmp = calloc(sizeof(func->syms[0]), 1);
>> +
>> + if (!tmp)
>> + return -ENOMEM;
>> +
>> + func->syms = tmp;
>> + func->syms[func->sym_cnt] = *sym;
>> + func->sym_cnt++;
>> +
>> + return 0;
>> +}
>> +
>> static int elf_functions__collect(struct elf_functions *functions)
>> {
>> uint32_t nr_symbols = elf_symtab__nr_symbols(functions->symtab);
>> - struct elf_function *tmp;
>> + struct elf_function_sym func_sym;
>> + struct elf_function *func, *tmp;
>> + const char *sym_name, *suffix;
>> Elf32_Word sym_sec_idx;
>> + int err = 0, i, j;
>> uint32_t core_id;
>> GElf_Sym sym;
>> - int err = 0;
>>
>> /* We know that number of functions is less than number of symbols,
>> * so we can overallocate temporarily.
>> @@ -2153,18 +2192,72 @@ static int elf_functions__collect(struct elf_functions *functions)
>> goto out_free;
>> }
>>
>> + /* First, collect an elf_function for each GElf_Sym
>> + * Where func->name is without a suffix
>> + */
>> functions->cnt = 0;
>> elf_symtab__for_each_symbol_index(functions->symtab, core_id, sym, sym_sec_idx) {
>> - elf_functions__collect_function(functions, &sym);
>> +
>> + if (elf_sym__type(&sym) != STT_FUNC)
>> + continue;
>> +
>> + sym_name = elf_sym__name(&sym, functions->symtab);
>> + if (!sym_name)
>> + continue;
>> +
>> + func = &functions->entries[functions->cnt];
>> +
>> + suffix = strchr(sym_name, '.');
>> + if (suffix)
>> + func->name = strndup(sym_name, suffix - sym_name);
>> + else
>> + func->name = strdup(sym_name);
>> +
>> + if (!func->name) {
>> + err = -ENOMEM;
>> + goto out_free;
>> + }
>> +
>> + func_sym.name = sym_name;
>> + func_sym.addr = sym.st_value;
>> +
>> + err = elf_function__push_sym(func, &func_sym);
>> + if (err)
>> + goto out_free;
>> +
>> + functions->cnt++;
>> }
>>
>> + /* At this point functions->entries is an unordered array of elf_function
>> + * each having a name (without a suffix) and a single elf_function_sym (maybe with suffix)
>> + * Now let's sort this table by name.
>> + */
>> if (functions->cnt) {
>> - qsort(functions->entries, functions->cnt, sizeof(*functions->entries), functions_cmp);
>> + qsort(functions->entries, functions->cnt, sizeof(*functions->entries), elf_function__name_cmp);
>> } else {
>> err = 0;
>> goto out_free;
>> }
>>
>> + /* Finally dedup by name, transforming { name -> syms[1] } entries into { name -> syms[n] } */
>> + i = 0;
>> + j = 1;
>> + for (j = 1; j < functions->cnt; j++) {
>> + struct elf_function *a = &functions->entries[i];
>> + struct elf_function *b = &functions->entries[j];
>> +
>> + if (!strcmp(a->name, b->name)) {
>> + elf_function__push_sym(a, &b->syms[0]);
>> + elf_function__free_content(b);
>> + } else {
>> + i++;
>> + if (i != j)
>> + functions->entries[i] = functions->entries[j];
>> + }
>> + }
>> +
>> + functions->cnt = i + 1;
>> +
>> /* Reallocate to the exact size */
>> tmp = realloc(functions->entries, functions->cnt * sizeof(struct elf_function));
>> if (tmp) {
>> @@ -2661,30 +2754,11 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
>> if (!name)
>> continue;
>>
>> - /* prefer exact function name match... */
>> - func = btf_encoder__find_function(encoder, name, 0);
>> - if (!func && funcs->suffix_cnt &&
>> - conf_load->btf_gen_optimized) {
>> - /* falling back to name.isra.0 match if no exact
>> - * match is found; only bother if we found any
>> - * .suffix function names. The function
>> - * will be saved and added once we ensure
>> - * it does not have optimized-out parameters
>> - * in any cu.
>> - */
>> - func = btf_encoder__find_function(encoder, name,
>> - strlen(name));
>> - if (func) {
>> - if (encoder->verbose)
>> - printf("matched function '%s' with '%s'%s\n",
>> - name, func->name,
>> - fn->proto.optimized_parms ?
>> - ", has optimized-out parameters" :
>> - fn->proto.unexpected_reg ? ", has unexpected register use by params" :
>> - "");
>> - if (!func->alias)
>> - func->alias = strdup(name);
>> - }
>> + func = btf_encoder__find_function(encoder, name);
>> + if (!func) {
>> + if (encoder->verbose)
>> + printf("could not find function '%s' in the ELF functions table\n", name);
>> + continue;
>> }
>> } else {
>> if (!fn->external)
>
next prev parent reply other threads:[~2025-07-31 16:51 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20250729020308.103139-1-isolodrai@meta.com>
2025-07-29 3:19 ` [PATCH dwarves v2] btf_encoder: group all function ELF syms by function name Ihor Solodrai
2025-07-29 3:28 ` Ihor Solodrai
2025-07-29 13:04 ` Jiri Olsa
2025-07-31 14:16 ` Alan Maguire
2025-07-31 16:51 ` Ihor Solodrai [this message]
2025-07-31 18:57 ` Alan Maguire
2025-08-01 20:51 ` Ihor Solodrai
2025-08-05 11:24 ` Alan Maguire
2025-08-19 19:34 ` Ihor Solodrai
2025-08-20 11:04 ` Alan Maguire
2025-08-22 15:57 ` Alan Maguire
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5fe3af2b-a12e-40d7-9f9d-d7c26d98cf14@linux.dev \
--to=ihor.solodrai@linux.dev \
--cc=acme@kernel.org \
--cc=alan.maguire@oracle.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=dwarves@vger.kernel.org \
--cc=eddyz87@gmail.com \
--cc=kernel-team@meta.com \
--cc=menglong8.dong@gmail.com \
--cc=olsajiri@gmail.com \
--cc=song@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).