#endif
#include <cassert>
+#include <sstream>
+#include "../libdw/dwarf.h"
#include "messages.h"
#include "low.h"
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 <const char *> (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,
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)
{
return st >= 0;
}
-static bool
+bool
checked_read_sleb128 (struct read_ctx *ctx, int64_t *ret,
struct where *where, const char *what)
{
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)
{
return retval;
}
-static void
+void
check_range_relocations (enum message_category cat,
struct where *where,
struct elf_file *file,
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,
return true;
}
-static bool
+bool
check_location_expression (struct elf_file *file,
struct read_ctx *parent_ctx,
struct cu *cu,
// 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,
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);
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
{