From: Petr Machata Date: Mon, 23 Nov 2009 17:22:56 +0000 (+0100) Subject: dwarflint: Move read_die_chain over to check_debug_info.cc X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=513ccb268bb0096688dbfb254610cec4a0d9bb8e;p=thirdparty%2Felfutils.git dwarflint: Move read_die_chain over to check_debug_info.cc --- diff --git a/src/dwarflint/check_debug_info.cc b/src/dwarflint/check_debug_info.cc index cf8c1b7fb..25ae93a2f 100644 --- a/src/dwarflint/check_debug_info.cc +++ b/src/dwarflint/check_debug_info.cc @@ -34,6 +34,8 @@ #endif #include +#include +#include "../libdw/dwarf.h" #include "messages.h" #include "low.h" @@ -224,6 +226,726 @@ namespace return ret; } + section_id + reloc_target (uint8_t form, struct abbrev_attrib *at) + { + switch (form) + { + case DW_FORM_strp: + return sec_str; + + case DW_FORM_addr: + + switch (at->name) + { + case DW_AT_low_pc: + case DW_AT_high_pc: + case DW_AT_entry_pc: + return rel_exec; + + case DW_AT_const_value: + /* Appears in some kernel modules. It's not allowed by the + standard, but leave that for high-level checks. */ + return rel_address; + }; + + break; + + case DW_FORM_ref_addr: + return sec_info; + + case DW_FORM_data1: + case DW_FORM_data2: + /* While these are technically legal, they are never used in + DWARF sections. So better mark them as illegal, and have + dwarflint flag them. */ + return sec_invalid; + + case DW_FORM_data4: + case DW_FORM_data8: + + switch (at->name) + { + case DW_AT_stmt_list: + return sec_line; + + case DW_AT_location: + case DW_AT_string_length: + case DW_AT_return_addr: + case DW_AT_data_member_location: + case DW_AT_frame_base: + case DW_AT_segment: + case DW_AT_static_link: + case DW_AT_use_location: + case DW_AT_vtable_elem_location: + return sec_loc; + + case DW_AT_mac_info: + return sec_mac; + + case DW_AT_ranges: + return sec_ranges; + } + + break; + + case DW_FORM_string: + case DW_FORM_ref1: + case DW_FORM_ref2: + case DW_FORM_ref4: + /* Shouldn't be relocated. */ + return sec_invalid; + + case DW_FORM_sdata: + case DW_FORM_udata: + case DW_FORM_flag: + case DW_FORM_flag_present: + case DW_FORM_ref_udata: + assert (!"Can't be relocated!"); + + case DW_FORM_block1: + case DW_FORM_block2: + case DW_FORM_block4: + case DW_FORM_block: + assert (!"Should be handled specially!"); + }; + + std::cout << "XXX don't know how to handle form=" << pri::form (form) + << ", at=" << pri::attr (at->name) << std::endl; + + return rel_value; + } + + struct value_check_cb_ctx + { + struct read_ctx *const ctx; + struct where *const where; + struct cu *const cu; + struct ref_record *local_die_refs; + Elf_Data *strings; + struct coverage *strings_coverage; + struct cu_coverage *cu_coverage; + }; + + typedef void (*value_check_cb_t) (uint64_t addr, + struct value_check_cb_ctx const *ctx); + + /* Callback for local DIE references. */ + void + check_die_ref_local (uint64_t addr, struct value_check_cb_ctx const *ctx) + { + assert (ctx->ctx->end > ctx->ctx->begin); + if (addr > (uint64_t)(ctx->ctx->end - ctx->ctx->begin)) + { + wr_error (ctx->where, + ": invalid reference outside the CU: 0x%" PRIx64 ".\n", + addr); + return; + } + + if (ctx->local_die_refs != NULL) + /* Address holds a CU-local reference, so add CU offset + to turn it into section offset. */ + ref_record_add (ctx->local_die_refs, + addr + ctx->cu->head->offset, ctx->where); + } + + /* Callback for global DIE references. */ + void + check_die_ref_global (uint64_t addr, struct value_check_cb_ctx const *ctx) + { + ref_record_add (&ctx->cu->die_refs, addr, ctx->where); + } + + /* Callback for strp values. */ + void + check_strp (uint64_t addr, struct value_check_cb_ctx const *ctx) + { + if (ctx->strings == NULL) + wr_error (ctx->where, ": strp attribute, but no .debug_str data.\n"); + else if (addr >= ctx->strings->d_size) + wr_error (ctx->where, + ": Invalid offset outside .debug_str: 0x%" PRIx64 ".\n", + addr); + else + { + /* Record used part of .debug_str. */ + const char *buf = static_cast (ctx->strings->d_buf); + const char *startp = buf + addr; + const char *data_end = buf + ctx->strings->d_size; + const char *strp = startp; + while (strp < data_end && *strp != 0) + ++strp; + if (strp == data_end) + wr_error (ctx->where, + ": String at .debug_str: 0x%" PRIx64 + " is not zero-terminated.\n", addr); + + if (ctx->strings_coverage != NULL) + coverage_add (ctx->strings_coverage, addr, strp - startp + 1); + } + } + + /* Callback for rangeptr values. */ + void + check_rangeptr (uint64_t value, struct value_check_cb_ctx const *ctx) + { + if ((value % ctx->cu->head->address_size) != 0) + wr_message (mc_ranges | mc_impact_2, ctx->where, + ": rangeptr value %#" PRIx64 + " not aligned to CU address size.\n", value); + ctx->cu_coverage->need_ranges = true; + ref_record_add (&ctx->cu->range_refs, value, ctx->where); + } + + /* Callback for lineptr values. */ + void + check_lineptr (uint64_t value, struct value_check_cb_ctx const *ctx) + { + ref_record_add (&ctx->cu->line_refs, value, ctx->where); + } + + /* Callback for locptr values. */ + void + check_locptr (uint64_t value, struct value_check_cb_ctx const *ctx) + { + ref_record_add (&ctx->cu->loc_refs, value, ctx->where); + } + + /* + Returns: + -1 in case of error + +0 in case of no error, but the chain only consisted of a + terminating zero die. + +1 in case some dies were actually loaded + */ + int + read_die_chain (dwarf_version_h ver, + struct elf_file *file, + struct read_ctx *ctx, + struct cu *cu, + struct abbrev_table *abbrevs, + Elf_Data *strings, + struct ref_record *local_die_refs, + struct coverage *strings_coverage, + struct relocation_data *reloc, + struct cu_coverage *cu_coverage) + { + bool got_die = false; + uint64_t sibling_addr = 0; + uint64_t die_off, prev_die_off = 0; + struct abbrev *abbrev = NULL; + struct abbrev *prev_abbrev = NULL; + struct where where = WHERE (sec_info, NULL); + + struct value_check_cb_ctx cb_ctx = { + ctx, &where, cu, + local_die_refs, + strings, strings_coverage, + cu_coverage + }; + + while (!read_ctx_eof (ctx)) + { + where = cu->head->where; + die_off = read_ctx_get_offset (ctx); + /* Shift reported DIE offset by CU offset, to match the way + readelf reports DIEs. */ + where_reset_2 (&where, die_off + cu->head->offset); + + uint64_t abbr_code; + + if (!checked_read_uleb128 (ctx, &abbr_code, &where, "abbrev code")) + return -1; + +#define DEF_PREV_WHERE \ + struct where prev_where = where; \ + where_reset_2 (&prev_where, prev_die_off + cu->head->offset) + + /* Check sibling value advertised last time through the loop. */ + if (sibling_addr != 0) + { + if (abbr_code == 0) + wr_error (&where, + ": is the last sibling in chain, " + "but has a DW_AT_sibling attribute.\n"); + else if (sibling_addr != die_off) + { + DEF_PREV_WHERE; + wr_error (&prev_where, + ": This DIE claims that its sibling is 0x%" + PRIx64 ", but it's actually 0x%" PRIx64 ".\n", + sibling_addr, die_off); + } + sibling_addr = 0; + } + else if (abbr_code != 0 + && abbrev != NULL && abbrev->has_children) + { + /* Even if it has children, the DIE can't have a sibling + attribute if it's the last DIE in chain. That's the + reason we can't simply check this when loading + abbrevs. */ + DEF_PREV_WHERE; + wr_message (mc_die_rel | mc_acc_suboptimal | mc_impact_4, &prev_where, + ": This DIE had children, but no DW_AT_sibling attribute.\n"); + } +#undef DEF_PREV_WHERE + + prev_die_off = die_off; + + /* The section ended. */ + if (abbr_code == 0) + break; + if (read_ctx_eof (ctx)) + { + wr_error (&where, ": DIE chain not terminated with DIE with zero abbrev code.\n"); + break; + } + + prev_die_off = die_off; + got_die = true; + if (dump_die_offsets) + fprintf (stderr, "%s: abbrev %" PRId64 "\n", + where_fmt (&where, NULL), abbr_code); + + /* Find the abbrev matching the code. */ + prev_abbrev = abbrev; + abbrev = abbrev_table_find_abbrev (abbrevs, abbr_code); + if (abbrev == NULL) + { + wr_error (&where, + ": abbrev section at 0x%" PRIx64 + " doesn't contain code %" PRIu64 ".\n", + abbrevs->offset, abbr_code); + return -1; + } + abbrev->used = true; + + addr_record_add (&cu->die_addrs, cu->head->offset + die_off); + + uint64_t low_pc = (uint64_t)-1, high_pc = (uint64_t)-1; + bool low_pc_relocated = false, high_pc_relocated = false; + GElf_Sym low_pc_symbol_mem, *low_pc_symbol = &low_pc_symbol_mem; + GElf_Sym high_pc_symbol_mem, *high_pc_symbol = &high_pc_symbol_mem; + + /* Attribute values. */ + for (struct abbrev_attrib *it = abbrev->attribs; + it->name != 0; ++it) + { + where.ref = &it->where; + + uint8_t form = it->form; + bool indirect = form == DW_FORM_indirect; + if (indirect) + { + uint64_t value; + if (!checked_read_uleb128 (ctx, &value, &where, + "indirect attribute form")) + return -1; + + if (!dwver_form_valid (ver, form)) + { + wr_error (&where, + ": invalid indirect form 0x%" PRIx64 ".\n", value); + return -1; + } + form = value; + + if (it->name == DW_AT_sibling) + switch (check_sibling_form (ver, form)) + { + case -1: + wr_message (mc_die_rel | mc_impact_2, &where, + ": DW_AT_sibling attribute with (indirect) form DW_FORM_ref_addr.\n"); + break; + + case -2: + wr_error (where) + << "DW_AT_sibling attribute with non-reference (indirect) form \"" + << pri::form (value) << "\"." << std::endl; + }; + } + + value_check_cb_t value_check_cb = NULL; + + /* For checking lineptr, rangeptr, locptr. */ + bool check_someptr = false; + enum message_category extra_mc = mc_none; + + uint64_t ctx_offset = read_ctx_get_offset (ctx) + cu->head->offset; + bool type_is_rel = file->ehdr.e_type == ET_REL; + + /* Attribute value. */ + uint64_t value = 0; + + /* Whether the value should be relocated first. Note that + relocations are really required only in REL files, so + missing relocations are not warned on even with + rel_require, unless type_is_rel. */ + enum + { + rel_no, // don't allow a relocation + rel_require, // require a relocation + rel_nonzero, // require a relocation if value != 0 + } relocate = rel_no; + size_t width = 0; + + /* Point to variable that you want to copy relocated value + to. */ + uint64_t *valuep = NULL; + + /* Point to variable that you want set to `true' in case the + value was relocated. */ + bool *relocatedp = NULL; + + /* Point to variable that you want set to symbol that the + relocation was made against. */ + GElf_Sym **symbolp = NULL; + + /* Setup locptr checking. */ + if (is_location_attrib (it->name)) + { + switch (form) + { + case DW_FORM_data8: + if (cu->head->offset_size == 4) + wr_error (where) + << "location attribute with form \"" + << pri::form (form) << "\" in 32-bit CU." << std::endl; + /* fall-through */ + + case DW_FORM_data4: + case DW_FORM_sec_offset: + value_check_cb = check_locptr; + extra_mc = mc_loc; + check_someptr = true; + break; + + case DW_FORM_block1: + case DW_FORM_block2: + case DW_FORM_block4: + case DW_FORM_block: + break; + + default: + /* Only print error if it's indirect. Otherwise we + gave diagnostic during abbrev loading. */ + if (indirect) + wr_error (where) + << "location attribute with invalid (indirect) form \"" + << pri::form (form) << "\"." << std::endl; + }; + } + /* Setup rangeptr or lineptr checking. */ + else if (it->name == DW_AT_ranges + || it->name == DW_AT_stmt_list) + switch (form) + { + case DW_FORM_data8: + if (cu->head->offset_size == 4) + wr_error (where) + << pri::attr (it->name) + << " with form DW_FORM_data8 in 32-bit CU." << std::endl; + /* fall-through */ + + case DW_FORM_data4: + case DW_FORM_sec_offset: + check_someptr = true; + if (it->name == DW_AT_ranges) + { + value_check_cb = check_rangeptr; + extra_mc = mc_ranges; + } + else + { + assert (it->name == DW_AT_stmt_list); + value_check_cb = check_lineptr; + extra_mc = mc_line; + } + break; + + default: + /* Only print error if it's indirect. Otherwise we + gave diagnostic during abbrev loading. */ + if (indirect) + wr_error (where) + << pri::attr (it->name) + << " with invalid (indirect) form \"" << pri::form (form) + << "\"." << std::endl; + } + /* Setup low_pc and high_pc checking. */ + else if (it->name == DW_AT_low_pc) + { + relocatedp = &low_pc_relocated; + symbolp = &low_pc_symbol; + valuep = &low_pc; + } + else if (it->name == DW_AT_high_pc) + { + relocatedp = &high_pc_relocated; + symbolp = &high_pc_symbol; + valuep = &high_pc; + } + + /* Load attribute value and setup per-form checking. */ + switch (form) + { + case DW_FORM_strp: + value_check_cb = check_strp; + case DW_FORM_sec_offset: + if (!read_ctx_read_offset (ctx, cu->head->offset_size == 8, &value)) + { + cant_read: + wr_error (where) + << "can't read value of attribute " + << pri::attr (it->name) << '.' << std::endl; + return -1; + } + + relocate = rel_require; + width = cu->head->offset_size; + break; + + case DW_FORM_string: + if (!read_ctx_read_str (ctx)) + goto cant_read; + break; + + case DW_FORM_ref_addr: + value_check_cb = check_die_ref_global; + width = cu->head->offset_size; + + if (cu->head->version == 2) + case DW_FORM_addr: + width = cu->head->address_size; + + if (!read_ctx_read_offset (ctx, width == 8, &value)) + goto cant_read; + + /* In non-rel files, neither addr, nor ref_addr /need/ + a relocation. */ + relocate = rel_nonzero; + break; + + case DW_FORM_ref_udata: + value_check_cb = check_die_ref_local; + case DW_FORM_udata: + if (!checked_read_uleb128 (ctx, &value, &where, + "attribute value")) + return -1; + break; + + case DW_FORM_flag_present: + value = 1; + break; + + case DW_FORM_ref1: + value_check_cb = check_die_ref_local; + case DW_FORM_flag: + case DW_FORM_data1: + if (!read_ctx_read_var (ctx, 1, &value)) + goto cant_read; + break; + + case DW_FORM_ref2: + value_check_cb = check_die_ref_local; + case DW_FORM_data2: + if (!read_ctx_read_var (ctx, 2, &value)) + goto cant_read; + break; + + case DW_FORM_data4: + if (check_someptr) + { + relocate = rel_require; + width = 4; + } + if (false) + case DW_FORM_ref4: + value_check_cb = check_die_ref_local; + if (!read_ctx_read_var (ctx, 4, &value)) + goto cant_read; + break; + + case DW_FORM_data8: + if (check_someptr) + { + relocate = rel_require; + width = 8; + } + if (false) + case DW_FORM_ref8: + value_check_cb = check_die_ref_local; + if (!read_ctx_read_8ubyte (ctx, &value)) + goto cant_read; + break; + + case DW_FORM_sdata: + { + int64_t value64; + if (!checked_read_sleb128 (ctx, &value64, &where, + "attribute value")) + return -1; + value = (uint64_t) value64; + break; + } + + case DW_FORM_block: + { + uint64_t length; + + if (false) + case DW_FORM_block1: + width = 1; + + if (false) + case DW_FORM_block2: + width = 2; + + if (false) + case DW_FORM_block4: + width = 4; + + if (width == 0) + { + if (!checked_read_uleb128 (ctx, &length, &where, + "attribute value")) + return -1; + } + else if (!read_ctx_read_var (ctx, width, &length)) + goto cant_read; + + if (is_location_attrib (it->name)) + { + uint64_t expr_start + = cu->head->offset + read_ctx_get_offset (ctx); + if (!check_location_expression (file, ctx, cu, expr_start, + reloc, length, &where)) + return -1; + } + else + /* xxx really skip_mismatched? We just don't know + how to process these... */ + relocation_skip (reloc, + read_ctx_get_offset (ctx) + length, + &where, skip_mismatched); + + if (!read_ctx_skip (ctx, length)) + goto cant_read; + break; + } + + case DW_FORM_indirect: + wr_error (&where, ": indirect form is again indirect.\n"); + return -1; + + default: + wr_error (&where, + ": internal error: unhandled form 0x%x.\n", form); + } + + /* Relocate the value if appropriate. */ + struct relocation *rel; + if ((rel = relocation_next (reloc, ctx_offset, + &where, skip_mismatched))) + { + if (relocate == rel_no) + wr_message (where, cat (mc_impact_4, mc_die_other, + mc_reloc, extra_mc)) + << "unexpected relocation of " << pri::form (form) << '.' + << std::endl; + + relocate_one (file, reloc, rel, width, &value, &where, + reloc_target (form, it), symbolp); + + if (relocatedp != NULL) + *relocatedp = true; + } + else + { + if (symbolp != NULL) + WIPE (*symbolp); + if (type_is_rel + && (relocate == rel_require + || (relocate == rel_nonzero + && value != 0))) + { + std::stringstream ss; + ss << pri::form (form); + wr_message (where, cat (mc_impact_2, mc_die_other, + mc_reloc, extra_mc)) + << pri::lacks_relocation (ss.str ()) << std::endl; + } + } + + /* Dispatch value checking. */ + if (it->name == DW_AT_sibling) + { + /* Full-blown DIE reference checking is too heavy-weight + and not practical (error messages wise) for checking + siblings. */ + assert (value_check_cb == check_die_ref_local + || value_check_cb == check_die_ref_global); + valuep = &sibling_addr; + } + else if (value_check_cb != NULL) + value_check_cb (value, &cb_ctx); + + /* Store the relocated value. Note valuep may point to + low_pc or high_pc. */ + if (valuep != NULL) + *valuep = value; + + /* Check PC coverage. */ + if (abbrev->tag == DW_TAG_compile_unit + || abbrev->tag == DW_TAG_partial_unit) + { + if (it->name == DW_AT_low_pc) + cu->low_pc = value; + + if (low_pc != (uint64_t)-1 && high_pc != (uint64_t)-1) + coverage_add (&cu_coverage->cov, low_pc, high_pc - low_pc); + } + } + where.ref = NULL; + + if (high_pc != (uint64_t)-1 && low_pc != (uint64_t)-1) + { + if (high_pc_relocated != low_pc_relocated) + wr_message (mc_die_other | mc_impact_2 | mc_reloc, &where, + ": only one of DW_AT_low_pc and DW_AT_high_pc is relocated.\n"); + else + check_range_relocations (mc_die_other, &where, + file, + low_pc_symbol, high_pc_symbol, + "DW_AT_low_pc and DW_AT_high_pc"); + } + + where.ref = &abbrev->where; + + if (abbrev->has_children) + { + int st = read_die_chain (ver, file, ctx, cu, abbrevs, strings, + local_die_refs, + strings_coverage, reloc, + cu_coverage); + if (st == -1) + return -1; + else if (st == 0) + wr_message (mc_impact_3 | mc_acc_suboptimal | mc_die_rel, + &where, + ": abbrev has_children, but the chain was empty.\n"); + } + } + + if (sibling_addr != 0) + wr_error (&where, + ": this DIE should have had its sibling at 0x%" + PRIx64 ", but the DIE chain ended.\n", sibling_addr); + + return got_die ? 1 : 0; + } bool check_cu_structural (struct elf_file *file, struct read_ctx *ctx, diff --git a/src/dwarflint/low.c b/src/dwarflint/low.c index 30caedfa9..d3b682bbc 100644 --- a/src/dwarflint/low.c +++ b/src/dwarflint/low.c @@ -60,14 +60,6 @@ static const bool do_range_coverage = false; static struct cu *cu_find_cu (struct cu *cu_chain, uint64_t offset); -static bool check_location_expression (struct elf_file *file, - struct read_ctx *ctx, - struct cu *cu, - uint64_t init_off, - struct relocation_data *reloc, - size_t length, - struct where *wh); - bool address_aligned (uint64_t addr, uint64_t align) { @@ -97,7 +89,7 @@ checked_read_uleb128 (struct read_ctx *ctx, uint64_t *ret, return st >= 0; } -static bool +bool checked_read_sleb128 (struct read_ctx *ctx, int64_t *ret, struct where *where, const char *what) { @@ -559,96 +551,6 @@ check_zero_padding (struct read_ctx *ctx, return true; } -static enum section_id -reloc_target (uint8_t form, struct abbrev_attrib *at) -{ - switch (form) - { - case DW_FORM_strp: - return sec_str; - - case DW_FORM_addr: - - switch (at->name) - { - case DW_AT_low_pc: - case DW_AT_high_pc: - case DW_AT_entry_pc: - return rel_exec; - - case DW_AT_const_value: - /* Appears in some kernel modules. It's not allowed by the - standard, but leave that for high-level checks. */ - return rel_address; - }; - - break; - - case DW_FORM_ref_addr: - return sec_info; - - case DW_FORM_data1: - case DW_FORM_data2: - /* While these are technically legal, they are never used in - DWARF sections. So better mark them as illegal, and have - dwarflint flag them. */ - return sec_invalid; - - case DW_FORM_data4: - case DW_FORM_data8: - - switch (at->name) - { - case DW_AT_stmt_list: - return sec_line; - - case DW_AT_location: - case DW_AT_string_length: - case DW_AT_return_addr: - case DW_AT_data_member_location: - case DW_AT_frame_base: - case DW_AT_segment: - case DW_AT_static_link: - case DW_AT_use_location: - case DW_AT_vtable_elem_location: - return sec_loc; - - case DW_AT_mac_info: - return sec_mac; - - case DW_AT_ranges: - return sec_ranges; - } - - break; - - case DW_FORM_string: - case DW_FORM_ref1: - case DW_FORM_ref2: - case DW_FORM_ref4: - /* Shouldn't be relocated. */ - return sec_invalid; - - case DW_FORM_sdata: - case DW_FORM_udata: - case DW_FORM_flag: - case DW_FORM_flag_present: - case DW_FORM_ref_udata: - assert (!"Can't be relocated!"); - - case DW_FORM_block1: - case DW_FORM_block2: - case DW_FORM_block4: - case DW_FORM_block: - assert (!"Should be handled specially!"); - }; - - printf ("XXX don't know how to handle form=%s, at=%s\n", - dwarf_form_string (form), dwarf_attr_string (at->name)); - - return rel_value; -} - static enum section_id reloc_target_loc (uint8_t opcode) { @@ -695,7 +597,7 @@ supported_version (unsigned version, return retval; } -static void +void check_range_relocations (enum message_category cat, struct where *where, struct elf_file *file, @@ -713,630 +615,6 @@ check_range_relocations (enum message_category cat, file->sec[end_symbol->st_shndx].name); } -struct value_check_cb_ctx -{ - struct read_ctx *const ctx; - struct where *const where; - struct cu *const cu; - struct ref_record *local_die_refs; - Elf_Data *strings; - struct coverage *strings_coverage; - struct cu_coverage *cu_coverage; -}; - -typedef void (*value_check_cb_t) (uint64_t addr, - struct value_check_cb_ctx const *ctx); - -/* Callback for local DIE references. */ -static void -check_die_ref_local (uint64_t addr, struct value_check_cb_ctx const *ctx) -{ - assert (ctx->ctx->end > ctx->ctx->begin); - if (addr > (uint64_t)(ctx->ctx->end - ctx->ctx->begin)) - { - wr_error (ctx->where, - ": invalid reference outside the CU: 0x%" PRIx64 ".\n", - addr); - return; - } - - if (ctx->local_die_refs != NULL) - /* Address holds a CU-local reference, so add CU offset - to turn it into section offset. */ - ref_record_add (ctx->local_die_refs, - addr + ctx->cu->head->offset, ctx->where); -} - -/* Callback for global DIE references. */ -static void -check_die_ref_global (uint64_t addr, struct value_check_cb_ctx const *ctx) -{ - ref_record_add (&ctx->cu->die_refs, addr, ctx->where); -} - -/* Callback for strp values. */ -static void -check_strp (uint64_t addr, struct value_check_cb_ctx const *ctx) -{ - if (ctx->strings == NULL) - wr_error (ctx->where, ": strp attribute, but no .debug_str data.\n"); - else if (addr >= ctx->strings->d_size) - wr_error (ctx->where, - ": Invalid offset outside .debug_str: 0x%" PRIx64 ".\n", - addr); - else - { - /* Record used part of .debug_str. */ - const char *startp = (const char *)ctx->strings->d_buf + addr; - const char *data_end = ctx->strings->d_buf + ctx->strings->d_size; - const char *strp = startp; - while (strp < data_end && *strp != 0) - ++strp; - if (strp == data_end) - wr_error (ctx->where, - ": String at .debug_str: 0x%" PRIx64 - " is not zero-terminated.\n", addr); - - if (ctx->strings_coverage != NULL) - coverage_add (ctx->strings_coverage, addr, strp - startp + 1); - } -} - -/* Callback for rangeptr values. */ -static void -check_rangeptr (uint64_t value, struct value_check_cb_ctx const *ctx) -{ - if ((value % ctx->cu->head->address_size) != 0) - wr_message (mc_ranges | mc_impact_2, ctx->where, - ": rangeptr value %#" PRIx64 - " not aligned to CU address size.\n", value); - ctx->cu_coverage->need_ranges = true; - ref_record_add (&ctx->cu->range_refs, value, ctx->where); -} - -/* Callback for lineptr values. */ -static void -check_lineptr (uint64_t value, struct value_check_cb_ctx const *ctx) -{ - ref_record_add (&ctx->cu->line_refs, value, ctx->where); -} - -/* Callback for locptr values. */ -static void -check_locptr (uint64_t value, struct value_check_cb_ctx const *ctx) -{ - ref_record_add (&ctx->cu->loc_refs, value, ctx->where); -} - -/* - Returns: - -1 in case of error - +0 in case of no error, but the chain only consisted of a - terminating zero die. - +1 in case some dies were actually loaded - */ -int -read_die_chain (dwarf_version_h ver, - struct elf_file *file, - struct read_ctx *ctx, - struct cu *cu, - struct abbrev_table *abbrevs, - Elf_Data *strings, - struct ref_record *local_die_refs, - struct coverage *strings_coverage, - struct relocation_data *reloc, - struct cu_coverage *cu_coverage) -{ - bool got_die = false; - uint64_t sibling_addr = 0; - uint64_t die_off, prev_die_off = 0; - struct abbrev *abbrev = NULL; - struct abbrev *prev_abbrev = NULL; - struct where where = WHERE (sec_info, NULL); - - struct value_check_cb_ctx cb_ctx = { - ctx, &where, cu, - local_die_refs, - strings, strings_coverage, - cu_coverage - }; - - while (!read_ctx_eof (ctx)) - { - where = cu->head->where; - die_off = read_ctx_get_offset (ctx); - /* Shift reported DIE offset by CU offset, to match the way - readelf reports DIEs. */ - where_reset_2 (&where, die_off + cu->head->offset); - - uint64_t abbr_code; - - if (!checked_read_uleb128 (ctx, &abbr_code, &where, "abbrev code")) - return -1; - -#define DEF_PREV_WHERE \ - struct where prev_where = where; \ - where_reset_2 (&prev_where, prev_die_off + cu->head->offset) - - /* Check sibling value advertised last time through the loop. */ - if (sibling_addr != 0) - { - if (abbr_code == 0) - wr_error (&where, - ": is the last sibling in chain, " - "but has a DW_AT_sibling attribute.\n"); - else if (sibling_addr != die_off) - { - DEF_PREV_WHERE; - wr_error (&prev_where, - ": This DIE claims that its sibling is 0x%" - PRIx64 ", but it's actually 0x%" PRIx64 ".\n", - sibling_addr, die_off); - } - sibling_addr = 0; - } - else if (abbr_code != 0 - && abbrev != NULL && abbrev->has_children) - { - /* Even if it has children, the DIE can't have a sibling - attribute if it's the last DIE in chain. That's the - reason we can't simply check this when loading - abbrevs. */ - DEF_PREV_WHERE; - wr_message (mc_die_rel | mc_acc_suboptimal | mc_impact_4, &prev_where, - ": This DIE had children, but no DW_AT_sibling attribute.\n"); - } -#undef DEF_PREV_WHERE - - prev_die_off = die_off; - - /* The section ended. */ - if (abbr_code == 0) - break; - if (read_ctx_eof (ctx)) - { - wr_error (&where, ": DIE chain not terminated with DIE with zero abbrev code.\n"); - break; - } - - prev_die_off = die_off; - got_die = true; - if (dump_die_offsets) - fprintf (stderr, "%s: abbrev %" PRId64 "\n", - where_fmt (&where, NULL), abbr_code); - - /* Find the abbrev matching the code. */ - prev_abbrev = abbrev; - abbrev = abbrev_table_find_abbrev (abbrevs, abbr_code); - if (abbrev == NULL) - { - wr_error (&where, - ": abbrev section at 0x%" PRIx64 - " doesn't contain code %" PRIu64 ".\n", - abbrevs->offset, abbr_code); - return -1; - } - abbrev->used = true; - - addr_record_add (&cu->die_addrs, cu->head->offset + die_off); - - uint64_t low_pc = (uint64_t)-1, high_pc = (uint64_t)-1; - bool low_pc_relocated = false, high_pc_relocated = false; - GElf_Sym low_pc_symbol_mem, *low_pc_symbol = &low_pc_symbol_mem; - GElf_Sym high_pc_symbol_mem, *high_pc_symbol = &high_pc_symbol_mem; - - /* Attribute values. */ - for (struct abbrev_attrib *it = abbrev->attribs; - it->name != 0; ++it) - { - where.ref = &it->where; - - uint8_t form = it->form; - bool indirect = form == DW_FORM_indirect; - if (indirect) - { - uint64_t value; - if (!checked_read_uleb128 (ctx, &value, &where, - "indirect attribute form")) - return -1; - - if (!dwver_form_valid (ver, form)) - { - wr_error (&where, - ": invalid indirect form 0x%" PRIx64 ".\n", value); - return -1; - } - form = value; - - if (it->name == DW_AT_sibling) - switch (check_sibling_form (ver, form)) - { - case -1: - wr_message (mc_die_rel | mc_impact_2, &where, - ": DW_AT_sibling attribute with (indirect) form DW_FORM_ref_addr.\n"); - break; - - case -2: - wr_error (&where, - ": DW_AT_sibling attribute with non-reference (indirect) form \"%s\".\n", - dwarf_form_string (value)); - }; - } - - value_check_cb_t value_check_cb = NULL; - - /* For checking lineptr, rangeptr, locptr. */ - bool check_someptr = false; - enum message_category extra_mc = mc_none; - - uint64_t ctx_offset = read_ctx_get_offset (ctx) + cu->head->offset; - bool type_is_rel = file->ehdr.e_type == ET_REL; - - /* Attribute value. */ - uint64_t value = 0; - - /* Whether the value should be relocated first. Note that - relocations are really required only in REL files, so - missing relocations are not warned on even with - rel_require, unless type_is_rel. */ - enum - { - rel_no, // don't allow a relocation - rel_require, // require a relocation - rel_nonzero, // require a relocation if value != 0 - } relocate = rel_no; - size_t width = 0; - - /* Point to variable that you want to copy relocated value - to. */ - uint64_t *valuep = NULL; - - /* Point to variable that you want set to `true' in case the - value was relocated. */ - bool *relocatedp = NULL; - - /* Point to variable that you want set to symbol that the - relocation was made against. */ - GElf_Sym **symbolp = NULL; - - /* Setup locptr checking. */ - if (is_location_attrib (it->name)) - { - switch (form) - { - case DW_FORM_data8: - if (cu->head->offset_size == 4) - wr_error (&where, - ": location attribute with form \"%s\" in 32-bit CU.\n", - dwarf_form_string (form)); - /* fall-through */ - - case DW_FORM_data4: - case DW_FORM_sec_offset: - value_check_cb = check_locptr; - extra_mc = mc_loc; - check_someptr = true; - break; - - case DW_FORM_block1: - case DW_FORM_block2: - case DW_FORM_block4: - case DW_FORM_block: - break; - - default: - /* Only print error if it's indirect. Otherwise we - gave diagnostic during abbrev loading. */ - if (indirect) - wr_error (&where, - ": location attribute with invalid (indirect) form \"%s\".\n", - dwarf_form_string (form)); - }; - } - /* Setup rangeptr or lineptr checking. */ - else if (it->name == DW_AT_ranges - || it->name == DW_AT_stmt_list) - switch (form) - { - case DW_FORM_data8: - if (cu->head->offset_size == 4) - wr_error (&where, - ": %s with form DW_FORM_data8 in 32-bit CU.\n", - dwarf_attr_string (it->name)); - /* fall-through */ - - case DW_FORM_data4: - case DW_FORM_sec_offset: - check_someptr = true; - if (it->name == DW_AT_ranges) - { - value_check_cb = check_rangeptr; - extra_mc = mc_ranges; - } - else - { - assert (it->name == DW_AT_stmt_list); - value_check_cb = check_lineptr; - extra_mc = mc_line; - } - break; - - default: - /* Only print error if it's indirect. Otherwise we - gave diagnostic during abbrev loading. */ - if (indirect) - wr_error (&where, - ": %s with invalid (indirect) form \"%s\".\n", - dwarf_attr_string (it->name), - dwarf_form_string (form)); - } - /* Setup low_pc and high_pc checking. */ - else if (it->name == DW_AT_low_pc) - { - relocatedp = &low_pc_relocated; - symbolp = &low_pc_symbol; - valuep = &low_pc; - } - else if (it->name == DW_AT_high_pc) - { - relocatedp = &high_pc_relocated; - symbolp = &high_pc_symbol; - valuep = &high_pc; - } - - /* Load attribute value and setup per-form checking. */ - switch (form) - { - case DW_FORM_strp: - value_check_cb = check_strp; - case DW_FORM_sec_offset: - if (!read_ctx_read_offset (ctx, cu->head->offset_size == 8, &value)) - { - cant_read: - wr_error (&where, ": can't read value of attribute %s.\n", - dwarf_attr_string (it->name)); - return -1; - } - - relocate = rel_require; - width = cu->head->offset_size; - break; - - case DW_FORM_string: - if (!read_ctx_read_str (ctx)) - goto cant_read; - break; - - case DW_FORM_ref_addr: - value_check_cb = check_die_ref_global; - width = cu->head->offset_size; - - if (cu->head->version == 2) - case DW_FORM_addr: - width = cu->head->address_size; - - if (!read_ctx_read_offset (ctx, width == 8, &value)) - goto cant_read; - - /* In non-rel files, neither addr, nor ref_addr /need/ - a relocation. */ - relocate = rel_nonzero; - break; - - case DW_FORM_ref_udata: - value_check_cb = check_die_ref_local; - case DW_FORM_udata: - if (!checked_read_uleb128 (ctx, &value, &where, - "attribute value")) - return -1; - break; - - case DW_FORM_flag_present: - value = 1; - break; - - case DW_FORM_ref1: - value_check_cb = check_die_ref_local; - case DW_FORM_flag: - case DW_FORM_data1: - if (!read_ctx_read_var (ctx, 1, &value)) - goto cant_read; - break; - - case DW_FORM_ref2: - value_check_cb = check_die_ref_local; - case DW_FORM_data2: - if (!read_ctx_read_var (ctx, 2, &value)) - goto cant_read; - break; - - case DW_FORM_data4: - if (check_someptr) - { - relocate = rel_require; - width = 4; - } - if (false) - case DW_FORM_ref4: - value_check_cb = check_die_ref_local; - if (!read_ctx_read_var (ctx, 4, &value)) - goto cant_read; - break; - - case DW_FORM_data8: - if (check_someptr) - { - relocate = rel_require; - width = 8; - } - if (false) - case DW_FORM_ref8: - value_check_cb = check_die_ref_local; - if (!read_ctx_read_8ubyte (ctx, &value)) - goto cant_read; - break; - - case DW_FORM_sdata: - { - int64_t value64; - if (!checked_read_sleb128 (ctx, &value64, &where, - "attribute value")) - return -1; - value = (uint64_t) value64; - break; - } - - case DW_FORM_block: - { - uint64_t length; - - if (false) - case DW_FORM_block1: - width = 1; - - if (false) - case DW_FORM_block2: - width = 2; - - if (false) - case DW_FORM_block4: - width = 4; - - if (width == 0) - { - if (!checked_read_uleb128 (ctx, &length, &where, - "attribute value")) - return -1; - } - else if (!read_ctx_read_var (ctx, width, &length)) - goto cant_read; - - if (is_location_attrib (it->name)) - { - uint64_t expr_start - = cu->head->offset + read_ctx_get_offset (ctx); - if (!check_location_expression (file, ctx, cu, expr_start, - reloc, length, &where)) - return -1; - } - else - /* xxx really skip_mismatched? We just don't know - how to process these... */ - relocation_skip (reloc, - read_ctx_get_offset (ctx) + length, - &where, skip_mismatched); - - if (!read_ctx_skip (ctx, length)) - goto cant_read; - break; - } - - case DW_FORM_indirect: - wr_error (&where, ": indirect form is again indirect.\n"); - return -1; - - default: - wr_error (&where, - ": internal error: unhandled form 0x%x.\n", form); - } - - /* Relocate the value if appropriate. */ - struct relocation *rel; - if ((rel = relocation_next (reloc, ctx_offset, - &where, skip_mismatched))) - { - if (relocate == rel_no) - wr_message (mc_impact_4 | mc_die_other | mc_reloc | extra_mc, - &where, ": unexpected relocation of %s.\n", - dwarf_form_string (form)); - - relocate_one (file, reloc, rel, width, &value, &where, - reloc_target (form, it), symbolp); - - if (relocatedp != NULL) - *relocatedp = true; - } - else - { - if (symbolp != NULL) - WIPE (*symbolp); - if (type_is_rel - && (relocate == rel_require - || (relocate == rel_nonzero - && value != 0))) - wr_message (mc_impact_2 | mc_die_other | mc_reloc | extra_mc, - &where, PRI_LACK_RELOCATION, - dwarf_form_string (form)); - } - - /* Dispatch value checking. */ - if (it->name == DW_AT_sibling) - { - /* Full-blown DIE reference checking is too heavy-weight - and not practical (error messages wise) for checking - siblings. */ - assert (value_check_cb == check_die_ref_local - || value_check_cb == check_die_ref_global); - valuep = &sibling_addr; - } - else if (value_check_cb != NULL) - value_check_cb (value, &cb_ctx); - - /* Store the relocated value. Note valuep may point to - low_pc or high_pc. */ - if (valuep != NULL) - *valuep = value; - - /* Check PC coverage. */ - if (abbrev->tag == DW_TAG_compile_unit - || abbrev->tag == DW_TAG_partial_unit) - { - if (it->name == DW_AT_low_pc) - cu->low_pc = value; - - if (low_pc != (uint64_t)-1 && high_pc != (uint64_t)-1) - coverage_add (&cu_coverage->cov, low_pc, high_pc - low_pc); - } - } - where.ref = NULL; - - if (high_pc != (uint64_t)-1 && low_pc != (uint64_t)-1) - { - if (high_pc_relocated != low_pc_relocated) - wr_message (mc_die_other | mc_impact_2 | mc_reloc, &where, - ": only one of DW_AT_low_pc and DW_AT_high_pc is relocated.\n"); - else - check_range_relocations (mc_die_other, &where, - file, - low_pc_symbol, high_pc_symbol, - "DW_AT_low_pc and DW_AT_high_pc"); - } - - where.ref = &abbrev->where; - - if (abbrev->has_children) - { - int st = read_die_chain (ver, file, ctx, cu, abbrevs, strings, - local_die_refs, - strings_coverage, reloc, - cu_coverage); - if (st == -1) - return -1; - else if (st == 0) - wr_message (mc_impact_3 | mc_acc_suboptimal | mc_die_rel, - &where, - ": abbrev has_children, but the chain was empty.\n"); - } - } - - if (sibling_addr != 0) - wr_error (&where, - ": this DIE should have had its sibling at 0x%" - PRIx64 ", but the DIE chain ended.\n", sibling_addr); - - return got_die ? 1 : 0; -} - bool read_address_size (bool elf_64, struct read_ctx *ctx, @@ -1930,7 +1208,7 @@ op_read_form (struct elf_file *file, return true; } -static bool +bool check_location_expression (struct elf_file *file, struct read_ctx *parent_ctx, struct cu *cu, diff --git a/src/dwarflint/low.h b/src/dwarflint/low.h index d6308c1c8..9783f3896 100644 --- a/src/dwarflint/low.h +++ b/src/dwarflint/low.h @@ -146,16 +146,6 @@ extern "C" // xxx low-level check entry points, will go away struct cu; - extern int read_die_chain (dwarf_version_h ver, - struct elf_file *file, - struct read_ctx *ctx, - struct cu *cu, - struct abbrev_table *abbrevs, - Elf_Data *strings, - struct ref_record *local_die_refs, - struct coverage *strings_coverage, - struct relocation_data *reloc, - struct cu_coverage *cu_coverage); extern bool check_loc_or_range_structural (struct elf_file *file, struct sec *sec, struct cu *cu_chain, @@ -170,6 +160,20 @@ extern "C" extern bool check_line_structural (struct elf_file *file, struct sec *sec, struct addr_record *line_tables); + extern bool check_location_expression (struct elf_file *file, + struct read_ctx *parent_ctx, + struct cu *cu, + uint64_t init_off, + struct relocation_data *reloc, + size_t length, + struct where *wh); + extern void check_range_relocations (enum message_category cat, + struct where *where, + struct elf_file *file, + GElf_Sym *begin_symbol, + GElf_Sym *end_symbol, + const char *description); + extern void cu_free (struct cu *cu_chain); extern int check_sibling_form (dwarf_version_h ver, uint64_t form); @@ -199,6 +203,8 @@ extern "C" struct section_coverage *sco, void *data); bool checked_read_uleb128 (struct read_ctx *ctx, uint64_t *ret, struct where *where, const char *what); + bool checked_read_sleb128 (struct read_ctx *ctx, int64_t *ret, + struct where *where, const char *what); struct abbrev_attrib {