unwind: Add dw_unwind interface
Signed-off-by: Jiri Olsa <jolsa@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
diff --git a/include/linux/dwarf_unwind.h b/include/linux/dwarf_unwind.h
new file mode 100644
index 0000000..3fd5c9c
--- /dev/null
+++ b/include/linux/dwarf_unwind.h
@@ -0,0 +1,10 @@
+#ifndef DWARF_UNWIND_H
+#define DWARF_UNWIND_H
+
+#include <linux/ptrace.h>
+
+typedef int (*dwarf_unwind_cb)(struct pt_regs *regs, void *data);
+
+int dwarf_unwind(struct pt_regs *regs, dwarf_unwind_cb cb, void *data);
+
+#endif /* DWARF_UNWIND_H */
diff --git a/kernel/dwarf_unwind/internal.h b/kernel/dwarf_unwind/internal.h
index 98846bd..f92ea8b 100644
--- a/kernel/dwarf_unwind/internal.h
+++ b/kernel/dwarf_unwind/internal.h
@@ -114,6 +114,7 @@
DU_DEBUG_EH_FRAMES = 1U << 2,
DU_DEBUG_CFI = 1U << 3,
DU_DEBUG_EXPR = 1U << 4,
+ DU_DEBUG_UNWIND = 1U << 5,
};
#define DU_DEBUG(mask, fmt, args...) \
@@ -130,6 +131,8 @@
#define DU_DEBUG_EH_FRAMES(fmt, args...) DU_DEBUG(EH_FRAMES, fmt, ## args)
#define DU_DEBUG_CFI(fmt, args...) DU_DEBUG(CFI, fmt, ## args)
#define DU_DEBUG_EXPR(fmt, args...) DU_DEBUG(EXPR, fmt, ## args)
+#define DU_DEBUG_UNWIND(fmt, args...) DU_DEBUG(UNWIND, fmt, ## args)
+
int du_read_uleb128(u8 **p, u8 *end, u64 *val);
int du_read_sleb128(u8 **p, u8 *end, s64 *val);
diff --git a/kernel/dwarf_unwind/unwind.c b/kernel/dwarf_unwind/unwind.c
index 0599860..10b0ba0 100644
--- a/kernel/dwarf_unwind/unwind.c
+++ b/kernel/dwarf_unwind/unwind.c
@@ -1,5 +1,285 @@
#include <linux/module.h>
+#include <linux/compiler.h>
+#include <linux/debugfs.h>
+#include <linux/ptrace.h>
+#include <linux/dwarf_unwind.h>
+#include <linux/uaccess.h>
+#include <asm/dwarf_unwind_regs.h>
+#include "internal.h"
unsigned int dwarf_unwind_debug = 0;
module_param(dwarf_unwind_debug, int, 0644);
MODULE_PARM_DESC(dwarf_unwind_debug, "Turns on debug for dwarf unwind code.");
+
+struct unwind {
+ struct pt_regs regs;
+};
+
+static void unwind_init(struct unwind *u, struct pt_regs *regs)
+{
+ memcpy(&u->regs, regs, sizeof(*regs));
+}
+
+struct du_state *state_get(void)
+{
+ struct du_state *us;
+
+ /* TODO be smarter with allocation.. */
+ /* All ZEROs means initialized to DU_LOCATION_SAME */
+ return kzalloc(sizeof(*us), GFP_ATOMIC);
+}
+
+void state_put(struct du_state *us)
+{
+ kfree(us);
+}
+
+static int process_fde(struct du_fde *fde, struct du_state *state,
+ unsigned long ip)
+{
+ struct du_cie *cie = fde->cie;
+ struct du_state_regs *state_current;
+ int ret;
+
+ ret = du_cfi(fde, state, ip, &cie->frame);
+ if (ret)
+ return ret;
+
+ state_current = &state->state_current[state->cur];
+
+ memcpy(&state->state_initial, state_current,
+ sizeof(*state_current));
+
+ return du_cfi(fde, state, ip, &fde->frame);
+}
+
+static int __apply_state(struct du_regs *regs,
+ struct du_state_regs *state)
+{
+ struct du_state_reg *cfa_state;
+ struct du_regs tmp_regs;
+ unsigned long prev_ip;
+ unsigned long prev_cfa;
+ unsigned long cfa;
+ unsigned long expr_len;
+ u8 *expr;
+ int i;
+
+ cfa_state = &state->reg[DU_REG_CFA_REG_COLUMN];
+
+ prev_ip = regs->reg[DU_REG_IP];
+ prev_cfa = regs->reg[DU_REG_CFA];
+
+ DU_DEBUG_UNWIND("prev_cfa 0x%lx, prev_ip 0x%lx\n",
+ prev_cfa, prev_ip);
+
+ if (cfa_state->loc == DU_LOCATION_REG) {
+ struct du_state_reg *sp_state;
+
+ /*
+ * CFA is equal to [reg] + offset:
+ *
+ * As a special-case, if the stack-pointer is the CFA and the
+ * stack-pointer wasn't saved, popping the CFA implicitly pops
+ * the stack-pointer as well.
+ */
+
+ sp_state = &state->reg[DU_REG_SP];
+
+ if ((cfa_state->val == DU_REG_SP) &&
+ (sp_state->loc == DU_LOCATION_SAME))
+ cfa = prev_cfa;
+ else {
+ unsigned long reg;
+
+ reg = cfa_state->val;
+
+ DU_DEBUG_UNWIND("cfa reg %ld, val 0x%lx\n",
+ cfa_state->val, regs->reg[reg]);
+
+ cfa = regs->reg[reg];
+ }
+
+ cfa += state->reg[DU_REG_CFA_OFF_COLUMN].val;
+
+ DU_DEBUG_UNWIND("cfa %lx += off 0x%lx\n",
+ cfa, state->reg[DU_REG_CFA_OFF_COLUMN].val);
+
+ } else {
+ if ((cfa_state->loc != DU_LOCATION_EXPR) ||
+ (cfa_state->loc != DU_LOCATION_EXPR_VALUE))
+ return -EINVAL;
+
+ DU_DEBUG_UNWIND("cfa expr\n");
+
+ expr = cfa_state->expr;
+ expr_len = cfa_state->len;
+
+ if (du_expr(regs, expr, expr_len, &cfa))
+ return -EINVAL;
+ }
+
+ regs->reg[DU_REG_CFA] = cfa;
+
+ /* Suck new register values. */
+ for (i = 0; i < DU_REGS_NUM; ++i) {
+ if (state->reg[i].loc == DU_LOCATION_REG) {
+ int reg = state->reg[i].val;
+ tmp_regs.reg[i] = regs->reg[reg];
+ }
+ }
+
+ /* And the rest. */
+ for (i = 0; i < DU_REGS_NUM; ++i) {
+ struct du_state_reg *rs = &state->reg[i];
+ unsigned long *p;
+ unsigned long val;
+
+ switch (rs->loc) {
+ case DU_LOCATION_REG:
+ regs->reg[i] = tmp_regs.reg[i];
+ break;
+
+ case DU_LOCATION_SAME:
+ break;
+
+ case DU_LOCATION_MEMORY:
+ p = (unsigned long *) (cfa + rs->val);
+
+ if (probe_kernel_address(p, val)) {
+ DU_DEBUG_UNWIND("LOC MEMORY failed %p\n", p);
+ return -EFAULT;
+ }
+
+ DU_DEBUG_UNWIND("LOC MEMORY reg %d, cfa 0x%lx + 0x%lx [%p] = 0x%lx\n",
+ i, cfa, rs->val, p, val);
+
+ regs->reg[i] = val;
+ break;
+
+ case DU_LOCATION_EXPR:
+ case DU_LOCATION_EXPR_VALUE:
+ expr = rs->expr;
+ expr_len = rs->len;
+
+ if (du_expr(regs, expr, expr_len, &val))
+ return -EINVAL;
+
+ if (rs->loc == DU_LOCATION_EXPR)
+ regs->reg[i] = *((unsigned long *) val);
+ else
+ regs->reg[i] = val;
+
+ case DU_LOCATION_UNDEF:
+ regs->reg[i] = 0;
+
+ case DU_LOCATION_VALUE:
+ break;
+ }
+ }
+
+ DU_DEBUG_UNWIND("cfa 0x%lx, ip 0x%lx\n", cfa, regs->reg[DU_REG_IP]);
+
+ /* No change, too bad.. */
+ if ((regs->reg[DU_REG_IP] == prev_ip) &&
+ (cfa == prev_cfa))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int apply_state(struct unwind *u,
+ struct du_state *state)
+{
+ struct du_regs regs;
+ struct du_state_regs *state_regs;
+ int ret;
+
+ du_arch_regs_get(®s, &u->regs);
+
+ state_regs = &state->state_current[state->cur];
+
+ ret = __apply_state(®s, state_regs);
+ if (!ret)
+ du_arch_regs_set(®s, &u->regs);
+
+ return ret;
+}
+
+static struct du_frames* frames_get(unsigned long addr)
+{
+ bool is_core = core_kernel_text(addr);
+ struct module *mod = NULL;
+
+ preempt_disable();
+
+ if (!is_core)
+ mod = __module_text_address(addr);
+
+ preempt_enable();
+
+ DU_DEBUG_UNWIND("is_core %d, mod %p\n", is_core, mod);
+
+ /*
+ * It's not in core, but no module was found,
+ * do not search core frames.
+ */
+ if (!is_core && !mod)
+ return NULL;
+
+ return du_frames_find(mod);
+}
+
+static int unwind_step(struct unwind *u)
+{
+ unsigned long addr = instruction_pointer(&u->regs);
+ struct du_frames *frames;
+ struct du_fde *fde;
+ struct du_state *state;
+ int ret = -EINVAL;
+
+ DU_DEBUG_UNWIND("addr 0x%lx\n", addr);
+
+ frames = frames_get(addr);
+ if (!frames)
+ goto out;
+
+ DU_DEBUG_UNWIND("frames %p\n", frames);
+
+ fde = du_fde_lookup(frames, addr);
+ if (!fde)
+ goto out_frames;
+
+ DU_DEBUG_UNWIND("fde %p\n", fde);
+
+ state = state_get();
+ if (!state)
+ goto out_frames;
+
+ ret = process_fde(fde, state, addr);
+ if (ret)
+ goto out_state;
+
+ ret = apply_state(u, state);
+
+ out_state:
+ state_put(state);
+ out_frames:
+ du_frames_put(frames);
+ out:
+ DU_DEBUG_UNWIND("ret %d\n", ret);
+ return ret;
+}
+
+int dwarf_unwind(struct pt_regs *regs, dwarf_unwind_cb cb, void *data)
+{
+ struct unwind u;
+ int ret = 0;
+
+ unwind_init(&u, regs);
+
+ while (!ret && !unwind_step(&u))
+ ret = cb(&u.regs, data);
+
+ return ret;
+}