| /* 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. */ |
| |
| /* This file contains functionality for parsing and interpreting the ARM |
| specific unwind information. Documentation about the exception handling |
| ABI for the ARM architecture can be found at: |
| http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf |
| */ |
| |
| #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 |
| |
| enum arm_exbuf_cmd_flags { |
| ARM_EXIDX_VFP_SHIFT_16 = 1 << 16, |
| ARM_EXIDX_VFP_DOUBLE = 1 << 17, |
| }; |
| |
| struct arm_cb_data |
| { |
| /* in: */ |
| unw_word_t ip; /* instruction-pointer we're looking for */ |
| unw_proc_info_t *pi; /* proc-info pointer */ |
| /* out: */ |
| unw_dyn_info_t di; /* info about the ARM exidx segment */ |
| }; |
| |
| static inline int |
| prel31_to_addr (unw_addr_space_t as, void *arg, unw_word_t prel31, |
| unw_word_t *val) |
| { |
| unw_word_t offset; |
| |
| if ((*as->acc.access_mem)(as, prel31, &offset, 0, arg) < 0) |
| return -UNW_EINVAL; |
| |
| offset = ((long)offset << 1) >> 1; |
| *val = prel31 + offset; |
| |
| return 0; |
| } |
| |
| /** |
| * 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 entry from the given cursor 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 dwarf_cursor *c, uint8_t *buf) |
| { |
| int nbuf = 0; |
| unw_word_t entry = (unw_word_t) c->pi.unwind_info; |
| unw_word_t addr; |
| uint32_t data; |
| |
| /* An ARM unwind entry consists of a prel31 offset to the start of a |
| function followed by 31bits of data: |
| * if set to 0x1: the function cannot be unwound (EXIDX_CANTUNWIND) |
| * if bit 31 is one: this is a table entry itself (ARM_EXIDX_COMPACT) |
| * if bit 31 is zero: this is a prel31 offset of the start of the |
| table entry for this function */ |
| if (prel31_to_addr(c->as, c->as_arg, entry, &addr) < 0) |
| return -UNW_EINVAL; |
| |
| if ((*c->as->acc.access_mem)(c->as, entry + 4, &data, 0, c->as_arg) < 0) |
| return -UNW_EINVAL; |
| |
| 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", (void *)addr, |
| (data >> 24) & 0x7f, data); |
| buf[nbuf++] = data >> 16; |
| buf[nbuf++] = data >> 8; |
| buf[nbuf++] = data; |
| } |
| else |
| { |
| unw_word_t extbl_data; |
| unsigned int n_table_words = 0; |
| |
| if (prel31_to_addr(c->as, c->as_arg, entry + 4, &extbl_data) < 0) |
| return -UNW_EINVAL; |
| |
| if ((*c->as->acc.access_mem)(c->as, extbl_data, &data, 0, c->as_arg) < 0) |
| return -UNW_EINVAL; |
| |
| if (data & ARM_EXIDX_COMPACT) |
| { |
| int pers = (data >> 24) & 0x0f; |
| Debug (2, "%p compact model %d [%8.8x]\n", (void *)addr, pers, data); |
| if (pers == 1 || pers == 2) |
| { |
| n_table_words = (data >> 16) & 0xff; |
| extbl_data += 4; |
| } |
| else |
| buf[nbuf++] = data >> 16; |
| buf[nbuf++] = data >> 8; |
| buf[nbuf++] = data; |
| } |
| else |
| { |
| unw_word_t pers; |
| if (prel31_to_addr (c->as, c->as_arg, extbl_data, &pers) < 0) |
| return -UNW_EINVAL; |
| Debug (2, "%p Personality routine: %8p\n", (void *)addr, |
| (void *)pers); |
| if ((*c->as->acc.access_mem)(c->as, extbl_data + 4, &data, 0, |
| c->as_arg) < 0) |
| return -UNW_EINVAL; |
| n_table_words = data >> 24; |
| buf[nbuf++] = data >> 16; |
| buf[nbuf++] = data >> 8; |
| buf[nbuf++] = data; |
| extbl_data += 8; |
| } |
| assert (n_table_words <= 5); |
| unsigned j; |
| for (j = 0; j < n_table_words; j++) |
| { |
| if ((*c->as->acc.access_mem)(c->as, extbl_data, &data, 0, |
| c->as_arg) < 0) |
| return -UNW_EINVAL; |
| extbl_data += 4; |
| 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; |
| } |
| |
| PROTECTED int |
| tdep_search_unwind_table (unw_addr_space_t as, unw_word_t ip, |
| unw_dyn_info_t *di, unw_proc_info_t *pi, |
| int need_unwind_info, void *arg) |
| { |
| if (UNW_TRY_METHOD (UNW_ARM_METHOD_EXIDX) |
| && di->format == UNW_INFO_FORMAT_ARM_EXIDX) |
| { |
| /* The .ARM.exidx section contains a sorted list of key-value pairs - |
| the unwind entries. The 'key' is a prel31 offset to the start of a |
| function. We binary search this section in order to find the |
| appropriate unwind entry. */ |
| unw_word_t first = di->u.rti.table_data; |
| unw_word_t last = di->u.rti.table_data + di->u.rti.table_len - 8; |
| unw_word_t entry, val; |
| |
| if (prel31_to_addr (as, arg, first, &val) < 0 || ip < val) |
| return -UNW_ENOINFO; |
| |
| if (prel31_to_addr (as, arg, last, &val) < 0) |
| return -UNW_EINVAL; |
| |
| if (ip >= val) |
| { |
| entry = last; |
| |
| if (prel31_to_addr (as, arg, last, &pi->start_ip) < 0) |
| return -UNW_EINVAL; |
| |
| pi->end_ip = di->end_ip -1; |
| } |
| else |
| { |
| while (first < last - 8) |
| { |
| entry = first + (((last - first) / 8 + 1) >> 1) * 8; |
| |
| if (prel31_to_addr (as, arg, entry, &val) < 0) |
| return -UNW_EINVAL; |
| |
| if (ip < val) |
| last = entry; |
| else |
| first = entry; |
| } |
| |
| entry = first; |
| |
| if (prel31_to_addr (as, arg, entry, &pi->start_ip) < 0) |
| return -UNW_EINVAL; |
| |
| if (prel31_to_addr (as, arg, entry + 8, &pi->end_ip) < 0) |
| return -UNW_EINVAL; |
| |
| pi->end_ip--; |
| } |
| |
| if (need_unwind_info) |
| { |
| pi->unwind_info_size = 8; |
| pi->unwind_info = (void *) entry; |
| pi->format = UNW_INFO_FORMAT_ARM_EXIDX; |
| } |
| return 0; |
| } |
| else if (UNW_TRY_METHOD(UNW_ARM_METHOD_DWARF) |
| && di->format != UNW_INFO_FORMAT_ARM_EXIDX) |
| return dwarf_search_unwind_table (as, ip, di, pi, need_unwind_info, arg); |
| |
| return -UNW_ENOINFO; |
| } |
| |
| #ifndef UNW_REMOTE_ONLY |
| /** |
| * Callback to dl_iterate_phdr to find infos about the ARM exidx segment. |
| */ |
| static int |
| arm_phdr_cb (struct dl_phdr_info *info, size_t size, void *data) |
| { |
| struct arm_cb_data *cb_data = data; |
| const Elf_W(Phdr) *p_text = NULL; |
| const Elf_W(Phdr) *p_arm_exidx = NULL; |
| const Elf_W(Phdr) *phdr = info->dlpi_phdr; |
| long n; |
| |
| for (n = info->dlpi_phnum; --n >= 0; phdr++) |
| { |
| switch (phdr->p_type) |
| { |
| case PT_LOAD: |
| if (cb_data->ip >= phdr->p_vaddr + info->dlpi_addr && |
| cb_data->ip < phdr->p_vaddr + info->dlpi_addr + phdr->p_memsz) |
| p_text = phdr; |
| break; |
| |
| case PT_ARM_EXIDX: |
| p_arm_exidx = phdr; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (p_text && p_arm_exidx) |
| { |
| cb_data->di.format = UNW_INFO_FORMAT_ARM_EXIDX; |
| cb_data->di.start_ip = p_text->p_vaddr + info->dlpi_addr; |
| cb_data->di.end_ip = cb_data->di.start_ip + p_text->p_memsz; |
| cb_data->di.u.rti.name_ptr = (unw_word_t) info->dlpi_name; |
| cb_data->di.u.rti.table_data = p_arm_exidx->p_vaddr + info->dlpi_addr; |
| cb_data->di.u.rti.table_len = p_arm_exidx->p_memsz; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| HIDDEN int |
| arm_find_proc_info (unw_addr_space_t as, unw_word_t ip, |
| unw_proc_info_t *pi, int need_unwind_info, void *arg) |
| { |
| int ret = -1; |
| intrmask_t saved_mask; |
| |
| Debug (14, "looking for IP=0x%lx\n", (long) ip); |
| |
| if (UNW_TRY_METHOD(UNW_ARM_METHOD_DWARF)) |
| { |
| ret = dwarf_find_proc_info (as, ip, pi, need_unwind_info, arg); |
| } |
| |
| if (ret < 0 && UNW_TRY_METHOD (UNW_ARM_METHOD_EXIDX)) |
| { |
| struct arm_cb_data cb_data; |
| |
| memset (&cb_data, 0, sizeof (cb_data)); |
| cb_data.ip = ip; |
| cb_data.pi = pi; |
| cb_data.di.format = -1; |
| |
| SIGPROCMASK (SIG_SETMASK, &unwi_full_mask, &saved_mask); |
| ret = dl_iterate_phdr (arm_phdr_cb, &cb_data); |
| SIGPROCMASK (SIG_SETMASK, &saved_mask, NULL); |
| |
| if (cb_data.di.format != -1) |
| ret = tdep_search_unwind_table (as, ip, &cb_data.di, pi, |
| need_unwind_info, arg); |
| else |
| ret = -UNW_ENOINFO; |
| } |
| |
| if (ret < 0) |
| /* ANDROID support update. */ |
| { |
| Debug (14, "IP=0x%lx not found\n", (long) ip); |
| } |
| /* End of ANDROID update. */ |
| |
| return ret; |
| } |
| |
| HIDDEN void |
| arm_put_unwind_info (unw_addr_space_t as, unw_proc_info_t *proc_info, void *arg) |
| { |
| /* it's a no-op */ |
| } |
| #endif /* !UNW_REMOTE_ONLY */ |
| |