Skip to content

Commit 2d86612

Browse files
Leo Yanacmel
authored andcommitted
perf symbol: Correct address for bss symbols
When using 'perf mem' and 'perf c2c', an issue is observed that tool reports the wrong offset for global data symbols. This is a common issue on both x86 and Arm64 platforms. Let's see an example, for a test program, below is the disassembly for its .bss section which is dumped with objdump: ... Disassembly of section .bss: 0000000000004040 <completed.0>: ... 0000000000004080 <buf1>: ... 00000000000040c0 <buf2>: ... 0000000000004100 <thread>: ... First we used 'perf mem record' to run the test program and then used 'perf --debug verbose=4 mem report' to observe what's the symbol info for 'buf1' and 'buf2' structures. # ./perf mem record -e ldlat-loads,ldlat-stores -- false_sharing.exe 8 # ./perf --debug verbose=4 mem report ... dso__load_sym_internal: adjusting symbol: st_value: 0x40c0 sh_addr: 0x4040 sh_offset: 0x3028 symbol__new: buf2 0x30a8-0x30e8 ... dso__load_sym_internal: adjusting symbol: st_value: 0x4080 sh_addr: 0x4040 sh_offset: 0x3028 symbol__new: buf1 0x3068-0x30a8 ... The perf tool relies on libelf to parse symbols, in executable and shared object files, 'st_value' holds a virtual address; 'sh_addr' is the address at which section's first byte should reside in memory, and 'sh_offset' is the byte offset from the beginning of the file to the first byte in the section. The perf tool uses below formula to convert a symbol's memory address to a file address: file_address = st_value - sh_addr + sh_offset ^ ` Memory address We can see the final adjusted address ranges for buf1 and buf2 are [0x30a8-0x30e8) and [0x3068-0x30a8) respectively, apparently this is incorrect, in the code, the structure for 'buf1' and 'buf2' specifies compiler attribute with 64-byte alignment. The problem happens for 'sh_offset', libelf returns it as 0x3028 which is not 64-byte aligned, combining with disassembly, it's likely libelf doesn't respect the alignment for .bss section, therefore, it doesn't return the aligned value for 'sh_offset'. Suggested by Fangrui Song, ELF file contains program header which contains PT_LOAD segments, the fields p_vaddr and p_offset in PT_LOAD segments contain the execution info. A better choice for converting memory address to file address is using the formula: file_address = st_value - p_vaddr + p_offset This patch introduces elf_read_program_header() which returns the program header based on the passed 'st_value', then it uses the formula above to calculate the symbol file address; and the debugging log is updated respectively. After applying the change: # ./perf --debug verbose=4 mem report ... dso__load_sym_internal: adjusting symbol: st_value: 0x40c0 p_vaddr: 0x3d28 p_offset: 0x2d28 symbol__new: buf2 0x30c0-0x3100 ... dso__load_sym_internal: adjusting symbol: st_value: 0x4080 p_vaddr: 0x3d28 p_offset: 0x2d28 symbol__new: buf1 0x3080-0x30c0 ... Fixes: f17e04a ("perf report: Fix ELF symbol parsing") Reported-by: Chang Rui <changruinj@gmail.com> Suggested-by: Fangrui Song <maskray@google.com> Signed-off-by: Leo Yan <leo.yan@linaro.org> Acked-by: Namhyung Kim <namhyung@kernel.org> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Ian Rogers <irogers@google.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://lore.kernel.org/r/20220724060013.171050-2-leo.yan@linaro.org Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
1 parent b226521 commit 2d86612

File tree

1 file changed

+41
-4
lines changed

1 file changed

+41
-4
lines changed

tools/perf/util/symbol-elf.c

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,33 @@ Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
233233
return NULL;
234234
}
235235

236+
static int elf_read_program_header(Elf *elf, u64 vaddr, GElf_Phdr *phdr)
237+
{
238+
size_t i, phdrnum;
239+
u64 sz;
240+
241+
if (elf_getphdrnum(elf, &phdrnum))
242+
return -1;
243+
244+
for (i = 0; i < phdrnum; i++) {
245+
if (gelf_getphdr(elf, i, phdr) == NULL)
246+
return -1;
247+
248+
if (phdr->p_type != PT_LOAD)
249+
continue;
250+
251+
sz = max(phdr->p_memsz, phdr->p_filesz);
252+
if (!sz)
253+
continue;
254+
255+
if (vaddr >= phdr->p_vaddr && (vaddr < phdr->p_vaddr + sz))
256+
return 0;
257+
}
258+
259+
/* Not found any valid program header */
260+
return -1;
261+
}
262+
236263
static bool want_demangle(bool is_kernel_sym)
237264
{
238265
return is_kernel_sym ? symbol_conf.demangle_kernel : symbol_conf.demangle;
@@ -1209,6 +1236,7 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
12091236
sym.st_value);
12101237
used_opd = true;
12111238
}
1239+
12121240
/*
12131241
* When loading symbols in a data mapping, ABS symbols (which
12141242
* has a value of SHN_ABS in its st_shndx) failed at
@@ -1262,11 +1290,20 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
12621290
goto out_elf_end;
12631291
} else if ((used_opd && runtime_ss->adjust_symbols) ||
12641292
(!used_opd && syms_ss->adjust_symbols)) {
1293+
GElf_Phdr phdr;
1294+
1295+
if (elf_read_program_header(syms_ss->elf,
1296+
(u64)sym.st_value, &phdr)) {
1297+
pr_warning("%s: failed to find program header for "
1298+
"symbol: %s st_value: %#" PRIx64 "\n",
1299+
__func__, elf_name, (u64)sym.st_value);
1300+
continue;
1301+
}
12651302
pr_debug4("%s: adjusting symbol: st_value: %#" PRIx64 " "
1266-
"sh_addr: %#" PRIx64 " sh_offset: %#" PRIx64 "\n", __func__,
1267-
(u64)sym.st_value, (u64)shdr.sh_addr,
1268-
(u64)shdr.sh_offset);
1269-
sym.st_value -= shdr.sh_addr - shdr.sh_offset;
1303+
"p_vaddr: %#" PRIx64 " p_offset: %#" PRIx64 "\n",
1304+
__func__, (u64)sym.st_value, (u64)phdr.p_vaddr,
1305+
(u64)phdr.p_offset);
1306+
sym.st_value -= phdr.p_vaddr - phdr.p_offset;
12701307
}
12711308

12721309
demangled = demangle_sym(dso, kmodule, elf_name);

0 commit comments

Comments
 (0)