perf annotate-arm64: Enable instruction tracking support

Enable basic instruction tracking for arm64 by implementing three
essential functions in the find_data_type_block() call path:

  find_data_type_block
    -> arch_supports_insn_tracking  (1)
    -> find_data_type_insn
         -> init_type_state         (2)
         -> update_var_state
         -> update_insn_state       (3)

Changes:

* arch_supports_insn_tracking(): add arm64 to the list of supported
  architectures, allowing find_data_type_block() to proceed with data
  type analysis.

* init_type_state(): correctly identify ret_reg, stack_reg, and
  caller-saved registers for arm64 during type state initialization.

* update_insn_state(): add the update_insn_state_arm64() callback with
  conservative register invalidation for call instructions and those with
  destination register(s), preventing stale type propagation.

  For now the invalidation path does not treat special instructions
  individually; doing so for every such instruction up front would be
  costly. A TODO is therefore left in invalidate_unhandled_insn_regs()
  marking where per-instruction invalidation handling is to be extended
  in future patches.

With these changes, arm64 gains support for basic variable type
inference during instruction tracking.

Reviewed-by: Ian Rogers <irogers@google.com>
Signed-off-by: Li Huafei <lihuafei1@huawei.com>
Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Tengda Wu 2026-10-20 13:44:58 +00:00 committed by Arnaldo Carvalho de Melo
parent edc11f5794
commit 059f12f8cd
2 changed files with 101 additions and 1 deletions

View File

@ -9,6 +9,10 @@
#include <regex.h>
#include "../annotate.h"
#include "../disasm.h"
#include "../annotate-data.h"
#include "../debug.h"
#include "../map.h"
#include "../symbol.h"
struct arch_arm64 {
struct arch arch;
@ -448,6 +452,86 @@ static void extract_op_location_arm64(const struct arch *arch,
}
}
#ifdef HAVE_LIBDW_SUPPORT
/* Check if the instruction is read-only (does not modify register state). */
static bool is_readonly_insn(const char *name)
{
return !strncmp(name, "cb", 2) || !strncmp(name, "tb", 2) ||
!strcmp(name, "tst") || !strcmp(name, "teq") ||
!strcmp(name, "cmp") || !strcmp(name, "cmn") ||
!strcmp(name, "ccmp") || !strcmp(name, "ccmn");
}
static void update_call_insn_state(struct type_state *state,
struct data_loc_info *dloc,
struct disasm_line *dl)
{
struct symbol *func = dl->ops.target.sym;
const char *call_name;
u64 call_addr;
u32 insn_offset = dl->al.offset;
call_name = func ? func->name : dl->ops.target.name;
pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: "<unknown>");
/* Invalidate caller-saved registers after call */
call_addr = map__rip_2objdump(dloc->ms->map,
dloc->ms->sym->start + dl->al.offset);
for (unsigned int i = 0; i < ARRAY_SIZE(state->regs); i++) {
struct type_state_reg *reg = &state->regs[i];
if (!reg->caller_saved)
continue;
/* Keep register valid within DWARF location lifetime */
if (reg->lifetime_active && call_addr < reg->lifetime_end)
continue;
invalidate_reg_state(reg);
}
}
/* Invalidate registers explicitly written by an unhandled instruction. */
static void invalidate_unhandled_insn_regs(struct type_state *state,
struct disasm_line *dl,
struct annotated_op_loc *dst)
{
u32 insn_offset = dl->al.offset;
/* Invalidate destination registers */
if (has_reg_type(state, dst->reg1) && !dst->mem_ref) {
pr_debug_dtp("%s [%x] invalidate reg%d",
dl->ins.name, insn_offset, dst->reg1);
invalidate_reg_state(&state->regs[dst->reg1]);
if (dst->multi_regs && has_reg_type(state, dst->reg2)) {
pr_debug_dtp(" and reg%d", dst->reg2);
invalidate_reg_state(&state->regs[dst->reg2]);
}
pr_debug_dtp("\n");
}
/* TODO: Extend invalidation support to more special instructions. */
}
static void update_insn_state_arm64(struct type_state *state,
struct data_loc_info *dloc, Dwarf_Die *cu_die __maybe_unused,
struct disasm_line *dl)
{
struct annotated_insn_loc loc;
struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];
if (is_readonly_insn(dl->ins.name))
return;
if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0)
return;
if (ins__is_call(&dl->ins))
update_call_insn_state(state, dloc, dl);
else
invalidate_unhandled_insn_regs(state, dl, dst);
}
#endif
const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
@ -467,6 +551,9 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
arch->objdump.imm_char = '#';
arch->associate_instruction_ops = arm64__associate_instruction_ops;
arch->extract_op_location = extract_op_location_arm64;
#ifdef HAVE_LIBDW_SUPPORT
arch->update_insn_state = update_insn_state_arm64;
#endif
/* bl, blr */
err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED);

View File

@ -29,6 +29,7 @@
/* register number of the stack pointer */
#define X86_REG_SP 7
#define ARM64_REG_SP 31
static void delete_var_types(struct die_var_type *var_types);
@ -179,6 +180,17 @@ static void init_type_state(struct type_state *state, const struct arch *arch)
state->regs[11].caller_saved = true;
state->ret_reg = 0;
state->stack_reg = X86_REG_SP;
} else if (arch__is_arm64(arch)) {
/*
* Mark x0..x18 as caller-saved. x30 (LR) is intentionally
* omitted from the caller_saved set: it is clobbered by
* calls, but holds only the return address and is not used
* to carry data type information.
*/
for (int i = 0; i < 19; i++)
state->regs[i].caller_saved = true;
state->ret_reg = 0;
state->stack_reg = ARM64_REG_SP;
}
}
@ -1543,7 +1555,8 @@ static enum type_match_result find_data_type_insn(struct data_loc_info *dloc,
static int arch_supports_insn_tracking(struct data_loc_info *dloc)
{
if ((arch__is_x86(dloc->arch)) || (arch__is_powerpc(dloc->arch)))
if (arch__is_x86(dloc->arch) || arch__is_powerpc(dloc->arch) ||
arch__is_arm64(dloc->arch))
return 1;
return 0;
}