From: Yonghong Song <yonghong.song@linux.dev>
To: Alan Maguire <alan.maguire@oracle.com>,
Arnaldo Carvalho de Melo <arnaldo.melo@gmail.com>,
dwarves@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
bpf@vger.kernel.org, kernel-team@fb.com
Subject: Re: [PATCH dwarves v4 01/11] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr
Date: Sat, 23 May 2026 09:22:46 -0700 [thread overview]
Message-ID: <71ef8e07-832a-4508-8fd6-bab919271117@linux.dev> (raw)
In-Reply-To: <b90f8fb7-539c-468f-a6e5-56cd8687896f@oracle.com>
On 3/30/26 1:31 AM, Alan Maguire wrote:
> On 26/03/2026 01:31, Yonghong Song wrote:
>> Currently every function is checked for its parameters to identify whether
>> the signature changed or not. If signature indeed changed, pahole may do
>> some adjustment for parameters for true signatures.
>>
>> In clang, any function with the following attribute
>> DW_AT_calling_convention (DW_CC_nocall)
>> indicates this function having signature changed.
>> pahole can take advantage of this to avoid parameter checking if
>> DW_AT_calling_convention is not DW_CC_nocall.
>>
>> But more importantly, DW_CC_nocall can identify signature-changed functions
>> and parameters can be checked one-after-another to create the true
>> signatures. Otherwise, it takes more effort to identify whether a
>> function has signature changed or not. For example, for funciton
>> __bpf_kfunc static void bbr_main(struct sock *sk, u32 ack, int flag,
>> const struct rate_sample *rs) { ... }
>> and bbr_main() is a callback function in
>> .cong_control = bbr_main
>> in 'struct tcp_congestion_ops tcp_bbr_cong_ops'.
>> In the above bbr_main(...), parameter 'ack' and 'flag' are not used.
>> The following are some details:
>>
>> 0x0a713b8d: DW_TAG_formal_parameter
>> DW_AT_location (indexed (0x28) loclist = 0x0166d452:
>> [0xffffffff83e77fd9, 0xffffffff83e78016): DW_OP_reg5 RDI
>> ...
>> DW_AT_name ("sk")
>> DW_AT_type (0x0a6f5b2b "sock *")
>> ...
>>
>> 0x0a713b98: DW_TAG_formal_parameter
>> DW_AT_name ("ack")
>> DW_AT_type (0x0a6f58fd "u32")
>> ...
>>
>> 0x0a713ba2: DW_TAG_formal_parameter
>> DW_AT_name ("flag")
>> DW_AT_type (0x0a6f57d1 "int")
>> ...
>>
>> 0x0a713bac: DW_TAG_formal_parameter
>> DW_AT_location (indexed (0x29) loclist = 0x0166d4a8:
>> [0xffffffff83e77fd9, 0xffffffff83e78016): DW_OP_reg2 RCX
>> ...
>> DW_AT_name ("rs")
>> DW_AT_type (0x0a710da5 "const rate_sample *")
>>
>> Some analysis for the above dwarf can conclude that the 'ark' and 'flag'
>> may be related to RSI and RDX, considering the last one is RCX. Basically this
>> requires all parameters are available to collectively decide whether the
>> true signature can be found or not. In such case, DW_CC_nocall can make things
>> easier as parameter can be checked one after another.
>>
>> For a clang built bpf-next kernel with x86_64, in non-LTO setup,
>> the number of kernel functions is 69103 and the number of signature changed
>> functions is 875, based on
>> DW_AT_calling_convention (DW_CC_nocall)
>> indication.
>>
>> Among 875 signature changed functions, after this patch, 343 functions
>> can have proper true signatures, mostly due to simple dead argument
>> elimination. The number of remaining functions, which cannot get the
>> true signature, is 532 due to dead or additional-checked parameters.
>>
>> They will be addressed in the subsequent commits.
>>
>> In llvm23, I implemented [1] which added DW_CC_nocall for ArgumentPromotion pass.
>> This compiler pass can add additional DW_CC_nocall cases for the following
>> compilation:
>> - Flag -O3 or FullLTO
>> So once llvm23 available, we may have more DW_CC_nocall cases, hence more
>> potential true signatures if the kernel is built with -O3 or
>> with FullLTO (CONFIG_LTO_CLANG_FULL).
>>
>> [1] https://github.com/llvm/llvm-project/pull/178973
>>
>> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
>> ---
>> dwarf_loader.c | 82 +++++++++++++++++++++++++++++++++++++++++---------
>> dwarves.h | 1 +
>> 2 files changed, 69 insertions(+), 14 deletions(-)
>>
>> diff --git a/dwarf_loader.c b/dwarf_loader.c
>> index 16fb7be..c569002 100644
>> --- a/dwarf_loader.c
>> +++ b/dwarf_loader.c
>> @@ -1190,6 +1190,10 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
>> return ret;
>> }
>>
>> +struct func_info {
>> + bool signature_changed;
>> +};
>> +
>> /* For DW_AT_location 'attr':
>> * - if first location is DW_OP_regXX with expected number, return the register;
>> * otherwise save the register for later return
>> @@ -1252,7 +1256,8 @@ out:
>> }
>>
>> static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> - struct conf_load *conf, int param_idx)
>> + struct conf_load *conf, int param_idx,
>> + struct func_info *info)
>> {
>> struct parameter *parm = tag__alloc(cu, sizeof(*parm));
>>
>> @@ -1263,8 +1268,15 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
>> tag__init(&parm->tag, cu, die);
>> parm->name = attr_string(die, DW_AT_name, conf);
>> parm->idx = param_idx;
>> - if (param_idx >= cu->nr_register_params || param_idx < 0)
>> + if (param_idx < 0)
>> + return parm;
>> + if (!info->signature_changed) {
>> + if (cu->producer_clang || param_idx >= cu->nr_register_params)
>> + return parm;
>> + } else if (param_idx >= cu->nr_register_params) {
>> return parm;
>> + }
>> +
> logic here is confusing. why not just retain
>
>
> if (param_idx >= cu->nr_register_params || param_idx < 0)
> return parm;
>
> and add
> if (cu->producer_clang && !info->signature_changed)
> return parm;
The code
+ if (param_idx < 0)
+ return parm;
+ if (!info->signature_changed) {
+ if (cu->producer_clang || param_idx >= cu->nr_register_params)
+ return parm;
+ } else if (param_idx >= cu->nr_register_params) {
return parm;
+ }
is written in a way to have minimum change later on.
For example,
if (param_idx >= cu->nr_register_params || param_idx < 0)
return parm;
works for this patch.
But consider parameters after cu->nr_register_params, e.g., 7th parameter
for x86_64. We cannot just return parm since it is possible the 7th
parameter may be dead or may have some other changes. We will go back
to
if (param_idx < 0)
return parm;
So I prefer to keep the current implementation.
>
> but given that info can be NULL, isn't it safer to have
>
> if (cu->producer_clang && info && !info->signature_changed)
> return parm;
'info' will not be NULL as this pointer. 'info' could be NULL if param_idx < 0.
But for param_idx >= 0 case, info is not NULL. I have audited it.
>
> Technically we could drop the cu->producer_clang clause since info->signature changed
> will only be true for clang functions with DW_CC_nocall.
cu->producer_clang is needed. Otherwise, for gcc, info->signature is false and
the original parm will be returned which is incorrect.
>
>
>> /* Parameters which use DW_AT_abstract_origin to point at
>> * the original parameter definition (with no name in the DIE)
>> * are the result of later DWARF generation during compilation
>> @@ -1337,7 +1349,7 @@ static int formal_parameter_pack__load_params(struct formal_parameter_pack *pack
>> continue;
>> }
>>
>> - struct parameter *param = parameter__new(die, cu, conf, -1);
>> + struct parameter *param = parameter__new(die, cu, conf, -1, NULL);
>>
>> if (param == NULL)
>> return -1;
>> @@ -1502,6 +1514,29 @@ static struct ftype *ftype__new(Dwarf_Die *die, struct cu *cu)
>> return ftype;
>> }
>>
>> +static bool function__signature_changed(struct function *func, Dwarf_Die *die)
>> +{
>
> given that AFAIK DW_CC_nocall is only used in clang currently, why not short-circuit
> the logic here and return false if (cu->producer_clang) ? Logical place to do this
> would be in die__create_new_function().
Yes, I can do this. I see your suggested code below with cu->producer_clang
as part of condition. It does make sense.
>
>> + /* The inlined DW_TAG_subprogram typically has the original source type for
>> + * abstract origin of a concrete function with address range, inlined subroutine,
>> + * or call site.
>> + */
>> + if (func->inlined)
>> + return false;
>> +
>> + if (!func->abstract_origin)
>> + return attr_numeric(die, DW_AT_calling_convention) == DW_CC_nocall;
>> +
>> + Dwarf_Attribute attr;
>> + if (dwarf_attr(die, DW_AT_abstract_origin, &attr)) {
>> + Dwarf_Die origin;
>> + if (dwarf_formref_die(&attr, &origin))
>> + return attr_numeric(&origin, DW_AT_calling_convention) == DW_CC_nocall;
>> + }
>> +
>> + /* This should not happen */
>> + return false;
>> +}
>> +
>> static struct function *function__new(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
>> {
>> struct function *func = tag__alloc(cu, sizeof(*func));
>> @@ -1800,9 +1835,9 @@ static struct tag *die__create_new_parameter(Dwarf_Die *die,
>> struct ftype *ftype,
>> struct lexblock *lexblock,
>> struct cu *cu, struct conf_load *conf,
>> - int param_idx)
>> + int param_idx, struct func_info *info)
>> {
>> - struct parameter *parm = parameter__new(die, cu, conf, param_idx);
>> + struct parameter *parm = parameter__new(die, cu, conf, param_idx, info);
>>
>> if (parm == NULL)
>> return NULL;
>> @@ -1889,7 +1924,7 @@ static struct tag *die__create_new_subroutine_type(Dwarf_Die *die,
>> tag__print_not_supported(die);
>> continue;
>> case DW_TAG_formal_parameter:
>> - tag = die__create_new_parameter(die, ftype, NULL, cu, conf, -1);
>> + tag = die__create_new_parameter(die, ftype, NULL, cu, conf, -1, NULL);
>> break;
>> case DW_TAG_unspecified_parameters:
>> ftype->unspec_parms = 1;
>> @@ -2118,7 +2153,8 @@ out_enomem:
>> }
>>
>> static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
>> - struct lexblock *lexblock, struct cu *cu, struct conf_load *conf);
>> + struct lexblock *lexblock, struct cu *cu, struct conf_load *conf,
>> + struct func_info *info);
>>
>> static int die__create_new_lexblock(Dwarf_Die *die,
>> struct cu *cu, struct lexblock *father, struct conf_load *conf)
>> @@ -2126,7 +2162,7 @@ static int die__create_new_lexblock(Dwarf_Die *die,
>> struct lexblock *lexblock = lexblock__new(die, cu);
>>
>> if (lexblock != NULL) {
>> - if (die__process_function(die, NULL, lexblock, cu, conf) != 0)
>> + if (die__process_function(die, NULL, lexblock, cu, conf, NULL) != 0)
>> goto out_delete;
>> }
>> if (father != NULL)
>> @@ -2246,7 +2282,8 @@ static struct tag *die__create_new_inline_expansion(Dwarf_Die *die,
>> }
>>
>> static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
>> - struct lexblock *lexblock, struct cu *cu, struct conf_load *conf)
>> + struct lexblock *lexblock, struct cu *cu, struct conf_load *conf,
>> + struct func_info *info)
>> {
>> int param_idx = 0;
>> Dwarf_Die child;
>> @@ -2320,7 +2357,7 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
>> continue;
>> }
>> case DW_TAG_formal_parameter:
>> - tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++);
>> + tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++, info);
>> break;
>> case DW_TAG_variable:
>> tag = die__create_new_variable(die, cu, conf, 0);
>> @@ -2391,11 +2428,15 @@ out_enomem:
>> static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
>> {
>> struct function *function = function__new(die, cu, conf);
>> + struct func_info info = {};
>> +
>> + if (function != NULL) {
>> + info.signature_changed = function__signature_changed(function, die);
>>
> i'd suggest adding a coment and a predicate here, something like
>
> /* For clang, we determine if function signature changes via DW_AT_calling_convention
> * set to DW_CC_nocall.
> */
> if (cu->producer_clang)
> info.signature_changed = function__signature_changed(function, die);
I will do
if (cu->pruducer_clang && funciton != NULL)
info.signature_changed = function__signature_changed(function, die);
>
>> - if (function != NULL &&
>> - die__process_function(die, &function->proto, &function->lexblock, cu, conf) != 0) {
>> - function__delete(function, cu);
>> - function = NULL;
>> + if (die__process_function(die, &function->proto, &function->lexblock, cu, conf, &info) != 0) {
>> + function__delete(function, cu);
>> + function = NULL;
>> + }
>> }
>>
>> return function ? &function->proto.tag : NULL;
>> @@ -3045,6 +3086,17 @@ static unsigned long long dwarf_tag__orig_id(const struct tag *tag,
>> return cu->extra_dbg_info ? dtag->id : 0;
>> }
>>
>> +static bool attr_producer_clang(Dwarf_Die *die)
>> +{
>> + const char *producer;
>> +
>> + producer = attr_string(die, DW_AT_producer, NULL);
>> + if (!producer)
>> + return false;
>> +
>> + return !!strstr(producer, "clang");
>> +}
>> +
>> struct debug_fmt_ops dwarf__ops;
>>
>> static int die__process(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
>> @@ -3082,6 +3134,7 @@ static int die__process(Dwarf_Die *die, struct cu *cu, struct conf_load *conf)
>> }
>>
>> cu->language = attr_numeric(die, DW_AT_language);
>> + cu->producer_clang = attr_producer_clang(die);
>>
>> if (conf->early_cu_filter)
>> cu = conf->early_cu_filter(cu);
>> @@ -3841,6 +3894,7 @@ static int cus__merge_and_process_cu(struct cus *cus, struct conf_load *conf,
>> cu->priv = dcu;
>> cu->dfops = &dwarf__ops;
>> cu->language = attr_numeric(cu_die, DW_AT_language);
>> + cu->producer_clang = attr_producer_clang(cu_die);
>> cus__add(cus, cu);
>> }
>>
>> diff --git a/dwarves.h b/dwarves.h
>> index d7c6474..4cabab0 100644
>> --- a/dwarves.h
>> +++ b/dwarves.h
>> @@ -302,6 +302,7 @@ struct cu {
>> uint8_t has_addr_info:1;
>> uint8_t uses_global_strings:1;
>> uint8_t little_endian:1;
>> + uint8_t producer_clang:1;
>> uint8_t nr_register_params;
>> int register_params[ARCH_MAX_REGISTER_PARAMS];
>> int functions_saved;
next prev parent reply other threads:[~2026-05-23 16:23 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-26 1:31 [PATCH dwarves v4 00/11] pahole: Encode true signatures in kernel BTF Yonghong Song
2026-03-26 1:31 ` [PATCH dwarves v4 01/11] dwarf_loader: Reduce parameter checking with clang DW_AT_calling_convention attr Yonghong Song
2026-03-30 8:31 ` Alan Maguire
2026-05-23 16:22 ` Yonghong Song [this message]
2026-03-26 1:31 ` [PATCH dwarves v4 02/11] dwarf_loader: Prescan all parameters with expected registers Yonghong Song
2026-03-26 1:31 ` [PATCH dwarves v4 03/11] dwarf_loader: Handle signatures with dead arguments Yonghong Song
2026-03-30 10:13 ` Alan Maguire
2026-05-23 16:28 ` Yonghong Song
2026-03-26 1:32 ` [PATCH dwarves v4 04/11] dwarf_loader: Refactor initial ret -1 to be macro PARM_DEFAULT_FAIL Yonghong Song
2026-03-26 1:32 ` [PATCH dwarves v4 05/11] dwarf_laoder: Handle locations with DW_OP_fbreg Yonghong Song
2026-03-26 1:32 ` [PATCH dwarves v4 06/11] dwarf_loader: Change exprlen checking condition in parameter__reg() Yonghong Song
2026-03-26 1:32 ` [PATCH dwarves v4 07/11] dwarf_loader: Detect optimized parameters with locations having constant values Yonghong Song
2026-03-26 1:32 ` [PATCH dwarves v4 08/11] dwarf_loader: Check whether two-reg parameter actually use two regs or not Yonghong Song
2026-03-26 1:32 ` [PATCH dwarves v4 09/11] dwarf_loader: Handle expression lists Yonghong Song
2026-03-31 8:04 ` Alan Maguire
2026-05-23 16:32 ` Yonghong Song
2026-03-26 1:33 ` [PATCH dwarves v4 10/11] btf_encoder: Handle optimized parameter properly Yonghong Song
2026-03-26 1:33 ` [PATCH dwarves v4 11/11] tests: Add a few clang true signature tests Yonghong Song
2026-03-27 16:02 ` [PATCH dwarves v4 00/11] pahole: Encode true signatures in kernel BTF Alan Maguire
2026-03-27 19:38 ` Yonghong Song
2026-03-30 9:56 ` 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=71ef8e07-832a-4508-8fd6-bab919271117@linux.dev \
--to=yonghong.song@linux.dev \
--cc=alan.maguire@oracle.com \
--cc=andrii@kernel.org \
--cc=arnaldo.melo@gmail.com \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=dwarves@vger.kernel.org \
--cc=kernel-team@fb.com \
/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