475 lines
14 KiB
Diff
475 lines
14 KiB
Diff
https://lists.fedorahosted.org/pipermail/elfutils-devel/2012-July/002418.html
|
|
|
|
From b1e42797293bcf34385d5cb0a18e8c773279241b Mon Sep 17 00:00:00 2001
|
|
From: Mark Wielaard <mjw@redhat.com>
|
|
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 <mjw@redhat.com>
|
|
|
|
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 <config.h>
|
|
#endif
|
|
|
|
+#include <assert.h>
|
|
+#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
+#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
+#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
+#include <fcntl.h>
|
|
|
|
#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;
|