lkml.org 
[lkml]   [2006]   [May]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[PATCH 1/3] reliable stack trace support
    These are the generic bits needed to enable reliable stack traces based
    on Dwarf2-like (.eh_frame) unwind information. Subsequent patches will
    enable x86-64 and i386 to make use of this.

    Signed-off-by: Jan Beulich <jbeulich@novell.com>

    --- linux-2.6.17-rc4/include/asm-generic/unwind.h 1970-01-01 01:00:00.000000000 +0100
    +++ 2.6.17-rc4-unwind-generic/include/asm-generic/unwind.h 2006-05-16 14:36:21.000000000 +0200
    @@ -0,0 +1,119 @@
    +#ifndef _ASM_GENERIC_UNWIND_H
    +#define _ASM_GENERIC_UNWIND_H
    +
    +/*
    + * Copyright (C) 2002-2006 Novell, Inc.
    + * Jan Beulich <jbeulich@novell.com>
    + *
    + * A simple API for unwinding kernel stacks. This is used for
    + * debugging and error reporting purposes. The kernel doesn't need
    + * full-blown stack unwinding with all the bells and whistles, so there
    + * is not much point in implementing the full Dwarf2 unwind API.
    + */
    +
    +struct module;
    +
    +#ifdef CONFIG_STACK_UNWIND
    +
    +#define ARCH_UNWIND_SECTION_NAME ".eh_frame"
    +
    +/*
    + * Initialize unwind support.
    + */
    +extern void unwind_init(void);
    +
    +extern void *
    +unwind_add_table(struct module *,
    + const void *table_start,
    + unsigned long table_size);
    +
    +extern void
    +unwind_remove_table(void *handle, int init_only);
    +
    +extern int
    +unwind_init_frame_info(struct unwind_frame_info *,
    + struct task_struct *,
    + /*const*/ struct pt_regs *);
    +
    +/*
    + * Prepare to unwind a blocked task.
    + */
    +extern int
    +unwind_init_blocked(struct unwind_frame_info *, struct task_struct *);
    +
    +/*
    + * Prepare to unwind the currently running thread.
    + */
    +extern int
    +unwind_init_running(struct unwind_frame_info *,
    + asmlinkage void (*callback)(struct unwind_frame_info *, void *arg),
    + void *arg);
    +
    +/*
    + * Unwind to previous to frame. Returns 0 if successful, negative
    + * number in case of an error.
    + */
    +extern int
    +unwind(struct unwind_frame_info *);
    +
    +/*
    + * Unwind until the return pointer is in user-land (or until an error
    + * occurs). Returns 0 if successful, negative number in case of
    + * error.
    + */
    +extern int
    +unwind_to_user(struct unwind_frame_info *);
    +
    +#else
    +
    +struct unwind_frame_info {};
    +
    +static inline void unwind_init(void) {}
    +
    +static inline void *
    +unwind_add_table(struct module *mod,
    + const void *table_start,
    + unsigned long table_size)
    +{
    + return NULL;
    +}
    +
    +static inline void
    +unwind_remove_table(void *handle, int init_only)
    +{}
    +
    +static inline int
    +unwind_init_frame_info(struct unwind_frame_info *info,
    + struct task_struct *tsk,
    + const struct pt_regs *regs)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int
    +unwind_init_blocked(struct unwind_frame_info *info, struct task_struct *tsk)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int
    +unwind_init_running(struct unwind_frame_info *info,
    + asmlinkage void (*callback)(struct unwind_frame_info *, void *arg),
    + void *arg)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int unwind(struct unwind_frame_info *info)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int unwind_to_user(struct unwind_frame_info *info)
    +{
    + return -ENOSYS;
    +}
    +
    +#endif
    +
    +#endif /* _ASM_GENERIC_UNWIND_H */
    --- linux-2.6.17-rc4/include/linux/module.h 2006-05-16 15:15:48.000000000 +0200
    +++ 2.6.17-rc4-unwind-generic/include/linux/module.h 2006-05-15 17:59:46.000000000 +0200
    @@ -284,6 +284,12 @@ struct module
    /* The size of the executable code in each section. */
    unsigned long init_text_size, core_text_size;

    +#if defined(CONFIG_STACK_UNWIND) /* || defined(CONFIG_IA64) || defined(CONFIG_PARISC) */
    +# define MODULE_UNWIND_INFO
    + /* The handle returned from unwind_add_table. */
    + void *unwind_info;
    +#endif
    +
    /* Arch-specific module values */
    struct mod_arch_specific arch;

    --- linux-2.6.17-rc4/init/main.c 2006-05-16 15:15:49.000000000 +0200
    +++ 2.6.17-rc4-unwind-generic/init/main.c 2006-05-15 17:26:14.000000000 +0200
    @@ -54,6 +54,12 @@
    #include <asm/sections.h>
    #include <asm/cacheflush.h>

    +#ifdef CONFIG_STACK_UNWIND
    +#include <asm/unwind.h>
    +#else
    +#include <asm-generic/unwind.h>
    +#endif
    +
    #ifdef CONFIG_X86_LOCAL_APIC
    #include <asm/smp.h>
    #endif
    @@ -482,6 +488,7 @@ asmlinkage void __init start_kernel(void
    __stop___param - __start___param,
    &unknown_bootoption);
    sort_main_extable();
    + unwind_init();
    trap_init();
    rcu_init();
    init_IRQ();
    --- linux-2.6.17-rc4/kernel/Makefile 2006-05-16 15:15:49.000000000 +0200
    +++ 2.6.17-rc4-unwind-generic/kernel/Makefile 2006-05-15 08:52:50.000000000 +0200
    @@ -22,6 +22,7 @@ obj-$(CONFIG_UID16) += uid16.o
    obj-$(CONFIG_MODULES) += module.o
    obj-$(CONFIG_OBSOLETE_INTERMODULE) += intermodule.o
    obj-$(CONFIG_KALLSYMS) += kallsyms.o
    +obj-$(CONFIG_STACK_UNWIND) += unwind.o
    obj-$(CONFIG_PM) += power/
    obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o
    obj-$(CONFIG_KEXEC) += kexec.o
    --- linux-2.6.17-rc4/kernel/module.c 2006-05-16 15:15:49.000000000 +0200
    +++ 2.6.17-rc4-unwind-generic/kernel/module.c 2006-05-16 08:46:46.000000000 +0200
    @@ -43,6 +43,9 @@
    #include <asm/uaccess.h>
    #include <asm/semaphore.h>
    #include <asm/cacheflush.h>
    +#ifdef MODULE_UNWIND_INFO
    +#include <asm/unwind.h>
    +#endif

    #if 0
    #define DEBUGP printk
    @@ -1051,6 +1054,10 @@ static void free_module(struct module *m
    remove_sect_attrs(mod);
    mod_kobject_remove(mod);

    +#ifdef MODULE_UNWIND_INFO
    + unwind_remove_table(mod->unwind_info, 0);
    +#endif
    +
    /* Arch-specific cleanup. */
    module_arch_cleanup(mod);

    @@ -1412,7 +1419,7 @@ static struct module *load_module(void _
    unsigned int i, symindex = 0, strindex = 0, setupindex, exindex,
    exportindex, modindex, obsparmindex, infoindex, gplindex,
    crcindex, gplcrcindex, versindex, pcpuindex, gplfutureindex,
    - gplfuturecrcindex;
    + gplfuturecrcindex, unwindex = 0;
    struct module *mod;
    long err = 0;
    void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
    @@ -1502,6 +1509,9 @@ static struct module *load_module(void _
    versindex = find_sec(hdr, sechdrs, secstrings, "__versions");
    infoindex = find_sec(hdr, sechdrs, secstrings, ".modinfo");
    pcpuindex = find_pcpusec(hdr, sechdrs, secstrings);
    +#ifdef ARCH_UNWIND_SECTION_NAME
    + unwindex = find_sec(hdr, sechdrs, secstrings, ARCH_UNWIND_SECTION_NAME);
    +#endif

    /* Don't keep modinfo section */
    sechdrs[infoindex].sh_flags &= ~(unsigned long)SHF_ALLOC;
    @@ -1510,6 +1520,8 @@ static struct module *load_module(void _
    sechdrs[symindex].sh_flags |= SHF_ALLOC;
    sechdrs[strindex].sh_flags |= SHF_ALLOC;
    #endif
    + if (unwindex)
    + sechdrs[unwindex].sh_flags |= SHF_ALLOC;

    /* Check module struct version now, before we try to use module. */
    if (!check_modstruct_version(sechdrs, versindex, mod)) {
    @@ -1738,6 +1750,13 @@ static struct module *load_module(void _
    goto arch_cleanup;
    add_sect_attrs(mod, hdr->e_shnum, secstrings, sechdrs);

    +#ifdef MODULE_UNWIND_INFO
    + /* Size of section 0 is 0, so this works well if no unwind info. */
    + mod->unwind_info = unwind_add_table(mod,
    + (void *)sechdrs[unwindex].sh_addr,
    + sechdrs[unwindex].sh_size);
    +#endif
    +
    /* Get rid of temporary copy */
    vfree(hdr);

    @@ -1836,6 +1855,9 @@ sys_init_module(void __user *umod,
    mod->state = MODULE_STATE_LIVE;
    /* Drop initial reference. */
    module_put(mod);
    +#ifdef MODULE_UNWIND_INFO
    + unwind_remove_table(mod->unwind_info, 1);
    +#endif
    module_free(mod, mod->module_init);
    mod->module_init = NULL;
    mod->init_size = 0;
    --- linux-2.6.17-rc4/kernel/unwind.c 1970-01-01 01:00:00.000000000 +0100
    +++ 2.6.17-rc4-unwind-generic/kernel/unwind.c 2006-05-16 14:36:08.000000000 +0200
    @@ -0,0 +1,876 @@
    +/*
    + * Copyright (C) 2002-2006 Novell, Inc.
    + * Jan Beulich <jbeulich@novell.com>
    + *
    + * A simple API for unwinding kernel stacks. This is used for
    + * debugging and error reporting purposes. The kernel doesn't need
    + * full-blown stack unwinding with all the bells and whistles, so there
    + * is not much point in implementing the full Dwarf2 unwind API.
    + */
    +
    +#include <linux/module.h>
    +#include <linux/sched.h>
    +#include <linux/delay.h>
    +#include <asm/sections.h>
    +#include <asm/uaccess.h>
    +#include <asm/unaligned.h>
    +#include <asm/unwind.h>
    +
    +extern char __start_unwind[], __end_unwind[];
    +
    +#define MAX_STACK_DEPTH 8
    +
    +#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
    +#define VALUE_BUILD_BUG_ON(e) (sizeof(char[1 - 2 * !!(e)]))
    +
    +#define EXTRA_INFO(f) { \
    + VALUE_BUILD_BUG_ON(offsetof(struct unwind_frame_info, f) \
    + % FIELD_SIZEOF(struct unwind_frame_info, f)) \
    + * offsetof(struct unwind_frame_info, f) / FIELD_SIZEOF(struct unwind_frame_info, f), \
    + FIELD_SIZEOF(struct unwind_frame_info, f) \
    + }
    +#define PTREGS_INFO(f) EXTRA_INFO(regs.f)
    +
    +static const struct {
    + unsigned offs:BITS_PER_LONG / 2;
    + unsigned width:BITS_PER_LONG / 2;
    +} reg_info[] = {
    + UNW_REGISTER_INFO
    +};
    +
    +#undef PTREGS_INFO
    +#undef EXTRA_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_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
    +
    +typedef unsigned long uleb128_t;
    +typedef signed long sleb128_t;
    +
    +DEFINE_SPINLOCK(table_lock);
    +static atomic_t lookups, removals;
    +
    +static struct unwind_table {
    + struct {
    + unsigned long pc;
    + unsigned long range;
    + } core, init;
    + const void *address;
    + unsigned long size;
    + struct unwind_table *link;
    + atomic_t users;
    + const char *name;
    +} root_table, *last_table;
    +
    +struct unwind_item {
    + enum item_location {
    + Nowhere,
    + Memory,
    + Register,
    + Value
    + } where;
    + uleb128_t value;
    +};
    +
    +struct unwind_state {
    + uleb128_t loc, org;
    + const uint8_t *cieStart, *cieEnd;
    + uleb128_t codeAlign;
    + sleb128_t dataAlign;
    + struct cfa {
    + uleb128_t reg, offs;
    + } cfa;
    + struct unwind_item regs[ARRAY_SIZE(reg_info)];
    + unsigned stackDepth:8;
    + unsigned version:8;
    + const uint8_t *label;
    + const uint8_t *stack[MAX_STACK_DEPTH];
    +};
    +
    +static const struct cfa badCFA = {ARRAY_SIZE(reg_info), 1};
    +
    +static struct unwind_table *
    +find_table(unsigned long pc)
    +{
    + int old_removals;
    + struct unwind_table *table = NULL;
    +
    + do {
    + if (table)
    + atomic_dec(&table->users);
    + old_removals = atomic_read(&removals);
    + atomic_inc(&lookups);
    + 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)) {
    + atomic_inc(&table->users);
    + break;
    + }
    + atomic_dec(&lookups);
    + } while (atomic_read(&removals) != old_removals);
    + return table;
    +}
    +
    +static void
    +drop_table(struct unwind_table *table)
    +{
    + if (table)
    + atomic_dec(&table->users);
    +}
    +
    +static void
    +init_unwind_table (struct unwind_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)
    +{
    + 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;
    + table->link = NULL;
    + atomic_set(&table->users, 0);
    + table->name = name;
    +}
    +
    +/*
    + * Initialize unwind support.
    + */
    +void __init
    +unwind_init(void)
    +{
    + init_unwind_table(&root_table, "kernel",
    + _text, _end - _text,
    + NULL, 0,
    + __start_unwind, __end_unwind - __start_unwind);
    +}
    +
    +void *
    +unwind_add_table(struct module *module,
    + const void *table_start,
    + unsigned long table_size)
    +{
    + struct unwind_table *table;
    +
    + if (table_size <= 0)
    + return NULL;
    +
    + table = kmalloc(sizeof(*table), GFP_USER);
    + if (!table)
    + return NULL;
    +
    + init_unwind_table(table, module->name,
    + module->module_core, module->core_size,
    + module->module_init, module->init_size,
    + table_start, table_size);
    +
    + spin_lock(&table_lock);
    + if (last_table)
    + last_table->link = table;
    + else
    + root_table.link = table;
    + last_table = table;
    + spin_unlock(&table_lock);
    +
    + return table;
    +}
    +
    +void
    +unwind_remove_table(void *handle, int init_only)
    +{
    + struct unwind_table *table = handle, *prev;
    +
    + if (!table || table == &root_table)
    + return;
    +
    + if (init_only && table == last_table) {
    + table->init.pc = 0;
    + table->init.range = 0;
    + return;
    + }
    +
    + spin_lock(&table_lock);
    + for (prev = &root_table; prev->link && prev->link != table; prev = prev->link)
    + ;
    + if (prev->link) {
    + if (init_only) {
    + table->init.pc = 0;
    + table->init.range = 0;
    + table = NULL;
    + } else {
    + prev->link = table->link;
    + if (!prev->link)
    + last_table = prev;
    + atomic_inc(&removals);
    + }
    + } else
    + table = NULL;
    + spin_unlock(&table_lock);
    +
    + if (table) {
    + while (atomic_read(&table->users) || atomic_read(&lookups))
    + msleep(1);
    + kfree(table);
    + }
    +}
    +
    +static uleb128_t
    +get_uleb128(const uint8_t **pcur, const uint8_t *end)
    +{
    + const uint8_t *cur = *pcur;
    + uleb128_t value;
    + unsigned 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 uint8_t **pcur, const uint8_t *end)
    +{
    + const uint8_t *cur = *pcur;
    + sleb128_t value;
    + unsigned 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 unsigned long
    +read_pointer(const uint8_t **pLoc, const void *end, signed ptrType)
    +{
    + unsigned long value = 0;
    + union {
    + const uint8_t *p8;
    + const uint16_t *p16;
    + const uint32_t *p32;
    + const unsigned long *pul;
    + } ptr;
    +
    + if (ptrType < 0 || ptrType == DW_EH_PE_omit)
    + return 0;
    + ptr.p8 = *pLoc;
    + switch(ptrType & DW_EH_PE_FORM) {
    + case DW_EH_PE_data2:
    + if (end < (const void *)(ptr.p16 + 1))
    + return 0;
    + BUILD_BUG_ON(sizeof(uint16_t) > sizeof(unsigned long));
    + value = get_unaligned(ptr.p16++);
    + break;
    + case DW_EH_PE_data4:
    +#ifdef CONFIG_64BIT
    + if (end < (const void *)(ptr.p32 + 1))
    + return 0;
    + BUILD_BUG_ON(sizeof(uint32_t) > sizeof(unsigned long));
    + value = get_unaligned(ptr.p32++);
    + break;
    + case DW_EH_PE_data8:
    + BUILD_BUG_ON(sizeof(uint64_t) != sizeof(unsigned long));
    +#else
    + BUILD_BUG_ON(sizeof(uint32_t) != sizeof(unsigned long));
    +#endif
    + case DW_EH_PE_native:
    + if (end < (const void *)(ptr.pul + 1))
    + return 0;
    + value = get_unaligned(ptr.pul++);
    + break;
    + case DW_EH_PE_leb128:
    + BUILD_BUG_ON(sizeof(uleb128_t) > sizeof(unsigned long));
    + value = ptrType & DW_EH_PE_signed ? get_sleb128(&ptr.p8, end) : get_uleb128(&ptr.p8, end);
    + if ((const void *)ptr.p8 > end)
    + return 0;
    + break;
    + default:
    + return 0;
    + }
    + switch(ptrType & DW_EH_PE_ADJUST) {
    + case DW_EH_PE_abs:
    + break;
    + case DW_EH_PE_pcrel:
    + value += (unsigned long)*pLoc;
    + break;
    + default:
    + return 0;
    + }
    + if ((ptrType & DW_EH_PE_indirect) && __get_user(value, (unsigned long *)value))
    + return 0;
    + *pLoc = ptr.p8;
    + return value;
    +}
    +
    +static signed
    +fde_pointer_type(const uint32_t *cie)
    +{
    + const uint8_t *ptr = (const uint8_t *)(cie + 2);
    + unsigned version = *ptr;
    +
    + if (version != 1)
    + return -1; // unsupported
    + if (*++ptr) {
    + const char *aug;
    + const uint8_t *end = (const uint8_t *)(cie + 1) + *cie;
    + uleb128_t len;
    +
    + // check if augmentation size is first (and thus present)
    + if (*ptr != 'z')
    + return -1;
    + // check if augmentation string is nul-terminated
    + if ((ptr = memchr(aug = (const void *)ptr, 0, end - ptr)) == NULL)
    + return -1;
    + ++ptr; // skip terminator
    + get_uleb128(&ptr, end); // skip code alignment
    + get_sleb128(&ptr, end); // skip data alignment
    + version <= 1 ? (void)++ptr : (void)get_uleb128(&ptr, end); // skip return address column
    + len = get_uleb128(&ptr, end); // augmentation length
    + if (ptr + len < ptr || ptr + len > end)
    + return -1;
    + end = ptr + len;
    + while (*++aug) {
    + if (ptr >= end)
    + return -1;
    + switch(*aug) {
    + case 'L':
    + ++ptr;
    + break;
    + case 'P': {
    + signed ptrType = *ptr++;
    +
    + if (!read_pointer(&ptr, end, ptrType) || ptr > end)
    + return -1;
    + }
    + break;
    + case 'R':
    + return *ptr;
    + default:
    + return -1;
    + }
    + }
    + }
    + return DW_EH_PE_native|DW_EH_PE_abs;
    +}
    +
    +static int
    +advance_loc(unsigned long delta, struct unwind_state *state)
    +{
    + state->loc += delta * state->codeAlign;
    + return delta > 0;
    +}
    +
    +static void
    +set_rule(uleb128_t reg, enum item_location where, uleb128_t value, struct unwind_state *state)
    +{
    + if (reg < ARRAY_SIZE(state->regs)) {
    + state->regs[reg].where = where;
    + state->regs[reg].value = value;
    + }
    +}
    +
    +static int
    +processCFI(const uint8_t *start,
    + const uint8_t *end,
    + unsigned long targetLoc,
    + signed ptrType,
    + struct unwind_state *state)
    +{
    + union {
    + const uint8_t *p8;
    + const uint16_t *p16;
    + const uint32_t *p32;
    + } ptr;
    + int result = 1;
    +
    + if (start != state->cieStart) {
    + state->loc = state->org;
    + result = processCFI(state->cieStart, state->cieEnd, 0, ptrType, state);
    + if (targetLoc == 0 && state->label == NULL)
    + return result;
    + }
    + for (ptr.p8 = start; result && ptr.p8 < end; ) {
    + switch(*ptr.p8 >> 6) {
    + uleb128_t value;
    +
    + case 0:
    + switch(*ptr.p8++) {
    + case DW_CFA_nop:
    + break;
    + case DW_CFA_set_loc:
    + if ((state->loc = read_pointer(&ptr.p8, end, ptrType)) == 0)
    + result = 0;
    + break;
    + case DW_CFA_advance_loc1:
    + result = ptr.p8 < end && advance_loc(*ptr.p8++, state);
    + break;
    + case DW_CFA_advance_loc2:
    + result = ptr.p8 <= end + 2 && advance_loc(*ptr.p16++, state);
    + break;
    + case DW_CFA_advance_loc4:
    + result = ptr.p8 <= end + 4 && advance_loc(*ptr.p32++, state);
    + break;
    + case DW_CFA_offset_extended:
    + value = get_uleb128(&ptr.p8, end);
    + set_rule(value, Memory, get_uleb128(&ptr.p8, end), state);
    + break;
    + case DW_CFA_val_offset:
    + value = get_uleb128(&ptr.p8, end);
    + set_rule(value, Value, get_uleb128(&ptr.p8, end), state);
    + break;
    + case DW_CFA_offset_extended_sf:
    + value = get_uleb128(&ptr.p8, end);
    + set_rule(value, Memory, get_sleb128(&ptr.p8, end), state);
    + break;
    + case DW_CFA_val_offset_sf:
    + value = get_uleb128(&ptr.p8, end);
    + set_rule(value, Value, get_sleb128(&ptr.p8, end), state);
    + break;
    + case DW_CFA_restore_extended:
    + case DW_CFA_undefined:
    + case DW_CFA_same_value:
    + set_rule(get_uleb128(&ptr.p8, end), Nowhere, 0, state);
    + break;
    + case DW_CFA_register:
    + value = get_uleb128(&ptr.p8, end);
    + set_rule(value, Register, get_uleb128(&ptr.p8, end), state);
    + break;
    + case DW_CFA_remember_state:
    + if (ptr.p8 == state->label) {
    + state->label = NULL;
    + return 1;
    + }
    + if (state->stackDepth >= MAX_STACK_DEPTH)
    + return 0;
    + state->stack[state->stackDepth++] = ptr.p8;
    + break;
    + case DW_CFA_restore_state:
    + if (state->stackDepth) {
    + const uleb128_t loc = state->loc;
    + const uint8_t *label = state->label;
    +
    + state->label = state->stack[state->stackDepth - 1];
    + memcpy(&state->cfa, &badCFA, sizeof(state->cfa));
    + memset(state->regs, 0, sizeof(state->regs));
    + state->stackDepth = 0;
    + result = processCFI(start, end, 0, ptrType, state);
    + state->loc = loc;
    + state->label = label;
    + } else
    + return 0;
    + break;
    + case DW_CFA_def_cfa:
    + state->cfa.reg = get_uleb128(&ptr.p8, end);
    + //nobreak
    + case DW_CFA_def_cfa_offset:
    + state->cfa.offs = get_uleb128(&ptr.p8, end);
    + break;
    + case DW_CFA_def_cfa_sf:
    + state->cfa.reg = get_uleb128(&ptr.p8, end);
    + //nobreak
    + case DW_CFA_def_cfa_offset_sf:
    + state->cfa.offs = get_sleb128(&ptr.p8, end) * state->dataAlign;
    + break;
    + case DW_CFA_def_cfa_register:
    + state->cfa.reg = get_uleb128(&ptr.p8, end);
    + break;
    +//todo case DW_CFA_def_cfa_expression:
    +//todo case DW_CFA_expression:
    +//todo case DW_CFA_val_expression:
    + case DW_CFA_GNU_args_size:
    + get_uleb128(&ptr.p8, end);
    + break;
    + case DW_CFA_GNU_negative_offset_extended:
    + value = get_uleb128(&ptr.p8, end);
    + set_rule(value, Memory, (uleb128_t)0 - get_uleb128(&ptr.p8, end), state);
    + break;
    + case DW_CFA_GNU_window_save:
    + default:
    + result = 0;
    + break;
    + }
    + break;
    + case 1:
    + result = advance_loc(*ptr.p8++ & 0x3f, state);
    + break;
    + case 2:
    + value = *ptr.p8++ & 0x3f;
    + set_rule(value, Memory, get_uleb128(&ptr.p8, end), state);
    + break;
    + case 3:
    + set_rule(*ptr.p8++ & 0x3f, Nowhere, 0, state);
    + break;
    + }
    + if (ptr.p8 > end)
    + result = 0;
    + if (result && targetLoc != 0 && targetLoc < state->loc)
    + return 1;
    + }
    + return result
    + && ptr.p8 == end
    + && (targetLoc == 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.
    + targetLoc < state->loc &&*/ state->label == NULL));
    +}
    +
    +/*
    + * Unwind to previous to frame. Returns 0 if successful, negative
    + * number in case of an error.
    + */
    +int
    +unwind(struct unwind_frame_info *frame)
    +{
    +#define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs])
    + const uint32_t *fde = NULL, *cie = NULL;
    + const uint8_t *ptr = NULL, *end = NULL;
    + unsigned long startLoc = 0, endLoc = 0, cfa;
    + unsigned i;
    + signed ptrType = -1;
    + uleb128_t retAddrReg = 0;
    + struct unwind_table *table;
    + struct unwind_state state;
    +
    + if (UNW_PC(frame) == 0)
    + return -EINVAL;
    + if ((table = find_table(UNW_PC(frame))) != NULL && !(table->size & (sizeof(*fde) - 1))) {
    + unsigned long tableSize = table->size;
    +
    + for (fde = (uint32_t *)table->address;
    + tableSize > sizeof(*fde) && tableSize - sizeof(*fde) >= *fde;
    + tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) {
    + if (!*fde || (*fde & (sizeof(*fde) - 1)))
    + break;
    + if (!fde[1])
    + continue; // this is a CIE
    + if ((fde[1] & (sizeof(*fde) - 1))
    + || fde[1] > (unsigned long)(fde + 1) - (unsigned long)table->address)
    + continue; // 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]
    + || (ptrType = fde_pointer_type(cie)) < 0) {
    + cie = NULL; // this is not a (valid) CIE
    + continue;
    + }
    + ptr = (const uint8_t *)(fde + 2);
    + startLoc = read_pointer(&ptr, (const uint8_t *)(fde + 1) + *fde, ptrType);
    + endLoc = startLoc + read_pointer(&ptr,
    + (const uint8_t *)(fde + 1) + *fde,
    + ptrType & DW_EH_PE_indirect
    + ? ptrType
    + : ptrType & (DW_EH_PE_FORM|DW_EH_PE_signed));
    + if (UNW_PC(frame) >= startLoc && UNW_PC(frame) < endLoc)
    + break;
    + cie = NULL;
    + }
    + }
    + if (cie != NULL) {
    + memset(&state, 0, sizeof(state));
    + state.cieEnd = ptr; // keep here temporarily
    + ptr = (const uint8_t *)(cie + 2);
    + end = (const uint8_t *)(cie + 1) + *cie;
    + if ((state.version = *ptr) != 1)
    + cie = NULL; // unsupported version
    + else if (*++ptr) {
    + // check if augmentation size is first (and thus present)
    + if (*ptr == 'z') {
    + // check for ignorable (or already handled) nul-terminated augmentation string
    + while (++ptr < end && *ptr)
    + if (strchr("LPR", *ptr) == NULL)
    + break;
    + }
    + if (ptr >= end || *ptr)
    + cie = NULL;
    + }
    + ++ptr;
    + }
    + if (cie != NULL) {
    + // get code aligment factor
    + state.codeAlign = get_uleb128(&ptr, end);
    + // get data aligment factor
    + state.dataAlign = get_sleb128(&ptr, end);
    + if (state.codeAlign == 0 || state.dataAlign == 0 || ptr >= end)
    + cie = NULL;
    + else {
    + retAddrReg = state.version <= 1 ? *ptr++ : get_uleb128(&ptr, end);
    + // skip augmentation
    + if (((const char *)(cie + 2))[1] == 'z')
    + ptr += get_uleb128(&ptr, end);
    + if (ptr > end
    + || retAddrReg >= ARRAY_SIZE(reg_info)
    + || REG_INVALID(retAddrReg)
    + || reg_info[retAddrReg].width != sizeof(unsigned long))
    + cie = NULL;
    + }
    + }
    + if (cie != NULL) {
    + state.cieStart = ptr;
    + ptr = state.cieEnd;
    + state.cieEnd = end;
    + end = (const uint8_t *)(fde + 1) + *fde;
    + // skip augmentation
    + if (((const char *)(cie + 2))[1] == 'z') {
    + uleb128_t augSize = get_uleb128(&ptr, end);
    +
    + if ((ptr += augSize) > end)
    + fde = NULL;
    + }
    + }
    + if (cie == NULL || fde == NULL) {
    +#ifdef UNW_FP
    + unsigned long top, bottom;
    +#endif
    +
    + drop_table(table);
    +#ifdef UNW_FP
    + top = STACK_TOP(frame->task);
    + bottom = STACK_BOTTOM(frame->task);
    +# if FRAME_RETADDR_OFFSET < 0
    + if (UNW_SP(frame) < top && UNW_FP(frame) <= UNW_SP(frame) && bottom < UNW_FP(frame)
    +# else
    + if (UNW_SP(frame) > top && UNW_FP(frame) >= UNW_SP(frame) && bottom > UNW_FP(frame)
    +# endif
    + && !((UNW_SP(frame) | UNW_FP(frame)) & (sizeof(unsigned long) - 1))) {
    + unsigned long link;
    +
    + if (!__get_user(link, (unsigned long *)(UNW_FP(frame) + FRAME_LINK_OFFSET))
    +# if FRAME_RETADDR_OFFSET < 0
    + && link > bottom && link < UNW_FP(frame)
    +# else
    + && link > UNW_FP(frame) && link < bottom
    +# endif
    + && !(link & (sizeof(link) - 1))
    + && !__get_user(UNW_PC(frame), (unsigned long *)(UNW_FP(frame) + FRAME_RETADDR_OFFSET))) {
    + UNW_SP(frame) = UNW_FP(frame) + FRAME_RETADDR_OFFSET
    +# if FRAME_RETADDR_OFFSET < 0
    + -
    +# else
    + +
    +# endif
    + sizeof(UNW_PC(frame));
    + UNW_FP(frame) = link;
    + return 0;
    + }
    + }
    +#endif
    + return -ENXIO;
    + }
    + state.org = startLoc;
    + memcpy(&state.cfa, &badCFA, sizeof(state.cfa));
    + // process instructions
    + if (!processCFI(ptr, end, UNW_PC(frame), ptrType, &state)
    + || state.loc > endLoc
    + || state.regs[retAddrReg].where == Nowhere
    + || state.cfa.reg >= ARRAY_SIZE(reg_info)
    + || reg_info[state.cfa.reg].width != sizeof(unsigned long)
    + || state.cfa.offs % sizeof(unsigned long)) {
    + drop_table(table);
    + return -EIO;
    + }
    + drop_table(table);
    + // update frame
    + cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs;
    + startLoc = min((unsigned long)UNW_SP(frame), cfa);
    + endLoc = max((unsigned long)UNW_SP(frame), cfa);
    +#ifndef CONFIG_64BIT
    +# define CASES CASE(8); CASE(16); CASE(32)
    +#else
    +# define CASES CASE(8); CASE(16); CASE(32); CASE(64)
    +#endif
    + for (i = 0; i < ARRAY_SIZE(state.regs); ++i) {
    + if (REG_INVALID(i)) {
    + if (state.regs[i].where == Nowhere)
    + continue;
    + return -EIO;
    + }
    + switch(state.regs[i].where) {
    + default:
    + break;
    + case Register:
    + 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)
    + return -EIO;
    + switch(reg_info[state.regs[i].value].width) {
    +#define CASE(n) \
    + case sizeof(uint##n##_t): \
    + state.regs[i].value = FRAME_REG(state.regs[i].value, const uint##n##_t); \
    + break
    + CASES;
    +#undef CASE
    + default:
    + return -EIO;
    + }
    + break;
    + }
    + }
    + 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(UNW_SP(frame))
    + || &FRAME_REG(i, __typeof__(UNW_SP(frame))) != &UNW_SP(frame))
    + continue;
    + UNW_SP(frame) = cfa;
    + break;
    + case Register:
    + switch(reg_info[i].width) {
    +#define CASE(n) case sizeof(uint##n##_t): \
    + FRAME_REG(i, uint##n##_t) = state.regs[i].value; \
    + break
    + CASES;
    +#undef CASE
    + default:
    + return -EIO;
    + }
    + break;
    + case Value:
    + if (reg_info[i].width != sizeof(unsigned long))
    + return -EIO;
    + FRAME_REG(i, unsigned long) = cfa + state.regs[i].value * state.dataAlign;
    + break;
    + case Memory: {
    + unsigned long addr = cfa + state.regs[i].value * state.dataAlign;
    +
    + if ((state.regs[i].value * state.dataAlign) % sizeof(unsigned long)
    + || addr < startLoc
    + || addr + sizeof(unsigned long) < addr
    + || addr + sizeof(unsigned long) > endLoc)
    + return -EIO;
    + switch(reg_info[i].width) {
    +#define CASE(n) case sizeof(uint##n##_t): \
    + __get_user(FRAME_REG(i, uint##n##_t), (uint##n##_t *)addr); \
    + break
    + CASES;
    +#undef CASE
    + default:
    + return -EIO;
    + }
    + }
    + break;
    + }
    + }
    + return 0;
    +#undef CASES
    +#undef FRAME_REG
    +}
    +EXPORT_SYMBOL_GPL(unwind);
    +
    +int unwind_init_frame_info(struct unwind_frame_info *info, struct task_struct *tsk, /*const*/ struct pt_regs *regs)
    +{
    + info->task = tsk;
    + arch_unw_init_frame_info(info, regs);
    + return 0;
    +}
    +EXPORT_SYMBOL_GPL(unwind_init_frame_info);
    +
    +/*
    + * Prepare to unwind a blocked task.
    + */
    +int unwind_init_blocked(struct unwind_frame_info *info, struct task_struct *tsk)
    +{
    + info->task = tsk;
    + arch_unw_init_blocked(info);
    + return 0;
    +}
    +EXPORT_SYMBOL_GPL(unwind_init_blocked);
    +
    +/*
    + * Prepare to unwind the currently running thread.
    + */
    +int unwind_init_running(struct unwind_frame_info *info,
    + asmlinkage void (*callback)(struct unwind_frame_info *, void *arg),
    + void *arg)
    +{
    + info->task = current;
    + arch_unwind_init_running(info, callback, arg);
    + return 0;
    +}
    +EXPORT_SYMBOL_GPL(unwind_init_running);
    +
    +/*
    + * Unwind until the return pointer is in user-land (or until an error
    + * occurs). Returns 0 if successful, negative number in case of
    + * error.
    + */
    +int unwind_to_user(struct unwind_frame_info *info)
    +{
    + while (!arch_unw_user_mode(info)) {
    + int err = unwind(info);
    +
    + if (err < 0)
    + return err;
    + }
    + return 0;
    +}
    +EXPORT_SYMBOL_GPL(unwind_to_user);
    --- linux-2.6.17-rc4/lib/Kconfig.debug 2006-05-16 15:15:49.000000000 +0200
    +++ 2.6.17-rc4-unwind-generic/lib/Kconfig.debug 2006-05-16 15:16:46.000000000 +0200
    @@ -188,7 +188,7 @@ config FRAME_POINTER

    config UNWIND_INFO
    bool "Compile the kernel with frame unwind information"
    - depends on !IA64
    + depends on !IA64 && !PARISC
    depends on !MODULES || !(MIPS || PARISC || PPC || SUPERH || SPARC64 || V850)
    help
    If you say Y here the resulting kernel image will be slightly larger
    @@ -196,6 +196,14 @@ config UNWIND_INFO
    If you don't debug the kernel, you can say N, but we may not be able
    to solve problems without frame unwind information or frame pointers.

    +config STACK_UNWIND
    + bool "Stack unwind support"
    + depends on UNWIND_INFO
    + depends on n
    + help
    + This enables more precise stack traces, omitting all unrelated
    + occurrences of pointers into kernel code from the dump.
    +
    config FORCED_INLINING
    bool "Force gcc to inline functions marked 'inline'"
    depends on DEBUG_KERNEL

    -
    To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at http://www.tux.org/lkml/

    \
     
     \ /
      Last update: 2006-05-16 16:23    [W:0.082 / U:89.584 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site