[RFC PATCH 10/10] x86/unwind: add undwarf unwinder

From: Josh Poimboeuf
Date: Thu Jun 01 2017 - 01:45:14 EST


Add a new 'undwarf' unwinder which is enabled by
CONFIG_UNDWARF_UNWINDER. It plugs into the existing x86 unwinder
framework.

It relies on objtool to generate the needed .undwarf section.

For more details on why undwarf is used instead of DWARF, see
tools/objtool/Documentation/undwarf.txt.

Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
---
arch/um/include/asm/unwind.h | 7 +
arch/x86/Kconfig | 1 +
arch/x86/Kconfig.debug | 26 +++
arch/x86/include/asm/module.h | 8 +
arch/x86/include/asm/unwind.h | 64 +++---
arch/x86/kernel/Makefile | 8 +-
arch/x86/kernel/module.c | 9 +-
arch/x86/kernel/unwind_frame.c | 39 ++--
arch/x86/kernel/unwind_guess.c | 5 +
arch/x86/kernel/unwind_undwarf.c | 402 ++++++++++++++++++++++++++++++++++++++
include/asm-generic/vmlinux.lds.h | 14 ++
lib/Kconfig.debug | 3 +
scripts/Makefile.build | 3 +-
scripts/link-vmlinux.sh | 5 +
14 files changed, 534 insertions(+), 60 deletions(-)
create mode 100644 arch/um/include/asm/unwind.h
create mode 100644 arch/x86/kernel/unwind_undwarf.c

diff --git a/arch/um/include/asm/unwind.h b/arch/um/include/asm/unwind.h
new file mode 100644
index 0000000..4e3f719
--- /dev/null
+++ b/arch/um/include/asm/unwind.h
@@ -0,0 +1,7 @@
+#ifndef _ASM_UML_UNWIND_H
+#define _ASM_UML_UNWIND_H
+
+static inline void
+unwind_module_init(struct module *mod, void *undwarf, size_t size) {}
+
+#endif /* _ASM_UML_UNWIND_H */
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 4ccfacc..869fbc5 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -151,6 +151,7 @@ config X86
select HAVE_MEMBLOCK
select HAVE_MEMBLOCK_NODE_MAP
select HAVE_MIXED_BREAKPOINTS_REGS
+ select HAVE_MOD_ARCH_SPECIFIC
select HAVE_NMI
select HAVE_OPROFILE
select HAVE_OPTPROBES
diff --git a/arch/x86/Kconfig.debug b/arch/x86/Kconfig.debug
index fcb7604..6717463 100644
--- a/arch/x86/Kconfig.debug
+++ b/arch/x86/Kconfig.debug
@@ -357,4 +357,30 @@ config PUNIT_ATOM_DEBUG
The current power state can be read from
/sys/kernel/debug/punit_atom/dev_power_state

+config UNDWARF_UNWINDER
+ bool "undwarf unwinder"
+ depends on X86_64
+ select STACK_VALIDATION
+ select SORTTABLE
+ ---help---
+ This option enables the "undwarf" unwinder for unwinding kernel stack
+ traces. It uses a custom data format which is a simplified version
+ of the DWARF Call Frame Information standard.
+
+ This unwinder is more accurate across interrupt entry frames than the
+ frame pointer unwinder. This also can enable a small performance
+ improvement across the entire kernel if CONFIG_FRAME_POINTER is
+ disabled.
+
+ Enabling this option will increase the kernel's runtime memory usage
+ by roughly 3-5MB, depending on the kernel config.
+
+config FRAME_POINTER_UNWINDER
+ def_bool y
+ depends on !UNDWARF_UNWINDER && FRAME_POINTER
+
+config GUESS_UNWINDER
+ def_bool y
+ depends on !UNDWARF_UNWINDER && !FRAME_POINTER
+
endmenu
diff --git a/arch/x86/include/asm/module.h b/arch/x86/include/asm/module.h
index e3b7819..454eeea 100644
--- a/arch/x86/include/asm/module.h
+++ b/arch/x86/include/asm/module.h
@@ -2,6 +2,14 @@
#define _ASM_X86_MODULE_H

#include <asm-generic/module.h>
+#include <asm/undwarf.h>
+
+struct mod_arch_specific {
+#ifdef CONFIG_UNDWARF_UNWINDER
+ unsigned int num_undwarves;
+ struct undwarf *undwarf;
+#endif
+};

#ifdef CONFIG_X86_64
/* X86_64 does not define MODULE_PROC_FAMILY */
diff --git a/arch/x86/include/asm/unwind.h b/arch/x86/include/asm/unwind.h
index e667649..f06be3f 100644
--- a/arch/x86/include/asm/unwind.h
+++ b/arch/x86/include/asm/unwind.h
@@ -12,11 +12,13 @@ struct unwind_state {
struct task_struct *task;
int graph_idx;
bool error;
-#ifdef CONFIG_FRAME_POINTER
+#if defined(CONFIG_UNDWARF_UNWINDER)
+ unsigned long sp, bp, ip;
+ struct pt_regs *regs;
+#elif defined(CONFIG_FRAME_POINTER)
bool got_irq;
- unsigned long *bp, *orig_sp;
+ unsigned long *bp, *orig_sp, ip;
struct pt_regs *regs;
- unsigned long ip;
#else
unsigned long *sp;
#endif
@@ -24,41 +26,30 @@ struct unwind_state {

void __unwind_start(struct unwind_state *state, struct task_struct *task,
struct pt_regs *regs, unsigned long *first_frame);
-
bool unwind_next_frame(struct unwind_state *state);
-
unsigned long unwind_get_return_address(struct unwind_state *state);
+unsigned long *unwind_get_return_address_ptr(struct unwind_state *state);

static inline bool unwind_done(struct unwind_state *state)
{
return state->stack_info.type == STACK_TYPE_UNKNOWN;
}

-static inline
-void unwind_start(struct unwind_state *state, struct task_struct *task,
- struct pt_regs *regs, unsigned long *first_frame)
-{
- first_frame = first_frame ? : get_stack_pointer(task, regs);
-
- __unwind_start(state, task, regs, first_frame);
-}
-
static inline bool unwind_error(struct unwind_state *state)
{
return state->error;
}

-#ifdef CONFIG_FRAME_POINTER
-
static inline
-unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+void unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs, unsigned long *first_frame)
{
- if (unwind_done(state))
- return NULL;
+ first_frame = first_frame ? : get_stack_pointer(task, regs);

- return state->regs ? &state->regs->ip : state->bp + 1;
+ __unwind_start(state, task, regs, first_frame);
}

+#if defined(CONFIG_UNDWARF_UNWINDER) || defined(CONFIG_FRAME_POINTER)
static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state)
{
if (unwind_done(state))
@@ -66,20 +57,33 @@ static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state)

return state->regs;
}
-
-#else /* !CONFIG_FRAME_POINTER */
-
-static inline
-unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
-{
- return NULL;
-}
-
+#else
static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state)
{
return NULL;
}
+#endif
+
+#ifdef CONFIG_UNDWARF_UNWINDER
+void unwind_module_init(struct module *mod, void *undwarf, size_t size);
+#else
+static inline void
+unwind_module_init(struct module *mod, void *undwarf, size_t size) {}
+#endif

-#endif /* CONFIG_FRAME_POINTER */
+/*
+ * This disables KASAN checking when reading a value from another task's stack,
+ * since the other task could be running on another CPU and could have poisoned
+ * the stack in the meantime.
+ */
+#define READ_ONCE_TASK_STACK(task, x) \
+({ \
+ unsigned long val; \
+ if (task == current) \
+ val = READ_ONCE(x); \
+ else \
+ val = READ_ONCE_NOCHECK(x); \
+ val; \
+})

#endif /* _ASM_X86_UNWIND_H */
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 3c7c419..4865889 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -125,11 +125,9 @@ obj-$(CONFIG_PERF_EVENTS) += perf_regs.o
obj-$(CONFIG_TRACING) += tracepoint.o
obj-$(CONFIG_SCHED_MC_PRIO) += itmt.o

-ifdef CONFIG_FRAME_POINTER
-obj-y += unwind_frame.o
-else
-obj-y += unwind_guess.o
-endif
+obj-$(CONFIG_UNDWARF_UNWINDER) += unwind_undwarf.o
+obj-$(CONFIG_FRAME_POINTER_UNWINDER) += unwind_frame.o
+obj-$(CONFIG_GUESS_UNWINDER) += unwind_guess.o

###
# 64 bit specific files
diff --git a/arch/x86/kernel/module.c b/arch/x86/kernel/module.c
index f67bd32..6756070 100644
--- a/arch/x86/kernel/module.c
+++ b/arch/x86/kernel/module.c
@@ -35,6 +35,7 @@
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/setup.h>
+#include <asm/unwind.h>

#if 0
#define DEBUGP(fmt, ...) \
@@ -213,7 +214,7 @@ int module_finalize(const Elf_Ehdr *hdr,
struct module *me)
{
const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL,
- *para = NULL;
+ *para = NULL, *undwarf = NULL;
char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;

for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
@@ -225,6 +226,8 @@ int module_finalize(const Elf_Ehdr *hdr,
locks = s;
if (!strcmp(".parainstructions", secstrings + s->sh_name))
para = s;
+ if (!strcmp(".undwarf", secstrings + s->sh_name))
+ undwarf = s;
}

if (alt) {
@@ -248,6 +251,10 @@ int module_finalize(const Elf_Ehdr *hdr,
/* make jump label nops */
jump_label_apply_nops(me);

+ if (undwarf)
+ unwind_module_init(me, (void *)undwarf->sh_addr,
+ undwarf->sh_size);
+
return 0;
}

diff --git a/arch/x86/kernel/unwind_frame.c b/arch/x86/kernel/unwind_frame.c
index b9389d7..7574ef5 100644
--- a/arch/x86/kernel/unwind_frame.c
+++ b/arch/x86/kernel/unwind_frame.c
@@ -10,20 +10,22 @@

#define FRAME_HEADER_SIZE (sizeof(long) * 2)

-/*
- * This disables KASAN checking when reading a value from another task's stack,
- * since the other task could be running on another CPU and could have poisoned
- * the stack in the meantime.
- */
-#define READ_ONCE_TASK_STACK(task, x) \
-({ \
- unsigned long val; \
- if (task == current) \
- val = READ_ONCE(x); \
- else \
- val = READ_ONCE_NOCHECK(x); \
- val; \
-})
+unsigned long unwind_get_return_address(struct unwind_state *state)
+{
+ if (unwind_done(state))
+ return 0;
+
+ return __kernel_text_address(state->ip) ? state->ip : 0;
+}
+EXPORT_SYMBOL_GPL(unwind_get_return_address);
+
+unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+{
+ if (unwind_done(state))
+ return NULL;
+
+ return state->regs ? &state->regs->ip : state->bp + 1;
+}

static void unwind_dump(struct unwind_state *state)
{
@@ -66,15 +68,6 @@ static void unwind_dump(struct unwind_state *state)
}
}

-unsigned long unwind_get_return_address(struct unwind_state *state)
-{
- if (unwind_done(state))
- return 0;
-
- return __kernel_text_address(state->ip) ? state->ip : 0;
-}
-EXPORT_SYMBOL_GPL(unwind_get_return_address);
-
static size_t regs_size(struct pt_regs *regs)
{
/* x86_32 regs from kernel mode are two words shorter: */
diff --git a/arch/x86/kernel/unwind_guess.c b/arch/x86/kernel/unwind_guess.c
index 039f367..4f0e17b 100644
--- a/arch/x86/kernel/unwind_guess.c
+++ b/arch/x86/kernel/unwind_guess.c
@@ -19,6 +19,11 @@ unsigned long unwind_get_return_address(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_get_return_address);

+unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+{
+ return NULL;
+}
+
bool unwind_next_frame(struct unwind_state *state)
{
struct stack_info *info = &state->stack_info;
diff --git a/arch/x86/kernel/unwind_undwarf.c b/arch/x86/kernel/unwind_undwarf.c
new file mode 100644
index 0000000..8023662
--- /dev/null
+++ b/arch/x86/kernel/unwind_undwarf.c
@@ -0,0 +1,402 @@
+#include <linux/module.h>
+#include <linux/sort.h>
+#include <asm/ptrace.h>
+#include <asm/stacktrace.h>
+#include <asm/unwind.h>
+#include <asm/undwarf.h>
+
+#define undwarf_warn(fmt, ...) \
+ printk_deferred_once(KERN_WARNING pr_fmt("WARNING: " fmt), ##__VA_ARGS__)
+
+extern struct undwarf __undwarf_start[];
+extern struct undwarf __undwarf_end[];
+
+unsigned long unwind_get_return_address(struct unwind_state *state)
+{
+ if (unwind_done(state))
+ return 0;
+
+ return __kernel_text_address(state->ip) ? state->ip : 0;
+}
+EXPORT_SYMBOL_GPL(unwind_get_return_address);
+
+unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+{
+ if (unwind_done(state))
+ return NULL;
+
+ if (state->regs)
+ return &state->regs->ip;
+
+ if (state->sp)
+ return (unsigned long *)state->sp - 1;
+
+ return NULL;
+}
+
+static inline unsigned long undwarf_ip(struct undwarf *undwarf)
+{
+ return (unsigned long)&undwarf->ip + undwarf->ip;
+}
+
+static struct undwarf *__undwarf_lookup(struct undwarf *undwarf,
+ unsigned int num, unsigned long ip)
+{
+ struct undwarf *first = undwarf;
+ struct undwarf *last = undwarf + num - 1;
+ struct undwarf *mid;
+ unsigned long u_ip;
+
+ while (first <= last) {
+ mid = first + ((last - first) / 2);
+ u_ip = undwarf_ip(mid);
+
+ if (ip >= u_ip) {
+ if (ip < u_ip + mid->len)
+ return mid;
+ first = mid + 1;
+ } else
+ last = mid - 1;
+ }
+
+ return NULL;
+}
+
+static struct undwarf *undwarf_lookup(unsigned long ip)
+{
+ struct undwarf *undwarf;
+ struct module *mod;
+
+ /* Look in vmlinux undwarf section: */
+ undwarf = __undwarf_lookup(__undwarf_start, __undwarf_end - __undwarf_start, ip);
+ if (undwarf)
+ return undwarf;
+
+ /* Look in module undwarf sections: */
+ preempt_disable();
+ mod = __module_address(ip);
+ if (!mod || !mod->arch.undwarf)
+ goto module_out;
+ undwarf = __undwarf_lookup(mod->arch.undwarf, mod->arch.num_undwarves, ip);
+
+module_out:
+ preempt_enable();
+ return undwarf;
+}
+
+static bool stack_access_ok(struct unwind_state *state, unsigned long addr,
+ size_t len)
+{
+ struct stack_info *info = &state->stack_info;
+
+ /*
+ * If the next bp isn't on the current stack, switch to the next one.
+ *
+ * We may have to traverse multiple stacks to deal with the possibility
+ * that info->next_sp could point to an empty stack and the next bp
+ * could be on a subsequent stack.
+ */
+ while (!on_stack(info, (void *)addr, len))
+ if (get_stack_info(info->next_sp, state->task, info,
+ &state->stack_mask))
+ return false;
+
+ return true;
+}
+
+static bool deref_stack_reg(struct unwind_state *state, unsigned long addr,
+ unsigned long *val)
+{
+ if (!stack_access_ok(state, addr, sizeof(long)))
+ return false;
+
+ *val = READ_ONCE_TASK_STACK(state->task, *(unsigned long *)addr);
+ return true;
+}
+
+#define REGS_SIZE (sizeof(struct pt_regs))
+#define SP_OFFSET (offsetof(struct pt_regs, sp))
+#define IRET_REGS_SIZE (REGS_SIZE - offsetof(struct pt_regs, ip))
+#define IRET_SP_OFFSET (SP_OFFSET - offsetof(struct pt_regs, ip))
+
+static bool deref_stack_regs(struct unwind_state *state, unsigned long addr,
+ unsigned long *ip, unsigned long *sp, bool full)
+{
+ size_t regs_size = full ? REGS_SIZE : IRET_REGS_SIZE;
+ size_t sp_offset = full ? SP_OFFSET : IRET_SP_OFFSET;
+ struct pt_regs *regs = (struct pt_regs *)(addr + regs_size - REGS_SIZE);
+
+ if (IS_ENABLED(CONFIG_X86_64)) {
+ if (!stack_access_ok(state, addr, regs_size))
+ return false;
+
+ *ip = regs->ip;
+ *sp = regs->sp;
+
+ return true;
+ }
+
+ if (!stack_access_ok(state, addr, sp_offset))
+ return false;
+
+ *ip = regs->ip;
+
+ if (user_mode(regs)) {
+ if (!stack_access_ok(state, addr + sp_offset, REGS_SIZE - SP_OFFSET))
+ return false;
+
+ *sp = regs->sp;
+ } else
+ *sp = (unsigned long)&regs->sp;
+
+ return true;
+}
+
+bool unwind_next_frame(struct unwind_state *state)
+{
+ struct undwarf *undwarf;
+ unsigned long cfa;
+ bool indirect = false;
+ enum stack_type prev_type = state->stack_info.type;
+ unsigned long ip_p, prev_sp = state->sp;
+
+ if (unwind_done(state))
+ return false;
+
+ /* Have we reached the end? */
+ if (state->regs && user_mode(state->regs))
+ goto done;
+
+ /* Look up the instruction address in the .undwarf table: */
+ undwarf = undwarf_lookup(state->ip);
+ if (!undwarf || undwarf->cfa_reg == UNDWARF_REG_UNDEFINED)
+ goto done;
+
+ /* Calculate the CFA (caller frame address): */
+ switch (undwarf->cfa_reg) {
+ case UNDWARF_REG_SP:
+ cfa = state->sp + undwarf->cfa_offset;
+ break;
+
+ case UNDWARF_REG_BP:
+ cfa = state->bp + undwarf->cfa_offset;
+ break;
+
+ case UNDWARF_REG_SP_INDIRECT:
+ cfa = state->sp + undwarf->cfa_offset;
+ indirect = true;
+ break;
+
+ case UNDWARF_REG_BP_INDIRECT:
+ cfa = state->bp + undwarf->cfa_offset;
+ indirect = true;
+ break;
+
+ case UNDWARF_REG_R10:
+ if (!state->regs) {
+ undwarf_warn("missing regs for base reg R10 at ip %p\n",
+ (void *)state->ip);
+ goto done;
+ }
+ cfa = state->regs->r10;
+ break;
+
+ case UNDWARF_REG_DI:
+ if (!state->regs) {
+ undwarf_warn("missing regs for base reg DI at ip %p\n",
+ (void *)state->ip);
+ goto done;
+ }
+ cfa = state->regs->di;
+ break;
+
+ case UNDWARF_REG_DX:
+ if (!state->regs) {
+ undwarf_warn("missing regs for base reg DI at ip %p\n",
+ (void *)state->ip);
+ goto done;
+ }
+ cfa = state->regs->dx;
+ break;
+
+ default:
+ undwarf_warn("unknown CFA base reg %d for ip %p\n",
+ undwarf->cfa_reg, (void *)state->ip);
+ goto done;
+ }
+
+ if (indirect) {
+ if (!deref_stack_reg(state, cfa, &cfa))
+ goto done;
+ }
+
+ /* Find IP, SP and possibly regs: */
+ switch (undwarf->type) {
+ case UNDWARF_TYPE_CFA:
+ ip_p = cfa - sizeof(long);
+
+ if (!deref_stack_reg(state, ip_p, &state->ip))
+ goto done;
+
+ state->ip = ftrace_graph_ret_addr(state->task, &state->graph_idx,
+ state->ip, (void *)ip_p);
+
+ state->sp = cfa;
+ state->regs = NULL;
+ break;
+
+ case UNDWARF_TYPE_REGS:
+ if (!deref_stack_regs(state, cfa, &state->ip, &state->sp, true)) {
+ undwarf_warn("can't dereference registers at %p for ip %p\n",
+ (void *)cfa, (void *)state->ip);
+ goto done;
+ }
+
+ state->regs = (struct pt_regs *)cfa;
+ break;
+
+ case UNDWARF_TYPE_REGS_IRET:
+ if (!deref_stack_regs(state, cfa, &state->ip, &state->sp, false)) {
+ undwarf_warn("can't dereference iret registers at %p for ip %p\n",
+ (void *)cfa, (void *)state->ip);
+ goto done;
+ }
+
+ state->regs = NULL;
+ break;
+
+ default:
+ undwarf_warn("unknown undwarf type %d\n", undwarf->type);
+ break;
+ }
+
+ /* Find BP: */
+ switch (undwarf->bp_reg) {
+ case UNDWARF_REG_UNDEFINED:
+ if (state->regs)
+ state->bp = state->regs->bp;
+ break;
+
+ case UNDWARF_REG_CFA:
+ if (!deref_stack_reg(state, cfa + undwarf->bp_offset, &state->bp))
+ goto done;
+ break;
+
+ case UNDWARF_REG_BP:
+ if (!deref_stack_reg(state, state->bp + undwarf->bp_offset, &state->bp))
+ goto done;
+ break;
+
+ default:
+ undwarf_warn("unknown BP base reg %d for ip %p\n",
+ undwarf->bp_reg, (void *)undwarf_ip(undwarf));
+ goto done;
+ }
+
+ /* Prevent a recursive loop due to bad .undwarf data: */
+ if (state->stack_info.type == prev_type &&
+ on_stack(&state->stack_info, (void *)state->sp, sizeof(long)) &&
+ state->sp <= prev_sp) {
+ undwarf_warn("stack going in the wrong direction? ip=%p\n",
+ (void *)state->ip);
+ goto done;
+ }
+
+ return true;
+
+done:
+ state->stack_info.type = STACK_TYPE_UNKNOWN;
+ return false;
+}
+EXPORT_SYMBOL_GPL(unwind_next_frame);
+
+void __unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs, unsigned long *first_frame)
+{
+ memset(state, 0, sizeof(*state));
+ state->task = task;
+
+ if (regs) {
+ if (user_mode(regs)) {
+ state->stack_info.type = STACK_TYPE_UNKNOWN;
+ return;
+ }
+
+ state->ip = regs->ip;
+ state->sp = kernel_stack_pointer(regs);
+ state->bp = regs->bp;
+ state->regs = regs;
+
+ } else if (task == current) {
+ register void *__sp asm(_ASM_SP);
+
+ asm volatile("lea (%%rip), %0\n\t"
+ "mov %%rsp, %1\n\t"
+ "mov %%rbp, %2\n\t"
+ : "=r" (state->ip), "=r" (state->sp),
+ "=r" (state->bp), "+r" (__sp));
+
+ state->regs = NULL;
+
+ } else {
+ struct inactive_task_frame *frame = (void *)task->thread.sp;
+
+ state->ip = frame->ret_addr;
+ state->sp = task->thread.sp;
+ state->bp = frame->bp;
+ state->regs = NULL;
+ }
+
+ if (get_stack_info((unsigned long *)state->sp, state->task,
+ &state->stack_info, &state->stack_mask))
+ return;
+
+ /*
+ * The caller can provide the address of the first frame directly
+ * (first_frame) or indirectly (regs->sp) to indicate which stack frame
+ * to start unwinding at. Skip ahead until we reach it.
+ */
+ while (!unwind_done(state) &&
+ (!on_stack(&state->stack_info, first_frame, sizeof(long)) ||
+ state->sp <= (unsigned long)first_frame))
+ unwind_next_frame(state);
+}
+EXPORT_SYMBOL_GPL(__unwind_start);
+
+static void undwarf_sort_swap(void *_a, void *_b, int size)
+{
+ struct undwarf *a = _a, *b = _b, tmp;
+ int delta = _b - _a;
+
+ tmp = *a;
+ *a = *b;
+ *b = tmp;
+
+ a->ip += delta;
+ b->ip -= delta;
+}
+
+static int undwarf_sort_cmp(const void *_a, const void *_b)
+{
+ unsigned long a = undwarf_ip((struct undwarf *)_a);
+ unsigned long b = undwarf_ip((struct undwarf *)_b);
+
+ if (a > b)
+ return 1;
+ if (a < b)
+ return -1;
+ return 0;
+}
+
+void unwind_module_init(struct module *mod, void *u, size_t size)
+{
+ struct undwarf *undwarf = u;
+ unsigned int num = size / sizeof(*undwarf);
+
+ WARN_ON_ONCE(size % sizeof(*undwarf) != 0);
+
+ sort(undwarf, num, sizeof(*undwarf), undwarf_sort_cmp, undwarf_sort_swap);
+
+ mod->arch.undwarf = undwarf;
+ mod->arch.num_undwarves = num;
+}
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 314a0b9..e350116 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -324,6 +324,8 @@
\
TRACEDATA \
\
+ UNDWARF_TABLE \
+ \
/* Kernel symbol table: Normal symbols */ \
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__start___ksymtab) = .; \
@@ -669,6 +671,18 @@
#define BUG_TABLE
#endif

+#ifdef CONFIG_UNDWARF_UNWINDER
+#define UNDWARF_TABLE \
+ . = ALIGN(16); \
+ .undwarf : AT(ADDR(.undwarf) - LOAD_OFFSET) { \
+ VMLINUX_SYMBOL(__undwarf_start) = .; \
+ KEEP(*(.undwarf)) \
+ VMLINUX_SYMBOL(__undwarf_end) = .; \
+ }
+#else
+#define UNDWARF_TABLE
+#endif
+
#ifdef CONFIG_PM_TRACE
#define TRACEDATA \
. = ALIGN(4); \
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index e4587eb..31f73d9 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -374,6 +374,9 @@ config STACK_VALIDATION
pointers (if CONFIG_FRAME_POINTER is enabled). This helps ensure
that runtime stack traces are more reliable.

+ This is also a prerequisite for creation of the undwarf format which
+ is needed for CONFIG_UNDWARF_UNWINDER.
+
For more information, see
tools/objtool/Documentation/stack-validation.txt.

diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 733e044..b43dddf 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -258,7 +258,8 @@ ifneq ($(SKIP_STACK_VALIDATION),1)

__objtool_obj := $(objtree)/tools/objtool/objtool

-objtool_args = check
+objtool_args = $(if $(CONFIG_UNDWARF_UNWINDER),undwarf generate,check)
+
ifndef CONFIG_FRAME_POINTER
objtool_args += --no-fp
endif
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index f4eb9dc..286ea8d 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -296,6 +296,11 @@ if [ -n "${CONFIG_BUILDTIME_EXTABLE_SORT}" ]; then
sortextable vmlinux
fi

+if [ -n "${CONFIG_UNDWARF_UNWINDER}" ]; then
+ info SORTUD vmlinux
+ sortundwarf vmlinux
+fi
+
info SYSMAP System.map
mksysmap vmlinux System.map

--
2.7.4