static inline int read_regnum (unw_addr_space_t as, unw_accessors_t *a, unw_word_t *addr, unw_word_t *valp, void *arg) { int ret; if ((ret = dwarf_read_uleb128 (as, a, addr, valp, arg)) < 0) return ret; if (*valp >= DWARF_NUM_PRESERVED_REGS) { Debug (1, "Invalid register number %u\n", (unsigned int) *valp); return -UNW_EBADREG; } return 0; }
static inline int eval_location_expr (struct dwarf_cursor *c, unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr, dwarf_loc_t *locp, void *arg) { int ret, is_register; unw_word_t len, val; /* read the length of the expression: */ if ((ret = dwarf_read_uleb128 (as, a, &addr, &len, arg)) < 0) return ret; /* evaluate the expression: */ if ((ret = dwarf_eval_expr (c, &addr, len, &val, &is_register)) < 0) return ret; if (is_register) *locp = DWARF_REG_LOC (c, dwarf_to_unw_regnum (val)); else *locp = DWARF_MEM_LOC (c, val); return 0; }
/* Run a CFI program to update the register state. */ static int run_cfi_program (struct dwarf_cursor *c, dwarf_state_record_t *sr, unw_word_t ip, unw_word_t *addr, unw_word_t end_addr, struct dwarf_cie_info *dci) { unw_word_t curr_ip, operand = 0, regnum, val, len, fde_encoding; dwarf_reg_state_t *rs_stack = NULL, *new_rs, *old_rs; unw_addr_space_t as; unw_accessors_t *a; uint8_t u8, op; uint16_t u16; uint32_t u32; void *arg; int ret; as = c->as; arg = c->as_arg; if (c->pi.flags & UNW_PI_FLAG_DEBUG_FRAME) { /* .debug_frame CFI is stored in local address space. */ as = unw_local_addr_space; arg = NULL; } a = unw_get_accessors (as); curr_ip = c->pi.start_ip; /* Process everything up to and including the current 'ip', including all the DW_CFA_advance_loc instructions. See 'c->use_prev_instr' use in 'fetch_proc_info' for details. */ while (curr_ip <= ip && *addr < end_addr) { if ((ret = dwarf_readu8 (as, a, addr, &op, arg)) < 0) return ret; if (op & DWARF_CFA_OPCODE_MASK) { operand = op & DWARF_CFA_OPERAND_MASK; op &= ~DWARF_CFA_OPERAND_MASK; } switch ((dwarf_cfa_t) op) { case DW_CFA_advance_loc: curr_ip += operand * dci->code_align; Debug (15, "CFA_advance_loc to 0x%lx\n", (long) curr_ip); break; case DW_CFA_advance_loc1: if ((ret = dwarf_readu8 (as, a, addr, &u8, arg)) < 0) goto fail; curr_ip += u8 * dci->code_align; Debug (15, "CFA_advance_loc1 to 0x%lx\n", (long) curr_ip); break; case DW_CFA_advance_loc2: if ((ret = dwarf_readu16 (as, a, addr, &u16, arg)) < 0) goto fail; curr_ip += u16 * dci->code_align; Debug (15, "CFA_advance_loc2 to 0x%lx\n", (long) curr_ip); break; case DW_CFA_advance_loc4: if ((ret = dwarf_readu32 (as, a, addr, &u32, arg)) < 0) goto fail; curr_ip += u32 * dci->code_align; Debug (15, "CFA_advance_loc4 to 0x%lx\n", (long) curr_ip); break; case DW_CFA_MIPS_advance_loc8: #ifdef UNW_TARGET_MIPS { uint64_t u64; if ((ret = dwarf_readu64 (as, a, addr, &u64, arg)) < 0) goto fail; curr_ip += u64 * dci->code_align; Debug (15, "CFA_MIPS_advance_loc8\n"); break; } #else Debug (1, "DW_CFA_MIPS_advance_loc8 on non-MIPS target\n"); ret = -UNW_EINVAL; goto fail; #endif case DW_CFA_offset: regnum = operand; if (regnum >= DWARF_NUM_PRESERVED_REGS) { Debug (1, "Invalid register number %u in DW_cfa_OFFSET\n", (unsigned int) regnum); ret = -UNW_EBADREG; goto fail; } if ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0) goto fail; set_reg (sr, regnum, DWARF_WHERE_CFAREL, val * dci->data_align); Debug (15, "CFA_offset r%lu at cfa+0x%lx\n", (long) regnum, (long) (val * dci->data_align)); break; case DW_CFA_offset_extended: if (((ret = read_regnum (as, a, addr, ®num, arg)) < 0) || ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0)) goto fail; set_reg (sr, regnum, DWARF_WHERE_CFAREL, val * dci->data_align); Debug (15, "CFA_offset_extended r%lu at cf+0x%lx\n", (long) regnum, (long) (val * dci->data_align)); break; case DW_CFA_offset_extended_sf: if (((ret = read_regnum (as, a, addr, ®num, arg)) < 0) || ((ret = dwarf_read_sleb128 (as, a, addr, &val, arg)) < 0)) goto fail; set_reg (sr, regnum, DWARF_WHERE_CFAREL, val * dci->data_align); Debug (15, "CFA_offset_extended_sf r%lu at cf+0x%lx\n", (long) regnum, (long) (val * dci->data_align)); break; case DW_CFA_restore: regnum = operand; if (regnum >= DWARF_NUM_PRESERVED_REGS) { Debug (1, "Invalid register number %u in DW_CFA_restore\n", (unsigned int) regnum); ret = -UNW_EINVAL; goto fail; } sr->rs_current.reg[regnum] = sr->rs_initial.reg[regnum]; Debug (15, "CFA_restore r%lu\n", (long) regnum); break; case DW_CFA_restore_extended: if ((ret = dwarf_read_uleb128 (as, a, addr, ®num, arg)) < 0) goto fail; if (regnum >= DWARF_NUM_PRESERVED_REGS) { Debug (1, "Invalid register number %u in " "DW_CFA_restore_extended\n", (unsigned int) regnum); ret = -UNW_EINVAL; goto fail; } sr->rs_current.reg[regnum] = sr->rs_initial.reg[regnum]; Debug (15, "CFA_restore_extended r%lu\n", (long) regnum); break; case DW_CFA_nop: break; case DW_CFA_set_loc: fde_encoding = dci->fde_encoding; if ((ret = dwarf_read_encoded_pointer (as, a, addr, fde_encoding, c->pi.gp, c->pi.start_ip, &curr_ip, arg)) < 0) goto fail; Debug (15, "CFA_set_loc to 0x%lx\n", (long) curr_ip); break; case DW_CFA_undefined: if ((ret = read_regnum (as, a, addr, ®num, arg)) < 0) goto fail; set_reg (sr, regnum, DWARF_WHERE_UNDEF, 0); Debug (15, "CFA_undefined r%lu\n", (long) regnum); break; case DW_CFA_same_value: if ((ret = read_regnum (as, a, addr, ®num, arg)) < 0) goto fail; set_reg (sr, regnum, DWARF_WHERE_SAME, 0); Debug (15, "CFA_same_value r%lu\n", (long) regnum); break; case DW_CFA_register: if (((ret = read_regnum (as, a, addr, ®num, arg)) < 0) || ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0)) goto fail; set_reg (sr, regnum, DWARF_WHERE_REG, val); Debug (15, "CFA_register r%lu to r%lu\n", (long) regnum, (long) val); break; case DW_CFA_remember_state: new_rs = alloc_reg_state (); if (!new_rs) { Debug (1, "Out of memory in DW_CFA_remember_state\n"); ret = -UNW_ENOMEM; goto fail; } memcpy (new_rs->reg, sr->rs_current.reg, sizeof (new_rs->reg)); new_rs->next = rs_stack; rs_stack = new_rs; Debug (15, "CFA_remember_state\n"); break; case DW_CFA_restore_state: if (!rs_stack) { Debug (1, "register-state stack underflow\n"); ret = -UNW_EINVAL; goto fail; } memcpy (&sr->rs_current.reg, &rs_stack->reg, sizeof (rs_stack->reg)); old_rs = rs_stack; rs_stack = rs_stack->next; free_reg_state (old_rs); Debug (15, "CFA_restore_state\n"); break; case DW_CFA_def_cfa: if (((ret = read_regnum (as, a, addr, ®num, arg)) < 0) || ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0)) goto fail; set_reg (sr, DWARF_CFA_REG_COLUMN, DWARF_WHERE_REG, regnum); set_reg (sr, DWARF_CFA_OFF_COLUMN, 0, val); /* NOT factored! */ Debug (15, "CFA_def_cfa r%lu+0x%lx\n", (long) regnum, (long) val); break; case DW_CFA_def_cfa_sf: if (((ret = read_regnum (as, a, addr, ®num, arg)) < 0) || ((ret = dwarf_read_sleb128 (as, a, addr, &val, arg)) < 0)) goto fail; set_reg (sr, DWARF_CFA_REG_COLUMN, DWARF_WHERE_REG, regnum); set_reg (sr, DWARF_CFA_OFF_COLUMN, 0, val * dci->data_align); /* factored! */ Debug (15, "CFA_def_cfa_sf r%lu+0x%lx\n", (long) regnum, (long) (val * dci->data_align)); break; case DW_CFA_def_cfa_register: if ((ret = read_regnum (as, a, addr, ®num, arg)) < 0) goto fail; set_reg (sr, DWARF_CFA_REG_COLUMN, DWARF_WHERE_REG, regnum); Debug (15, "CFA_def_cfa_register r%lu\n", (long) regnum); break; case DW_CFA_def_cfa_offset: if ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0) goto fail; set_reg (sr, DWARF_CFA_OFF_COLUMN, 0, val); /* NOT factored! */ Debug (15, "CFA_def_cfa_offset 0x%lx\n", (long) val); break; case DW_CFA_def_cfa_offset_sf: if ((ret = dwarf_read_sleb128 (as, a, addr, &val, arg)) < 0) goto fail; set_reg (sr, DWARF_CFA_OFF_COLUMN, 0, val * dci->data_align); /* factored! */ Debug (15, "CFA_def_cfa_offset_sf 0x%lx\n", (long) (val * dci->data_align)); break; case DW_CFA_def_cfa_expression: /* Save the address of the DW_FORM_block for later evaluation. */ set_reg (sr, DWARF_CFA_REG_COLUMN, DWARF_WHERE_EXPR, *addr); if ((ret = dwarf_read_uleb128 (as, a, addr, &len, arg)) < 0) goto fail; Debug (15, "CFA_def_cfa_expr @ 0x%lx [%lu bytes]\n", (long) *addr, (long) len); *addr += len; break; case DW_CFA_expression: if ((ret = read_regnum (as, a, addr, ®num, arg)) < 0) goto fail; /* Save the address of the DW_FORM_block for later evaluation. */ set_reg (sr, regnum, DWARF_WHERE_EXPR, *addr); if ((ret = dwarf_read_uleb128 (as, a, addr, &len, arg)) < 0) goto fail; Debug (15, "CFA_expression r%lu @ 0x%lx [%lu bytes]\n", (long) regnum, (long) addr, (long) len); *addr += len; break; case DW_CFA_val_expression: if ((ret = read_regnum (as, a, addr, ®num, arg)) < 0) goto fail; /* Save the address of the DW_FORM_block for later evaluation. */ set_reg (sr, regnum, DWARF_WHERE_VAL_EXPR, *addr); if ((ret = dwarf_read_uleb128 (as, a, addr, &len, arg)) < 0) goto fail; Debug (15, "CFA_val_expression r%lu @ 0x%lx [%lu bytes]\n", (long) regnum, (long) addr, (long) len); *addr += len; break; case DW_CFA_GNU_args_size: if ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0) goto fail; sr->args_size = val; Debug (15, "CFA_GNU_args_size %lu\n", (long) val); break; case DW_CFA_GNU_negative_offset_extended: /* A comment in GCC says that this is obsoleted by DW_CFA_offset_extended_sf, but that it's used by older PowerPC code. */ if (((ret = read_regnum (as, a, addr, ®num, arg)) < 0) || ((ret = dwarf_read_uleb128 (as, a, addr, &val, arg)) < 0)) goto fail; set_reg (sr, regnum, DWARF_WHERE_CFAREL, -(val * dci->data_align)); Debug (15, "CFA_GNU_negative_offset_extended cfa+0x%lx\n", (long) -(val * dci->data_align)); break; case DW_CFA_GNU_window_save: #ifdef UNW_TARGET_SPARC /* This is a special CFA to handle all 16 windowed registers on SPARC. */ for (regnum = 16; regnum < 32; ++regnum) set_reg (sr, regnum, DWARF_WHERE_CFAREL, (regnum - 16) * sizeof (unw_word_t)); Debug (15, "CFA_GNU_window_save\n"); break; #else /* FALL THROUGH */ #endif case DW_CFA_lo_user: case DW_CFA_hi_user: Debug (1, "Unexpected CFA opcode 0x%x\n", op); ret = -UNW_EINVAL; goto fail; } } ret = 0; fail: /* Free the register-state stack, if not empty already. */ while (rs_stack) { old_rs = rs_stack; rs_stack = rs_stack->next; free_reg_state (old_rs); } return ret; }
/* Note: we don't need to keep track of more than the first four characters of the augmentation string, because we (a) ignore any augmentation string contents once we find an unrecognized character and (b) those characters that we do recognize, can't be repeated. */ static inline int parse_cie (unw_addr_space_t as, unw_accessors_t *a, unw_word_t addr, const unw_proc_info_t *pi, struct dwarf_cie_info *dci, unw_word_t base, void *arg) { uint8_t version, ch, augstr[5], fde_encoding, handler_encoding; unw_word_t len, cie_end_addr, aug_size; uint32_t u32val; uint64_t u64val; size_t i; int ret; # define STR2(x) #x # define STR(x) STR2(x) /* Pick appropriate default for FDE-encoding. DWARF spec says start-IP (initial_location) and the code-size (address_range) are "address-unit sized constants". The `R' augmentation can be used to override this, but by default, we pick an address-sized unit for fde_encoding. */ switch (dwarf_addr_size (as)) { case 4: fde_encoding = DW_EH_PE_udata4; break; case 8: fde_encoding = DW_EH_PE_udata8; break; default: fde_encoding = DW_EH_PE_omit; break; } dci->lsda_encoding = DW_EH_PE_omit; dci->handler = 0; if ((ret = dwarf_readu32 (as, a, &addr, &u32val, arg)) < 0) return ret; if (u32val != 0xffffffff) { /* the CIE is in the 32-bit DWARF format */ uint32_t cie_id; /* DWARF says CIE id should be 0xffffffff, but in .eh_frame, it's 0 */ const uint32_t expected_id = (base) ? 0xffffffff : 0; len = u32val; cie_end_addr = addr + len; if ((ret = dwarf_readu32 (as, a, &addr, &cie_id, arg)) < 0) return ret; if (cie_id != expected_id) { Debug (1, "Unexpected CIE id %x\n", cie_id); return -UNW_EINVAL; } } else { /* the CIE is in the 64-bit DWARF format */ uint64_t cie_id; /* DWARF says CIE id should be 0xffffffffffffffff, but in .eh_frame, it's 0 */ const uint64_t expected_id = (base) ? 0xffffffffffffffffull : 0; if ((ret = dwarf_readu64 (as, a, &addr, &u64val, arg)) < 0) return ret; len = u64val; cie_end_addr = addr + len; if ((ret = dwarf_readu64 (as, a, &addr, &cie_id, arg)) < 0) return ret; if (cie_id != expected_id) { Debug (1, "Unexpected CIE id %llx\n", (long long) cie_id); return -UNW_EINVAL; } } dci->cie_instr_end = cie_end_addr; if ((ret = dwarf_readu8 (as, a, &addr, &version, arg)) < 0) return ret; if (version != 1 && version != DWARF_CIE_VERSION) { Debug (1, "Got CIE version %u, expected version 1 or " STR (DWARF_CIE_VERSION) "\n", version); return -UNW_EBADVERSION; } /* read and parse the augmentation string: */ memset (augstr, 0, sizeof (augstr)); for (i = 0;;) { if ((ret = dwarf_readu8 (as, a, &addr, &ch, arg)) < 0) return ret; if (!ch) break; /* end of augmentation string */ if (i < sizeof (augstr) - 1) augstr[i++] = ch; } if ((ret = dwarf_read_uleb128 (as, a, &addr, &dci->code_align, arg)) < 0 || (ret = dwarf_read_sleb128 (as, a, &addr, &dci->data_align, arg)) < 0) return ret; /* Read the return-address column either as a u8 or as a uleb128. */ if (version == 1) { if ((ret = dwarf_readu8 (as, a, &addr, &ch, arg)) < 0) return ret; dci->ret_addr_column = ch; } else if ((ret = dwarf_read_uleb128 (as, a, &addr, &dci->ret_addr_column, arg)) < 0) return ret; i = 0; if (augstr[0] == 'z') { dci->sized_augmentation = 1; if ((ret = dwarf_read_uleb128 (as, a, &addr, &aug_size, arg)) < 0) return ret; i++; } for (; i < sizeof (augstr) && augstr[i]; ++i) switch (augstr[i]) { case 'L': /* read the LSDA pointer-encoding format. */ if ((ret = dwarf_readu8 (as, a, &addr, &ch, arg)) < 0) return ret; dci->lsda_encoding = ch; break; case 'R': /* read the FDE pointer-encoding format. */ if ((ret = dwarf_readu8 (as, a, &addr, &fde_encoding, arg)) < 0) return ret; break; case 'P': /* read the personality-routine pointer-encoding format. */ if ((ret = dwarf_readu8 (as, a, &addr, &handler_encoding, arg)) < 0) return ret; if ((ret = dwarf_read_encoded_pointer (as, a, &addr, handler_encoding, pi->gp, pi->start_ip, &dci->handler, arg)) < 0) return ret; break; case 'S': /* This is a signal frame. */ dci->signal_frame = 1; /* Temporarily set it to one so dwarf_parse_fde() knows that it should fetch the actual ABI/TAG pair from the FDE. */ dci->have_abi_marker = 1; break; default: Debug (1, "Unexpected augmentation string `%s'\n", augstr); if (dci->sized_augmentation) /* If we have the size of the augmentation body, we can skip over the parts that we don't understand, so we're OK. */ goto done; else return -UNW_EINVAL; } done: dci->fde_encoding = fde_encoding; dci->cie_instr_start = addr; Debug (15, "CIE parsed OK, augmentation = \"%s\", handler=0x%lx\n", augstr, (long) dci->handler); return 0; }
HIDDEN int dwarf_extract_proc_info_from_fde (unw_addr_space_t as, unw_accessors_t *a, unw_word_t *addrp, unw_proc_info_t *pi, int need_unwind_info, unw_word_t base, void *arg) { unw_word_t fde_end_addr, cie_addr, cie_offset_addr, aug_end_addr = 0; unw_word_t start_ip, ip_range, aug_size, addr = *addrp; int ret, ip_range_encoding; struct dwarf_cie_info dci; uint64_t u64val; uint32_t u32val; Debug (12, "FDE @ 0x%lx\n", (long) addr); memset (&dci, 0, sizeof (dci)); if ((ret = dwarf_readu32 (as, a, &addr, &u32val, arg)) < 0) return ret; if (u32val != 0xffffffff) { int32_t cie_offset; /* In some configurations, an FDE with a 0 length indicates the end of the FDE-table. */ if (u32val == 0) return -UNW_ENOINFO; /* the FDE is in the 32-bit DWARF format */ *addrp = fde_end_addr = addr + u32val; cie_offset_addr = addr; if ((ret = dwarf_reads32 (as, a, &addr, &cie_offset, arg)) < 0) return ret; if (is_cie_id (cie_offset, base != 0)) /* ignore CIEs (happens during linear searches) */ return 0; if (base != 0) cie_addr = base + cie_offset; else /* DWARF says that the CIE_pointer in the FDE is a .debug_frame-relative offset, but the GCC-generated .eh_frame sections instead store a "pcrelative" offset, which is just as fine as it's self-contained. */ cie_addr = cie_offset_addr - cie_offset; } else { int64_t cie_offset; /* the FDE is in the 64-bit DWARF format */ if ((ret = dwarf_readu64 (as, a, &addr, &u64val, arg)) < 0) return ret; *addrp = fde_end_addr = addr + u64val; cie_offset_addr = addr; if ((ret = dwarf_reads64 (as, a, &addr, &cie_offset, arg)) < 0) return ret; if (is_cie_id (cie_offset, base != 0)) /* ignore CIEs (happens during linear searches) */ return 0; if (base != 0) cie_addr = base + cie_offset; else /* DWARF says that the CIE_pointer in the FDE is a .debug_frame-relative offset, but the GCC-generated .eh_frame sections instead store a "pcrelative" offset, which is just as fine as it's self-contained. */ cie_addr = (unw_word_t) ((uint64_t) cie_offset_addr - cie_offset); } Debug (15, "looking for CIE at address %lx\n", (long) cie_addr); if ((ret = parse_cie (as, a, cie_addr, pi, &dci, base, arg)) < 0) return ret; /* IP-range has same encoding as FDE pointers, except that it's always an absolute value: */ ip_range_encoding = dci.fde_encoding & DW_EH_PE_FORMAT_MASK; if ((ret = dwarf_read_encoded_pointer (as, a, &addr, dci.fde_encoding, pi->gp, pi->start_ip, &start_ip, arg)) < 0 || (ret = dwarf_read_encoded_pointer (as, a, &addr, ip_range_encoding, pi->gp, pi->start_ip, &ip_range, arg)) < 0) return ret; pi->start_ip = start_ip; pi->end_ip = start_ip + ip_range; pi->handler = dci.handler; if (dci.sized_augmentation) { if ((ret = dwarf_read_uleb128 (as, a, &addr, &aug_size, arg)) < 0) return ret; aug_end_addr = addr + aug_size; } if ((ret = dwarf_read_encoded_pointer (as, a, &addr, dci.lsda_encoding, pi->gp, pi->start_ip, &pi->lsda, arg)) < 0) return ret; Debug (15, "FDE covers IP 0x%lx-0x%lx, LSDA=0x%lx\n", (long) pi->start_ip, (long) pi->end_ip, (long) pi->lsda); if (need_unwind_info) { pi->format = UNW_INFO_FORMAT_TABLE; pi->unwind_info_size = sizeof (dci); pi->unwind_info = mempool_alloc (&dwarf_cie_info_pool); if (!pi->unwind_info) return -UNW_ENOMEM; if (dci.have_abi_marker) { if ((ret = dwarf_readu16 (as, a, &addr, &dci.abi, arg)) < 0 || (ret = dwarf_readu16 (as, a, &addr, &dci.tag, arg)) < 0) return ret; Debug (13, "Found ABI marker = (abi=%u, tag=%u)\n", dci.abi, dci.tag); } if (dci.sized_augmentation) dci.fde_instr_start = aug_end_addr; else dci.fde_instr_start = addr; dci.fde_instr_end = fde_end_addr; memcpy (pi->unwind_info, &dci, sizeof (dci)); } return 0; }