Re: [PATCH v4 20/23] perf annotate-arm64: Support 'add' instruction tracking

Shuai Xue <[email protected]>
Newsgroups dev.linux.lists.llvm,org.kernel.vger.linux-kernel,org.kernel.vger.linux-perf-users
Message-ID <[email protected]>

On 8/8/26 8:23 PM, Tengda Wu wrote:
> Extend update_insn_state() for arm64 to track 'add' instructions for
> structure member address calculation, which commonly appear as:
> 
>    add  dreg, base, #offset
>    add  dreg, base, reg2     (reg2 holds a constant)
> 
> Unlike x86, the arm64 'add' instruction has an extra base register among
> its source operands. Therefore, in terms of propagating the data type,
> it is essentially performing a 'mov', except that before the 'mov', it
> first needs to be updated by adding the offset or reg2.
> 
> A real-world example is shown below:
> 
>    ffff80008001c9a8 <flush_ptrace_hw_breakpoint>:
>    ffff80008001c9c4:  add  x19, x0, #0xeb8   // x0 (task_struct*) + 0xeb8 -> x19
> * ffff80008001c9d0:  ldr  x0, [x19]
> 
> Before this commit, the type flow broke at the 'add' instruction,
> leaving the subsequent load with no type information:
> 
>    chk [28] reg19 offset=0 ok=0 kind=0 cfa : no type information
>    final result: no type information
> 
> After this commit, the tracker correctly follows the member address
> calculation:
> 
>    var [0] reg0 offset 0 type='struct task_struct*'
>    add [1c] address of 0xeb8(reg0) -> reg19 type='struct task_struct*'
>    chk [28] reg19 offset=0 ok=1 kind=1 (struct task_struct*) : Good!
>    found by insn track: 0(reg19) type-offset=0xeb8
>    final result: type='struct task_struct'
> 
> Signed-off-by: Tengda Wu <[email protected]>
> ---
>   .../perf/util/annotate-arch/annotate-arm64.c  | 87 ++++++++++++++++++-
>   1 file changed, 85 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
> index 7b780bad8c07..eaeb4433fc3a 100644
> --- a/tools/perf/util/annotate-arch/annotate-arm64.c
> +++ b/tools/perf/util/annotate-arch/annotate-arm64.c
> @@ -688,6 +688,87 @@ static void update_mov_insn_state(struct type_state *state,
>   	pr_debug_type_name(&tsr->type, tsr->kind);
>   }
>   
> +static void update_add_insn_state(struct type_state *state,
> +				  struct disasm_line *dl,
> +				  struct annotated_op_loc *src,
> +				  struct annotated_op_loc *dst)
> +{
> +	struct type_state_reg *tsr;
> +	struct type_state_reg src_tsr;
> +	u32 insn_offset = dl->al.offset;
> +	int sreg = src->reg1;
> +	int dreg = dst->reg1;
> +	u64 imm_value;
> +
> +	if (!has_reg_type(state, dreg))
> +		return;
> +
> +	tsr = &state->regs[dreg];
> +	tsr->copied_from = -1;
> +
> +retry:
> +	if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
> +		invalidate_reg_state(tsr);
> +		return;
> +	}
> +
> +	src_tsr = state->regs[sreg];
> +
> +	/*
> +	 * Handle 'add' instructions of the form:
> +	 *   add  dreg, base, #offset     (immediate offset)
> +	 *   add  dreg, base, reg2        (reg2 holds a constant)
> +	 *
> +	 * For case 2, retrieve the constant value from reg2
> +	 * and use it as the offset.
> +	 */
> +	imm_value = src->offset;
> +	if (src->multi_regs) {
> +		int reg2 = (sreg == src->reg1) ? src->reg2 : src->reg1;
> +
> +		if (!has_reg_type(state, reg2) || !state->regs[reg2].ok) {
> +			/* Unable to resolve type for dst, bail out */
> +			invalidate_reg_state(tsr);
> +			return;
> +		}
> +		if (state->regs[reg2].kind == TSR_KIND_CONST)
> +			imm_value = state->regs[reg2].imm_value;

First, when reg2 is valid but not TSR_KIND_CONST - say a pointer
freshly loaded from memory - the addend is unknown at analysis time,
yet imm_value silently stays 0 and propagation continues, tracking
the destination as base + 0. Note the asymmetry: no state at all
invalidates, but state that doesn't yield a value pretends the
addend is zero. Unknown addend should invalidate as well:

         if (state->regs[reg2].kind != TSR_KIND_CONST) {
             invalidate_reg_state(tsr);
             return;
         }

Second, the shifted/extended forms still break even with a constant
reg2: extract_op_location_arm64() drops the "lsl #3" (or uxtw/sxtw)
modifier, so add x0, x1, x2, lsl #3 contributes the unshifted value
and maps later accesses to the wrong field. Maybe flag such operands
during extraction (or fail the reg2 extraction) so the handler can
skip them.

> +	}
> +
> +	if (src_tsr.kind == TSR_KIND_CONST) {
> +		tsr->kind = src_tsr.kind;
> +		tsr->imm_value = src_tsr.imm_value + imm_value;
> +		tsr->offset = 0;
> +		tsr->ok = src_tsr.ok;
> +
> +		pr_debug_dtp("add [%x] imm %#"PRIx64"(reg%d) -> reg%d\n",
> +			     insn_offset, imm_value, sreg, dreg);

And since add is commutative, this early return drops the pointer
side for add rd, const_reg, ptr_reg: sreg starts as reg1, hits the
CONST case and returns before the goto retry at the bottom ever gets
a chance to evaluate the pointer register. The result should be the
pointer with the constant applied as an offset, not a plain constant.
Maybe check the other register for a pointer type before falling into
the CONST case (or move the CONST handling after the retry).

Thanks,
Shuai
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.