* [PATCH 0/3] perf annotate: Data type profiling support for C++ classes and virtual calls
@ 2026-08-21 5:02 Yanbo Zhao
2026-08-21 5:02 ` [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types Yanbo Zhao
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Yanbo Zhao @ 2026-08-21 5:02 UTC (permalink / raw)
To: Namhyung Kim, Arnaldo Carvalho de Melo
Cc: Peter Zijlstra, Ingo Molnar, Mark Rutland, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Zecheng Li,
Xu Liu, linux-perf-users, linux-kernel, Yanbo Zhao
Data type profiling currently only understands C struct/union types.
For C++ workloads, member accesses on classes cannot be resolved, and
virtual function calls (indirect calls through the vtable) break the
instruction type tracking entirely: the call target is unknown, so the
return type is lost and register states get invalidated.
This series extends data type profiling to C++:
Patch 1 introduces die_is_compound_type() covering
DW_TAG_structure_type, DW_TAG_union_type and DW_TAG_class_type, and
converts all existing open-coded struct/union checks in dwarf-aux.c
and annotate-data.c to use it. It also accepts DW_TAG_inheritance in
the member lookup callbacks so offset-based lookup descends into base
class subobjects.
Patch 2 adds the C++-specific DWARF helpers: CU language detection,
base class and vtable index inspection, virtual function lookup by
vtable index (recursing into base classes), and vtable pointer
(_vptr) member identification. Fallback definitions are provided for
DW_AT_vtable_elem_index and DW_LANG_C_plus_plus_17/20 so the code
builds against older elfutils.
Patch 3 wires it up in the disassembler and the x86 instruction
tracking: a load of a '_vptr' member marks the destination register
as TSR_KIND_VTABLE_PTR, and an indirect call through such a register
is resolved statically via the vtable index, recovering the return
type and updating the receiver ('this' pointer) register to the class
type. The receiver register number comes from the new
type_state::arg0_reg field initialized per arch like ret_reg.
Caller-saved register invalidation is moved after the target operands
are read so the state needed for resolution is not lost.
Only the primary vtable is handled for now; multiple inheritance is
left as a TODO. Updating the receiver register for any method call
that passes a 'this' pointer (not only resolved virtual calls) needs
a callee DIE lookup by linkage name and is planned as a follow-up on
top of this series.
Verified on an x86-64 C++ workload with virtual calls in a hot loop
(built with -O2 -g): the vptr loads are tracked, the indirect calls
are resolved, and 'perf report -s type,typeoff' attributes the
samples to 'struct Base +0 (_vptr.Base)'. Also passed 'perf test'
annotate basic tests and checkpatch on all three patches.
Yanbo Zhao (3):
perf dwarf-aux: Add die_is_compound_type() to handle C++ class types
perf dwarf-aux: Add C++ class, inheritance and vtable helpers
perf disasm & annotate x86: Track C++ vptr and resolve virtual
function calls
tools/perf/util/annotate-arch/annotate-x86.c | 103 +++++++--
tools/perf/util/annotate-data.c | 36 ++--
tools/perf/util/annotate-data.h | 3 +
tools/perf/util/annotate.c | 4 +
tools/perf/util/disasm.c | 1 +
tools/perf/util/dwarf-aux.c | 209 ++++++++++++++++++-
tools/perf/util/dwarf-aux.h | 27 +++
7 files changed, 342 insertions(+), 41 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types
2026-08-21 5:02 [PATCH 0/3] perf annotate: Data type profiling support for C++ classes and virtual calls Yanbo Zhao
@ 2026-08-21 5:02 ` Yanbo Zhao
2026-08-21 5:13 ` sashiko-bot
2026-08-21 5:02 ` [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers Yanbo Zhao
2026-08-21 5:02 ` [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls Yanbo Zhao
2 siblings, 1 reply; 7+ messages in thread
From: Yanbo Zhao @ 2026-08-21 5:02 UTC (permalink / raw)
To: Namhyung Kim, Arnaldo Carvalho de Melo
Cc: Peter Zijlstra, Ingo Molnar, Mark Rutland, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Zecheng Li,
Xu Liu, linux-perf-users, linux-kernel, Yanbo Zhao
Introduce the die_is_compound_type() helper which checks for
DW_TAG_structure_type, DW_TAG_union_type, and DW_TAG_class_type, and
convert all the existing open-coded struct/union tag checks to use it:
- die_get_member_type() in dwarf-aux.c.
- __add_member_cb(), is_compound_type(), and set_stack_state() in
annotate-data.c.
Also accept DW_TAG_inheritance in the member lookup callbacks
(__die_find_member_offset_cb() and __add_member_cb()) so that member
lookup by offset descends into C++ base class subobjects.
This extends the existing member type resolution and data type
profiling state handling to C++ classes with inheritance without
changing behavior for C struct/union types.
Signed-off-by: Yanbo Zhao <yzhao62@ncsu.edu>
---
tools/perf/util/annotate-data.c | 29 ++++++-----------------------
tools/perf/util/dwarf-aux.c | 17 ++++++++++++-----
tools/perf/util/dwarf-aux.h | 3 +++
3 files changed, 21 insertions(+), 28 deletions(-)
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 4e4c58764082..ee6bd2d0012d 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -230,9 +230,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
Dwarf_Word size, loc, bit_size = 0;
Dwarf_Attribute attr;
struct strbuf sb;
- int tag;
+ int tag = dwarf_tag(die);
- if (dwarf_tag(die) != DW_TAG_member)
+ if (tag != DW_TAG_member && tag != DW_TAG_inheritance)
return DIE_FIND_CB_SIBLING;
member = zalloc(sizeof(*member));
@@ -292,15 +292,8 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
INIT_LIST_HEAD(&member->children);
list_add_tail(&member->node, &parent->children);
- tag = dwarf_tag(&die_mem);
- switch (tag) {
- case DW_TAG_structure_type:
- case DW_TAG_union_type:
+ if (die_is_compound_type(&die_mem))
die_find_child(&die_mem, __add_member_cb, member, &die_mem);
- break;
- default:
- break;
- }
return DIE_FIND_CB_SIBLING;
}
@@ -464,9 +457,7 @@ static const char *match_result_str(enum type_match_result tmr)
static bool is_compound_type(Dwarf_Die *type_die)
{
- int tag = dwarf_tag(type_die);
-
- return tag == DW_TAG_structure_type || tag == DW_TAG_union_type;
+ return die_is_compound_type(type_die);
}
/* returns if Type B has better information than Type A */
@@ -584,7 +575,6 @@ struct type_state_stack *find_stack_state(struct type_state *state,
void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
Dwarf_Die *type_die, int ptr_offset)
{
- int tag;
Dwarf_Word size;
if (kind == TSR_KIND_POINTER) {
@@ -605,17 +595,10 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind,
return;
}
- tag = dwarf_tag(type_die);
-
- switch (tag) {
- case DW_TAG_structure_type:
- case DW_TAG_union_type:
+ if (die_is_compound_type(type_die))
stack->compound = (kind != TSR_KIND_PERCPU_POINTER);
- break;
- default:
+ else
stack->compound = false;
- break;
- }
}
struct type_state_stack *findnew_stack_state(struct type_state *state,
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index d7160f87ac7d..88ba0ec23a68 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -60,6 +60,14 @@ const char *cu_get_comp_dir(Dwarf_Die *cu_die)
return dwarf_formstring(&attr);
}
+bool die_is_compound_type(Dwarf_Die *type_die)
+{
+ int tag = dwarf_tag(type_die);
+
+ return tag == DW_TAG_structure_type || tag == DW_TAG_union_type ||
+ tag == DW_TAG_class_type;
+}
+
/* Unlike dwarf_getsrc_die(), cu_getsrc_die() only returns statement line */
static Dwarf_Line *cu_getsrc_die(Dwarf_Die *cu_die, Dwarf_Addr addr)
{
@@ -2053,7 +2061,7 @@ static int __die_find_member_offset_cb(Dwarf_Die *die_mem, void *arg)
Dwarf_Word offset = (long)arg;
int tag = dwarf_tag(die_mem);
- if (tag != DW_TAG_member)
+ if (tag != DW_TAG_member && tag != DW_TAG_inheritance)
return DIE_FIND_CB_SIBLING;
/* Unions might not have location */
@@ -2104,7 +2112,7 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
tag = dwarf_tag(type_die);
/* If it's not a compound type, return the type directly */
- if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) {
+ if (!die_is_compound_type(type_die)) {
Dwarf_Word size;
if (dwarf_aggregate_size(type_die, &size) < 0)
@@ -2119,7 +2127,7 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
mb_type = *type_die;
/* TODO: Handle union types better? */
- while (tag == DW_TAG_structure_type || tag == DW_TAG_union_type) {
+ while (die_is_compound_type(&mb_type)) {
member = die_find_child(&mb_type, __die_find_member_offset_cb,
(void *)(long)offset, die_mem);
if (member == NULL)
@@ -2130,8 +2138,7 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
tag = dwarf_tag(&mb_type);
- if (tag == DW_TAG_structure_type || tag == DW_TAG_union_type ||
- tag == DW_TAG_array_type) {
+ if (die_is_compound_type(&mb_type) || tag == DW_TAG_array_type) {
Dwarf_Word loc;
/* Update offset for the start of the member struct */
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index 161f0bf980b6..855c45fec5bb 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -23,6 +23,9 @@ const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname);
/* Get DW_AT_comp_dir (should be NULL with older gcc) */
const char *cu_get_comp_dir(Dwarf_Die *cu_die);
+/* Check if DIE is a compound type (structure, union, or class) */
+bool die_is_compound_type(Dwarf_Die *type_die);
+
/* Get a line number and file name for given address */
int cu_find_lineinfo(Dwarf_Die *cudie, Dwarf_Addr addr,
const char **fname, int *lineno);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers
2026-08-21 5:02 [PATCH 0/3] perf annotate: Data type profiling support for C++ classes and virtual calls Yanbo Zhao
2026-08-21 5:02 ` [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types Yanbo Zhao
@ 2026-08-21 5:02 ` Yanbo Zhao
2026-08-21 5:12 ` sashiko-bot
2026-08-21 5:02 ` [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls Yanbo Zhao
2 siblings, 1 reply; 7+ messages in thread
From: Yanbo Zhao @ 2026-08-21 5:02 UTC (permalink / raw)
To: Namhyung Kim, Arnaldo Carvalho de Melo
Cc: Peter Zijlstra, Ingo Molnar, Mark Rutland, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Zecheng Li,
Xu Liu, linux-perf-users, linux-kernel, Yanbo Zhao
Add DWARF helper functions for C++ virtual function support:
- cu_get_language() and cu_is_cplusplus() to detect source language.
- die_get_base_class() and die_get_vtable_index() for virtual table
inspection.
- die_get_parent() to retrieve enclosing scopes.
- die_find_member_by_offset() and die_find_virtual_func() to locate
class members and virtual function DIEs.
- die_is_vptr_member() to check if a member is a virtual table pointer.
- Define DW_AT_vtable_elem_index and DW_LANG_C_plus_plus_17/20
fallbacks for older elfutils that lack these DWARF 5 constants.
Signed-off-by: Yanbo Zhao <yzhao62@ncsu.edu>
---
tools/perf/util/dwarf-aux.c | 192 ++++++++++++++++++++++++++++++++++++
tools/perf/util/dwarf-aux.h | 24 +++++
2 files changed, 216 insertions(+)
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 88ba0ec23a68..e991eed963a8 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -12,6 +12,19 @@
#include "dwarf-regs.h"
#include "strbuf.h"
#include "string2.h"
+#include "symbol.h"
+
+#ifndef DW_AT_vtable_elem_index
+#define DW_AT_vtable_elem_index 0x8c
+#endif
+
+/* Added in DWARF 5, may be missing in older elfutils */
+#ifndef DW_LANG_C_plus_plus_17
+#define DW_LANG_C_plus_plus_17 0x2a
+#endif
+#ifndef DW_LANG_C_plus_plus_20
+#define DW_LANG_C_plus_plus_20 0x2b
+#endif
/**
* cu_find_realpath - Find the realpath of the target file
@@ -60,6 +73,37 @@ const char *cu_get_comp_dir(Dwarf_Die *cu_die)
return dwarf_formstring(&attr);
}
+Dwarf_Word cu_get_language(Dwarf_Die *cu_die)
+{
+ Dwarf_Attribute attr;
+ Dwarf_Word lang;
+
+ if (dwarf_attr(cu_die, DW_AT_language, &attr) == NULL)
+ return 0;
+
+ if (dwarf_formudata(&attr, &lang) != 0)
+ return 0;
+
+ return lang;
+}
+
+bool cu_is_cplusplus(Dwarf_Die *cu_die)
+{
+ Dwarf_Word lang = cu_get_language(cu_die);
+
+ switch (lang) {
+ case DW_LANG_C_plus_plus:
+ case DW_LANG_C_plus_plus_03:
+ case DW_LANG_C_plus_plus_11:
+ case DW_LANG_C_plus_plus_14:
+ case DW_LANG_C_plus_plus_17:
+ case DW_LANG_C_plus_plus_20:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool die_is_compound_type(Dwarf_Die *type_die)
{
int tag = dwarf_tag(type_die);
@@ -68,6 +112,90 @@ bool die_is_compound_type(Dwarf_Die *type_die)
tag == DW_TAG_class_type;
}
+static int __die_find_inheritance_cb(Dwarf_Die *die_mem, void *arg)
+{
+ int tag = dwarf_tag(die_mem);
+
+ if (tag == DW_TAG_inheritance) {
+ *(Dwarf_Die *)arg = *die_mem;
+ return DIE_FIND_CB_END;
+ }
+
+ return DIE_FIND_CB_SIBLING;
+}
+
+Dwarf_Die *die_get_base_class(Dwarf_Die *class_die, Dwarf_Die *base_die, int *offset)
+{
+ Dwarf_Die inherit_die;
+ Dwarf_Attribute attr;
+ Dwarf_Word loc;
+
+ if (die_find_child(class_die, __die_find_inheritance_cb,
+ &inherit_die, &inherit_die) == NULL)
+ return NULL;
+
+ if (__die_get_real_type(&inherit_die, base_die) == NULL)
+ return NULL;
+
+ if (dwarf_attr_integrate(&inherit_die, DW_AT_data_member_location, &attr) &&
+ dwarf_formudata(&attr, &loc) == 0) {
+ *offset = loc;
+ } else {
+ *offset = 0;
+ }
+
+ return base_die;
+}
+
+int die_get_vtable_index(Dwarf_Die *func_die, int *index)
+{
+ Dwarf_Attribute attr;
+ Dwarf_Word idx;
+
+ /* Try DW_AT_vtable_elem_index first (DWARF 5+) */
+ if (dwarf_attr(func_die, DW_AT_vtable_elem_index, &attr) &&
+ dwarf_formudata(&attr, &idx) == 0) {
+ *index = idx;
+ return 0;
+ }
+
+ /* Fallback to DW_AT_vtable_elem_location (older DWARF) */
+ if (dwarf_attr(func_die, DW_AT_vtable_elem_location, &attr)) {
+ Dwarf_Op *expr;
+ size_t expr_len;
+
+ /* Compile often emits it as a simple constant expression or block */
+ if (dwarf_getlocation(&attr, &expr, &expr_len) == 0 && expr_len > 0) {
+ if (expr[0].atom == DW_OP_constu) {
+ *index = expr[0].number;
+ return 0;
+ }
+ if (expr[0].atom >= DW_OP_lit0 && expr[0].atom <= DW_OP_lit31) {
+ *index = expr[0].atom - DW_OP_lit0;
+ return 0;
+ }
+ }
+ }
+
+ return -1;
+}
+
+Dwarf_Die *die_get_parent(Dwarf_Die *die, Dwarf_Die *parent_die)
+{
+ Dwarf_Die *scopes = NULL;
+ int n = dwarf_getscopes_die(die, &scopes);
+
+ if (n <= 1) {
+ free(scopes);
+ return NULL;
+ }
+
+ /* scopes[0] is the DIE itself, scopes[1] is the parent */
+ *parent_die = scopes[1];
+ free(scopes);
+ return parent_die;
+}
+
/* Unlike dwarf_getsrc_die(), cu_getsrc_die() only returns statement line */
static Dwarf_Line *cu_getsrc_die(Dwarf_Die *cu_die, Dwarf_Addr addr)
{
@@ -2164,6 +2292,70 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
return die_mem;
}
+Dwarf_Die *die_find_member_by_offset(Dwarf_Die *type_die, int offset, Dwarf_Die *member_die)
+{
+ if (!die_is_compound_type(type_die))
+ return NULL;
+
+ return die_find_child(type_die, __die_find_member_offset_cb,
+ (void *)(long)offset, member_die);
+}
+
+struct find_virtual_func_data {
+ int index;
+ Dwarf_Die func_die;
+ bool found;
+};
+
+static int __die_find_virtual_func_cb(Dwarf_Die *die_mem, void *arg)
+{
+ struct find_virtual_func_data *ad = arg;
+ int tag = dwarf_tag(die_mem);
+
+ if (tag == DW_TAG_subprogram) {
+ int idx;
+
+ if (die_get_vtable_index(die_mem, &idx) == 0 && idx == ad->index) {
+ ad->func_die = *die_mem;
+ ad->found = true;
+ return DIE_FIND_CB_END;
+ }
+ }
+
+ if (tag == DW_TAG_inheritance) {
+ Dwarf_Die base_type;
+
+ if (__die_get_real_type(die_mem, &base_type)) {
+ if (die_find_child(&base_type,
+ __die_find_virtual_func_cb,
+ ad, &ad->func_die))
+ return DIE_FIND_CB_END;
+ }
+ }
+
+ return DIE_FIND_CB_SIBLING;
+}
+
+Dwarf_Die *die_find_virtual_func(Dwarf_Die *class_die, int index, Dwarf_Die *die_mem)
+{
+ struct find_virtual_func_data ad = {
+ .index = index,
+ .found = false,
+ };
+
+ if (die_find_child(class_die, __die_find_virtual_func_cb, &ad, die_mem))
+ return die_mem;
+
+ return NULL;
+}
+
+bool die_is_vptr_member(Dwarf_Die *die)
+{
+ const char *name = dwarf_diename(die);
+
+ return name && strstarts(name, "_vptr");
+}
+
/**
* die_deref_ptr_type - Return type info for pointer access
* @ptr_die: a pointer type DIE
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index 855c45fec5bb..0f30f44ad863 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -23,9 +23,33 @@ const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname);
/* Get DW_AT_comp_dir (should be NULL with older gcc) */
const char *cu_get_comp_dir(Dwarf_Die *cu_die);
+/* Get source language of CU */
+Dwarf_Word cu_get_language(Dwarf_Die *cu_die);
+
+/* Check if CU is C++ */
+bool cu_is_cplusplus(Dwarf_Die *cu_die);
+
/* Check if DIE is a compound type (structure, union, or class) */
bool die_is_compound_type(Dwarf_Die *type_die);
+/* Get base class of a class DIE */
+Dwarf_Die *die_get_base_class(Dwarf_Die *class_die, Dwarf_Die *base_die, int *offset);
+
+/* Get vtable index of a virtual function */
+int die_get_vtable_index(Dwarf_Die *func_die, int *index);
+
+/* Get parent DIE */
+Dwarf_Die *die_get_parent(Dwarf_Die *die, Dwarf_Die *parent_die);
+
+/* Find member DIE by offset */
+Dwarf_Die *die_find_member_by_offset(Dwarf_Die *type_die, int offset, Dwarf_Die *member_die);
+
+/* Find virtual function by vtable index */
+Dwarf_Die *die_find_virtual_func(Dwarf_Die *class_die, int index, Dwarf_Die *die_mem);
+
+/* Check if member DIE is a vtable pointer (_vptr) */
+bool die_is_vptr_member(Dwarf_Die *die);
+
/* Get a line number and file name for given address */
int cu_find_lineinfo(Dwarf_Die *cudie, Dwarf_Addr addr,
const char **fname, int *lineno);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls
2026-08-21 5:02 [PATCH 0/3] perf annotate: Data type profiling support for C++ classes and virtual calls Yanbo Zhao
2026-08-21 5:02 ` [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types Yanbo Zhao
2026-08-21 5:02 ` [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers Yanbo Zhao
@ 2026-08-21 5:02 ` Yanbo Zhao
2026-08-21 5:26 ` sashiko-bot
2 siblings, 1 reply; 7+ messages in thread
From: Yanbo Zhao @ 2026-08-21 5:02 UTC (permalink / raw)
To: Namhyung Kim, Arnaldo Carvalho de Melo
Cc: Peter Zijlstra, Ingo Molnar, Mark Rutland, Alexander Shishkin,
Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark, Zecheng Li,
Xu Liu, linux-perf-users, linux-kernel, Yanbo Zhao
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 <yzhao62@ncsu.edu>
---
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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers
2026-08-21 5:02 ` [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers Yanbo Zhao
@ 2026-08-21 5:12 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-21 5:12 UTC (permalink / raw)
To: Yanbo Zhao; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] `die_find_virtual_func` returns a `DW_TAG_inheritance` DIE instead of a `DW_TAG_subprogram` DIE when resolving inherited virtual functions.
- [High] `die_get_vtable_index` inconsistently returns a byte offset instead of an array index on pre-DWARF 5 binaries.
- [Medium] `die_get_base_class` bypasses DWARF expression handling for `DW_AT_data_member_location`, causing incorrect offsets on older DWARF formats.
--
--- Patch [2]: [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers ---
commit 11247af931b847d92f3746e9aef5b7921e7db0a0
Author: Yanbo Zhao <yzhao62@ncsu.edu>
perf dwarf-aux: Add C++ class, inheritance and vtable helpers
This patch adds several DWARF helper functions to support C++ virtual function
inspection. It includes utilities for detecting source language, retrieving
base classes, inspecting virtual tables, and locating class members. The
helpers fall back to older DWARF constructs when DWARF 5 constants are
unavailable.
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> index 88ba0ec23a686..e991eed963a8a 100644
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
> [ ... ]
> @@ -68,6 +112,90 @@ bool die_is_compound_type(Dwarf_Die *type_die)
> [ ... ]
> +Dwarf_Die *die_get_base_class(Dwarf_Die *class_die, Dwarf_Die *base_die, int *offset)
> +{
> + Dwarf_Die inherit_die;
> + Dwarf_Attribute attr;
> + Dwarf_Word loc;
> +
> + if (die_find_child(class_die, __die_find_inheritance_cb,
> + &inherit_die, &inherit_die) == NULL)
> + return NULL;
> +
> + if (__die_get_real_type(&inherit_die, base_die) == NULL)
> + return NULL;
> +
> + if (dwarf_attr_integrate(&inherit_die, DW_AT_data_member_location, &attr) &&
> + dwarf_formudata(&attr, &loc) == 0) {
> + *offset = loc;
> + } else {
> + *offset = 0;
> + }
[Severity: Medium]
On older DWARF binaries, DW_AT_data_member_location is emitted as a location
block (e.g., DW_OP_plus_uconst) rather than a simple udata constant.
Since dwarf_formudata() fails on blocks, does this silently fall back to
setting the offset to 0, yielding the wrong base class offset?
Could this use the existing die_get_data_member_location() utility which
already handles both dwarf_formudata() and location expressions?
> +
> + return base_die;
> +}
> +
> +int die_get_vtable_index(Dwarf_Die *func_die, int *index)
> +{
> [ ... ]
> + /* Fallback to DW_AT_vtable_elem_location (older DWARF) */
> + if (dwarf_attr(func_die, DW_AT_vtable_elem_location, &attr)) {
> + Dwarf_Op *expr;
> + size_t expr_len;
> +
> + /* Compile often emits it as a simple constant expression or block */
> + if (dwarf_getlocation(&attr, &expr, &expr_len) == 0 && expr_len > 0) {
> + if (expr[0].atom == DW_OP_constu) {
> + *index = expr[0].number;
[Severity: High]
Does this fallback logic return a byte offset instead of an array index on
pre-DWARF 5 binaries?
If a binary uses DWARF 4, DW_AT_vtable_elem_location yields the byte offset
in the vtable. Should this byte offset be divided by the pointer size before
being assigned to the index to ensure it behaves consistently with DWARF 5?
> + return 0;
> + }
> [ ... ]
> @@ -2164,6 +2292,70 @@ Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
> [ ... ]
> +static int __die_find_virtual_func_cb(Dwarf_Die *die_mem, void *arg)
> +{
> [ ... ]
> + if (tag == DW_TAG_inheritance) {
> + Dwarf_Die base_type;
> +
> + if (__die_get_real_type(die_mem, &base_type)) {
> + if (die_find_child(&base_type,
> + __die_find_virtual_func_cb,
> + ad, &ad->func_die))
> + return DIE_FIND_CB_END;
> + }
> + }
> +
> + return DIE_FIND_CB_SIBLING;
> +}
> +
> +Dwarf_Die *die_find_virtual_func(Dwarf_Die *class_die, int index, Dwarf_Die *die_mem)
> +{
> + struct find_virtual_func_data ad = {
> + .index = index,
> + .found = false,
> + };
> +
> + if (die_find_child(class_die, __die_find_virtual_func_cb, &ad, die_mem))
> + return die_mem;
[Severity: High]
When resolving a virtual function implemented in a base class, does this logic
return a DW_TAG_inheritance DIE instead of the actual DW_TAG_subprogram DIE?
If the nested search in __die_find_virtual_func_cb() successfully finds the
subprogram, it populates ad->func_die and returns DIE_FIND_CB_END. However,
the outer die_find_child() loop in die_find_virtual_func() terminates and
returns its current iteration pointer, die_mem.
Since die_mem points to the DW_TAG_inheritance DIE at that moment, does this
ignore the populated ad.func_die and return the inheritance DIE to the caller?
> +
> + return NULL;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260821050207.4517-1-yzhao62@ncsu.edu?part=2
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types
2026-08-21 5:02 ` [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types Yanbo Zhao
@ 2026-08-21 5:13 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-21 5:13 UTC (permalink / raw)
To: Yanbo Zhao; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Empty Base Optimization (EBO) causes zero-sized base classes to shadow data members at the same offset.
- [Medium] Virtual inheritance uses complex location expressions that are unsupported, silently defaulting to offset 0.
--
--- Patch [1]: [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types ---
commit 9d64a45d6e2e13c946726cd0182607e969f8dd87
Author: Yanbo Zhao <yzhao62@ncsu.edu>
perf dwarf-aux: Add die_is_compound_type() to handle C++ class types
Introduce the die_is_compound_type() helper which checks for
DW_TAG_structure_type, DW_TAG_union_type, and DW_TAG_class_type, and
convert all the existing open-coded struct/union tag checks to use it.
Also accept DW_TAG_inheritance so member lookup descends into C++ base
classes.
> diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
> --- a/tools/perf/util/annotate-data.c
> +++ b/tools/perf/util/annotate-data.c
> @@ -230,9 +230,9 @@ static int __add_member_cb(Dwarf_Die *die, void *arg)
> Dwarf_Word size, loc, bit_size = 0;
> Dwarf_Attribute attr;
> struct strbuf sb;
> - int tag;
> + int tag = dwarf_tag(die);
>
> - if (dwarf_tag(die) != DW_TAG_member)
> + if (tag != DW_TAG_member && tag != DW_TAG_inheritance)
> return DIE_FIND_CB_SIBLING;
[Severity: Medium]
Since DW_TAG_inheritance is now processed here, how does this handle virtual
inheritance? Virtual base classes often use complex DWARF location
expressions (like DW_OP_deref).
Looking at the location parsing slightly further down in __add_member_cb():
if (dwarf_attr_integrate(die, DW_AT_data_member_location, &attr)) {
if (dwarf_formudata(&attr, &loc) != 0) {
if (die_get_data_member_location(die, &loc) != 0)
loc = 0;
}
}
If die_get_data_member_location() fails due to an unsupported complex
expression, it silently defaults to offset 0. Will this incorrectly assign
virtual base classes an offset of 0, potentially masking actual fields at
offset 0 like the vtable pointer?
[ ... ]
> diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
> --- a/tools/perf/util/dwarf-aux.c
> +++ b/tools/perf/util/dwarf-aux.c
[ ... ]
> @@ -2053,7 +2061,7 @@ static int __die_find_member_offset_cb(Dwarf_Die *die_mem, void *arg)
> Dwarf_Word offset = (long)arg;
> int tag = dwarf_tag(die_mem);
>
> - if (tag != DW_TAG_member)
> + if (tag != DW_TAG_member && tag != DW_TAG_inheritance)
> return DIE_FIND_CB_SIBLING;
[Severity: Medium]
With DW_TAG_inheritance now included, does this code correctly handle the
Empty Base Optimization (EBO)?
Zero-sized base classes share the same offset as the first data member.
Further down in __die_find_member_offset_cb(), there is an exact offset
match check:
if (offset == loc)
return DIE_FIND_CB_END;
Since an empty base class often appears first in DWARF before the data
member, will this unconditionally return DIE_FIND_CB_END when matching the
empty base class? This appears to stop the search and cause data type
resolution to fail for the actual data member at the same offset.
Additionally, __die_find_member_offset_cb() contains similar location parsing
to __add_member_cb() which falls back to 0 if die_get_data_member_location()
fails. Could this also cause virtual base classes to incorrectly resolve to
offset 0 here?
> /* Unions might not have location */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260821050207.4517-1-yzhao62@ncsu.edu?part=1
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls
2026-08-21 5:02 ` [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls Yanbo Zhao
@ 2026-08-21 5:26 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-21 5:26 UTC (permalink / raw)
To: Yanbo Zhao; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] Dead code and conceptually flawed receiver type tracking for C++ virtual calls
- [Medium] Indirect calls through registers (e.g., `*%rax`) bypass operand extraction logic
- [Medium] Virtual functions returning `void` silently abort receiver tracking
--
--- Patch [3]: [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls ---
commit 5fa55e70aac42c5267a1e3215b8634a1d2a5ae9b
Author: Yanbo Zhao <yzhao62@ncsu.edu>
perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls
This commit implements disassembler and type profiling support in perf for
C++ virtual dispatch and indirect call resolution on x86. It introduces
tracking for vtable pointers, resolves virtual function calls via DWARF
information, and updates register states for the receiver pointer upon
virtual call resolution.
> diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c
> --- a/tools/perf/util/annotate-arch/annotate-x86.c
> +++ b/tools/perf/util/annotate-arch/annotate-x86.c
[ ... ]
> @@ -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)) {
[Severity: Medium]
Will die_get_type() return NULL for virtual functions returning void since
they lack a DW_AT_type attribute?
If it returns NULL, it seems this would leave resolved_statically as false and
completely bypass the subsequent block that tracks the receiver's C++ class
type.
> + resolved_statically = true;
> + class_die = target_class_die;
> + has_class_die = true;
> + }
> + }
> + }
[ ... ]
> - /* 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);
> }
[ ... ]
> @@ -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)) {
[Severity: Medium]
Is it possible for recv_tsr->ok to be true here?
Since arg0_reg (%rdi) is a caller-saved register, the loop right before this
block unconditionally invalidates all caller-saved registers, setting reg->ok
to false. This appears to make the condition structurally impossible to meet.
Additionally, does it make sense to update the receiver type of a caller-saved
register after the call? Since the callee could have clobbered it per the ABI,
wouldn't the tool end up tracking a garbage value instead of the 'this'
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;
> }
[ ... ]
> diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
> --- a/tools/perf/util/annotate.c
> +++ b/tools/perf/util/annotate.c
> @@ -2486,6 +2486,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++;
> +
[Severity: Medium]
Is this logic reachable for direct register indirect calls like *%rax?
Looking at annotate_get_insn_location() where this gets called:
} else if (strchr(insn_str, arch->objdump.memory_ref_char)) {
op_loc->mem_ref = true;
op_loc->multi_regs = multi_regs;
extract_reg_offset(arch, insn_str, op_loc);
} else {
Since *%rax lacks a memory reference character (like '('), it seems it
would bypass extract_reg_offset() entirely and fall into the else block.
This would fail to track the target register type because the '*' prefix
is not expected by the fallback code.
> /*
> * It should start from offset, but it's possible to skip 0
> * in the asm. So 0(%rax) should be same as (%rax).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260821050207.4517-1-yzhao62@ncsu.edu?part=3
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-21 5:26 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 5:02 [PATCH 0/3] perf annotate: Data type profiling support for C++ classes and virtual calls Yanbo Zhao
2026-08-21 5:02 ` [PATCH 1/3] perf dwarf-aux: Add die_is_compound_type() to handle C++ class types Yanbo Zhao
2026-08-21 5:13 ` sashiko-bot
2026-08-21 5:02 ` [PATCH 2/3] perf dwarf-aux: Add C++ class, inheritance and vtable helpers Yanbo Zhao
2026-08-21 5:12 ` sashiko-bot
2026-08-21 5:02 ` [PATCH 3/3] perf disasm & annotate x86: Track C++ vptr and resolve virtual function calls Yanbo Zhao
2026-08-21 5:26 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.