__libdw_getdieranges builds an aranges list by iterating over CUs and recording each address range.
__libdw_getdieranges provides an alternative to relying on .debug_aranges for address ranges, since this section might be absent or incomplete. https://sourceware.org/bugzilla/show_bug.cgi?id=22288 https://sourceware.org/bugzilla/show_bug.cgi?id=30948 Signed-off-by: Aaron Merey <ame...@redhat.com> --- Once this function's interface is refined it can be added to the public libdw API. We might add some form of lazy loading, for example. I mentioned [1] that there are some clang-compiled shared libraries where some subprograms starting at address 0 don't have a corresponding entry in .debug_ranges (for a CU that contains DW_AT_ranges). These address ranges with no corresponding entry in .debug_ranges won't be included in the aranges generated by this patch's CU iteration approach. [1] https://sourceware.org/pipermail/elfutils-devel/2024q1/006832.html libdw/dwarf_addrdie.c | 2 +- libdw/dwarf_getaranges.c | 190 ++++++++++++++++++++++++++++++--------- libdw/libdwP.h | 13 ++- libdwfl/cu.c | 8 +- tests/run-getsrc-die.sh | 5 ++ 5 files changed, 169 insertions(+), 49 deletions(-) diff --git a/libdw/dwarf_addrdie.c b/libdw/dwarf_addrdie.c index 3a08ab75..48a1aaea 100644 --- a/libdw/dwarf_addrdie.c +++ b/libdw/dwarf_addrdie.c @@ -41,7 +41,7 @@ dwarf_addrdie (Dwarf *dbg, Dwarf_Addr addr, Dwarf_Die *result) size_t naranges; Dwarf_Off off; - if (INTUSE(dwarf_getaranges) (dbg, &aranges, &naranges) != 0 + if (__libdw_getdieranges (dbg, &aranges, &naranges) != 0 || INTUSE(dwarf_getarangeinfo) (INTUSE(dwarf_getarange_addr) (aranges, addr), NULL, NULL, &off) != 0) diff --git a/libdw/dwarf_getaranges.c b/libdw/dwarf_getaranges.c index 27439d37..41fe96d0 100644 --- a/libdw/dwarf_getaranges.c +++ b/libdw/dwarf_getaranges.c @@ -33,7 +33,6 @@ #endif #include <stdlib.h> -#include <assert.h> #include "libdwP.h" #include <dwarf.h> @@ -54,6 +53,149 @@ compare_aranges (const void *a, const void *b) return 0; } +/* Convert ARANGELIST into Dwarf_Aranges and store at ARANGES. */ +static bool +finalize_aranges (Dwarf *dbg, Dwarf_Aranges **aranges, size_t *naranges, + struct arangelist *arangelist, unsigned int narangelist) +{ + /* Allocate the array for the result. */ + void *buf = libdw_alloc (dbg, Dwarf_Aranges, + sizeof (Dwarf_Aranges) + + narangelist * sizeof (Dwarf_Arange), 1); + + /* First use the buffer for the pointers, and sort the entries. + We'll write the pointers in the end of the buffer, and then + copy into the buffer from the beginning so the overlap works. */ + eu_static_assert (sizeof (Dwarf_Arange) >= sizeof (Dwarf_Arange *)); + struct arangelist **sortaranges + = (buf + sizeof (Dwarf_Aranges) + + ((sizeof (Dwarf_Arange) - sizeof sortaranges[0]) * narangelist)); + + /* The list is in LIFO order and usually they come in clumps with + ascending addresses. So fill from the back to probably start with + runs already in order before we sort. */ + unsigned int i = narangelist; + while (i-- > 0) + { + sortaranges[i] = arangelist; + arangelist = arangelist->next; + } + + /* Something went wrong if narangelist is less then the actual length + of arangelist. */ + if (arangelist != NULL) + { + __libdw_seterrno (DWARF_E_UNKNOWN_ERROR); + return false; + } + + /* Sort by ascending address. */ + qsort (sortaranges, narangelist, sizeof sortaranges[0], &compare_aranges); + + /* Now that they are sorted, put them in the final array. + The buffers overlap, so we've clobbered the early elements + of SORTARANGES by the time we're reading the later ones. */ + *aranges = buf; + (*aranges)->dbg = dbg; + (*aranges)->naranges = narangelist; + if (naranges != NULL) + *naranges = narangelist; + for (i = 0; i < narangelist; ++i) + { + struct arangelist *elt = sortaranges[i]; + (*aranges)->info[i] = elt->arange; + free (elt); + } + + return true; +} + +int +__libdw_getdieranges (Dwarf *dbg, Dwarf_Aranges **aranges, size_t *naranges) +{ + if (dbg == NULL) + return -1; + + if (dbg->dieranges != NULL) + { + *aranges = dbg->dieranges; + if (naranges != NULL) + *naranges = dbg->dieranges->naranges; + return 0; + } + + struct arangelist *arangelist = NULL; + unsigned int narangelist = 0; + + Dwarf_CU *cu = NULL; + while (INTUSE(dwarf_get_units) (dbg, cu, &cu, NULL, NULL, NULL, NULL) == 0) + { + Dwarf_Addr base; + Dwarf_Addr low; + Dwarf_Addr high; + + Dwarf_Die cudie = CUDIE (cu); + + /* Skip CUs that only contain type information. */ + if (!INTUSE(dwarf_hasattr) (&cudie, DW_AT_low_pc) + && !INTUSE(dwarf_hasattr) (&cudie, DW_AT_ranges)) + continue; + + ptrdiff_t offset = 0; + + /* Add arange for each range list entry or high_pc and low_pc. */ + while ((offset = INTUSE(dwarf_ranges) (&cudie, offset, + &base, &low, &high)) > 0) + { + if (offset == -1) + { + __libdw_seterrno (DWARF_E_INVALID_DWARF); + goto fail; + } + + struct arangelist *new_arange = malloc (sizeof *new_arange); + if (unlikely (new_arange == NULL)) + { + __libdw_seterrno (DWARF_E_NOMEM); + goto fail; + } + + new_arange->arange.addr = low; + new_arange->arange.length = (Dwarf_Word) (high - low); + new_arange->arange.offset = __libdw_first_die_off_from_cu (cu); + + new_arange->next = arangelist; + arangelist = new_arange; + ++narangelist; + } + } + + if (narangelist == 0) + { + if (arangelist != NULL) + goto fail; + if (naranges != NULL) + *naranges = 0; + *aranges = NULL; + return 0; + } + + if (!finalize_aranges (dbg, aranges, naranges, arangelist, narangelist)) + goto fail; + + dbg->dieranges = *aranges; + return 0; + +fail: + while (arangelist != NULL) + { + struct arangelist *next = arangelist->next; + free (arangelist); + arangelist = next; + } + return -1; +} + int dwarf_getaranges (Dwarf *dbg, Dwarf_Aranges **aranges, size_t *naranges) { @@ -235,56 +377,18 @@ dwarf_getaranges (Dwarf *dbg, Dwarf_Aranges **aranges, size_t *naranges) if (narangelist == 0) { - assert (arangelist == NULL); + if (arangelist != NULL) + goto fail; if (naranges != NULL) *naranges = 0; *aranges = NULL; return 0; } - /* Allocate the array for the result. */ - void *buf = libdw_alloc (dbg, Dwarf_Aranges, - sizeof (Dwarf_Aranges) - + narangelist * sizeof (Dwarf_Arange), 1); - - /* First use the buffer for the pointers, and sort the entries. - We'll write the pointers in the end of the buffer, and then - copy into the buffer from the beginning so the overlap works. */ - assert (sizeof (Dwarf_Arange) >= sizeof (Dwarf_Arange *)); - struct arangelist **sortaranges - = (buf + sizeof (Dwarf_Aranges) - + ((sizeof (Dwarf_Arange) - sizeof sortaranges[0]) * narangelist)); - - /* The list is in LIFO order and usually they come in clumps with - ascending addresses. So fill from the back to probably start with - runs already in order before we sort. */ - unsigned int i = narangelist; - while (i-- > 0) - { - sortaranges[i] = arangelist; - arangelist = arangelist->next; - } - assert (arangelist == NULL); - - /* Sort by ascending address. */ - qsort (sortaranges, narangelist, sizeof sortaranges[0], &compare_aranges); + if (!finalize_aranges (dbg, aranges, naranges, arangelist, narangelist)) + goto fail; - /* Now that they are sorted, put them in the final array. - The buffers overlap, so we've clobbered the early elements - of SORTARANGES by the time we're reading the later ones. */ - *aranges = buf; - (*aranges)->dbg = dbg; - (*aranges)->naranges = narangelist; dbg->aranges = *aranges; - if (naranges != NULL) - *naranges = narangelist; - for (i = 0; i < narangelist; ++i) - { - struct arangelist *elt = sortaranges[i]; - (*aranges)->info[i] = elt->arange; - free (elt); - } - return 0; } INTDEF(dwarf_getaranges) diff --git a/libdw/libdwP.h b/libdw/libdwP.h index 75e0283b..714e95e3 100644 --- a/libdw/libdwP.h +++ b/libdw/libdwP.h @@ -232,9 +232,12 @@ struct Dwarf /* Search tree for decoded .debug_line units. */ void *files_lines; - /* Address ranges. */ + /* Address ranges read from .debug_aranges. */ Dwarf_Aranges *aranges; + /* Address ranges inferred from CUs. */ + Dwarf_Aranges *dieranges; + /* Cached info from the CFI section. */ struct Dwarf_CFI_s *cfi; @@ -1472,4 +1475,12 @@ void __libdw_set_debugdir (Dwarf *dbg); char * __libdw_filepath (const char *debugdir, const char *dir, const char *file) internal_function; + +/* Like dwarf_getaranges but aranges are generated from CU address + ranges instead of being read from .debug_aranges. + + Returns 0 if successful and updates ARANGES and NARANGES. Otherwise + returns -1 and sets libdw_errno. +*/ +int __libdw_getdieranges (Dwarf *dbg, Dwarf_Aranges **aranges, size_t *naranges); #endif /* libdwP.h */ diff --git a/libdwfl/cu.c b/libdwfl/cu.c index b1afb19a..06684357 100644 --- a/libdwfl/cu.c +++ b/libdwfl/cu.c @@ -39,7 +39,7 @@ static inline Dwarf_Arange * dwar (Dwfl_Module *mod, unsigned int idx) { - return &mod->dw->aranges->info[mod->aranges[idx].arange]; + return &mod->dw->dieranges->info[mod->aranges[idx].arange]; } @@ -51,7 +51,7 @@ addrarange (Dwfl_Module *mod, Dwarf_Addr addr, struct dwfl_arange **arange) struct dwfl_arange *aranges = NULL; Dwarf_Aranges *dwaranges = NULL; size_t naranges; - if (INTUSE(dwarf_getaranges) (mod->dw, &dwaranges, &naranges) != 0) + if (__libdw_getdieranges (mod->dw, &dwaranges, &naranges) != 0) return DWFL_E_LIBDW; /* If the module has no aranges (when no code is included) we @@ -119,7 +119,7 @@ addrarange (Dwfl_Module *mod, Dwarf_Addr addr, struct dwfl_arange **arange) { /* It might be in the last range. */ const Dwarf_Arange *last - = &mod->dw->aranges->info[mod->dw->aranges->naranges - 1]; + = &mod->dw->dieranges->info[mod->dw->dieranges->naranges - 1]; if (addr > last->addr + last->length) break; } @@ -296,7 +296,7 @@ arangecu (Dwfl_Module *mod, struct dwfl_arange *arange, struct dwfl_cu **cu) { if (arange->cu == NULL) { - const Dwarf_Arange *dwarange = &mod->dw->aranges->info[arange->arange]; + const Dwarf_Arange *dwarange = &mod->dw->dieranges->info[arange->arange]; Dwfl_Error result = intern_cu (mod, dwarange->offset, &arange->cu); if (result != DWFL_E_NOERROR) return result; diff --git a/tests/run-getsrc-die.sh b/tests/run-getsrc-die.sh index 4da16e7a..3418b33a 100755 --- a/tests/run-getsrc-die.sh +++ b/tests/run-getsrc-die.sh @@ -23,6 +23,11 @@ # dwarf_getsrc_die testfiles testfile testfile-inlines testfile-lex-inlines +# The following tests should pass without .debug_aranges present. +objcopy --remove-section .debug_aranges testfile +objcopy --remove-section .debug_aranges testfile-inlines +objcopy --remove-section .debug_aranges testfile-lex-inlines + testrun_compare ${abs_top_builddir}/tests/getsrc_die testfile 0x08048468 0x0804845c <<\EOF /home/drepper/gnu/new-bu/build/ttt/f.c:3 /home/drepper/gnu/new-bu/build/ttt/b.c:4 -- 2.43.0