Hi,
I'm poking into .debug_loc. Currently the validation is done only on
portions referenced from .debug_info. I rewrote the code to do it the
way it's done in other cases: .debug_info references are remembered, and
used to cross-check connectivity later, during sequential scan of
.debug_loc.
But when validating "holes", i.e. portions of .debug_loc that are not
referenced from .debug_info, I'm getting a lot of weird errors: negative
and empty ranges, invalid location expressions (opcode 0 is a norm). It
looks like the unreferenced portions are filled with garbage, and that
the section as a whole is not supposed to be scanned sequentially. But
if it's garbage, it screws the stream of location lists surprisingly seldom.
One example that I've found is
/usr/lib/debug/usr/lib64/openoffice.org/program/deployment680lx.uno.so.debug,
where the stream actually ends up screwed royally after offset 70dd, and
the file ends up with dozens of unresolved location list references.
readelf -wloc shows implausible location expressions after 70dd, too.
Many debuginfo files from
openoffice.org suite show similar problems.
So... am I missing some arcane insider info (or just misreading the
spec), or are these actual errors in our DWARF files?
In the mean time, I switched back to the old way of validation, but I'm
attaching the patch (formed against 8aef81) in case anyone wants to see
on their own eyes.
PM
diff --git a/src/dwarflint.c b/src/dwarflint.c
index dc71805..5060994 100644
--- a/src/dwarflint.c
+++ b/src/dwarflint.c
@@ -170,8 +170,8 @@ enum message_category
mc_pubtables = 0x100000, // table of public names/types
mc_pubtypes = 0x200000, // .debug_pubtypes presence
mc_loc = 0x400000, // messages related to .debug_loc
- mc_ranges = 0x800000, // messages related to .debug_ranges
- mc_other = 0x1000000, // messages unrelated to any of the above
+ mc_ranges = 0x1000000, // messages related to .debug_ranges
+ mc_other = 0x2000000, // messages unrelated to any of the above
mc_all = 0xffffff00, // all areas
};
@@ -190,7 +190,7 @@ accept_message (struct message_criteria *crit, enum message_category
cat)
}
static struct message_criteria warning_criteria
- = {mc_all & ~(mc_strings | mc_loc),
+ = {mc_all & ~(mc_strings),
mc_pubtypes};
static struct message_criteria error_criteria
= {mc_impact_4 | mc_error,
@@ -321,7 +321,7 @@ main (int argc, char *argv[])
warning_criteria.reject |= mc_acc_bloat | mc_pubtypes;
if (be_strict)
{
- warning_criteria.accept |= mc_strings | mc_loc;
+ warning_criteria.accept |= mc_strings;
if (!be_gnu)
warning_criteria.reject &= ~mc_pubtypes;
}
@@ -586,7 +586,6 @@ struct hole_info
static void coverage_init (struct coverage *ar, uint64_t size);
static void coverage_add (struct coverage *ar, uint64_t begin, uint64_t end);
-static bool coverage_is_covered (struct coverage *ar, uint64_t address);
static void coverage_find_holes (struct coverage *ar,
void (*cb)(uint64_t begin, uint64_t end, void *data),
void *data);
@@ -604,6 +603,7 @@ struct cu
uint64_t base; // DW_AT_low_pc value of CU DIE, 0 if not present.
struct addr_record die_addrs; // Addresses where DIEs begin in this CU.
struct ref_record die_refs; // DIE references into other CUs from this CU.
+ struct ref_record loc_refs; // DIE references into .debug_loc
struct where where; // Where was this section defined.
bool has_arange; // Whether we saw arange section pointing to this CU.
bool has_pubnames; // Likewise for pubnames.
@@ -619,24 +619,23 @@ static struct cu *cu_find_cu (struct cu *cu_chain, uint64_t
offset);
static struct cu *check_debug_info_structural (struct read_ctx *ctx,
struct abbrev_table *abbrev_chain,
- Elf_Data *strings,
- Elf_Data *loc);
+ Elf_Data *strings);
static bool check_cu_structural (struct read_ctx *ctx,
struct cu *const cu,
struct abbrev_table *abbrev_chain,
Elf_Data *strings,
- Elf_Data *loc,
bool dwarf_64,
struct ref_record *die_refs,
- struct addr_record *loc_addrs,
- struct coverage *strings_coverage,
- struct coverage *loc_coverage);
+ struct coverage *strings_coverage);
static bool check_aranges_structural (struct read_ctx *ctx,
struct cu *cu_chain);
static bool check_pub_structural (struct read_ctx *ctx,
struct cu *cu_chain,
enum section_id sec);
-
+static bool check_loc_structural (struct read_ctx *ctx,
+ struct cu *cu_chain,
+ bool addr_64,
+ struct addr_record *addrs);
static const char *where_fmt (struct where *wh, char *ptr)
{
@@ -841,7 +840,7 @@ process_file (int fd __attribute__((unused)),
{
read_ctx_init (&ctx, dwarf, info_data);
cu_chain = check_debug_info_structural (&ctx, abbrev_chain,
- str_data, loc_data);
+ str_data);
}
else if (!tolerate_nodebug)
/* Hard error, not a message. We can't debug without this. */
@@ -872,6 +871,57 @@ process_file (int fd __attribute__((unused)),
wr_message (mc_impact_4 | mc_acc_suboptimal | mc_elf | mc_pubtypes,
&WHERE (sec_pubtypes, NULL), ": data not found.\n");
+ if (loc_data != NULL && cu_chain != NULL)
+ {
+ /* We can only do exhaustive .debug_loc validation, if all CUs
+ have the same address size. Sanity of address size is
+ checked when building the cu_chain. */
+ uint8_t address_size = 0;
+ for (struct cu *it = cu_chain; it != NULL; it = it->next)
+ if (address_size == 0)
+ address_size = it->address_size;
+ else if (address_size != it->address_size)
+ {
+ wr_message (mc_loc | mc_info, &it->where,
+ ": has different address size from the previous CU.\n");
+ address_size = 0;
+ break;
+ }
+
+ if (address_size != 0)
+ {
+ read_ctx_init (&ctx, dwarf, loc_data);
+
+ struct addr_record loclist_addrs;
+ memset (&loclist_addrs, 0, sizeof (loclist_addrs));
+
+ if (check_loc_structural (&ctx, cu_chain, address_size == 8,
+ &loclist_addrs))
+ {
+ bool have_unresolved = false;
+
+ /* Check that all references are resolved. */
+ for (struct cu *it = cu_chain; it != NULL; it = it->next)
+ for (size_t i = 0; i < it->loc_refs.size; ++i)
+ {
+ struct ref *ref = it->loc_refs.refs + i;
+ if (!addr_record_has_addr (&loclist_addrs, ref->addr))
+ {
+ if (!have_unresolved)
+ {
+ wr_error (&WHERE (sec_info, NULL),
+ ": unresolved location expression references:\n");
+ have_unresolved = true;
+ }
+ wr_error (&ref->who,
+ ": references 0x%" PRIx64 ".\n", ref->addr);
+ }
+ }
+ }
+ }
+ }
+ /* XXX check that all cu->loc_refs are satisfied. */
+
cu_free (cu_chain);
abbrev_table_free (abbrev_chain);
}
@@ -1620,27 +1670,6 @@ coverage_add (struct coverage *ar, uint64_t begin, uint64_t end)
}
}
-static bool
-coverage_is_covered (struct coverage *ar, uint64_t address)
-{
- assert (ar);
- assert (address <= ar->size);
-
- uint64_t bi = address / coverage_emt_bits;
- uint8_t bb = address % coverage_emt_bits;
- coverage_emt_type bm = (coverage_emt_type)1 << (coverage_emt_bits - 1 - bb);
- return !!(ar->buf[bi] & bm);
-}
-
-static bool
-coverage_pristine (struct coverage *ar, uint64_t begin, uint64_t length)
-{
- for (uint64_t i = 0; i < length; ++i)
- if (coverage_is_covered (ar, begin + i))
- return false;
- return true;
-}
-
static void
coverage_find_holes (struct coverage *ar,
void (*cb)(uint64_t begin, uint64_t end, void *user),
@@ -1844,6 +1873,7 @@ wr_check_zero_padding (struct read_ctx *ctx,
return true;
}
+/*
static bool
check_x_location_expression (struct read_ctx *ctx,
struct cu *cu,
@@ -1851,12 +1881,12 @@ check_x_location_expression (struct read_ctx *ctx,
struct addr_record *loc_addrs,
uint64_t addr, bool addr_64,
struct where *wh);
+*/
static struct cu *
check_debug_info_structural (struct read_ctx *ctx,
struct abbrev_table *abbrev_chain,
- Elf_Data *strings,
- Elf_Data *loc)
+ Elf_Data *strings)
{
struct ref_record die_refs;
memset (&die_refs, 0, sizeof (die_refs));
@@ -1872,16 +1902,6 @@ check_debug_info_structural (struct read_ctx *ctx,
strings_coverage = &strings_coverage_mem;
}
- struct coverage loc_coverage_mem, *loc_coverage = NULL;
- struct addr_record loc_addrs_mem, *loc_addrs = NULL;
- if (loc != NULL && check_category (mc_loc))
- {
- coverage_init (&loc_coverage_mem, loc->d_size);
- loc_coverage = &loc_coverage_mem;
- memset (&loc_addrs_mem, 0, sizeof (loc_addrs_mem));
- loc_addrs = &loc_addrs_mem;
- }
-
while (!read_ctx_eof (ctx))
{
const unsigned char *cu_begin = ctx->ptr;
@@ -1955,9 +1975,8 @@ check_debug_info_structural (struct read_ctx *ctx,
}
cu_ctx.ptr = ctx->ptr;
- if (!check_cu_structural (&cu_ctx, cur, abbrev_chain, strings, loc,
- dwarf_64, &die_refs, loc_addrs,
- strings_coverage, loc_coverage))
+ if (!check_cu_structural (&cu_ctx, cur, abbrev_chain, strings,
+ dwarf_64, &die_refs, strings_coverage))
{
success = false;
break;
@@ -2008,24 +2027,26 @@ check_debug_info_structural (struct read_ctx *ctx,
coverage_free (strings_coverage);
}
- if (loc_coverage != NULL)
- {
- if (success)
- coverage_find_holes (loc_coverage, found_hole,
- &((struct hole_info)
- {sec_loc, mc_loc, loc->d_buf}));
- coverage_free (loc_coverage);
- }
-
- if (loc_addrs != NULL)
- addr_record_free (loc_addrs);
-
if (!success || !references_sound)
{
cu_free (cu_chain);
cu_chain = NULL;
}
+ /* Reverse the chain, so that it's organized "naturally". Has
+ significant impact on performance when building set of all loc
+ references in CUs, as the addr_record_add doesn't have to add to
+ the beginning. */
+ struct cu *last = NULL;
+ for (struct cu *it = cu_chain; it != NULL; )
+ {
+ struct cu *next = it->next;
+ it->next = last;
+ last = it;
+ it = next;
+ }
+ cu_chain = last;
+
return cu_chain;
}
@@ -2051,6 +2072,7 @@ get_location_opcode_operands (uint8_t opcode, uint8_t *op1, uint8_t
*op2)
};
}
+/* CAT is message category. */
static void
check_location_expression (struct read_ctx *ctx, struct where *wh, bool addr_64)
{
@@ -2068,8 +2090,9 @@ check_location_expression (struct read_ctx *ctx, struct where *wh,
bool addr_64)
uint8_t op1, op2;
if (!get_location_opcode_operands (opcode, &op1, &op2))
{
- wr_error (&where, ": can't decode opcode \"%s\".\n",
- dwarf_locexpr_opcode_string (opcode));
+ wr_message (mc_loc | mc_error, &where,
+ ": can't decode opcode \"%s\".\n",
+ dwarf_locexpr_opcode_string (opcode));
return;
}
@@ -2093,6 +2116,7 @@ check_location_expression (struct read_ctx *ctx, struct where *wh,
bool addr_64)
}
}
+#if 0
static bool
check_x_location_expression (struct read_ctx *ctx,
struct cu *cu,
@@ -2228,6 +2252,7 @@ check_x_location_expression (struct read_ctx *ctx,
return retval;
}
+#endif
/*
@@ -2242,13 +2267,10 @@ read_die_chain (struct read_ctx *ctx,
struct cu *cu,
struct abbrev_table *abbrevs,
Elf_Data *strings,
- Elf_Data *loc,
bool dwarf_64, bool addr_64,
struct ref_record *die_refs,
- struct ref_record *die_loc_refs,
- struct addr_record *loc_addrs,
- struct coverage *strings_coverage,
- struct coverage *loc_coverage)
+ struct ref_record *local_die_refs,
+ struct coverage *strings_coverage)
{
bool got_die = false;
uint64_t sibling_addr = 0;
@@ -2320,7 +2342,7 @@ read_die_chain (struct read_ctx *ctx,
{
where.ref = &it->where;
- void record_ref (uint64_t addr, struct where *who, bool local)
+ void record_ref (uint64_t addr, bool local)
{
struct ref_record *record = &cu->die_refs;
if (local)
@@ -2337,11 +2359,11 @@ read_die_chain (struct read_ctx *ctx,
/* Address holds a CU-local reference, so add CU
offset to turn it into section offset. */
addr += cu->offset;
- record = die_loc_refs;
+ record = local_die_refs;
}
if (record != NULL)
- ref_record_add (record, addr, who);
+ ref_record_add (record, addr, &where);
}
uint8_t form = it->form;
@@ -2377,7 +2399,6 @@ read_die_chain (struct read_ctx *ctx,
}
bool check_locptr = false;
- bool locptr_64 = addr_64;
if (is_location_attrib (it->name))
{
switch (check_CU_location_form (form, dwarf_64))
@@ -2395,7 +2416,6 @@ read_die_chain (struct read_ctx *ctx,
wr_error (&where,
": location attribute with form \"%s\" in %d-bit CU.\n",
dwarf_form_string (form), (dwarf_64 ? 64 : 32));
- locptr_64 = !locptr_64;
/* fall-through */
case 1: /* locptr */
@@ -2455,7 +2475,7 @@ read_die_chain (struct read_ctx *ctx,
goto cant_read;
if (it->form == DW_FORM_ref_addr)
- record_ref (addr, &where, false);
+ record_ref (addr, false);
else if (abbrev->tag == DW_TAG_compile_unit
|| abbrev->tag == DW_TAG_partial_unit)
cu->base = addr;
@@ -2474,7 +2494,7 @@ read_die_chain (struct read_ctx *ctx,
if (it->name == DW_AT_sibling)
sibling_addr = value;
else if (it->form == DW_FORM_ref_udata)
- record_ref (value, &where, true);
+ record_ref (value, true);
break;
}
@@ -2489,7 +2509,7 @@ read_die_chain (struct read_ctx *ctx,
if (it->name == DW_AT_sibling)
sibling_addr = value;
else if (it->form == DW_FORM_ref1)
- record_ref (value, &where, true);
+ record_ref (value, true);
break;
}
@@ -2503,14 +2523,14 @@ read_die_chain (struct read_ctx *ctx,
if (it->name == DW_AT_sibling)
sibling_addr = value;
else if (it->form == DW_FORM_ref2)
- record_ref (value, &where, true);
+ record_ref (value, true);
break;
}
case DW_FORM_data4:
case DW_FORM_ref4:
{
- uint32_t value;
+ uint32_t value = -1;
if (!read_ctx_read_4ubyte (ctx, &value))
goto cant_read;
@@ -2518,14 +2538,11 @@ read_die_chain (struct read_ctx *ctx,
sibling_addr = value;
else if (check_locptr)
{
- struct read_ctx sub_ctx;
- read_ctx_init (&sub_ctx, ctx->dbg, loc);
- check_x_location_expression (&sub_ctx, cu, loc_coverage,
- loc_addrs, value, locptr_64,
- &where);
+ //printf ("loclist reference 32 %#" PRIx32 "\n", value);
+ ref_record_add (&cu->loc_refs, value, &where);
}
else if (it->form == DW_FORM_ref4)
- record_ref (value, &where, true);
+ record_ref (value, true);
break;
}
@@ -2540,14 +2557,11 @@ read_die_chain (struct read_ctx *ctx,
sibling_addr = value;
else if (check_locptr)
{
- struct read_ctx sub_ctx;
- read_ctx_init (&sub_ctx, ctx->dbg, loc);
- check_x_location_expression (&sub_ctx, cu, loc_coverage,
- loc_addrs, value, locptr_64,
- &where);
+ //printf ("loclist reference 64 %#" PRIx64 "\n", value);
+ ref_record_add (&cu->loc_refs, value, &where);
}
else if (it->form == DW_FORM_ref8)
- record_ref (value, &where, true);
+ record_ref (value, true);
break;
}
@@ -2618,10 +2632,10 @@ read_die_chain (struct read_ctx *ctx,
if (abbrev->has_children)
{
- int st = read_die_chain (ctx, cu, abbrevs, strings, loc,
+ int st = read_die_chain (ctx, cu, abbrevs, strings,
dwarf_64, addr_64,
- die_refs, die_loc_refs, loc_addrs,
- strings_coverage, loc_coverage);
+ die_refs, local_die_refs,
+ strings_coverage);
if (st == -1)
return -1;
else if (st == 0)
@@ -2672,12 +2686,9 @@ check_cu_structural (struct read_ctx *ctx,
struct cu *const cu,
struct abbrev_table *abbrev_chain,
Elf_Data *strings,
- Elf_Data *loc,
bool dwarf_64,
struct ref_record *die_refs,
- struct addr_record *loc_addrs,
- struct coverage *strings_coverage,
- struct coverage *loc_coverage)
+ struct coverage *strings_coverage)
{
uint16_t version;
uint64_t abbrev_offset;
@@ -2722,15 +2733,14 @@ check_cu_structural (struct read_ctx *ctx,
return false;
}
- struct ref_record die_loc_refs;
- memset (&die_loc_refs, 0, sizeof (die_loc_refs));
+ struct ref_record local_die_refs;
+ memset (&local_die_refs, 0, sizeof (local_die_refs));
bool retval = true;
- if (read_die_chain (ctx, cu, abbrevs, strings, loc,
+ if (read_die_chain (ctx, cu, abbrevs, strings,
dwarf_64, address_size == 8,
- die_refs, &die_loc_refs,
- loc_addrs, strings_coverage,
- loc_coverage) >= 0)
+ die_refs, &local_die_refs,
+ strings_coverage) >= 0)
{
for (size_t i = 0; i < abbrevs->size; ++i)
if (!abbrevs->abbr[i].used)
@@ -2738,13 +2748,13 @@ check_cu_structural (struct read_ctx *ctx,
": Abbreviation with code %" PRIu64 " is never used.\n",
abbrevs->abbr[i].code);
- if (!check_die_references (cu, &die_loc_refs))
+ if (!check_die_references (cu, &local_die_refs))
retval = false;
}
else
retval = false;
- ref_record_free (&die_loc_refs);
+ ref_record_free (&local_die_refs);
return retval;
}
@@ -3063,3 +3073,116 @@ check_pub_structural (struct read_ctx *ctx, struct cu *cu_chain,
return retval;
}
+
+
+static bool
+check_loc_structural (struct read_ctx *ctx,
+ struct cu *cu_chain,
+ bool addr_64,
+ struct addr_record *addrs)
+{
+ /* A record of all references for fast look-up of
+ unreferenced .debug_loc portions. */
+ struct addr_record refs;
+ memset (&refs, 0, sizeof (refs));
+ for (struct cu *it = cu_chain; it != NULL; it = it->next)
+ for (size_t i = 0; i < it->loc_refs.size; ++i)
+ addr_record_add (&refs, it->loc_refs.refs[i].addr);
+
+ uint64_t escape = addr_64 ? (uint64_t)-1 : (uint64_t)(uint32_t)-1;
+ while (!read_ctx_eof (ctx))
+ {
+ struct where where = WHERE (sec_loc, NULL);
+ uint64_t loclist_addr = read_ctx_get_offset (ctx);
+ where_reset_1 (&where, loclist_addr);
+ addr_record_add (addrs, loclist_addr);
+
+ if (!addr_record_has_addr (&refs, loclist_addr))
+ wr_message (mc_loc | mc_acc_bloat | mc_impact_2,
+ &where, ": unreferenced loclist.\n");
+
+ uint64_t base = -1;
+ while (!read_ctx_eof (ctx))
+ {
+ where_reset_2 (&where, read_ctx_get_offset (ctx));
+ /*
+ for (size_t i = 0; i < 32; ++i)
+ printf (" %02x", ctx->ptr[i]);
+ printf ("\n");
+ */
+ /* 7.7.3: the two addresses are the same size as used by
+ DW_FORM_addr on the target machine. */
+
+ /* begin address */
+ uint64_t begin_addr;
+ if (!read_ctx_read_offset (ctx, addr_64, &begin_addr))
+ {
+ wr_error (&where, ": can't read address range beginning.\n");
+ return false;
+ }
+
+ /* end address */
+ uint64_t end_addr;
+ if (!read_ctx_read_offset (ctx, addr_64, &end_addr))
+ {
+ wr_error (&where, ": can't read address range ending.\n");
+ return false;
+ }
+
+ if (begin_addr == 0 && end_addr == 0)
+ /* 2.6.6: A location list containing only an end of list
+ entry describes an object that exists in the source
+ code but not in the executable program. */
+ break;
+ else if (begin_addr == escape)
+ {
+ if (end_addr == base)
+ wr_message (mc_acc_bloat | mc_impact_3 | mc_loc, &where,
+ ": base address selection entry doesn't change base address.\n");
+ base = end_addr;
+ }
+ else
+ {
+ /* location expression length */
+ uint16_t len;
+ if (!read_ctx_read_2ubyte (ctx, &len))
+ {
+ wr_error (&where, ": can't read length of location
expression.\n");
+ return false;
+ }
+ /* DWARF expression containing no operations
+ [i.e. length == 0] [...] represents a piece of an
+ object that is present in the source code but not in
+ the object code (perhaps due to optimization). */
+
+ if (end_addr < begin_addr)
+ wr_message (mc_loc | mc_error, &where,
+ ": has negative range 0x%" PRIx64 "..0x%" PRIx64
".\n",
+ begin_addr, end_addr);
+ else if (begin_addr == end_addr && len > 0)
+ /* 2.6.6: A location list entry [...] whose beginning
+ and ending addresses are equal has no effect. */
+ wr_message (mc_acc_bloat | mc_impact_3 | mc_loc, &where,
+ ": location list entry covers no range.\n");
+
+ /* location expression itself */
+ struct read_ctx expr_ctx;
+ if (!read_ctx_init_sub (&expr_ctx, ctx, ctx->ptr, ctx->ptr + len))
+ {
+ not_enough:
+ wr_error (&where,
+ ": not enough data for location expression of length %d.\n",
+ len);
+ return false;
+ }
+
+ check_location_expression (&expr_ctx, &where, addr_64);
+ if (!read_ctx_skip (ctx, len))
+ /* "can't happen" */
+ goto not_enough;
+ }
+ }
+ }
+
+ return true;
+}