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 <jpoim...@redhat.com>
---
 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

Reply via email to