Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / tools / perf / util / unwind-libdw.c
diff --git a/kernel/tools/perf/util/unwind-libdw.c b/kernel/tools/perf/util/unwind-libdw.c
new file mode 100644 (file)
index 0000000..2dcfe9a
--- /dev/null
@@ -0,0 +1,211 @@
+#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"
+
+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;
+
+       thread__find_addr_location(ui->thread,
+                                  PERF_RECORD_MISC_USER,
+                                  MAP__FUNCTION, ip, al);
+
+       if (al->map)
+               dso = al->map->dso;
+
+       if (!dso)
+               return 0;
+
+       mod = dwfl_addrmodule(ui->dwfl, ip);
+       if (!mod)
+               mod = dwfl_report_elf(ui->dwfl, dso->short_name,
+                                     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);
+}
+
+static int entry(u64 ip, struct unwind_info *ui)
+
+{
+       struct unwind_entry e;
+       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 ui->cb(&e, ui->arg);
+}
+
+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;
+
+       thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
+                             MAP__FUNCTION, addr, &al);
+       if (!al.map) {
+               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;
+
+       if (!dwfl_frame_pc(state, &pc, NULL)) {
+               pr_err("%s", dwfl_errmsg(-1));
+               return DWARF_CB_ABORT;
+       }
+
+       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 = {
+               .sample         = data,
+               .thread         = thread,
+               .machine        = thread->mg->machine,
+               .cb             = cb,
+               .arg            = arg,
+               .max_stack      = max_stack,
+       };
+       Dwarf_Word ip;
+       int err = -EINVAL;
+
+       if (!data->user_regs.regs)
+               return -EINVAL;
+
+       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;
+
+       if (!dwfl_attach_state(ui.dwfl, EM_NONE, thread->tid, &callbacks, &ui))
+               goto out;
+
+       err = dwfl_getthread_frames(ui.dwfl, thread->tid, frame_callback, &ui);
+
+       if (err && !ui.max_stack)
+               err = 0;
+
+ out:
+       if (err)
+               pr_debug("unwind: failed with '%s'\n", dwfl_errmsg(-1));
+
+       dwfl_end(ui.dwfl);
+       return 0;
+}