From patchwork Mon Apr 4 14:11:02 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ken Werner X-Patchwork-Id: 895 Return-Path: Delivered-To: unknown Received: from imap.gmail.com (74.125.159.109) by localhost6.localdomain6 with IMAP4-SSL; 08 Jun 2011 14:47:09 -0000 Delivered-To: patches@linaro.org Received: by 10.68.42.132 with SMTP id o4cs28037pbl; Mon, 4 Apr 2011 07:11:21 -0700 (PDT) Received: by 10.227.168.138 with SMTP id u10mr7190245wby.186.1301926277875; Mon, 04 Apr 2011 07:11:17 -0700 (PDT) Received: from mtagate2.uk.ibm.com (mtagate2.uk.ibm.com [194.196.100.162]) by mx.google.com with ESMTPS id z15si10123546wby.57.2011.04.04.07.11.16 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 04 Apr 2011 07:11:17 -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 d06nrmr1307.portsmouth.uk.ibm.com (d06nrmr1307.portsmouth.uk.ibm.com [9.149.38.129]) by mtagate2.uk.ibm.com (8.13.1/8.13.1) with ESMTP id p34EBGGH025636 for ; Mon, 4 Apr 2011 14:11:16 GMT Received: from d06av03.portsmouth.uk.ibm.com (d06av03.portsmouth.uk.ibm.com [9.149.37.213]) by d06nrmr1307.portsmouth.uk.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id p34EBumW1274070 for ; Mon, 4 Apr 2011 15:11:56 +0100 Received: from d06av03.portsmouth.uk.ibm.com (localhost.localdomain [127.0.0.1]) by d06av03.portsmouth.uk.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id p34EBF3k024224 for ; Mon, 4 Apr 2011 08:11:15 -0600 Received: from localhost.localdomain (dyn-9-152-224-51.boeblingen.de.ibm.com [9.152.224.51]) by d06av03.portsmouth.uk.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id p34EBFOJ024193; Mon, 4 Apr 2011 08:11:15 -0600 From: Ken Werner To: libunwind-devel@nongnu.org Subject: [PATCH 1/3] Create a generic and local variant of the extbl parser. Date: Mon, 4 Apr 2011 16:11:02 +0200 Message-Id: <1301926264-13393-2-git-send-email-ken.werner@linaro.org> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1301926264-13393-1-git-send-email-ken.werner@linaro.org> References: <1301926264-13393-1-git-send-email-ken.werner@linaro.org> In order to have the DWARF_* macros working properly a generic and a local variant of the ex_tables.c have been created. Signed-off-by: Ken Werner --- include/tdep-arm/ex_tables.h | 7 + src/Makefile.am | 8 +- src/arm/Gex_tables.c | 439 ++++++++++++++++++++++++++++++++++++++++++ src/arm/Gstep.c | 2 + src/arm/Lex_tables.c | 5 + src/arm/ex_tables.c | 433 ----------------------------------------- 6 files changed, 458 insertions(+), 436 deletions(-) create mode 100644 src/arm/Gex_tables.c create mode 100644 src/arm/Lex_tables.c delete mode 100644 src/arm/ex_tables.c diff --git a/include/tdep-arm/ex_tables.h b/include/tdep-arm/ex_tables.h index 6376ea7..b664995 100644 --- a/include/tdep-arm/ex_tables.h +++ b/include/tdep-arm/ex_tables.h @@ -57,6 +57,13 @@ struct arm_exbuf_data uint32_t data; }; +#define arm_exidx_init_local UNW_OBJ(arm_exidx_init_local) +#define arm_exidx_table_add UNW_OBJ(arm_exidx_table_add) +#define arm_exidx_table_find UNW_OBJ(arm_exidx_table_find) +#define arm_exidx_table_lookup UNW_OBJ(arm_exidx_table_lookup) +#define arm_exidx_extract UNW_OBJ(arm_exidx_extract) +#define arm_exidx_decode UNW_OBJ(arm_exidx_decode) +#define arm_exidx_apply_cmd UNW_OBJ(arm_exidx_apply_cmd) int arm_exidx_init_local (void); int arm_exidx_table_add (const char *name, struct arm_exidx_entry *start, diff --git a/src/Makefile.am b/src/Makefile.am index 9703724..e889235 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -138,7 +138,7 @@ libunwind_la_LIBADD += $(LIBUNWIND_ELF) 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/ex_tables.c arm/ex_tables.h + arm/ex_tables.h # The list of files that go into libunwind: libunwind_la_SOURCES_arm = $(libunwind_la_SOURCES_arm_common) \ @@ -146,13 +146,15 @@ libunwind_la_SOURCES_arm = $(libunwind_la_SOURCES_arm_common) \ arm/getcontext.S \ arm/Lcreate_addr_space.c arm/Lget_proc_info.c arm/Lget_save_loc.c \ arm/Lglobal.c arm/Linit.c arm/Linit_local.c arm/Linit_remote.c \ - arm/Lis_signal_frame.c arm/Lregs.c arm/Lresume.c arm/Lstep.c + arm/Lis_signal_frame.c arm/Lregs.c arm/Lresume.c arm/Lstep.c \ + arm/Lex_tables.c libunwind_arm_la_SOURCES_arm = $(libunwind_la_SOURCES_arm_common) \ $(libunwind_la_SOURCES_generic) \ arm/Gcreate_addr_space.c arm/Gget_proc_info.c arm/Gget_save_loc.c \ arm/Gglobal.c arm/Ginit.c arm/Ginit_local.c arm/Ginit_remote.c \ - arm/Gis_signal_frame.c arm/Gregs.c arm/Gresume.c arm/Gstep.c + arm/Gis_signal_frame.c arm/Gregs.c arm/Gresume.c arm/Gstep.c \ + arm/Gex_tables.c # The list of files that go both into libunwind and libunwind-ia64: noinst_HEADERS += ia64/init.h ia64/offsets.h ia64/regs.h \ diff --git a/src/arm/Gex_tables.c b/src/arm/Gex_tables.c new file mode 100644 index 0000000..035b24a --- /dev/null +++ b/src/arm/Gex_tables.c @@ -0,0 +1,439 @@ +/* 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 + +#define arm_exidx_tables UNW_OBJ(arm_exidx_tables) +#define arm_exidx_table_count UNW_OBJ(arm_exidx_table_count) +#define arm_exidx_table_find UNW_OBJ(arm_exidx_table_find) +#define arm_exidx_table_reset_all UNW_OBJ(arm_exidx_table_reset_all) +#define arm_exidx_init_local_cb UNW_OBJ(arm_exidx_init_local_cb) + +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 uint32_t +prel31_read (uint32_t prel31) +{ + return ((int32_t)prel31 << 1) >> 1; +} + +static inline void * +prel31_to_addr (void *addr) +{ + uint32_t offset = ((long)*(uint32_t *)addr << 1) >> 1; + return (char *)addr + offset; +} + +static void +arm_exidx_table_reset_all (void) +{ + arm_exidx_table_count = 0; +} + +HIDDEN int +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); + return 0; +} + +/** + * 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; +} + +/** + * Finds the corresponding arm_exidx_entry from a given index table and PC. + */ +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 * 4 + 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 int +arm_exidx_init_local (void) +{ + arm_exidx_table_reset_all(); + return dl_iterate_phdr (&arm_exidx_init_local_cb, NULL); +} diff --git a/src/arm/Gstep.c b/src/arm/Gstep.c index f036093..9164d78 100644 --- a/src/arm/Gstep.c +++ b/src/arm/Gstep.c @@ -27,6 +27,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "offsets.h" #include "ex_tables.h" +#define arm_exidx_step UNW_OBJ(arm_exidx_step) + static inline int arm_exidx_step (struct cursor *c) { diff --git a/src/arm/Lex_tables.c b/src/arm/Lex_tables.c new file mode 100644 index 0000000..4a4f925 --- /dev/null +++ b/src/arm/Lex_tables.c @@ -0,0 +1,5 @@ +#define UNW_LOCAL_ONLY +#include +#if defined(UNW_LOCAL_ONLY) && !defined(UNW_REMOTE_ONLY) +#include "Gex_tables.c" +#endif diff --git a/src/arm/ex_tables.c b/src/arm/ex_tables.c deleted file mode 100644 index 499febe..0000000 --- a/src/arm/ex_tables.c +++ /dev/null @@ -1,433 +0,0 @@ -/* 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 uint32_t -prel31_read (uint32_t prel31) -{ - return ((int32_t)prel31 << 1) >> 1; -} - -static inline void * -prel31_to_addr (void *addr) -{ - uint32_t offset = ((long)*(uint32_t *)addr << 1) >> 1; - return (char *)addr + offset; -} - -static void -arm_exidx_table_reset_all (void) -{ - arm_exidx_table_count = 0; -} - -HIDDEN int -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); - return 0; -} - -/** - * 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; -} - -/** - * Finds the corresponding arm_exidx_entry from a given index table and PC. - */ -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 * 4 + 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 int -arm_exidx_init_local (void) -{ - arm_exidx_table_reset_all(); - return dl_iterate_phdr (&arm_exidx_init_local_cb, NULL); -}