summaryrefslogtreecommitdiff
path: root/libdwfl
diff options
context:
space:
mode:
authorRoland McGrath <roland@redhat.com>2011-02-18 15:38:37 -0800
committerRoland McGrath <roland@redhat.com>2011-02-18 15:38:37 -0800
commite5c1b229023a5c97d6f37c0bc6c26c4357fa4359 (patch)
treebf5b4df40e7195642b879f76ee5ae5d62229efcc /libdwfl
parent2167f54e91fec095b5dd443432687e1d253033dd (diff)
parent170ecbd2b0578aa9b9408432dbca1fada0f9d80e (diff)
downloadelfutils-e5c1b229023a5c97d6f37c0bc6c26c4357fa4359.tar.gz
Merge branch 'master' into roland/relocate
Diffstat (limited to 'libdwfl')
-rw-r--r--libdwfl/ChangeLog24
-rw-r--r--libdwfl/dwfl_module_getdwarf.c335
-rw-r--r--libdwfl/libdwflP.h3
-rw-r--r--libdwfl/linux-kernel-modules.c46
4 files changed, 337 insertions, 71 deletions
diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog
index 26e6caf0..715849f2 100644
--- a/libdwfl/ChangeLog
+++ b/libdwfl/ChangeLog
@@ -1,3 +1,27 @@
+2011-02-11 Roland McGrath <roland@redhat.com>
+
+ * linux-kernel-modules.c (try_kernel_name): Try .gz, .bz2, .xz
+ suffixes if corresponding decompression support is enabled.
+
+2011-02-01 Roland McGrath <roland@redhat.com>
+
+ * dwfl_module_getdwarf.c (find_prelink_address_sync): Use the
+ section-end address as the synchronization point, rather than sh_addr.
+
+ * dwfl_module_getdwarf.c (find_prelink_address_sync): Discover
+ PT_INTERP p_vaddr separately from main phdrs and undo phdrs.
+
+ * dwfl_module_getdwarf.c (find_prelink_address_sync): Fix pasto in
+ last change, so we recognize PT_INTERP in ELFCLASS64 correctly.
+
+2011-01-11 Roland McGrath <roland@redhat.com>
+
+ * dwfl_module_getdwarf.c (open_elf): Remove section-based
+ address_sync fixup from here.
+ (find_prelink_address_sync): New function.
+ (find_debuginfo): Call it.
+ * libdwflP.h (DWFL_ERRORS): Add BAD_PRELINK error.
+
2011-01-04 Roland McGrath <roland@redhat.com>
* dwfl_module_getdwarf.c (open_elf): Enhance address_sync calculation
diff --git a/libdwfl/dwfl_module_getdwarf.c b/libdwfl/dwfl_module_getdwarf.c
index ffcc2328..926d41e4 100644
--- a/libdwfl/dwfl_module_getdwarf.c
+++ b/libdwfl/dwfl_module_getdwarf.c
@@ -55,7 +55,7 @@
/* Open libelf FILE->fd and compute the load base of ELF as loaded in MOD.
- When we return success, FILE->elf and FILE->bias are set up. */
+ When we return success, FILE->elf and FILE->vaddr are set up. */
static inline Dwfl_Error
open_elf (Dwfl_Module *mod, struct dwfl_file *file)
{
@@ -122,34 +122,18 @@ open_elf (Dwfl_Module *mod, struct dwfl_file *file)
something different. There it juggles the "special" sections
(SHT_DYNSYM et al) to make space for the additional prelink
special sections. Sometimes it will do this by moving a special
- section like .dynstr after the real program sections in the
- first PT_LOAD segment--i.e. to the end. That changes the end
- address of the segment, so it no longer lines up correctly and
- is not a valid synchronization address to use.
-
- So, instead we use a method based on the section headers. We
- look at the allocated SHT_PROGBITS (or SHT_NOBITS) sections.
- (Most every file will have some SHT_PROGBITS sections, but it's
- possible to have one with nothing but .bss, i.e. SHT_NOBITS.)
- The special sections that can be moved around have different
- sh_type values--except for .interp, the section that became the
- PT_INTERP segment. So we exclude the SHT_PROGBITS section whose
- address matches the PT_INTERP p_vaddr.
-
- Since the debug file will always have section headers, we must
- choose a method of examining section headers that will also line
- up with the end of the first PT_LOAD segment, in case the main
- file was fully stripped so we are synchronizing between a
- PT_LOAD-based and a section-based calculation. To that end, we
- use the highest section end address that lies inside the first
- segment. If none does, then we use the highest end address of
- any non-special section. */
+ section like .dynstr after the real program sections in the first
+ PT_LOAD segment--i.e. to the end. That changes the end address of
+ the segment, so it no longer lines up correctly and is not a valid
+ synchronization address to use. Because of this, we need to apply
+ a different prelink-savvy means to discover the synchronization
+ address when there is a separate debug file and a prelinked main
+ file. That is done in find_debuginfo, below. */
size_t phnum;
if (unlikely (elf_getphdrnum (file->elf, &phnum) != 0))
goto elf_error;
- GElf_Addr interp = 0;
file->vaddr = file->address_sync = 0;
for (size_t i = 0; i < phnum; ++i)
{
@@ -157,52 +141,12 @@ open_elf (Dwfl_Module *mod, struct dwfl_file *file)
GElf_Phdr *ph = gelf_getphdr (file->elf, i, &ph_mem);
if (unlikely (ph == NULL))
goto elf_error;
- switch (ph->p_type)
+ if (ph->p_type == PT_LOAD)
{
- case PT_INTERP:
- interp = ph->p_vaddr;
+ file->vaddr = ph->p_vaddr & -ph->p_align;
+ file->address_sync = ph->p_vaddr + ph->p_memsz;
break;
- case PT_LOAD:
- if (file->address_sync == 0)
- {
- file->vaddr = ph->p_vaddr & -ph->p_align;
- file->address_sync = ph->p_vaddr + ph->p_memsz;
- }
- break;
- default:
- continue;
- }
- if (interp != 0 && file->address_sync != 0)
- break;
- }
-
- if (file->address_sync != 0)
- {
- GElf_Addr highest_end = 0;
- GElf_Addr highest_end_in_seg = 0;
- Elf_Scn *scn = NULL;
- while ((scn = elf_nextscn (file->elf, scn)) != NULL)
- {
- GElf_Shdr sh_mem;
- GElf_Shdr *sh = gelf_getshdr (scn, &sh_mem);
- if (unlikely (sh == NULL))
- goto elf_error;
- if ((sh->sh_flags & SHF_ALLOC)
- && ((sh->sh_type == SHT_PROGBITS && sh->sh_addr != interp)
- || sh->sh_type == SHT_NOBITS))
- {
- const GElf_Addr sh_end = sh->sh_addr + sh->sh_size;
- if (sh_end > highest_end)
- highest_end = sh_end;
- if (sh_end <= file->address_sync
- && sh_end > highest_end_in_seg)
- highest_end_in_seg = sh_end;
- }
}
- if (highest_end_in_seg >= file->vaddr && highest_end_in_seg != 0)
- file->address_sync = highest_end_in_seg;
- else if (highest_end != 0)
- file->address_sync = highest_end;
}
}
@@ -341,6 +285,258 @@ find_debuglink (Elf *elf, GElf_Word *crc)
return rawdata->d_buf;
}
+/* If the main file might have been prelinked, then we need to
+ discover the correct synchronization address between the main and
+ debug files. Because of prelink's section juggling, we cannot rely
+ on the address_sync computed from PT_LOAD segments (see open_elf).
+
+ We will attempt to discover a synchronization address based on the
+ section headers instead. But finding a section address that is
+ safe to use requires identifying which sections are SHT_PROGBITS.
+ We can do that in the main file, but in the debug file all the
+ allocated sections have been transformed into SHT_NOBITS so we have
+ lost the means to match them up correctly.
+
+ The only method left to us is to decode the .gnu.prelink_undo
+ section in the prelinked main file. This shows what the sections
+ looked like before prelink juggled them--when they still had a
+ direct correspondence to the debug file. */
+static Dwfl_Error
+find_prelink_address_sync (Dwfl_Module *mod)
+{
+ /* The magic section is only identified by name. */
+ size_t shstrndx;
+ if (elf_getshdrstrndx (mod->main.elf, &shstrndx) < 0)
+ return DWFL_E_LIBELF;
+
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (mod->main.elf, scn)) != NULL)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ if (unlikely (shdr == NULL))
+ return DWFL_E_LIBELF;
+ if (shdr->sh_type == SHT_PROGBITS
+ && !(shdr->sh_flags & SHF_ALLOC)
+ && shdr->sh_name != 0)
+ {
+ const char *secname = elf_strptr (mod->main.elf, shstrndx,
+ shdr->sh_name);
+ if (unlikely (secname == NULL))
+ return DWFL_E_LIBELF;
+ if (!strcmp (secname, ".gnu.prelink_undo"))
+ break;
+ }
+ }
+
+ if (scn == NULL)
+ /* There was no .gnu.prelink_undo section. */
+ return DWFL_E_NOERROR;
+
+ Elf_Data *undodata = elf_rawdata (scn, NULL);
+ if (unlikely (undodata == NULL))
+ return DWFL_E_LIBELF;
+
+ /* Decode the section. It consists of the original ehdr, phdrs,
+ and shdrs (but omits section 0). */
+
+ union
+ {
+ Elf32_Ehdr e32;
+ Elf64_Ehdr e64;
+ } ehdr;
+ Elf_Data dst =
+ {
+ .d_buf = &ehdr,
+ .d_size = sizeof ehdr,
+ .d_type = ELF_T_EHDR,
+ .d_version = EV_CURRENT
+ };
+ Elf_Data src = *undodata;
+ src.d_size = gelf_fsize (mod->main.elf, ELF_T_EHDR, 1, EV_CURRENT);
+ src.d_type = ELF_T_EHDR;
+ if (unlikely (gelf_xlatetom (mod->main.elf, &dst, &src,
+ elf_getident (mod->main.elf, NULL)[EI_DATA])
+ == NULL))
+ return DWFL_E_LIBELF;
+
+ size_t shentsize = gelf_fsize (mod->main.elf, ELF_T_SHDR, 1, EV_CURRENT);
+ size_t phentsize = gelf_fsize (mod->main.elf, ELF_T_PHDR, 1, EV_CURRENT);
+
+ uint_fast16_t phnum;
+ uint_fast16_t shnum;
+ if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32)
+ {
+ if (ehdr.e32.e_shentsize != shentsize
+ || ehdr.e32.e_phentsize != phentsize)
+ return DWFL_E_BAD_PRELINK;
+ phnum = ehdr.e32.e_phnum;
+ shnum = ehdr.e32.e_shnum;
+ }
+ else
+ {
+ if (ehdr.e64.e_shentsize != shentsize
+ || ehdr.e64.e_phentsize != phentsize)
+ return DWFL_E_BAD_PRELINK;
+ phnum = ehdr.e64.e_phnum;
+ shnum = ehdr.e64.e_shnum;
+ }
+
+ /* Since prelink does not store the zeroth section header in the undo
+ section, it cannot support SHN_XINDEX encoding. */
+ if (unlikely (shnum >= SHN_LORESERVE)
+ || unlikely (undodata->d_size != (src.d_size
+ + phnum * phentsize
+ + (shnum - 1) * shentsize)))
+ return DWFL_E_BAD_PRELINK;
+
+ /* We look at the allocated SHT_PROGBITS (or SHT_NOBITS) sections. (Most
+ every file will have some SHT_PROGBITS sections, but it's possible to
+ have one with nothing but .bss, i.e. SHT_NOBITS.) The special sections
+ that can be moved around have different sh_type values--except for
+ .interp, the section that became the PT_INTERP segment. So we exclude
+ the SHT_PROGBITS section whose address matches the PT_INTERP p_vaddr.
+ For this reason, we must examine the phdrs first to find PT_INTERP. */
+
+ GElf_Addr main_interp = 0;
+ {
+ size_t main_phnum;
+ if (unlikely (elf_getphdrnum (mod->main.elf, &main_phnum)))
+ return DWFL_E_LIBELF;
+ for (size_t i = 0; i < main_phnum; ++i)
+ {
+ GElf_Phdr phdr;
+ if (unlikely (gelf_getphdr (mod->main.elf, i, &phdr) == NULL))
+ return DWFL_E_LIBELF;
+ if (phdr.p_type == PT_INTERP)
+ {
+ main_interp = phdr.p_vaddr;
+ break;
+ }
+ }
+ }
+
+ src.d_buf += src.d_size;
+ src.d_type = ELF_T_PHDR;
+ src.d_size = phnum * phentsize;
+
+ GElf_Addr undo_interp = 0;
+ {
+ union
+ {
+ Elf32_Phdr p32[phnum];
+ Elf64_Phdr p64[phnum];
+ } phdr;
+ dst.d_buf = &phdr;
+ dst.d_size = sizeof phdr;
+ if (unlikely (gelf_xlatetom (mod->main.elf, &dst, &src,
+ ehdr.e32.e_ident[EI_DATA]) == NULL))
+ return DWFL_E_LIBELF;
+ if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32)
+ {
+ for (uint_fast16_t i = 0; i < phnum; ++i)
+ if (phdr.p32[i].p_type == PT_INTERP)
+ {
+ undo_interp = phdr.p32[i].p_vaddr;
+ break;
+ }
+ }
+ else
+ {
+ for (uint_fast16_t i = 0; i < phnum; ++i)
+ if (phdr.p64[i].p_type == PT_INTERP)
+ {
+ undo_interp = phdr.p64[i].p_vaddr;
+ break;
+ }
+ }
+ }
+
+ if (unlikely ((main_interp == 0) != (undo_interp == 0)))
+ return DWFL_E_BAD_PRELINK;
+
+ src.d_buf += src.d_size;
+ src.d_type = ELF_T_SHDR;
+ src.d_size = gelf_fsize (mod->main.elf, ELF_T_SHDR, shnum - 1, EV_CURRENT);
+
+ union
+ {
+ Elf32_Shdr s32[shnum - 1];
+ Elf64_Shdr s64[shnum - 1];
+ } shdr;
+ dst.d_buf = &shdr;
+ dst.d_size = sizeof shdr;
+ if (unlikely (gelf_xlatetom (mod->main.elf, &dst, &src,
+ ehdr.e32.e_ident[EI_DATA]) == NULL))
+ return DWFL_E_LIBELF;
+
+ /* Now we can look at the original section headers of the main file
+ before it was prelinked. First we'll apply our method to the main
+ file sections as they are after prelinking, to calculate the
+ synchronization address of the main file. Then we'll apply that
+ same method to the saved section headers, to calculate the matching
+ synchronization address of the debug file.
+
+ The method is to consider SHF_ALLOC sections that are either
+ SHT_PROGBITS or SHT_NOBITS, excluding the section whose sh_addr
+ matches the PT_INTERP p_vaddr. The special sections that can be
+ moved by prelink have other types, except for .interp (which
+ becomes PT_INTERP). The "real" sections cannot move as such, but
+ .bss can be split into .dynbss and .bss, with the total memory
+ image remaining the same but being spread across the two sections.
+ So we consider the highest section end, which still matches up. */
+
+ GElf_Addr highest;
+
+ inline void consider_shdr (GElf_Addr interp,
+ GElf_Word sh_type,
+ GElf_Xword sh_flags,
+ GElf_Addr sh_addr,
+ GElf_Xword sh_size)
+ {
+ if ((sh_flags & SHF_ALLOC)
+ && ((sh_type == SHT_PROGBITS && sh_addr != interp)
+ || sh_type == SHT_NOBITS))
+ {
+ const GElf_Addr sh_end = sh_addr + sh_size;
+ if (sh_end > highest)
+ highest = sh_end;
+ }
+ }
+
+ highest = 0;
+ scn = NULL;
+ while ((scn = elf_nextscn (mod->main.elf, scn)) != NULL)
+ {
+ GElf_Shdr sh_mem;
+ GElf_Shdr *sh = gelf_getshdr (scn, &sh_mem);
+ if (unlikely (sh == NULL))
+ return DWFL_E_LIBELF;
+ consider_shdr (main_interp, sh->sh_type, sh->sh_flags,
+ sh->sh_addr, sh->sh_size);
+ }
+ if (highest > mod->main.vaddr)
+ {
+ mod->main.address_sync = highest;
+
+ highest = 0;
+ if (ehdr.e32.e_ident[EI_CLASS] == ELFCLASS32)
+ for (size_t i = 0; i < shnum - 1; ++i)
+ consider_shdr (undo_interp, shdr.s32[i].sh_type, shdr.s32[i].sh_flags,
+ shdr.s32[i].sh_addr, shdr.s32[i].sh_size);
+ else
+ for (size_t i = 0; i < shnum - 1; ++i)
+ consider_shdr (undo_interp, shdr.s64[i].sh_type, shdr.s64[i].sh_flags,
+ shdr.s64[i].sh_addr, shdr.s64[i].sh_size);
+
+ if (highest > mod->debug.vaddr)
+ mod->debug.address_sync = highest;
+ else
+ return DWFL_E_BAD_PRELINK;
+ }
+
+ return DWFL_E_NOERROR;
+}
/* Find the separate debuginfo file for this module and open libelf on it.
When we return success, MOD->debug is set up. */
@@ -358,7 +554,10 @@ find_debuginfo (Dwfl_Module *mod)
debuglink_file,
debuglink_crc,
&mod->debug.name);
- return open_elf (mod, &mod->debug);
+ Dwfl_Error result = open_elf (mod, &mod->debug);
+ if (result == DWFL_E_NOERROR && mod->debug.address_sync != 0)
+ result = find_prelink_address_sync (mod);
+ return result;
}
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index abab3561..5537fe33 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -94,7 +94,8 @@
DWFL_ERROR (ALREADY_ELF, N_("ELF file opened")) \
DWFL_ERROR (BADELF, N_("not a valid ELF file")) \
DWFL_ERROR (WEIRD_TYPE, N_("cannot handle DWARF type description")) \
- DWFL_ERROR (WRONG_ID_ELF, N_("ELF file does not match build ID"))
+ DWFL_ERROR (WRONG_ID_ELF, N_("ELF file does not match build ID")) \
+ DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data"))
#define DWFL_ERROR(name, text) DWFL_E_##name,
typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error;
diff --git a/libdwfl/linux-kernel-modules.c b/libdwfl/linux-kernel-modules.c
index 2479292a..f3d9af10 100644
--- a/libdwfl/linux-kernel-modules.c
+++ b/libdwfl/linux-kernel-modules.c
@@ -1,5 +1,5 @@
/* Standard libdwfl callbacks for debugging the running Linux kernel.
- Copyright (C) 2005-2010 Red Hat, Inc.
+ Copyright (C) 2005-2011 Red Hat, Inc.
This file is part of Red Hat elfutils.
Red Hat elfutils is free software; you can redistribute it and/or modify
@@ -78,6 +78,19 @@
#define MODULE_SECT_NAME_LEN 32 /* Minimum any linux/module.h has had. */
+static const char *vmlinux_suffixes[] =
+ {
+#ifdef USE_ZLIB
+ ".gz",
+#endif
+#ifdef USE_BZLIB
+ ".bz2",
+#endif
+#ifdef USE_LZMA
+ ".xz",
+#endif
+ };
+
/* Try to open the given file as it is or under the debuginfo directory. */
static int
try_kernel_name (Dwfl *dwfl, char **fname, bool try_debug)
@@ -91,6 +104,7 @@ try_kernel_name (Dwfl *dwfl, char **fname, bool try_debug)
? *dwfl->callbacks->debuginfo_path : NULL)
?: DEFAULT_DEBUGINFO_PATH)[0] == ':') ? -1
: TEMP_FAILURE_RETRY (open64 (*fname, O_RDONLY)));
+
if (fd < 0)
{
char *debugfname = NULL;
@@ -106,8 +120,36 @@ try_kernel_name (Dwfl *dwfl, char **fname, bool try_debug)
fd = INTUSE(dwfl_standard_find_debuginfo) (&fakemod, NULL, NULL, 0,
*fname, NULL, 0,
&debugfname);
+ if (debugfname != NULL)
+ {
+ free (*fname);
+ *fname = debugfname;
+ }
+ }
+
+ if (fd < 0)
+ for (size_t i = 0;
+ i < sizeof vmlinux_suffixes / sizeof vmlinux_suffixes[0];
+ ++i)
+ {
+ char *zname;
+ if (asprintf (&zname, "%s%s", *fname, vmlinux_suffixes[i]) > 0)
+ {
+ fd = TEMP_FAILURE_RETRY (open64 (zname, O_RDONLY));
+ if (fd < 0)
+ free (zname);
+ else
+ {
+ free (*fname);
+ *fname = zname;
+ }
+ }
+ }
+
+ if (fd < 0)
+ {
free (*fname);
- *fname = debugfname;
+ *fname = NULL;
}
return fd;