mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 17:59:32 +00:00
![Sandipan Das](/assets/img/avatar_default.png)
When perf/data is recorded with the dwarf call-graph option, the callchain shown by 'perf script' still shows the binary offsets of the userspace symbols instead of their virtual addresses. Since the symbol offset calculation is based on using virtual address as the ip, we see incorrect offsets as well. The use of virtual addresses affects the ability to find out the line number in the corresponding source file to which an address maps to as described in commit6754075915
("perf unwind: Use addr_location::addr instead of ip for entries"). This has also been addressed by temporarily converting the virtual address to the correponding binary offset so that it can be mapped to the source line number correctly. This is a follow-up for commit1961018469
("perf script: Show virtual addresses instead of offsets"). This can be verified on a powerpc64le system running Fedora 27 as shown below: # perf probe -x /usr/lib64/libc-2.26.so -a inet_pton # perf record -e probe_libc:inet_pton --call-graph=dwarf ping -6 -c 1 ::1 Before: # perf report --stdio --no-children -s sym,srcline -g address # Samples: 1 of event 'probe_libc:inet_pton' # Event count (approx.): 1 # # Overhead Symbol Source:Line # ........ .................... ........... # 100.00% [.] __GI___inet_pton inet_pton.c | ---gaih_inet getaddrinfo.c:537 (inlined) __GI_getaddrinfo getaddrinfo.c:2304 (inlined) main ping.c:519 generic_start_main libc-start.c:308 (inlined) __libc_start_main libc-start.c:102 ... # perf script -F comm,ip,sym,symoff,srcline,dso ping 15af28 __GI___inet_pton+0xffff000099160008 (/usr/lib64/libc-2.26.so) libc-2.26.so[ffff80004ca0af28] 10fa53 gaih_inet+0xffff000099160f43 libc-2.26.so[ffff80004c9bfa53] (inlined) 1105b3 __GI_getaddrinfo+0xffff000099160163 libc-2.26.so[ffff80004c9c05b3] (inlined) 2d6f main+0xfffffffd9f1003df (/usr/bin/ping) ping[fffffffecf882d6f] 2369f generic_start_main+0xffff00009916013f libc-2.26.so[ffff80004c8d369f] (inlined) 23897 __libc_start_main+0xffff0000991600b7 (/usr/lib64/libc-2.26.so) libc-2.26.so[ffff80004c8d3897] After: # perf report --stdio --no-children -s sym,srcline -g address # Samples: 1 of event 'probe_libc:inet_pton' # Event count (approx.): 1 # # Overhead Symbol Source:Line # ........ .................... ........... # 100.00% [.] __GI___inet_pton inet_pton.c | ---gaih_inet.constprop.7 getaddrinfo.c:537 getaddrinfo getaddrinfo.c:2304 main ping.c:519 generic_start_main.isra.0 libc-start.c:308 __libc_start_main libc-start.c:102 ... # perf script -F comm,ip,sym,symoff,srcline,dso ping 7fffb38aaf28 __GI___inet_pton+0x8 (/usr/lib64/libc-2.26.so) inet_pton.c:68 7fffb385fa53 gaih_inet.constprop.7+0xf43 (/usr/lib64/libc-2.26.so) getaddrinfo.c:537 7fffb38605b3 getaddrinfo+0x163 (/usr/lib64/libc-2.26.so) getaddrinfo.c:2304 130782d6f main+0x3df (/usr/bin/ping) ping.c:519 7fffb377369f generic_start_main.isra.0+0x13f (/usr/lib64/libc-2.26.so) libc-start.c:308 7fffb3773897 __libc_start_main+0xb7 (/usr/lib64/libc-2.26.so) libc-start.c:102 Signed-off-by: Sandipan Das <sandipan@linux.ibm.com> Acked-by: Jiri Olsa <jolsa@kernel.org> Cc: Milian Wolff <milian.wolff@kdab.com> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com> Cc: Ravi Bangoria <ravi.bangoria@linux.ibm.com> Fixes:6754075915
("perf unwind: Use addr_location::addr instead of ip for entries") Link: http://lkml.kernel.org/r/20180703120555.32971-1-sandipan@linux.ibm.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
256 lines
5.5 KiB
C
256 lines
5.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/compiler.h>
|
|
#include <elfutils/libdw.h>
|
|
#include <elfutils/libdwfl.h>
|
|
#include <inttypes.h>
|
|
#include <errno.h>
|
|
#include "debug.h"
|
|
#include "unwind.h"
|
|
#include "unwind-libdw.h"
|
|
#include "machine.h"
|
|
#include "thread.h"
|
|
#include <linux/types.h>
|
|
#include "event.h"
|
|
#include "perf_regs.h"
|
|
#include "callchain.h"
|
|
#include "util.h"
|
|
|
|
static char *debuginfo_path;
|
|
|
|
static const Dwfl_Callbacks offline_callbacks = {
|
|
.find_debuginfo = dwfl_standard_find_debuginfo,
|
|
.debuginfo_path = &debuginfo_path,
|
|
.section_address = dwfl_offline_section_address,
|
|
};
|
|
|
|
static int __report_module(struct addr_location *al, u64 ip,
|
|
struct unwind_info *ui)
|
|
{
|
|
Dwfl_Module *mod;
|
|
struct dso *dso = NULL;
|
|
/*
|
|
* Some callers will use al->sym, so we can't just use the
|
|
* cheaper thread__find_map() here.
|
|
*/
|
|
thread__find_symbol(ui->thread, PERF_RECORD_MISC_USER, ip, al);
|
|
|
|
if (al->map)
|
|
dso = al->map->dso;
|
|
|
|
if (!dso)
|
|
return 0;
|
|
|
|
mod = dwfl_addrmodule(ui->dwfl, ip);
|
|
if (mod) {
|
|
Dwarf_Addr s;
|
|
|
|
dwfl_module_info(mod, NULL, &s, NULL, NULL, NULL, NULL, NULL);
|
|
if (s != al->map->start)
|
|
mod = 0;
|
|
}
|
|
|
|
if (!mod)
|
|
mod = dwfl_report_elf(ui->dwfl, dso->short_name,
|
|
(dso->symsrc_filename ? dso->symsrc_filename : dso->long_name), -1, al->map->start,
|
|
false);
|
|
|
|
return mod && dwfl_addrmodule(ui->dwfl, ip) == mod ? 0 : -1;
|
|
}
|
|
|
|
static int report_module(u64 ip, struct unwind_info *ui)
|
|
{
|
|
struct addr_location al;
|
|
|
|
return __report_module(&al, ip, ui);
|
|
}
|
|
|
|
/*
|
|
* Store all entries within entries array,
|
|
* we will process it after we finish unwind.
|
|
*/
|
|
static int entry(u64 ip, struct unwind_info *ui)
|
|
|
|
{
|
|
struct unwind_entry *e = &ui->entries[ui->idx++];
|
|
struct addr_location al;
|
|
|
|
if (__report_module(&al, ip, ui))
|
|
return -1;
|
|
|
|
e->ip = ip;
|
|
e->map = al.map;
|
|
e->sym = al.sym;
|
|
|
|
pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n",
|
|
al.sym ? al.sym->name : "''",
|
|
ip,
|
|
al.map ? al.map->map_ip(al.map, ip) : (u64) 0);
|
|
return 0;
|
|
}
|
|
|
|
static pid_t next_thread(Dwfl *dwfl, void *arg, void **thread_argp)
|
|
{
|
|
/* We want only single thread to be processed. */
|
|
if (*thread_argp != NULL)
|
|
return 0;
|
|
|
|
*thread_argp = arg;
|
|
return dwfl_pid(dwfl);
|
|
}
|
|
|
|
static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr,
|
|
Dwarf_Word *data)
|
|
{
|
|
struct addr_location al;
|
|
ssize_t size;
|
|
|
|
if (!thread__find_map(ui->thread, PERF_RECORD_MISC_USER, addr, &al)) {
|
|
pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
|
|
return -1;
|
|
}
|
|
|
|
if (!al.map->dso)
|
|
return -1;
|
|
|
|
size = dso__data_read_addr(al.map->dso, al.map, ui->machine,
|
|
addr, (u8 *) data, sizeof(*data));
|
|
|
|
return !(size == sizeof(*data));
|
|
}
|
|
|
|
static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word *result,
|
|
void *arg)
|
|
{
|
|
struct unwind_info *ui = arg;
|
|
struct stack_dump *stack = &ui->sample->user_stack;
|
|
u64 start, end;
|
|
int offset;
|
|
int ret;
|
|
|
|
ret = perf_reg_value(&start, &ui->sample->user_regs, PERF_REG_SP);
|
|
if (ret)
|
|
return false;
|
|
|
|
end = start + stack->size;
|
|
|
|
/* Check overflow. */
|
|
if (addr + sizeof(Dwarf_Word) < addr)
|
|
return false;
|
|
|
|
if (addr < start || addr + sizeof(Dwarf_Word) > end) {
|
|
ret = access_dso_mem(ui, addr, result);
|
|
if (ret) {
|
|
pr_debug("unwind: access_mem 0x%" PRIx64 " not inside range"
|
|
" 0x%" PRIx64 "-0x%" PRIx64 "\n",
|
|
addr, start, end);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
offset = addr - start;
|
|
*result = *(Dwarf_Word *)&stack->data[offset];
|
|
pr_debug("unwind: access_mem addr 0x%" PRIx64 ", val %lx, offset %d\n",
|
|
addr, (unsigned long)*result, offset);
|
|
return true;
|
|
}
|
|
|
|
static const Dwfl_Thread_Callbacks callbacks = {
|
|
.next_thread = next_thread,
|
|
.memory_read = memory_read,
|
|
.set_initial_registers = libdw__arch_set_initial_registers,
|
|
};
|
|
|
|
static int
|
|
frame_callback(Dwfl_Frame *state, void *arg)
|
|
{
|
|
struct unwind_info *ui = arg;
|
|
Dwarf_Addr pc;
|
|
bool isactivation;
|
|
|
|
if (!dwfl_frame_pc(state, &pc, NULL)) {
|
|
pr_err("%s", dwfl_errmsg(-1));
|
|
return DWARF_CB_ABORT;
|
|
}
|
|
|
|
// report the module before we query for isactivation
|
|
report_module(pc, ui);
|
|
|
|
if (!dwfl_frame_pc(state, &pc, &isactivation)) {
|
|
pr_err("%s", dwfl_errmsg(-1));
|
|
return DWARF_CB_ABORT;
|
|
}
|
|
|
|
if (!isactivation)
|
|
--pc;
|
|
|
|
return entry(pc, ui) || !(--ui->max_stack) ?
|
|
DWARF_CB_ABORT : DWARF_CB_OK;
|
|
}
|
|
|
|
int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
|
|
struct thread *thread,
|
|
struct perf_sample *data,
|
|
int max_stack)
|
|
{
|
|
struct unwind_info *ui, ui_buf = {
|
|
.sample = data,
|
|
.thread = thread,
|
|
.machine = thread->mg->machine,
|
|
.cb = cb,
|
|
.arg = arg,
|
|
.max_stack = max_stack,
|
|
};
|
|
Dwarf_Word ip;
|
|
int err = -EINVAL, i;
|
|
|
|
if (!data->user_regs.regs)
|
|
return -EINVAL;
|
|
|
|
ui = zalloc(sizeof(ui_buf) + sizeof(ui_buf.entries[0]) * max_stack);
|
|
if (!ui)
|
|
return -ENOMEM;
|
|
|
|
*ui = ui_buf;
|
|
|
|
ui->dwfl = dwfl_begin(&offline_callbacks);
|
|
if (!ui->dwfl)
|
|
goto out;
|
|
|
|
err = perf_reg_value(&ip, &data->user_regs, PERF_REG_IP);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = report_module(ip, ui);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = !dwfl_attach_state(ui->dwfl, EM_NONE, thread->tid, &callbacks, ui);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = dwfl_getthread_frames(ui->dwfl, thread->tid, frame_callback, ui);
|
|
|
|
if (err && ui->max_stack != max_stack)
|
|
err = 0;
|
|
|
|
/*
|
|
* Display what we got based on the order setup.
|
|
*/
|
|
for (i = 0; i < ui->idx && !err; i++) {
|
|
int j = i;
|
|
|
|
if (callchain_param.order == ORDER_CALLER)
|
|
j = ui->idx - i - 1;
|
|
|
|
err = ui->entries[j].ip ? ui->cb(&ui->entries[j], ui->arg) : 0;
|
|
}
|
|
|
|
out:
|
|
if (err)
|
|
pr_debug("unwind: failed with '%s'\n", dwfl_errmsg(-1));
|
|
|
|
dwfl_end(ui->dwfl);
|
|
free(ui);
|
|
return 0;
|
|
}
|