[PATCH 12/14] perf annotate-data: Handle this-cpu variables in kernel

From: Namhyung Kim
Date: Fri Feb 02 2024 - 17:08:27 EST


On x86, the kernel gets the current task using the current macro like
below:

#define current get_current()

static __always_inline struct task_struct *get_current(void)
{
return this_cpu_read_stable(pcpu_hot.current_task);
}

So it returns the current_task field of struct pcpu_hot which is the
first member. On my build, it's located at 0x32940.

$ nm vmlinux | grep pcpu_hot
0000000000032940 D pcpu_hot

And the current macro generates the instructions like below:

mov %gs:0x32940, %rcx

So the %gs segment register points to the beginning of the per-cpu
region of this cpu and it points the variable with a constant.

Let's update the instruction location info to have a segment register
and handle %gs in kernel to look up a global variable. The new
get_percpu_var_info() helper is to get information about the variable.
Pretend it as a global variable by changing the register number to
DWARF_REG_PC.

Signed-off-by: Namhyung Kim <namhyung@xxxxxxxxxx>
---
tools/perf/util/annotate.c | 31 +++++++++++++++++++++++++++++++
tools/perf/util/annotate.h | 4 ++++
2 files changed, 35 insertions(+)

diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 86ac44c476bf..5f3136f57c62 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -3810,6 +3810,27 @@ void get_global_var_info(struct thread *thread, struct map_symbol *ms, u64 ip,
addr_location__exit(&al);
}

+void get_percpu_var_info(struct thread *thread, struct map_symbol *ms,
+ u8 cpumode, u64 var_addr, const char **var_name,
+ int *poffset)
+{
+ struct addr_location al;
+ struct symbol *var;
+ u64 map_addr;
+
+ /* Kernel symbols might be relocated */
+ map_addr = var_addr + map__reloc(ms->map);
+
+ addr_location__init(&al);
+ var = thread__find_symbol_fb(thread, cpumode, map_addr, &al);
+ if (var) {
+ *var_name = var->name;
+ /* Calculate type offset from the start of variable */
+ *poffset = map_addr - map__unmap_ip(al.map, var->start);
+ }
+ addr_location__exit(&al);
+}
+
/**
* hist_entry__get_data_type - find data type for given hist entry
* @he: hist entry
@@ -3906,6 +3927,16 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
&dloc.type_offset);
}

+ /* This CPU access in kernel - pretend PC-relative addressing */
+ if (op_loc->reg1 < 0 && map__dso(ms->map)->kernel &&
+ arch__is(arch, "x86") && op_loc->segment == INSN_SEG_X86_GS) {
+ dloc.var_addr = op_loc->offset;
+ get_percpu_var_info(he->thread, ms, he->cpumode,
+ dloc.var_addr, &dloc.var_name,
+ &dloc.type_offset);
+ op_loc->reg1 = DWARF_REG_PC;
+ }
+
mem_type = find_data_type(&dloc);
if (mem_type)
istat->good++;
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index 2bd654620de3..490134d19c9d 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -513,6 +513,10 @@ void get_global_var_info(struct thread *thread, struct map_symbol *ms, u64 ip,
struct disasm_line *dl, u8 cpumode, u64 *var_addr,
const char **var_name, int *poffset);

+void get_percpu_var_info(struct thread *thread, struct map_symbol *ms,
+ u8 cpumode, u64 var_addr, const char **var_name,
+ int *poffset);
+
/**
* struct annotated_basic_block - Basic block of instructions
* @list: List node
--
2.43.0.594.gd9cf4e227d-goog