Commit 690a973f authored by Jan Beulich's avatar Jan Beulich Committed by Andi Kleen

[PATCH] x86-64: Speed up dwarf2 unwinder

This changes the dwarf2 unwinder to do a binary search for CIEs
instead of a linear work. The linker is unfortunately not
able to build a proper lookup table at link time, instead it creates
one at runtime as soon as the bootmem allocator is usable (so you'll continue
using the linear lookup for the first [hopefully] few calls).
The code should be ready to utilize a build-time created table once
a fixed linker becomes available.
Signed-off-by: default avatarJan Beulich <jbeulich@novell.com>
Signed-off-by: default avatarAndi Kleen <ak@suse.de>
parent cdfce1f5
...@@ -499,6 +499,7 @@ endif ...@@ -499,6 +499,7 @@ endif
ifdef CONFIG_UNWIND_INFO ifdef CONFIG_UNWIND_INFO
CFLAGS += -fasynchronous-unwind-tables CFLAGS += -fasynchronous-unwind-tables
LDFLAGS_vmlinux += --eh-frame-hdr
endif endif
ifdef CONFIG_DEBUG_INFO ifdef CONFIG_DEBUG_INFO
......
...@@ -125,6 +125,10 @@ ...@@ -125,6 +125,10 @@
*(__param) \ *(__param) \
VMLINUX_SYMBOL(__stop___param) = .; \ VMLINUX_SYMBOL(__stop___param) = .; \
} \ } \
\
/* Unwind data binary search table */ \
EH_FRAME_HDR \
\
__end_rodata = .; \ __end_rodata = .; \
. = ALIGN(4096); . = ALIGN(4096);
...@@ -157,6 +161,18 @@ ...@@ -157,6 +161,18 @@
*(.kprobes.text) \ *(.kprobes.text) \
VMLINUX_SYMBOL(__kprobes_text_end) = .; VMLINUX_SYMBOL(__kprobes_text_end) = .;
#ifdef CONFIG_STACK_UNWIND
/* Unwind data binary search table */
#define EH_FRAME_HDR \
.eh_frame_hdr : AT(ADDR(.eh_frame_hdr) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__start_unwind_hdr) = .; \
*(.eh_frame_hdr) \
VMLINUX_SYMBOL(__end_unwind_hdr) = .; \
}
#else
#define EH_FRAME_HDR
#endif
/* DWARF debug sections. /* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to Symbols in the DWARF debugging sections are relative to
the beginning of the section so we begin them at 0. */ the beginning of the section so we begin them at 0. */
......
...@@ -26,6 +26,7 @@ struct module; ...@@ -26,6 +26,7 @@ struct module;
* Initialize unwind support. * Initialize unwind support.
*/ */
extern void unwind_init(void); extern void unwind_init(void);
extern void unwind_setup(void);
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
...@@ -73,6 +74,7 @@ extern int unwind_to_user(struct unwind_frame_info *); ...@@ -73,6 +74,7 @@ extern int unwind_to_user(struct unwind_frame_info *);
struct unwind_frame_info {}; struct unwind_frame_info {};
static inline void unwind_init(void) {} static inline void unwind_init(void) {}
static inline void unwind_setup(void) {}
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
......
...@@ -503,6 +503,7 @@ asmlinkage void __init start_kernel(void) ...@@ -503,6 +503,7 @@ asmlinkage void __init start_kernel(void)
printk(KERN_NOTICE); printk(KERN_NOTICE);
printk(linux_banner); printk(linux_banner);
setup_arch(&command_line); setup_arch(&command_line);
unwind_setup();
setup_per_cpu_areas(); setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */ smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
......
...@@ -11,13 +11,15 @@ ...@@ -11,13 +11,15 @@
#include <linux/unwind.h> #include <linux/unwind.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/delay.h> #include <linux/bootmem.h>
#include <linux/sort.h>
#include <linux/stop_machine.h> #include <linux/stop_machine.h>
#include <asm/sections.h> #include <asm/sections.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
extern char __start_unwind[], __end_unwind[]; extern char __start_unwind[], __end_unwind[];
extern const u8 __start_unwind_hdr[], __end_unwind_hdr[];
#define MAX_STACK_DEPTH 8 #define MAX_STACK_DEPTH 8
...@@ -100,6 +102,8 @@ static struct unwind_table { ...@@ -100,6 +102,8 @@ static struct unwind_table {
} core, init; } core, init;
const void *address; const void *address;
unsigned long size; unsigned long size;
const unsigned char *header;
unsigned long hdrsz;
struct unwind_table *link; struct unwind_table *link;
const char *name; const char *name;
} root_table; } root_table;
...@@ -145,6 +149,10 @@ static struct unwind_table *find_table(unsigned long pc) ...@@ -145,6 +149,10 @@ static struct unwind_table *find_table(unsigned long pc)
return table; return table;
} }
static unsigned long read_pointer(const u8 **pLoc,
const void *end,
signed ptrType);
static void init_unwind_table(struct unwind_table *table, static void init_unwind_table(struct unwind_table *table,
const char *name, const char *name,
const void *core_start, const void *core_start,
...@@ -152,14 +160,30 @@ static void init_unwind_table(struct unwind_table *table, ...@@ -152,14 +160,30 @@ static void init_unwind_table(struct unwind_table *table,
const void *init_start, const void *init_start,
unsigned long init_size, unsigned long init_size,
const void *table_start, const void *table_start,
unsigned long table_size) 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.pc = (unsigned long)core_start;
table->core.range = core_size; table->core.range = core_size;
table->init.pc = (unsigned long)init_start; table->init.pc = (unsigned long)init_start;
table->init.range = init_size; table->init.range = init_size;
table->address = table_start; table->address = table_start;
table->size = table_size; table->size = table_size;
/* See if the linker provided table looks valid. */
if (header_size <= 4
|| header_start[0] != 1
|| (void *)read_pointer(&ptr, end, header_start[1]) != table_start
|| header_start[2] == DW_EH_PE_omit
|| read_pointer(&ptr, end, header_start[2]) <= 0
|| header_start[3] == DW_EH_PE_omit)
header_start = NULL;
table->hdrsz = header_size;
smp_wmb();
table->header = header_start;
table->link = NULL; table->link = NULL;
table->name = name; table->name = name;
} }
...@@ -169,7 +193,143 @@ void __init unwind_init(void) ...@@ -169,7 +193,143 @@ void __init unwind_init(void)
init_unwind_table(&root_table, "kernel", init_unwind_table(&root_table, "kernel",
_text, _end - _text, _text, _end - _text,
NULL, 0, NULL, 0,
__start_unwind, __end_unwind - __start_unwind); __start_unwind, __end_unwind - __start_unwind,
__start_unwind_hdr, __end_unwind_hdr - __start_unwind_hdr);
}
static const u32 bad_cie, not_fde;
static const u32 *cie_for_fde(const u32 *fde, const struct unwind_table *);
static signed fde_pointer_type(const u32 *cie);
struct eh_frame_hdr_table_entry {
unsigned long start, fde;
};
static int 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 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 setup_unwind_table(struct unwind_table *table,
void *(*alloc)(unsigned long))
{
const u8 *ptr;
unsigned long tableSize = table->size, hdrSize;
unsigned 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)
printk(KERN_WARNING ".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);
signed ptrType;
if (cie == &not_fde)
continue;
if (cie == NULL
|| cie == &bad_cie
|| (ptrType = fde_pointer_type(cie)) < 0)
return;
ptr = (const u8 *)(fde + 2);
if (!read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType))
return;
++n;
}
if (tableSize || !n)
return;
hdrSize = 4 + sizeof(unsigned long) + sizeof(unsigned int)
+ 2 * n * sizeof(unsigned long);
header = alloc(hdrSize);
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 = fde + 1 - fde[1] / sizeof(*fde);
if (!fde[1])
continue; /* this is a CIE */
ptr = (const u8 *)(fde + 2);
header->table[n].start = read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
fde_pointer_type(cie));
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();
table->header = (const void *)header;
}
static void *__init balloc(unsigned long sz)
{
return __alloc_bootmem_nopanic(sz,
sizeof(unsigned int),
__pa(MAX_DMA_ADDRESS));
}
void __init unwind_setup(void)
{
setup_unwind_table(&root_table, balloc);
} }
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
...@@ -193,7 +353,8 @@ void *unwind_add_table(struct module *module, ...@@ -193,7 +353,8 @@ void *unwind_add_table(struct module *module,
init_unwind_table(table, module->name, init_unwind_table(table, module->name,
module->module_core, module->core_size, module->module_core, module->core_size,
module->module_init, module->init_size, module->module_init, module->init_size,
table_start, table_size); table_start, table_size,
NULL, 0);
if (last_table) if (last_table)
last_table->link = table; last_table->link = table;
...@@ -303,6 +464,26 @@ static sleb128_t get_sleb128(const u8 **pcur, const u8 *end) ...@@ -303,6 +464,26 @@ static sleb128_t get_sleb128(const u8 **pcur, const u8 *end)
return value; return value;
} }
static const u32 *cie_for_fde(const u32 *fde, const struct unwind_table *table)
{
const u32 *cie;
if (!*fde || (*fde & (sizeof(*fde) - 1)))
return &bad_cie;
if (!fde[1])
return &not_fde; /* this is a CIE */
if ((fde[1] & (sizeof(*fde) - 1))
|| fde[1] > (unsigned long)(fde + 1) - (unsigned long)table->address)
return NULL; /* this is not a valid FDE */
cie = fde + 1 - fde[1] / sizeof(*fde);
if (*cie <= sizeof(*cie) + 4
|| *cie >= fde[1] - sizeof(*fde)
|| (*cie & (sizeof(*cie) - 1))
|| cie[1])
return NULL; /* this is not a (valid) CIE */
return cie;
}
static unsigned long read_pointer(const u8 **pLoc, static unsigned long read_pointer(const u8 **pLoc,
const void *end, const void *end,
signed ptrType) signed ptrType)
...@@ -610,49 +791,108 @@ int unwind(struct unwind_frame_info *frame) ...@@ -610,49 +791,108 @@ int unwind(struct unwind_frame_info *frame)
unsigned i; unsigned i;
signed ptrType = -1; signed ptrType = -1;
uleb128_t retAddrReg = 0; uleb128_t retAddrReg = 0;
struct unwind_table *table; const struct unwind_table *table;
struct unwind_state state; struct unwind_state state;
if (UNW_PC(frame) == 0) if (UNW_PC(frame) == 0)
return -EINVAL; return -EINVAL;
if ((table = find_table(pc)) != NULL if ((table = find_table(pc)) != NULL
&& !(table->size & (sizeof(*fde) - 1))) { && !(table->size & (sizeof(*fde) - 1))) {
unsigned long tableSize = table->size; const u8 *hdr = table->header;
unsigned long tableSize;
for (fde = table->address;
tableSize > sizeof(*fde) && tableSize - sizeof(*fde) >= *fde; smp_rmb();
tableSize -= sizeof(*fde) + *fde, if (hdr && hdr[0] == 1) {
fde += 1 + *fde / sizeof(*fde)) { switch(hdr[3] & DW_EH_PE_FORM) {
if (!*fde || (*fde & (sizeof(*fde) - 1))) case DW_EH_PE_native: tableSize = sizeof(unsigned long); break;
break; case DW_EH_PE_data2: tableSize = 2; break;
if (!fde[1]) case DW_EH_PE_data4: tableSize = 4; break;
continue; /* this is a CIE */ case DW_EH_PE_data8: tableSize = 8; break;
if ((fde[1] & (sizeof(*fde) - 1)) default: tableSize = 0; break;
|| fde[1] > (unsigned long)(fde + 1) }
- (unsigned long)table->address) ptr = hdr + 4;
continue; /* this is not a valid FDE */ end = hdr + table->hdrsz;
cie = fde + 1 - fde[1] / sizeof(*fde); if (tableSize
if (*cie <= sizeof(*cie) + 4 && read_pointer(&ptr, end, hdr[1])
|| *cie >= fde[1] - sizeof(*fde) == (unsigned long)table->address
|| (*cie & (sizeof(*cie) - 1)) && (i = read_pointer(&ptr, end, hdr[2])) > 0
|| cie[1] && i == (end - ptr) / (2 * tableSize)
|| (ptrType = fde_pointer_type(cie)) < 0) { && !((end - ptr) % (2 * tableSize))) {
cie = NULL; /* this is not a (valid) CIE */ do {
continue; const u8 *cur = ptr + (i / 2) * (2 * tableSize);
startLoc = read_pointer(&cur,
cur + tableSize,
hdr[3]);
if (pc < startLoc)
i /= 2;
else {
ptr = cur - tableSize;
i = (i + 1) / 2;
}
} while (startLoc && i > 1);
if (i == 1
&& (startLoc = read_pointer(&ptr,
ptr + tableSize,
hdr[3])) != 0
&& pc >= startLoc)
fde = (void *)read_pointer(&ptr,
ptr + tableSize,
hdr[3]);
} }
}
if (fde != NULL) {
cie = cie_for_fde(fde, table);
ptr = (const u8 *)(fde + 2); ptr = (const u8 *)(fde + 2);
startLoc = read_pointer(&ptr, if(cie != NULL
(const u8 *)(fde + 1) + *fde, && cie != &bad_cie
ptrType); && cie != &not_fde
endLoc = startLoc && (ptrType = fde_pointer_type(cie)) >= 0
+ read_pointer(&ptr, && read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde, (const u8 *)(fde + 1) + *fde,
ptrType & DW_EH_PE_indirect ptrType) == startLoc) {
? ptrType if (!(ptrType & DW_EH_PE_indirect))
: ptrType & (DW_EH_PE_FORM|DW_EH_PE_signed)); ptrType &= DW_EH_PE_FORM|DW_EH_PE_signed;
if (pc >= startLoc && pc < endLoc) endLoc = startLoc
break; + read_pointer(&ptr,
cie = NULL; (const u8 *)(fde + 1) + *fde,
ptrType);
if(pc >= endLoc)
fde = NULL;
} else
fde = NULL;
}
if (fde == NULL) {
for (fde = table->address, tableSize = table->size;
cie = NULL, tableSize > sizeof(*fde)
&& tableSize - sizeof(*fde) >= *fde;
tableSize -= sizeof(*fde) + *fde,
fde += 1 + *fde / sizeof(*fde)) {
cie = cie_for_fde(fde, table);
if (cie == &bad_cie) {
cie = NULL;
break;
}
if (cie == NULL
|| cie == &not_fde
|| (ptrType = fde_pointer_type(cie)) < 0)
continue;
ptr = (const u8 *)(fde + 2);
startLoc = read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType);
if (!startLoc)
continue;
if (!(ptrType & DW_EH_PE_indirect))
ptrType &= DW_EH_PE_FORM|DW_EH_PE_signed;
endLoc = startLoc
+ read_pointer(&ptr,
(const u8 *)(fde + 1) + *fde,
ptrType);
if (pc >= startLoc && pc < endLoc)
break;
}
} }
} }
if (cie != NULL) { if (cie != NULL) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment