123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332 |
- /*
- * Copyright (C) 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
- * Copyright (C) 2002-2006 Novell, Inc.
- * Jan Beulich <jbeulich@novell.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * 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/sched.h>
- #include <linux/module.h>
- #include <linux/bootmem.h>
- #include <linux/sort.h>
- #include <linux/slab.h>
- #include <linux/stop_machine.h>
- #include <linux/uaccess.h>
- #include <linux/ptrace.h>
- #include <asm/sections.h>
- #include <asm/unaligned.h>
- #include <asm/unwind.h>
- extern char __start_unwind[], __end_unwind[];
- /* extern const u8 __start_unwind_hdr[], __end_unwind_hdr[];*/
- /* #define UNWIND_DEBUG */
- #ifdef UNWIND_DEBUG
- int dbg_unw;
- #define unw_debug(fmt, ...) \
- do { \
- if (dbg_unw) \
- pr_info(fmt, ##__VA_ARGS__); \
- } while (0);
- #else
- #define unw_debug(fmt, ...)
- #endif
- #define MAX_STACK_DEPTH 8
- #define EXTRA_INFO(f) { \
- BUILD_BUG_ON_ZERO(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;
- static struct unwind_table {
- struct {
- unsigned long pc;
- unsigned long range;
- } core, init;
- const void *address;
- unsigned long size;
- const unsigned char *header;
- unsigned long hdrsz;
- struct unwind_table *link;
- const char *name;
- } root_table;
- struct unwind_item {
- enum item_location {
- Nowhere,
- Memory,
- Register,
- Value
- } where;
- uleb128_t value;
- };
- struct unwind_state {
- uleb128_t loc, org;
- const u8 *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 u8 *label;
- const u8 *stack[MAX_STACK_DEPTH];
- };
- static const struct cfa badCFA = { ARRAY_SIZE(reg_info), 1 };
- static struct unwind_table *find_table(unsigned long pc)
- {
- struct unwind_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 read_pointer(const u8 **pLoc,
- const void *end, signed ptrType);
- 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,
- 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
- || (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->name = name;
- }
- void __init arc_unwind_init(void)
- {
- init_unwind_table(&root_table, "kernel", _text, _end - _text, NULL, 0,
- __start_unwind, __end_unwind - __start_unwind,
- NULL, 0);
- /*__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)
- 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);
- signed ptrType;
- if (cie == ¬_fde)
- continue;
- if (cie == NULL || cie == &bad_cie)
- return;
- ptrType = fde_pointer_type(cie);
- if (ptrType < 0)
- return;
- ptr = (const u8 *)(fde + 2);
- if (!read_pointer(&ptr, (const u8 *)(fde + 1) + *fde,
- ptrType)) {
- /* FIXME_Rajesh We have 4 instances of null addresses
- * instead of the initial loc addr
- * return;
- */
- WARN(1, "unwinder: FDE->initial_location NULL %p\n",
- (const u8 *)(fde + 1) + *fde);
- }
- ++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); */
- const u32 *cie = (const u32 *)(fde[1]);
- if (fde[1] == 0xffffffff)
- 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 arc_unwind_setup(void)
- {
- setup_unwind_table(&root_table, balloc);
- }
- #ifdef CONFIG_MODULES
- static struct unwind_table *last_table;
- /* Must be called with module_mutex held. */
- 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_KERNEL);
- 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,
- NULL, 0);
- #ifdef UNWIND_DEBUG
- unw_debug("Table added for [%s] %lx %lx\n",
- module->name, table->core.pc, table->core.range);
- #endif
- if (last_table)
- last_table->link = table;
- else
- root_table.link = table;
- last_table = table;
- return table;
- }
- struct unlink_table_info {
- struct unwind_table *table;
- int init_only;
- };
- static int unlink_table(void *arg)
- {
- struct unlink_table_info *info = arg;
- struct unwind_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;
- }
- /* Must be called with module_mutex held. */
- void unwind_remove_table(void *handle, int init_only)
- {
- struct unwind_table *table = handle;
- 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;
- unlink_table(&info); /* XXX: SMP */
- kfree(table);
- }
- #endif /* CONFIG_MODULES */
- static uleb128_t get_uleb128(const u8 **pcur, const u8 *end)
- {
- const u8 *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 u8 **pcur, const u8 *end)
- {
- const u8 *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 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] == 0xffffffff)
- return ¬_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); */
- cie = (u32 *) fde[1];
- if (*cie <= sizeof(*cie) + 4 || *cie >= fde[1] - sizeof(*fde)
- || (*cie & (sizeof(*cie) - 1))
- || (cie[1] != 0xffffffff))
- return NULL; /* this is not a (valid) CIE */
- return cie;
- }
- static unsigned long read_pointer(const u8 **pLoc, const void *end,
- signed ptrType)
- {
- unsigned long value = 0;
- union {
- const u8 *p8;
- const u16 *p16u;
- const s16 *p16s;
- const u32 *p32u;
- const s32 *p32s;
- 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.p16u + 1))
- return 0;
- if (ptrType & DW_EH_PE_signed)
- value = get_unaligned((u16 *) ptr.p16s++);
- else
- value = get_unaligned((u16 *) ptr.p16u++);
- break;
- case DW_EH_PE_data4:
- #ifdef CONFIG_64BIT
- if (end < (const void *)(ptr.p32u + 1))
- return 0;
- if (ptrType & DW_EH_PE_signed)
- value = get_unaligned(ptr.p32s++);
- else
- value = get_unaligned(ptr.p32u++);
- break;
- case DW_EH_PE_data8:
- BUILD_BUG_ON(sizeof(u64) != sizeof(value));
- #else
- BUILD_BUG_ON(sizeof(u32) != sizeof(value));
- #endif
- case DW_EH_PE_native:
- if (end < (const void *)(ptr.pul + 1))
- return 0;
- value = get_unaligned((unsigned long *)ptr.pul++);
- break;
- case DW_EH_PE_leb128:
- BUILD_BUG_ON(sizeof(uleb128_t) > sizeof(value));
- 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 __user *)value))
- return 0;
- *pLoc = ptr.p8;
- return value;
- }
- static signed fde_pointer_type(const u32 *cie)
- {
- const u8 *ptr = (const u8 *)(cie + 2);
- unsigned version = *ptr;
- if (version != 1)
- return -1; /* unsupported */
- if (*++ptr) {
- const char *aug;
- const u8 *end = (const u8 *)(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 */
- aug = (const void *)ptr;
- ptr = memchr(aug, 0, end - ptr);
- if (ptr == NULL)
- return -1;
- ++ptr; /* skip terminator */
- get_uleb128(&ptr, end); /* skip code alignment */
- get_sleb128(&ptr, end); /* skip data alignment */
- /* skip return address column */
- version <= 1 ? (void) ++ptr : (void)get_uleb128(&ptr, end);
- 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;
- /* FIXME_Rajesh: Probably we are defining for the initial range as well;
- return delta > 0;
- */
- unw_debug("delta %3lu => loc 0x%lx: ", delta, state->loc);
- return 1;
- }
- 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;
- #ifdef UNWIND_DEBUG
- unw_debug("r%lu: ", reg);
- switch (where) {
- case Nowhere:
- unw_debug("s ");
- break;
- case Memory:
- unw_debug("c(%lu) ", value);
- break;
- case Register:
- unw_debug("r(%lu) ", value);
- break;
- case Value:
- unw_debug("v(%lu) ", value);
- break;
- default:
- break;
- }
- #endif
- }
- }
- static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc,
- signed ptrType, struct unwind_state *state)
- {
- union {
- const u8 *p8;
- const u16 *p16;
- const u32 *p32;
- } ptr;
- int result = 1;
- u8 opcode;
- 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:
- opcode = *ptr.p8++;
- switch (opcode) {
- case DW_CFA_nop:
- unw_debug("cfa nop ");
- break;
- case DW_CFA_set_loc:
- state->loc = read_pointer(&ptr.p8, end,
- ptrType);
- if (state->loc == 0)
- result = 0;
- unw_debug("cfa_set_loc: 0x%lx ", state->loc);
- break;
- case DW_CFA_advance_loc1:
- unw_debug("\ncfa advance loc1:");
- result = ptr.p8 < end
- && advance_loc(*ptr.p8++, state);
- break;
- case DW_CFA_advance_loc2:
- value = *ptr.p8++;
- value += *ptr.p8++ << 8;
- unw_debug("\ncfa advance loc2:");
- result = ptr.p8 <= end + 2
- /* && advance_loc(*ptr.p16++, state); */
- && advance_loc(value, state);
- break;
- case DW_CFA_advance_loc4:
- unw_debug("\ncfa advance loc4:");
- result = ptr.p8 <= end + 4
- && advance_loc(*ptr.p32++, state);
- break;
- case DW_CFA_offset_extended:
- value = get_uleb128(&ptr.p8, end);
- unw_debug("cfa_offset_extended: ");
- 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:
- unw_debug("cfa_restore_extended: ");
- case DW_CFA_undefined:
- unw_debug("cfa_undefined: ");
- case DW_CFA_same_value:
- unw_debug("cfa_same_value: ");
- set_rule(get_uleb128(&ptr.p8, end), Nowhere, 0,
- state);
- break;
- case DW_CFA_register:
- unw_debug("cfa_register: ");
- value = get_uleb128(&ptr.p8, end);
- set_rule(value,
- Register,
- get_uleb128(&ptr.p8, end), state);
- break;
- case DW_CFA_remember_state:
- unw_debug("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:
- unw_debug("cfa_restore_state: ");
- if (state->stackDepth) {
- const uleb128_t loc = state->loc;
- const u8 *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);
- unw_debug("cfa_def_cfa: r%lu ", state->cfa.reg);
- /*nobreak*/
- case DW_CFA_def_cfa_offset:
- state->cfa.offs = get_uleb128(&ptr.p8, end);
- unw_debug("cfa_def_cfa_offset: 0x%lx ",
- state->cfa.offs);
- 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:
- unw_debug("cfa_def_cfa_regsiter: ");
- 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:
- unw_debug("UNKNOWN OPCODE 0x%x\n", opcode);
- result = 0;
- break;
- }
- break;
- case 1:
- unw_debug("\ncfa_adv_loc: ");
- result = advance_loc(*ptr.p8++ & 0x3f, state);
- break;
- case 2:
- unw_debug("cfa_offset: ");
- value = *ptr.p8++ & 0x3f;
- set_rule(value, Memory, get_uleb128(&ptr.p8, end),
- state);
- break;
- case 3:
- unw_debug("cfa_restore: ");
- 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 arc_unwind(struct unwind_frame_info *frame)
- {
- #define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs])
- const u32 *fde = NULL, *cie = NULL;
- const u8 *ptr = NULL, *end = NULL;
- unsigned long pc = UNW_PC(frame) - frame->call_frame;
- unsigned long startLoc = 0, endLoc = 0, cfa;
- unsigned i;
- signed ptrType = -1;
- uleb128_t retAddrReg = 0;
- const struct unwind_table *table;
- struct unwind_state state;
- unsigned long *fptr;
- unsigned long addr;
- unw_debug("\n\nUNWIND FRAME:\n");
- unw_debug("PC: 0x%lx BLINK: 0x%lx, SP: 0x%lx, FP: 0x%x\n",
- UNW_PC(frame), UNW_BLINK(frame), UNW_SP(frame),
- UNW_FP(frame));
- if (UNW_PC(frame) == 0)
- return -EINVAL;
- #ifdef UNWIND_DEBUG
- {
- unsigned long *sptr = (unsigned long *)UNW_SP(frame);
- unw_debug("\nStack Dump:\n");
- for (i = 0; i < 20; i++, sptr++)
- unw_debug("0x%p: 0x%lx\n", sptr, *sptr);
- unw_debug("\n");
- }
- #endif
- table = find_table(pc);
- if (table != NULL
- && !(table->size & (sizeof(*fde) - 1))) {
- const u8 *hdr = table->header;
- unsigned long tableSize;
- smp_rmb();
- if (hdr && hdr[0] == 1) {
- 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:
- tableSize = 0;
- break;
- }
- ptr = hdr + 4;
- end = hdr + table->hdrsz;
- if (tableSize && read_pointer(&ptr, end, hdr[1])
- == (unsigned long)table->address
- && (i = read_pointer(&ptr, end, hdr[2])) > 0
- && i == (end - ptr) / (2 * tableSize)
- && !((end - ptr) % (2 * tableSize))) {
- do {
- 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);
- if (cie != NULL
- && cie != &bad_cie
- && cie != ¬_fde
- && (ptrType = fde_pointer_type(cie)) >= 0
- && read_pointer(&ptr,
- (const u8 *)(fde + 1) + *fde,
- ptrType) == startLoc) {
- 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 >= 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 == ¬_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) {
- memset(&state, 0, sizeof(state));
- state.cieEnd = ptr; /* keep here temporarily */
- ptr = (const u8 *)(cie + 2);
- end = (const u8 *)(cie + 1) + *cie;
- frame->call_frame = 1;
- if ((state.version = *ptr) != 1)
- cie = NULL; /* unsupported version */
- else if (*++ptr) {
- /* check if augmentation size is first (thus present) */
- if (*ptr == 'z') {
- while (++ptr < end && *ptr) {
- switch (*ptr) {
- /* chk for ignorable or already handled
- * nul-terminated augmentation string */
- case 'L':
- case 'P':
- case 'R':
- continue;
- case 'S':
- frame->call_frame = 0;
- continue;
- default:
- break;
- }
- 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);
- unw_debug("CIE Frame Info:\n");
- unw_debug("return Address register 0x%lx\n",
- retAddrReg);
- unw_debug("data Align: %ld\n", state.dataAlign);
- unw_debug("code Align: %lu\n", state.codeAlign);
- /* skip augmentation */
- if (((const char *)(cie + 2))[1] == 'z') {
- uleb128_t augSize = get_uleb128(&ptr, end);
- ptr += augSize;
- }
- 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 u8 *)(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 CONFIG_FRAME_POINTER
- unsigned long top, bottom;
- top = STACK_TOP_UNW(frame->task);
- bottom = STACK_BOTTOM_UNW(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));
- unw_debug("\nProcess instructions\n");
- /* process instructions
- * For ARC, we optimize by having blink(retAddrReg) with
- * the sameValue in the leaf function, so we should not check
- * state.regs[retAddrReg].where == Nowhere
- */
- if (!processCFI(ptr, end, pc, 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))
- return -EIO;
- #ifdef UNWIND_DEBUG
- unw_debug("\n");
- unw_debug("\nRegister State Based on the rules parsed from FDE:\n");
- for (i = 0; i < ARRAY_SIZE(state.regs); ++i) {
- if (REG_INVALID(i))
- continue;
- switch (state.regs[i].where) {
- case Nowhere:
- break;
- case Memory:
- unw_debug(" r%d: c(%lu),", i, state.regs[i].value);
- break;
- case Register:
- unw_debug(" r%d: r(%lu),", i, state.regs[i].value);
- break;
- case Value:
- unw_debug(" r%d: v(%lu),", i, state.regs[i].value);
- break;
- }
- }
- unw_debug("\n");
- #endif
- /* update frame */
- #ifndef CONFIG_AS_CFI_SIGNAL_FRAME
- if (frame->call_frame
- && !UNW_DEFAULT_RA(state.regs[retAddrReg], state.dataAlign))
- frame->call_frame = 0;
- #endif
- cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs;
- startLoc = min_t(unsigned long, UNW_SP(frame), cfa);
- endLoc = max_t(unsigned long, UNW_SP(frame), cfa);
- if (STACK_LIMIT(startLoc) != STACK_LIMIT(endLoc)) {
- startLoc = min(STACK_LIMIT(cfa), cfa);
- endLoc = max(STACK_LIMIT(cfa), cfa);
- }
- unw_debug("\nCFA reg: 0x%lx, offset: 0x%lx => 0x%lx\n",
- state.cfa.reg, state.cfa.offs, cfa);
- 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) {
- case sizeof(u8):
- state.regs[i].value =
- FRAME_REG(state.regs[i].value, const u8);
- break;
- case sizeof(u16):
- state.regs[i].value =
- FRAME_REG(state.regs[i].value, const u16);
- break;
- case sizeof(u32):
- state.regs[i].value =
- FRAME_REG(state.regs[i].value, const u32);
- break;
- #ifdef CONFIG_64BIT
- case sizeof(u64):
- state.regs[i].value =
- FRAME_REG(state.regs[i].value, const u64);
- break;
- #endif
- default:
- return -EIO;
- }
- break;
- }
- }
- unw_debug("\nRegister state after evaluation with realtime Stack:\n");
- fptr = (unsigned long *)(&frame->regs);
- for (i = 0; i < ARRAY_SIZE(state.regs); ++i, fptr++) {
- 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) {
- case sizeof(u8):
- FRAME_REG(i, u8) = state.regs[i].value;
- break;
- case sizeof(u16):
- FRAME_REG(i, u16) = state.regs[i].value;
- break;
- case sizeof(u32):
- FRAME_REG(i, u32) = state.regs[i].value;
- break;
- #ifdef CONFIG_64BIT
- case sizeof(u64):
- FRAME_REG(i, u64) = state.regs[i].value;
- break;
- #endif
- 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:
- 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) {
- case sizeof(u8):
- __get_user(FRAME_REG(i, u8),
- (u8 __user *)addr);
- break;
- case sizeof(u16):
- __get_user(FRAME_REG(i, u16),
- (u16 __user *)addr);
- break;
- case sizeof(u32):
- __get_user(FRAME_REG(i, u32),
- (u32 __user *)addr);
- break;
- #ifdef CONFIG_64BIT
- case sizeof(u64):
- __get_user(FRAME_REG(i, u64),
- (u64 __user *)addr);
- break;
- #endif
- default:
- return -EIO;
- }
- break;
- }
- unw_debug("r%d: 0x%lx ", i, *fptr);
- }
- return 0;
- #undef FRAME_REG
- }
- EXPORT_SYMBOL(arc_unwind);
|