From patchwork Tue Mar 22 18:37:07 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ken Werner X-Patchwork-Id: 737 Return-Path: Delivered-To: unknown Received: from imap.gmail.com (74.125.159.109) by localhost6.localdomain6 with IMAP4-SSL; 08 Jun 2011 14:45:18 -0000 Delivered-To: patches@linaro.org Received: by 10.204.113.5 with SMTP id y5cs10158bkp; Tue, 22 Mar 2011 11:37:13 -0700 (PDT) Received: by 10.227.183.133 with SMTP id cg5mr5672089wbb.49.1300819033265; Tue, 22 Mar 2011 11:37:13 -0700 (PDT) Received: from mtagate2.uk.ibm.com (mtagate2.uk.ibm.com [194.196.100.162]) by mx.google.com with ESMTPS id j7si11625708wbj.99.2011.03.22.11.37.13 (version=TLSv1/SSLv3 cipher=OTHER); Tue, 22 Mar 2011 11:37:13 -0700 (PDT) Received-SPF: neutral (google.com: 194.196.100.162 is neither permitted nor denied by best guess record for domain of ken.werner@linaro.org) client-ip=194.196.100.162; Authentication-Results: mx.google.com; spf=neutral (google.com: 194.196.100.162 is neither permitted nor denied by best guess record for domain of ken.werner@linaro.org) smtp.mail=ken.werner@linaro.org Received: from d06nrmr1806.portsmouth.uk.ibm.com (d06nrmr1806.portsmouth.uk.ibm.com [9.149.39.193]) by mtagate2.uk.ibm.com (8.13.1/8.13.1) with ESMTP id p2MIbB5q010890 for ; Tue, 22 Mar 2011 18:37:11 GMT Received: from d06av10.portsmouth.uk.ibm.com (d06av10.portsmouth.uk.ibm.com [9.149.37.251]) by d06nrmr1806.portsmouth.uk.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id p2MIbcNa1179800 for ; Tue, 22 Mar 2011 18:37:38 GMT Received: from d06av10.portsmouth.uk.ibm.com (loopback [127.0.0.1]) by d06av10.portsmouth.uk.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id p2MIbBHP016709 for ; Tue, 22 Mar 2011 12:37:11 -0600 Received: from localhost.localdomain (dyn-9-152-224-51.boeblingen.de.ibm.com [9.152.224.51]) by d06av10.portsmouth.uk.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id p2MIb9Tk016683; Tue, 22 Mar 2011 12:37:10 -0600 From: Ken Werner To: libunwind-devel@nongnu.org Subject: [PATCH 2/3] Add module for parsing ARM-specific unwind tables Date: Tue, 22 Mar 2011 18:37:07 +0000 Message-Id: <1300819028-31314-3-git-send-email-ken.werner@linaro.org> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1300819028-31314-1-git-send-email-ken.werner@linaro.org> References: <1300819028-31314-1-git-send-email-ken.werner@linaro.org> From: Zachary T Welch Handles lookup, extracting unwind entries, and decoding the entry. Signed-off-by: Ken Werner --- include/tdep-arm/ex_tables.h | 75 +++++++ include/tdep-arm/libunwind_i.h | 1 + src/Makefile.am | 3 +- src/arm/ex_tables.c | 432 ++++++++++++++++++++++++++++++++++++++++ src/elfxx.h | 4 +- 5 files changed, 512 insertions(+), 3 deletions(-) create mode 100644 include/tdep-arm/ex_tables.h create mode 100644 src/arm/ex_tables.c diff --git a/include/tdep-arm/ex_tables.h b/include/tdep-arm/ex_tables.h new file mode 100644 index 0000000..17de86b --- /dev/null +++ b/include/tdep-arm/ex_tables.h @@ -0,0 +1,75 @@ +/* libunwind - a platform-independent unwind library + Copyright 2011 Linaro Limited + +This file is part of libunwind. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#ifndef ARM_EX_TABLES_H +#define ARM_EX_TABLES_H + +struct arm_exidx_entry +{ + uint32_t addr; + uint32_t data; +}; + +struct arm_exidx_table +{ + const char *name; + struct arm_exidx_entry *start; + struct arm_exidx_entry *end; + void *start_addr; + void *end_addr; +}; + +typedef enum arm_exbuf_cmd +{ + ARM_EXIDX_CMD_FINISH, + ARM_EXIDX_CMD_DATA_PUSH, + ARM_EXIDX_CMD_DATA_POP, + ARM_EXIDX_CMD_REG_POP, + ARM_EXIDX_CMD_REG_TO_SP, + ARM_EXIDX_CMD_VFP_POP, + ARM_EXIDX_CMD_WREG_POP, + ARM_EXIDX_CMD_WCGR_POP, + ARM_EXIDX_CMD_RESERVED, + ARM_EXIDX_CMD_REFUSED, +} arm_exbuf_cmd_t; + +struct arm_exbuf_data +{ + arm_exbuf_cmd_t cmd; + uint32_t data; +}; + +void arm_exidx_init_local (void); +void arm_exidx_table_add (const char *name, + struct arm_exidx_entry *start, + struct arm_exidx_entry *end); +struct arm_exidx_table *arm_exidx_table_find (void *pc); +struct arm_exidx_entry *arm_exidx_table_lookup (struct arm_exidx_table *table, + void *pc); +int arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t * buf); +int arm_exidx_decode (const uint8_t * buf, uint8_t len, + struct dwarf_cursor *c); +int arm_exidx_apply_cmd (struct arm_exbuf_data *edata, struct dwarf_cursor *c); + +#endif /* ARM_EX_TABLES_H */ diff --git a/include/tdep-arm/libunwind_i.h b/include/tdep-arm/libunwind_i.h index 5f02ed0..21628cf 100644 --- a/include/tdep-arm/libunwind_i.h +++ b/include/tdep-arm/libunwind_i.h @@ -34,6 +34,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "elf32.h" #include "mempool.h" #include "dwarf.h" +#include "ex_tables.h" struct unw_addr_space { diff --git a/src/Makefile.am b/src/Makefile.am index b87cbab..7c1505a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -137,7 +137,8 @@ libunwind_la_LIBADD += $(LIBUNWIND_ELF) # The list of files that go into libunwind and libunwind-arm: noinst_HEADERS += arm/init.h arm/offsets.h arm/unwind_i.h libunwind_la_SOURCES_arm_common = $(libunwind_la_SOURCES_common) \ - arm/is_fpreg.c arm/regname.c + arm/is_fpreg.c arm/regname.c \ + arm/ex_tables.c arm/ex_tables.h # The list of files that go into libunwind: libunwind_la_SOURCES_arm = $(libunwind_la_SOURCES_arm_common) \ diff --git a/src/arm/ex_tables.c b/src/arm/ex_tables.c new file mode 100644 index 0000000..9a7baf3 --- /dev/null +++ b/src/arm/ex_tables.c @@ -0,0 +1,432 @@ +/* libunwind - a platform-independent unwind library + Copyright 2011 Linaro Limited + +This file is part of libunwind. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#include "libunwind_i.h" + +#define ARM_EXBUF_START(x) (((x) >> 4) & 0x0f) +#define ARM_EXBUF_COUNT(x) ((x) & 0x0f) +#define ARM_EXBUF_END(x) (ARM_EXBUF_START(x) + ARM_EXBUF_COUNT(x)) + +#define ARM_EXIDX_CANT_UNWIND 0x00000001 +#define ARM_EXIDX_COMPACT 0x80000000 + +#define ARM_EXTBL_OP_FINISH 0xb0 + +#define ARM_EXIDX_TABLE_LIMIT 32 + +enum arm_exbuf_cmd_flags +{ + ARM_EXIDX_VFP_SHIFT_16 = 1 << 16, + ARM_EXIDX_VFP_DOUBLE = 1 << 17, +}; + +static struct arm_exidx_table arm_exidx_tables[ARM_EXIDX_TABLE_LIMIT]; +static unsigned arm_exidx_table_count = 0; + +static inline void * +prel31_to_addr (void *addr) +{ + uint32_t offset = ((long) *(uint32_t *) addr << 1) >> 1; + return (char *) addr + offset; +} + +static inline uint32_t +prel31_read (uint32_t prel31) +{ + return ((int32_t) prel31 << 1) >> 1; +} + +static void +arm_exidx_table_reset_all (void) +{ + arm_exidx_table_count = 0; +} + +HIDDEN void +arm_exidx_table_add (const char *name, + struct arm_exidx_entry *start, + struct arm_exidx_entry *end) +{ + if (arm_exidx_table_count >= ARM_EXIDX_TABLE_LIMIT) + return -1; + struct arm_exidx_table *table = &arm_exidx_tables[arm_exidx_table_count++]; + table->name = name; + table->start = start; + table->end = end; + table->start_addr = prel31_to_addr (&start->addr); + table->end_addr = prel31_to_addr (&(end - 1)->addr); + Debug (2, "name=%s, range=%p-%p, addr=%p-%p\n", + name, start, end, table->start_addr, table->end_addr); +} + +/** + * Locate the appropriate unwind table from the given PC. + */ +HIDDEN struct arm_exidx_table * +arm_exidx_table_find (void *pc) +{ + struct arm_exidx_table *table; + unsigned i; + for (i = 0; i < arm_exidx_table_count; i++) + { + table = &arm_exidx_tables[i]; + if (pc >= table->start_addr && pc < table->end_addr) + return table; + } + return NULL; +} + +HIDDEN struct arm_exidx_entry * +arm_exidx_table_lookup (struct arm_exidx_table *table, void *pc) +{ + struct arm_exidx_entry *first = table->start, *last = table->end - 1; + if (pc < prel31_to_addr (&first->addr)) + return NULL; + else if (pc >= prel31_to_addr (&last->addr)) + return last; + while (first < last - 1) + { + struct arm_exidx_entry *mid = first + ((last - first + 1) >> 1); + if (pc < prel31_to_addr (&mid->addr)) + last = mid; + else + first = mid; + } + return first; +} + +/** + * Applies the given command onto the new state to the given dwarf_cursor. + */ +HIDDEN int +arm_exidx_apply_cmd (struct arm_exbuf_data *edata, struct dwarf_cursor *c) +{ + int ret = 0; + unsigned i; + + switch (edata->cmd) + { + case ARM_EXIDX_CMD_FINISH: + /* Set LR to PC if not set already. */ + if (DWARF_IS_NULL_LOC (c->loc[UNW_ARM_R15])) + c->loc[UNW_ARM_R15] = c->loc[UNW_ARM_R14]; + /* Set IP. */ + dwarf_get (c, c->loc[UNW_ARM_R15], &c->ip); + break; + case ARM_EXIDX_CMD_DATA_PUSH: + Debug (2, "vsp = vsp - %d\n", edata->data); + c->cfa -= edata->data; + break; + case ARM_EXIDX_CMD_DATA_POP: + Debug (2, "vsp = vsp + %d\n", edata->data); + c->cfa += edata->data; + break; + case ARM_EXIDX_CMD_REG_POP: + for (i = 0; i < 16; i++) + if (edata->data & (1 << i)) + { + Debug (2, "pop {r%d}\n", i); + c->loc[UNW_ARM_R0 + i] = DWARF_LOC (c->cfa, 0); + c->cfa += 4; + } + /* Set cfa in case the SP got popped. */ + if (edata->data & (1 << 13)) + dwarf_get (c, c->loc[UNW_ARM_R13], &c->cfa); + break; + case ARM_EXIDX_CMD_REG_TO_SP: + assert (edata->data < 16); + Debug (2, "vsp = r%d\n", edata->data); + c->loc[UNW_ARM_R13] = c->loc[UNW_ARM_R0 + edata->data]; + dwarf_get (c, c->loc[UNW_ARM_R13], &c->cfa); + break; + case ARM_EXIDX_CMD_VFP_POP: + /* Skip VFP registers, but be sure to adjust stack */ + for (i = ARM_EXBUF_START (edata->data); i < ARM_EXBUF_END (edata->data); + i++) + c->cfa += 8; + if (!(edata->data & ARM_EXIDX_VFP_DOUBLE)) + c->cfa += 4; + break; + case ARM_EXIDX_CMD_WREG_POP: + for (i = ARM_EXBUF_START (edata->data); i < ARM_EXBUF_END (edata->data); + i++) + c->cfa += 8; + break; + case ARM_EXIDX_CMD_WCGR_POP: + for (i = 0; i < 4; i++) + if (edata->data & (1 << i)) + c->cfa += 4; + break; + case ARM_EXIDX_CMD_REFUSED: + case ARM_EXIDX_CMD_RESERVED: + ret = -1; + break; + } + return ret; +} + +/** + * Decodes the given unwind instructions into arm_exbuf_data and calls + * arm_exidx_apply_cmd that applies the command onto the dwarf_cursor. + */ +HIDDEN int +arm_exidx_decode (const uint8_t *buf, uint8_t len, struct dwarf_cursor *c) +{ +#define READ_OP() *buf++ + const uint8_t *end = buf + len; + int ret; + struct arm_exbuf_data edata; + + assert (buf != NULL); + assert (len > 0); + + while (buf < end) + { + uint8_t op = READ_OP (); + if ((op & 0xc0) == 0x00) + { + edata.cmd = ARM_EXIDX_CMD_DATA_POP; + edata.data = (((int) op & 0x3f) << 2) + 4; + } + else if ((op & 0xc0) == 0x40) + { + edata.cmd = ARM_EXIDX_CMD_DATA_PUSH; + edata.data = (((int) op & 0x3f) << 2) + 4; + } + else if ((op & 0xf0) == 0x80) + { + uint8_t op2 = READ_OP (); + if (op == 0x80 && op2 == 0x00) + edata.cmd = ARM_EXIDX_CMD_REFUSED; + else + { + edata.cmd = ARM_EXIDX_CMD_REG_POP; + edata.data = ((op & 0xf) << 8) | op2; + edata.data = edata.data << 4; + } + } + else if ((op & 0xf0) == 0x90) + { + if (op == 0x9d || op == 0x9f) + edata.cmd = ARM_EXIDX_CMD_RESERVED; + else + { + edata.cmd = ARM_EXIDX_CMD_REG_TO_SP; + edata.data = op & 0x0f; + } + } + else if ((op & 0xf0) == 0xa0) + { + unsigned end = (op & 0x07); + edata.data = (1 << (end + 1)) - 1; + edata.data = edata.data << 4; + if (op & 0x08) + edata.data |= 1 << 14; + edata.cmd = ARM_EXIDX_CMD_REG_POP; + } + else if (op == ARM_EXTBL_OP_FINISH) + { + edata.cmd = ARM_EXIDX_CMD_FINISH; + buf = end; + } + else if (op == 0xb1) + { + uint8_t op2 = READ_OP (); + if (op2 == 0 || (op2 & 0xf0)) + edata.cmd = ARM_EXIDX_CMD_RESERVED; + else + { + edata.cmd = ARM_EXIDX_CMD_REG_POP; + edata.data = op2 & 0x0f; + } + } + else if (op == 0xb2) + { + uint32_t offset = 0; + uint8_t byte, shift = 0; + do + { + byte = READ_OP (); + offset |= (byte & 0x7f) << shift; + shift += 7; + } + while (byte & 0x80); + edata.data = (offset << 2) + 0x204; + edata.cmd = ARM_EXIDX_CMD_DATA_POP; + } + else if (op == 0xb3 || op == 0xc8 || op == 0xc9) + { + edata.cmd = ARM_EXIDX_CMD_VFP_POP; + edata.data = READ_OP (); + if (op == 0xc8) + edata.data |= ARM_EXIDX_VFP_SHIFT_16; + if (op != 0xb3) + edata.data |= ARM_EXIDX_VFP_DOUBLE; + } + else if ((op & 0xf8) == 0xb8 || (op & 0xf8) == 0xd0) + { + edata.cmd = ARM_EXIDX_CMD_VFP_POP; + edata.data = 0x80 | (op & 0x07); + if ((op & 0xf8) == 0xd0) + edata.data |= ARM_EXIDX_VFP_DOUBLE; + } + else if (op >= 0xc0 && op <= 0xc5) + { + edata.cmd = ARM_EXIDX_CMD_WREG_POP; + edata.data = 0xa0 | (op & 0x07); + } + else if (op == 0xc6) + { + edata.cmd = ARM_EXIDX_CMD_WREG_POP; + edata.data = READ_OP (); + } + else if (op == 0xc7) + { + uint8_t op2 = READ_OP (); + if (op2 == 0 || (op2 & 0xf0)) + edata.cmd = ARM_EXIDX_CMD_RESERVED; + else + { + edata.cmd = ARM_EXIDX_CMD_WCGR_POP; + edata.data = op2 & 0x0f; + } + } + else + edata.cmd = ARM_EXIDX_CMD_RESERVED; + + ret = arm_exidx_apply_cmd (&edata, c); + if (ret < 0) + return ret; + } + return 0; +} + +/** + * Reads the given entry and extracts the unwind instructions into buf. + * Returns the number of the extracted unwind insns or -UNW_ESTOPUNWIND + * if the special bit pattern ARM_EXIDX_CANT_UNWIND (0x1) was found. + */ +HIDDEN int +arm_exidx_extract (struct arm_exidx_entry *entry, uint8_t *buf) +{ + int nbuf = 0; + uint32_t *addr = prel31_to_addr (&entry->addr); + + uint32_t data = entry->data; + if (data == ARM_EXIDX_CANT_UNWIND) + { + Debug (2, "0x1 [can't unwind]\n"); + nbuf = -UNW_ESTOPUNWIND; + } + else if (data & ARM_EXIDX_COMPACT) + { + Debug (2, "%p compact model %d [%8.8x]\n", addr, (data >> 24) & 0x7f, + data); + buf[nbuf++] = data >> 16; + buf[nbuf++] = data >> 8; + buf[nbuf++] = data; + } + else + { + uint32_t *extbl_data = prel31_to_addr (&entry->data); + data = extbl_data[0]; + unsigned int n_table_words = 0; + if (data & ARM_EXIDX_COMPACT) + { + int pers = (data >> 24) & 0x0f; + Debug (2, "%p compact model %d [%8.8x]\n", addr, pers, data); + if (pers == 1 || pers == 2) + { + n_table_words = (data >> 16) & 0xff; + extbl_data += 1; + } + else + buf[nbuf++] = data >> 16; + buf[nbuf++] = data >> 8; + buf[nbuf++] = data; + } + else + { + void *pers = prel31_to_addr (extbl_data); + Debug (2, "%p Personality routine: %8p\n", addr, pers); + n_table_words = extbl_data[1] >> 24; + buf[nbuf++] = extbl_data[1] >> 16; + buf[nbuf++] = extbl_data[1] >> 8; + buf[nbuf++] = extbl_data[1]; + extbl_data += 2; + } + assert (n_table_words <= 5); + unsigned j; + for (j = 0; j < n_table_words; j++) + { + data = *extbl_data++; + buf[nbuf++] = data >> 24; + buf[nbuf++] = data >> 16; + buf[nbuf++] = data >> 8; + buf[nbuf++] = data >> 0; + } + } + + if (nbuf > 0 && buf[nbuf - 1] != ARM_EXTBL_OP_FINISH) + buf[nbuf++] = ARM_EXTBL_OP_FINISH; + + return nbuf; +} + +/** + * Callback to dl_iterate_phdr to find the unwind tables. + * If found, calls arm_exidx_table_add to remember it for later lookups. + */ +static int +arm_exidx_init_local_cb (struct dl_phdr_info *info, size_t size, void *data) +{ + unsigned i; + + for (i = 0; i < info->dlpi_phnum; i++) + { + const ElfW (Phdr) * phdr = info->dlpi_phdr + i; + if (phdr->p_type != PT_ARM_EXIDX) + continue; + + ElfW (Addr) addr = info->dlpi_addr + phdr->p_vaddr; + ElfW (Word) size = phdr->p_filesz; + + arm_exidx_table_add (info->dlpi_name, + (struct arm_exidx_entry *) addr, + (struct arm_exidx_entry *) (addr + size)); + break; + } + return 0; +} + +/** + * Traverse the program headers of the executable and its loaded + * shared objects to collect the unwind tables. + */ +HIDDEN void +arm_exidx_init_local (void) +{ + arm_exidx_table_reset_all (); + dl_iterate_phdr (&arm_exidx_init_local_cb, NULL); +} diff --git a/src/elfxx.h b/src/elfxx.h index e316970..a85cc68 100644 --- a/src/elfxx.h +++ b/src/elfxx.h @@ -31,8 +31,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include -#include "libunwind_i.h" - #if ELF_CLASS == ELFCLASS32 # define ELF_W(x) ELF32_##x # define Elf_W(x) Elf32_##x @@ -43,6 +41,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ # define elf_w(x) _Uelf64_##x #endif +#include "libunwind_i.h" + extern int elf_w (valid_object) (struct elf_image *ei); extern int elf_w (get_proc_name) (unw_addr_space_t as, pid_t pid, unw_word_t ip,