[PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls

Yanbo Zhao <[email protected]>
Newsgroups org.kernel.vger.linux-perf-users,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Implement disassembler and x86 type profiling support for C++ virtual
dispatch and indirect call resolution:
- In call__parse() for indirect calls, duplicate and store ops->raw into
  ops->target.raw so instruction location extraction can retrieve target
  operand registers and offsets.
- In extract_reg_offset(), skip any leading '*' prefix (e.g. *%rax).
- Add TSR_KIND_VTABLE_PTR to enum type_state_kind and handle it in
  pr_debug_type_name().
- In update_insn_state_x86() for mov instructions, inspect member
  name and identify '_vptr' access, setting register kind to
  TSR_KIND_VTABLE_PTR via die_is_vptr_member() helper.
- For call instructions targeting virtual function tables, look up
  the virtual function DIE via die_find_virtual_func() before
  register state invalidation.  Only the primary vtable is handled
  for now (TODO: multiple inheritance).
- Update the receiver ('this' pointer) register to the C++ class DIE
  upon virtual call resolution when CU is C++.  The register number
  comes from the new type_state::arg0_reg field initialized per arch
  like ret_reg, instead of being hardcoded.
- Move caller-saved register invalidation after reading target
  operands to prevent losing state required for virtual call
  resolution.

Signed-off-by: Yanbo Zhao <[email protected]>
---
 tools/perf/util/annotate-arch/annotate-x86.c | 103 ++++++++++++++++---
 tools/perf/util/annotate-data.c              |   7 ++
 tools/perf/util/annotate-data.h              |   3 +
 tools/perf/util/annotate.c                   |   4 +
 tools/perf/util/disasm.c                     |   1 +
 5 files changed, 105 insertions(+), 13 deletions(-)

diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
index 7e6136536393..cfb37a6130ba 100644
--- a/tools/perf/util/annotate-arch/annotate-x86.c
+++ b/tools/perf/util/annotate-arch/annotate-x86.c
@@ -11,6 +11,8 @@
 #include "../map.h"
 #include "../string2.h" // strstarts
 #include "../symbol.h"
+#include "../hist.h"
+#include "../dwarf-aux.h"
 
 /*
  * x86 instruction nmemonic table to parse disasm lines for annotate.
@@ -234,6 +236,11 @@ static void update_insn_state_x86(struct type_state *state,
 		struct symbol *func = dl->ops.target.sym;
 		const char *call_name;
 		u64 call_addr;
+		Dwarf_Die target_func_die;
+		bool resolved_statically = false;
+		Dwarf_Die class_die;
+		bool has_class_die = false;
+		struct annotated_op_loc *target_op;
 
 		/* Try to resolve the call target name */
 		if (func)
@@ -245,12 +252,42 @@ static void update_insn_state_x86(struct type_state *state,
 		if (call_name && !strcmp(call_name, "__fentry__"))
 			return;
 
+		/*
+		 * 1. Resolve target statically (virtual call fallback) FIRST
+		 * (before invalidation)
+		 */
+		target_op = &loc.ops[INSN_OP_TARGET];
+
+		if (target_op->mem_ref && has_reg_type(state, target_op->reg1)) {
+			struct type_state_reg *reg = &state->regs[target_op->reg1];
+
+			if (reg->ok && reg->kind == TSR_KIND_VTABLE_PTR) {
+				/* TODO: handle multiple inheritance (non-primary vtables) */
+				int vtable_index = target_op->offset / 8;
+				Dwarf_Die target_class_die = reg->type;
+
+				if (die_find_virtual_func(&target_class_die, vtable_index,
+							  &target_func_die) &&
+				    die_get_type(&target_func_die, &type_die)) {
+					resolved_statically = true;
+					class_die = target_class_die;
+					has_class_die = true;
+				}
+			}
+		}
+
+		/* Fallback to name-based resolution for direct calls */
+		if (!resolved_statically && call_name) {
+			if (die_find_func_rettype(cu_die, call_name, &type_die))
+				resolved_statically = true;
+		}
+
 		if (call_name)
 			pr_debug_dtp("call [%x] %s\n", insn_offset, call_name);
 		else
 			pr_debug_dtp("call [%x] <unknown>\n", insn_offset);
 
-		/* Invalidate caller-saved registers after call */
+		/* 2. Invalidate caller-saved registers after we read them for resolution */
 		call_addr = map__rip_2objdump(dloc->ms->map,
 					      dloc->ms->sym->start + dl->al.offset);
 		for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {
@@ -264,8 +301,8 @@ static void update_insn_state_x86(struct type_state *state,
 			invalidate_reg_state(reg);
 		}
 
-		/* Update register with the return type (if any) */
-		if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) {
+		/* 3. Apply resolved types to registers */
+		if (resolved_statically) {
 			tsr = &state->regs[state->ret_reg];
 			tsr->type = type_die;
 			tsr->kind = TSR_KIND_TYPE;
@@ -275,6 +312,20 @@ static void update_insn_state_x86(struct type_state *state,
 			pr_debug_dtp("call [%x] return -> reg%d",
 				     insn_offset, state->ret_reg);
 			pr_debug_type_name(&type_die, tsr->kind);
+
+			/* Update receiver ('this' pointer) register if C++ */
+			if (has_class_die && cu_is_cplusplus(cu_die)) {
+				struct type_state_reg *recv_tsr = &state->regs[state->arg0_reg];
+
+				if (recv_tsr->ok &&
+				    (recv_tsr->kind == TSR_KIND_TYPE ||
+				     recv_tsr->kind == TSR_KIND_POINTER)) {
+					recv_tsr->type = class_die;
+					pr_debug_dtp("call [%x] update receiver reg%d to C++ class",
+						     insn_offset, state->arg0_reg);
+					pr_debug_type_name(&class_die, recv_tsr->kind);
+				}
+			}
 		}
 		return;
 	}
@@ -622,17 +673,43 @@ static void update_insn_state_x86(struct type_state *state,
 		}
 		/* And then dereference the pointer if it has one */
 		else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&
-			 state->regs[sreg].kind == TSR_KIND_TYPE &&
-			 die_deref_ptr_type(&state->regs[sreg].type,
-					    src->offset + state->regs[sreg].offset, &type_die)) {
-			tsr->type = type_die;
-			tsr->kind = TSR_KIND_TYPE;
-			tsr->offset = 0;
-			tsr->ok = true;
+			 state->regs[sreg].kind == TSR_KIND_TYPE) {
+			Dwarf_Die class_type;
+			Dwarf_Die member;
+			int total_offset = src->offset + state->regs[sreg].offset;
+			bool is_vptr = false;
+
+			if (die_get_real_type(&state->regs[sreg].type, &class_type) &&
+			    die_is_compound_type(&class_type) &&
+			    die_find_member_by_offset(&class_type, total_offset, &member) &&
+			    die_is_vptr_member(&member)) {
+				tsr->type = class_type;
+				tsr->kind = TSR_KIND_VTABLE_PTR;
+				tsr->offset = 0;
+				tsr->ok = true;
 
-			pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d",
-				     insn_offset, src->offset, sreg, dst->reg1);
-			pr_debug_type_name(&tsr->type, tsr->kind);
+				pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d (vptr)",
+					     insn_offset, src->offset,
+					     sreg, dst->reg1);
+				pr_debug_type_name(&class_type, tsr->kind);
+				is_vptr = true;
+			}
+
+			if (!is_vptr) {
+				if (die_deref_ptr_type(&state->regs[sreg].type,
+						       total_offset, &type_die)) {
+					tsr->type = type_die;
+					tsr->kind = TSR_KIND_TYPE;
+					tsr->offset = 0;
+					tsr->ok = true;
+
+					pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d",
+						     insn_offset, src->offset, sreg, dst->reg1);
+					pr_debug_type_name(&tsr->type, tsr->kind);
+				} else {
+					invalidate_reg_state(tsr);
+				}
+			}
 		}
 		/* Handle dereference of TSR_KIND_POINTER registers */
 		else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index ee6bd2d0012d..875e5ba2fe9f 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -28,6 +28,8 @@
 
 /* register number of the stack pointer */
 #define X86_REG_SP 7
+/* register number of the first integer argument (%rdi) */
+#define X86_REG_DI 5
 
 static void delete_var_types(struct die_var_type *var_types);
 
@@ -66,6 +68,10 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind)
 		pr_info(" pointer");
 		/* it also prints the type info */
 		break;
+	case TSR_KIND_VTABLE_PTR:
+		pr_info(" C++ vtable pointer");
+		/* it also prints the type info */
+		break;
 	case TSR_KIND_CANARY:
 		pr_info(" stack canary\n");
 		return;
@@ -177,6 +183,7 @@ static void init_type_state(struct type_state *state, const struct arch *arch)
 		state->regs[10].caller_saved = true;
 		state->regs[11].caller_saved = true;
 		state->ret_reg = 0;
+		state->arg0_reg = X86_REG_DI;
 		state->stack_reg = X86_REG_SP;
 	}
 }
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index c26130744260..785e1d1777ce 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -36,6 +36,7 @@ enum type_state_kind {
 	TSR_KIND_CONST,
 	TSR_KIND_PERCPU_POINTER,
 	TSR_KIND_POINTER,
+	TSR_KIND_VTABLE_PTR,
 	TSR_KIND_CANARY,
 };
 
@@ -224,6 +225,8 @@ struct type_state {
 	struct list_head stack_vars;
 	/* return value register */
 	int ret_reg;
+	/* first integer argument register (holds 'this' for C++ methods) */
+	int arg0_reg;
 	/* stack pointer register */
 	int stack_reg;
 };
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index df70e95a8470..b4c6b45b4da3 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -2509,6 +2509,10 @@ static int extract_reg_offset(const struct arch *arch, const char *str,
 	if (arch->objdump.register_char == 0)
 		return -1;
 
+	/* Skip leading '*' for indirect call/jmp operands (e.g. *%rax) */
+	if (*str == '*')
+		str++;
+
 	/*
 	 * It should start from offset, but it's possible to skip 0
 	 * in the asm.  So 0(%rax) should be same as (%rax).
diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c
index 6cfdbabbb8c7..01266eeddbb4 100644
--- a/tools/perf/util/disasm.c
+++ b/tools/perf/util/disasm.c
@@ -279,6 +279,7 @@ static int call__parse(const struct arch *arch, struct ins_operands *ops, struct
 	return 0;
 
 indirect_call:
+	ops->target.raw = strdup(ops->raw);
 	tok = strchr(endptr, '*');
 	if (tok != NULL) {
 		endptr++;
-- 
2.25.1
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.