lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Fri,  5 May 2017 14:21:55 +0200
From:   Jiri Slaby <jslaby@...e.cz>
To:     akpm@...ux-foundation.org
Cc:     torvalds@...ux-foundation.org, live-patching@...r.kernel.org,
        linux-kernel@...r.kernel.org, Jiri Slaby <jslaby@...e.cz>,
        Jan Beulich <jbeulich@...e.com>, Jeff Mahoney <jeffm@...e.com>,
        Thomas Gleixner <tglx@...utronix.de>,
        Ingo Molnar <mingo@...hat.com>,
        "H. Peter Anvin" <hpa@...or.com>, x86@...nel.org
Subject: [PATCH 2/7] DWARF: EH-frame based stack unwinding

Add core code of the DWARF unwinder. This code is capable of unwinding
the stack using the out-of-band DWARF information. We have been using
this for years in SUSE. Now, we have cleaned it up and propose it for
merging upstream. Not only as a frame pointer unwinding alternative for
debugging purposes, but also as a base for DWARF-based stack checking
in the kernel live patching.

Also, a nice side effect of using the DWARF unwinder is increased
performance. We performed several benchmarks and the gain is speedup
about 10 %. This stems from using the out-of-band data and freeing up
one register (rbp), as we do not need to preserve the frame pointer
anywhere.

It is worth noting, that this is incomplete implementation in terms of
the DWARF standard. But it is complete in terms of what we actually need
for the in-kernel DWARF unwinding.

A known drawback is that the assembler is not unwound properly. We used
to have manual DWARF annotations before 131484c8da97 (x86/debug: Remove
perpetually broken, unmaintainable dwarf annotations), but they are long
gone. There is a current work in progress to annotate assembly
automatically by objtool. In the meantime, if enabled, the frame pointer
can be used for the rest of the stack instead.

Besides that, there is a current work in progress to annotate assembly
automatically by objtool. For this reason, the DWARF unwinder will be
by default off until assembly is handled properly.

This is only core code, the further patches will plug it into the kernel
unwinder's core and to kernel and module inits/exits. Finally, an
appropriate config option is added.

Final note: yes, this is the unwinded which was submitted for upstream a
long time ago and it was rejected. This is a completely redesigned
version, so we send it again for consideration.

Thanks should go also to Andi Kleen, and Jeff Mahoney, who helped to
drag the patch in SUSE's repositories for almost a decade.

Signed-off-by: Jiri Slaby <jslaby@...e.cz>
Cc: Jan Beulich <jbeulich@...e.com>
Cc: Jeff Mahoney <jeffm@...e.com>
Cc: Thomas Gleixner <tglx@...utronix.de>
Cc: Ingo Molnar <mingo@...hat.com>
Cc: "H. Peter Anvin" <hpa@...or.com>
Cc: x86@...nel.org
---
 arch/x86/entry/entry_32.S    |   21 +
 arch/x86/entry/entry_64.S    |   18 +
 arch/x86/include/asm/dwarf.h |  130 +++
 include/linux/dwarf.h        |   61 ++
 kernel/Makefile              |    1 +
 kernel/dwarf.c               | 1802 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 2033 insertions(+)
 create mode 100644 arch/x86/include/asm/dwarf.h
 create mode 100644 include/linux/dwarf.h
 create mode 100644 kernel/dwarf.c

diff --git a/arch/x86/entry/entry_32.S b/arch/x86/entry/entry_32.S
index 50bc26949e9e..1229ced3f584 100644
--- a/arch/x86/entry/entry_32.S
+++ b/arch/x86/entry/entry_32.S
@@ -291,6 +291,27 @@ ENTRY(ret_from_fork)
 	jmp	2b
 END(ret_from_fork)
 
+#ifdef CONFIG_DWARF_UNWIND
+ENTRY(arch_dwarf_init_running)
+	movl	4(%esp), %edx
+	movl	(%esp), %ecx
+	movl	%ecx, PT_EIP(%edx)
+	leal	4(%esp), %eax
+	movl	%eax, PT_OLDESP(%edx)
+	movl	%ebx, PT_EBX(%edx)
+	movl	%esi, PT_ESI(%edx)
+	movl	%edi, PT_EDI(%edx)
+	movl	%ebp, PT_EBP(%edx)
+	movl	$__KERNEL_CS, PT_CS(%edx)
+	movl	$__USER_DS, PT_DS(%edx)
+	movl	$__USER_DS, PT_ES(%edx)
+	movl	$__KERNEL_PERCPU, PT_FS(%edx)
+	movl	$__KERNEL_STACK_CANARY, PT_GS(%edx)
+	movl	$__KERNEL_DS, PT_OLDSS(%edx)
+	ret
+ENDPROC(arch_dwarf_init_running)
+#endif
+
 /*
  * Return to user mode is not as complex as all this looks,
  * but we want the default path for a system call return to
diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index 607d72c4a485..ee90b81065bd 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -902,6 +902,24 @@ ENTRY(do_softirq_own_stack)
 	ret
 END(do_softirq_own_stack)
 
+#ifdef CONFIG_DWARF_UNWIND
+ENTRY(arch_dwarf_init_running)
+	movq	%r15, R15(%rdi)
+	movq	%r14, R14(%rdi)
+	movq	%r13, R13(%rdi)
+	movq	%r12, R12(%rdi)
+	movq	%rbp, RBP(%rdi)
+	movq	%rbx, RBX(%rdi)
+	movq	(%rsp), %r9
+	movq	%r9, RIP(%rdi)
+	leaq	8(%rsp), %r9
+	movq	%r9, RSP(%rdi)
+	movq	$__KERNEL_CS, CS(%rdi)
+	movq	$__KERNEL_DS, SS(%rdi)
+	ret
+ENDPROC(arch_dwarf_init_running)
+#endif
+
 #ifdef CONFIG_XEN
 idtentry xen_hypervisor_callback xen_do_hypervisor_callback has_error_code=0
 
diff --git a/arch/x86/include/asm/dwarf.h b/arch/x86/include/asm/dwarf.h
new file mode 100644
index 000000000000..87f0839a6037
--- /dev/null
+++ b/arch/x86/include/asm/dwarf.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2002-2017 Novell, Inc.
+ *	Jan Beulich <jbeulich@...ell.com>
+ *	Jiri Slaby <jirislaby@...nel.org>
+ * The code below is released under version 2 of the GNU GPL.
+ */
+
+#ifndef _ASM_X86_DWARF_H
+#define _ASM_X86_DWARF_H
+
+#ifdef CONFIG_DWARF_UNWIND
+
+#include <linux/uaccess.h>
+
+/*
+ * On x86-64, we might need to account for the special exception and interrupt
+ * handling stacks here, since normally
+ *	EXCEPTION_STACK_ORDER < THREAD_ORDER < IRQSTACK_ORDER,
+ * but the construct is needed only for getting across the stack switch to
+ * the interrupt stack - thus considering the IRQ stack itself is unnecessary,
+ * and the overhead of comparing against all exception handling stacks seems
+ * not desirable.
+ */
+#define STACK_LIMIT(ptr)	(((ptr) - 1) & ~(THREAD_SIZE - 1))
+
+#define DW_PC(frame)		((frame)->u.regs.ip)
+#define DW_SP(frame)		((frame)->u.regs.sp)
+#ifdef CONFIG_FRAME_POINTER
+# define DW_FP(frame)		((frame)->u.regs.bp)
+# define FRAME_LINK_OFFSET	0
+# define STACK_BOTTOM(tsk)	STACK_LIMIT((tsk)->thread.sp0)
+# define TSK_STACK_TOP(tsk)	((tsk)->thread.sp0)
+#else
+# define DW_FP(frame)		((void)(frame), 0UL)
+#endif
+
+#ifdef CONFIG_X86_64
+
+#include <asm/vsyscall.h>
+
+#define FRAME_RETADDR_OFFSET 8
+
+#define DW_REGISTER_INFO \
+	PTREGS_INFO(ax), \
+	PTREGS_INFO(dx), \
+	PTREGS_INFO(cx), \
+	PTREGS_INFO(bx), \
+	PTREGS_INFO(si), \
+	PTREGS_INFO(di), \
+	PTREGS_INFO(bp), \
+	PTREGS_INFO(sp), \
+	PTREGS_INFO(r8), \
+	PTREGS_INFO(r9), \
+	PTREGS_INFO(r10), \
+	PTREGS_INFO(r11), \
+	PTREGS_INFO(r12), \
+	PTREGS_INFO(r13), \
+	PTREGS_INFO(r14), \
+	PTREGS_INFO(r15), \
+	PTREGS_INFO(ip)
+
+#else /* X86_32 */
+
+#define FRAME_RETADDR_OFFSET 4
+
+#define DW_REGISTER_INFO \
+	PTREGS_INFO(ax), \
+	PTREGS_INFO(cx), \
+	PTREGS_INFO(dx), \
+	PTREGS_INFO(bx), \
+	PTREGS_INFO(sp), \
+	PTREGS_INFO(bp), \
+	PTREGS_INFO(si), \
+	PTREGS_INFO(di), \
+	PTREGS_INFO(ip)
+
+#endif
+
+#define DW_DEFAULT_RA(raItem, data_align) \
+	((raItem).where == MEMORY && \
+	 !((raItem).value * (data_align) + sizeof(void *)))
+
+static inline void arch_dwarf_init_frame_info(struct unwind_state *info,
+		struct pt_regs *regs)
+{
+#ifdef CONFIG_X86_64
+	info->u.regs = *regs;
+#else
+	if (user_mode(regs))
+		info->u.regs = *regs;
+	else {
+		memcpy(&info->u.regs, regs, offsetof(struct pt_regs, sp));
+		info->u.regs.sp = (unsigned long)&regs->sp;
+		info->u.regs.ss = __KERNEL_DS;
+	}
+#endif
+}
+
+static inline void arch_dwarf_init_blocked(struct unwind_state *info)
+{
+	struct inactive_task_frame *frame = (void *)info->task->thread.sp;
+
+	probe_kernel_address(&frame->ret_addr, info->u.regs.ip);
+	probe_kernel_address(&frame->bp, info->u.regs.bp);
+	info->u.regs.sp = info->task->thread.sp;
+	info->u.regs.cs = __KERNEL_CS;
+	info->u.regs.ss = __KERNEL_DS;
+#ifdef CONFIG_X86_32
+	info->u.regs.ds = __USER_DS;
+	info->u.regs.es = __USER_DS;
+#endif
+}
+
+static inline int arch_dwarf_user_mode(struct unwind_state *info)
+{
+	return user_mode(&info->u.regs)
+#ifdef CONFIG_X86_64
+	       || (long)info->u.regs.ip >= 0
+	       || (info->u.regs.ip >= VSYSCALL_ADDR &&
+		   info->u.regs.ip < VSYSCALL_ADDR + PAGE_SIZE)
+	       || (long)info->u.regs.sp >= 0;
+#else
+	       || info->u.regs.ip < PAGE_OFFSET
+	       || info->u.regs.sp < PAGE_OFFSET;
+#endif
+}
+
+#endif /* CONFIG_DWARF_UNWIND */
+
+#endif /* _ASM_X86_DWARF_H */
diff --git a/include/linux/dwarf.h b/include/linux/dwarf.h
new file mode 100644
index 000000000000..2ec3ea4872f1
--- /dev/null
+++ b/include/linux/dwarf.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2002-2017 Novell, Inc.
+ *	Jan Beulich <jbeulich@...ell.com>
+ *	Jiri Slaby <jirislaby@...nel.org>
+ * This code is released under version 2 of the GNU GPL.
+ */
+
+#ifndef _LINUX_DWARF_H
+#define _LINUX_DWARF_H
+
+#include <linux/linkage.h>
+
+struct module;
+struct dwarf_table;
+
+#ifdef CONFIG_DWARF_UNWIND
+
+#include <asm/stacktrace.h>
+#include <asm/unwind.h>
+
+#ifndef ARCH_DWARF_SECTION_NAME
+#define ARCH_DWARF_SECTION_NAME ".eh_frame"
+#endif
+
+void dwarf_init(void);
+void dwarf_setup(void);
+
+#ifdef CONFIG_MODULES
+
+struct dwarf_table *dwarf_add_table(struct module *mod, const void *table_start,
+		unsigned long table_size);
+void dwarf_remove_table(struct dwarf_table *table, bool init_only);
+
+#endif /* CONFIG_MODULES */
+
+asmlinkage void arch_dwarf_init_running(struct pt_regs *regs);
+
+int dwarf_unwind(struct unwind_state *state);
+
+#else /* CONFIG_DWARF_UNWIND */
+
+static inline void dwarf_init(void) {}
+static inline void dwarf_setup(void) {}
+
+#ifdef CONFIG_MODULES
+
+static inline struct dwarf_table *dwarf_add_table(struct module *mod,
+		const void *table_start, unsigned long table_size)
+{
+	return NULL;
+}
+
+static inline void dwarf_remove_table(struct dwarf_table *table, bool init_only)
+{
+}
+
+#endif /* CONFIG_MODULES */
+
+#endif /* CONFIG_DWARF_UNWIND */
+
+#endif /* _LINUX_DWARF_H */
diff --git a/kernel/Makefile b/kernel/Makefile
index 72aa080f91f0..8fae6c1f5dd7 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_UID16) += uid16.o
 obj-$(CONFIG_MODULES) += module.o
 obj-$(CONFIG_MODULE_SIG) += module_signing.o
 obj-$(CONFIG_KALLSYMS) += kallsyms.o
+obj-$(CONFIG_DWARF_UNWIND) += dwarf.o
 obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o
 obj-$(CONFIG_CRASH_CORE) += crash_core.o
 obj-$(CONFIG_KEXEC_CORE) += kexec_core.o
diff --git a/kernel/dwarf.c b/kernel/dwarf.c
new file mode 100644
index 000000000000..ae8b38289034
--- /dev/null
+++ b/kernel/dwarf.c
@@ -0,0 +1,1802 @@
+/*
+ * Copyright (C) 2002-2017 Novell, Inc.
+ *	Jan Beulich <jbeulich@...ell.com>
+ *	Jiri Slaby <jirislaby@...nel.org>
+ * This code is released under version 2 of the GNU GPL.
+ *
+ * Simple DWARF unwinder for kernel stacks. This is used for debugging and
+ * error reporting purposes. The kernel does not need full-blown stack
+ * unwinding with all the bells and whistles, so there is not much point in
+ * implementing the full DWARF2 API.
+ */
+
+#include <linux/bootmem.h>
+#include <linux/dwarf.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/stop_machine.h>
+#include <linux/uaccess.h>
+
+#include <asm/sections.h>
+#include <asm/unaligned.h>
+
+/* this is just for measuring and debugging purposes */
+#if 0
+#define DW_NOINLINE noinline
+#else
+#define DW_NOINLINE
+#endif
+
+#define MAX_STACK_DEPTH 8
+
+#define PTREGS_INFO(f) { \
+		BUILD_BUG_ON_ZERO(offsetof(struct pt_regs, f) \
+				  % FIELD_SIZEOF(struct pt_regs, f)) \
+		+ offsetof(struct pt_regs, f) \
+		  / FIELD_SIZEOF(struct pt_regs, f), \
+		FIELD_SIZEOF(struct pt_regs, f) \
+	}
+#define FRAME_REG(f, r, t) (((t *)&(f)->u.regs_arr)[reg_info[r].offs])
+
+static const struct {
+	unsigned offs:BITS_PER_LONG / 2;
+	unsigned width:BITS_PER_LONG / 2;
+} reg_info[] = {
+	DW_REGISTER_INFO
+};
+
+#undef PTREGS_INFO
+
+#ifndef REG_INVALID
+#define REG_INVALID(r) (reg_info[r].width == 0)
+#endif
+
+#define DW_CFA_nop				0x00
+#define DW_CFA_set_loc				0x01
+#define DW_CFA_advance_loc1			0x02
+#define DW_CFA_advance_loc2			0x03
+#define DW_CFA_advance_loc4			0x04
+#define DW_CFA_offset_extended			0x05
+#define DW_CFA_restore_extended			0x06
+#define DW_CFA_undefined			0x07
+#define DW_CFA_same_value			0x08
+#define DW_CFA_register				0x09
+#define DW_CFA_remember_state			0x0a
+#define DW_CFA_restore_state			0x0b
+#define DW_CFA_def_cfa				0x0c
+#define DW_CFA_def_cfa_register			0x0d
+#define DW_CFA_def_cfa_offset			0x0e
+#define DW_CFA_def_cfa_expression		0x0f
+#define DW_CFA_expression			0x10
+#define DW_CFA_offset_extended_sf		0x11
+#define DW_CFA_def_cfa_sf			0x12
+#define DW_CFA_def_cfa_offset_sf		0x13
+#define DW_CFA_val_offset			0x14
+#define DW_CFA_val_offset_sf			0x15
+#define DW_CFA_val_expression			0x16
+#define DW_CFA_lo_user				0x1c
+#define DW_CFA_GNU_window_save			0x2d
+#define DW_CFA_GNU_args_size			0x2e
+#define DW_CFA_GNU_negative_offset_extended	0x2f
+#define DW_CFA_hi_user				0x3f
+
+#define DW_CFA_advance_loc			0x40
+#define DW_CFA_offset				0x80
+#define DW_CFA_restore				0xc0
+#define DW_CFA_ENCODED_MASK			0xc0
+#define DW_CFA_ENCODED_OP			(~DW_CFA_ENCODED_MASK)
+
+#define DW_EH_PE_FORM		0x07
+#define DW_EH_PE_native		0x00
+#define DW_EH_PE_leb128		0x01
+#define DW_EH_PE_data2		0x02
+#define DW_EH_PE_data4		0x03
+#define DW_EH_PE_data8		0x04
+#define DW_EH_PE_signed		0x08
+#define DW_EH_PE_ADJUST		0x70
+#define DW_EH_PE_abs		0x00
+#define DW_EH_PE_pcrel		0x10
+#define DW_EH_PE_textrel	0x20
+#define DW_EH_PE_datarel	0x30
+#define DW_EH_PE_funcrel	0x40
+#define DW_EH_PE_aligned	0x50
+#define DW_EH_PE_indirect	0x80
+#define DW_EH_PE_omit		0xff
+
+#define DW_OP_addr		0x03
+#define DW_OP_deref		0x06
+#define DW_OP_const1u		0x08
+#define DW_OP_const1s		0x09
+#define DW_OP_const2u		0x0a
+#define DW_OP_const2s		0x0b
+#define DW_OP_const4u		0x0c
+#define DW_OP_const4s		0x0d
+#define DW_OP_const8u		0x0e
+#define DW_OP_const8s		0x0f
+#define DW_OP_constu		0x10
+#define DW_OP_consts		0x11
+#define DW_OP_dup		0x12
+#define DW_OP_drop		0x13
+#define DW_OP_over		0x14
+#define DW_OP_pick		0x15
+#define DW_OP_swap		0x16
+#define DW_OP_rot		0x17
+#define DW_OP_xderef		0x18
+#define DW_OP_abs		0x19
+#define DW_OP_and		0x1a
+#define DW_OP_div		0x1b
+#define DW_OP_minus		0x1c
+#define DW_OP_mod		0x1d
+#define DW_OP_mul		0x1e
+#define DW_OP_neg		0x1f
+#define DW_OP_not		0x20
+#define DW_OP_or		0x21
+#define DW_OP_plus		0x22
+#define DW_OP_plus_uconst	0x23
+#define DW_OP_shl		0x24
+#define DW_OP_shr		0x25
+#define DW_OP_shra		0x26
+#define DW_OP_xor		0x27
+#define DW_OP_bra		0x28
+#define DW_OP_eq		0x29
+#define DW_OP_ge		0x2a
+#define DW_OP_gt		0x2b
+#define DW_OP_le		0x2c
+#define DW_OP_lt		0x2d
+#define DW_OP_ne		0x2e
+#define DW_OP_skip		0x2f
+#define DW_OP_lit0		0x30
+#define DW_OP_lit31		0x4f
+#define DW_OP_reg0		0x50
+#define DW_OP_reg31		0x6f
+#define DW_OP_breg0		0x70
+#define DW_OP_breg31		0x8f
+#define DW_OP_regx		0x90
+#define DW_OP_fbreg		0x91
+#define DW_OP_bregx		0x92
+#define DW_OP_piece		0x93
+#define DW_OP_deref_size	0x94
+#define DW_OP_xderef_size	0x95
+#define DW_OP_nop		0x96
+
+typedef unsigned long uleb128_t;
+typedef signed long sleb128_t;
+#define sleb128abs __builtin_labs
+
+static struct dwarf_table {
+	struct {
+		unsigned long pc;
+		unsigned long range;
+	} core, init;
+	const void *address;
+	unsigned long size;
+	const unsigned char *header;
+	unsigned long hdrsz;
+	struct dwarf_table *link;
+	const char *name;
+} root_table;
+
+union dw_uni_ptr {
+	const u8 *pu8;
+	const s8 *ps8;
+	const u16 *pu16;
+	const s16 *ps16;
+	const u32 *pu32;
+	const s32 *ps32;
+	const u64 *pu64;
+	const s64 *ps64;
+	const unsigned long *pul;
+};
+
+struct dw_unwind_state {
+	uleb128_t loc, orig_loc;
+
+	struct {
+		const u8 *ins_start, *ins_end;
+
+		uleb128_t code_align, ret_addr_reg;
+		sleb128_t data_align;
+		bool is_signal_frame, has_aug_data;
+		u8 ptr_type;
+	} cie;
+
+	struct {
+		unsigned long pc_begin, pc_end;
+		const u8 *ins_start, *ins_end;
+	} fde;
+
+	struct cfa {
+		uleb128_t reg, offs, elen;
+		const u8 *expr;
+	} cfa;
+	struct {
+		enum dw_item_location {
+			NOWHERE,
+			MEMORY,
+			REGISTER,
+			VALUE,
+		} where;
+		uleb128_t value;
+	} regs[ARRAY_SIZE(reg_info)];
+	const u8 *label;
+	const u8 *stack[MAX_STACK_DEPTH];
+	u8 stackDepth;
+};
+
+static const struct cfa badCFA = {
+	.reg = ARRAY_SIZE(reg_info),
+	.offs = 1,
+};
+
+static unsigned int unwind_debug;
+static int __init unwind_debug_setup(char *s)
+{
+	if (kstrtouint(s, 0, &unwind_debug))
+		return 1;
+	return 0;
+}
+early_param("unwind_debug", unwind_debug_setup);
+
+#define dprintk(lvl, fmt, args...) do {				\
+	if (unwind_debug >= lvl)					\
+		printk(KERN_DEBUG "unwind: " fmt "\n", ##args);	\
+} while (0)
+
+static struct dwarf_table *dw_find_table(unsigned long pc)
+{
+	struct dwarf_table *table;
+
+	for (table = &root_table; table; table = table->link)
+		if ((pc >= table->core.pc &&
+				 pc < table->core.pc + table->core.range) ||
+				(pc >= table->init.pc &&
+				 pc < table->init.pc + table->init.range))
+			break;
+
+	return table;
+}
+
+static unsigned long dw_read_pointer(const u8 **pLoc,
+				  const void *end,
+				  int ptrType,
+				  unsigned long data_base);
+
+static void __init_or_module
+init_unwind_table(struct dwarf_table *table, const char *name,
+		  const void *core_start, unsigned long core_size,
+		  const void *init_start, unsigned long init_size,
+		  const void *table_start, unsigned long table_size,
+		  const u8 *header_start, unsigned long header_size)
+{
+	const u8 *ptr = header_start + 4;
+	const u8 *end = header_start + header_size;
+
+	table->core.pc = (unsigned long)core_start;
+	table->core.range = core_size;
+	table->init.pc = (unsigned long)init_start;
+	table->init.range = init_size;
+	table->address = table_start;
+	table->size = table_size;
+
+	/* See if the linker provided table looks valid. */
+	if (header_size <= 4 || header_start[0] != 1 ||
+			/* ptr to eh_frame */
+			(void *)dw_read_pointer(&ptr, end, header_start[1], 0) !=
+				table_start ||
+			/* fde count */
+			!dw_read_pointer(&ptr, end, header_start[2], 0) ||
+			/* table[0] -- initial location */
+			!dw_read_pointer(&ptr, end, header_start[3],
+				(unsigned long)header_start) ||
+			/* table[0] -- address */
+			!dw_read_pointer(&ptr, end, header_start[3],
+				(unsigned long)header_start))
+		header_start = NULL;
+
+	table->hdrsz = header_size;
+	smp_wmb(); /* counterpart in dwarf_unwind */
+	table->header = header_start;
+	table->link = NULL;
+	table->name = name;
+}
+
+/*
+ * dwarf_init -- initialize unwind support
+ */
+void __init dwarf_init(void)
+{
+	extern const char __start_unwind[], __end_unwind[];
+	extern const char __start_unwind_hdr[], __end_unwind_hdr[];
+#ifdef CONFIG_DEBUG_RODATA
+	unsigned long text_len = _etext - _text;
+	const void *init_start = __init_begin;
+	unsigned long init_len = __init_end - __init_begin;
+#else
+	unsigned long text_len = _end - _text;
+	const void *init_start = NULL;
+	unsigned long init_len = 0;
+#endif
+	init_unwind_table(&root_table, "kernel", _text, text_len,
+			init_start, init_len,
+			__start_unwind, __end_unwind - __start_unwind,
+			__start_unwind_hdr,
+			__end_unwind_hdr - __start_unwind_hdr);
+}
+
+static const u32 *cie_for_fde(const u32 *fde, const struct dwarf_table *table,
+		unsigned long *start_loc, struct dw_unwind_state *state);
+
+struct eh_frame_hdr_table_entry {
+	unsigned long start, fde;
+};
+
+static int __init_or_module
+cmp_eh_frame_hdr_table_entries(const void *p1, const void *p2)
+{
+	const struct eh_frame_hdr_table_entry *e1 = p1;
+	const struct eh_frame_hdr_table_entry *e2 = p2;
+
+	return (e1->start > e2->start) - (e1->start < e2->start);
+}
+
+static void __init_or_module
+swap_eh_frame_hdr_table_entries(void *p1, void *p2, int size)
+{
+	struct eh_frame_hdr_table_entry *e1 = p1;
+	struct eh_frame_hdr_table_entry *e2 = p2;
+	unsigned long v;
+
+	v = e1->start;
+	e1->start = e2->start;
+	e2->start = v;
+	v = e1->fde;
+	e1->fde = e2->fde;
+	e2->fde = v;
+}
+
+static void __init_or_module
+setup_unwind_table(struct dwarf_table *table,
+		   void *(*alloc)(size_t, gfp_t))
+{
+	unsigned long tableSize = table->size, hdrSize, start_loc;
+	unsigned int n;
+	const u32 *fde;
+	struct {
+		u8 version;
+		u8 eh_frame_ptr_enc;
+		u8 fde_count_enc;
+		u8 table_enc;
+		unsigned long eh_frame_ptr;
+		unsigned int fde_count;
+		struct eh_frame_hdr_table_entry table[];
+	} __attribute__((__packed__)) *header;
+
+	if (table->header)
+		return;
+
+	if (table->hdrsz)
+		pr_warn(".eh_frame_hdr for '%s' present but unusable\n",
+			table->name);
+
+	if (tableSize & (sizeof(*fde) - 1))
+		return;
+
+	for (fde = table->address, n = 0;
+			tableSize > sizeof(*fde) &&
+			tableSize - sizeof(*fde) >= *fde;
+			tableSize -= sizeof(*fde) + *fde,
+			fde += 1 + *fde / sizeof(*fde)) {
+		const u32 *cie = cie_for_fde(fde, table, &start_loc, NULL);
+
+		if (IS_ERR(cie)) {
+			if (PTR_ERR(cie) == -ENODEV)
+				continue;
+			return;
+		}
+
+		if (!start_loc)
+			return;
+		++n;
+	}
+
+	if (tableSize || n < 32)
+		return;
+
+	hdrSize = sizeof(*header) + n * sizeof(*header->table);
+	dprintk(2, "Binary lookup table size for %s: %lu bytes", table->name,
+			hdrSize);
+	header = alloc(hdrSize, GFP_KERNEL);
+	if (!header)
+		return;
+	header->version	= 1;
+	header->eh_frame_ptr_enc = DW_EH_PE_abs | DW_EH_PE_native;
+	header->fde_count_enc = DW_EH_PE_abs | DW_EH_PE_data4;
+	header->table_enc = DW_EH_PE_abs | DW_EH_PE_native;
+	put_unaligned((unsigned long)table->address, &header->eh_frame_ptr);
+	BUILD_BUG_ON(offsetof(typeof(*header), fde_count) %
+			__alignof(typeof(header->fde_count)));
+	header->fde_count = n;
+
+	BUILD_BUG_ON(offsetof(typeof(*header), table) %
+			__alignof(typeof(*header->table)));
+	for (fde = table->address, tableSize = table->size, n = 0;
+			tableSize;
+			tableSize -= sizeof(*fde) + *fde,
+			fde += 1 + *fde / sizeof(*fde)) {
+		const u32 *cie = cie_for_fde(fde, table, &start_loc, NULL);
+		if (PTR_ERR(cie) == -ENODEV)
+			continue;
+
+		header->table[n].start = start_loc;
+		header->table[n].fde = (unsigned long)fde;
+		++n;
+	}
+	WARN_ON(n != header->fde_count);
+
+	sort(header->table, n, sizeof(*header->table),
+			cmp_eh_frame_hdr_table_entries,
+			swap_eh_frame_hdr_table_entries);
+
+	table->hdrsz = hdrSize;
+	smp_wmb(); /* counterpart in dwarf_unwind */
+	table->header = (const void *)header;
+}
+
+static void *__init balloc(size_t sz, gfp_t unused)
+{
+	return __alloc_bootmem_nopanic(sz, sizeof(unsigned long),
+			__pa(MAX_DMA_ADDRESS));
+}
+
+void __init dwarf_setup(void)
+{
+	setup_unwind_table(&root_table, balloc);
+}
+
+#ifdef CONFIG_MODULES
+
+static struct dwarf_table *last_table = &root_table;
+
+/*
+ * dwarf_add_table -- insert a dwarf table for a module
+ *
+ * Must be called with module_mutex held.
+ */
+struct dwarf_table *dwarf_add_table(struct module *module,
+		const void *table_start, unsigned long table_size)
+{
+	struct dwarf_table *table;
+#ifdef CONFIG_DEBUG_SET_MODULE_RONX
+	unsigned long core_size = module->core_layout.text_size;
+	unsigned long init_size = module->init_layout.text_size;
+#else
+	unsigned long core_size = module->core_layout.size;
+	unsigned long init_size = module->init_layout.size;
+#endif
+
+	if (table_size <= 0)
+		return NULL;
+
+	table = kmalloc(sizeof(*table), GFP_KERNEL);
+	if (!table)
+		return NULL;
+
+	init_unwind_table(table, module->name,
+			module->core_layout.base, core_size,
+			module->init_layout.base, init_size,
+			table_start, table_size, NULL, 0);
+	setup_unwind_table(table, kmalloc);
+
+	last_table->link = table;
+	last_table = table;
+
+	return table;
+}
+
+struct unlink_table_info {
+	struct dwarf_table *table;
+	bool init_only;
+};
+
+static int unlink_table(void *arg)
+{
+	struct unlink_table_info *info = arg;
+	struct dwarf_table *table = info->table, *prev;
+
+	for (prev = &root_table; prev->link && prev->link != table;
+			prev = prev->link)
+		;
+
+	if (prev->link) {
+		if (info->init_only) {
+			table->init.pc = 0;
+			table->init.range = 0;
+			info->table = NULL;
+		} else {
+			prev->link = table->link;
+			if (!prev->link)
+				last_table = prev;
+		}
+	} else
+		info->table = NULL;
+
+	return 0;
+}
+
+/*
+ * dwarf_remove_table -- remove a dwarf table for a module
+ *
+ * Must be called with module_mutex held.
+ */
+void dwarf_remove_table(struct dwarf_table *table, bool init_only)
+{
+	struct unlink_table_info info;
+
+	if (!table || table == &root_table)
+		return;
+
+	if (init_only && table == last_table) {
+		table->init.pc = 0;
+		table->init.range = 0;
+		return;
+	}
+
+	info.table = table;
+	info.init_only = init_only;
+	stop_machine(unlink_table, &info, NULL);
+
+	if (info.table) {
+		kfree(table->header);
+		kfree(table);
+	}
+}
+
+#endif /* CONFIG_MODULES */
+
+static uleb128_t get_uleb128(const u8 **pcur, const u8 *end)
+{
+	const u8 *cur = *pcur;
+	uleb128_t value;
+	unsigned int shift;
+
+	for (shift = 0, value = 0; cur < end; shift += 7) {
+		if (shift + 7 > 8 * sizeof(value)
+		    && (*cur & 0x7fU) >= (1U << (8 * sizeof(value) - shift))) {
+			cur = end + 1;
+			break;
+		}
+		value |= (uleb128_t)(*cur & 0x7f) << shift;
+		if (!(*cur++ & 0x80))
+			break;
+	}
+	*pcur = cur;
+
+	return value;
+}
+
+static sleb128_t get_sleb128(const u8 **pcur, const u8 *end)
+{
+	const u8 *cur = *pcur;
+	sleb128_t value;
+	unsigned int shift;
+
+	for (shift = 0, value = 0; cur < end; shift += 7) {
+		if (shift + 7 > 8 * sizeof(value)
+		    && (*cur & 0x7fU) >= (1U << (8 * sizeof(value) - shift))) {
+			cur = end + 1;
+			break;
+		}
+		value |= (sleb128_t)(*cur & 0x7f) << shift;
+		if (!(*cur & 0x80)) {
+			value |= -(*cur++ & 0x40) << shift;
+			break;
+		}
+	}
+	*pcur = cur;
+
+	return value;
+}
+
+static int dw_parse_cie(const u32 *cie, struct dw_unwind_state *state)
+{
+	const u8 *ptr = (const u8 *)(cie + 2); /* skip len+ID */
+	const u8 *end = (const u8 *)(cie + 1) + *cie, *aug_data_end;
+	const char *aug = NULL;
+	unsigned int version = *ptr++;
+	int ptr_type = DW_EH_PE_native | DW_EH_PE_abs;
+	uleb128_t code_align, ret_addr_reg, len;
+	sleb128_t data_align;
+	bool signal_frame = false;
+
+	if (version != 1)
+		return -1;
+
+	if (*ptr) {
+		aug = (const char *)ptr;
+
+		/* check if augmentation string is nul-terminated */
+		ptr = memchr(aug, 0, end - ptr);
+		if (ptr == NULL)
+			return -1;
+
+		/* check if augmentation size is first (and thus present) */
+		if (*aug != 'z')
+			return -1;
+	} else if (!state)
+		goto finish;
+
+	ptr++; /* skip terminator */
+	code_align = get_uleb128(&ptr, end);
+	data_align = get_sleb128(&ptr, end);
+	ret_addr_reg = version <= 1 ? *ptr++ : get_uleb128(&ptr, end);
+
+	if (aug) {
+		len = get_uleb128(&ptr, end); /* augmentation length */
+		if (ptr + len < ptr || ptr + len > end)
+			return -1;
+
+		aug_data_end = ptr + len;
+		while (*++aug) {
+			if (ptr >= aug_data_end)
+				return -1;
+			switch (*aug) {
+			case 'L':
+				++ptr;
+				break;
+			case 'P': {
+				int ptr_type = *ptr++;
+
+				if (!dw_read_pointer(&ptr, aug_data_end,
+							ptr_type, 0) ||
+						ptr > aug_data_end)
+					return -1;
+				break;
+			}
+			case 'R':
+				ptr_type = *ptr;
+				break;
+			case 'S':
+				signal_frame = true;
+				break;
+			default:
+				pr_info("%s: unhandled AUG %c\n", __func__,
+						*aug);
+				return -1;
+			}
+		}
+	} else
+		aug_data_end = ptr;
+
+	if (state) {
+		state->cie.ins_start = aug_data_end;
+		state->cie.ins_end = end;
+
+		state->cie.code_align = code_align;
+		state->cie.data_align = data_align;
+		state->cie.ret_addr_reg = ret_addr_reg;
+		state->cie.is_signal_frame = signal_frame;
+		state->cie.has_aug_data = aug;
+	}
+finish:
+	return ptr_type;
+}
+
+static const u32 *cie_for_fde(const u32 *fde, const struct dwarf_table *table,
+		unsigned long *start_loc, struct dw_unwind_state *state)
+{
+	const u32 *cie;
+	int ptr_type;
+
+	/* no len or odd len? */
+	if (!*fde || (*fde & (sizeof(*fde) - 1)))
+		return ERR_PTR(-EBADF);
+
+	/* a CIE? */
+	if (!fde[1])
+		return ERR_PTR(-ENODEV);
+
+	/* invalid pointer? */
+	if (fde[1] & (sizeof(*fde) - 1))
+		return ERR_PTR(-EINVAL);
+
+	/* out of range? */
+	if (fde[1] > (unsigned long)(fde + 1) - (unsigned long)table->address)
+		return ERR_PTR(-EINVAL);
+
+	cie = fde + 1 - fde[1] / sizeof(*fde);
+
+	/* invalid CIE? */
+	if (*cie <= sizeof(*cie) + 4 || *cie >= fde[1] - sizeof(*fde) ||
+			(*cie & (sizeof(*cie) - 1)) || cie[1])
+		return ERR_PTR(-EINVAL);
+
+	ptr_type = dw_parse_cie(cie, state);
+	if (ptr_type < 0)
+		return ERR_PTR(-EINVAL);
+
+	if (start_loc) {
+		const u8 *ptr = (const u8 *)(fde + 2);
+		const u8 *end = (const u8 *)(fde + 1) + *fde;
+		/* PC begin */
+		*start_loc = dw_read_pointer(&ptr, end, ptr_type, 0);
+
+		/* force absolute type for PC range */
+		if (!(ptr_type & DW_EH_PE_indirect))
+			ptr_type &= DW_EH_PE_FORM | DW_EH_PE_signed;
+
+		if (state) {
+			/* PC range */
+			state->fde.pc_end = *start_loc +
+				dw_read_pointer(&ptr, end, ptr_type, 0);
+
+			/* skip augmentation */
+			if (state->cie.has_aug_data) {
+				uleb128_t aug_len = get_uleb128(&ptr, end);
+
+				ptr += aug_len;
+				if (ptr > end)
+					return ERR_PTR(-EINVAL);
+			}
+
+			state->fde.ins_start = ptr;
+			state->fde.ins_end = end;
+			state->cie.ptr_type = ptr_type;
+		}
+	}
+
+	return cie;
+}
+
+static unsigned long dw_read_pointer(const u8 **pLoc, const void *end,
+		int ptrType, unsigned long data_base)
+{
+	unsigned long value = 0;
+	union dw_uni_ptr ptr;
+
+	if (ptrType < 0 || ptrType == DW_EH_PE_omit) {
+		dprintk(1, "Invalid pointer encoding %02X (%p,%p).", ptrType,
+				*pLoc, end);
+		return 0;
+	}
+	ptr.pu8 = *pLoc;
+	switch (ptrType & DW_EH_PE_FORM) {
+	case DW_EH_PE_data2:
+		if (end < (const void *)(ptr.pu16 + 1)) {
+			dprintk(1, "Data16 overrun (%p,%p).", ptr.pu8, end);
+			return 0;
+		}
+		if (ptrType & DW_EH_PE_signed)
+			value = get_unaligned(ptr.ps16++);
+		else
+			value = get_unaligned(ptr.pu16++);
+		break;
+	case DW_EH_PE_data4:
+#ifdef CONFIG_64BIT
+		if (end < (const void *)(ptr.pu32 + 1)) {
+			dprintk(1, "Data32 overrun (%p,%p).", ptr.pu8, end);
+			return 0;
+		}
+		if (ptrType & DW_EH_PE_signed)
+			value = get_unaligned(ptr.ps32++);
+		else
+			value = get_unaligned(ptr.pu32++);
+		break;
+	case DW_EH_PE_data8:
+		BUILD_BUG_ON(sizeof(u64) != sizeof(value));
+#else
+		BUILD_BUG_ON(sizeof(u32) != sizeof(value));
+#endif
+		/* fallthrough */
+	case DW_EH_PE_native:
+		if (end < (const void *)(ptr.pul + 1)) {
+			dprintk(1, "DataUL overrun (%p,%p).", ptr.pu8, end);
+			return 0;
+		}
+		value = get_unaligned(ptr.pul++);
+		break;
+	case DW_EH_PE_leb128:
+		BUILD_BUG_ON(sizeof(uleb128_t) > sizeof(value));
+		if (ptrType & DW_EH_PE_signed)
+			value = get_sleb128(&ptr.pu8, end);
+		else
+			value = get_uleb128(&ptr.pu8, end);
+		if ((const void *)ptr.pu8 > end) {
+			dprintk(1, "DataLEB overrun (%p,%p).", ptr.pu8, end);
+			return 0;
+		}
+		break;
+	default:
+		dprintk(2, "Cannot decode pointer type %02X (%p,%p).",
+				ptrType, ptr.pu8, end);
+		return 0;
+	}
+	switch (ptrType & DW_EH_PE_ADJUST) {
+	case DW_EH_PE_abs:
+		break;
+	case DW_EH_PE_pcrel:
+		value += (unsigned long)*pLoc;
+		break;
+	case DW_EH_PE_textrel:
+		dprintk(2, "Text-relative encoding %02X (%p,%p), but zero text base.",
+				ptrType, *pLoc, end);
+		return 0;
+	case DW_EH_PE_datarel:
+		if (likely(data_base)) {
+			value += data_base;
+			break;
+		}
+		dprintk(2, "Data-relative encoding %02X (%p,%p), but zero data base.",
+				ptrType, *pLoc, end);
+		return 0;
+	default:
+		dprintk(2, "Cannot adjust pointer type %02X (%p,%p).",
+				ptrType, *pLoc, end);
+		return 0;
+	}
+	if ((ptrType & DW_EH_PE_indirect)
+	    && probe_kernel_address((unsigned long *)value, value)) {
+		dprintk(1, "Cannot read indirect value %lx (%p,%p).",
+				value, *pLoc, end);
+		return 0;
+	}
+	*pLoc = ptr.pu8;
+
+	return value;
+}
+
+static bool dw_advance_loc(unsigned long delta, struct dw_unwind_state *state)
+{
+	state->loc += delta * state->cie.code_align;
+
+	return delta > 0;
+}
+
+static void dw_set_rule(uleb128_t reg, enum dw_item_location where,
+		uleb128_t value, struct dw_unwind_state *state)
+{
+	if (reg < ARRAY_SIZE(state->regs)) {
+		state->regs[reg].where = where;
+		state->regs[reg].value = value;
+	}
+}
+
+static bool dw_process_CFI_encoded(struct dw_unwind_state *state,
+		union dw_uni_ptr *ptr, const u8 *end, u8 inst)
+{
+	u8 op = inst & DW_CFA_ENCODED_OP;
+
+	switch (inst & DW_CFA_ENCODED_MASK) {
+	case DW_CFA_advance_loc:
+		return dw_advance_loc(op, state);
+	case DW_CFA_offset:
+		dw_set_rule(op, MEMORY, get_uleb128(&ptr->pu8, end), state);
+		break;
+	case DW_CFA_restore:
+		dw_set_rule(op, NOWHERE, 0, state);
+		break;
+	}
+
+	return true;
+}
+
+static bool dw_process_CFIs(struct dw_unwind_state *state, unsigned long pc);
+
+static int dw_process_CFI_normal(struct dw_unwind_state *state,
+		union dw_uni_ptr *ptr, const u8 *end, u8 inst)
+{
+	uleb128_t value;
+	bool ok = true;
+
+	switch (inst) {
+	case DW_CFA_nop:
+		break;
+	case DW_CFA_set_loc:
+		state->loc = dw_read_pointer(&ptr->pu8, end,
+				state->cie.ptr_type, 0);
+		if (state->loc == 0)
+			return false;
+		break;
+	case DW_CFA_advance_loc1:
+		return ptr->pu8 < end && dw_advance_loc(*ptr->pu8++, state);
+	case DW_CFA_advance_loc2:
+		return ptr->pu8 <= end + 2 &&
+			dw_advance_loc(*ptr->pu16++, state);
+	case DW_CFA_advance_loc4:
+		return ptr->pu8 <= end + 4 &&
+			dw_advance_loc(*ptr->pu32++, state);
+	case DW_CFA_offset_extended:
+		value = get_uleb128(&ptr->pu8, end);
+		dw_set_rule(value, MEMORY, get_uleb128(&ptr->pu8, end), state);
+		break;
+	case DW_CFA_val_offset:
+		value = get_uleb128(&ptr->pu8, end);
+		dw_set_rule(value, VALUE, get_uleb128(&ptr->pu8, end), state);
+		break;
+	case DW_CFA_offset_extended_sf:
+		value = get_uleb128(&ptr->pu8, end);
+		dw_set_rule(value, MEMORY, get_sleb128(&ptr->pu8, end), state);
+		break;
+	case DW_CFA_val_offset_sf:
+		value = get_uleb128(&ptr->pu8, end);
+		dw_set_rule(value, VALUE, get_sleb128(&ptr->pu8, end), state);
+		break;
+	/*todo case DW_CFA_expression: */
+	/*todo case DW_CFA_val_expression: */
+	case DW_CFA_restore_extended:
+	case DW_CFA_undefined:
+	case DW_CFA_same_value:
+		dw_set_rule(get_uleb128(&ptr->pu8, end), NOWHERE, 0, state);
+		break;
+	case DW_CFA_register:
+		value = get_uleb128(&ptr->pu8, end);
+		dw_set_rule(value, REGISTER, get_uleb128(&ptr->pu8, end),
+				state);
+		break;
+	case DW_CFA_remember_state:
+		if (ptr->pu8 == state->label) {
+			state->label = NULL;
+			/* required state */
+			return 2;
+		}
+		if (state->stackDepth >= MAX_STACK_DEPTH) {
+			dprintk(1, "State stack overflow (%p, %p).", ptr->pu8,
+					end);
+			return false;
+		}
+		state->stack[state->stackDepth++] = ptr->pu8;
+		break;
+	case DW_CFA_restore_state: {
+		const uleb128_t loc = state->loc;
+		const u8 *label = state->label;
+
+		if (!state->stackDepth) {
+			dprintk(1, "State stack underflow (%p, %p).", ptr->pu8,
+					end);
+			return false;
+		}
+
+		state->label = state->stack[state->stackDepth - 1];
+		memcpy(&state->cfa, &badCFA, sizeof(badCFA));
+		memset(state->regs, 0, sizeof(state->regs));
+		state->stackDepth = 0;
+		ok = dw_process_CFIs(state, 0);
+		state->loc = loc;
+		state->label = label;
+
+		break;
+	}
+	case DW_CFA_def_cfa:
+		state->cfa.reg = get_uleb128(&ptr->pu8, end);
+		state->cfa.elen = 0;
+		/* fallthrough */
+	case DW_CFA_def_cfa_offset:
+		state->cfa.offs = get_uleb128(&ptr->pu8, end);
+		break;
+	case DW_CFA_def_cfa_sf:
+		state->cfa.reg = get_uleb128(&ptr->pu8, end);
+		state->cfa.elen = 0;
+		/* fallthrough */
+	case DW_CFA_def_cfa_offset_sf:
+		state->cfa.offs = get_sleb128(&ptr->pu8, end) *
+			state->cie.data_align;
+		break;
+	case DW_CFA_def_cfa_register:
+		state->cfa.reg = get_uleb128(&ptr->pu8, end);
+		state->cfa.elen = 0;
+		break;
+	case DW_CFA_def_cfa_expression:
+		state->cfa.elen = get_uleb128(&ptr->pu8, end);
+		if (!state->cfa.elen) {
+			dprintk(1, "Zero-length CFA expression.");
+			return false;
+		}
+		state->cfa.expr = ptr->pu8;
+		ptr->pu8 += state->cfa.elen;
+		break;
+	case DW_CFA_GNU_args_size:
+		get_uleb128(&ptr->pu8, end);
+		break;
+	case DW_CFA_GNU_negative_offset_extended:
+		value = get_uleb128(&ptr->pu8, end);
+		dw_set_rule(value, MEMORY,
+				(uleb128_t)0 - get_uleb128(&ptr->pu8, end),
+				state);
+		break;
+	case DW_CFA_GNU_window_save:
+	default:
+		dprintk(1, "Unrecognized CFI op %02X (%p, %p).", ptr->pu8[-1],
+				ptr->pu8 - 1, end);
+		return false;
+	}
+
+	return ok;
+}
+
+static bool dw_process_CFI(struct dw_unwind_state *state, const u8 *start,
+		const u8 *end, unsigned long pc)
+{
+	union dw_uni_ptr ptr;
+	int res;
+
+	dprintk(4, "processing CFI: %p-%p", start, end);
+
+	for (ptr.pu8 = start; ptr.pu8 < end; ) {
+		u8 inst = *ptr.pu8++;
+
+		if (inst & DW_CFA_ENCODED_MASK)
+			res = dw_process_CFI_encoded(state, &ptr, end, inst);
+		else {
+			res = dw_process_CFI_normal(state, &ptr, end, inst);
+			if (res == 2)
+				return true;
+		}
+
+		if (!res)
+			return false;
+
+		if (ptr.pu8 > end) {
+			dprintk(1, "Data overrun (%p, %p).", ptr.pu8, end);
+			return false;
+		}
+		if (pc != 0 && pc < state->loc)
+			return true;
+	}
+
+	if (ptr.pu8 < end) {
+		dprintk(1, "Data underrun (%p, %p).", ptr.pu8, end);
+		return false;
+	}
+
+	if (ptr.pu8 != end) {
+		pr_err("%s: Data mismatch (%p, %p).\n", __func__, ptr.pu8, end);
+		return false;
+	}
+
+	return (pc == 0 ||
+		(/*
+		  * TODO While in theory this should apply, gcc in practice
+		  * omits everything past the function prolog, and hence the
+		  * location never reaches the end of the function.
+		  *    pc < state->loc &&
+		  */
+		 state->label == NULL));
+}
+
+static bool dw_process_CFIs(struct dw_unwind_state *state, unsigned long pc)
+{
+	bool ok = true;
+
+	state->loc = state->orig_loc;
+
+	ok = dw_process_CFI(state, state->cie.ins_start, state->cie.ins_end, 0);
+	if (pc == 0 && state->label == NULL)
+		return ok;
+	if (!ok)
+		return false;
+
+	return dw_process_CFI(state, state->fde.ins_start, state->fde.ins_end,
+			pc);
+}
+
+static unsigned long dw_evaluate_cfa(const u8 *expr, const u8 *end,
+			      const struct unwind_state *frame)
+{
+	union dw_uni_ptr ptr = { expr };
+	unsigned long stack[8], val1, val2;
+	unsigned int stidx = 0;
+	int ret;
+
+#define PUSH(v) do {							\
+	unsigned long v__ = (v);					\
+									\
+	if (stidx >= ARRAY_SIZE(stack))					\
+		return 0;						\
+	stack[stidx++] = v__;						\
+} while (0)
+
+#define POP() ({							\
+	if (!stidx)							\
+		return 0;						\
+	stack[--stidx];							\
+})
+
+	while (ptr.pu8 < end) {
+		switch (*ptr.pu8++) {
+		/*todo case DW_OP_addr: */
+		case DW_OP_deref:
+			val1 = POP();
+			ret = probe_kernel_address((unsigned long *)val1, val2);
+			if (ret) {
+				dprintk(1, "Cannot de-reference %lx (%p,%p).",
+						val1, ptr.pu8 - 1, end);
+				return 0;
+			}
+			PUSH(val2);
+			break;
+		/*todo? case DW_OP_xderef: */
+		/*todo case DW_OP_deref_size: */
+		/*todo? case DW_OP_xderef_size: */
+		case DW_OP_const1u:
+			if (ptr.pu8 < end)
+				PUSH(*ptr.pu8);
+			++ptr.pu8;
+			break;
+		case DW_OP_const1s:
+			if (ptr.pu8 < end)
+				PUSH(*ptr.ps8);
+			++ptr.ps8;
+			break;
+		case DW_OP_const2u:
+			if (ptr.pu8 + 1 < end)
+				PUSH(*ptr.pu16);
+			++ptr.pu16;
+			break;
+		case DW_OP_const2s:
+			if (ptr.pu8 + 1 < end)
+				PUSH(*ptr.ps16);
+			++ptr.ps16;
+			break;
+		case DW_OP_const4u:
+			if (ptr.pu8 + 3 < end)
+				PUSH(*ptr.pu32);
+			++ptr.pu32;
+			break;
+		case DW_OP_const4s:
+			if (ptr.pu8 + 3 < end)
+				PUSH(*ptr.ps32);
+			++ptr.ps32;
+			break;
+		case DW_OP_const8u:
+			if (ptr.pu8 + 7 < end)
+				PUSH(*ptr.pu64);
+			++ptr.pu64;
+			break;
+		case DW_OP_const8s:
+			if (ptr.pu8 + 7 < end)
+				PUSH(*ptr.ps64);
+			++ptr.ps64;
+			break;
+		case DW_OP_constu:
+			PUSH(get_uleb128(&ptr.pu8, end));
+			break;
+		case DW_OP_consts:
+			PUSH(get_sleb128(&ptr.pu8, end));
+			break;
+		case DW_OP_dup:
+			if (!stidx)
+				return 0;
+			PUSH(stack[stidx - 1]);
+			break;
+		case DW_OP_drop:
+			(void)POP();
+			break;
+		case DW_OP_over:
+			if (stidx <= 1)
+				return 0;
+			PUSH(stack[stidx - 2]);
+			break;
+		case DW_OP_pick:
+			if (ptr.pu8 < end) {
+				if (stidx <= *ptr.pu8)
+					return 0;
+				PUSH(stack[stidx - *ptr.pu8 - 1]);
+			}
+			++ptr.pu8;
+			break;
+		case DW_OP_swap:
+			if (stidx <= 1)
+				return 0;
+			val1 = stack[stidx - 1];
+			stack[stidx - 1] = stack[stidx - 2];
+			stack[stidx - 2] = val1;
+			break;
+		case DW_OP_rot:
+			if (stidx <= 2)
+				return 0;
+			val1 = stack[stidx - 1];
+			stack[stidx - 1] = stack[stidx - 2];
+			stack[stidx - 2] = stack[stidx - 3];
+			stack[stidx - 3] = val1;
+			break;
+		case DW_OP_abs:
+			PUSH(__builtin_labs(POP()));
+			break;
+		case DW_OP_and:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 & val1);
+			break;
+		case DW_OP_div:
+			val1 = POP();
+			if (!val1)
+				return 0;
+			val2 = POP();
+			PUSH(val2 / val1);
+			break;
+		case DW_OP_minus:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 - val1);
+			break;
+		case DW_OP_mod:
+			val1 = POP();
+			if (!val1)
+				return 0;
+			val2 = POP();
+			PUSH(val2 % val1);
+			break;
+		case DW_OP_mul:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 * val1);
+			break;
+		case DW_OP_neg:
+			PUSH(-(long)POP());
+			break;
+		case DW_OP_not:
+			PUSH(~POP());
+			break;
+		case DW_OP_or:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 | val1);
+			break;
+		case DW_OP_plus:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 + val1);
+			break;
+		case DW_OP_plus_uconst:
+			PUSH(POP() + get_uleb128(&ptr.pu8, end));
+			break;
+		case DW_OP_shl:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val1 < BITS_PER_LONG ? val2 << val1 : 0);
+			break;
+		case DW_OP_shr:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val1 < BITS_PER_LONG ? val2 >> val1 : 0);
+			break;
+		case DW_OP_shra:
+			val1 = POP();
+			val2 = POP();
+			if (val1 < BITS_PER_LONG)
+				PUSH((long)val2 >> val1);
+			else
+				PUSH((long)val2 < 0 ? -1 : 0);
+			break;
+		case DW_OP_xor:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 ^ val1);
+			break;
+		case DW_OP_bra:
+			if (!POP()) {
+				++ptr.ps16;
+				break;
+			}
+			/* fallthrough */
+		case DW_OP_skip:
+			if (ptr.pu8 + 1 < end) {
+				ptr.pu8 += *ptr.ps16;
+				if (ptr.pu8 < expr)
+					return 0;
+			} else
+				++ptr.ps16;
+			break;
+		case DW_OP_eq:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 == val1);
+			break;
+		case DW_OP_ne:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 != val1);
+			break;
+		case DW_OP_lt:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 < val1);
+			break;
+		case DW_OP_le:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 <= val1);
+			break;
+		case DW_OP_ge:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 >= val1);
+			break;
+		case DW_OP_gt:
+			val1 = POP();
+			val2 = POP();
+			PUSH(val2 > val1);
+			break;
+		case DW_OP_lit0 ... DW_OP_lit31:
+			PUSH(ptr.pu8[-1] - DW_OP_lit0);
+			break;
+		case DW_OP_breg0 ... DW_OP_breg31:
+			val1 = ptr.pu8[-1] - DW_OP_breg0;
+			if (0)
+				/* fallthrough */
+		case DW_OP_bregx:
+			val1 = get_uleb128(&ptr.pu8, end);
+			if (val1 >= ARRAY_SIZE(reg_info) ||
+					reg_info[val1].width != sizeof(unsigned long))
+				return 0;
+
+			PUSH(FRAME_REG(frame, val1, unsigned long)
+					+ get_sleb128(&ptr.pu8, end));
+			break;
+		/*todo? case DW_OP_fbreg: */
+		/*todo? case DW_OP_piece: */
+		case DW_OP_nop:
+			break;
+		default:
+			dprintk(1, "Unsupported expression op %02x (%p,%p).",
+					ptr.pu8[-1], ptr.pu8 - 1, end);
+			return 0;
+		}
+	}
+	if (ptr.pu8 > end)
+		return 0;
+	val1 = POP();
+#undef POP
+#undef PUSH
+	return val1;
+}
+
+static unsigned long dw_get_cfa(const struct unwind_state *frame,
+		const struct dw_unwind_state *state)
+{
+	if (state->cfa.elen) {
+		unsigned long cfa = dw_evaluate_cfa(state->cfa.expr,
+				state->cfa.expr + state->cfa.elen, frame);
+		if (!cfa)
+			dprintk(1, "Bad CFA expr (%p:%lu).", state->cfa.expr,
+					state->cfa.elen);
+
+		return cfa;
+	}
+
+	if (state->cfa.reg >= ARRAY_SIZE(reg_info) ||
+			reg_info[state->cfa.reg].width !=
+					sizeof(unsigned long) ||
+			FRAME_REG(frame, state->cfa.reg, unsigned long) %
+					sizeof(unsigned long) ||
+			state->cfa.offs % sizeof(unsigned long)) {
+		dprintk(1, "Bad CFA (%lu, %lx).", state->cfa.reg,
+				state->cfa.offs);
+		return 0;
+	}
+
+	return FRAME_REG(frame, state->cfa.reg, unsigned long) +
+		state->cfa.offs;
+}
+
+static int dw_update_frame(struct unwind_state *frame,
+		struct dw_unwind_state *state)
+{
+	unsigned long start_loc, end_loc, sp, pc, cfa;
+	unsigned int i;
+	int ret;
+
+	cfa = dw_get_cfa(frame, state);
+	if (!cfa)
+		return -EIO;
+
+#ifndef CONFIG_AS_CFI_SIGNAL_FRAME
+	if (frame->call_frame &&
+			!UNW_DEFAULT_RA(state->regs[state->cie.ret_addr_reg],
+					state->cie.data_align))
+		frame->call_frame = 0;
+#endif
+	start_loc = min_t(unsigned long, DW_SP(frame), cfa);
+	end_loc = max_t(unsigned long, DW_SP(frame), cfa);
+	if (STACK_LIMIT(start_loc) != STACK_LIMIT(end_loc)) {
+		start_loc = min(STACK_LIMIT(cfa), cfa);
+		end_loc = max(STACK_LIMIT(cfa), cfa);
+	}
+
+#ifdef CONFIG_64BIT
+# define CASES CASE(8); CASE(16); CASE(32); CASE(64)
+#else
+# define CASES CASE(8); CASE(16); CASE(32)
+#endif
+	pc = DW_PC(frame);
+	sp = DW_SP(frame);
+
+	/* 1st step: store the computed register values */
+	for (i = 0; i < ARRAY_SIZE(state->regs); ++i) {
+		if (REG_INVALID(i)) {
+			if (state->regs[i].where == NOWHERE)
+				continue;
+			dprintk(1, "Cannot restore register %u (%d).", i,
+					state->regs[i].where);
+			return -EIO;
+		}
+
+		if (state->regs[i].where != REGISTER)
+			continue;
+
+		if (state->regs[i].value >= ARRAY_SIZE(reg_info) ||
+				REG_INVALID(state->regs[i].value) ||
+				reg_info[i].width > reg_info[state->regs[i].value].width) {
+			dprintk(1, "Cannot restore register %u from register %lu.",
+					i, state->regs[i].value);
+			return -EIO;
+		}
+
+		switch (reg_info[state->regs[i].value].width) {
+#define CASE(n)	case sizeof(u##n):					\
+			state->regs[i].value = FRAME_REG(frame, 	\
+					state->regs[i].value, const u##n); \
+			break
+		CASES;
+#undef CASE
+		default:
+			dprintk(1, "Unsupported register size %u (%lu).",
+					reg_info[state->regs[i].value].width,
+					state->regs[i].value);
+			return -EIO;
+		}
+	}
+
+	/* 2nd step: update the state, incl. registers */
+	for (i = 0; i < ARRAY_SIZE(state->regs); ++i) {
+		if (REG_INVALID(i))
+			continue;
+		switch (state->regs[i].where) {
+		case NOWHERE:
+			if (reg_info[i].width != sizeof(DW_SP(frame))
+			    || &FRAME_REG(frame, i, __typeof__(DW_SP(frame)))
+			       != &DW_SP(frame))
+				continue;
+			DW_SP(frame) = cfa;
+			break;
+		case REGISTER:
+			switch (reg_info[i].width) {
+#define CASE(n)		case sizeof(u##n):				\
+				FRAME_REG(frame, i, u##n) =		\
+					state->regs[i].value;		\
+				break
+			CASES;
+#undef CASE
+			default:
+				dprintk(1, "Unsupported register size %u (%u).",
+						reg_info[i].width, i);
+				return -EIO;
+			}
+			break;
+		case VALUE:
+			if (reg_info[i].width != sizeof(unsigned long)) {
+				dprintk(1, "Unsupported value size %u (%u).",
+						reg_info[i].width, i);
+				return -EIO;
+			}
+			FRAME_REG(frame, i, unsigned long) = cfa +
+				state->regs[i].value * state->cie.data_align;
+			break;
+		case MEMORY: {
+			unsigned long off = state->regs[i].value *
+				state->cie.data_align;
+			unsigned long addr = cfa + off;
+
+			if (off % sizeof(unsigned long) ||
+					addr < start_loc ||
+					addr + sizeof(unsigned long) < addr ||
+					addr + sizeof(unsigned long) > end_loc) {
+				dprintk(1, "Bad memory location %lx (%lx).",
+					addr, state->regs[i].value);
+				return -EIO;
+			}
+
+			switch (reg_info[i].width) {
+#define CASE(n)		case sizeof(u##n):				\
+				ret = probe_kernel_address((void *)addr, \
+						FRAME_REG(frame, i, u##n)); \
+				if (ret)				\
+					return -EFAULT;			\
+				break
+			CASES;
+#undef CASE
+			default:
+				dprintk(1, "Unsupported memory size %u (%u).",
+					reg_info[i].width, i);
+				return -EIO;
+			}
+			break;
+		}
+		}
+	}
+
+	if (DW_PC(frame) % state->cie.code_align ||
+			DW_SP(frame) % sleb128abs(state->cie.data_align)) {
+		dprintk(1, "Output pointer(s) misaligned (%lx,%lx).",
+				DW_PC(frame), DW_SP(frame));
+		return -EIO;
+	}
+
+	if (pc == DW_PC(frame) && sp == DW_SP(frame)) {
+		dprintk(1, "No progress (%lx,%lx).", pc, sp);
+		return -EIO;
+	}
+
+	return 0;
+#undef CASES
+}
+
+static DW_NOINLINE int
+dw_lookup_fde_binary(struct unwind_state *frame,
+		const struct dwarf_table *table, unsigned long pc,
+		struct dw_unwind_state *state)
+{
+	unsigned long tableSize, pc_begin = 0;
+	unsigned int fde_cnt;
+	const u32 *fde = NULL, *cie;
+	const u8 *hdr = table->header;
+	const u8 *ptr, *end;
+
+	/* version */
+	if (hdr[0] != 1)
+		goto failed;
+
+	/* table_enc */
+	switch (hdr[3] & DW_EH_PE_FORM) {
+	case DW_EH_PE_native:
+		tableSize = sizeof(unsigned long);
+		break;
+	case DW_EH_PE_data2:
+		tableSize = 2;
+		break;
+	case DW_EH_PE_data4:
+		tableSize = 4;
+		break;
+	case DW_EH_PE_data8:
+		tableSize = 8;
+		break;
+	default:
+		goto failed;
+	}
+
+	ptr = hdr + 4;
+	end = hdr + table->hdrsz;
+
+	/* eh_frame_ptr */
+	if (dw_read_pointer(&ptr, end, hdr[1], 0) !=
+			(unsigned long)table->address)
+		goto failed;
+
+	/* fde_cnt */
+	fde_cnt = dw_read_pointer(&ptr, end, hdr[2], 0);
+	if (fde_cnt == 0 || fde_cnt != (end - ptr) / (2 * tableSize))
+		goto failed;
+
+	if ((end - ptr) % (2 * tableSize))
+		goto failed;
+
+	/* binary search in fde_cnt entries */
+	do {
+		const u8 *cur = ptr + (fde_cnt / 2) * (2 * tableSize);
+
+		/* location */
+		pc_begin = dw_read_pointer(&cur, cur + tableSize, hdr[3],
+				(unsigned long)hdr);
+		if (pc < pc_begin)
+			fde_cnt /= 2;
+		else {
+			ptr = cur - tableSize;
+			fde_cnt = (fde_cnt + 1) / 2;
+		}
+	} while (pc_begin && fde_cnt > 1);
+
+	if (pc_begin == 0 || fde_cnt != 1)
+		goto failed;
+
+	/* read the bisected one -- location... */
+	pc_begin = dw_read_pointer(&ptr, ptr + tableSize, hdr[3],
+			(unsigned long)hdr);
+	if (pc_begin == 0 || pc < pc_begin)
+		goto failed;
+
+	/* ...and its corresponding fde */
+	fde = (void *)dw_read_pointer(&ptr, ptr + tableSize, hdr[3],
+			(unsigned long)hdr);
+	if (!fde)
+		goto failed;
+
+	dprintk(4, "have fde: %zx at %p", (void *)fde - table->address, fde);
+
+	/* now find a cie for the fde */
+
+	cie = cie_for_fde(fde, table, &state->fde.pc_begin, state);
+	if (IS_ERR(cie) || state->fde.pc_begin != pc_begin ||
+			pc >= state->fde.pc_end)
+		goto discard;
+
+	dprintk(4, "still have fde at %p", fde);
+
+	return 0;
+discard:
+	dprintk(1, "Binary lookup result for %lx discarded.", pc);
+	return -EIO;
+failed:
+	if (hdr)
+		dprintk(3, "Binary lookup for %lx failed.", pc);
+	return -EIO;
+}
+
+static DW_NOINLINE int
+dw_lookup_fde_linear(struct unwind_state *frame,
+		const struct dwarf_table *table, unsigned long pc,
+		struct dw_unwind_state *state)
+{
+	unsigned long tableSize = table->size;
+	const u32 *fde;
+
+	for (fde = table->address; tableSize > sizeof(*fde) &&
+			tableSize - sizeof(*fde) >= *fde;
+			tableSize -= sizeof(*fde) + *fde,
+			fde += 1 + *fde / sizeof(*fde)) {
+		const u32 *cie = cie_for_fde(fde, table, &state->fde.pc_begin,
+				state);
+		if (IS_ERR(cie)) {
+			if (PTR_ERR(cie) == -EBADF)
+				break;
+			continue;
+		}
+
+		if (!state->fde.pc_begin)
+			continue;
+
+		if (pc >= state->fde.pc_begin && pc < state->fde.pc_end)
+			return 0;
+	}
+
+	dprintk(3, "Linear lookup for %lx failed.", pc);
+
+	return -ENOENT;
+}
+
+
+#ifdef CONFIG_FRAME_POINTER
+static DW_NOINLINE int dw_fp_fallback(struct unwind_state *frame)
+{
+	unsigned long top = TSK_STACK_TOP(frame->task);
+	unsigned long bottom = STACK_BOTTOM(frame->task);
+	unsigned long fp = DW_FP(frame);
+	unsigned long sp = DW_SP(frame);
+	unsigned long link;
+	int ret;
+
+	if ((sp | fp) & (sizeof(unsigned long) - 1))
+		return -EPERM;
+
+#if FRAME_RETADDR_OFFSET < 0
+	if (!(sp < top && fp <= sp && bottom < fp))
+#else
+	if (!(sp > top && fp >= sp && bottom > fp))
+#endif
+		return -ENXIO;
+
+	ret = probe_kernel_address((unsigned long *)(fp + FRAME_LINK_OFFSET),
+			link);
+	if (ret)
+		return -ENXIO;
+
+#if FRAME_RETADDR_OFFSET < 0
+	if (!(link > bottom && link < fp))
+#else
+	if (!(link < bottom && link > fp))
+#endif
+		return -ENXIO;
+
+	if (link & (sizeof(link) - 1))
+		return -ENXIO;
+
+	fp += FRAME_RETADDR_OFFSET;
+	ret = probe_kernel_address((unsigned long *)fp, DW_PC(frame));
+	if (ret)
+		return -ENXIO;
+	pr_info("%s: read fp=%lx into PC=%lx\n", __func__,
+			fp, DW_PC(frame));
+
+	/* Ok, we can use it */
+#if FRAME_RETADDR_OFFSET < 0
+	DW_SP(frame) = fp - sizeof(DW_PC(frame));
+#else
+	DW_SP(frame) = fp + sizeof(DW_PC(frame));
+#endif
+	DW_FP(frame) = link;
+	return 0;
+}
+#else
+static inline int dw_fp_fallback(struct unwind_state *frame)
+{
+	return -ENXIO;
+}
+#endif
+
+/*
+ * dwarf_unwind -- unwind to previous to frame
+ *
+ * Returns 0 if successful, negative number in case of error.
+ */
+int dwarf_unwind(struct unwind_state *frame)
+{
+	struct dw_unwind_state state = {
+		.cie.ptr_type = -1,
+		.cfa = badCFA,
+	};
+	const struct dwarf_table *table;
+	unsigned long pc = DW_PC(frame) - frame->call_frame;
+	int ret;
+
+	if (DW_PC(frame) == 0)
+		return -EINVAL;
+
+	table = dw_find_table(pc);
+	if (table == NULL || (table->size & (sizeof(u32) - 1)))
+		goto fallback;
+
+	/* counterpart in setup_unwind_table, to protect table->header */
+	smp_rmb();
+
+	dprintk(4, "have table: %lx-%lx (%s) hdr=%p addr=%p for %pS",
+			table->core.pc,
+			table->core.pc + table->core.range - 1,
+			table->name,
+			table->header, table->address, (void *)pc);
+
+	if (table->header)
+		ret = dw_lookup_fde_binary(frame, table, pc, &state);
+	else
+		ret = dw_lookup_fde_linear(frame, table, pc, &state);
+
+	if (ret)
+		goto fallback;
+
+	if (state.cie.code_align == 0 || state.cie.data_align == 0)
+		goto fallback;
+
+	if (DW_PC(frame) % state.cie.code_align ||
+			DW_SP(frame) % sleb128abs(state.cie.data_align)) {
+		dprintk(1, "Input pointer(s) misaligned (%lx, %lx).",
+				DW_PC(frame), DW_SP(frame));
+		return -EPERM;
+	}
+
+	if (state.cie.ret_addr_reg >= ARRAY_SIZE(reg_info) ||
+			REG_INVALID(state.cie.ret_addr_reg) ||
+			reg_info[state.cie.ret_addr_reg].width !=
+				sizeof(unsigned long)) {
+		dprintk(1, "CIE validation failed (ret addr reg).");
+		goto fallback;
+	}
+
+	frame->call_frame = !state.cie.is_signal_frame;
+	state.orig_loc = state.fde.pc_begin;
+
+	/* process instructions */
+	ret = dw_process_CFIs(&state, pc);
+	if (!ret || state.loc > state.fde.pc_end ||
+			state.regs[state.cie.ret_addr_reg].where == NOWHERE) {
+		dprintk(1, "Unusable unwind info (%p, %p).",
+				state.fde.ins_start, state.fde.ins_end);
+		return -EIO;
+	}
+
+	ret = dw_update_frame(frame, &state);
+	if (ret)
+		return ret;
+
+	return 0;
+fallback:
+	return dw_fp_fallback(frame);
+}
+EXPORT_SYMBOL_GPL(dwarf_unwind);
-- 
2.12.2

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ