Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751769AbdF1PNh (ORCPT ); Wed, 28 Jun 2017 11:13:37 -0400 Received: from mx1.redhat.com ([209.132.183.28]:50112 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751795AbdF1PMG (ORCPT ); Wed, 28 Jun 2017 11:12:06 -0400 DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com B9DF57A162 Authentication-Results: ext-mx04.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx04.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=jpoimboe@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com B9DF57A162 From: Josh Poimboeuf To: x86@kernel.org Cc: linux-kernel@vger.kernel.org, live-patching@vger.kernel.org, Linus Torvalds , Andy Lutomirski , Jiri Slaby , Ingo Molnar , "H. Peter Anvin" , Peter Zijlstra Subject: [PATCH v2 8/8] x86/unwind: add undwarf unwinder Date: Wed, 28 Jun 2017 10:11:12 -0500 Message-Id: In-Reply-To: References: X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.28]); Wed, 28 Jun 2017 15:12:06 +0000 (UTC) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 39331 Lines: 1265 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. Thanks to Andy Lutomirski for the performance improvement ideas: splitting the undwarf table into two parallel arrays and creating a fast lookup table to search a subset of the undwarf table. Signed-off-by: Josh Poimboeuf --- Documentation/x86/undwarf.txt | 146 ++++++++++ arch/um/include/asm/unwind.h | 8 + arch/x86/Kconfig | 1 + arch/x86/Kconfig.debug | 25 ++ arch/x86/include/asm/module.h | 9 + arch/x86/include/asm/unwind.h | 77 +++-- arch/x86/kernel/Makefile | 8 +- arch/x86/kernel/module.c | 12 +- arch/x86/kernel/setup.c | 3 + arch/x86/kernel/unwind_frame.c | 39 ++- arch/x86/kernel/unwind_guess.c | 5 + arch/x86/kernel/unwind_undwarf.c | 589 ++++++++++++++++++++++++++++++++++++++ arch/x86/kernel/vmlinux.lds.S | 2 + include/asm-generic/vmlinux.lds.h | 20 +- lib/Kconfig.debug | 3 + scripts/Makefile.build | 14 +- 16 files changed, 898 insertions(+), 63 deletions(-) create mode 100644 Documentation/x86/undwarf.txt create mode 100644 arch/um/include/asm/unwind.h create mode 100644 arch/x86/kernel/unwind_undwarf.c diff --git a/Documentation/x86/undwarf.txt b/Documentation/x86/undwarf.txt new file mode 100644 index 0000000..d76c6b4 --- /dev/null +++ b/Documentation/x86/undwarf.txt @@ -0,0 +1,146 @@ +Undwarf unwinder debuginfo generation +===================================== + +Overview +-------- + +The kernel CONFIG_UNDWARF_UNWINDER option enables objtool generation of +undwarf debuginfo, which is out-of-band data which is used by the +in-kernel undwarf unwinder. It's similar in concept to DWARF CFI +debuginfo which would be used by a DWARF unwinder. The difference is +that the format of the undwarf data is simpler than DWARF, which in turn +allows the unwinder to be simpler and faster. + +Objtool generates the undwarf data by first doing compile-time stack +metadata validation (CONFIG_STACK_VALIDATION). After analyzing all the +code paths of a .o file, it determines information about the stack state +at each instruction address in the file and outputs that information to +the .undwarf and .undwarf_ip sections. + +The undwarf sections are combined at link time and are sorted at boot +time. The unwinder uses the resulting data to correlate instruction +addresses with their stack states at run time. + + +Undwarf vs frame pointers +------------------------- + +With frame pointers enabled, GCC adds instrumentation code to every +function in the kernel. The kernel's .text size increases by about +3.2%, resulting in a broad kernel-wide slowdown. Measurements by Mel +Gorman [1] have shown a slowdown of 5-10% for some workloads. + +In contrast, the undwarf unwinder has no effect on text size or runtime +performance, because the debuginfo is out of band. So if you disable +frame pointers and enable undwarf, you get a nice performance +improvement across the board, and still have reliable stack traces. + +Another benefit of undwarf compared to frame pointers is that it can +reliably unwind across interrupts and exceptions. Frame pointer based +unwinds can skip the caller of the interrupted function if it was a leaf +function or if the interrupt hit before the frame pointer was saved. + +The main disadvantage of undwarf compared to frame pointers is that it +needs more memory to store the undwarf table: roughly 2-4MB depending on +the kernel config. + + +Undwarf vs DWARF +---------------- + +Undwarf debuginfo's advantage over DWARF itself is that it's much +simpler. It gets rid of the complex DWARF CFI state machine and also +gets rid of the tracking of unnecessary registers. This allows the +unwinder to be much simpler, meaning fewer bugs, which is especially +important for mission critical oops code. + +The simpler debuginfo format also enables the unwinder to be much faster +than DWARF, which is important for perf and lockdep. In a basic +performance test by Jiri Slaby [2], the undwarf unwinder was about 20x +faster than an out-of-tree DWARF unwinder. (Note: that measurement was +taken before some performance tweaks were implemented, so the speedup +may be even higher.) + +The undwarf format does have a few downsides compared to DWARF. The +undwarf table takes up ~2MB more memory than an DWARF .eh_frame table. + +Another potential downside is that, as GCC evolves, it's conceivable +that the undwarf data may end up being *too* simple to describe the +state of the stack for certain optimizations. But IMO this is unlikely +because GCC saves the frame pointer for any unusual stack adjustments it +does, so I suspect we'll really only ever need to keep track of the +stack pointer and the frame pointer between call frames. But even if we +do end up having to track all the registers DWARF tracks, at least we +will still be able to control the format, e.g. no complex state +machines. + + +Undwarf debuginfo generation +---------------------------- + +The undwarf data is generated by objtool. With the existing +compile-time stack metadata validation feature, objtool already follows +all code paths, and so it already has all the information it needs to be +able to generate undwarf data from scratch. So it's an easy step to go +from stack validation to undwarf generation. + +It should be possible to instead generate the undwarf data with a simple +tool which converts DWARF to undwarf. However, such a solution would be +incomplete due to the kernel's extensive use of asm, inline asm, and +special sections like exception tables. + +That could be rectified by manually annotating those special code paths +using GNU assembler .cfi annotations in .S files, and homegrown +annotations for inline asm in .c files. But asm annotations were tried +in the past and were found to be unmaintainable. They were often +incorrect/incomplete and made the code harder to read and keep updated. +And based on looking at glibc code, annotating inline asm in .c files +might be even worse. + +Objtool still needs a few annotations, but only in code which does +unusual things to the stack like entry code. And even then, far fewer +annotations are needed than what DWARF would need, so they're much more +maintainable than DWARF CFI annotations. + +So the advantages of using objtool to generate undwarf are that it gives +more accurate debuginfo, with very few annotations. It also insulates +the kernel from toolchain bugs which can be very painful to deal with in +the kernel since we often have to workaround issues in older versions of +the toolchain for years. + +The downside is that the unwinder now becomes dependent on objtool's +ability to reverse engineer GCC code paths. If GCC optimizations become +too complicated for objtool to follow, the undwarf generation might stop +working or become incomplete. (It's worth noting that livepatch already +has such a dependency on objtool's ability to follow GCC code paths.) + +If newer versions of GCC come up with some optimizations which break +objtool, we may need to revisit the current implementation. Some +possible solutions would be asking GCC to make the optimizations more +palatable, or having objtool use DWARF as an additional input, or +creating a GCC plugin to assist objtool with its analysis. But for now, +objtool follows GCC code quite well. + + +Unwinder implementation details +------------------------------- + +Objtool generates the undwarf data by integrating with the compile-time +stack metadata validation feature, which is described in detail in +tools/objtool/Documentation/stack-validation.txt. After analyzing all +the code paths of a .o file, it creates an array of undwarf structs, and +a parallel array of instruction addresses associated with those structs, +and writes them to the .undwarf and .undwarf_ip sections respectively. + +The undwarf data is split into the two arrays for performance reasons, +to make the searchable part of the data (.undwarf_ip) more compact. The +arrays are sorted in parallel at boot time. + +Performance is further improved by the use of a fast lookup table which +is created at runtime. The fast lookup table associates a given address +with a range of undwarf table indices, so that only a small subset of +the undwarf table needs to be searched. + + +[1] https://lkml.kernel.org/r/20170602104048.jkkzssljsompjdwy@suse.de +[2] https://lkml.kernel.org/r/d2ca5435-6386-29b8-db87-7f227c2b713a@suse.cz diff --git a/arch/um/include/asm/unwind.h b/arch/um/include/asm/unwind.h new file mode 100644 index 0000000..53f507c --- /dev/null +++ b/arch/um/include/asm/unwind.h @@ -0,0 +1,8 @@ +#ifndef _ASM_UML_UNWIND_H +#define _ASM_UML_UNWIND_H + +static inline void +unwind_module_init(struct module *mod, void *undwarf_ip, size_t unward_ip_size, + void *undwarf, size_t undwarf_size) {} + +#endif /* _ASM_UML_UNWIND_H */ diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 72028a1..adf3222 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -153,6 +153,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..995434c 100644 --- a/arch/x86/Kconfig.debug +++ b/arch/x86/Kconfig.debug @@ -357,4 +357,29 @@ 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 + ---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 2-4MB, depending on your 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..4dc6427 100644 --- a/arch/x86/include/asm/module.h +++ b/arch/x86/include/asm/module.h @@ -2,6 +2,15 @@ #define _ASM_X86_MODULE_H #include +#include + +struct mod_arch_specific { +#ifdef CONFIG_UNDWARF_UNWINDER + unsigned int num_undwarves; + int *undwarf_ip; + 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..1f8cb78 100644 --- a/arch/x86/include/asm/unwind.h +++ b/arch/x86/include/asm/unwind.h @@ -12,11 +12,14 @@ struct unwind_state { struct task_struct *task; int graph_idx; bool error; -#ifdef CONFIG_FRAME_POINTER +#if defined(CONFIG_UNDWARF_UNWINDER) + bool signal, full_regs; + 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 +27,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 +58,47 @@ 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) +#else +static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state) { return NULL; } +#endif -static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state) +#ifdef CONFIG_UNDWARF_UNWINDER +void unwind_init(void); +void unwind_module_init(struct module *mod, void *undwarf_ip, + size_t undwarf_ip_size, void *undwarf, + size_t undwarf_size); +#else +static inline void unwind_init(void) {} +static inline void unwind_module_init(struct module *mod, void *undwarf_ip, + size_t undwarf_ip_size, void *undwarf, + size_t undwarf_size) {} +#endif + +/* + * 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; \ +}) + +static inline bool task_on_another_cpu(struct task_struct *task) { - return NULL; +#ifdef CONFIG_SMP + return task != current && task->on_cpu; +#else + return false; +#endif } -#endif /* CONFIG_FRAME_POINTER */ - #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..203b5a7 100644 --- a/arch/x86/kernel/module.c +++ b/arch/x86/kernel/module.c @@ -35,6 +35,7 @@ #include #include #include +#include #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, *undwarf_ip = NULL; char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) { @@ -225,6 +226,10 @@ 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 (!strcmp(".undwarf_ip", secstrings + s->sh_name)) + undwarf_ip = s; } if (alt) { @@ -248,6 +253,11 @@ int module_finalize(const Elf_Ehdr *hdr, /* make jump label nops */ jump_label_apply_nops(me); + if (undwarf && undwarf_ip) + unwind_module_init(me, (void *)undwarf_ip->sh_addr, + undwarf_ip->sh_size, + (void *)undwarf->sh_addr, undwarf->sh_size); + return 0; } diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index 65622f0..d736761 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -115,6 +115,7 @@ #include #include #include +#include /* * max_low_pfn_mapped: highest direct mapped pfn under 4GB @@ -1303,6 +1304,8 @@ void __init setup_arch(char **cmdline_p) if (efi_enabled(EFI_BOOT)) efi_apply_memmap_quirks(); #endif + + unwind_init(); } #ifdef CONFIG_X86_32 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..44f62af --- /dev/null +++ b/arch/x86/kernel/unwind_undwarf.c @@ -0,0 +1,589 @@ +#include +#include +#include +#include +#include +#include +#include + +#define undwarf_warn(fmt, ...) \ + printk_deferred_once(KERN_WARNING pr_fmt("WARNING: " fmt), ##__VA_ARGS__) + +extern int __start_undwarf_ip[]; +extern int __stop_undwarf_ip[]; +extern struct undwarf __start_undwarf[]; +extern struct undwarf __stop_undwarf[]; + +bool undwarf_init; +static DEFINE_MUTEX(sort_mutex); + +int *cur_undwarf_ip_table = __start_undwarf_ip; +struct undwarf *cur_undwarf_table = __start_undwarf; + +/* + * This is a lookup table for speeding up access to the undwarf table. Given + * an input address offset, the corresponding lookup table entry specifies a + * subset of the undwarf table to search. + * + * Each block represents the end of the previous range and the start of the + * next range. An extra block is added to give the last range an end. + * + * Some measured performance results for different values of LOOKUP_NUM_BLOCKS: + * + * num blocks array size lookup speedup total speedup + * 2k 8k 1.5x 1.5x + * 4k 16k 1.6x 1.6x + * 8k 32k 1.8x 1.7x + * 16k 64k 2.0x 1.8x + * 32k 128k 2.5x 2.0x + * 64k 256k 2.9x 2.2x + * 128k 512k 3.3x 2.4x + * + * Go with 32k blocks because it doubles unwinder performance while only adding + * 3.5% to the undwarf data footprint. + */ +#define LOOKUP_NUM_BLOCKS (32 * 1024) +static unsigned int undwarf_fast_lookup[LOOKUP_NUM_BLOCKS + 1] __ro_after_init; + +#define LOOKUP_START_IP (unsigned long)_stext +#define LOOKUP_STOP_IP (unsigned long)_etext +#define LOOKUP_BLOCK_SIZE \ + (DIV_ROUND_UP(LOOKUP_STOP_IP - LOOKUP_START_IP, \ + LOOKUP_NUM_BLOCKS)) + + +static inline unsigned long undwarf_ip(const int *ip) +{ + return (unsigned long)ip + *ip; +} + +static struct undwarf *__undwarf_find(int *ip_table, struct undwarf *u_table, + unsigned int num_entries, + unsigned long ip) +{ + int *first = ip_table; + int *last = ip_table + num_entries - 1; + int *mid = first, *found = first; + + if (!num_entries) + return NULL; + + /* + * Do a binary range search to find the rightmost duplicate of a given + * starting address. Some entries are section terminators which are + * "weak" entries for ensuring there are no gaps. They should be + * ignored when they conflict with a real entry. + */ + while (first <= last) { + mid = first + ((last - first) / 2); + + if (undwarf_ip(mid) <= ip) { + found = mid; + first = mid + 1; + } else + last = mid - 1; + } + + return u_table + (found - ip_table); +} + +static struct undwarf *undwarf_find(unsigned long ip) +{ + struct module *mod; + + if (!undwarf_init) + return NULL; + + /* For non-init vmlinux addresses, use the fast lookup table: */ + if (ip >= LOOKUP_START_IP && ip < LOOKUP_STOP_IP) { + unsigned int idx, start, stop; + + idx = (ip - LOOKUP_START_IP) / LOOKUP_BLOCK_SIZE; + + if (WARN_ON_ONCE(idx >= LOOKUP_NUM_BLOCKS)) + return NULL; + + start = undwarf_fast_lookup[idx]; + stop = undwarf_fast_lookup[idx + 1] + 1; + + if (WARN_ON_ONCE(__start_undwarf + start >= __stop_undwarf) || + __start_undwarf + stop > __stop_undwarf) + return NULL; + + return __undwarf_find(__start_undwarf_ip + start, + __start_undwarf + start, + stop - start, ip); + } + + /* vmlinux .init slow lookup: */ + if (ip >= (unsigned long)_sinittext && ip < (unsigned long)_einittext) + return __undwarf_find(__start_undwarf_ip, __start_undwarf, + __stop_undwarf - __start_undwarf, ip); + + /* Module lookup: */ + mod = __module_address(ip); + if (!mod || !mod->arch.undwarf || !mod->arch.undwarf_ip) + return NULL; + return __undwarf_find(mod->arch.undwarf_ip, mod->arch.undwarf, + mod->arch.num_undwarves, ip); +} + +static void undwarf_sort_swap(void *_a, void *_b, int size) +{ + struct undwarf *undwarf_a, *undwarf_b; + struct undwarf undwarf_tmp; + int *a = _a, *b = _b, tmp; + int delta = _b - _a; + + /* Swap the undwarf_ip entries: */ + tmp = *a; + *a = *b + delta; + *b = tmp - delta; + + /* Swap the corresponding undwarf entries: */ + undwarf_a = cur_undwarf_table + (a - cur_undwarf_ip_table); + undwarf_b = cur_undwarf_table + (b - cur_undwarf_ip_table); + undwarf_tmp = *undwarf_a; + *undwarf_a = *undwarf_b; + *undwarf_b = undwarf_tmp; +} + +static int undwarf_sort_cmp(const void *_a, const void *_b) +{ + struct undwarf *undwarf_a; + const int *a = _a, *b = _b; + unsigned long a_val = undwarf_ip(a); + unsigned long b_val = undwarf_ip(b); + + if (a_val > b_val) + return 1; + if (a_val < b_val) + return -1; + + /* + * The "weak" section terminator entries need to always be on the left + * to ensure the lookup code skips them in favor of real entries. + * These terminator entries exist to handle any gaps created by + * whitelisted .o files which didn't get objtool generation. + */ + undwarf_a = cur_undwarf_table + (a - cur_undwarf_ip_table); + return undwarf_a->cfa_reg == UNDWARF_REG_UNDEFINED ? -1 : 1; +} + +void unwind_module_init(struct module *mod, void *_undwarf_ip, + size_t undwarf_ip_size, void *_undwarf, + size_t undwarf_size) +{ + int *undwarf_ip = _undwarf_ip; + struct undwarf *undwarf = _undwarf; + unsigned int num_entries = undwarf_ip_size / sizeof(int); + + WARN_ON_ONCE(undwarf_ip_size % sizeof(int) != 0 || + undwarf_size % sizeof(*undwarf) != 0 || + num_entries != undwarf_size / sizeof(*undwarf)); + + /* + * The 'cur_undwarf_*' globals allow the undwarf_sort_swap() callback + * to associate an undwarf_ip table entry with its corresponding + * undwarf entry so they can both be swapped. + */ + mutex_lock(&sort_mutex); + cur_undwarf_ip_table = undwarf_ip; + cur_undwarf_table = undwarf; + sort(undwarf_ip, num_entries, sizeof(int),undwarf_sort_cmp, + undwarf_sort_swap); + mutex_unlock(&sort_mutex); + + mod->arch.undwarf_ip = undwarf_ip; + mod->arch.undwarf = undwarf; + mod->arch.num_undwarves = num_entries; +} + +void __init unwind_init(void) +{ + size_t undwarf_ip_size = (void *)__stop_undwarf_ip - (void *)__start_undwarf_ip; + size_t undwarf_size = (void *)__stop_undwarf - (void *)__start_undwarf; + size_t num_entries = undwarf_ip_size / sizeof(int); + struct undwarf *undwarf; + int i; + + if (!num_entries || undwarf_ip_size % sizeof(int) != 0 || + undwarf_size % sizeof(struct undwarf) != 0 || + num_entries != undwarf_size / sizeof(struct undwarf)) { + pr_warn("WARNING: Bad or missing undwarf table. Disabling unwinder.\n"); + return; + } + + /* Sort the undwarf table: */ + sort(__start_undwarf_ip, num_entries, sizeof(int), undwarf_sort_cmp, + undwarf_sort_swap); + + /* Initialize the fast lookup table: */ + for (i = 0; i < LOOKUP_NUM_BLOCKS; i++) { + undwarf = __undwarf_find(__start_undwarf_ip, __start_undwarf, + num_entries, + LOOKUP_START_IP + (LOOKUP_BLOCK_SIZE * i)); + if (!undwarf) { + pr_warn("WARNING: Corrupt undwarf table. Disabling unwinder.\n"); + return; + } + + undwarf_fast_lookup[i] = undwarf - __start_undwarf; + } + + /* Initialize the last 'end' block: */ + undwarf = __undwarf_find(__start_undwarf_ip, __start_undwarf, + num_entries, LOOKUP_STOP_IP); + if (!undwarf) { + pr_warn("WARNING: Corrupt undwarf table. Disabling unwinder.\n"); + return; + } + undwarf_fast_lookup[LOOKUP_NUM_BLOCKS] = undwarf - __start_undwarf; + + undwarf_init = true; +} + +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 bool stack_access_ok(struct unwind_state *state, unsigned long addr, + size_t len) +{ + struct stack_info *info = &state->stack_info; + + /* + * If the address 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 address + * 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)®s->sp; + + return true; +} + +bool unwind_next_frame(struct unwind_state *state) +{ + enum stack_type prev_type = state->stack_info.type; + unsigned long ip_p, prev_sp = state->sp; + unsigned long cfa, orig_ip, orig_sp; + struct undwarf *undwarf; + struct pt_regs *ptregs; + bool indirect = false; + + if (unwind_done(state)) + return false; + + /* Don't let modules unload while we're reading their undwarf data. */ + preempt_disable(); + + /* Have we reached the end? */ + if (state->regs && user_mode(state->regs)) + goto done; + + /* + * Find the undwarf table entry associated with the text address. + * + * Decrement call return addresses by one so they work for sibling + * calls and calls to noreturn functions. + */ + undwarf = undwarf_find(state->signal ? state->ip : state->ip - 1); + if (!undwarf || undwarf->cfa_reg == UNDWARF_REG_UNDEFINED) + goto done; + orig_ip = state->ip; + + /* 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 || !state->full_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_R13: + if (!state->regs || !state->full_regs) { + undwarf_warn("missing regs for base reg R13 at ip %p\n", + (void *)state->ip); + goto done; + } + cfa = state->regs->r13; + break; + + case UNDWARF_REG_DI: + if (!state->regs || !state->full_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 || !state->full_regs) { + undwarf_warn("missing regs for base reg DX 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; + state->signal = false; + 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 *)orig_ip); + goto done; + } + + state->regs = (struct pt_regs *)cfa; + state->full_regs = true; + state->signal = true; + break; + + case UNDWARF_TYPE_REGS_IRET: + orig_sp = state->sp; + 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 *)orig_ip); + goto done; + } + + ptregs = container_of((void *)cfa, struct pt_regs, ip); + if ((unsigned long)ptregs >= orig_sp && + on_stack(&state->stack_info, ptregs, REGS_SIZE)) { + state->regs = ptregs; + state->full_regs = false; + } else + state->regs = NULL; + + state->signal = true; + 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->full_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 *)orig_ip); + 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 *)orig_ip); + goto done; + } + + preempt_enable(); + return true; + +done: + preempt_enable(); + 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; + + /* + * Refuse to unwind the stack of a task while it's executing on another + * CPU. This check is racy, but that's ok: the unwinder has other + * checks to prevent it from going off the rails. + */ + if (task_on_another_cpu(task)) + goto done; + + if (regs) { + if (user_mode(regs)) + goto done; + + state->ip = regs->ip; + state->sp = kernel_stack_pointer(regs); + state->bp = regs->bp; + state->regs = regs; + state->full_regs = true; + state->signal = true; + + } else if (task == current) { + 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)); + + } else { + struct inactive_task_frame *frame = (void *)task->thread.sp; + + state->ip = frame->ret_addr; + state->sp = task->thread.sp; + state->bp = frame->bp; + } + + 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); + + return; + +done: + state->stack_info.type = STACK_TYPE_UNKNOWN; + return; +} +EXPORT_SYMBOL_GPL(__unwind_start); diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index c8a3b61..e3b7cfc 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -148,6 +148,8 @@ SECTIONS BUG_TABLE + UNDWARF_TABLE + . = ALIGN(PAGE_SIZE); __vvar_page = .; diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 0d64658..a8ed616 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -668,6 +668,24 @@ #define BUG_TABLE #endif +#ifdef CONFIG_UNDWARF_UNWINDER +#define UNDWARF_TABLE \ + . = ALIGN(4); \ + .undwarf_ip : AT(ADDR(.undwarf_ip) - LOAD_OFFSET) { \ + VMLINUX_SYMBOL(__start_undwarf_ip) = .; \ + KEEP(*(.undwarf_ip)) \ + VMLINUX_SYMBOL(__stop_undwarf_ip) = .; \ + } \ + . = ALIGN(8); \ + .undwarf : AT(ADDR(.undwarf) - LOAD_OFFSET) { \ + VMLINUX_SYMBOL(__start_undwarf) = .; \ + KEEP(*(.undwarf)) \ + VMLINUX_SYMBOL(__stop_undwarf) = .; \ + } +#else +#define UNDWARF_TABLE +#endif + #ifdef CONFIG_PM_TRACE #define TRACEDATA \ . = ALIGN(4); \ @@ -854,7 +872,7 @@ DATA_DATA \ CONSTRUCTORS \ } \ - BUG_TABLE + BUG_TABLE \ #define INIT_TEXT_SECTION(inittext_align) \ . = ALIGN(inittext_align); \ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 9c5d40a..ec79366 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..7859c79 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 @@ -276,6 +277,11 @@ objtool_obj = $(if $(patsubst y%,, \ endif # SKIP_STACK_VALIDATION endif # CONFIG_STACK_VALIDATION +# Rebuild all objects when objtool changes, or is enabled/disabled. +objtool_dep = $(objtool_obj) \ + $(wildcard include/config/undwarf/unwinder.h \ + include/config/stack/validation.h) + define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call cmd_and_fixdep,cc_o_c) \ @@ -298,13 +304,13 @@ cmd_undef_syms = echo endif # Built-in and composite module parts -$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_obj) FORCE +$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) # Single-part modules are special since we need to mark them in $(MODVERDIR) -$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_obj) FORCE +$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) @{ echo $(@:.o=.ko); echo $@; \ @@ -399,7 +405,7 @@ cmd_modversions_S = \ endif endif -$(obj)/%.o: $(src)/%.S $(objtool_obj) FORCE +$(obj)/%.o: $(src)/%.S $(objtool_dep) FORCE $(call if_changed_rule,as_o_S) targets += $(real-objs-y) $(real-objs-m) $(lib-y) -- 2.7.5