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 */ if ((ptr = memchr(aug = (const void *)ptr, 0, end - 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 get_member_offset(Dwarf_Die *memdie, Dwarf_Word *off_out) { Dwarf_Attribute loc_attr; Dwarf_Block block; if (dwarf_attr_integrate(memdie, DW_AT_data_member_location, &loc_attr) == NULL) dwarf_err(EX_DATAERR, "dwarf_attr_integrate(%s/loc)", dwarf_diename(memdie)); switch (dwarf_whatform(&loc_attr)) { case DW_FORM_block: case DW_FORM_block1: case DW_FORM_block2: case DW_FORM_block4: if (dwarf_formblock(&loc_attr, &block)) dwarf_err(EX_DATAERR, "dwarf_formblock(%s)", dwarf_diename(memdie)); assert(block.data[0] == DW_OP_plus_uconst || block.data[0] == DW_OP_constu); get_uleb128(off_out, &block.data[1]); return (0); default: printf("ZZZ!\n"); return (-1); } }
static struct dwarf_fde * intern_fde (Dwarf_CFI *cache, const Dwarf_FDE *entry) { /* Look up the new entry's CIE. */ struct dwarf_cie *cie = __libdw_find_cie (cache, entry->CIE_pointer); if (cie == NULL) return (void *) -1l; struct dwarf_fde *fde = malloc (sizeof (struct dwarf_fde)); if (fde == NULL) { __libdw_seterrno (DWARF_E_NOMEM); return NULL; } fde->instructions = entry->start; fde->instructions_end = entry->end; if (unlikely (read_encoded_value (cache, cie->fde_encoding, &fde->instructions, &fde->start)) || unlikely (read_encoded_value (cache, cie->fde_encoding & 0x0f, &fde->instructions, &fde->end))) { free (fde); __libdw_seterrno (DWARF_E_INVALID_DWARF); return NULL; } fde->end += fde->start; fde->cie = cie; if (cie->sized_augmentation_data) { /* The CIE augmentation says the FDE has a DW_FORM_block before its actual instruction stream. */ Dwarf_Word len; get_uleb128 (len, fde->instructions, fde->instructions_end); if ((Dwarf_Word) (fde->instructions_end - fde->instructions) < len) { free (fde); __libdw_seterrno (DWARF_E_INVALID_DWARF); return NULL; } fde->instructions += len; } else /* We had to understand all of the CIE augmentation string. We've recorded the number of data bytes in FDEs. */ fde->instructions += cie->fde_augmentation_data_size; /* Add the new entry to the search tree. */ if (tsearch (fde, &cache->fde_tree, &compare_fde) == NULL) { free (fde); __libdw_seterrno (DWARF_E_NOMEM); return NULL; } return fde; }
int dwarf_hasattr (Dwarf_Die *die, unsigned int search_name) { if (die == NULL) return 0; /* Find the abbreviation entry. */ Dwarf_Abbrev *abbrevp = __libdw_dieabbrev (die, NULL); if (unlikely (abbrevp == DWARF_END_ABBREV)) { invalid_dwarf: __libdw_seterrno (DWARF_E_INVALID_DWARF); return 0; } Dwarf *dbg = die->cu->dbg; /* Search the name attribute. */ unsigned char *const endp = ((unsigned char *) dbg->sectiondata[IDX_debug_abbrev]->d_buf + dbg->sectiondata[IDX_debug_abbrev]->d_size); const unsigned char *attrp = abbrevp->attrp; while (1) { /* Are we still in bounds? This test needs to be refined. */ if (unlikely (attrp >= endp)) goto invalid_dwarf; /* Get attribute name and form. */ unsigned int attr_name; get_uleb128 (attr_name, attrp, endp); unsigned int attr_form; if (unlikely (attrp >= endp)) goto invalid_dwarf; get_uleb128 (attr_form, attrp, endp); /* We can stop if we found the attribute with value zero. */ if (attr_name == 0 || attr_form == 0) return 0; if (attr_name == search_name) return 1; } }
int dwarf_formblock (Dwarf_Attribute *attr, Dwarf_Block *return_block) { if (attr == NULL) return -1; const unsigned char *datap = attr->valp; const unsigned char *endp = attr->cu->endp; switch (attr->form) { case DW_FORM_block1: if (unlikely (endp - datap < 1)) goto invalid; return_block->length = *(uint8_t *) attr->valp; return_block->data = attr->valp + 1; break; case DW_FORM_block2: if (unlikely (endp - datap < 2)) goto invalid; return_block->length = read_2ubyte_unaligned (attr->cu->dbg, attr->valp); return_block->data = attr->valp + 2; break; case DW_FORM_block4: if (unlikely (endp - datap < 4)) goto invalid; return_block->length = read_4ubyte_unaligned (attr->cu->dbg, attr->valp); return_block->data = attr->valp + 4; break; case DW_FORM_block: case DW_FORM_exprloc: if (unlikely (endp - datap < 1)) goto invalid; get_uleb128 (return_block->length, datap, endp); return_block->data = (unsigned char *) datap; break; default: __libdw_seterrno (DWARF_E_NO_BLOCK); return -1; } if (unlikely (return_block->length > (size_t) (endp - return_block->data))) { /* Block does not fit. */ invalid: __libdw_seterrno (DWARF_E_INVALID_DWARF); return -1; } return 0; }
internal_function_def __libdw_find_attr (Dwarf_Die *die, unsigned int search_name, unsigned int *codep, unsigned int *formp) { Dwarf *dbg = die->cu->dbg; unsigned char *readp = (unsigned char *) die->addr; /* First we have to get the abbreviation code so that we can decode the data in the DIE. */ unsigned int abbrev_code; get_uleb128 (abbrev_code, readp); /* Find the abbreviation entry. */ Dwarf_Abbrev *abbrevp = die->abbrev; if (abbrevp == NULL) { abbrevp = __libdw_findabbrev (die->cu, abbrev_code); die->abbrev = abbrevp ?: (Dwarf_Abbrev *) -1l; }
int dwarf_getlocation_implicit_value (Dwarf_Attribute *attr, const Dwarf_Op *op, Dwarf_Block *return_block) { if (attr == NULL) return -1; struct loc_block_s fake = { .addr = (void *) op }; struct loc_block_s **found = tfind (&fake, &attr->cu->locs, loc_compare); if (unlikely (found == NULL)) { __libdw_seterrno (DWARF_E_NO_BLOCK); return -1; } return_block->length = (*found)->length; return_block->data = (*found)->data; return 0; } /* DW_AT_data_member_location can be a constant as well as a loclistptr. Only data[48] indicate a loclistptr. */ static int check_constant_offset (Dwarf_Attribute *attr, Dwarf_Op **llbuf, size_t *listlen) { if (attr->code != DW_AT_data_member_location) return 1; switch (attr->form) { /* Punt for any non-constant form. */ default: return 1; case DW_FORM_data1: case DW_FORM_data2: case DW_FORM_data4: case DW_FORM_data8: case DW_FORM_sdata: case DW_FORM_udata: break; } /* Check whether we already cached this location. */ struct loc_s fake = { .addr = attr->valp }; struct loc_s **found = tfind (&fake, &attr->cu->locs, loc_compare); if (found == NULL) { Dwarf_Word offset; if (INTUSE(dwarf_formudata) (attr, &offset) != 0) return -1; Dwarf_Op *result = libdw_alloc (attr->cu->dbg, Dwarf_Op, sizeof (Dwarf_Op), 1); result->atom = DW_OP_plus_uconst; result->number = offset; result->number2 = 0; result->offset = 0; /* Insert a record in the search tree so we can find it again later. */ struct loc_s *newp = libdw_alloc (attr->cu->dbg, struct loc_s, sizeof (struct loc_s), 1); newp->addr = attr->valp; newp->loc = result; newp->nloc = 1; found = tsearch (newp, &attr->cu->locs, loc_compare); } assert ((*found)->nloc == 1); if (llbuf != NULL) { *llbuf = (*found)->loc; *listlen = 1; } return 0; } int internal_function __libdw_intern_expression (Dwarf *dbg, bool other_byte_order, unsigned int address_size, unsigned int ref_size, void **cache, const Dwarf_Block *block, bool cfap, bool valuep, Dwarf_Op **llbuf, size_t *listlen, int sec_index) { /* Empty location expressions don't have any ops to intern. */ if (block->length == 0) { *listlen = 0; return 0; } /* Check whether we already looked at this list. */ struct loc_s fake = { .addr = block->data }; struct loc_s **found = tfind (&fake, cache, loc_compare); if (found != NULL) { /* We already saw it. */ *llbuf = (*found)->loc; *listlen = (*found)->nloc; if (valuep) { assert (*listlen > 1); assert ((*llbuf)[*listlen - 1].atom == DW_OP_stack_value); } return 0; } const unsigned char *data = block->data; const unsigned char *const end_data = data + block->length; const struct { bool other_byte_order; } bo = { other_byte_order }; struct loclist *loclist = NULL; unsigned int n = 0; /* Stack allocate at most this many locs. */ #define MAX_STACK_LOCS 256 struct loclist stack_locs[MAX_STACK_LOCS]; #define NEW_LOC() ({ struct loclist *ll; \ ll = (likely (n < MAX_STACK_LOCS) \ ? &stack_locs[n] \ : malloc (sizeof (struct loclist))); \ if (unlikely (ll == NULL)) \ goto nomem; \ n++; \ ll->next = loclist; \ loclist = ll; \ ll; }) if (cfap) { /* Synthesize the operation to push the CFA before the expression. */ struct loclist *newloc = NEW_LOC (); newloc->atom = DW_OP_call_frame_cfa; newloc->number = 0; newloc->number2 = 0; newloc->offset = -1; } /* Decode the opcodes. It is possible in some situations to have a block of size zero. */ while (data < end_data) { struct loclist *newloc; newloc = NEW_LOC (); newloc->number = 0; newloc->number2 = 0; newloc->offset = data - block->data; switch ((newloc->atom = *data++)) { case DW_OP_addr: /* Address, depends on address size of CU. */ if (dbg == NULL) { // XXX relocation? if (address_size == 4) { if (unlikely (data + 4 > end_data)) goto invalid; else newloc->number = read_4ubyte_unaligned_inc (&bo, data); } else { if (unlikely (data + 8 > end_data)) goto invalid; else newloc->number = read_8ubyte_unaligned_inc (&bo, data); } } else if (__libdw_read_address_inc (dbg, sec_index, &data, address_size, &newloc->number)) goto invalid; break; case DW_OP_call_ref: /* DW_FORM_ref_addr, depends on offset size of CU. */ if (dbg == NULL || __libdw_read_offset_inc (dbg, sec_index, &data, ref_size, &newloc->number, IDX_debug_info, 0)) goto invalid; break; case DW_OP_deref: case DW_OP_dup: case DW_OP_drop: case DW_OP_over: case DW_OP_swap: case DW_OP_rot: case DW_OP_xderef: case DW_OP_abs: case DW_OP_and: case DW_OP_div: case DW_OP_minus: case DW_OP_mod: case DW_OP_mul: case DW_OP_neg: case DW_OP_not: case DW_OP_or: case DW_OP_plus: case DW_OP_shl: case DW_OP_shr: case DW_OP_shra: case DW_OP_xor: case DW_OP_eq: case DW_OP_ge: case DW_OP_gt: case DW_OP_le: case DW_OP_lt: case DW_OP_ne: case DW_OP_lit0 ... DW_OP_lit31: case DW_OP_reg0 ... DW_OP_reg31: case DW_OP_nop: case DW_OP_push_object_address: case DW_OP_call_frame_cfa: case DW_OP_form_tls_address: case DW_OP_GNU_push_tls_address: case DW_OP_stack_value: /* No operand. */ break; case DW_OP_const1u: case DW_OP_pick: case DW_OP_deref_size: case DW_OP_xderef_size: if (unlikely (data >= end_data)) { invalid: __libdw_seterrno (DWARF_E_INVALID_DWARF); returnmem: /* Free any dynamicly allocated loclists, if any. */ while (n > MAX_STACK_LOCS) { struct loclist *loc = loclist; loclist = loc->next; free (loc); n--; } return -1; } newloc->number = *data++; break; case DW_OP_const1s: if (unlikely (data >= end_data)) goto invalid; newloc->number = *((int8_t *) data); ++data; break; case DW_OP_const2u: if (unlikely (data + 2 > end_data)) goto invalid; newloc->number = read_2ubyte_unaligned_inc (&bo, data); break; case DW_OP_const2s: case DW_OP_skip: case DW_OP_bra: case DW_OP_call2: if (unlikely (data + 2 > end_data)) goto invalid; newloc->number = read_2sbyte_unaligned_inc (&bo, data); break; case DW_OP_const4u: if (unlikely (data + 4 > end_data)) goto invalid; newloc->number = read_4ubyte_unaligned_inc (&bo, data); break; case DW_OP_const4s: case DW_OP_call4: case DW_OP_GNU_parameter_ref: if (unlikely (data + 4 > end_data)) goto invalid; newloc->number = read_4sbyte_unaligned_inc (&bo, data); break; case DW_OP_const8u: if (unlikely (data + 8 > end_data)) goto invalid; newloc->number = read_8ubyte_unaligned_inc (&bo, data); break; case DW_OP_const8s: if (unlikely (data + 8 > end_data)) goto invalid; newloc->number = read_8sbyte_unaligned_inc (&bo, data); break; case DW_OP_constu: case DW_OP_plus_uconst: case DW_OP_regx: case DW_OP_piece: case DW_OP_GNU_convert: case DW_OP_GNU_reinterpret: get_uleb128 (newloc->number, data, end_data); break; case DW_OP_consts: case DW_OP_breg0 ... DW_OP_breg31: case DW_OP_fbreg: get_sleb128 (newloc->number, data, end_data); break; case DW_OP_bregx: get_uleb128 (newloc->number, data, end_data); if (unlikely (data >= end_data)) goto invalid; get_sleb128 (newloc->number2, data, end_data); break; case DW_OP_bit_piece: case DW_OP_GNU_regval_type: get_uleb128 (newloc->number, data, end_data); if (unlikely (data >= end_data)) goto invalid; get_uleb128 (newloc->number2, data, end_data); break; case DW_OP_implicit_value: case DW_OP_GNU_entry_value: /* This cannot be used in a CFI expression. */ if (unlikely (dbg == NULL)) goto invalid; /* start of block inc. len. */ newloc->number2 = (Dwarf_Word) (uintptr_t) data; get_uleb128 (newloc->number, data, end_data); /* Block length. */ if (unlikely ((Dwarf_Word) (end_data - data) < newloc->number)) goto invalid; data += newloc->number; /* Skip the block. */ break; case DW_OP_GNU_implicit_pointer: /* DW_FORM_ref_addr, depends on offset size of CU. */ if (dbg == NULL || __libdw_read_offset_inc (dbg, sec_index, &data, ref_size, &newloc->number, IDX_debug_info, 0)) goto invalid; if (unlikely (data >= end_data)) goto invalid; get_uleb128 (newloc->number2, data, end_data); /* Byte offset. */ break; case DW_OP_GNU_deref_type: if (unlikely (data + 1 >= end_data)) goto invalid; newloc->number = *data++; get_uleb128 (newloc->number2, data, end_data); break; case DW_OP_GNU_const_type: { size_t size; get_uleb128 (newloc->number, data, end_data); if (unlikely (data >= end_data)) goto invalid; /* start of block inc. len. */ newloc->number2 = (Dwarf_Word) (uintptr_t) data; size = *data++; if (unlikely ((Dwarf_Word) (end_data - data) < size)) goto invalid; data += size; /* Skip the block. */ } break; default: goto invalid; } } if (unlikely (n == 0)) { /* This is not allowed. It would mean an empty location expression, which we handled already as a special case above. */ goto invalid; } if (valuep) { struct loclist *newloc = NEW_LOC (); newloc->atom = DW_OP_stack_value; newloc->number = 0; newloc->number2 = 0; newloc->offset = data - block->data; } /* Allocate the array. */ Dwarf_Op *result; if (dbg != NULL) result = libdw_alloc (dbg, Dwarf_Op, sizeof (Dwarf_Op), n); else { result = malloc (sizeof *result * n); if (result == NULL) { nomem: __libdw_seterrno (DWARF_E_NOMEM); goto returnmem; } } /* Store the result. */ *llbuf = result; *listlen = n; do { /* We populate the array from the back since the list is backwards. */ --n; result[n].atom = loclist->atom; result[n].number = loclist->number; result[n].number2 = loclist->number2; result[n].offset = loclist->offset; if (result[n].atom == DW_OP_implicit_value) store_implicit_value (dbg, cache, &result[n]); struct loclist *loc = loclist; loclist = loclist->next; if (unlikely (n + 1 > MAX_STACK_LOCS)) free (loc); } while (n > 0); /* Insert a record in the search tree so that we can find it again later. */ struct loc_s *newp; if (dbg != NULL) newp = libdw_alloc (dbg, struct loc_s, sizeof (struct loc_s), 1); else { newp = malloc (sizeof *newp); if (newp == NULL) { free (result); goto nomem; } } newp->addr = block->data; newp->loc = result; newp->nloc = *listlen; (void) tsearch (newp, cache, loc_compare); /* We did it. */ return 0; } static int getlocation (struct Dwarf_CU *cu, const Dwarf_Block *block, Dwarf_Op **llbuf, size_t *listlen, int sec_index) { /* Empty location expressions don't have any ops to intern. Note that synthetic empty_cu doesn't have an associated DWARF dbg. */ if (block->length == 0) { *listlen = 0; return 0; } return __libdw_intern_expression (cu->dbg, cu->dbg->other_byte_order, cu->address_size, (cu->version == 2 ? cu->address_size : cu->offset_size), &cu->locs, block, false, false, llbuf, listlen, sec_index); } int dwarf_getlocation (Dwarf_Attribute *attr, Dwarf_Op **llbuf, size_t *listlen) { if (! attr_ok (attr)) return -1; int result = check_constant_offset (attr, llbuf, listlen); if (result != 1) return result; /* If it has a block form, it's a single location expression. */ Dwarf_Block block; if (INTUSE(dwarf_formblock) (attr, &block) != 0) return -1; return getlocation (attr->cu, &block, llbuf, listlen, cu_sec_idx (attr->cu)); } static int attr_base_address (Dwarf_Attribute *attr, Dwarf_Addr *basep) { /* Fetch the CU's base address. */ Dwarf_Die cudie = CUDIE (attr->cu); /* Find the base address of the compilation unit. It will normally be specified by DW_AT_low_pc. In DWARF-3 draft 4, the base address could be overridden by DW_AT_entry_pc. It's been removed, but GCC emits DW_AT_entry_pc and not DW_AT_lowpc for compilation units with discontinuous ranges. */ Dwarf_Attribute attr_mem; if (unlikely (INTUSE(dwarf_lowpc) (&cudie, basep) != 0) && INTUSE(dwarf_formaddr) (INTUSE(dwarf_attr) (&cudie, DW_AT_entry_pc, &attr_mem), basep) != 0) { if (INTUSE(dwarf_errno) () != 0) return -1; /* The compiler provided no base address when it should have. Buggy GCC does this when it used absolute addresses in the location list and no DW_AT_ranges. */ *basep = 0; } return 0; }
int dwarf_getsrclines (Dwarf_Die *cudie, Dwarf_Lines **lines, size_t *nlines) { if (unlikely (cudie == NULL || INTUSE(dwarf_tag) (cudie) != DW_TAG_compile_unit)) return -1; int res = -1; /* Get the information if it is not already known. */ struct Dwarf_CU *const cu = cudie->cu; if (cu->lines == NULL) { /* Failsafe mode: no data found. */ cu->lines = (void *) -1l; cu->files = (void *) -1l; /* The die must have a statement list associated. */ Dwarf_Attribute stmt_list_mem; Dwarf_Attribute *stmt_list = INTUSE(dwarf_attr) (cudie, DW_AT_stmt_list, &stmt_list_mem); /* Get the offset into the .debug_line section. NB: this call also checks whether the previous dwarf_attr call failed. */ Dwarf_Word offset; if (INTUSE(dwarf_formudata) (stmt_list, &offset) != 0) goto out; Dwarf *dbg = cu->dbg; if (dbg->sectiondata[IDX_debug_line] == NULL) { __libdw_seterrno (DWARF_E_NO_DEBUG_LINE); goto out; } const uint8_t *linep = dbg->sectiondata[IDX_debug_line]->d_buf + offset; const uint8_t *lineendp = (dbg->sectiondata[IDX_debug_line]->d_buf + dbg->sectiondata[IDX_debug_line]->d_size); /* Get the compilation directory. */ Dwarf_Attribute compdir_attr_mem; Dwarf_Attribute *compdir_attr = INTUSE(dwarf_attr) (cudie, DW_AT_comp_dir, &compdir_attr_mem); const char *comp_dir = INTUSE(dwarf_formstring) (compdir_attr); if (unlikely (linep + 4 > lineendp)) { invalid_data: __libdw_seterrno (DWARF_E_INVALID_DEBUG_LINE); goto out; } Dwarf_Word unit_length = read_4ubyte_unaligned_inc (dbg, linep); unsigned int length = 4; if (unlikely (unit_length == DWARF3_LENGTH_64_BIT)) { if (unlikely (linep + 8 > lineendp)) goto invalid_data; unit_length = read_8ubyte_unaligned_inc (dbg, linep); length = 8; } /* Check whether we have enough room in the section. */ if (unit_length < 2 + length + 5 * 1 || unlikely (linep + unit_length > lineendp)) goto invalid_data; lineendp = linep + unit_length; /* The next element of the header is the version identifier. */ uint_fast16_t version = read_2ubyte_unaligned_inc (dbg, linep); if (unlikely (version > DWARF_VERSION)) { __libdw_seterrno (DWARF_E_VERSION); goto out; } /* Next comes the header length. */ Dwarf_Word header_length; if (length == 4) header_length = read_4ubyte_unaligned_inc (dbg, linep); else header_length = read_8ubyte_unaligned_inc (dbg, linep); const unsigned char *header_start = linep; /* Next the minimum instruction length. */ uint_fast8_t minimum_instr_len = *linep++; /* Then the flag determining the default value of the is_stmt register. */ uint_fast8_t default_is_stmt = *linep++; /* Now the line base. */ int_fast8_t line_base = *((int_fast8_t *) linep); ++linep; /* And the line range. */ uint_fast8_t line_range = *linep++; /* The opcode base. */ uint_fast8_t opcode_base = *linep++; /* Remember array with the standard opcode length (-1 to account for the opcode with value zero not being mentioned). */ const uint8_t *standard_opcode_lengths = linep - 1; linep += opcode_base - 1; if (unlikely (linep >= lineendp)) goto invalid_data; /* First comes the list of directories. Add the compilation directory first since the index zero is used for it. */ struct dirlist { const char *dir; size_t len; struct dirlist *next; } comp_dir_elem = { .dir = comp_dir, .len = comp_dir ? strlen (comp_dir) : 0, .next = NULL }; struct dirlist *dirlist = &comp_dir_elem; unsigned int ndirlist = 1; // XXX Directly construct array to conserve memory? while (*linep != 0) { struct dirlist *new_dir = (struct dirlist *) alloca (sizeof (*new_dir)); new_dir->dir = (char *) linep; uint8_t *endp = memchr (linep, '\0', lineendp - linep); if (endp == NULL) goto invalid_data; new_dir->len = endp - linep; new_dir->next = dirlist; dirlist = new_dir; ++ndirlist; linep = endp + 1; } /* Skip the final NUL byte. */ ++linep; /* Rearrange the list in array form. */ struct dirlist **dirarray = (struct dirlist **) alloca (ndirlist * sizeof (*dirarray)); for (unsigned int n = ndirlist; n-- > 0; dirlist = dirlist->next) dirarray[n] = dirlist; /* Now read the files. */ struct filelist null_file = { .info = { .name = "???", .mtime = 0, .length = 0 }, .next = NULL }; struct filelist *filelist = &null_file; unsigned int nfilelist = 1; if (unlikely (linep >= lineendp)) goto invalid_data; while (*linep != 0) { struct filelist *new_file = (struct filelist *) alloca (sizeof (*new_file)); /* First comes the file name. */ char *fname = (char *) linep; uint8_t *endp = memchr (fname, '\0', lineendp - linep); if (endp == NULL) goto invalid_data; size_t fnamelen = endp - (uint8_t *) fname; linep = endp + 1; /* Then the index. */ Dwarf_Word diridx; get_uleb128 (diridx, linep); if (unlikely (diridx >= ndirlist)) { __libdw_seterrno (DWARF_E_INVALID_DIR_IDX); goto out; } if (*fname == '/') /* It's an absolute path. */ new_file->info.name = fname; else { new_file->info.name = libdw_alloc (dbg, char, 1, dirarray[diridx]->len + 1 + fnamelen + 1); char *cp = new_file->info.name; if (dirarray[diridx]->dir != NULL) { /* This value could be NULL in case the DW_AT_comp_dir was not present. We cannot do much in this case. The easiest thing is to convert the path in an absolute path. */ cp = stpcpy (cp, dirarray[diridx]->dir); } *cp++ = '/'; strcpy (cp, fname); assert (strlen (new_file->info.name) < dirarray[diridx]->len + 1 + fnamelen + 1); } /* Next comes the modification time. */ get_uleb128 (new_file->info.mtime, linep); /* Finally the length of the file. */ get_uleb128 (new_file->info.length, linep); new_file->next = filelist; filelist = new_file; ++nfilelist; } /* Skip the final NUL byte. */ ++linep; /* Consistency check. */ if (unlikely (linep != header_start + header_length)) { __libdw_seterrno (DWARF_E_INVALID_DWARF); goto out; } /* We are about to process the statement program. Initialize the state machine registers (see 6.2.2 in the v2.1 specification). */ Dwarf_Word address = 0; size_t file = 1; size_t line = 1; size_t column = 0; uint_fast8_t is_stmt = default_is_stmt; int basic_block = 0; int prologue_end = 0; int epilogue_begin = 0; /* Process the instructions. */ struct linelist *linelist = NULL; unsigned int nlinelist = 0; while (linep < lineendp) { struct linelist *new_line; unsigned int opcode; unsigned int u128; int s128; /* Read the opcode. */ opcode = *linep++; /* Is this a special opcode? */ if (likely (opcode >= opcode_base)) { /* Yes. Handling this is quite easy since the opcode value is computed with opcode = (desired line increment - line_base) + (line_range * address advance) + opcode_base */ int line_increment = (line_base + (opcode - opcode_base) % line_range); unsigned int address_increment = (minimum_instr_len * ((opcode - opcode_base) / line_range)); /* Perform the increments. */ line += line_increment; address += address_increment; /* Add a new line with the current state machine values. */ NEW_LINE (0); /* Reset the flags. */ basic_block = 0; prologue_end = 0; epilogue_begin = 0; } else if (opcode == 0) { /* This an extended opcode. */ if (unlikely (linep + 2 > lineendp)) goto invalid_data; /* The length. */ unsigned int len = *linep++; if (unlikely (linep + len > lineendp)) goto invalid_data; /* The sub-opcode. */ opcode = *linep++; switch (opcode) { case DW_LNE_end_sequence: /* Add a new line with the current state machine values. The is the end of the sequence. */ NEW_LINE (1); /* Reset the registers. */ address = 0; file = 1; line = 1; column = 0; is_stmt = default_is_stmt; basic_block = 0; prologue_end = 0; epilogue_begin = 0; break; case DW_LNE_set_address: /* The value is an address. The size is defined as apporiate for the target machine. We use the address size field from the CU header. */ if (cu->address_size == 4) address = read_4ubyte_unaligned_inc (dbg, linep); else address = read_8ubyte_unaligned_inc (dbg, linep); break; case DW_LNE_define_file: { char *fname = (char *) linep; uint8_t *endp = memchr (linep, '\0', lineendp - linep); if (endp == NULL) goto invalid_data; size_t fnamelen = endp - linep; linep = endp + 1; unsigned int diridx; get_uleb128 (diridx, linep); Dwarf_Word mtime; get_uleb128 (mtime, linep); Dwarf_Word filelength; get_uleb128 (filelength, linep); struct filelist *new_file = (struct filelist *) alloca (sizeof (*new_file)); if (fname[0] == '/') new_file->info.name = fname; else { new_file->info.name = libdw_alloc (dbg, char, 1, (dirarray[diridx]->len + 1 + fnamelen + 1)); char *cp = new_file->info.name; if (dirarray[diridx]->dir != NULL) /* This value could be NULL in case the DW_AT_comp_dir was not present. We cannot do much in this case. The easiest thing is to convert the path in an absolute path. */ cp = stpcpy (cp, dirarray[diridx]->dir); *cp++ = '/'; strcpy (cp, fname); } new_file->info.mtime = mtime; new_file->info.length = filelength; new_file->next = filelist; filelist = new_file; ++nfilelist; } break; default: /* Unknown, ignore it. */ linep += len - 1; break; } } else if (opcode <= DW_LNS_set_epilogue_begin) { /* This is a known standard opcode. */ switch (opcode) { case DW_LNS_copy: /* Takes no argument. */ if (unlikely (standard_opcode_lengths[opcode] != 0)) goto invalid_data; /* Add a new line with the current state machine values. */ NEW_LINE (0); /* Reset the flags. */ basic_block = 0; /* XXX Whether the following two lines are necessary is unclear. I guess the current v2.1 specification has a bug in that it says clearing these two registers is not necessary. */ prologue_end = 0; epilogue_begin = 0; break; case DW_LNS_advance_pc: /* Takes one uleb128 parameter which is added to the address. */ if (unlikely (standard_opcode_lengths[opcode] != 1)) goto invalid_data; get_uleb128 (u128, linep); address += minimum_instr_len * u128; break; case DW_LNS_advance_line: /* Takes one sleb128 parameter which is added to the line. */ if (unlikely (standard_opcode_lengths[opcode] != 1)) goto invalid_data; get_sleb128 (s128, linep); line += s128; break; case DW_LNS_set_file: /* Takes one uleb128 parameter which is stored in file. */ if (unlikely (standard_opcode_lengths[opcode] != 1)) goto invalid_data; get_uleb128 (u128, linep); file = u128; break; case DW_LNS_set_column: /* Takes one uleb128 parameter which is stored in column. */ if (unlikely (standard_opcode_lengths[opcode] != 1)) goto invalid_data; get_uleb128 (u128, linep); column = u128; break; case DW_LNS_negate_stmt: /* Takes no argument. */ if (unlikely (standard_opcode_lengths[opcode] != 0)) goto invalid_data; is_stmt = 1 - is_stmt; break; case DW_LNS_set_basic_block: /* Takes no argument. */ if (unlikely (standard_opcode_lengths[opcode] != 0)) goto invalid_data; basic_block = 1; break; case DW_LNS_const_add_pc: /* Takes no argument. */ if (unlikely (standard_opcode_lengths[opcode] != 0)) goto invalid_data; address += (minimum_instr_len * ((255 - opcode_base) / line_range)); break; case DW_LNS_fixed_advance_pc: /* Takes one 16 bit parameter which is added to the address. */ if (unlikely (standard_opcode_lengths[opcode] != 1)) goto invalid_data; address += read_2ubyte_unaligned_inc (dbg, linep); break; case DW_LNS_set_prologue_end: /* Takes no argument. */ if (unlikely (standard_opcode_lengths[opcode] != 0)) goto invalid_data; prologue_end = 1; break; case DW_LNS_set_epilogue_begin: /* Takes no argument. */ if (unlikely (standard_opcode_lengths[opcode] != 0)) goto invalid_data; epilogue_begin = 1; break; } } else { /* This is a new opcode the generator but not we know about. Read the parameters associated with it but then discard everything. Read all the parameters for this opcode. */ for (int n = standard_opcode_lengths[opcode]; n > 0; --n) get_uleb128 (u128, linep); /* Next round, ignore this opcode. */ continue; } }
static int getlocation (struct Dwarf_CU *cu, const Dwarf_Block *block, Dwarf_Op **llbuf, size_t *listlen) { Dwarf *dbg = cu->dbg; /* Check whether we already looked at this list. */ struct loc_s fake = { .addr = block->data }; struct loc_s **found = tfind (&fake, &cu->locs, loc_compare); if (found != NULL) { /* We already saw it. */ *llbuf = (*found)->loc; *listlen = (*found)->nloc; return 0; } const unsigned char *data = block->data; const unsigned char *const end_data = data + block->length; struct loclist *loclist = NULL; unsigned int n = 0; /* Decode the opcodes. It is possible in some situations to have a block of size zero. */ while (data < end_data) { struct loclist *newloc; newloc = (struct loclist *) alloca (sizeof (struct loclist)); newloc->number = 0; newloc->number2 = 0; newloc->offset = data - block->data; newloc->next = loclist; loclist = newloc; ++n; switch ((newloc->atom = *data++)) { case DW_OP_addr: /* Address, depends on address size of CU. */ if (cu->address_size == 4) { if (unlikely (data + 4 > end_data)) { invalid: __libdw_seterrno (DWARF_E_INVALID_DWARF); return -1; } newloc->number = read_4ubyte_unaligned_inc (dbg, data); } else { if (unlikely (data + 8 > end_data)) goto invalid; newloc->number = read_8ubyte_unaligned_inc (dbg, data); } break; case DW_OP_deref: case DW_OP_dup: case DW_OP_drop: case DW_OP_over: case DW_OP_swap: case DW_OP_rot: case DW_OP_xderef: case DW_OP_abs: case DW_OP_and: case DW_OP_div: case DW_OP_minus: case DW_OP_mod: case DW_OP_mul: case DW_OP_neg: case DW_OP_not: case DW_OP_or: case DW_OP_plus: case DW_OP_shl: case DW_OP_shr: case DW_OP_shra: case DW_OP_xor: case DW_OP_eq: case DW_OP_ge: case DW_OP_gt: case DW_OP_le: case DW_OP_lt: case DW_OP_ne: case DW_OP_lit0 ... DW_OP_lit31: case DW_OP_reg0 ... DW_OP_reg31: case DW_OP_nop: case DW_OP_push_object_address: case DW_OP_call_ref: /* No operand. */ break; case DW_OP_const1u: case DW_OP_pick: case DW_OP_deref_size: case DW_OP_xderef_size: if (unlikely (data >= end_data)) goto invalid; newloc->number = *data++; break; case DW_OP_const1s: if (unlikely (data >= end_data)) goto invalid; newloc->number = *((int8_t *) data); ++data; break; case DW_OP_const2u: if (unlikely (data + 2 > end_data)) goto invalid; newloc->number = read_2ubyte_unaligned_inc (dbg, data); break; case DW_OP_const2s: case DW_OP_skip: case DW_OP_bra: case DW_OP_call2: if (unlikely (data + 2 > end_data)) goto invalid; newloc->number = read_2sbyte_unaligned_inc (dbg, data); break; case DW_OP_const4u: if (unlikely (data + 4 > end_data)) goto invalid; newloc->number = read_4ubyte_unaligned_inc (dbg, data); break; case DW_OP_const4s: case DW_OP_call4: if (unlikely (data + 4 > end_data)) goto invalid; newloc->number = read_4sbyte_unaligned_inc (dbg, data); break; case DW_OP_const8u: if (unlikely (data + 8 > end_data)) goto invalid; newloc->number = read_8ubyte_unaligned_inc (dbg, data); break; case DW_OP_const8s: if (unlikely (data + 8 > end_data)) goto invalid; newloc->number = read_8sbyte_unaligned_inc (dbg, data); break; case DW_OP_constu: case DW_OP_plus_uconst: case DW_OP_regx: case DW_OP_piece: /* XXX Check size. */ get_uleb128 (newloc->number, data); break; case DW_OP_consts: case DW_OP_breg0 ... DW_OP_breg31: case DW_OP_fbreg: /* XXX Check size. */ get_sleb128 (newloc->number, data); break; case DW_OP_bregx: /* XXX Check size. */ get_uleb128 (newloc->number, data); get_sleb128 (newloc->number2, data); break; default: goto invalid; } } if (unlikely (n == 0)) { /* This is not allowed. XXX Is it? */ goto invalid; } /* Allocate the array. */ Dwarf_Op *result = libdw_alloc (dbg, Dwarf_Op, sizeof (Dwarf_Op), n); /* Store the result. */ *llbuf = result; *listlen = n; do { /* We populate the array from the back since the list is backwards. */ --n; result[n].atom = loclist->atom; result[n].number = loclist->number; result[n].number2 = loclist->number2; result[n].offset = loclist->offset; loclist = loclist->next; } while (n > 0); /* Insert a record in the search tree so that we can find it again later. */ struct loc_s *newp = libdw_alloc (dbg, struct loc_s, sizeof (struct loc_s), 1); newp->addr = block->data; newp->loc = result; newp->nloc = *listlen; (void) tsearch (newp, &cu->locs, loc_compare); /* We did it. */ return 0; } int dwarf_getlocation (attr, llbuf, listlen) Dwarf_Attribute *attr; Dwarf_Op **llbuf; size_t *listlen; { if (! attr_ok (attr)) return -1; /* If it has a block form, it's a single location expression. */ Dwarf_Block block; if (INTUSE(dwarf_formblock) (attr, &block) != 0) return -1; return getlocation (attr->cu, &block, llbuf, listlen); } int dwarf_getlocation_addr (attr, address, llbufs, listlens, maxlocs) Dwarf_Attribute *attr; Dwarf_Addr address; Dwarf_Op **llbufs; size_t *listlens; size_t maxlocs; { if (! attr_ok (attr)) return -1; if (llbufs == NULL) maxlocs = SIZE_MAX; /* If it has a block form, it's a single location expression. */ Dwarf_Block block; if (INTUSE(dwarf_formblock) (attr, &block) == 0) { if (maxlocs == 0) return 0; if (llbufs != NULL && getlocation (attr->cu, &block, &llbufs[0], &listlens[0]) != 0) return -1; return listlens[0] == 0 ? 0 : 1; } int error = INTUSE(dwarf_errno) (); if (unlikely (error != DWARF_E_NO_BLOCK)) { __libdw_seterrno (error); return -1; } /* Must have the form data4 or data8 which act as an offset. */ Dwarf_Word offset; if (unlikely (INTUSE(dwarf_formudata) (attr, &offset) != 0)) return -1; const Elf_Data *d = attr->cu->dbg->sectiondata[IDX_debug_loc]; if (unlikely (d == NULL)) { __libdw_seterrno (DWARF_E_NO_LOCLIST); return -1; } Dwarf_Addr base = (Dwarf_Addr) -1; unsigned char *readp = d->d_buf + offset; size_t got = 0; while (got < maxlocs) { if ((unsigned char *) d->d_buf + d->d_size - readp < attr->cu->address_size * 2) { invalid: __libdw_seterrno (DWARF_E_INVALID_DWARF); return -1; } Dwarf_Addr begin; Dwarf_Addr end; if (attr->cu->address_size == 8) { begin = read_8ubyte_unaligned_inc (attr->cu->dbg, readp); end = read_8ubyte_unaligned_inc (attr->cu->dbg, readp); if (begin == (Elf64_Addr) -1l) /* Base address entry. */ { base = end; if (unlikely (base == (Dwarf_Addr) -1)) goto invalid; continue; } } else { begin = read_4ubyte_unaligned_inc (attr->cu->dbg, readp); end = read_4ubyte_unaligned_inc (attr->cu->dbg, readp); if (begin == (Elf32_Addr) -1) /* Base address entry. */ { base = end; continue; } } if (begin == 0 && end == 0) /* End of list entry. */ break; if ((unsigned char *) d->d_buf + d->d_size - readp < 2) goto invalid; /* We have a location expression. */ block.length = read_2ubyte_unaligned_inc (attr->cu->dbg, readp); block.data = readp; if ((unsigned char *) d->d_buf + d->d_size - readp < (ptrdiff_t) block.length) goto invalid; readp += block.length; if (base == (Dwarf_Addr) -1) { /* Fetch the CU's base address. */ Dwarf_Die cudie = CUDIE (attr->cu); /* Find the base address of the compilation unit. It will normally be specified by DW_AT_low_pc. In DWARF-3 draft 4, the base address could be overridden by DW_AT_entry_pc. It's been removed, but GCC emits DW_AT_entry_pc and not DW_AT_lowpc for compilation units with discontinuous ranges. */ Dwarf_Attribute attr_mem; if (unlikely (INTUSE(dwarf_lowpc) (&cudie, &base) != 0) && INTUSE(dwarf_formaddr) (INTUSE(dwarf_attr) (&cudie, DW_AT_entry_pc, &attr_mem), &base) != 0) { if (INTUSE(dwarf_errno) () != 0) return -1; /* The compiler provided no base address when it should have. Buggy GCC does this when it used absolute addresses in the location list and no DW_AT_ranges. */ base = 0; } } if (address >= base + begin && address < base + end) { /* This one matches the address. */ if (llbufs != NULL && unlikely (getlocation (attr->cu, &block, &llbufs[got], &listlens[got]) != 0)) return -1; ++got; } } return got; }
int dwarf_getsrclines (Dwarf_Die *cudie, Dwarf_Lines **lines, size_t *nlines) { if (unlikely (cudie == NULL || (INTUSE(dwarf_tag) (cudie) != DW_TAG_compile_unit && INTUSE(dwarf_tag) (cudie) != DW_TAG_partial_unit))) return -1; int res = -1; struct linelist *linelist = NULL; unsigned int nlinelist = 0; /* If there are a large number of lines don't blow up the stack. Keep track of the last malloced linelist record and free them through the next pointer at the end. */ #define MAX_STACK_ALLOC 4096 struct linelist *malloc_linelist = NULL; /* Get the information if it is not already known. */ struct Dwarf_CU *const cu = cudie->cu; if (cu->lines == NULL) { /* Failsafe mode: no data found. */ cu->lines = (void *) -1l; cu->files = (void *) -1l; /* The die must have a statement list associated. */ Dwarf_Attribute stmt_list_mem; Dwarf_Attribute *stmt_list = INTUSE(dwarf_attr) (cudie, DW_AT_stmt_list, &stmt_list_mem); /* Get the offset into the .debug_line section. NB: this call also checks whether the previous dwarf_attr call failed. */ const unsigned char *lineendp; const unsigned char *linep = __libdw_formptr (stmt_list, IDX_debug_line, DWARF_E_NO_DEBUG_LINE, (unsigned char **) &lineendp, NULL); if (linep == NULL) goto out; /* Get the compilation directory. */ Dwarf_Attribute compdir_attr_mem; Dwarf_Attribute *compdir_attr = INTUSE(dwarf_attr) (cudie, DW_AT_comp_dir, &compdir_attr_mem); const char *comp_dir = INTUSE(dwarf_formstring) (compdir_attr); if (unlikely (linep + 4 > lineendp)) { invalid_data: __libdw_seterrno (DWARF_E_INVALID_DEBUG_LINE); goto out; } Dwarf *dbg = cu->dbg; Dwarf_Word unit_length = read_4ubyte_unaligned_inc (dbg, linep); unsigned int length = 4; if (unlikely (unit_length == DWARF3_LENGTH_64_BIT)) { if (unlikely (linep + 8 > lineendp)) goto invalid_data; unit_length = read_8ubyte_unaligned_inc (dbg, linep); length = 8; } /* Check whether we have enough room in the section. */ if (unit_length < 2 + length + 5 * 1 || unlikely (linep + unit_length > lineendp)) goto invalid_data; lineendp = linep + unit_length; /* The next element of the header is the version identifier. */ uint_fast16_t version = read_2ubyte_unaligned_inc (dbg, linep); if (unlikely (version < 2) || unlikely (version > 4)) { __libdw_seterrno (DWARF_E_VERSION); goto out; } /* Next comes the header length. */ Dwarf_Word header_length; if (length == 4) header_length = read_4ubyte_unaligned_inc (dbg, linep); else header_length = read_8ubyte_unaligned_inc (dbg, linep); const unsigned char *header_start = linep; /* Next the minimum instruction length. */ uint_fast8_t minimum_instr_len = *linep++; /* Next the maximum operations per instruction, in version 4 format. */ uint_fast8_t max_ops_per_instr = 1; if (version >= 4) { if (unlikely (lineendp - linep < 5)) goto invalid_data; max_ops_per_instr = *linep++; if (unlikely (max_ops_per_instr == 0)) goto invalid_data; } /* Then the flag determining the default value of the is_stmt register. */ uint_fast8_t default_is_stmt = *linep++; /* Now the line base. */ int_fast8_t line_base = (int8_t) *linep++; /* And the line range. */ uint_fast8_t line_range = *linep++; /* The opcode base. */ uint_fast8_t opcode_base = *linep++; /* Remember array with the standard opcode length (-1 to account for the opcode with value zero not being mentioned). */ const uint8_t *standard_opcode_lengths = linep - 1; if (unlikely (lineendp - linep < opcode_base - 1)) goto invalid_data; linep += opcode_base - 1; /* First comes the list of directories. Add the compilation directory first since the index zero is used for it. */ struct dirlist { const char *dir; size_t len; struct dirlist *next; } comp_dir_elem = { .dir = comp_dir, .len = comp_dir ? strlen (comp_dir) : 0, .next = NULL }; struct dirlist *dirlist = &comp_dir_elem; unsigned int ndirlist = 1; // XXX Directly construct array to conserve memory? while (*linep != 0) { struct dirlist *new_dir = (struct dirlist *) alloca (sizeof (*new_dir)); new_dir->dir = (char *) linep; uint8_t *endp = memchr (linep, '\0', lineendp - linep); if (endp == NULL) goto invalid_data; new_dir->len = endp - linep; new_dir->next = dirlist; dirlist = new_dir; ++ndirlist; linep = endp + 1; } /* Skip the final NUL byte. */ ++linep; /* Rearrange the list in array form. */ struct dirlist **dirarray = (struct dirlist **) alloca (ndirlist * sizeof (*dirarray)); for (unsigned int n = ndirlist; n-- > 0; dirlist = dirlist->next) dirarray[n] = dirlist; /* Now read the files. */ struct filelist null_file = { .info = { .name = "???", .mtime = 0, .length = 0 }, .next = NULL }; struct filelist *filelist = &null_file; unsigned int nfilelist = 1; if (unlikely (linep >= lineendp)) goto invalid_data; while (*linep != 0) { struct filelist *new_file = (struct filelist *) alloca (sizeof (*new_file)); /* First comes the file name. */ char *fname = (char *) linep; uint8_t *endp = memchr (fname, '\0', lineendp - linep); if (endp == NULL) goto invalid_data; size_t fnamelen = endp - (uint8_t *) fname; linep = endp + 1; /* Then the index. */ Dwarf_Word diridx; get_uleb128 (diridx, linep); if (unlikely (diridx >= ndirlist)) { __libdw_seterrno (DWARF_E_INVALID_DIR_IDX); goto out; } if (*fname == '/') /* It's an absolute path. */ new_file->info.name = fname; else { new_file->info.name = libdw_alloc (dbg, char, 1, dirarray[diridx]->len + 1 + fnamelen + 1); char *cp = new_file->info.name; if (dirarray[diridx]->dir != NULL) { /* This value could be NULL in case the DW_AT_comp_dir was not present. We cannot do much in this case. The easiest thing is to convert the path in an absolute path. */ cp = stpcpy (cp, dirarray[diridx]->dir); } *cp++ = '/'; strcpy (cp, fname); assert (strlen (new_file->info.name) < dirarray[diridx]->len + 1 + fnamelen + 1); } /* Next comes the modification time. */ get_uleb128 (new_file->info.mtime, linep); /* Finally the length of the file. */ get_uleb128 (new_file->info.length, linep); new_file->next = filelist; filelist = new_file; ++nfilelist; } /* Skip the final NUL byte. */ ++linep; /* Consistency check. */ if (unlikely (linep != header_start + header_length)) { __libdw_seterrno (DWARF_E_INVALID_DWARF); goto out; } /* We are about to process the statement program. Initialize the state machine registers (see 6.2.2 in the v2.1 specification). */ Dwarf_Word addr = 0; unsigned int op_index = 0; unsigned int file = 1; int line = 1; unsigned int column = 0; uint_fast8_t is_stmt = default_is_stmt; bool basic_block = false; bool prologue_end = false; bool epilogue_begin = false; unsigned int isa = 0; unsigned int discriminator = 0; /* Apply the "operation advance" from a special opcode or DW_LNS_advance_pc (as per DWARF4 6.2.5.1). */ inline void advance_pc (unsigned int op_advance) { addr += minimum_instr_len * ((op_index + op_advance) / max_ops_per_instr); op_index = (op_index + op_advance) % max_ops_per_instr; }
int dwarf_formudata (Dwarf_Attribute *attr, Dwarf_Word *return_uval) { if (attr == NULL) return -1; const unsigned char *datap = attr->valp; const unsigned char *endp = attr->cu->endp; switch (attr->form) { case DW_FORM_data1: if (datap + 1 > endp) { invalid: __libdw_seterrno (DWARF_E_INVALID_DWARF); return -1; } *return_uval = *attr->valp; break; case DW_FORM_data2: if (datap + 2 > endp) goto invalid; *return_uval = read_2ubyte_unaligned (attr->cu->dbg, attr->valp); break; case DW_FORM_data4: case DW_FORM_data8: case DW_FORM_sec_offset: /* Before DWARF4 data4 and data8 are pure constants unless the attribute also allows offsets (*ptr classes), since DWARF4 they are always just constants (start_scope is special though, since it only could express a rangelist since DWARF4). */ if (attr->form == DW_FORM_sec_offset || (attr->cu->version < 4 && attr->code != DW_AT_start_scope)) { switch (attr->code) { case DW_AT_data_member_location: case DW_AT_frame_base: case DW_AT_location: case DW_AT_return_addr: case DW_AT_segment: case DW_AT_static_link: case DW_AT_string_length: case DW_AT_use_location: case DW_AT_vtable_elem_location: /* loclistptr */ if (__libdw_formptr (attr, IDX_debug_loc, DWARF_E_NO_LOCLIST, NULL, return_uval) == NULL) return -1; break; case DW_AT_macro_info: /* macptr into .debug_macinfo */ if (__libdw_formptr (attr, IDX_debug_macinfo, DWARF_E_NO_ENTRY, NULL, return_uval) == NULL) return -1; break; case DW_AT_GNU_macros: /* macptr into .debug_macro */ if (__libdw_formptr (attr, IDX_debug_macro, DWARF_E_NO_ENTRY, NULL, return_uval) == NULL) return -1; break; case DW_AT_ranges: case DW_AT_start_scope: /* rangelistptr */ if (__libdw_formptr (attr, IDX_debug_ranges, DWARF_E_NO_DEBUG_RANGES, NULL, return_uval) == NULL) return -1; break; case DW_AT_stmt_list: /* lineptr */ if (__libdw_formptr (attr, IDX_debug_line, DWARF_E_NO_DEBUG_LINE, NULL, return_uval) == NULL) return -1; break; default: /* sec_offset can only be used by one of the above attrs. */ if (attr->form == DW_FORM_sec_offset) { __libdw_seterrno (DWARF_E_INVALID_DWARF); return -1; } /* Not one of the special attributes, just a constant. */ if (__libdw_read_address (attr->cu->dbg, cu_sec_idx (attr->cu), attr->valp, attr->form == DW_FORM_data4 ? 4 : 8, return_uval)) return -1; break; } } else { /* We are dealing with a constant data4 or data8. */ if (__libdw_read_address (attr->cu->dbg, cu_sec_idx (attr->cu), attr->valp, attr->form == DW_FORM_data4 ? 4 : 8, return_uval)) return -1; } break; case DW_FORM_sdata: if (datap + 1 > endp) goto invalid; get_sleb128 (*return_uval, datap, endp); break; case DW_FORM_udata: if (datap + 1 > endp) goto invalid; get_uleb128 (*return_uval, datap, endp); break; default: __libdw_seterrno (DWARF_E_NO_CONSTANT); return -1; } return 0; }
int internal_function __libdw_formref (Dwarf_Attribute *attr, Dwarf_Off *return_offset) { const unsigned char *datap = attr->valp; const unsigned char *endp = attr->cu->endp; if (attr->valp == NULL) { __libdw_seterrno (DWARF_E_INVALID_REFERENCE); return -1; } switch (attr->form) { case DW_FORM_ref1: if (datap + 1 > endp) { invalid: __libdw_seterrno (DWARF_E_INVALID_DWARF); return -1; } *return_offset = *attr->valp; break; case DW_FORM_ref2: if (datap + 2 > endp) goto invalid; *return_offset = read_2ubyte_unaligned (attr->cu->dbg, attr->valp); break; case DW_FORM_ref4: if (datap + 4 > endp) goto invalid; *return_offset = read_4ubyte_unaligned (attr->cu->dbg, attr->valp); break; case DW_FORM_ref8: if (datap + 8 > endp) goto invalid; *return_offset = read_8ubyte_unaligned (attr->cu->dbg, attr->valp); break; case DW_FORM_ref_udata: if (datap + 1 > endp) goto invalid; get_uleb128 (*return_offset, datap, endp); break; case DW_FORM_ref_addr: case DW_FORM_ref_sig8: case DW_FORM_GNU_ref_alt: /* These aren't handled by dwarf_formref, only by dwarf_formref_die. */ __libdw_seterrno (DWARF_E_INVALID_REFERENCE); return -1; default: __libdw_seterrno (DWARF_E_NO_REFERENCE); return -1; } return 0; }
/* Unwind to previous to frame. Returns 0 if successful, negative * number in case of an error. */ int unwind(struct unwind_frame_info *frame, int is_ehframe) { #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 startLoc = 0, endLoc = 0, cfa; unsigned i; signed ptrType = -1; uleb128_t retAddrReg = 0; // struct unwind_table *table; void *unwind_table; struct local_unwind_table *table; struct unwind_state state; u64 reg_ptr = 0; if (UNW_PC(frame) == 0) return -EINVAL; if ((table = find_table(UNW_PC(frame)))) { // unsigned long tableSize = unwind_table_size; unsigned long tableSize = table->size; unwind_table = table->address; for (fde = unwind_table; tableSize > sizeof(*fde) && tableSize - sizeof(*fde) >= *fde; tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) { if (!*fde || (*fde & (sizeof(*fde) - 1))) break; if (is_ehframe && !fde[1]) continue; /* this is a CIE */ else if (fde[1] == 0xffffffff) continue; /* this is a CIE */ if ((fde[1] & (sizeof(*fde) - 1)) || fde[1] > (unsigned long)(fde + 1) - (unsigned long)unwind_table) continue; /* this is not a valid FDE */ if (is_ehframe) cie = fde + 1 - fde[1] / sizeof(*fde); else cie = unwind_table + fde[1]; if (*cie <= sizeof(*cie) + 4 || *cie >= fde[1] - sizeof(*fde) || (*cie & (sizeof(*cie) - 1)) || (cie[1] != 0xffffffff && cie[1]) || (ptrType = fde_pointer_type(cie)) < 0) { cie = NULL; /* this is not a (valid) CIE */ continue; } ptr = (const u8 *)(fde + 2); startLoc = read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType); endLoc = startLoc + read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType & DW_EH_PE_indirect ? ptrType : ptrType & (DW_EH_PE_FORM|DW_EH_PE_signed)); if (UNW_PC(frame) >= startLoc && UNW_PC(frame) < endLoc) break; cie = NULL; } } 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; if ((state.version = *ptr) != 1) cie = NULL; /* unsupported version */ else if (*++ptr) { /* check if augmentation size is first (and thus present) */ if (*ptr == 'z') { /* check for ignorable (or already handled) * nul-terminated augmentation string */ while (++ptr < end && *ptr) if (strchr("LPR", *ptr) == NULL) 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); /* skip augmentation */ if (((const char *)(cie + 2))[1] == 'z') ptr += get_uleb128(&ptr, end); 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) return -ENXIO; state.org = startLoc; memcpy(&state.cfa, &badCFA, sizeof(state.cfa)); /* process instructions */ if (!processCFI(ptr, end, UNW_PC(frame), 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; } /* update frame */ cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs; startLoc = min((unsigned long)UNW_SP(frame), cfa); endLoc = max((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); } #ifndef CONFIG_64BIT # define CASES CASE(8); CASE(16); CASE(32) #else # define CASES CASE(8); CASE(16); CASE(32); CASE(64) #endif 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) { #define CASE(n) \ case sizeof(u##n): \ state.regs[i].value = FRAME_REG(state.regs[i].value, \ const u##n); \ break CASES; #undef CASE default: return -EIO; } break; } } for (i = 0; i < ARRAY_SIZE(state.regs); ++i) { 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) { #define CASE(n) case sizeof(u##n): \ FRAME_REG(i, u##n) = state.regs[i].value; \ break CASES; #undef CASE 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: { unsigned long 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) { #define CASE(n) case sizeof(u##n): \ readmem(addr, KVADDR, ®_ptr,sizeof(u##n), "register", RETURN_ON_ERROR|QUIET); \ FRAME_REG(i, u##n) = (u##n)reg_ptr;\ break CASES; #undef CASE default: return -EIO; } } break; } } return 0; #undef CASES #undef FRAME_REG }
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; 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: switch(*ptr.p8++) { case DW_CFA_nop: break; case DW_CFA_set_loc: if ((state->loc = read_pointer(&ptr.p8, end, ptrType)) == 0) result = 0; break; case DW_CFA_advance_loc1: result = ptr.p8 < end && advance_loc(*ptr.p8++, state); break; case DW_CFA_advance_loc2: result = ptr.p8 <= end + 2 && advance_loc(*ptr.p16++, state); break; case DW_CFA_advance_loc4: result = ptr.p8 <= end + 4 && advance_loc(*ptr.p32++, state); break; case DW_CFA_offset_extended: value = get_uleb128(&ptr.p8, end); 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: case DW_CFA_undefined: case DW_CFA_same_value: set_rule(get_uleb128(&ptr.p8, end), Nowhere, 0, state); break; case DW_CFA_register: value = get_uleb128(&ptr.p8, end); set_rule(value, Register, get_uleb128(&ptr.p8, end), state); break; case DW_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: 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); /*nobreak*/ case DW_CFA_def_cfa_offset: state->cfa.offs = get_uleb128(&ptr.p8, end); 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: 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: result = 0; break; } break; case 1: result = advance_loc(*ptr.p8++ & 0x3f, state); break; case 2: value = *ptr.p8++ & 0x3f; set_rule(value, Memory, get_uleb128(&ptr.p8, end), state); break; case 3: 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)); }
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(ptr.p16s++); else value = get_unaligned(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(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; } /* TBD if ((ptrType & DW_EH_PE_indirect) && __get_user(value, (unsigned long *)value)) return 0; */ *pLoc = ptr.p8; return value; }
size_t internal_function __libdw_form_val_compute_len (Dwarf *dbg, struct Dwarf_CU *cu, unsigned int form, const unsigned char *valp) { const unsigned char *saved; Dwarf_Word u128; size_t result; /* NB: This doesn't cover constant form lengths, which are already handled by the inlined __libdw_form_val_len. */ switch (form) { case DW_FORM_addr: result = cu->address_size; break; case DW_FORM_ref_addr: result = cu->version == 2 ? cu->address_size : cu->offset_size; break; case DW_FORM_strp: case DW_FORM_sec_offset: case DW_FORM_GNU_ref_alt: case DW_FORM_GNU_strp_alt: result = cu->offset_size; break; case DW_FORM_block1: result = *valp + 1; break; case DW_FORM_block2: result = read_2ubyte_unaligned (dbg, valp) + 2; break; case DW_FORM_block4: result = read_4ubyte_unaligned (dbg, valp) + 4; break; case DW_FORM_block: case DW_FORM_exprloc: saved = valp; get_uleb128 (u128, valp); result = u128 + (valp - saved); break; case DW_FORM_string: result = strlen ((char *) valp) + 1; break; case DW_FORM_sdata: case DW_FORM_udata: case DW_FORM_ref_udata: saved = valp; get_uleb128 (u128, valp); result = valp - saved; break; case DW_FORM_indirect: saved = valp; get_uleb128 (u128, valp); // XXX Is this really correct? result = __libdw_form_val_len (dbg, cu, u128, valp); if (result != (size_t) -1) result += valp - saved; break; default: __libdw_seterrno (DWARF_E_INVALID_DWARF); result = (size_t) -1l; break; } return result; }