https://lists.fedorahosted.org/pipermail/elfutils-devel/2012-July/002418.html From b1e42797293bcf34385d5cb0a18e8c773279241b Mon Sep 17 00:00:00 2001 From: Mark Wielaard Date: Fri, 22 Jun 2012 12:02:45 +0200 Subject: [PATCH] libdw: Add support for DWZ multifile forms DW_FORM_GNU_ref_alt/strp_alt. DWZ multifile forms http://www.dwarfstd.org/ShowIssue.php?issue=120604.1 DW_FORM_GNU_ref_alt and DW_FORM_GNU_strp_alt reference an alternative debuginfo file. dwarf_begin and dwarf_begin_elf will try to use this automatically. There are no user visible changes to the libdw interface. dwarf_formref_die, dwarf_formstring and dwarf_formudata can now return a Dwarf_Die which comes from a CU in the alternative Dwarf descriptor. __libdw_read_offset was adjusted to take an alternative Dwarf descriptor into account. Signed-off-by: Mark Wielaard diff --git a/libdw/dwarf.h b/libdw/dwarf.h index f41d296..81bc7fe 100644 --- a/libdw/dwarf.h +++ b/libdw/dwarf.h @@ -299,7 +299,10 @@ enum DW_FORM_sec_offset = 0x17, DW_FORM_exprloc = 0x18, DW_FORM_flag_present = 0x19, - DW_FORM_ref_sig8 = 0x20 + DW_FORM_ref_sig8 = 0x20, + + DW_FORM_GNU_ref_alt = 0x1f20, /* offset in alternate .debuginfo. */ + DW_FORM_GNU_strp_alt = 0x1f21 /* offset in alternate .debug_str. */ }; diff --git a/libdw/dwarf_begin.c b/libdw/dwarf_begin.c index 1f3fc3b..9f3050f 100644 --- a/libdw/dwarf_begin.c +++ b/libdw/dwarf_begin.c @@ -98,3 +98,4 @@ dwarf_begin (fd, cmd) return result; } +INTDEF(dwarf_begin) diff --git a/libdw/dwarf_begin_elf.c b/libdw/dwarf_begin_elf.c index 3e01800..fd95770 100644 --- a/libdw/dwarf_begin_elf.c +++ b/libdw/dwarf_begin_elf.c @@ -31,12 +31,17 @@ # include #endif +#include +#include #include #include #include +#include #include #include +#include #include +#include #include "libdwP.h" @@ -66,6 +71,110 @@ static const char dwarf_scnnames[IDX_last][17] = }; #define ndwarf_scnnames (sizeof (dwarf_scnnames) / sizeof (dwarf_scnnames[0])) +internal_function int +__check_build_id (Dwarf *dw, const uint8_t *build_id, const size_t id_len) +{ + if (dw == NULL) + return -1; + + Elf *elf = dw->elf; + Elf_Scn *scn = elf_nextscn (elf, NULL); + if (scn == NULL) + return -1; + + do + { + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + if (likely (shdr != NULL) && shdr->sh_type == SHT_NOTE) + { + size_t pos = 0; + GElf_Nhdr nhdr; + size_t name_pos; + size_t desc_pos; + Elf_Data *data = elf_getdata (scn, NULL); + while ((pos = gelf_getnote (data, pos, &nhdr, &name_pos, + &desc_pos)) > 0) + if (nhdr.n_type == NT_GNU_BUILD_ID + && nhdr.n_namesz == sizeof "GNU" + && ! memcmp (data->d_buf + name_pos, "GNU", sizeof "GNU")) + return (nhdr.n_descsz == id_len + && ! memcmp (data->d_buf + desc_pos, + build_id, id_len)) ? 0 : 1; + } + } + while ((scn = elf_nextscn (elf, scn)) != NULL); + + return -1; +} + +/* Try to open an debug alt link by name, checking build_id. + Marks free_alt on success, return NULL on failure. */ +static Dwarf * +try_debugaltlink (Dwarf *result, const char *try_name, + const uint8_t *build_id, const size_t id_len) +{ + int fd = open (try_name, O_RDONLY); + if (fd > 0) + { + result->alt_dwarf = INTUSE (dwarf_begin) (fd, DWARF_C_READ); + if (result->alt_dwarf != NULL) + { + Elf *elf = result->alt_dwarf->elf; + if (__check_build_id (result->alt_dwarf, build_id, id_len) == 0 + && elf_cntl (elf, ELF_C_FDREAD) == 0) + { + close (fd); + result->free_alt = 1; + return result; + } + INTUSE (dwarf_end) (result->alt_dwarf); + } + close (fd); + } + return NULL; +} + +/* For dwz multifile support, ignore if it looks wrong. */ +static Dwarf * +open_debugaltlink (Dwarf *result, const char *alt_name, + const uint8_t *build_id, const size_t id_len) +{ + /* First try the name itself, it is either an absolute path or + a relative one. Sadly we don't know relative from where at + this point. */ + if (try_debugaltlink (result, alt_name, build_id, id_len) != NULL) + return result; + + /* Lets try based on the build-id. This is somewhat distro specific, + we are following the Fedora implementation described at + https://fedoraproject.org/wiki/Releases/FeatureBuildId#Find_files_by_build_ID + */ +#define DEBUG_PREFIX "/usr/lib/debug/.build-id/" +#define PREFIX_LEN sizeof (DEBUG_PREFIX) + char id_name[PREFIX_LEN + 1 + id_len * 2 + sizeof ".debug" - 1]; + strcpy (id_name, DEBUG_PREFIX); + int n = snprintf (&id_name[PREFIX_LEN - 1], + 4, "%02" PRIx8 "/", (uint8_t) build_id[0]); + assert (n == 3); + for (size_t i = 1; i < id_len; ++i) + { + n = snprintf (&id_name[PREFIX_LEN - 1 + 3 + (i - 1) * 2], + 3, "%02" PRIx8, (uint8_t) build_id[i]); + assert (n == 2); + } + strcpy (&id_name[PREFIX_LEN - 1 + 3 + (id_len - 1) * 2], + ".debug"); + + if (try_debugaltlink (result, id_name, build_id, id_len)) + return result; + + /* Everything failed, mark this Dwarf as not having an alternate, + but don't fail the load. The user may want to set it by hand + before usage. */ + result->alt_dwarf = NULL; + return result; +} static Dwarf * check_section (Dwarf *result, GElf_Ehdr *ehdr, Elf_Scn *scn, bool inscngrp) @@ -110,6 +219,20 @@ check_section (Dwarf *result, GElf_Ehdr *ehdr, Elf_Scn *scn, bool inscngrp) return NULL; } + /* For dwz multifile support, ignore if it looks wrong. */ + if (strcmp (scnname, ".gnu_debugaltlink") == 0) + { + Elf_Data *data = elf_getdata (scn, NULL); + if (data != NULL && data->d_size != 0) + { + const char *alt_name = data->d_buf; + const void *build_id = memchr (data->d_buf, '\0', data->d_size); + const int id_len = data->d_size - (build_id - data->d_buf + 1); + if (alt_name && build_id && id_len > 0) + return open_debugaltlink (result, alt_name, build_id + 1, id_len); + } + } + /* Recognize the various sections. Most names start with .debug_. */ size_t cnt; diff --git a/libdw/dwarf_end.c b/libdw/dwarf_end.c index b77988f..e65314a 100644 --- a/libdw/dwarf_end.c +++ b/libdw/dwarf_end.c @@ -111,6 +111,10 @@ dwarf_end (dwarf) if (dwarf->free_elf) elf_end (dwarf->elf); + /* Free the alternative Dwarf descriptor if necessary. */ + if (dwarf->free_alt) + INTUSE (dwarf_end) (dwarf->alt_dwarf); + /* Free the context descriptor. */ free (dwarf); } diff --git a/libdw/dwarf_error.c b/libdw/dwarf_error.c index 89047dc..2292914 100644 --- a/libdw/dwarf_error.c +++ b/libdw/dwarf_error.c @@ -91,6 +91,7 @@ static const char *errmsgs[] = [DWARF_E_INVALID_OFFSET] = N_("invalid offset"), [DWARF_E_NO_DEBUG_RANGES] = N_(".debug_ranges section missing"), [DWARF_E_INVALID_CFI] = N_("invalid CFI section"), + [DWARF_E_NO_ALT_DEBUGLINK] = N_("no alternative debug link found"), }; #define nerrmsgs (sizeof (errmsgs) / sizeof (errmsgs[0])) diff --git a/libdw/dwarf_formref.c b/libdw/dwarf_formref.c index a2554e9..86da7ea 100644 --- a/libdw/dwarf_formref.c +++ b/libdw/dwarf_formref.c @@ -72,6 +72,8 @@ __libdw_formref (attr, return_offset) case DW_FORM_ref_addr: case DW_FORM_ref_sig8: + case DW_FORM_GNU_ref_alt: + /* These aren't handled by dwarf_formref, only by dwarf_formref_die. */ __libdw_seterrno (DWARF_E_INVALID_REFERENCE); return -1; diff --git a/libdw/dwarf_formref_die.c b/libdw/dwarf_formref_die.c index 342f6b9..f070127 100644 --- a/libdw/dwarf_formref_die.c +++ b/libdw/dwarf_formref_die.c @@ -46,7 +46,7 @@ dwarf_formref_die (attr, result) struct Dwarf_CU *cu = attr->cu; Dwarf_Off offset; - if (attr->form == DW_FORM_ref_addr) + if (attr->form == DW_FORM_ref_addr || attr->form == DW_FORM_GNU_ref_alt) { /* This has an absolute offset. */ @@ -54,11 +54,20 @@ dwarf_formref_die (attr, result) ? cu->address_size : cu->offset_size); - if (__libdw_read_offset (cu->dbg, IDX_debug_info, attr->valp, + Dwarf *dbg_ret = (attr->form == DW_FORM_GNU_ref_alt + ? cu->dbg->alt_dwarf : cu->dbg); + + if (dbg_ret == NULL) + { + __libdw_seterrno (DWARF_E_NO_ALT_DEBUGLINK); + return NULL; + } + + if (__libdw_read_offset (cu->dbg, dbg_ret, IDX_debug_info, attr->valp, ref_size, &offset, IDX_debug_info, 0)) return NULL; - return INTUSE(dwarf_offdie) (cu->dbg, offset, result); + return INTUSE(dwarf_offdie) (dbg_ret, offset, result); } Elf_Data *data; diff --git a/libdw/dwarf_formstring.c b/libdw/dwarf_formstring.c index fe2183a..c66454e 100644 --- a/libdw/dwarf_formstring.c +++ b/libdw/dwarf_formstring.c @@ -49,8 +49,17 @@ dwarf_formstring (attrp) return (const char *) attrp->valp; Dwarf *dbg = attrp->cu->dbg; + Dwarf *dbg_ret = attrp->form == DW_FORM_GNU_strp_alt ? dbg->alt_dwarf : dbg; - if (unlikely (attrp->form != DW_FORM_strp) + if (unlikely (dbg_ret == NULL)) + { + __libdw_seterrno (DWARF_E_NO_ALT_DEBUGLINK); + return NULL; + } + + + if (unlikely (attrp->form != DW_FORM_strp + && attrp->form != DW_FORM_GNU_strp_alt) || dbg->sectiondata[IDX_debug_str] == NULL) { __libdw_seterrno (DWARF_E_NO_STRING); @@ -58,10 +67,10 @@ dwarf_formstring (attrp) } uint64_t off; - if (__libdw_read_offset (dbg, cu_sec_idx (attrp->cu), attrp->valp, + if (__libdw_read_offset (dbg, dbg_ret, cu_sec_idx (attrp->cu), attrp->valp, attrp->cu->offset_size, &off, IDX_debug_str, 1)) return NULL; - return (const char *) dbg->sectiondata[IDX_debug_str]->d_buf + off; + return (const char *) dbg_ret->sectiondata[IDX_debug_str]->d_buf + off; } INTDEF(dwarf_formstring) diff --git a/libdw/dwarf_formudata.c b/libdw/dwarf_formudata.c index f08e0d8..41b09e1 100644 --- a/libdw/dwarf_formudata.c +++ b/libdw/dwarf_formudata.c @@ -52,7 +52,8 @@ __libdw_formptr (Dwarf_Attribute *attr, int sec_index, Dwarf_Word offset; if (attr->form == DW_FORM_sec_offset) { - if (__libdw_read_offset (attr->cu->dbg, cu_sec_idx (attr->cu), attr->valp, + if (__libdw_read_offset (attr->cu->dbg, attr->cu->dbg, + cu_sec_idx (attr->cu), attr->valp, attr->cu->offset_size, &offset, sec_index, 0)) return NULL; } @@ -63,7 +64,8 @@ __libdw_formptr (Dwarf_Attribute *attr, int sec_index, { case DW_FORM_data4: case DW_FORM_data8: - if (__libdw_read_offset (attr->cu->dbg, cu_sec_idx (attr->cu), + if (__libdw_read_offset (attr->cu->dbg, attr->cu->dbg, + cu_sec_idx (attr->cu), attr->valp, attr->form == DW_FORM_data4 ? 4 : 8, &offset, sec_index, 0)) diff --git a/libdw/dwarf_getpubnames.c b/libdw/dwarf_getpubnames.c index 4ea3889..12728a3 100644 --- a/libdw/dwarf_getpubnames.c +++ b/libdw/dwarf_getpubnames.c @@ -102,7 +102,8 @@ get_offsets (Dwarf *dbg) } /* Get the CU offset. */ - if (__libdw_read_offset (dbg, IDX_debug_pubnames, readp + 2, len_bytes, + if (__libdw_read_offset (dbg, dbg, IDX_debug_pubnames, + readp + 2, len_bytes, &mem[cnt].cu_offset, IDX_debug_info, 3)) /* Error has been already set in reader. */ goto err_return; diff --git a/libdw/libdwP.h b/libdw/libdwP.h index 77e1b31..da82e5d 100644 --- a/libdw/libdwP.h +++ b/libdw/libdwP.h @@ -116,6 +116,7 @@ enum DWARF_E_INVALID_OFFSET, DWARF_E_NO_DEBUG_RANGES, DWARF_E_INVALID_CFI, + DWARF_E_NO_ALT_DEBUGLINK }; @@ -127,6 +128,9 @@ struct Dwarf /* The underlying ELF file. */ Elf *elf; + /* dwz alternate DWARF file. */ + Dwarf *alt_dwarf; + /* The section data. */ Elf_Data *sectiondata[IDX_last]; @@ -141,6 +145,9 @@ struct Dwarf /* If true, we allocated the ELF descriptor ourselves. */ bool free_elf; + /* If true, we allocated the Dwarf descriptor for alt_dwarf ourselves. */ + bool free_alt; + /* Information for traversing the .debug_pubnames section. This is an array and separately allocated with malloc. */ struct pubnames_s @@ -580,13 +587,13 @@ __libdw_read_offset_inc (Dwarf *dbg, } static inline int -__libdw_read_offset (Dwarf *dbg, +__libdw_read_offset (Dwarf *dbg, Dwarf *dbg_ret, int sec_index, const unsigned char *addr, int width, Dwarf_Off *ret, int sec_ret, size_t size) { READ_AND_RELOCATE (__libdw_relocate_offset, (*ret)); - return __libdw_offset_in_section (dbg, sec_ret, *ret, size); + return __libdw_offset_in_section (dbg_ret, sec_ret, *ret, size); } static inline size_t @@ -617,12 +624,19 @@ unsigned char * __libdw_formptr (Dwarf_Attribute *attr, int sec_index, Dwarf_Off *offsetp) internal_function; +/* Checks that the build_id of the underlying Elf matches the expected. + Returns zero on match, -1 on error or no build_id found or 1 when + build_id doesn't match. */ +int __check_build_id (Dwarf *dw, const uint8_t *build_id, const size_t id_len) + internal_function; + /* Aliases to avoid PLTs. */ INTDECL (dwarf_aggregate_size) INTDECL (dwarf_attr) INTDECL (dwarf_attr_integrate) +INTDECL (dwarf_begin) INTDECL (dwarf_begin_elf) INTDECL (dwarf_child) INTDECL (dwarf_dieoffset) diff --git a/libdw/libdw_form.c b/libdw/libdw_form.c index 2ff8868..c476a6e 100644 --- a/libdw/libdw_form.c +++ b/libdw/libdw_form.c @@ -58,6 +58,8 @@ __libdw_form_val_len (Dwarf *dbg, struct Dwarf_CU *cu, unsigned int form, case DW_FORM_strp: case DW_FORM_sec_offset: + case DW_FORM_GNU_ref_alt: + case DW_FORM_GNU_strp_alt: result = cu->offset_size; break; diff --git a/src/readelf.c b/src/readelf.c index 3a27f8f..644e0f7 100644 --- a/src/readelf.c +++ b/src/readelf.c @@ -3651,6 +3651,20 @@ dwarf_form_string (unsigned int form) if (likely (form < nknown_forms)) result = known_forms[form]; + else + { + /* GNU extensions use vendor numbers. */ + switch (form) + { + case DW_FORM_GNU_ref_alt: + result = "GNU_ref_alt"; + break; + + case DW_FORM_GNU_strp_alt: + result = "GNU_strp_alt"; + break; + } + } if (unlikely (result == NULL)) { @@ -5593,6 +5607,7 @@ attr_callback (Dwarf_Attribute *attrp, void *arg) case DW_FORM_indirect: case DW_FORM_strp: case DW_FORM_string: + case DW_FORM_GNU_strp_alt: if (cbargs->silent) break; const char *str = dwarf_formstring (attrp); @@ -5608,7 +5623,8 @@ attr_callback (Dwarf_Attribute *attrp, void *arg) case DW_FORM_ref8: case DW_FORM_ref4: case DW_FORM_ref2: - case DW_FORM_ref1:; + case DW_FORM_ref1: + case DW_FORM_GNU_ref_alt: if (cbargs->silent) break; Dwarf_Die ref;