diff options
author | Roland McGrath <roland@redhat.com> | 2011-02-18 15:38:37 -0800 |
---|---|---|
committer | Roland McGrath <roland@redhat.com> | 2011-02-18 15:38:37 -0800 |
commit | e5c1b229023a5c97d6f37c0bc6c26c4357fa4359 (patch) | |
tree | bf5b4df40e7195642b879f76ee5ae5d62229efcc /libdwfl | |
parent | 2167f54e91fec095b5dd443432687e1d253033dd (diff) | |
parent | 170ecbd2b0578aa9b9408432dbca1fada0f9d80e (diff) | |
download | elfutils-e5c1b229023a5c97d6f37c0bc6c26c4357fa4359.tar.gz |
Merge branch 'master' into roland/relocate
Diffstat (limited to 'libdwfl')
-rw-r--r-- | libdwfl/ChangeLog | 24 | ||||
-rw-r--r-- | libdwfl/dwfl_module_getdwarf.c | 335 | ||||
-rw-r--r-- | libdwfl/libdwflP.h | 3 | ||||
-rw-r--r-- | libdwfl/linux-kernel-modules.c | 46 |
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; |