summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Wielaard <mjw@redhat.com>2012-06-22 12:02:45 +0200
committerMark Wielaard <mjw@redhat.com>2012-08-09 16:32:43 +0200
commit775375e3bd177cb19acb5020b6e0917551807276 (patch)
tree26ec5e40f426d5ace9fd636818197cd0025b672a
parent836db608432ba50f76c0a63ec7bd05694341a1d4 (diff)
downloadandroid_external_elfutils-775375e3bd177cb19acb5020b6e0917551807276.tar.gz
android_external_elfutils-775375e3bd177cb19acb5020b6e0917551807276.tar.bz2
android_external_elfutils-775375e3bd177cb19acb5020b6e0917551807276.zip
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>
-rw-r--r--libdw/ChangeLog27
-rw-r--r--libdw/dwarf.h5
-rw-r--r--libdw/dwarf_begin.c1
-rw-r--r--libdw/dwarf_begin_elf.c123
-rw-r--r--libdw/dwarf_end.c4
-rw-r--r--libdw/dwarf_error.c1
-rw-r--r--libdw/dwarf_formref.c2
-rw-r--r--libdw/dwarf_formref_die.c15
-rw-r--r--libdw/dwarf_formstring.c15
-rw-r--r--libdw/dwarf_formudata.c6
-rw-r--r--libdw/dwarf_getpubnames.c3
-rw-r--r--libdw/libdwP.h18
-rw-r--r--libdw/libdw_form.c2
-rw-r--r--src/ChangeLog6
-rw-r--r--src/readelf.c18
-rw-r--r--tests/ChangeLog11
-rw-r--r--tests/Makefile.am4
-rwxr-xr-xtests/libtestfile_multi_shared.so.bz2bin0 -> 2547 bytes
-rwxr-xr-xtests/run-readelf-dwz-multi.sh156
-rw-r--r--tests/testfile_multi.dwz.bz2bin0 -> 512 bytes
-rwxr-xr-xtests/testfile_multi_main.bz2bin0 -> 3086 bytes
21 files changed, 403 insertions, 14 deletions
diff --git a/libdw/ChangeLog b/libdw/ChangeLog
index 2bafdc22..572252da 100644
--- a/libdw/ChangeLog
+++ b/libdw/ChangeLog
@@ -1,3 +1,30 @@
+2012-06-27 Mark Wielaard <mjw@redhat.com>
+
+ * dwarf.h: Add DW_FORM_GNU_ref_alt and DW_FORM_GNU_strp_alt.
+ * dwarf_begin.c (dwarf_begin): Add INTDEF.
+ * dwarf_begin_elf.c (__check_build_id): New internal_function.
+ (try_debugaltlink): New function.
+ (open_debugaltlink): Likewise.
+ (check_section): Try open_debugaltlink for .gnu_debugaltlink.
+ * dwarf_end.c (dwarf_end): Free the alternative Dwarf descriptor if
+ necessary.
+ * dwarf_error.c (errmsgs): Add DWARF_E_NO_ALT_DEBUGLINK.
+ * dwarf_formref.c (__libdw_formref): Using DW_FORM_GNU_ref_alt
+ is an error here.
+ * dwarf_formref_die.c (dwarf_formref_die): Handle DW_FORM_GNU_ref_alt.
+ * dwarf_formstring.c (dwarf_formstring): Handle DW_FORM_GNU_strp_alt.
+ * dwarf_formudata.c (__libdw_formptr): Adjust __libdw_read_offset
+ calls.
+ * dwarf_getpubnames.c (get_offsets): Adjust __libdw_read_offset call.
+ * libdwP.h: Add DWARF_E_NO_ALT_DEBUGLINK.
+ (struct Dwarf): Add alt_dwarf and free_alt fields.
+ (__libdw_read_offset): Add dbg_ret argument, use to check with
+ __libdw_offset_in_section.
+ (__check_build_id): New function declaration.
+ (dwarf_begin): Define as INTDECL.
+ * libdw_form.c (__libdw_form_val_len): Handle DW_FORM_GNU_ref_alt
+ and DW_FORM_GNU_strp_alt.
+
2012-07-19 Mark Wielaard <mjw@redhat.com>
* dwarf.h: Add DW_OP_GNU_parameter_ref.
diff --git a/libdw/dwarf.h b/libdw/dwarf.h
index f41d2960..81bc7fe5 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 1f3fc3b9..9f3050fd 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 3e01800a..fd957701 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 b77988f7..e65314ab 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 89047dcd..22929141 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 a2554e91..86da7eae 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 342f6b9e..f0701270 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 fe2183a2..c66454e1 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 f08e0d8f..41b09e1a 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 4ea38896..12728a34 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 77e1b31f..da82e5d2 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 2ff8868b..c476a6e3 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/ChangeLog b/src/ChangeLog
index d05bb2e3..294e31b6 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,9 @@
+2012-06-27 Mark Wielaard <mjw@redhat.com>
+
+ * readelf.c (dwarf_form_string): Handle DW_FORM_GNU_ref_alt and
+ DW_FORM_GNU_strp_alt.
+ (attr_callback): Likewise.
+
2012-07-30 Petr Machata <pmachata@redhat.com>
* nm.c (show_symbols_bsd): Reorder arguments in {S,}FMTSTRS (and
diff --git a/src/readelf.c b/src/readelf.c
index 9aaf4ece..36724a71 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;
diff --git a/tests/ChangeLog b/tests/ChangeLog
index 2291b62b..1cdd4646 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -1,3 +1,14 @@
+2012-06-27 Mark Wielaard <mjw@redhat.com>
+
+ * Makefile.am (TESTS): Add run-readelf-dwz-multi.sh.
+ (EXTRA_DIST): Add run-readelf-dwz-multi.sh,
+ libtestfile_multi_shared.so.bz2, testfile_multi.dwz.bz2 and
+ testfile_multi_main.bz2.
+ * run-readelf-dwz-multi.sh: New test.
+ * libtestfile_multi_shared.so.bz2: New testfile.
+ * testfile_multi.dwz.bz2: New testifle.
+ * testfile_multi_main.bz2: New testifle.
+
2012-08-01 Petr Machata <pmachata@redhat.com>
* run-test-archive64.sh: New test.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d0f4e80a..df66db6e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -73,7 +73,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
run-nm-self.sh run-readelf-self.sh \
run-readelf-test1.sh run-readelf-test2.sh run-readelf-test3.sh \
run-readelf-test4.sh run-readelf-twofiles.sh \
- run-readelf-macro.sh \
+ run-readelf-macro.sh run-readelf-dwz-multi.sh \
run-native-test.sh run-bug1-test.sh \
dwfl-bug-addr-overflow run-addrname-test.sh \
dwfl-bug-fd-leak dwfl-bug-report \
@@ -145,6 +145,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
testfile46.bz2 testfile47.bz2 testfile48.bz2 testfile48.debug.bz2 \
testfile49.bz2 testfile50.bz2 testfile51.bz2 \
run-readelf-macro.sh testfilemacro.bz2 \
+ run-readelf-dwz-multi.sh libtestfile_multi_shared.so.bz2 \
+ testfile_multi.dwz.bz2 testfile_multi_main.bz2 \
run-prelink-addr-test.sh \
testfile52-32.so.bz2 testfile52-32.so.debug.bz2 \
testfile52-32.prelink.so.bz2 testfile52-32.noshdrs.so.bz2 \
diff --git a/tests/libtestfile_multi_shared.so.bz2 b/tests/libtestfile_multi_shared.so.bz2
new file mode 100755
index 00000000..e9eb6a70
--- /dev/null
+++ b/tests/libtestfile_multi_shared.so.bz2
Binary files differ
diff --git a/tests/run-readelf-dwz-multi.sh b/tests/run-readelf-dwz-multi.sh
new file mode 100755
index 00000000..42e6aa4c
--- /dev/null
+++ b/tests/run-readelf-dwz-multi.sh
@@ -0,0 +1,156 @@
+#! /bin/sh
+# Copyright (C) 2012 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+# common.h
+#
+# #include <stdio.h>
+#
+# struct foobar
+# {
+# int foo;
+# struct foobar *bar;
+# };
+#
+# extern int call_foo(struct foobar *foobar_struct_ptr);
+
+# main.c
+#
+# #include "common.h"
+#
+# int main(int argc, char ** argv)
+# {
+# struct foobar b;
+# b.foo = 42;
+# b.bar = &b;
+#
+# return call_foo(b.bar);
+# }
+
+# shared.c
+#
+# #include "common.h"
+#
+# int call_foo(struct foobar *fb)
+# {
+# return fb->bar->foo - 42;
+# }
+
+# gcc -fPIC -g -c -Wall shared.c
+# gcc -shared -o libtestfile_multi_shared.so shared.o
+# gcc -g -o testfile_multi_main -L. -ltestfile_multi_shared main.c -Wl,-rpath,.
+# dwz -m testfile_multi.dwz testfile_multi_main libtestfile_multi_shared.so
+
+testfiles libtestfile_multi_shared.so testfile_multi_main testfile_multi.dwz
+
+testrun_compare ../src/readelf --debug-dump=info testfile_multi_main <<\EOF
+
+DWARF section [28] '.debug_info' at offset 0x1078:
+ [Offset]
+ Compilation unit at offset 0:
+ Version: 4, Abbreviation section offset: 0, Address size: 8, Offset size: 4
+ [ b] compile_unit
+ producer (strp) "GNU C 4.7.0 20120507 (Red Hat 4.7.0-5) -mtune=generic -march=x86-64 -g"
+ language (data1) ISO C89 (1)
+ name (strp) "main.c"
+ comp_dir (GNU_strp_alt) "/home/mark/src/tests/dwz"
+ low_pc (addr) 0x00000000004006ac <main>
+ high_pc (udata) 44
+ stmt_list (sec_offset) 0
+ [ 26] imported_unit
+ import (GNU_ref_alt) [ b]
+ [ 2b] pointer_type
+ byte_size (data1) 8
+ type (GNU_ref_alt) [ 53]
+ [ 31] subprogram
+ external (flag_present)
+ name (strp) "main"
+ decl_file (data1) 1
+ decl_line (data1) 3
+ prototyped (flag_present)
+ type (GNU_ref_alt) [ 3e]
+ low_pc (addr) 0x00000000004006ac <main>
+ high_pc (udata) 44
+ frame_base (exprloc)
+ [ 0] call_frame_cfa
+ GNU_all_tail_call_sites (flag_present)
+ sibling (ref_udata) [ 6e]
+ [ 48] formal_parameter
+ name (strp) "argc"
+ decl_file (data1) 1
+ decl_line (data1) 3
+ type (GNU_ref_alt) [ 3e]
+ location (exprloc)
+ [ 0] fbreg -36
+ [ 56] formal_parameter
+ name (strp) "argv"
+ decl_file (data1) 1
+ decl_line (data1) 3
+ type (ref_udata) [ 6e]
+ location (exprloc)
+ [ 0] fbreg -48
+ [ 61] variable
+ name (string) "b"
+ decl_file (data1) 1
+ decl_line (data1) 5
+ type (GNU_ref_alt) [ 5a]
+ location (exprloc)
+ [ 0] fbreg -32
+ [ 6e] pointer_type
+ byte_size (data1) 8
+ type (ref_udata) [ 2b]
+EOF
+
+testrun_compare ../src/readelf --debug-dump=info libtestfile_multi_shared.so <<\EOF
+
+DWARF section [25] '.debug_info' at offset 0x106c:
+ [Offset]
+ Compilation unit at offset 0:
+ Version: 4, Abbreviation section offset: 0, Address size: 8, Offset size: 4
+ [ b] compile_unit
+ producer (strp) "GNU C 4.7.0 20120507 (Red Hat 4.7.0-5) -fpreprocessed -mtune=generic -march=x86-64 -g -fPIC"
+ language (data1) ISO C89 (1)
+ name (strp) "shared.c"
+ comp_dir (GNU_strp_alt) "/home/mark/src/tests/dwz"
+ low_pc (addr) +0x0000000000000670 <call_foo>
+ high_pc (udata) 23
+ stmt_list (sec_offset) 0
+ [ 26] imported_unit
+ import (GNU_ref_alt) [ b]
+ [ 2b] subprogram
+ external (flag_present)
+ name (strp) "call_foo"
+ decl_file (data1) 1
+ decl_line (data1) 3
+ prototyped (flag_present)
+ type (GNU_ref_alt) [ 3e]
+ low_pc (addr) +0x0000000000000670 <call_foo>
+ high_pc (udata) 23
+ frame_base (exprloc)
+ [ 0] call_frame_cfa
+ GNU_all_call_sites (flag_present)
+ [ 41] formal_parameter
+ name (string) "fb"
+ decl_file (data1) 1
+ decl_line (data1) 3
+ type (GNU_ref_alt) [ 76]
+ location (exprloc)
+ [ 0] fbreg -24
+EOF
+
+exit 0
diff --git a/tests/testfile_multi.dwz.bz2 b/tests/testfile_multi.dwz.bz2
new file mode 100644
index 00000000..1f52fb69
--- /dev/null
+++ b/tests/testfile_multi.dwz.bz2
Binary files differ
diff --git a/tests/testfile_multi_main.bz2 b/tests/testfile_multi_main.bz2
new file mode 100755
index 00000000..bc6ca5fd
--- /dev/null
+++ b/tests/testfile_multi_main.bz2
Binary files differ