Re: [PATCH dwarves v5 00/11] pahole: Encode true signatures in kernel BTF

Alan Maguire <[email protected]> Mon, 15 Jun 2026 18:17:48 +0100
Newsgroups org.kernel.vger.dwarves,org.kernel.vger.bpf
Message-ID <[email protected]>
On 23/05/2026 17:57, Yonghong Song wrote:
> Current vmlinux BTF encoding is based on the source level signatures.
> But the compiler may do some optimization and changed the signature.
> If the user tried with source level signature, their initial implementation
> may have wrong results and then the user need to check what is the
> problem and work around it, e.g. through kprobe since kprobe does not
> need vmlinux BTF.
> 
> Majority of changed signatures are due to dead argument elimination.
> The following is a more complex one. The original source signature:
>   typedef struct {
>         union {
>                 void            *kernel;
>                 void __user     *user;
>         };
>         bool            is_kernel : 1;
>   } sockptr_t;
>   typedef sockptr_t bpfptr_t;
>   static int map_create(union bpf_attr *attr, bpfptr_t uattr) { ... }
> After compiler optimization, the signature becomes:
>   static int map_create(union bpf_attr *attr, bool uattr__is_kernel) { ... }
> In the above, uattr__is_kernel corresponds to 'is_kernel' field in sockptr_t.
> This makes it easier for developers to understand what changed.
> 
> The new signature needs to properly follow ABI specification based on
> locations. Otherwise, that signature should be discarded. For example,
> 
>     0x0242f1f7:   DW_TAG_subprogram
>                     DW_AT_name      ("memblock_find_in_range")
>                     DW_AT_calling_convention        (DW_CC_nocall)
>                     DW_AT_type      (0x0242decc "phys_addr_t")
>                     ...
>     0x0242f22e:     DW_TAG_formal_parameter
>                       DW_AT_location        (indexed (0x14a) loclist = 0x005595bc:
>                          [0xffffffff87a000f9, 0xffffffff87a00178): DW_OP_reg5 RDI
>                          [0xffffffff87a00178, 0xffffffff87a001be): DW_OP_reg14 R14
>                          [0xffffffff87a001be, 0xffffffff87a001c7): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
>                          [0xffffffff87a001c7, 0xffffffff87a00214): DW_OP_reg14 R14)
>                       DW_AT_name    ("start")
>                       DW_AT_type    (0x0242decc "phys_addr_t")
>                       ...
>     0x0242f239:     DW_TAG_formal_parameter
>                       DW_AT_location        (indexed (0x14b) loclist = 0x005595e6:
>                          [0xffffffff87a000f9, 0xffffffff87a00175): DW_OP_reg4 RSI
>                          [0xffffffff87a00175, 0xffffffff87a001b8): DW_OP_reg3 RBX
>                          [0xffffffff87a001b8, 0xffffffff87a001c7): DW_OP_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value
>                          [0xffffffff87a001c7, 0xffffffff87a00214): DW_OP_reg3 RBX)
>                       DW_AT_name    ("end")
>                       DW_AT_type    (0x0242decc "phys_addr_t")
>                       ...
>     0x0242f245:     DW_TAG_formal_parameter
>                       DW_AT_location        (indexed (0x14c) loclist = 0x00559610:
>                          [0xffffffff87a001e3, 0xffffffff87a001ef): DW_OP_breg4 RSI+0)
>                       DW_AT_name    ("size")
>                       DW_AT_type    (0x0242decc "phys_addr_t")
>                       ...
>     0x0242f250:     DW_TAG_formal_parameter
>                       DW_AT_const_value     (4096)
>                       DW_AT_name    ("align")
>                       DW_AT_type    (0x0242decc "phys_addr_t")
>                       ...
> 
> The third argument should correspond to RDX for x86_64. But the location suggests that
> the parameter value is stored in the address with 'RSI + 0'. It is not clear whether
> the parameter value is stored in RDX or not. So we have to discard this funciton in
> vmlinux BTF to avoid incorrect true signatures.
> 
> For llvm, any function having
>   DW_AT_calling_convention        (DW_CC_nocall)
> in dwarf DW_TAG_subprogram will indicate that this function has signature changed.
> I did experiment with latest bpf-next. For x86_64, there are 69103 kernel functions
> and 875 kernel functions having signature changed. A series of patches are intended
> to ensure true signatures are properly represented. Eventually, only 18 functions
> cannot have true signatures due to locations.
> 
> For arm64, there are 863 kernel functions having signature changed, and
> 70 functions cannot have true signatures due to locations. I checked those
> functions and look like llvm arm64 backend more relaxed to compute parameter
> values.
> 
> For full testing, I enabled true signature support in kernel scripts/Makefile.btf like below:
>   -pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes
>   +pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes --btf_features=+true_signature
> 
> For the patch set, Patch 1 introduced usage of DW_AT_calling_convention, which
> can precisely identify which function has signature changed. This can filter
> majority of functions where their signature won't change. Patch 2 did a prescan
> of parameter registers to accommodate some cases where the optimization could
> happen but didn't. Patches 3 to 9 tried to find functions with true signature.
> Patch 10 enables to btf encoder to properly generate BTF.
> Patch 11 includes a few tests.
> 
> Changelog:
>   v4 -> v5:
>     - v4: https://lore.kernel.org/bpf/[email protected]/
>     - Check info.signature_changed only under clang.
>     - Fix an uninitialized varable issue (var reg_dix) for gcc.
>   v3 -> v4:
>     - v3: https://lore.kernel.org/bpf/[email protected]/
>     - Add simple prescan of parameter registers in order to get true signatures
>       for those functions where optimization could happen but compiler didn't do it.
>     - Do not create a new name (e.g. "uattr__is_kernel") with malloc at parameter_reg()
>       stage. Instead remember both "uattr" and "is_kernel" and later generate the
>       name "uattr_is_kernel" in btf encoder.
>     - Add comments to explain how to handle parameters which may take two registers.
>     - Fix some test failures on aarch64.
>   v2 -> v3:
>     - v2: https://lore.kernel.org/bpf/[email protected]/
>     - Change tests by using newly added test_lib.sh.
>     - Simplify to get bool variable producer_clang.
>     - Try to avoid producer_clang appearance in dwarf_loader.c in order to avoid
>       clear separation between clang and gcc.
>   v1 -> v2:
>     - v1: https://lore.kernel.org/bpf/[email protected]/
>     - Added producer_clang guarding in btf_encoder. Otherwise, gcc kernel build
>       will crash pahole.
>     - Fix an early return in parameter__reg() which didn't do pthread_mutex_unlock()
>       which caused the deadlock for arm64.
>     - Add a few more places to guard with producer_clang and conf->true_signature
>       to maintain the previous behavior if not clang or conf->true_signature is false.
>

In order to be a bit more concrete about a proposed way forward, I'm thinking something
along the lines of the attached patch (which should apply on top of this whole series); 
rather than doing prescans etc, we record param info as we go as we do today, and once done
compute true signature info. This saves some complexity around prescan of params etc, so 
is a bit more  consistent with what's there today. Ideally we'd be able to enhance DWARF 
processing for both cases (you have some great improvements in that area in this series),
and unify the representation of modified signatures where feasible. Let me know what you think.

Thanks!

Alan
0001-dwarf_loader-unify-true-signature-parameter-analysis.patch (text/x-patch, 33.4 KB)
From 370eb7cd61c4e56944c3fde5353f639e747b037c Mon Sep 17 00:00:00 2001
From: Alan Maguire <[email protected]>
Date: Mon, 15 Jun 2026 15:44:41 +0100
Subject: [PATCH] dwarf_loader: unify true-signature parameter analysis

Move true-signature parameter decisions out of the clang-only prescan
path and into a post-recode function analysis pass.

Parameter loading now records DWARF location information on each
parameter: observed register, stack/constant locations, aggregate
piece use, and possible member replacement metadata. After DWARF type
recoding and abstract-origin resolution, a single pass walks the final
parameter list, matches it against ABI register order, and marks
optimized, unexpected, or member-shrunk parameters.

This removes the func_info prescan/skip-index representation and makes
clang DW_CC_nocall handling consume the same parameter-order machinery as
the existing GCC abstract/concrete reordered-parameter path. The BTF
encoder now keys off ftype->signature_changed instead of checking for
clang directly.

Signed-off-by: Alan Maguire <[email protected]>
Assisted-by: Codex GPT-5.5
---
 btf_encoder.c  |   4 +-
 dwarf_loader.c | 721 ++++++++++++++++++++++---------------------------
 dwarves.h      |  10 +
 3 files changed, 332 insertions(+), 403 deletions(-)

diff --git a/btf_encoder.c b/btf_encoder.c
index 26be31d..ab667d0 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1263,7 +1263,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
 	if (!state)
 		return -ENOMEM;
 
-	if (encoder->true_signature && encoder->cu->producer_clang) {
+	if (encoder->true_signature && ftype->signature_changed) {
 		ftype__for_each_parameter(ftype, param) {
 			if (param->optimized) skip_idx++;
 		}
@@ -1310,7 +1310,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
 			state->nr_parms--;
 			continue;
 		}
-		if (encoder->true_signature && encoder->cu->producer_clang && param->optimized)
+		if (encoder->true_signature && ftype->signature_changed && param->optimized)
 			continue;
 
 		name = parameter__name(param);
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 870c167..a791693 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -1200,22 +1200,7 @@ static ptrdiff_t __dwarf_getlocations(Dwarf_Attribute *attr,
 	return ret;
 }
 
-#define	PARM_DEFAULT_FAIL	-1
-#define	PARM_UNEXPECTED		-2
-#define	PARM_OPTIMIZED_OUT	-3
-#define	PARM_CONTINUE		-4
-#define	PARM_TWO_ADDR_LEN	-5
-#define	PARM_TO_BE_IMPROVED	-6
-
-/* Max 20 register parameters, considering some parameters may be optimized out.  */
-#define	MAX_PRESCAN_PARAMS	20
-
-struct func_info {
-	bool signature_changed;
-	int skip_idx;
-	int nr_params;
-	int param_start_regs[MAX_PRESCAN_PARAMS];
-};
+#define PARAMETER_UNKNOWN_REG -1
 
 static int __get_type_byte_size(Dwarf_Die *die, struct cu *cu)
 {
@@ -1268,31 +1253,6 @@ static int get_type_byte_size(Dwarf_Die *die, struct cu *cu)
 	return byte_size;
 }
 
-/* Get the first DW_OP_X (should be a register) from a parameter's DW_AT_location. */
-static int parameter__peek_first_reg(Dwarf_Die *die)
-{
-	Dwarf_Attribute attr;
-	if (dwarf_attr(die, DW_AT_location, &attr) == NULL)
-		return -1;
-
-	Dwarf_Addr base, start, end;
-	Dwarf_Op *expr;
-	size_t exprlen;
-	ptrdiff_t offset = 0;
-
-	pthread_mutex_lock(&libdw__lock);
-	offset = __dwarf_getlocations(&attr, offset, &base, &start, &end, &expr, &exprlen);
-	pthread_mutex_unlock(&libdw__lock);
-
-	if (offset <= 0 || exprlen == 0)
-		return -1;
-
-	if (expr[0].atom >= DW_OP_reg0 && expr[0].atom <= DW_OP_reg31)
-		return expr[0].atom;
-
-	return -1;
-}
-
 /* Traverse the parameter type until finding the member type which has expected
  * struct type offset.
  */
@@ -1319,325 +1279,224 @@ static Dwarf_Die *get_member_with_offset(Dwarf_Die *die, int offset, Dwarf_Die *
 		if (dwarf_tag(member_die) != DW_TAG_member)
 			continue;
 
-		int off = attr_numeric(member_die, DW_AT_data_bit_offset);
-		if (off == offset * 8)
+		Dwarf_Attribute attr;
+		Dwarf_Off bit_offset;
+
+		if (dwarf_attr(member_die, DW_AT_data_bit_offset, &attr) != NULL)
+			bit_offset = __attr_offset(&attr);
+		else if (dwarf_attr(member_die, DW_AT_data_member_location, &attr) != NULL)
+			bit_offset = __attr_offset(&attr) * 8;
+		else
+			continue;
+
+		if (bit_offset == offset * 8)
 			return member_die;
 	} while (dwarf_siblingof(member_die, member_die) == 0);
 
 	return NULL;
 }
 
-/* For two address length case, first_half and second_half represents the parameter.
- * The first_half and second_half accumulates field information across possible multiple
- * location lists.
- */
-static int parameter__multi_exprs(Dwarf_Op *expr, int loc_num, struct cu *cu, size_t exprlen,
-				  Dwarf_Die *die, int expected_reg, int byte_size,
-				  unsigned long *first_half, unsigned long *second_half, int *ret)
+static bool dwarf_op__is_reg(unsigned int atom)
 {
-	switch (expr[0].atom) {
-	case DW_OP_lit0 ... DW_OP_lit31:
-	case DW_OP_constu:
-	case DW_OP_consts:
-		if (loc_num != 0)
-			break;
-		return PARM_OPTIMIZED_OUT;
-	}
+	return atom >= DW_OP_reg0 && atom <= DW_OP_reg31;
+}
 
-	if (byte_size <= cu->addr_size || !cu->agg_use_two_regs) {
-		/* parameter_size <= cu->addr_size */
-		switch (expr[0].atom) {
-		case DW_OP_reg0 ... DW_OP_reg31:
-			if (loc_num != 0)
-				break;
-			*ret = expr[0].atom;
-			if (*ret == expected_reg)
-				return *ret;
-			break;
-		case DW_OP_breg0 ... DW_OP_breg31:
-			if (loc_num != 0)
-				break;
-			bool has_op_stack_value = false;
-			for (int i = 1; i < exprlen; i++) {
-				if (expr[i].atom == DW_OP_stack_value) {
-					has_op_stack_value = true;
-					break;
-				}
-			}
-			if (!has_op_stack_value)
-				break;
-			/* The existence of DW_OP_stack_value means that
-			 * DW_OP_bregX register is used as value.
-			 */
-			*ret = expr[0].atom - DW_OP_breg0 + DW_OP_reg0;
-			if (*ret == expected_reg)
-				return *ret;
-		}
-	} else {
-		/* cu->addr < parameter_size <= cu->addr * 2
-		 * first_half encodes field starts for the first register.
-		 * second_half encodes field starts for the second register.
-		 *
-		 * For example:
-		 *   loclist 1: DW_OP_reg5 RDI, DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1
-		 *   loclist 2: DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1
-		 *   loclist 3: DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x1)
-		 *
-		 * After iterating all the above three location lists (see PARM_CONTINUE below),
-		 * first_half encodes as 0x1 and second_half encodes as 0x1. The 'ret' value will
-		 * encode the first used register which is RDI. Each bit in first_half/second_half
-		 * represents a member field.
-		 *
-		 * Another example:
-		 *   loclist 1: DW_OP_reg5 RDI, DW_OP_piece 0x4
-		 *   loclist 2: DW_OP_piece 0x4, DW_OP_reg4 RDI, DW_OP_piece 0x4
-		 *
-		 * After iterating all the above two location lists, first_half encodes 0x11.
-		 * After loclist 1, first_half encoding is 0x1. After loclist 2, first_half encoding is 0x11.
-		 * second_half is 0. The 'ret' value is RDI.
-		 */
-		int off = 0;
-		for (int i = 0; i < exprlen; i++) {
-			if (expr[i].atom == DW_OP_piece) {
-				int num = expr[i].number;
-				if (i == 0) {
-					off = num;
-					continue;
-				}
-				if (off < cu->addr_size) (*first_half) |= (1 << off);
-				else (*second_half) |= (1 << (off - cu->addr_size));
-				off += num;
-			} else if (expr[i].atom >= DW_OP_reg0 && expr[i].atom <= DW_OP_reg31) {
-				if (off < cu->addr_size)
-					*ret = expr[i].atom;
-				else if (*ret < 0)
-					*ret = expr[i].atom;
-			}
-			/* FIXME: not handling DW_OP_bregX yet since we do not have
-			 * a use case for it yet for linux kernel.
-			 */
-		}
+static bool dwarf_expr__has_stack_value(Dwarf_Op *expr, size_t exprlen)
+{
+	for (size_t i = 1; i < exprlen; i++) {
+		if (expr[i].atom == DW_OP_stack_value)
+			return true;
 	}
-
-	return PARM_CONTINUE;
+	return false;
 }
 
-/* The first_half and second_half, computed in parameter__multi_exprs(), are handled here. */
-static int parameter__handle_two_addr_len(int expected_reg, unsigned long first_half, unsigned long second_half,
-					  int ret, Dwarf_Die *die, struct conf_load *conf, struct cu *cu,
-					  struct parameter *parm, int param_idx, int reg_idx, int byte_size,
-					  struct func_info *info)
+static void parameter__set_loc_reg(struct parameter *parm, int reg)
 {
-	if (!first_half && !second_half)
-		return ret;
-
-	if (ret != expected_reg)
-		return ret;
-
-	if (!conf->true_signature)
-		return PARM_DEFAULT_FAIL;
+	if (parm->loc_reg == PARAMETER_UNKNOWN_REG)
+		parm->loc_reg = reg;
+}
 
-	/* Both halves are used based on dwarf */
-	if (first_half && second_half)
-		return PARM_TWO_ADDR_LEN;
+static void parameter__set_field_bit(unsigned long *fields, int byte_offset)
+{
+	if (byte_offset >= 0 && byte_offset < (int)(sizeof(*fields) * 8))
+		*fields |= 1UL << byte_offset;
+}
 
-	/* Only one half is used. Check if the next parameter's starting register
-	 * indicates the ABI still reserves the full register space for this
-	 * parameter. If so, the compiler only eliminated the dead half but the
-	 * register layout is preserved — keep the original source type.
-	 *
-	 * Use register_params[] array for the expected next register since
-	 * DW_OP_reg numbers are not necessarily sequential across architectures.
-	 */
-	if (param_idx + 1 < info->nr_params) {
-		int next_start = info->param_start_regs[param_idx + 1];
+static void parameter__record_true_sig_member(struct parameter *parm, Dwarf_Die *die,
+					      int field_offset, struct conf_load *conf)
+{
+	Dwarf_Die member_die;
 
-		if (next_start >= 0) {
-			int num_regs = (byte_size + cu->addr_size - 1) / cu->addr_size;
-			int next_reg_idx = reg_idx + num_regs;
+	if (parm->true_sig_member_name)
+		return;
+	if (!parm->name)
+		return;
+	if (!get_member_with_offset(die, field_offset, &member_die))
+		return;
 
-			if (next_reg_idx < cu->nr_register_params &&
-			    next_start == cu->register_params[next_reg_idx])
-				return PARM_TWO_ADDR_LEN;
-		}
+	parm->true_sig_member_name = attr_string(&member_die, DW_AT_name, conf);
+	if (!parm->true_sig_member_name) {
+		parm->true_sig_member_name = NULL;
+		return;
 	}
 
-	/* FIXME: parm->name may be NULL due to abstract origin. We do not want to
-	 * update abstract origin as the type in abstract origin may be used
-	 * in some other places. We could remove abstract origin in this parameter
-	 * and add name and type in parameter itself. Right now, for current bpf-next
-	 * repo, we do not have instances below where parm->name is NULL for x86_64 arch.
-	 */
-	if (!parm->name)
-		return PARM_TO_BE_IMPROVED;
-
-	/* FIXME: Only support single field now so we can have a good parameter name and
-	 * type for it. For more than one field, another option could be named as
-	 * <parameter_name>__first_half or <parameter_name>__second_half, but it is not
-	 * that intuitive.
-	 */
-	if (__builtin_popcountll(first_half) >= 2 || __builtin_popcountll(second_half) >= 2)
-		return PARM_TO_BE_IMPROVED;
+	parm->true_sig_type_from_types = attr_type(&member_die, DW_AT_type, &parm->true_sig_type);
+	if (parm->true_sig_type == 0)
+		parm->true_sig_member_name = NULL;
+}
 
+static void parameter__finish_piece_decode(struct parameter *parm, Dwarf_Die *die,
+					   struct conf_load *conf, struct cu *cu)
+{
+	unsigned long first = parm->first_reg_fields;
+	unsigned long second = parm->second_reg_fields;
 	int field_offset;
-	if (__builtin_popcountll(first_half) == 1)
-		field_offset = __builtin_ctzll(first_half);
+
+	if (!first && !second)
+		return;
+	if (first && second)
+		return;
+	if (__builtin_popcountl(first) >= 2 || __builtin_popcountl(second) >= 2)
+		return;
+
+	if (__builtin_popcountl(first) == 1)
+		field_offset = __builtin_ctzl(first);
 	else
-		field_offset = cu->addr_size + __builtin_ctzll(second_half);
+		field_offset = cu->addr_size + __builtin_ctzl(second);
 
-	/* FIXME: Only struct type is supported. */
-	Dwarf_Die member_die;
-	if (!get_member_with_offset(die, field_offset, &member_die))
-		return PARM_TO_BE_IMPROVED;
+	parameter__record_true_sig_member(parm, die, field_offset, conf);
+}
 
-	/* FIXME: cannot get a proper member_name, e.g. if the member type is a union. */
-	const char *member_name = attr_string(&member_die, DW_AT_name, conf);
-	if (!member_name)
-		return PARM_TO_BE_IMPROVED;
+/* For aggregate parameters represented by pieces, first_reg_fields and
+ * second_reg_fields record the byte offsets materialized in each ABI register.
+ * The later function-level pass decides whether the source aggregate is still
+ * ABI-preserved or should be replaced by the single used member candidate.
+ */
+static void parameter__multi_exprs(Dwarf_Op *expr, int loc_num, struct cu *cu,
+				   size_t exprlen, struct parameter *parm)
+{
+	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;
+		return;
+	}
 
-	/* true_sig_member_name is the member name which will be used for later btf name
-	 * like <parameter_name>__<member_name>.
-	 */
-	parm->true_sig_member_name = member_name;
+	if (parm->type_byte_size <= cu->addr_size || !cu->agg_use_two_regs) {
+		switch (expr[0].atom) {
+		case DW_OP_reg0 ... DW_OP_reg31:
+			if (loc_num == 0)
+				parameter__set_loc_reg(parm, expr[0].atom);
+			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);
+			return;
+		default:
+			return;
+		}
+	}
 
-	struct tag *tag = &parm->tag;
-	struct dwarf_tag *dtag = tag__dwarf(tag);
-	dwarf_tag__set_attr_type(dtag, type, &member_die, DW_AT_type);
+	int off = 0;
+	for (size_t i = 0; i < exprlen; i++) {
+		if (expr[i].atom == DW_OP_piece) {
+			int num = expr[i].number;
 
-	return ret;
+			if (i == 0) {
+				off = num;
+				continue;
+			}
+
+			if (off < cu->addr_size)
+				parameter__set_field_bit(&parm->first_reg_fields, off);
+			else
+				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);
+		}
+		/* FIXME: not handling DW_OP_bregX pieces yet since we do not
+		 * have a use case for it yet in the Linux kernel.
+		 */
+	}
 }
 
-/* For DW_AT_location 'attr':
- * - if first location is DW_OP_regXX with expected number, return the register;
- *   otherwise save the register for later return
- * - if location DW_OP_entry_value(DW_OP_regXX) with expected number is in the
- *   list, return the register; otherwise save register for later return
- * - otherwise if no register was found for locations, return PARM_DEFAULT_FAIL.
- */
-static int parameter__reg(Dwarf_Attribute *attr, int expected_reg, struct conf_load *conf,
-			  struct func_info *info, struct cu *cu, Dwarf_Die *die,
-			  struct parameter *parm, int param_idx, int reg_idx)
+static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *conf,
+				       struct cu *cu, Dwarf_Die *die,
+				       struct parameter *parm)
 {
 	Dwarf_Addr base, start, end;
 	Dwarf_Op *expr, *entry_ops;
 	Dwarf_Attribute entry_attr;
 	size_t exprlen, entry_len;
 	ptrdiff_t offset = 0;
-	int byte_size = 0;
 	int loc_num = -1;
-	int ret = PARM_DEFAULT_FAIL;
-	unsigned long first_half = 0, second_half = 0;
 
-	/* use libdw__lock as dwarf_getlocation(s) has concurrency issues
-	 * when libdw is not compiled with experimental --enable-thread-safety
-	 */
 	pthread_mutex_lock(&libdw__lock);
 	while ((offset = __dwarf_getlocations(attr, offset, &base, &start, &end, &expr, &exprlen)) > 0) {
+		bool had_stack_value;
+
 		loc_num++;
+		if (exprlen == 0)
+			continue;
 
-		/* Convert expression list (XX DW_OP_stack_value) -> (XX).
-		 * DW_OP_stack_value instructs interpreter to pop current value from
-		 * DWARF expression evaluation stack, and thus is not important here.
-		 */
-		if (exprlen == 2 && expr[exprlen - 1].atom == DW_OP_stack_value)
+		had_stack_value = expr[exprlen - 1].atom == DW_OP_stack_value;
+		if (exprlen == 2 && had_stack_value)
 			exprlen--;
 
 		if (exprlen != 1) {
-			if (!info->signature_changed || !conf->true_signature)
-				continue;
-
-			if (!byte_size)
-				byte_size = get_type_byte_size(die, cu);
-			/* This should not happen. */
-			if (!byte_size) {
-				ret = PARM_UNEXPECTED;
-				goto out;
-			}
-
-			int res;
-			res = parameter__multi_exprs(expr, loc_num, cu, exprlen, die, expected_reg,
-						     byte_size, &first_half, &second_half, &ret);
-			if (res == PARM_CONTINUE)
-				continue;
-			ret = res;
-			goto out;
+			parameter__multi_exprs(expr, loc_num, cu, exprlen, parm);
+			continue;
 		}
 
 		switch (expr->atom) {
-		/* match DW_OP_regXX at first location */
 		case DW_OP_reg0 ... DW_OP_reg31:
-			if (loc_num != 0)
-				break;
-			ret = expr->atom;
-			if (ret == expected_reg)
-				goto out;
+			if (loc_num == 0)
+				parameter__set_loc_reg(parm, expr->atom);
+			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);
 			break;
 		case DW_OP_fbreg:
-			/* The location like
-			 *   DW_AT_location        (DW_OP_fbreg +<num>)
-			 * indicates that the parameter is on the stack. But it is possible
-			 * that the parameter can fit in register(s). So conservatively
-			 * mark this parameter not suitable for true signatures.
-			 */
-			if (info->signature_changed && conf->true_signature)
-				ret = PARM_UNEXPECTED;
+			parm->loc_stack = 1;
 			break;
 		case DW_OP_lit0 ... DW_OP_lit31:
 		case DW_OP_constu:
 		case DW_OP_consts:
-			if (info->signature_changed && conf->true_signature) {
-				if (loc_num != 0)
-					break;
-				ret = PARM_OPTIMIZED_OUT;
-				goto out;
-			}
+			if (loc_num == 0)
+				parm->loc_const_value = 1;
 			break;
-		/* match DW_OP_entry_value(DW_OP_regXX) at any location */
 		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) {
-				ret = entry_ops->atom;
-				if (ret == expected_reg)
-					goto out;
-			}
+			    entry_len == 1 && dwarf_op__is_reg(entry_ops->atom))
+				parameter__set_loc_reg(parm, entry_ops->atom);
 			break;
 		}
 	}
-
-	ret = parameter__handle_two_addr_len(expected_reg, first_half, second_half,
-					     ret, die, conf, cu, parm, param_idx, reg_idx,
-					     byte_size, info);
-
-out:
 	pthread_mutex_unlock(&libdw__lock);
-	return ret;
+
+	parameter__finish_piece_decode(parm, die, conf, cu);
 }
 
 static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
-					struct conf_load *conf, int param_idx,
-					struct func_info *info)
+					struct conf_load *conf, int param_idx)
 {
 	struct parameter *parm = tag__alloc(cu, sizeof(*parm));
 
 	if (parm != NULL) {
-		bool has_const_value, true_sig_enabled;
 		Dwarf_Attribute attr;
-		int reg_idx;
 
 		tag__init(&parm->tag, cu, die);
 		parm->name = attr_string(die, DW_AT_name, conf);
 		parm->idx = param_idx;
-		if (param_idx < 0)
-			return parm;
-		if (!info->signature_changed) {
-			if (cu->producer_clang || param_idx >= cu->nr_register_params)
-				return parm;
-			reg_idx = param_idx;
-		} else {
-			reg_idx = param_idx - info->skip_idx;
-		}
+		parm->loc_reg = PARAMETER_UNKNOWN_REG;
+		parm->type_byte_size = get_type_byte_size(die, cu);
 
 		/* Parameters which use DW_AT_abstract_origin to point at
 		 * the original parameter definition (with no name in the DIE)
@@ -1672,66 +1531,10 @@ static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu,
 		 * between these parameter representations.  See
 		 * ftype__recode_dwarf_types() below for how this is handled.
 		 */
-		has_const_value = dwarf_attr(die, DW_AT_const_value, &attr) != NULL;
+		parm->has_const_value = dwarf_attr(die, DW_AT_const_value, &attr) != NULL;
 		parm->has_loc = dwarf_attr(die, DW_AT_location, &attr) != NULL;
-		true_sig_enabled = conf->true_signature && info->signature_changed;
-
-		if (parm->has_loc) {
-			if (reg_idx >= cu->nr_register_params)
-				return parm;
-
-			int expected_reg = cu->register_params[reg_idx];
-			int actual_reg = parameter__reg(&attr, expected_reg, conf, info, cu, die,
-							parm, param_idx, reg_idx);
-
-			if (actual_reg == PARM_DEFAULT_FAIL) {
-				parm->optimized = 1;
-			} else if (actual_reg == PARM_OPTIMIZED_OUT) {
-				parm->optimized = 1;
-				info->skip_idx++;
-			} else if (actual_reg == PARM_TWO_ADDR_LEN) {
-				/* account for parameter with two registers */
-				info->skip_idx--;
-			} else if (actual_reg == PARM_UNEXPECTED || actual_reg == PARM_TO_BE_IMPROVED ||
-				   (expected_reg >= 0 && expected_reg != actual_reg)) {
-				/* mark parameters that use an unexpected
-				 * register to hold a parameter; these will
-				 * be problematic for users of BTF as they
-				 * violate expectations about register
-				 * contents.
-				 */
-				parm->unexpected_reg = 1;
-			}
-		} else if (has_const_value && !cu->producer_clang) {
-			parm->optimized = 1;
-		} else if (true_sig_enabled) {
-			int byte_size, num_regs, next_reg_idx;
-
-			if (param_idx + 1 < info->nr_params) {
-				int next_start = info->param_start_regs[param_idx + 1];
-				if (next_start >= 0) {
-					/* check whether we should preserve the argument or not */
-					byte_size = get_type_byte_size(die, cu);
-					/* byte_size 0 should not happen. */
-					if (!byte_size) {
-						parm->unexpected_reg = 1;
-						return parm;
-					}
-
-					num_regs = (byte_size + cu->addr_size - 1) / cu->addr_size;
-					next_reg_idx = reg_idx + num_regs;
-					if (next_reg_idx < cu->nr_register_params &&
-					    next_start == cu->register_params[next_reg_idx]) {
-						if (byte_size > cu->addr_size)
-							info->skip_idx--;
-						return parm;
-					}
-				}
-			}
-
-			parm->optimized = 1;
-			info->skip_idx++;
-		}
+		if (parm->has_loc)
+			parameter__decode_location(&attr, conf, cu, die, parm);
 	}
 
 	return parm;
@@ -1751,7 +1554,7 @@ static int formal_parameter_pack__load_params(struct formal_parameter_pack *pack
 			continue;
 		}
 
-		struct parameter *param = parameter__new(die, cu, conf, -1, NULL);
+		struct parameter *param = parameter__new(die, cu, conf, -1);
 
 		if (param == NULL)
 			return -1;
@@ -2237,9 +2040,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, struct func_info *info)
+					     int param_idx)
 {
-	struct parameter *parm = parameter__new(die, cu, conf, param_idx, info);
+	struct parameter *parm = parameter__new(die, cu, conf, param_idx);
 
 	if (parm == NULL)
 		return NULL;
@@ -2326,7 +2129,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, NULL);
+			tag = die__create_new_parameter(die, ftype, NULL, cu, conf, -1);
 			break;
 		case DW_TAG_unspecified_parameters:
 			ftype->unspec_parms = 1;
@@ -2555,8 +2358,7 @@ out_enomem:
 }
 
 static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
-				 struct lexblock *lexblock, struct cu *cu, struct conf_load *conf,
-				 struct func_info *info);
+				 struct lexblock *lexblock, struct cu *cu, struct conf_load *conf);
 
 static int die__create_new_lexblock(Dwarf_Die *die,
 				    struct cu *cu, struct lexblock *father, struct conf_load *conf)
@@ -2564,7 +2366,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, NULL) != 0)
+		if (die__process_function(die, NULL, lexblock, cu, conf) != 0)
 			goto out_delete;
 	}
 	if (father != NULL)
@@ -2684,8 +2486,7 @@ 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 func_info *info)
+				 struct lexblock *lexblock, struct cu *cu, struct conf_load *conf)
 {
 	int param_idx = 0;
 	Dwarf_Die child;
@@ -2759,7 +2560,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++, info);
+			tag = die__create_new_parameter(die, ftype, lexblock, cu, conf, param_idx++);
 			break;
 		case DW_TAG_variable:
 			tag = die__create_new_variable(die, cu, conf, 0);
@@ -2827,58 +2628,18 @@ out_enomem:
 	return -ENOMEM;
 }
 
-/* Pre-scan all formal parameters to collect their starting registers.
- * This allows look-ahead when processing parameters sequentially, so that
- * a parameter can check the next parameter's register to determine if the
- * ABI register layout is preserved despite partial optimization.
- * For example, for a function like below:
- *  struct t { long f1; long f2; };
- *  __attribute__((noinline)) static long foo(struct t a, struct t b)
- *  {
- *      return a.f1 + b.f1 + b.f2;
- *  }
- * If dwarf has parameter 'a' at aarch64 register W0, and 'b' at register W2,
- * even compiler could optimize 'a' to 'a.f1'. To conform to ABI, the
- * parameter 'a' will keep 'struct t' type.
- */
-static void func_info__prescan_params(struct func_info *info, Dwarf_Die *die)
-{
-	Dwarf_Die child;
-	int idx = 0;
-
-	if (!info->signature_changed)
-		return;
-
-	if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0)
-		return;
-
-	do {
-		if (dwarf_tag(&child) != DW_TAG_formal_parameter)
-			continue;
-		if (idx >= MAX_PRESCAN_PARAMS)
-			break;
-		info->param_start_regs[idx] = parameter__peek_first_reg(&child);
-		idx++;
-	} while (dwarf_siblingof(&child, &child) == 0);
-
-	info->nr_params = idx;
-}
-
 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) {
 		/* 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);
-			func_info__prescan_params(&info, die);
-		}
+		if (cu->producer_clang)
+			function->proto.signature_changed = function__signature_changed(function, die);
 
-		if (die__process_function(die, &function->proto, &function->lexblock, cu, conf, &info) != 0) {
+		if (die__process_function(die, &function->proto, &function->lexblock, cu, conf) != 0) {
 			function__delete(function, cu);
 			function = NULL;
 		}
@@ -3133,6 +2894,23 @@ static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
 			 */
 			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;
+			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 (pos->optimized)
 				opos->optimized = pos->optimized;
@@ -3150,6 +2928,145 @@ static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu)
 	}
 }
 
+static struct parameter *ftype__next_parameter(struct ftype *ftype, struct parameter *parm)
+{
+	if (parm->tag.node.next == &ftype->parms)
+		return NULL;
+	return list_entry(parm->tag.node.next, struct parameter, tag.node);
+}
+
+static int parameter__abi_slots(const struct parameter *parm, const struct cu *cu)
+{
+	int slots;
+
+	if (!cu->agg_use_two_regs || parm->type_byte_size <= cu->addr_size)
+		return 1;
+
+	slots = (parm->type_byte_size + cu->addr_size - 1) / cu->addr_size;
+	return slots > 0 ? slots : 1;
+}
+
+static bool parameter__has_piece_info(const struct parameter *parm)
+{
+	return parm->first_reg_fields || parm->second_reg_fields;
+}
+
+static bool parameter__uses_full_aggregate(const struct parameter *parm)
+{
+	return parm->first_reg_fields && parm->second_reg_fields;
+}
+
+static bool ftype__next_parameter_preserves_slots(struct ftype *ftype, struct parameter *parm,
+						  int reg_idx, int slots, struct cu *cu)
+{
+	struct parameter *next = ftype__next_parameter(ftype, parm);
+	int next_reg_idx;
+
+	if (!next || next->loc_reg == PARAMETER_UNKNOWN_REG)
+		return false;
+
+	next_reg_idx = reg_idx + slots;
+	return next_reg_idx < cu->nr_register_params &&
+	       next->loc_reg == cu->register_params[next_reg_idx];
+}
+
+static bool parameter__apply_true_sig_member(struct parameter *parm, struct cu *cu)
+{
+	struct dwarf_tag tmp = {};
+	struct dwarf_tag *dtype;
+
+	if (!parm->true_sig_member_name || parm->true_sig_type == 0)
+		return false;
+
+	tmp.type = parm->true_sig_type;
+	tmp.from_types_section.type = parm->true_sig_type_from_types;
+	dtype = __dwarf_cu__find_type_by_ref(cu->priv, tmp.type, tmp.from_types_section.type);
+	if (!dtype)
+		return false;
+
+	parm->tag.type = dtype->small_id;
+	return true;
+}
+
+static void function__analyze_parameter_locations(struct function *fn, struct cu *cu,
+						  struct conf_load *conf)
+{
+	struct ftype *ftype = &fn->proto;
+	struct parameter *pos;
+	bool true_sig_enabled = conf->true_signature && ftype->signature_changed;
+	bool check_registers = !cu->producer_clang || true_sig_enabled;
+	int reg_idx = 0;
+
+	if (!check_registers)
+		return;
+
+	ftype__for_each_parameter(ftype, pos) {
+		bool consumes_register = true;
+		int slots = parameter__abi_slots(pos, cu);
+		int expected_reg;
+
+		if (reg_idx >= cu->nr_register_params)
+			continue;
+
+		expected_reg = cu->register_params[reg_idx];
+
+		if (pos->has_loc) {
+			if (true_sig_enabled && pos->loc_const_value) {
+				pos->optimized = 1;
+				consumes_register = false;
+				goto next;
+			}
+
+			if (true_sig_enabled && pos->loc_stack) {
+				pos->unexpected_reg = 1;
+				goto next;
+			}
+
+			if (pos->loc_reg == PARAMETER_UNKNOWN_REG) {
+				pos->optimized = 1;
+				consumes_register = !true_sig_enabled;
+				goto next;
+			}
+
+			if (expected_reg >= 0 && expected_reg != pos->loc_reg) {
+				pos->unexpected_reg = 1;
+				goto next;
+			}
+
+			if (true_sig_enabled && parameter__has_piece_info(pos)) {
+				if (parameter__uses_full_aggregate(pos) ||
+				    ftype__next_parameter_preserves_slots(ftype, pos, reg_idx, slots, cu)) {
+					reg_idx += slots;
+					continue;
+				}
+
+				if (parameter__apply_true_sig_member(pos, cu)) {
+					reg_idx++;
+					continue;
+				}
+
+				pos->unexpected_reg = 1;
+				reg_idx += slots;
+				continue;
+			}
+		} else if (pos->has_const_value && !cu->producer_clang) {
+			pos->optimized = 1;
+		} else if (true_sig_enabled) {
+			if (ftype__next_parameter_preserves_slots(ftype, pos, reg_idx, slots, cu)) {
+				reg_idx += slots;
+				continue;
+			}
+
+			pos->optimized = 1;
+			consumes_register = false;
+		}
+
+next:
+		if (consumes_register)
+			reg_idx++;
+	}
+}
+
 static void lexblock__recode_dwarf_types(struct lexblock *tag, struct cu *cu)
 {
 	struct tag *pos;
@@ -3425,7 +3342,7 @@ static bool param__is_struct(struct cu *cu, struct tag *tag)
 	}
 }
 
-static int cu__resolve_func_ret_types_optimized(struct cu *cu)
+static int cu__resolve_func_ret_types_optimized(struct cu *cu, struct conf_load *conf)
 {
 	struct ptr_table *pt = &cu->functions_table;
 	uint32_t i;
@@ -3436,6 +3353,8 @@ static int cu__resolve_func_ret_types_optimized(struct cu *cu)
 		struct function *fn = tag__function(tag);
 		bool has_unexpected_reg = false, has_struct_param = false;
 
+		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
@@ -3614,7 +3533,7 @@ static int die__process_and_recode(Dwarf_Die *die, struct cu *cu, struct conf_lo
 	if (ret != 0)
 		return ret;
 
-	return cu__resolve_func_ret_types_optimized(cu);
+	return cu__resolve_func_ret_types_optimized(cu, conf);
 }
 
 static int class_member__cache_byte_size(struct tag *tag, struct cu *cu,
@@ -4377,7 +4296,7 @@ static int cus__merge_and_process_cu(struct cus *cus, struct conf_load *conf,
 	 * encoded in another subprogram through abstract_origin
 	 * tag. Let us visit all subprograms again to resolve this.
 	 */
-	if (cu__resolve_func_ret_types_optimized(cu) != LSK__KEEPIT)
+	if (cu__resolve_func_ret_types_optimized(cu, conf) != LSK__KEEPIT)
 		goto out_abort;
 
 	cu__finalize(cu, cus, conf);
diff --git a/dwarves.h b/dwarves.h
index 2fc937a..ed3f005 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -949,6 +949,15 @@ struct parameter {
 	struct tag tag;
 	const char *name;
 	const char *true_sig_member_name;
+	Dwarf_Off true_sig_type;
+	unsigned long first_reg_fields;
+	unsigned long second_reg_fields;
+	int loc_reg;
+	uint16_t type_byte_size;
+	uint8_t true_sig_type_from_types:1;
+	uint8_t has_const_value:1;
+	uint8_t loc_const_value:1;
+	uint8_t loc_stack:1;
 	uint8_t optimized:1;
 	uint8_t unexpected_reg:1;
 	uint8_t has_loc:1;
@@ -1033,6 +1042,7 @@ struct ftype {
 	uint8_t		 inconsistent_proto:1;
 	uint8_t		 uncertain_parm_loc:1;
 	uint8_t		 reordered_parm:1;
+	uint8_t		 signature_changed:1;
 	struct list_head template_type_params;
 	struct list_head template_value_params;
 	struct template_parameter_pack *template_parameter_pack;
-- 
2.43.5