Hi,
jankratochvil/move-core
New testcase: run-addrname-test.sh -> libfunc+0x9
a follow-up to: Re: [commit] [patch] Fix dwfl_report_elf BASE alignment [Re: pending patches ping#2] https://lists.fedorahosted.org/pipermail/elfutils-devel/2013-April/003015.ht... Message-ID: 20130401192624.GA17190@host2.jankratochvil.net
With my former patches I improved some cases and regressed different cases, so with this patch it should behave better in general - but not in 100% cases. The former code using ELF headers from segments could find more modules but their proper address was only a luck, sometimes wrong, as proven in the mail above ELF.
"Fix loading core files without build-ids" - with build-id header the ELF headers from segments method almost always works, it has more wrong results when build-ids are missing, therefore even the ELF headers are missing.
elfutils was always fragile by searching for ELF headers in core files segments first /* Now sniff segment contents for modules. */ and only then trying to adjust them from libc link_map list. /* Next, we should follow the chain from DT_DEBUG. */ + /* If content-sniffing already reported a module covering the same area, find that existing module to adjust. The l_ld address is the only one we know for sure to be within the module's own segments (its .dynamic). */
But that adjustment currently does not work as it uses Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (dwfl, l_ld); but if the module is placed incorrectly one cannot find the proper module by its address -- by its placement.
IMO one should follow link_map first and fall back to the ELF headers only if it fails.
Not sure if one should try to map address-non-conflicting files from ELF headers, this patch does not do it. (That could better fit case (2) below.)
There are IMO two basic use cases with different pros/cons:
(1) Map address -> symbol: eu-addr2line -S --core=xxx 0xaddress Exact modules placement is needed and it is better to report no symbol than a wrong symbol.
(2) I want to know all build-ids: eu-unstrip -n --core=xxx Modules placement does not matter, excessive output does not matter, I need to only install needed debuginfos.
This change will behave better in (1) but it may regress (2), for example in cases where link_map list is overwritten/corrupted in its middle.
I admit I do not fully understand how the current raw segments ELF headers reporting code worked and how well it copes with overlapping files due to the false matches.
Also there are some FIXMEs how this patch could behave better. But the current patch should be an improvement (at least for the case (1)) already so those further fixes for various corner cases can be incremental updates. Unfortunately it is duplicating more and more the work already done in GDB.
The testcase run-unstrip-n.sh had to be updated: (a) It was currently reporting filenames like /lib/libc.so.6 but those were a false match, the system files do not match build-ids from the core file, therefore there should be no successful match to system files. (b) Current code outputs [exe] and [vdso] in slightly different order.
Thanks, Jan
commit e88be1bac6665f8905ef1cb6e3003267f92e912d Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Fri Apr 26 20:27:10 2013 +0200
Use DT_DEBUG library search first.
libdwfl/ 2013-04-26 Jan Kratochvil jan.kratochvil@redhat.com
* argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report. * core-file.c (dwfl_core_file_report): Move raw segments reporting lower. New variables vdso_bias, vdso_l_ld and exec_vaddr. Call also dwfl_segment_report_module for EXEC_VADDR and VDSO_L_LD. Call dwfl_segment_report_module for raw segments only if LISTED is zero. * dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (dwfl_link_map_report): Add parameters vdso_bias, vdso_l_ld and exec_vaddr. * link_map.c: Include system.h and fcntl.h. (report_r_debug): Add parameters vdso_bias, vdso_l_ld and exec_vaddr, describe them in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it and skip vDSO. Report vdso_bias and vdso_l_ld when found. Clear exec_vaddr when found. (dwfl_link_map_report): Add parameters vdso_bias, vdso_l_ld and exec_vaddr. (dwfl_link_map_report) (consider_phdr): Add parameter offset. Set exec_vaddr when found. (dwfl_link_map_report): New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update consider_phdr caller parameters. Update report_r_debug caller parameters.
tests/ 2013-04-26 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file.
Signed-off-by: Jan Kratochvil jan.kratochvil@redhat.com
diff --git a/libdwfl/argp-std.c b/libdwfl/argp-std.c index e54f720..c884390 100644 --- a/libdwfl/argp-std.c +++ b/libdwfl/argp-std.c @@ -295,6 +295,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
if (opt->core) { + if (opt->e) + dwfl->executable_for_core = strdup (opt->e); + int fd = open64 (opt->core, O_RDONLY); if (fd < 0) { @@ -330,9 +333,6 @@ parse_opt (int key, char *arg, struct argp_state *state) _("No modules recognized in core file")); return ENOENT; } - - if (opt->e) - dwfl->executable_for_core = strdup (opt->e); } else if (opt->e) { diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index ac2aa8c..bf6630b 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -431,11 +431,47 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) /* Now we have NT_AUXV contents. From here on this processing could be used for a live process with auxv read from /proc. */
+ GElf_Addr vdso_bias = 0; + GElf_Addr vdso_l_ld = 0; + GElf_Addr exec_vaddr = 0; int listed = dwfl_link_map_report (dwfl, auxv, auxv_size, - dwfl_elf_phdr_memory_callback, elf); + dwfl_elf_phdr_memory_callback, elf, + &vdso_bias, &vdso_l_ld, &exec_vaddr); + if (listed > 0 && (vdso_l_ld != 0 || exec_vaddr != 0)) + { + for (ndx = 0; ndx < (int) phnum; ++ndx) + { + GElf_Phdr phdr_mem; + GElf_Phdr *phdr = gelf_getphdr (elf, ndx, &phdr_mem); + if (unlikely (phdr == NULL)) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + if (phdr->p_type != PT_LOAD) + continue; + if (exec_vaddr != 0 && phdr->p_vaddr == exec_vaddr) + dwfl_segment_report_module (dwfl, ndx, "[exe]", + &dwfl_elf_phdr_memory_callback, elf, + core_file_read_eagerly, elf); + if (vdso_l_ld != 0 && phdr->p_filesz == phdr->p_memsz + && phdr->p_vaddr <= vdso_l_ld + && vdso_l_ld < phdr->p_vaddr + phdr->p_memsz) + { + /* FIXME: Use vdso_bias. Currently dwfl_segment_report_module + detects the bias on its own. */ + dwfl_segment_report_module (dwfl, ndx, "[vdso]", + &dwfl_elf_phdr_memory_callback, elf, + core_file_read_eagerly, elf); + break; + } + } + }
/* Now sniff segment contents for modules. */ int sniffed = 0; + if (listed == 0) + { ndx = 0; do { @@ -453,6 +489,7 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) ++ndx; } while (ndx < (int) phnum); + }
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an diff --git a/libdwfl/dwfl_module_build_id.c b/libdwfl/dwfl_module_build_id.c index 98cb9f4..b19bc9c 100644 --- a/libdwfl/dwfl_module_build_id.c +++ b/libdwfl/dwfl_module_build_id.c @@ -56,10 +56,17 @@ found_build_id (Dwfl_Module *mod, bool set,
int internal_function -__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +__libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, int *build_id_len) { - int - check_notes (Dwfl_Module *mod, bool set, Elf_Data *data, GElf_Addr data_vaddr) + { + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + assert (mod == NULL || ehdr->e_type == mod->e_type); + assert (ehdr->e_type != ET_REL || mod != NULL); + } + + int check_notes (Elf_Data *data, GElf_Addr data_elfaddr) { size_t pos = 0; GElf_Nhdr nhdr; @@ -69,10 +76,13 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == sizeof "GNU" && !memcmp (data->d_buf + name_pos, "GNU", sizeof "GNU")) - return found_build_id (mod, set, - data->d_buf + desc_pos, nhdr.n_descsz, - data_vaddr == NO_VADDR ? 0 - : data_vaddr + desc_pos); + { + *build_id_bits = data->d_buf + desc_pos; + *build_id_elfaddr = (data_elfaddr == NO_VADDR + ? 0 : data_elfaddr + desc_pos); + *build_id_len = nhdr.n_descsz; + return 1; + } return 0; }
@@ -98,12 +108,11 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Phdr phdr_mem; GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); if (likely (phdr != NULL) && phdr->p_type == PT_NOTE) - result = check_notes (mod, set, - elf_getdata_rawchunk (elf, + result = check_notes (elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz, ELF_T_NHDR), - dwfl_adjusted_address (mod, phdr->p_vaddr)); + phdr->p_vaddr); } } else @@ -117,12 +126,12 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Addr vaddr = 0; if (!(shdr->sh_flags & SHF_ALLOC)) vaddr = NO_VADDR; - else if (mod->e_type != ET_REL) - vaddr = dwfl_adjusted_address (mod, shdr->sh_addr); + else if (mod == NULL || mod->e_type != ET_REL) + vaddr = shdr->sh_addr; else if (__libdwfl_relocate_value (mod, elf, &shstrndx, elf_ndxscn (scn), &vaddr)) vaddr = NO_VADDR; - result = check_notes (mod, set, elf_getdata (scn, NULL), vaddr); + result = check_notes (elf_getdata (scn, NULL), vaddr); } } while (result == 0 && (scn = elf_nextscn (elf, scn)) != NULL); @@ -131,6 +140,24 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) }
int +internal_function +__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +{ + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + + int result = __libdwfl_find_elf_build_id (mod, elf, &build_id_bits, + &build_id_elfaddr, &build_id_len); + if (result <= 0) + return result; + + GElf_Addr build_id_vaddr = build_id_elfaddr + (build_id_elfaddr != 0 + ? mod->main_bias : 0); + return found_build_id (mod, set, build_id_bits, build_id_len, build_id_vaddr); +} + +int dwfl_module_build_id (Dwfl_Module *mod, const unsigned char **bits, GElf_Addr *vaddr) { diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 5aaa778..7777b53 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -362,6 +362,16 @@ extern Dwfl_Error __libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, extern Dwfl_Error __libdwfl_cu_getsrclines (struct dwfl_cu *cu) internal_function;
+/* Look in ELF for an NT_GNU_BUILD_ID note. Store it to BUILD_ID_BITS, + its vaddr in ELF to BUILD_ID_VADDR (it is unrelocated, even if MOD is not + NULL) and store length to BUILD_ID_LEN. Returns -1 for errors, 1 if it was + stored and 0 if no note is found. MOD may be NULL, MOD must be non-NULL + only if ELF is ET_REL. */ +extern int __libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, + int *build_id_len); + /* Look in ELF for an NT_GNU_BUILD_ID note. If SET is true, store it in MOD and return its length. If SET is false, instead compare it to that stored in MOD and return 2 if they match, 1 if they do not. @@ -458,10 +468,18 @@ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, only find where to begin if the correct executable file was previously reported and preloaded as with dwfl_report_elf.
+ Return found l_addr and l_ld fields for vDSO in VDSO_BIAS and + VDSO_L_LD respectively. Return found memory address of start of the + executable in EXEC_VADDR. If either vDSO or executable is not found + the referenced addresses are not modified by this function. Any of + these parameters can be NULL. + Returns the number of modules found, or -1 for errors. */ extern int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg); + void *memory_callback_arg, + GElf_Addr *vdso_bias, GElf_Addr *vdso_l_ld, + GElf_Addr *exec_vaddr);
/* Avoid PLT entries. */ diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c index c79a2bf..8a81c47 100644 --- a/libdwfl/link_map.c +++ b/libdwfl/link_map.c @@ -29,9 +29,11 @@ #include <config.h> #include "libdwflP.h" #include "../libdw/memory-access.h" +#include "system.h"
#include <byteswap.h> #include <endian.h> +#include <fcntl.h>
/* This element is always provided and always has a constant value. This makes it an easy thing to scan for to discern the format. */ @@ -221,7 +223,8 @@ addrsize (uint_fast8_t elfclass) }
/* Report a module for each struct link_map in the linked list at r_map - in the struct r_debug at R_DEBUG_VADDR. + in the struct r_debug at R_DEBUG_VADDR. For vdso_bias, vdso_l_ld and + exec_vaddr descriptions see dwfl_link_map_report in libdwflP.h.
For each link_map entry, if an existing module resides at its address, this just modifies that module's name and suggested file name. If @@ -233,7 +236,9 @@ static int report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, Dwfl *dwfl, GElf_Addr r_debug_vaddr, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + GElf_Addr *vdso_bias, GElf_Addr *vdso_l_ld, + GElf_Addr *exec_vaddr) { /* Skip r_version, to aligned r_map field. */ GElf_Addr read_vaddr = r_debug_vaddr + addrsize (elfclass); @@ -351,9 +356,93 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, /* We have to find the file's phdrs to compute along with l_addr what its runtime address boundaries are. */
- // XXX hook for sysroot - mod = INTUSE(dwfl_report_elf) (dwfl, basename (name), - name, -1, l_addr); + Dwfl_Module *mod = NULL; + if (iterations == 1 && dwfl->executable_for_core != NULL) + { + /* Find the main executable. + FIXME: Try to also find it via build-id. */ + name = dwfl->executable_for_core; + } + if (iterations != 2 && name != NULL) + { + /* Find a shared library or main executable. Do not try to + find a file for vDSO (where ITERATIONS equals 2). */ + + /* This code is mostly inlined dwfl_report_elf. */ + // XXX hook for sysroot + int fd = open64 (name, O_RDONLY); + if (fd >= 0) + { + Elf *elf; + Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false); + if (error == DWFL_E_NOERROR) + { + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + bool valid = true; + + /* FIXME: Bias L_ADDR should be computed from the prelink + state in memory (when the file got loaded), not against + the current on-disk file state as is computed below. + + This verification gives false positive if in-core ELF had + build-id but on-disk ELF does not have any. But we cannot + reliably find ELF header and/or the ELF build id just from + the link map (and checking core segments is also not + reliable). */ + + if (__libdwfl_find_elf_build_id (NULL, elf, &build_id_bits, + &build_id_elfaddr, + &build_id_len) > 0 + && build_id_elfaddr != 0) + { + GElf_Addr build_id_vaddr = build_id_elfaddr + l_addr; + release_buffer (0); + int segndx = INTUSE(dwfl_addrsegment) (dwfl, + build_id_vaddr, + NULL); + if (! (*memory_callback) (dwfl, segndx, + &buffer, &buffer_available, + build_id_vaddr, build_id_len, + memory_callback_arg) + || memcmp (build_id_bits, buffer, build_id_len) != 0) + { + /* File has valid build-id which cannot be verified + in memory. */ + valid = false; + } + } + + if (valid) + // XXX hook for sysroot + mod = __libdwfl_report_elf (dwfl, basename (name), name, + fd, elf, l_addr, true); + if (mod == NULL) + { + elf_end (elf); + close (fd); + } + } + } + } + if (mod != NULL && iterations == 1) + { + /* Cancel reporting of raw segments for the main executable as + its file has been found now. */ + if (exec_vaddr) + *exec_vaddr = 0; + } + if (iterations == 2) + { + /* vDSO is only reported to the caller, it is never searched + for on the disk. */ + assert (mod == NULL); + if (vdso_bias) + *vdso_bias = l_addr; + if (vdso_l_ld) + *vdso_l_ld = l_ld; + }
if (mod != NULL) { @@ -569,7 +658,9 @@ find_executable (Dwfl *dwfl, GElf_Addr at_phdr, GElf_Addr at_entry, int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + GElf_Addr *vdso_bias, GElf_Addr *vdso_l_ld, + GElf_Addr *exec_vaddr) { GElf_Addr r_debug_vaddr = 0;
@@ -631,8 +722,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, GElf_Xword dyn_filesz = 0; GElf_Addr dyn_bias = (GElf_Addr) -1;
- inline bool consider_phdr (GElf_Word type, - GElf_Addr vaddr, GElf_Xword filesz) + inline bool consider_phdr (GElf_Word type, GElf_Addr vaddr, + GElf_Xword filesz, Elf32_Off offset) { switch (type) { @@ -643,6 +734,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, == (phdr & (dwfl->segment_align - 1)))) { dyn_bias = phdr - vaddr; + if (exec_vaddr) + *exec_vaddr = phdr - offset; return dyn_vaddr != 0; } break; @@ -667,8 +760,65 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, .d_size = phnum * phent, .d_buf = NULL }; - if ((*memory_callback) (dwfl, phdr_segndx, &in.d_buf, &in.d_size, - phdr, phnum * phent, memory_callback_arg)) + bool in_ok = (*memory_callback) (dwfl, phdr_segndx, &in.d_buf, + &in.d_size, phdr, phnum * phent, + memory_callback_arg); + if (! in_ok && dwfl->executable_for_core != NULL) + { + /* AUXV -> PHDR -> DYNAMIC + Both AUXV and DYNAMIC should be always present in a core file. + PHDR may be missing in core file, try to read it from + EXECUTABLE_FOR_CORE to find where DYNAMIC is located in the + core file. */ + + int fd = open (dwfl->executable_for_core, O_RDONLY); + Elf *elf; + Dwfl_Error error = DWFL_E_ERRNO; + if (fd != -1) + error = __libdw_open_file (&fd, &elf, true, false); + if (error != DWFL_E_NOERROR) + { + __libdwfl_seterrno (error); + return false; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + if (ehdr->e_phnum != phnum || ehdr->e_phentsize != phent) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_BADELF); + return false; + } + off_t off = ehdr->e_phoff; + assert (in.d_buf == NULL); + assert (in.d_size == phnum * phent); + in.d_buf = malloc (in.d_size); + if (unlikely (in.d_buf == NULL)) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + ssize_t nread = pread_retry (fd, in.d_buf, in.d_size, off); + elf_end (elf); + close (fd); + if (nread != (ssize_t) in.d_size) + { + free (in.d_buf); + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + in_ok = true; + } + if (in_ok) { union { @@ -699,7 +849,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, for (size_t i = 0; i < phnum; ++i) if (consider_phdr (u->p32[i].p_type, u->p32[i].p_vaddr, - u->p32[i].p_filesz)) + u->p32[i].p_filesz, + u->p32[i].p_offset)) break; } else @@ -707,7 +858,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, for (size_t i = 0; i < phnum; ++i) if (consider_phdr (u->p64[i].p_type, u->p64[i].p_vaddr, - u->p64[i].p_filesz)) + u->p64[i].p_filesz, + u->p64[i].p_offset)) break; } } @@ -830,6 +982,7 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size,
/* Now we can follow the dynamic linker's library list. */ return report_r_debug (elfclass, elfdata, dwfl, r_debug_vaddr, - &integrated_memory_callback, &mcb); + &integrated_memory_callback, &mcb, + vdso_bias, vdso_l_ld, exec_vaddr); } INTDEF (dwfl_link_map_report) diff --git a/tests/Makefile.am b/tests/Makefile.am index e10da8a..31c8fba 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -198,7 +198,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ testfile69.core.bz2 testfile69.so.bz2 \ testfile70.core.bz2 testfile70.exec.bz2 \ run-dwfllines.sh run-dwfl-report-elf-align.sh \ - testfile-dwfl-report-elf-align-shlib.so.bz2 + testfile-dwfl-report-elf-align-shlib.so.bz2 \ + test-core-lib.so.bz2 test-core.core.bz2 test-core.exec.bz2
if USE_VALGRIND valgrind_cmd="valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no" diff --git a/tests/run-addrname-test.sh b/tests/run-addrname-test.sh index 99abf9d..dd6a8f0 100755 --- a/tests/run-addrname-test.sh +++ b/tests/run-addrname-test.sh @@ -292,6 +292,12 @@ l0local2 ??:0 EOF
+testfiles test-core-lib.so test-core.core test-core.exec +testrun_compare ../src/addr2line -S -e test-core.exec --core=test-core.core 0x7f67f2aaf619 <<\EOF +libfunc+0x9 +??:0 +EOF + testfiles testfile65 testrun_compare ../src/addr2line -S --core=testfile65 0x7fff94bffa30 <<\EOF __vdso_time diff --git a/tests/run-unstrip-n.sh b/tests/run-unstrip-n.sh index 1353b1d..0d93b29 100755 --- a/tests/run-unstrip-n.sh +++ b/tests/run-unstrip-n.sh @@ -35,20 +35,20 @@ testfiles testcore-rtlib testcore-rtlib-ppc
testrun_compare ../src/unstrip -n --core=testcore-rtlib <<\EOF 0x8048000+0x2000 f1c600bc36cb91bf01f9a63a634ecb79aa4c3199@0x8048178 . - [exe] +0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 - - [dso] +0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 - - [dso] +0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 - - [dso] 0xf77d6000+0x1000 676560b1b765cde9c2e53f134f4ee354ea894747@0xf77d6210 . - linux-gate.so.1 -0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 /lib/librt.so.1 - librt.so.1 -0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 /lib/libc.so.6 - libc.so.6 -0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 /lib/libpthread.so.0 - libpthread.so.0 -0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 /lib/ld-linux.so.2 - ld-linux.so.2 +0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 - - ld-linux.so.2 EOF
testrun_compare ../src/unstrip -n --core=testcore-rtlib-ppc <<\EOF -0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] 0x100000+0x10000 708b900b05176964512a6b0fe90c2a0c9d73d726@0x100334 . - linux-vdso32.so.1 -0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 /lib/librt.so.1 - librt.so.1 -0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 /lib/libc.so.6 - libc.so.6 -0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 /lib/libpthread.so.0 - libpthread.so.0 -0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 /lib/ld.so.1 - ld.so.1 +0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 . - librt.so.1 +0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 - - libpthread.so.0 +0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 - - [dso] +0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 - - ld.so.1 +0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] EOF
test_cleanup diff --git a/tests/test-core-lib.so.bz2 b/tests/test-core-lib.so.bz2 new file mode 100755 index 0000000..bb2da88 Binary files /dev/null and b/tests/test-core-lib.so.bz2 differ diff --git a/tests/test-core.core.bz2 b/tests/test-core.core.bz2 new file mode 100644 index 0000000..4d4346b Binary files /dev/null and b/tests/test-core.core.bz2 differ diff --git a/tests/test-core.exec.bz2 b/tests/test-core.exec.bz2 new file mode 100755 index 0000000..49ce551 Binary files /dev/null and b/tests/test-core.exec.bz2 differ
Hi Jan,
Haven't gotten completely through the patches yet, but here is some early feedback.
On Fri, 2013-04-26 at 20:57 +0200, Jan Kratochvil wrote:
With my former patches I improved some cases and regressed different cases, so with this patch it should behave better in general - but not in 100% cases. The former code using ELF headers from segments could find more modules but their proper address was only a luck, sometimes wrong, as proven in the mail above ELF.
"Fix loading core files without build-ids" - with build-id header the ELF headers from segments method almost always works, it has more wrong results when build-ids are missing, therefore even the ELF headers are missing.
BTW. How often does it happen that build-id are missing in core files? Is that just when a distro "misconfigures" the kernel dumping settings? We should of course handle it correctly anyway, just wondering.
elfutils was always fragile by searching for ELF headers in core files segments first /* Now sniff segment contents for modules. */ and only then trying to adjust them from libc link_map list. /* Next, we should follow the chain from DT_DEBUG. */
- /* If content-sniffing already reported a module covering the same area, find that existing module to adjust. The l_ld address is the only one we know for sure to be within the module's own segments (its .dynamic). */
But that adjustment currently does not work as it uses Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (dwfl, l_ld); but if the module is placed incorrectly one cannot find the proper module by its address -- by its placement.
IMO one should follow link_map first and fall back to the ELF headers only if it fails.
Yes, that order makes sense to me.
Not sure if one should try to map address-non-conflicting files from ELF headers, this patch does not do it. (That could better fit case (2) below.)
There are IMO two basic use cases with different pros/cons:
(1) Map address -> symbol: eu-addr2line -S --core=xxx 0xaddress Exact modules placement is needed and it is better to report no symbol than a wrong symbol.
Agreed.
(2) I want to know all build-ids: eu-unstrip -n --core=xxx Modules placement does not matter, excessive output does not matter, I need to only install needed debuginfos.
Thanks for this example. Now I finally understand why the current code does the "sniffing". The build-ids found could be interesting, but we don't know for sure in this case.
The testcase run-unstrip-n.sh had to be updated: (a) It was currently reporting filenames like /lib/libc.so.6 but those were a false match, the system files do not match build-ids from the core file, therefore there should be no successful match to system files. (b) Current code outputs [exe] and [vdso] in slightly different order.
The order shouldn't really matter, so that part of the change is fine. But now you don't know the name at all? That is a pity since they are a good hint. Would be nice if we could provide them in some way as hint as you see with /proc/self/exe -> /bin/foo (deleted), or by putting them between [brackets].
commit e88be1bac6665f8905ef1cb6e3003267f92e912d Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Fri Apr 26 20:27:10 2013 +0200
Use DT_DEBUG library search first. libdwfl/ 2013-04-26 Jan Kratochvil <jan.kratochvil@redhat.com> * argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report.
This makes sense to me.
- core-file.c (dwfl_core_file_report): Move raw segments reporting lower. New variables vdso_bias, vdso_l_ld and exec_vaddr. Call also dwfl_segment_report_module for EXEC_VADDR and VDSO_L_LD. Call dwfl_segment_report_module for raw segments only if LISTED is zero.
Is the FIXME: Use vdso_bias. just informational, or were you going to do that? So you don't sniff at all if dwfl_link_map_report () found something. Would it hurt at this point to sniff? It is a pity dwfl_segment_report_module () doesn't have documentation. I am still staring at it to fully understand what it does.
- dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id.
- libdwflP.h (__libdwfl_find_elf_build_id): New declaration.
OK, as written here the moving around confused me a little. But reading the code it looks correct. Since __libdwfl_find_build_id () adds the main bias afterwards, you don't adjusting vaddr in the other places anymore.
(dwfl_link_map_report): Add parameters vdso_bias, vdso_l_ld and exec_vaddr. * link_map.c: Include system.h and fcntl.h. (report_r_debug): Add parameters vdso_bias, vdso_l_ld and exec_vaddr, describe them in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it and skip vDSO. Report vdso_bias and vdso_l_ld when found. Clear exec_vaddr when found. (dwfl_link_map_report): Add parameters vdso_bias, vdso_l_ld and exec_vaddr. (dwfl_link_map_report) (consider_phdr): Add parameter offset. Set exec_vaddr when found. (dwfl_link_map_report): New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update consider_phdr caller parameters. Update report_r_debug caller parameters.
I still need to go through and understand the above.
Cheers,
Mark
Hi Mark,
there is still some work on this patch as I have found from your review, see below.
On Sat, 18 May 2013 00:00:58 +0200, Mark Wielaard wrote:
BTW. How often does it happen that build-id are missing in core files? Is that just when a distro "misconfigures" the kernel dumping settings? We should of course handle it correctly anyway, just wondering.
CORE_DUMP_DEFAULT_ELF_HEADERS is in linux-2.6.git since commit 656eb2cd5da153762f2e8419ca117ce12ef522c3 Author: Roland McGrath roland@redhat.com Date: Sat Oct 18 20:28:23 2008 -0700
and it is default Y since commit 895021552d6ffe8a4d076cb5c4b1e700c33e96e1 Author: Roland McGrath roland@redhat.com Date: Wed Oct 27 15:34:09 2010 -0700
So anything older than 2010 or anything non-Linux or any GDB gcore-generated core file (still not fixed, mea culpa). Besides that someone can also unconfigure it, I guess some ultra-mini kernel configs may illogically do it.
OTOH for practical Fedora/RHEL cases this patch is probably irrelevant.
The testcase run-unstrip-n.sh had to be updated: (a) It was currently reporting filenames like /lib/libc.so.6 but those were a false match, the system files do not match build-ids from the core file, therefore there should be no successful match to system files.
[...]
But now you don't know the name at all? That is a pity since they are a good hint. Would be nice if we could provide them in some way as hint as you see with /proc/self/exe -> /bin/foo (deleted), or by putting them between [brackets].
This is a real bug, thanks for noticing it.
New dwfl_link_map_report does not report modules for which it cannot find its file on disk. This is correct as one does not know the module boundaries just from a link map entry. The module boundaries were found in such case by dwfl_segment_report_module and names filled in later by dwfl_link_map_report.
Recent Linux kernels now produce also core note NT_FILE but in such case one usually has also build-id headers already so this patch is not needed.
My current plan is: (1) Save filenames for non-existing files during initial dwfl_link_map_report. (2) Run dwfl_segment_report_module even if dwfl_link_map_report found something. (2b) dwfl_segment_report_module should omit conflicting modules. (3) Update found anonymous "[dso]" modules by the saved filenames.
- core-file.c (dwfl_core_file_report): Move raw segments reporting lower. New variables vdso_bias, vdso_l_ld and exec_vaddr. Call also dwfl_segment_report_module for EXEC_VADDR and VDSO_L_LD. Call dwfl_segment_report_module for raw segments only if LISTED is zero.
Is the FIXME: Use vdso_bias. just informational, or were you going to do that?
I am not going to do that. I was trying to refactor the code to make it possible but I find too many changes needed in a code I do not understand well. Primarily I believe the bias auto-detection in dwfl_segment_report_module should always work correctly for vDSO.
So you don't sniff at all if dwfl_link_map_report () found something. Would it hurt at this point to sniff?
That's the question. Sniffing will always bring in possibly incorrect data. I believe it would be useful only in the case link_map (r_debug->r_map) is corrupted in the middle and some of the libraries do not get reported (as I have seen in real world core files from ABRT). Maybe report_r_debug could report corrupted link_map - it currently internally checks ++iterations < dwfl->lookup_elts but it should also check l_prev does match the previous entry like GDB does.
Still core files with corrupted link_map I find more a corner case and it could be added as an add-on patch IMO.
It is a pity dwfl_segment_report_module () doesn't have documentation. I am still staring at it to fully understand what it does.
Yes, I also had this problem, this is why I used it only as a 2nd pass if dwfl_link_map_report did not find anything.
One needs to decide there when it is really safe to add a new non-conflicting module by dwfl_segment_report_module but I admit I did not investigate it much.
- dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id.
- libdwflP.h (__libdwfl_find_elf_build_id): New declaration.
OK, as written here the moving around confused me a little. But reading the code it looks correct. Since __libdwfl_find_build_id () adds the main bias afterwards, you don't adjusting vaddr in the other places anymore.
Technically I just split out the libelf/ part of __libdwfl_find_build_id into __libdwfl_find_elf_build_id. But in the end I found I cannot easily make it really libelf/-clean as __libdwfl_find_elf_build_id needs __libdwfl_relocate_value for ET_REL and separating the call out of it would complicate the code more than worth moving it to libelf/. But still I could refactor it more to really it move __libdwfl_find_elf_build_id to libelf/ .
Thanks, Jan
Ubuntu is misconfigured so that build IDs are missing from core files. A bug was filed but it doesn't look like much has been done with it.
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1108082
So yeah, it is unfortunately important to handle this case.
-----Original Message----- From: elfutils-devel-bounces@lists.fedorahosted.org [mailto:elfutils-devel-bounces@lists.fedorahosted.org] On Behalf Of Jan Kratochvil Sent: Sunday, May 19, 2013 9:39 AM To: Mark Wielaard Cc: elfutils-devel@lists.fedorahosted.org Subject: Re: [patch 2/2] Fix loading core files without build-ids
Hi Mark,
there is still some work on this patch as I have found from your review, see below.
On Sat, 18 May 2013 00:00:58 +0200, Mark Wielaard wrote:
BTW. How often does it happen that build-id are missing in core files? Is that just when a distro "misconfigures" the kernel dumping settings? We should of course handle it correctly anyway, just wondering.
CORE_DUMP_DEFAULT_ELF_HEADERS is in linux-2.6.git since commit 656eb2cd5da153762f2e8419ca117ce12ef522c3 Author: Roland McGrath roland@redhat.com Date: Sat Oct 18 20:28:23 2008 -0700
and it is default Y since commit 895021552d6ffe8a4d076cb5c4b1e700c33e96e1 Author: Roland McGrath roland@redhat.com Date: Wed Oct 27 15:34:09 2010 -0700
So anything older than 2010 or anything non-Linux or any GDB gcore-generated core file (still not fixed, mea culpa). Besides that someone can also unconfigure it, I guess some ultra-mini kernel configs may illogically do it.
OTOH for practical Fedora/RHEL cases this patch is probably irrelevant.
The testcase run-unstrip-n.sh had to be updated: (a) It was currently reporting filenames like /lib/libc.so.6 but those were a false match, the system files do not match build-ids from the core file, therefore there should be no successful match to system files.
[...]
But now you don't know the name at all? That is a pity since they are a good hint. Would be nice if we could provide them in some way as hint as you see with /proc/self/exe -> /bin/foo (deleted), or by putting them between [brackets].
This is a real bug, thanks for noticing it.
New dwfl_link_map_report does not report modules for which it cannot find its file on disk. This is correct as one does not know the module boundaries just from a link map entry. The module boundaries were found in such case by dwfl_segment_report_module and names filled in later by dwfl_link_map_report.
Recent Linux kernels now produce also core note NT_FILE but in such case one usually has also build-id headers already so this patch is not needed.
My current plan is: (1) Save filenames for non-existing files during initial dwfl_link_map_report. (2) Run dwfl_segment_report_module even if dwfl_link_map_report found something. (2b) dwfl_segment_report_module should omit conflicting modules. (3) Update found anonymous "[dso]" modules by the saved filenames.
- core-file.c (dwfl_core_file_report): Move raw segments reporting lower. New variables vdso_bias, vdso_l_ld and exec_vaddr. Call also dwfl_segment_report_module for EXEC_VADDR and VDSO_L_LD. Call dwfl_segment_report_module for raw segments only if LISTED is zero.
Is the FIXME: Use vdso_bias. just informational, or were you going to do that?
I am not going to do that. I was trying to refactor the code to make it possible but I find too many changes needed in a code I do not understand well. Primarily I believe the bias auto-detection in dwfl_segment_report_module should always work correctly for vDSO.
So you don't sniff at all if dwfl_link_map_report () found something. Would it hurt at this point to sniff?
That's the question. Sniffing will always bring in possibly incorrect data. I believe it would be useful only in the case link_map (r_debug->r_map) is corrupted in the middle and some of the libraries do not get reported (as I have seen in real world core files from ABRT). Maybe report_r_debug could report corrupted link_map - it currently internally checks ++iterations < dwfl->lookup_elts but it should also check l_prev does match the previous entry like GDB does.
Still core files with corrupted link_map I find more a corner case and it could be added as an add-on patch IMO.
It is a pity dwfl_segment_report_module () doesn't have documentation. I am still staring at it to fully understand what it does.
Yes, I also had this problem, this is why I used it only as a 2nd pass if dwfl_link_map_report did not find anything.
One needs to decide there when it is really safe to add a new non-conflicting module by dwfl_segment_report_module but I admit I did not investigate it much.
- dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id.
- libdwflP.h (__libdwfl_find_elf_build_id): New declaration.
OK, as written here the moving around confused me a little. But reading the code it looks correct. Since __libdwfl_find_build_id () adds the main bias afterwards, you don't adjusting vaddr in the other places anymore.
Technically I just split out the libelf/ part of __libdwfl_find_build_id into __libdwfl_find_elf_build_id. But in the end I found I cannot easily make it really libelf/-clean as __libdwfl_find_elf_build_id needs __libdwfl_relocate_value for ET_REL and separating the call out of it would complicate the code more than worth moving it to libelf/. But still I could refactor it more to really it move __libdwfl_find_elf_build_id to libelf/ .
Thanks, Jan _______________________________________________ elfutils-devel mailing list elfutils-devel@lists.fedorahosted.org https://lists.fedorahosted.org/mailman/listinfo/elfutils-devel
Hi Mark,
patch updated to address the issues/regressions.
On Sun, 19 May 2013 18:38:56 +0200, Jan Kratochvil wrote:
New dwfl_link_map_report does not report modules for which it cannot find its file on disk. This is correct as one does not know the module boundaries just from a link map entry. The module boundaries were found in such case by dwfl_segment_report_module and names filled in later by dwfl_link_map_report.
Recent Linux kernels now produce also core note NT_FILE but in such case one usually has also build-id headers already so this patch is not needed.
My current plan is: (1) Save filenames for non-existing files during initial dwfl_link_map_report. (2) Run dwfl_segment_report_module even if dwfl_link_map_report found something. (2b) dwfl_segment_report_module should omit conflicting modules. (3) Update found anonymous "[dso]" modules by the saved filenames.
Implemented.
This patch addresses the issues and use case (2) discussed below:
On Sat, 18 May 2013 00:00:58 +0200, Mark Wielaard wrote:
So you don't sniff at all if dwfl_link_map_report () found something. Would it hurt at this point to sniff?
That's the question. Sniffing will always bring in possibly incorrect data. I believe it would be useful only in the case link_map (r_debug->r_map) is corrupted in the middle and some of the libraries do not get reported (as I have seen in real world core files from ABRT). Maybe report_r_debug could report corrupted link_map - it currently internally checks ++iterations < dwfl->lookup_elts but it should also check l_prev does match the previous entry like GDB does.
Still core files with corrupted link_map I find more a corner case and it could be added as an add-on patch IMO.
On Fri, 26 Apr 2013 20:57:39 +0200, Jan Kratochvil wrote:
Not sure if one should try to map address-non-conflicting files from ELF headers, this patch does not do it. (That could better fit case (2) below.)
[...]
(2) I want to know all build-ids: eu-unstrip -n --core=xxx Modules placement does not matter, excessive output does not matter, I need to only install needed debuginfos.
This change will behave better in (1) but it may regress (2), for example in cases where link_map list is overwritten/corrupted in its middle.
Thanks, Jan
commit 457b771b842a0f6d23118e4b39d93f6581fb9b41 Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 23 20:19:16 2013 +0200
Use DT_DEBUG library search first.
libdwfl/ 2013-05-23 Jan Kratochvil jan.kratochvil@redhat.com
* argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report. * core-file.c (free_r_debug_info): New function. (dwfl_core_file_report): Move raw segments reporting lower. New variable r_debug_info. (dwfl_core_file_report) (report_module): New function. (dwfl_core_file_report): Call also report_module for EXEC_VADDR and VDSO_L_LD. Call free_r_debug_info in the end. Return sum of LISTED and SNIFFED. * dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. * dwfl_segment_report_module.c (dwfl_segment_report_module): New parameter r_debug_info. New variable name_is_final. Adjust addresses according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. Do not overwise NAME by SONAME if NAME_IS_FINAL. * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (struct r_debug_info_module, struct r_debug_info): New definitions. (dwfl_segment_report_module, dwfl_link_map_report): Add parameter r_debug_info. * link_map.c: Include fcntl.h. (report_r_debug): Add parameter r_debug_info, describe them in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it and skip vDSO. Report vdso_bias and vdso_l_ld when found. Clear exec_vaddr when found. Store r_debug_info->module info when appropriate. (dwfl_link_map_report): Add parameter r_debug_info. (dwfl_link_map_report) (consider_phdr): Add parameter offset. Set r_debug_info.exec_vaddr when found. (dwfl_link_map_report): New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update consider_phdr caller parameters. Update report_r_debug caller parameters.
tests/ 2013-05-23 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. New test for these files. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file.
Signed-off-by: Jan Kratochvil jan.kratochvil@redhat.com
diff --git a/libdwfl/argp-std.c b/libdwfl/argp-std.c index e54f720..c884390 100644 --- a/libdwfl/argp-std.c +++ b/libdwfl/argp-std.c @@ -295,6 +295,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
if (opt->core) { + if (opt->e) + dwfl->executable_for_core = strdup (opt->e); + int fd = open64 (opt->core, O_RDONLY); if (fd < 0) { @@ -330,9 +333,6 @@ parse_opt (int key, char *arg, struct argp_state *state) _("No modules recognized in core file")); return ENOENT; } - - if (opt->e) - dwfl->executable_for_core = strdup (opt->e); } else if (opt->e) { diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index ac2aa8c..324bc5c 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -381,6 +381,19 @@ dwfl_elf_phdr_memory_callback (Dwfl *dwfl, int ndx, return true; }
+/* Free the contents of R_DEBUG_INFO without the R_DEBUG_INFO memory itself. */ + +static void +free_r_debug_info (struct r_debug_info *r_debug_info) +{ + while (r_debug_info->module != NULL) + { + struct r_debug_info_module *module = r_debug_info->module; + r_debug_info->module = module->next; + free (module); + } +} + int dwfl_core_file_report (Dwfl *dwfl, Elf *elf) { @@ -431,33 +444,85 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) /* Now we have NT_AUXV contents. From here on this processing could be used for a live process with auxv read from /proc. */
+ struct r_debug_info r_debug_info; + memset (&r_debug_info, 0, sizeof r_debug_info); int listed = dwfl_link_map_report (dwfl, auxv, auxv_size, - dwfl_elf_phdr_memory_callback, elf); + dwfl_elf_phdr_memory_callback, elf, + &r_debug_info); + + /* Call dwfl_segment_report_module, update *NDXP when appropriate. Return + true for a successfully reported module. Otherwise return false and also + call free_r_debug_info. */ + + inline bool report_module (int *ndxp, int *count, const char *name) + { + int seg = dwfl_segment_report_module (dwfl, *ndxp, name, + &dwfl_elf_phdr_memory_callback, elf, + core_file_read_eagerly, elf, + &r_debug_info); + if (unlikely (seg < 0)) + { + free_r_debug_info (&r_debug_info); + return false; + } + if (seg > *ndxp) + { + *ndxp = seg; + ++*count; + } + else + ++*ndxp; + return true; + } + + if (r_debug_info.vdso_l_ld != 0 || r_debug_info.exec_vaddr != 0) + for (ndx = 0; ndx < (int) phnum;) + { + GElf_Phdr phdr_mem; + GElf_Phdr *phdr = gelf_getphdr (elf, ndx, &phdr_mem); + if (unlikely (phdr == NULL)) + { + free_r_debug_info (&r_debug_info); + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + if (phdr->p_type == PT_LOAD && r_debug_info.exec_vaddr != 0 + && phdr->p_vaddr == r_debug_info.exec_vaddr) + { + if (unlikely (! report_module (&ndx, &listed, "[exe]"))) + return -1; + } + else if (phdr->p_type == PT_LOAD && r_debug_info.vdso_l_ld != 0 + && phdr->p_filesz == phdr->p_memsz + && phdr->p_vaddr <= r_debug_info.vdso_l_ld + && r_debug_info.vdso_l_ld < phdr->p_vaddr + phdr->p_memsz) + { + /* FIXME: Use vdso_bias. Currently dwfl_segment_report_module + detects the bias on its own. */ + if (unlikely (! report_module (&ndx, &listed, "[vdso]"))) + return -1; + } + else + ++ndx; + } + + /* Now sniff segment contents for modules hinted by information gathered + from DT_DEBUG. */
- /* Now sniff segment contents for modules. */ int sniffed = 0; - ndx = 0; - do - { - int seg = dwfl_segment_report_module (dwfl, ndx, NULL, - &dwfl_elf_phdr_memory_callback, elf, - core_file_read_eagerly, elf); - if (unlikely (seg < 0)) - return seg; - if (seg > ndx) - { - ndx = seg; - ++sniffed; - } - else - ++ndx; - } - while (ndx < (int) phnum); + ndx = 0; + do + { + if (unlikely (! report_module (&ndx, &sniffed, NULL) )) + return -1; + } + while (ndx < (int) phnum); + + free_r_debug_info (&r_debug_info);
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an - error rather than just nothing found. If link_map handling - failed, we still have the sniffed modules. */ - return sniffed == 0 || listed > sniffed ? listed : sniffed; + error rather than just nothing found. */ + return sniffed || listed >= 0 ? listed + sniffed : listed; } INTDEF (dwfl_core_file_report) diff --git a/libdwfl/dwfl_module_build_id.c b/libdwfl/dwfl_module_build_id.c index 98cb9f4..cae007b 100644 --- a/libdwfl/dwfl_module_build_id.c +++ b/libdwfl/dwfl_module_build_id.c @@ -56,10 +56,20 @@ found_build_id (Dwfl_Module *mod, bool set,
int internal_function -__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +__libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, int *build_id_len) { - int - check_notes (Dwfl_Module *mod, bool set, Elf_Data *data, GElf_Addr data_vaddr) + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (unlikely (ehdr == NULL)) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + // MOD->E_TYPE is zero here. + assert (ehdr->e_type != ET_REL || mod != NULL); + + int check_notes (Elf_Data *data, GElf_Addr data_elfaddr) { size_t pos = 0; GElf_Nhdr nhdr; @@ -69,10 +79,13 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == sizeof "GNU" && !memcmp (data->d_buf + name_pos, "GNU", sizeof "GNU")) - return found_build_id (mod, set, - data->d_buf + desc_pos, nhdr.n_descsz, - data_vaddr == NO_VADDR ? 0 - : data_vaddr + desc_pos); + { + *build_id_bits = data->d_buf + desc_pos; + *build_id_elfaddr = (data_elfaddr == NO_VADDR + ? 0 : data_elfaddr + desc_pos); + *build_id_len = nhdr.n_descsz; + return 1; + } return 0; }
@@ -84,11 +97,8 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (scn == NULL) { /* No sections, have to look for phdrs. */ - GElf_Ehdr ehdr_mem; - GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); size_t phnum; - if (unlikely (ehdr == NULL) - || unlikely (elf_getphdrnum (elf, &phnum) != 0)) + if (unlikely (elf_getphdrnum (elf, &phnum) != 0)) { __libdwfl_seterrno (DWFL_E_LIBELF); return -1; @@ -98,12 +108,11 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Phdr phdr_mem; GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); if (likely (phdr != NULL) && phdr->p_type == PT_NOTE) - result = check_notes (mod, set, - elf_getdata_rawchunk (elf, + result = check_notes (elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz, ELF_T_NHDR), - dwfl_adjusted_address (mod, phdr->p_vaddr)); + phdr->p_vaddr); } } else @@ -117,12 +126,12 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Addr vaddr = 0; if (!(shdr->sh_flags & SHF_ALLOC)) vaddr = NO_VADDR; - else if (mod->e_type != ET_REL) - vaddr = dwfl_adjusted_address (mod, shdr->sh_addr); + else if (mod == NULL || ehdr->e_type != ET_REL) + vaddr = shdr->sh_addr; else if (__libdwfl_relocate_value (mod, elf, &shstrndx, elf_ndxscn (scn), &vaddr)) vaddr = NO_VADDR; - result = check_notes (mod, set, elf_getdata (scn, NULL), vaddr); + result = check_notes (elf_getdata (scn, NULL), vaddr); } } while (result == 0 && (scn = elf_nextscn (elf, scn)) != NULL); @@ -131,6 +140,24 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) }
int +internal_function +__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +{ + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + + int result = __libdwfl_find_elf_build_id (mod, elf, &build_id_bits, + &build_id_elfaddr, &build_id_len); + if (result <= 0) + return result; + + GElf_Addr build_id_vaddr = build_id_elfaddr + (build_id_elfaddr != 0 + ? mod->main_bias : 0); + return found_build_id (mod, set, build_id_bits, build_id_len, build_id_vaddr); +} + +int dwfl_module_build_id (Dwfl_Module *mod, const unsigned char **bits, GElf_Addr *vaddr) { diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 7cf7499..e2d0337 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -83,7 +83,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg) + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info) { size_t segment = ndx;
@@ -433,6 +434,43 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
dyn_vaddr += bias;
+ /* NAME found from link map has precedence over DT_SONAME possibly read + below. */ + bool name_is_final = false; + + /* Try to match up DYN_VADDR against L_LD as found in link map. + Segments sniffing may guess invalid address as the first read-only memory + mapping may not be dumped to the core file (if ELF headers are not dumped) + and the ELF header is dumped first with the read/write mapping of the same + file at higher addresses. */ + if (r_debug_info != NULL) + for (const struct r_debug_info_module *module = r_debug_info->module; + module != NULL; module = module->next) + if (module_start <= module->l_ld && module->l_ld < module_end) + { + GElf_Addr fixup = module->l_ld - dyn_vaddr; + if (module_start + fixup <= module->l_ld + && module->l_ld < module_end + fixup) + { + module_start += fixup; + module_end += fixup; + dyn_vaddr += fixup; + bias += fixup; + if (module->name[0] != '\0') + { + name = module->name; + name_is_final = true; + } + break; + } + } + + /* Ignore this found module if it would conflict in address space with any + already existing module of DWFL. */ + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + if (module_end > mod->low_addr && module_start < mod->high_addr) + return finish (); + /* Our return value now says to skip the segments contained within the module. */ ndx = addr_segndx (dwfl, segment, module_end, true); @@ -518,7 +556,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
void *soname = NULL; size_t soname_size = 0; - if (dynstrsz != 0 && dynstr_vaddr != 0) + if (! name_is_final && dynstrsz != 0 && dynstr_vaddr != 0) { /* We know the bounds of the .dynstr section.
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 37a9dff..1307bdb 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -362,6 +362,16 @@ extern Dwfl_Error __libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, extern Dwfl_Error __libdwfl_cu_getsrclines (struct dwfl_cu *cu) internal_function;
+/* Look in ELF for an NT_GNU_BUILD_ID note. Store it to BUILD_ID_BITS, + its vaddr in ELF to BUILD_ID_VADDR (it is unrelocated, even if MOD is not + NULL) and store length to BUILD_ID_LEN. Returns -1 for errors, 1 if it was + stored and 0 if no note is found. MOD may be NULL, MOD must be non-NULL + only if ELF is ET_REL. */ +extern int __libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, + int *build_id_len); + /* Look in ELF for an NT_GNU_BUILD_ID note. If SET is true, store it in MOD and return its length. If SET is false, instead compare it to that stored in MOD and return 2 if they match, 1 if they do not. @@ -439,13 +449,35 @@ typedef bool Dwfl_Module_Callback (Dwfl_Module *mod, void **userdata, GElf_Off whole, GElf_Off contiguous, void *arg, Elf **elfp);
+/* One shared library (or executable) info from DT_DEBUG link map. */ +struct r_debug_info_module +{ + struct r_debug_info_module *next; + GElf_Addr l_ld; + char name[0]; +}; + +/* Information gathered from DT_DEBUG by dwfl_link_map_report hinted to + dwfl_segment_report_module. */ +struct r_debug_info +{ + /* Found l_addr and l_ld fields for vDSO. */ + GElf_Addr vdso_bias, vdso_l_ld; + + /* Found memory address of start of the executable. */ + GElf_Addr exec_vaddr; + + struct r_debug_info_module *module; +}; + /* ... */ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg); + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info);
/* Report a module for entry in the dynamic linker's struct link_map list. For each link_map entry, if an existing module resides at its address, @@ -459,10 +491,14 @@ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, only find where to begin if the correct executable file was previously reported and preloaded as with dwfl_report_elf.
+ Fill in R_DEBUG_INFO if it is not NULL. It should be cleared by the + caller, this function does not touch fields it does not need to modify. + Returns the number of modules found, or -1 for errors. */ extern int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg); + void *memory_callback_arg, + struct r_debug_info *r_debug_info);
/* Avoid PLT entries. */ diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c index 5dbf17e..c1bd60a 100644 --- a/libdwfl/link_map.c +++ b/libdwfl/link_map.c @@ -33,6 +33,7 @@
#include <byteswap.h> #include <endian.h> +#include <fcntl.h>
/* This element is always provided and always has a constant value. This makes it an easy thing to scan for to discern the format. */ @@ -222,7 +223,8 @@ addrsize (uint_fast8_t elfclass) }
/* Report a module for each struct link_map in the linked list at r_map - in the struct r_debug at R_DEBUG_VADDR. + in the struct r_debug at R_DEBUG_VADDR. For r_debug_info description + see dwfl_link_map_report in libdwflP.h.
For each link_map entry, if an existing module resides at its address, this just modifies that module's name and suggested file name. If @@ -234,7 +236,8 @@ static int report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, Dwfl *dwfl, GElf_Addr r_debug_vaddr, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { /* Skip r_version, to aligned r_map field. */ GElf_Addr read_vaddr = r_debug_vaddr + addrsize (elfclass); @@ -352,9 +355,108 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, /* We have to find the file's phdrs to compute along with l_addr what its runtime address boundaries are. */
- // XXX hook for sysroot - mod = INTUSE(dwfl_report_elf) (dwfl, basename (name), - name, -1, l_addr, true); + Dwfl_Module *mod = NULL; + if (iterations == 1 && dwfl->executable_for_core != NULL) + { + /* Find the main executable. + FIXME: Try to also find it via build-id. */ + name = dwfl->executable_for_core; + } + if (iterations != 2 && name != NULL) + { + /* Find a shared library or main executable. Do not try to + find a file for vDSO (where ITERATIONS equals 2). */ + + /* This code is mostly inlined dwfl_report_elf. */ + // XXX hook for sysroot + int fd = open64 (name, O_RDONLY); + if (fd >= 0) + { + Elf *elf; + Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false); + if (error == DWFL_E_NOERROR) + { + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + bool valid = true; + + /* FIXME: Bias L_ADDR should be computed from the prelink + state in memory (when the file got loaded), not against + the current on-disk file state as is computed below. + + This verification gives false positive if in-core ELF had + build-id but on-disk ELF does not have any. But we cannot + reliably find ELF header and/or the ELF build id just from + the link map (and checking core segments is also not + reliable). */ + + if (__libdwfl_find_elf_build_id (NULL, elf, &build_id_bits, + &build_id_elfaddr, + &build_id_len) > 0 + && build_id_elfaddr != 0) + { + GElf_Addr build_id_vaddr = build_id_elfaddr + l_addr; + release_buffer (0); + int segndx = INTUSE(dwfl_addrsegment) (dwfl, + build_id_vaddr, + NULL); + if (! (*memory_callback) (dwfl, segndx, + &buffer, &buffer_available, + build_id_vaddr, build_id_len, + memory_callback_arg) + || memcmp (build_id_bits, buffer, build_id_len) != 0) + { + /* File has valid build-id which cannot be verified + in memory. */ + valid = false; + } + } + + if (valid) + // XXX hook for sysroot + mod = __libdwfl_report_elf (dwfl, basename (name), name, + fd, elf, l_addr, true, true); + if (mod == NULL) + { + elf_end (elf); + close (fd); + } + } + } + } + if (mod != NULL && iterations == 1) + { + /* Cancel reporting of raw segments for the main executable as + its file has been found now. */ + if (r_debug_info != NULL) + r_debug_info->exec_vaddr = 0; + } + if (iterations == 2) + { + /* vDSO is only reported to the caller, it is never searched + for on the disk. */ + assert (mod == NULL); + if (r_debug_info != NULL) + { + r_debug_info->vdso_bias = l_addr; + r_debug_info->vdso_l_ld = l_ld; + } + } + if (r_debug_info != NULL && mod == NULL) + { + /* Save link map information about valid shared library (or + executable) which has not been found on disk. */ + const char *base = name == NULL ? "" : basename (name); + struct r_debug_info_module *module; + module = malloc (sizeof (*module) + strlen (base) + 1); + if (module == NULL) + return release_buffer (result); + module->l_ld = l_ld; + strcpy (module->name, base); + module->next = r_debug_info->module; + r_debug_info->module = module; + }
if (mod != NULL) { @@ -570,7 +672,8 @@ find_executable (Dwfl *dwfl, GElf_Addr at_phdr, GElf_Addr at_entry, int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { GElf_Addr r_debug_vaddr = 0;
@@ -632,8 +735,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, GElf_Xword dyn_filesz = 0; GElf_Addr dyn_bias = (GElf_Addr) -1;
- inline bool consider_phdr (GElf_Word type, - GElf_Addr vaddr, GElf_Xword filesz) + inline bool consider_phdr (GElf_Word type, GElf_Addr vaddr, + GElf_Xword filesz, Elf32_Off offset) { switch (type) { @@ -644,6 +747,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, == (phdr & (dwfl->segment_align - 1)))) { dyn_bias = phdr - vaddr; + if (r_debug_info != NULL) + r_debug_info->exec_vaddr = phdr - offset; return dyn_vaddr != 0; } break; @@ -668,8 +773,65 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, .d_size = phnum * phent, .d_buf = NULL }; - if ((*memory_callback) (dwfl, phdr_segndx, &in.d_buf, &in.d_size, - phdr, phnum * phent, memory_callback_arg)) + bool in_ok = (*memory_callback) (dwfl, phdr_segndx, &in.d_buf, + &in.d_size, phdr, phnum * phent, + memory_callback_arg); + if (! in_ok && dwfl->executable_for_core != NULL) + { + /* AUXV -> PHDR -> DYNAMIC + Both AUXV and DYNAMIC should be always present in a core file. + PHDR may be missing in core file, try to read it from + EXECUTABLE_FOR_CORE to find where DYNAMIC is located in the + core file. */ + + int fd = open (dwfl->executable_for_core, O_RDONLY); + Elf *elf; + Dwfl_Error error = DWFL_E_ERRNO; + if (fd != -1) + error = __libdw_open_file (&fd, &elf, true, false); + if (error != DWFL_E_NOERROR) + { + __libdwfl_seterrno (error); + return false; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + if (ehdr->e_phnum != phnum || ehdr->e_phentsize != phent) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_BADELF); + return false; + } + off_t off = ehdr->e_phoff; + assert (in.d_buf == NULL); + assert (in.d_size == phnum * phent); + in.d_buf = malloc (in.d_size); + if (unlikely (in.d_buf == NULL)) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + ssize_t nread = pread_retry (fd, in.d_buf, in.d_size, off); + elf_end (elf); + close (fd); + if (nread != (ssize_t) in.d_size) + { + free (in.d_buf); + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + in_ok = true; + } + if (in_ok) { union { @@ -700,7 +862,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, for (size_t i = 0; i < phnum; ++i) if (consider_phdr (u->p32[i].p_type, u->p32[i].p_vaddr, - u->p32[i].p_filesz)) + u->p32[i].p_filesz, + u->p32[i].p_offset)) break; } else @@ -708,7 +871,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, for (size_t i = 0; i < phnum; ++i) if (consider_phdr (u->p64[i].p_type, u->p64[i].p_vaddr, - u->p64[i].p_filesz)) + u->p64[i].p_filesz, + u->p64[i].p_offset)) break; } } @@ -831,6 +995,6 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size,
/* Now we can follow the dynamic linker's library list. */ return report_r_debug (elfclass, elfdata, dwfl, r_debug_vaddr, - &integrated_memory_callback, &mcb); + &integrated_memory_callback, &mcb, r_debug_info); } INTDEF (dwfl_link_map_report) diff --git a/tests/Makefile.am b/tests/Makefile.am index 6327edb..2d819c5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -199,7 +199,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ testfile70.core.bz2 testfile70.exec.bz2 \ run-dwfllines.sh run-dwfl-report-elf-align.sh \ testfile-dwfl-report-elf-align-shlib.so.bz2 \ - testfilenolines + testfilenolines test-core-lib.so.bz2 test-core.core.bz2 \ + test-core.exec.bz2
if USE_VALGRIND valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no' diff --git a/tests/run-addrname-test.sh b/tests/run-addrname-test.sh index 9351fb2..8624074 100755 --- a/tests/run-addrname-test.sh +++ b/tests/run-addrname-test.sh @@ -316,4 +316,10 @@ main+0x9 ??:0 EOF
+testfiles test-core-lib.so test-core.core test-core.exec +testrun_compare ${abs_top_builddir}/src/addr2line -S -e test-core.exec --core=test-core.core 0x7f67f2aaf619 <<\EOF +libfunc+0x9 +??:0 +EOF + exit 0 diff --git a/tests/run-unstrip-n.sh b/tests/run-unstrip-n.sh index 9c2b43b..b35d064 100755 --- a/tests/run-unstrip-n.sh +++ b/tests/run-unstrip-n.sh @@ -36,19 +36,34 @@ testfiles testcore-rtlib testcore-rtlib-ppc testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib <<\EOF 0x8048000+0x2000 f1c600bc36cb91bf01f9a63a634ecb79aa4c3199@0x8048178 . - [exe] 0xf77d6000+0x1000 676560b1b765cde9c2e53f134f4ee354ea894747@0xf77d6210 . - linux-gate.so.1 -0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 /lib/librt.so.1 - librt.so.1 -0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 /lib/libc.so.6 - libc.so.6 -0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 /lib/libpthread.so.0 - libpthread.so.0 -0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 /lib/ld-linux.so.2 - ld-linux.so.2 +0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 - - libpthread.so.0 +0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 - - libc.so.6 +0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 - - librt.so.1 +0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 - - ld-linux.so.2 EOF
testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib-ppc <<\EOF -0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] 0x100000+0x10000 708b900b05176964512a6b0fe90c2a0c9d73d726@0x100334 . - linux-vdso32.so.1 -0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 /lib/librt.so.1 - librt.so.1 -0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 /lib/libc.so.6 - libc.so.6 -0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 /lib/libpthread.so.0 - libpthread.so.0 -0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 /lib/ld.so.1 - ld.so.1 +0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] +0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 . - librt.so.1 +0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 - - libpthread.so.0 +0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 - - libc.so.6 +0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 - - ld.so.1 +EOF + +# FAIL was 0x7f67f2caf000 for test-core-lib.so . +# /lib64/libc.so.6 and /lib64/ld-linux-x86-64.so.2 from link map +# do not have ELF header stored in the core file. +# ELF headers in the core file: +# Offset VirtAddr +# 0x014000 0x00007f67f2caf000 ./test-core-lib.so +# 0x03a000 0x00007fff1596c000 linux-vdso.so.1 +testfiles test-core.core test-core.exec +rm -f test-core-lib.so +testrun_compare ${abs_top_builddir}/src/unstrip -n -e test-core.exec --core=test-core.core <<\EOF +0x400000+0x202038 - test-core.exec - test-core.exec +0x7fff1596c000+0x1000 a9cf37f53897b5468ee018655760be61b8633d3c@0x7fff1596c340 . - linux-vdso.so.1 +0x7f67f2aaf000+0x202000 - . - test-core-lib.so EOF
test_cleanup diff --git a/tests/test-core-lib.so.bz2 b/tests/test-core-lib.so.bz2 new file mode 100755 index 0000000..bb2da88 Binary files /dev/null and b/tests/test-core-lib.so.bz2 differ diff --git a/tests/test-core.core.bz2 b/tests/test-core.core.bz2 new file mode 100644 index 0000000..4d4346b Binary files /dev/null and b/tests/test-core.core.bz2 differ diff --git a/tests/test-core.exec.bz2 b/tests/test-core.exec.bz2 new file mode 100755 index 0000000..49ce551 Binary files /dev/null and b/tests/test-core.exec.bz2 differ
Hi Mark,
jankratochvil/move-core
just a small "fixup" handling fixup to consider only properly aligned mis-placements.
Thanks, Jan
commit 50e825e5ba27fc38a548961d42941ea7209ff588 Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 23 20:19:16 2013 +0200
Use DT_DEBUG library search first.
libdwfl/ 2013-05-24 Jan Kratochvil jan.kratochvil@redhat.com
* argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report. * core-file.c (free_r_debug_info): New function. (dwfl_core_file_report): Move raw segments reporting lower. New variable r_debug_info. (dwfl_core_file_report) (report_module): New function. (dwfl_core_file_report): Call also report_module for EXEC_VADDR and VDSO_L_LD. Call free_r_debug_info in the end. Return sum of LISTED and SNIFFED. * dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. * dwfl_segment_report_module.c (dwfl_segment_report_module): New parameter r_debug_info. New variable name_is_final. Adjust addresses according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. Do not overwise NAME by SONAME if NAME_IS_FINAL. * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (struct r_debug_info_module, struct r_debug_info): New definitions. (dwfl_segment_report_module, dwfl_link_map_report): Add parameter r_debug_info. * link_map.c: Include fcntl.h. (report_r_debug): Add parameter r_debug_info, describe them in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it and skip vDSO. Report vdso_bias and vdso_l_ld when found. Clear exec_vaddr when found. Store r_debug_info->module info when appropriate. (dwfl_link_map_report): Add parameter r_debug_info. (dwfl_link_map_report) (consider_phdr): Add parameter offset. Set r_debug_info.exec_vaddr when found. (dwfl_link_map_report): New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update consider_phdr caller parameters. Update report_r_debug caller parameters.
tests/ 2013-05-23 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. New test for these files. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file.
Signed-off-by: Jan Kratochvil jan.kratochvil@redhat.com
diff --git a/libdwfl/argp-std.c b/libdwfl/argp-std.c index e54f720..c884390 100644 --- a/libdwfl/argp-std.c +++ b/libdwfl/argp-std.c @@ -295,6 +295,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
if (opt->core) { + if (opt->e) + dwfl->executable_for_core = strdup (opt->e); + int fd = open64 (opt->core, O_RDONLY); if (fd < 0) { @@ -330,9 +333,6 @@ parse_opt (int key, char *arg, struct argp_state *state) _("No modules recognized in core file")); return ENOENT; } - - if (opt->e) - dwfl->executable_for_core = strdup (opt->e); } else if (opt->e) { diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index ac2aa8c..324bc5c 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -381,6 +381,19 @@ dwfl_elf_phdr_memory_callback (Dwfl *dwfl, int ndx, return true; }
+/* Free the contents of R_DEBUG_INFO without the R_DEBUG_INFO memory itself. */ + +static void +free_r_debug_info (struct r_debug_info *r_debug_info) +{ + while (r_debug_info->module != NULL) + { + struct r_debug_info_module *module = r_debug_info->module; + r_debug_info->module = module->next; + free (module); + } +} + int dwfl_core_file_report (Dwfl *dwfl, Elf *elf) { @@ -431,33 +444,85 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) /* Now we have NT_AUXV contents. From here on this processing could be used for a live process with auxv read from /proc. */
+ struct r_debug_info r_debug_info; + memset (&r_debug_info, 0, sizeof r_debug_info); int listed = dwfl_link_map_report (dwfl, auxv, auxv_size, - dwfl_elf_phdr_memory_callback, elf); + dwfl_elf_phdr_memory_callback, elf, + &r_debug_info); + + /* Call dwfl_segment_report_module, update *NDXP when appropriate. Return + true for a successfully reported module. Otherwise return false and also + call free_r_debug_info. */ + + inline bool report_module (int *ndxp, int *count, const char *name) + { + int seg = dwfl_segment_report_module (dwfl, *ndxp, name, + &dwfl_elf_phdr_memory_callback, elf, + core_file_read_eagerly, elf, + &r_debug_info); + if (unlikely (seg < 0)) + { + free_r_debug_info (&r_debug_info); + return false; + } + if (seg > *ndxp) + { + *ndxp = seg; + ++*count; + } + else + ++*ndxp; + return true; + } + + if (r_debug_info.vdso_l_ld != 0 || r_debug_info.exec_vaddr != 0) + for (ndx = 0; ndx < (int) phnum;) + { + GElf_Phdr phdr_mem; + GElf_Phdr *phdr = gelf_getphdr (elf, ndx, &phdr_mem); + if (unlikely (phdr == NULL)) + { + free_r_debug_info (&r_debug_info); + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + if (phdr->p_type == PT_LOAD && r_debug_info.exec_vaddr != 0 + && phdr->p_vaddr == r_debug_info.exec_vaddr) + { + if (unlikely (! report_module (&ndx, &listed, "[exe]"))) + return -1; + } + else if (phdr->p_type == PT_LOAD && r_debug_info.vdso_l_ld != 0 + && phdr->p_filesz == phdr->p_memsz + && phdr->p_vaddr <= r_debug_info.vdso_l_ld + && r_debug_info.vdso_l_ld < phdr->p_vaddr + phdr->p_memsz) + { + /* FIXME: Use vdso_bias. Currently dwfl_segment_report_module + detects the bias on its own. */ + if (unlikely (! report_module (&ndx, &listed, "[vdso]"))) + return -1; + } + else + ++ndx; + } + + /* Now sniff segment contents for modules hinted by information gathered + from DT_DEBUG. */
- /* Now sniff segment contents for modules. */ int sniffed = 0; - ndx = 0; - do - { - int seg = dwfl_segment_report_module (dwfl, ndx, NULL, - &dwfl_elf_phdr_memory_callback, elf, - core_file_read_eagerly, elf); - if (unlikely (seg < 0)) - return seg; - if (seg > ndx) - { - ndx = seg; - ++sniffed; - } - else - ++ndx; - } - while (ndx < (int) phnum); + ndx = 0; + do + { + if (unlikely (! report_module (&ndx, &sniffed, NULL) )) + return -1; + } + while (ndx < (int) phnum); + + free_r_debug_info (&r_debug_info);
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an - error rather than just nothing found. If link_map handling - failed, we still have the sniffed modules. */ - return sniffed == 0 || listed > sniffed ? listed : sniffed; + error rather than just nothing found. */ + return sniffed || listed >= 0 ? listed + sniffed : listed; } INTDEF (dwfl_core_file_report) diff --git a/libdwfl/dwfl_module_build_id.c b/libdwfl/dwfl_module_build_id.c index 98cb9f4..cae007b 100644 --- a/libdwfl/dwfl_module_build_id.c +++ b/libdwfl/dwfl_module_build_id.c @@ -56,10 +56,20 @@ found_build_id (Dwfl_Module *mod, bool set,
int internal_function -__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +__libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, int *build_id_len) { - int - check_notes (Dwfl_Module *mod, bool set, Elf_Data *data, GElf_Addr data_vaddr) + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (unlikely (ehdr == NULL)) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + // MOD->E_TYPE is zero here. + assert (ehdr->e_type != ET_REL || mod != NULL); + + int check_notes (Elf_Data *data, GElf_Addr data_elfaddr) { size_t pos = 0; GElf_Nhdr nhdr; @@ -69,10 +79,13 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == sizeof "GNU" && !memcmp (data->d_buf + name_pos, "GNU", sizeof "GNU")) - return found_build_id (mod, set, - data->d_buf + desc_pos, nhdr.n_descsz, - data_vaddr == NO_VADDR ? 0 - : data_vaddr + desc_pos); + { + *build_id_bits = data->d_buf + desc_pos; + *build_id_elfaddr = (data_elfaddr == NO_VADDR + ? 0 : data_elfaddr + desc_pos); + *build_id_len = nhdr.n_descsz; + return 1; + } return 0; }
@@ -84,11 +97,8 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (scn == NULL) { /* No sections, have to look for phdrs. */ - GElf_Ehdr ehdr_mem; - GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); size_t phnum; - if (unlikely (ehdr == NULL) - || unlikely (elf_getphdrnum (elf, &phnum) != 0)) + if (unlikely (elf_getphdrnum (elf, &phnum) != 0)) { __libdwfl_seterrno (DWFL_E_LIBELF); return -1; @@ -98,12 +108,11 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Phdr phdr_mem; GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); if (likely (phdr != NULL) && phdr->p_type == PT_NOTE) - result = check_notes (mod, set, - elf_getdata_rawchunk (elf, + result = check_notes (elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz, ELF_T_NHDR), - dwfl_adjusted_address (mod, phdr->p_vaddr)); + phdr->p_vaddr); } } else @@ -117,12 +126,12 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Addr vaddr = 0; if (!(shdr->sh_flags & SHF_ALLOC)) vaddr = NO_VADDR; - else if (mod->e_type != ET_REL) - vaddr = dwfl_adjusted_address (mod, shdr->sh_addr); + else if (mod == NULL || ehdr->e_type != ET_REL) + vaddr = shdr->sh_addr; else if (__libdwfl_relocate_value (mod, elf, &shstrndx, elf_ndxscn (scn), &vaddr)) vaddr = NO_VADDR; - result = check_notes (mod, set, elf_getdata (scn, NULL), vaddr); + result = check_notes (elf_getdata (scn, NULL), vaddr); } } while (result == 0 && (scn = elf_nextscn (elf, scn)) != NULL); @@ -131,6 +140,24 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) }
int +internal_function +__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +{ + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + + int result = __libdwfl_find_elf_build_id (mod, elf, &build_id_bits, + &build_id_elfaddr, &build_id_len); + if (result <= 0) + return result; + + GElf_Addr build_id_vaddr = build_id_elfaddr + (build_id_elfaddr != 0 + ? mod->main_bias : 0); + return found_build_id (mod, set, build_id_bits, build_id_len, build_id_vaddr); +} + +int dwfl_module_build_id (Dwfl_Module *mod, const unsigned char **bits, GElf_Addr *vaddr) { diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 7cf7499..26c422e 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -83,7 +83,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg) + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info) { size_t segment = ndx;
@@ -433,6 +434,44 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
dyn_vaddr += bias;
+ /* NAME found from link map has precedence over DT_SONAME possibly read + below. */ + bool name_is_final = false; + + /* Try to match up DYN_VADDR against L_LD as found in link map. + Segments sniffing may guess invalid address as the first read-only memory + mapping may not be dumped to the core file (if ELF headers are not dumped) + and the ELF header is dumped first with the read/write mapping of the same + file at higher addresses. */ + if (r_debug_info != NULL) + for (const struct r_debug_info_module *module = r_debug_info->module; + module != NULL; module = module->next) + if (module_start <= module->l_ld && module->l_ld < module_end) + { + GElf_Addr fixup = module->l_ld - dyn_vaddr; + if ((fixup & (dwfl->segment_align - 1)) == 0 + && module_start + fixup <= module->l_ld + && module->l_ld < module_end + fixup) + { + module_start += fixup; + module_end += fixup; + dyn_vaddr += fixup; + bias += fixup; + if (module->name[0] != '\0') + { + name = module->name; + name_is_final = true; + } + break; + } + } + + /* Ignore this found module if it would conflict in address space with any + already existing module of DWFL. */ + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + if (module_end > mod->low_addr && module_start < mod->high_addr) + return finish (); + /* Our return value now says to skip the segments contained within the module. */ ndx = addr_segndx (dwfl, segment, module_end, true); @@ -518,7 +557,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
void *soname = NULL; size_t soname_size = 0; - if (dynstrsz != 0 && dynstr_vaddr != 0) + if (! name_is_final && dynstrsz != 0 && dynstr_vaddr != 0) { /* We know the bounds of the .dynstr section.
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 37a9dff..1307bdb 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -362,6 +362,16 @@ extern Dwfl_Error __libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, extern Dwfl_Error __libdwfl_cu_getsrclines (struct dwfl_cu *cu) internal_function;
+/* Look in ELF for an NT_GNU_BUILD_ID note. Store it to BUILD_ID_BITS, + its vaddr in ELF to BUILD_ID_VADDR (it is unrelocated, even if MOD is not + NULL) and store length to BUILD_ID_LEN. Returns -1 for errors, 1 if it was + stored and 0 if no note is found. MOD may be NULL, MOD must be non-NULL + only if ELF is ET_REL. */ +extern int __libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, + int *build_id_len); + /* Look in ELF for an NT_GNU_BUILD_ID note. If SET is true, store it in MOD and return its length. If SET is false, instead compare it to that stored in MOD and return 2 if they match, 1 if they do not. @@ -439,13 +449,35 @@ typedef bool Dwfl_Module_Callback (Dwfl_Module *mod, void **userdata, GElf_Off whole, GElf_Off contiguous, void *arg, Elf **elfp);
+/* One shared library (or executable) info from DT_DEBUG link map. */ +struct r_debug_info_module +{ + struct r_debug_info_module *next; + GElf_Addr l_ld; + char name[0]; +}; + +/* Information gathered from DT_DEBUG by dwfl_link_map_report hinted to + dwfl_segment_report_module. */ +struct r_debug_info +{ + /* Found l_addr and l_ld fields for vDSO. */ + GElf_Addr vdso_bias, vdso_l_ld; + + /* Found memory address of start of the executable. */ + GElf_Addr exec_vaddr; + + struct r_debug_info_module *module; +}; + /* ... */ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg); + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info);
/* Report a module for entry in the dynamic linker's struct link_map list. For each link_map entry, if an existing module resides at its address, @@ -459,10 +491,14 @@ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, only find where to begin if the correct executable file was previously reported and preloaded as with dwfl_report_elf.
+ Fill in R_DEBUG_INFO if it is not NULL. It should be cleared by the + caller, this function does not touch fields it does not need to modify. + Returns the number of modules found, or -1 for errors. */ extern int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg); + void *memory_callback_arg, + struct r_debug_info *r_debug_info);
/* Avoid PLT entries. */ diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c index 5dbf17e..c1bd60a 100644 --- a/libdwfl/link_map.c +++ b/libdwfl/link_map.c @@ -33,6 +33,7 @@
#include <byteswap.h> #include <endian.h> +#include <fcntl.h>
/* This element is always provided and always has a constant value. This makes it an easy thing to scan for to discern the format. */ @@ -222,7 +223,8 @@ addrsize (uint_fast8_t elfclass) }
/* Report a module for each struct link_map in the linked list at r_map - in the struct r_debug at R_DEBUG_VADDR. + in the struct r_debug at R_DEBUG_VADDR. For r_debug_info description + see dwfl_link_map_report in libdwflP.h.
For each link_map entry, if an existing module resides at its address, this just modifies that module's name and suggested file name. If @@ -234,7 +236,8 @@ static int report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, Dwfl *dwfl, GElf_Addr r_debug_vaddr, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { /* Skip r_version, to aligned r_map field. */ GElf_Addr read_vaddr = r_debug_vaddr + addrsize (elfclass); @@ -352,9 +355,108 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, /* We have to find the file's phdrs to compute along with l_addr what its runtime address boundaries are. */
- // XXX hook for sysroot - mod = INTUSE(dwfl_report_elf) (dwfl, basename (name), - name, -1, l_addr, true); + Dwfl_Module *mod = NULL; + if (iterations == 1 && dwfl->executable_for_core != NULL) + { + /* Find the main executable. + FIXME: Try to also find it via build-id. */ + name = dwfl->executable_for_core; + } + if (iterations != 2 && name != NULL) + { + /* Find a shared library or main executable. Do not try to + find a file for vDSO (where ITERATIONS equals 2). */ + + /* This code is mostly inlined dwfl_report_elf. */ + // XXX hook for sysroot + int fd = open64 (name, O_RDONLY); + if (fd >= 0) + { + Elf *elf; + Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false); + if (error == DWFL_E_NOERROR) + { + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + bool valid = true; + + /* FIXME: Bias L_ADDR should be computed from the prelink + state in memory (when the file got loaded), not against + the current on-disk file state as is computed below. + + This verification gives false positive if in-core ELF had + build-id but on-disk ELF does not have any. But we cannot + reliably find ELF header and/or the ELF build id just from + the link map (and checking core segments is also not + reliable). */ + + if (__libdwfl_find_elf_build_id (NULL, elf, &build_id_bits, + &build_id_elfaddr, + &build_id_len) > 0 + && build_id_elfaddr != 0) + { + GElf_Addr build_id_vaddr = build_id_elfaddr + l_addr; + release_buffer (0); + int segndx = INTUSE(dwfl_addrsegment) (dwfl, + build_id_vaddr, + NULL); + if (! (*memory_callback) (dwfl, segndx, + &buffer, &buffer_available, + build_id_vaddr, build_id_len, + memory_callback_arg) + || memcmp (build_id_bits, buffer, build_id_len) != 0) + { + /* File has valid build-id which cannot be verified + in memory. */ + valid = false; + } + } + + if (valid) + // XXX hook for sysroot + mod = __libdwfl_report_elf (dwfl, basename (name), name, + fd, elf, l_addr, true, true); + if (mod == NULL) + { + elf_end (elf); + close (fd); + } + } + } + } + if (mod != NULL && iterations == 1) + { + /* Cancel reporting of raw segments for the main executable as + its file has been found now. */ + if (r_debug_info != NULL) + r_debug_info->exec_vaddr = 0; + } + if (iterations == 2) + { + /* vDSO is only reported to the caller, it is never searched + for on the disk. */ + assert (mod == NULL); + if (r_debug_info != NULL) + { + r_debug_info->vdso_bias = l_addr; + r_debug_info->vdso_l_ld = l_ld; + } + } + if (r_debug_info != NULL && mod == NULL) + { + /* Save link map information about valid shared library (or + executable) which has not been found on disk. */ + const char *base = name == NULL ? "" : basename (name); + struct r_debug_info_module *module; + module = malloc (sizeof (*module) + strlen (base) + 1); + if (module == NULL) + return release_buffer (result); + module->l_ld = l_ld; + strcpy (module->name, base); + module->next = r_debug_info->module; + r_debug_info->module = module; + }
if (mod != NULL) { @@ -570,7 +672,8 @@ find_executable (Dwfl *dwfl, GElf_Addr at_phdr, GElf_Addr at_entry, int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { GElf_Addr r_debug_vaddr = 0;
@@ -632,8 +735,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, GElf_Xword dyn_filesz = 0; GElf_Addr dyn_bias = (GElf_Addr) -1;
- inline bool consider_phdr (GElf_Word type, - GElf_Addr vaddr, GElf_Xword filesz) + inline bool consider_phdr (GElf_Word type, GElf_Addr vaddr, + GElf_Xword filesz, Elf32_Off offset) { switch (type) { @@ -644,6 +747,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, == (phdr & (dwfl->segment_align - 1)))) { dyn_bias = phdr - vaddr; + if (r_debug_info != NULL) + r_debug_info->exec_vaddr = phdr - offset; return dyn_vaddr != 0; } break; @@ -668,8 +773,65 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, .d_size = phnum * phent, .d_buf = NULL }; - if ((*memory_callback) (dwfl, phdr_segndx, &in.d_buf, &in.d_size, - phdr, phnum * phent, memory_callback_arg)) + bool in_ok = (*memory_callback) (dwfl, phdr_segndx, &in.d_buf, + &in.d_size, phdr, phnum * phent, + memory_callback_arg); + if (! in_ok && dwfl->executable_for_core != NULL) + { + /* AUXV -> PHDR -> DYNAMIC + Both AUXV and DYNAMIC should be always present in a core file. + PHDR may be missing in core file, try to read it from + EXECUTABLE_FOR_CORE to find where DYNAMIC is located in the + core file. */ + + int fd = open (dwfl->executable_for_core, O_RDONLY); + Elf *elf; + Dwfl_Error error = DWFL_E_ERRNO; + if (fd != -1) + error = __libdw_open_file (&fd, &elf, true, false); + if (error != DWFL_E_NOERROR) + { + __libdwfl_seterrno (error); + return false; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + if (ehdr->e_phnum != phnum || ehdr->e_phentsize != phent) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_BADELF); + return false; + } + off_t off = ehdr->e_phoff; + assert (in.d_buf == NULL); + assert (in.d_size == phnum * phent); + in.d_buf = malloc (in.d_size); + if (unlikely (in.d_buf == NULL)) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + ssize_t nread = pread_retry (fd, in.d_buf, in.d_size, off); + elf_end (elf); + close (fd); + if (nread != (ssize_t) in.d_size) + { + free (in.d_buf); + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + in_ok = true; + } + if (in_ok) { union { @@ -700,7 +862,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, for (size_t i = 0; i < phnum; ++i) if (consider_phdr (u->p32[i].p_type, u->p32[i].p_vaddr, - u->p32[i].p_filesz)) + u->p32[i].p_filesz, + u->p32[i].p_offset)) break; } else @@ -708,7 +871,8 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, for (size_t i = 0; i < phnum; ++i) if (consider_phdr (u->p64[i].p_type, u->p64[i].p_vaddr, - u->p64[i].p_filesz)) + u->p64[i].p_filesz, + u->p64[i].p_offset)) break; } } @@ -831,6 +995,6 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size,
/* Now we can follow the dynamic linker's library list. */ return report_r_debug (elfclass, elfdata, dwfl, r_debug_vaddr, - &integrated_memory_callback, &mcb); + &integrated_memory_callback, &mcb, r_debug_info); } INTDEF (dwfl_link_map_report) diff --git a/tests/Makefile.am b/tests/Makefile.am index 6327edb..2d819c5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -199,7 +199,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ testfile70.core.bz2 testfile70.exec.bz2 \ run-dwfllines.sh run-dwfl-report-elf-align.sh \ testfile-dwfl-report-elf-align-shlib.so.bz2 \ - testfilenolines + testfilenolines test-core-lib.so.bz2 test-core.core.bz2 \ + test-core.exec.bz2
if USE_VALGRIND valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no' diff --git a/tests/run-addrname-test.sh b/tests/run-addrname-test.sh index 9351fb2..8624074 100755 --- a/tests/run-addrname-test.sh +++ b/tests/run-addrname-test.sh @@ -316,4 +316,10 @@ main+0x9 ??:0 EOF
+testfiles test-core-lib.so test-core.core test-core.exec +testrun_compare ${abs_top_builddir}/src/addr2line -S -e test-core.exec --core=test-core.core 0x7f67f2aaf619 <<\EOF +libfunc+0x9 +??:0 +EOF + exit 0 diff --git a/tests/run-unstrip-n.sh b/tests/run-unstrip-n.sh index 9c2b43b..b35d064 100755 --- a/tests/run-unstrip-n.sh +++ b/tests/run-unstrip-n.sh @@ -36,19 +36,34 @@ testfiles testcore-rtlib testcore-rtlib-ppc testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib <<\EOF 0x8048000+0x2000 f1c600bc36cb91bf01f9a63a634ecb79aa4c3199@0x8048178 . - [exe] 0xf77d6000+0x1000 676560b1b765cde9c2e53f134f4ee354ea894747@0xf77d6210 . - linux-gate.so.1 -0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 /lib/librt.so.1 - librt.so.1 -0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 /lib/libc.so.6 - libc.so.6 -0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 /lib/libpthread.so.0 - libpthread.so.0 -0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 /lib/ld-linux.so.2 - ld-linux.so.2 +0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 - - libpthread.so.0 +0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 - - libc.so.6 +0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 - - librt.so.1 +0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 - - ld-linux.so.2 EOF
testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib-ppc <<\EOF -0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] 0x100000+0x10000 708b900b05176964512a6b0fe90c2a0c9d73d726@0x100334 . - linux-vdso32.so.1 -0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 /lib/librt.so.1 - librt.so.1 -0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 /lib/libc.so.6 - libc.so.6 -0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 /lib/libpthread.so.0 - libpthread.so.0 -0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 /lib/ld.so.1 - ld.so.1 +0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] +0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 . - librt.so.1 +0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 - - libpthread.so.0 +0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 - - libc.so.6 +0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 - - ld.so.1 +EOF + +# FAIL was 0x7f67f2caf000 for test-core-lib.so . +# /lib64/libc.so.6 and /lib64/ld-linux-x86-64.so.2 from link map +# do not have ELF header stored in the core file. +# ELF headers in the core file: +# Offset VirtAddr +# 0x014000 0x00007f67f2caf000 ./test-core-lib.so +# 0x03a000 0x00007fff1596c000 linux-vdso.so.1 +testfiles test-core.core test-core.exec +rm -f test-core-lib.so +testrun_compare ${abs_top_builddir}/src/unstrip -n -e test-core.exec --core=test-core.core <<\EOF +0x400000+0x202038 - test-core.exec - test-core.exec +0x7fff1596c000+0x1000 a9cf37f53897b5468ee018655760be61b8633d3c@0x7fff1596c340 . - linux-vdso.so.1 +0x7f67f2aaf000+0x202000 - . - test-core-lib.so EOF
test_cleanup diff --git a/tests/test-core-lib.so.bz2 b/tests/test-core-lib.so.bz2 new file mode 100755 index 0000000..bb2da88 Binary files /dev/null and b/tests/test-core-lib.so.bz2 differ diff --git a/tests/test-core.core.bz2 b/tests/test-core.core.bz2 new file mode 100644 index 0000000..4d4346b Binary files /dev/null and b/tests/test-core.core.bz2 differ diff --git a/tests/test-core.exec.bz2 b/tests/test-core.exec.bz2 new file mode 100755 index 0000000..49ce551 Binary files /dev/null and b/tests/test-core.exec.bz2 differ
On Thu, 2013-05-23 at 21:15 +0200, Jan Kratochvil wrote:
commit 457b771b842a0f6d23118e4b39d93f6581fb9b41 Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 23 20:19:16 2013 +0200
Use DT_DEBUG library search first. libdwfl/ 2013-05-23 Jan Kratochvil <jan.kratochvil@redhat.com> * argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report.
OK.
* core-file.c (free_r_debug_info): New function.
Since it doesn't actually free the struct itself, lets call it clear_r_debug_info to avoid confusion.
(dwfl_core_file_report): Move raw segments reporting lower. New variable r_debug_info. (dwfl_core_file_report) (report_module): New function. (dwfl_core_file_report): Call also report_module for EXEC_VADDR and VDSO_L_LD. Call free_r_debug_info in the end. Return sum of LISTED and SNIFFED.
OK, but see comments below.
* dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id.
OK.
* dwfl_segment_report_module.c (dwfl_segment_report_module): New parameter r_debug_info. New variable name_is_final. Adjust addresses according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. Do not overwise NAME by SONAME if NAME_IS_FINAL.
s/overwise/overwrite/ Also see some questions below.
* libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (struct r_debug_info_module, struct r_debug_info): New definitions. (dwfl_segment_report_module, dwfl_link_map_report): Add parameter r_debug_info.
OK.
* link_map.c: Include fcntl.h. (report_r_debug): Add parameter r_debug_info, describe them in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it and skip vDSO. Report vdso_bias and vdso_l_ld when found. Clear exec_vaddr when found. Store r_debug_info->module info when appropriate. (dwfl_link_map_report): Add parameter r_debug_info. (dwfl_link_map_report) (consider_phdr): Add parameter offset. Set r_debug_info.exec_vaddr when found. (dwfl_link_map_report): New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update consider_phdr caller parameters. Update report_r_debug caller parameters.
Was a bit hard to follow since I didn't know much about the link-map, and there is no real documentation it seems except the glibc sources. But in general looks OK. See some questions below.
tests/ 2013-05-23 Jan Kratochvil <jan.kratochvil@redhat.com> * Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. New test for these files. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file.
The changes in test results now make sense to me. Reporting the full file names was arguably wrong to begin with. I don't think we should restore that, but if people do feel this is a regression we could add a guesser to unstrip.c itself (but I don't think we should).
int dwfl_core_file_report (Dwfl *dwfl, Elf *elf) { @@ -431,33 +444,85 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) /* Now we have NT_AUXV contents. From here on this processing could be used for a live process with auxv read from /proc. */
- struct r_debug_info r_debug_info;
- memset (&r_debug_info, 0, sizeof r_debug_info); int listed = dwfl_link_map_report (dwfl, auxv, auxv_size,
dwfl_elf_phdr_memory_callback, elf);
dwfl_elf_phdr_memory_callback, elf,
&r_debug_info);
- /* Call dwfl_segment_report_module, update *NDXP when appropriate. Return
true for a successfully reported module. Otherwise return false and also
call free_r_debug_info. */
- inline bool report_module (int *ndxp, int *count, const char *name)
Document that count gets updated on success.
The clearing of r_debug_info on failure is a nice because it prevents accidental memory leaks, but also confused me on first read of the patch (because I forgot the comment when reading the later code).
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an
error rather than just nothing found. If link_map handling
failed, we still have the sniffed modules. */
- return sniffed == 0 || listed > sniffed ? listed : sniffed;
error rather than just nothing found. */
- return sniffed || listed >= 0 ? listed + sniffed : listed;
What about the case of sniffed == 0 && listed == 0? Should that return -1?
diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 7cf7499..e2d0337 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -83,7 +83,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly,
void *read_eagerly_arg)
void *read_eagerly_arg,
const struct r_debug_info *r_debug_info)
{ size_t segment = ndx;
@@ -433,6 +434,43 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
dyn_vaddr += bias;
- /* NAME found from link map has precedence over DT_SONAME possibly read
below. */
- bool name_is_final = false;
- /* Try to match up DYN_VADDR against L_LD as found in link map.
Segments sniffing may guess invalid address as the first read-only memory
mapping may not be dumped to the core file (if ELF headers are not dumped)
and the ELF header is dumped first with the read/write mapping of the same
file at higher addresses. */
- if (r_debug_info != NULL)
- for (const struct r_debug_info_module *module = r_debug_info->module;
module != NULL; module = module->next)
if (module_start <= module->l_ld && module->l_ld < module_end)
- {
GElf_Addr fixup = module->l_ld - dyn_vaddr;
if (module_start + fixup <= module->l_ld
&& module->l_ld < module_end + fixup)
{
module_start += fixup;
module_end += fixup;
dyn_vaddr += fixup;
bias += fixup;
if (module->name[0] != '\0')
{
name = module->name;
name_is_final = true;
}
break;
}
- }
fixup might need a comment. It wasn't immediately clear to me. The fixup is the difference between the PT_LOAD address that the link-map know is the correct one and the PT_DYNAMIC address (of which there can be only one). And module_start points to the PT_LOAD address that was just gotten from the phdrs. Right? Actually now that I do understand, that is kind of what the comment above already says. I just didn't immediately made the connection with the offset/fixup being static. Is fixup always positive?
BTW. Why does link-map set module->name to the empty string when no name (NULL) was found? Just convenience like here, so no NULL check is needed, or is the empty name significant?
diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c @@ -352,9 +355,108 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, /* We have to find the file's phdrs to compute along with l_addr what its runtime address boundaries are. */
// XXX hook for sysroot
mod = INTUSE(dwfl_report_elf) (dwfl, basename (name),
name, -1, l_addr, true);
Dwfl_Module *mod = NULL;
if (iterations == 1 && dwfl->executable_for_core != NULL)
- {
/* Find the main executable.
FIXME: Try to also find it via build-id. */
name = dwfl->executable_for_core;
- }
To solve that FIXME we should do somewhat the same as is done below with the "inlined dwfl_report_elf" snippet? And we already do try to open the executable if we cannot read the phdrs. Maybe this can be factored out a bit? On the other hand, we are going way beyond call of duty here just to read a somewhat incomplete/corrupt core file. So it might be lots of work for not much gain.
if (iterations != 2 && name != NULL)
- {
/* Find a shared library or main executable. Do not try to
find a file for vDSO (where ITERATIONS equals 2). */
I couldn't find where the iteration == 1 => exe, iteration == 2 => vDSO comes from. Shouldn't the second check be iterations > 2 ?
/* This code is mostly inlined dwfl_report_elf. */
// XXX hook for sysroot
int fd = open64 (name, O_RDONLY);
if (fd >= 0)
{
Elf *elf;
Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false);
if (error == DWFL_E_NOERROR)
{
const void *build_id_bits;
GElf_Addr build_id_elfaddr;
int build_id_len;
bool valid = true;
/* FIXME: Bias L_ADDR should be computed from the prelink
state in memory (when the file got loaded), not against
the current on-disk file state as is computed below.
This verification gives false positive if in-core ELF had
build-id but on-disk ELF does not have any. But we cannot
reliably find ELF header and/or the ELF build id just from
the link map (and checking core segments is also not
reliable). */
if (__libdwfl_find_elf_build_id (NULL, elf, &build_id_bits,
&build_id_elfaddr,
&build_id_len) > 0
&& build_id_elfaddr != 0)
{
GElf_Addr build_id_vaddr = build_id_elfaddr + l_addr;
release_buffer (0);
int segndx = INTUSE(dwfl_addrsegment) (dwfl,
build_id_vaddr,
NULL);
if (! (*memory_callback) (dwfl, segndx,
&buffer, &buffer_available,
build_id_vaddr, build_id_len,
memory_callback_arg)
|| memcmp (build_id_bits, buffer, build_id_len) != 0)
{
/* File has valid build-id which cannot be verified
in memory. */
valid = false;
}
}
if (valid)
// XXX hook for sysroot
mod = __libdwfl_report_elf (dwfl, basename (name), name,
fd, elf, l_addr, true, true);
if (mod == NULL)
{
elf_end (elf);
close (fd);
}
}
}
- }
if (mod != NULL && iterations == 1)
- {
/* Cancel reporting of raw segments for the main executable as
its file has been found now. */
if (r_debug_info != NULL)
r_debug_info->exec_vaddr = 0;
- }
if (iterations == 2)
- {
/* vDSO is only reported to the caller, it is never searched
for on the disk. */
assert (mod == NULL);
if (r_debug_info != NULL)
{
r_debug_info->vdso_bias = l_addr;
r_debug_info->vdso_l_ld = l_ld;
}
- }
if (r_debug_info != NULL && mod == NULL)
- {
/* Save link map information about valid shared library (or
executable) which has not been found on disk. */
const char *base = name == NULL ? "" : basename (name);
struct r_debug_info_module *module;
module = malloc (sizeof (*module) + strlen (base) + 1);
if (module == NULL)
return release_buffer (result);
module->l_ld = l_ld;
strcpy (module->name, base);
module->next = r_debug_info->module;
r_debug_info->module = module;
- }
Should this last if condition have iterations > 2 && ... ? Or do you want the executable and vdso also in the r_debug_info?
@@ -668,8 +773,65 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, .d_size = phnum * phent, .d_buf = NULL };
if ((*memory_callback) (dwfl, phdr_segndx, &in.d_buf, &in.d_size,
phdr, phnum * phent, memory_callback_arg))
bool in_ok = (*memory_callback) (dwfl, phdr_segndx, &in.d_buf,
&in.d_size, phdr, phnum * phent,
memory_callback_arg);
if (! in_ok && dwfl->executable_for_core != NULL)
{
/* AUXV -> PHDR -> DYNAMIC
Both AUXV and DYNAMIC should be always present in a core file.
PHDR may be missing in core file, try to read it from
EXECUTABLE_FOR_CORE to find where DYNAMIC is located in the
core file. */
int fd = open (dwfl->executable_for_core, O_RDONLY);
Elf *elf;
Dwfl_Error error = DWFL_E_ERRNO;
if (fd != -1)
error = __libdw_open_file (&fd, &elf, true, false);
if (error != DWFL_E_NOERROR)
{
__libdwfl_seterrno (error);
return false;
}
GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem);
if (ehdr == NULL)
{
elf_end (elf);
close (fd);
__libdwfl_seterrno (DWFL_E_LIBELF);
return false;
}
if (ehdr->e_phnum != phnum || ehdr->e_phentsize != phent)
{
elf_end (elf);
close (fd);
__libdwfl_seterrno (DWFL_E_BADELF);
return false;
}
off_t off = ehdr->e_phoff;
assert (in.d_buf == NULL);
assert (in.d_size == phnum * phent);
in.d_buf = malloc (in.d_size);
if (unlikely (in.d_buf == NULL))
{
elf_end (elf);
close (fd);
__libdwfl_seterrno (DWFL_E_NOMEM);
return false;
}
ssize_t nread = pread_retry (fd, in.d_buf, in.d_size, off);
elf_end (elf);
close (fd);
if (nread != (ssize_t) in.d_size)
{
free (in.d_buf);
__libdwfl_seterrno (DWFL_E_ERRNO);
return false;
}
in_ok = true;
}
This looks right. But what a lot of extra work just so some parts can be left out of a core file. And we better hope the given executable really matches the core file.
Even though I had some questions above I do think in general this looks OK.
Thanks,
Mark
On Mon, 27 May 2013 16:05:46 +0200, Mark Wielaard wrote:
Since it doesn't actually free the struct itself, lets call it clear_r_debug_info to avoid confusion.
Done.
Reporting the full file names was arguably wrong to begin with. I don't think we should restore that, but if people do feel this is a regression we could add a guesser to unstrip.c itself (but I don't think we should).
The full pathnames were clearly a bug for this patchset.
But when we talk about it in general, as a new patchset, I rather find a bug dropping the path, when core file already contains the absolute pathname why to hide it from the user?
But when we talk about some possible further extensions we should rather forget about the whole link map and just use NT_FILE. Or one can just already display the full pathnames easily (not in eu-readelf): $ readelf -n test-core.core Start End Page Offset 0x0000000000400000 0x0000000000402000 0x0000000000000000 /home/jkratoch/redhat/elfutils-libregr/test-core-main 0x00000037ef400000 0x00000037ef420000 0x0000000000000000 /usr/lib64/ld-2.16.so
Document that count gets updated on success.
The report_module helper is gone now.
The clearing of r_debug_info on failure is a nice because it prevents accidental memory leaks, but also confused me on first read of the patch (because I forgot the comment when reading the later code).
The report_module helper is gone now.
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an
error rather than just nothing found. If link_map handling
failed, we still have the sniffed modules. */
- return sniffed == 0 || listed > sniffed ? listed : sniffed;
error rather than just nothing found. */
- return sniffed || listed >= 0 ? listed + sniffed : listed;
What about the case of sniffed == 0 && listed == 0? Should that return -1?
It did not return -1 before so why we should now? There may exist valid core file which just does not contain any ELF module (=does not contain any identifiable ELF module).
A different case is when there is some real unexpected failure processing the data structures, in such case the function still returns -1, either be an early return -1 call or due to LISTED == -1 (from report_r_debug() returning -1).
- /* Try to match up DYN_VADDR against L_LD as found in link map.
Segments sniffing may guess invalid address as the first read-only memory
mapping may not be dumped to the core file (if ELF headers are not dumped)
and the ELF header is dumped first with the read/write mapping of the same
file at higher addresses. */
- if (r_debug_info != NULL)
- for (const struct r_debug_info_module *module = r_debug_info->module;
module != NULL; module = module->next)
if (module_start <= module->l_ld && module->l_ld < module_end)
- {
GElf_Addr fixup = module->l_ld - dyn_vaddr;
[...]
fixup might need a comment.
added: /* L_LD read from link map must be right while DYN_VADDR is unsafe. Therefore subtract DYN_VADDR and add L_LD to get a possibly corrective displacement for all addresses computed so far. */
It wasn't immediately clear to me. The fixup is the difference between the PT_LOAD address that the link-map know is the correct one and the PT_DYNAMIC address (of which there can be only one). And module_start points to the PT_LOAD address that was just gotten from the phdrs. Right?
Right.
Actually now that I do understand, that is kind of what the comment above already says. I just didn't immediately made the connection with the offset/fixup being static. Is fixup always positive?
At least in the supplied testcase it is negative.
In fact I did not spent time thinking if it is positive or negative, I was following the logic which address is safe and which unsafe above.
BTW. Why does link-map set module->name to the empty string when no name (NULL) was found? Just convenience like here, so no NULL check is needed, or is the empty name significant?
'name' is a GCC zero-length array, I am sorry but I cannot make it NULL: struct r_debug_info_module { [...] char name[0]; };
diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c @@ -352,9 +355,108 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, /* We have to find the file's phdrs to compute along with l_addr what its runtime address boundaries are. */
// XXX hook for sysroot
mod = INTUSE(dwfl_report_elf) (dwfl, basename (name),
name, -1, l_addr, true);
Dwfl_Module *mod = NULL;
if (iterations == 1 && dwfl->executable_for_core != NULL)
- {
/* Find the main executable.
FIXME: Try to also find it via build-id. */
name = dwfl->executable_for_core;
- }
To solve that FIXME we should do somewhat the same as is done below with the "inlined dwfl_report_elf" snippet? And we already do try to open the executable if we cannot read the phdrs. Maybe this can be factored out a bit?
In fact when we do not have any executable filename or we failed to open the executable filename the only thing we can do is currently already done by the second pass of dwfl_segment_report_module calls with the r_debug_info hints.
This code was there before I implemented the second pass even after the link map pass succeeded (found >= 1 shared libraries).
So I have removed the FIXME comment and simplified it all a bit.
if (iterations != 2 && name != NULL)
- {
/* Find a shared library or main executable. Do not try to
find a file for vDSO (where ITERATIONS equals 2). */
I couldn't find where the iteration == 1 => exe, iteration == 2 => vDSO comes from. Shouldn't the second check be iterations > 2 ?
As discussed on #elfutils this ITERATIONS == 2 condition was unsafe, while currently vDSO is the 2nd entry with glibc at least with older or non-Linux core files 2nd entry is a regular shared library.
One could make some name-based exceptions ("linux-vdso.so.1") like in http://pkgs.fedoraproject.org/cgit/gdb.git/tree/gdb-glibc-vdso-workaround.pa...
I found also this case is now obsoleted by the second dwfl_segment_report_module pass, finding the vDSO did not do anything else than just to call dwfl_segment_report_module for its segment.
if (r_debug_info != NULL && mod == NULL)
- {
/* Save link map information about valid shared library (or
executable) which has not been found on disk. */
const char *base = name == NULL ? "" : basename (name);
struct r_debug_info_module *module;
module = malloc (sizeof (*module) + strlen (base) + 1);
if (module == NULL)
return release_buffer (result);
module->l_ld = l_ld;
strcpy (module->name, base);
module->next = r_debug_info->module;
r_debug_info->module = module;
- }
Should this last if condition have iterations > 2 && ... ?
No.
Or do you want the executable and vdso also in the r_debug_info?
vDSO does not matter and executable should be in r_debug_info.
This looks right. But what a lot of extra work just so some parts can be left out of a core file. And we better hope the given executable really matches the core file.
Fun is this all is now worthless - only for backward compatibility which has discutable value for core files - as it has been obsoleted by NT_FILE; probably, not sure, NT_FILE contains the list of any mapped files, not just the files really opened as shared libraries.
But NT_FILE discussion is outside of the scope of this patch.
Attaching significantly simplified patch.
Thanks, Jan
commit cd584cc822842862923670ebc44d40bf55485a69 Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 23 20:19:16 2013 +0200
Use DT_DEBUG library search first.
libdwfl/ 2013-05-28 Jan Kratochvil jan.kratochvil@redhat.com
* argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report. * core-file.c (clear_r_debug_info): New function. (dwfl_core_file_report): Move raw segments reporting lower. New variable r_debug_info, pass it to dwfl_segment_report_module. Call clear_r_debug_info in the end. Return sum of LISTED and SNIFFED. * dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. * dwfl_segment_report_module.c (dwfl_segment_report_module): New parameter r_debug_info. New variable name_is_final. Adjust addresses according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. Do not overwrite NAME by SONAME if NAME_IS_FINAL. * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (struct r_debug_info_module, struct r_debug_info): New definitions. (dwfl_segment_report_module, dwfl_link_map_report): Add parameter r_debug_info. * link_map.c: Include fcntl.h. (report_r_debug): Add parameter r_debug_info, describe it in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it. Store r_debug_info->module info when appropriate. (dwfl_link_map_report): Add parameter r_debug_info. New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update report_r_debug caller parameters.
tests/ 2013-05-28 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. New test for these files. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file.
Signed-off-by: Jan Kratochvil jan.kratochvil@redhat.com
diff --git a/libdwfl/argp-std.c b/libdwfl/argp-std.c index e54f720..c884390 100644 --- a/libdwfl/argp-std.c +++ b/libdwfl/argp-std.c @@ -295,6 +295,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
if (opt->core) { + if (opt->e) + dwfl->executable_for_core = strdup (opt->e); + int fd = open64 (opt->core, O_RDONLY); if (fd < 0) { @@ -330,9 +333,6 @@ parse_opt (int key, char *arg, struct argp_state *state) _("No modules recognized in core file")); return ENOENT; } - - if (opt->e) - dwfl->executable_for_core = strdup (opt->e); } else if (opt->e) { diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index ac2aa8c..d5e340c 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -381,6 +381,19 @@ dwfl_elf_phdr_memory_callback (Dwfl *dwfl, int ndx, return true; }
+/* Free the contents of R_DEBUG_INFO without the R_DEBUG_INFO memory itself. */ + +static void +clear_r_debug_info (struct r_debug_info *r_debug_info) +{ + while (r_debug_info->module != NULL) + { + struct r_debug_info_module *module = r_debug_info->module; + r_debug_info->module = module->next; + free (module); + } +} + int dwfl_core_file_report (Dwfl *dwfl, Elf *elf) { @@ -431,33 +444,43 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) /* Now we have NT_AUXV contents. From here on this processing could be used for a live process with auxv read from /proc. */
+ struct r_debug_info r_debug_info; + memset (&r_debug_info, 0, sizeof r_debug_info); int listed = dwfl_link_map_report (dwfl, auxv, auxv_size, - dwfl_elf_phdr_memory_callback, elf); + dwfl_elf_phdr_memory_callback, elf, + &r_debug_info); + + /* Now sniff segment contents for modules hinted by information gathered + from DT_DEBUG. */
- /* Now sniff segment contents for modules. */ int sniffed = 0; - ndx = 0; - do + ndx = 0; + do + { + int seg = dwfl_segment_report_module (dwfl, ndx, NULL, + &dwfl_elf_phdr_memory_callback, elf, + core_file_read_eagerly, elf, + &r_debug_info); + if (unlikely (seg < 0)) { - int seg = dwfl_segment_report_module (dwfl, ndx, NULL, - &dwfl_elf_phdr_memory_callback, elf, - core_file_read_eagerly, elf); - if (unlikely (seg < 0)) - return seg; - if (seg > ndx) - { - ndx = seg; - ++sniffed; - } - else - ++ndx; + clear_r_debug_info (&r_debug_info); + return seg; + } + if (seg > ndx) + { + ndx = seg; + ++sniffed; } - while (ndx < (int) phnum); + else + ++ndx; + } + while (ndx < (int) phnum); + + clear_r_debug_info (&r_debug_info);
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an - error rather than just nothing found. If link_map handling - failed, we still have the sniffed modules. */ - return sniffed == 0 || listed > sniffed ? listed : sniffed; + error rather than just nothing found. */ + return sniffed || listed >= 0 ? listed + sniffed : listed; } INTDEF (dwfl_core_file_report) diff --git a/libdwfl/dwfl_module_build_id.c b/libdwfl/dwfl_module_build_id.c index 98cb9f4..cae007b 100644 --- a/libdwfl/dwfl_module_build_id.c +++ b/libdwfl/dwfl_module_build_id.c @@ -56,10 +56,20 @@ found_build_id (Dwfl_Module *mod, bool set,
int internal_function -__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +__libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, int *build_id_len) { - int - check_notes (Dwfl_Module *mod, bool set, Elf_Data *data, GElf_Addr data_vaddr) + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (unlikely (ehdr == NULL)) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + // MOD->E_TYPE is zero here. + assert (ehdr->e_type != ET_REL || mod != NULL); + + int check_notes (Elf_Data *data, GElf_Addr data_elfaddr) { size_t pos = 0; GElf_Nhdr nhdr; @@ -69,10 +79,13 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == sizeof "GNU" && !memcmp (data->d_buf + name_pos, "GNU", sizeof "GNU")) - return found_build_id (mod, set, - data->d_buf + desc_pos, nhdr.n_descsz, - data_vaddr == NO_VADDR ? 0 - : data_vaddr + desc_pos); + { + *build_id_bits = data->d_buf + desc_pos; + *build_id_elfaddr = (data_elfaddr == NO_VADDR + ? 0 : data_elfaddr + desc_pos); + *build_id_len = nhdr.n_descsz; + return 1; + } return 0; }
@@ -84,11 +97,8 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (scn == NULL) { /* No sections, have to look for phdrs. */ - GElf_Ehdr ehdr_mem; - GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); size_t phnum; - if (unlikely (ehdr == NULL) - || unlikely (elf_getphdrnum (elf, &phnum) != 0)) + if (unlikely (elf_getphdrnum (elf, &phnum) != 0)) { __libdwfl_seterrno (DWFL_E_LIBELF); return -1; @@ -98,12 +108,11 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Phdr phdr_mem; GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); if (likely (phdr != NULL) && phdr->p_type == PT_NOTE) - result = check_notes (mod, set, - elf_getdata_rawchunk (elf, + result = check_notes (elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz, ELF_T_NHDR), - dwfl_adjusted_address (mod, phdr->p_vaddr)); + phdr->p_vaddr); } } else @@ -117,12 +126,12 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Addr vaddr = 0; if (!(shdr->sh_flags & SHF_ALLOC)) vaddr = NO_VADDR; - else if (mod->e_type != ET_REL) - vaddr = dwfl_adjusted_address (mod, shdr->sh_addr); + else if (mod == NULL || ehdr->e_type != ET_REL) + vaddr = shdr->sh_addr; else if (__libdwfl_relocate_value (mod, elf, &shstrndx, elf_ndxscn (scn), &vaddr)) vaddr = NO_VADDR; - result = check_notes (mod, set, elf_getdata (scn, NULL), vaddr); + result = check_notes (elf_getdata (scn, NULL), vaddr); } } while (result == 0 && (scn = elf_nextscn (elf, scn)) != NULL); @@ -131,6 +140,24 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) }
int +internal_function +__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +{ + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + + int result = __libdwfl_find_elf_build_id (mod, elf, &build_id_bits, + &build_id_elfaddr, &build_id_len); + if (result <= 0) + return result; + + GElf_Addr build_id_vaddr = build_id_elfaddr + (build_id_elfaddr != 0 + ? mod->main_bias : 0); + return found_build_id (mod, set, build_id_bits, build_id_len, build_id_vaddr); +} + +int dwfl_module_build_id (Dwfl_Module *mod, const unsigned char **bits, GElf_Addr *vaddr) { diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 7cf7499..d454ccb 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -83,7 +83,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg) + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info) { size_t segment = ndx;
@@ -433,6 +434,47 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
dyn_vaddr += bias;
+ /* NAME found from link map has precedence over DT_SONAME possibly read + below. */ + bool name_is_final = false; + + /* Try to match up DYN_VADDR against L_LD as found in link map. + Segments sniffing may guess invalid address as the first read-only memory + mapping may not be dumped to the core file (if ELF headers are not dumped) + and the ELF header is dumped first with the read/write mapping of the same + file at higher addresses. */ + if (r_debug_info != NULL) + for (const struct r_debug_info_module *module = r_debug_info->module; + module != NULL; module = module->next) + if (module_start <= module->l_ld && module->l_ld < module_end) + { + /* L_LD read from link map must be right while DYN_VADDR is unsafe. + Therefore subtract DYN_VADDR and add L_LD to get a possibly + corrective displacement for all addresses computed so far. */ + GElf_Addr fixup = module->l_ld - dyn_vaddr; + if ((fixup & (dwfl->segment_align - 1)) == 0 + && module_start + fixup <= module->l_ld + && module->l_ld < module_end + fixup) + { + module_start += fixup; + module_end += fixup; + dyn_vaddr += fixup; + bias += fixup; + if (module->name[0] != '\0') + { + name = module->name; + name_is_final = true; + } + break; + } + } + + /* Ignore this found module if it would conflict in address space with any + already existing module of DWFL. */ + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + if (module_end > mod->low_addr && module_start < mod->high_addr) + return finish (); + /* Our return value now says to skip the segments contained within the module. */ ndx = addr_segndx (dwfl, segment, module_end, true); @@ -518,7 +560,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
void *soname = NULL; size_t soname_size = 0; - if (dynstrsz != 0 && dynstr_vaddr != 0) + if (! name_is_final && dynstrsz != 0 && dynstr_vaddr != 0) { /* We know the bounds of the .dynstr section.
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 37a9dff..69e6e12 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -362,6 +362,16 @@ extern Dwfl_Error __libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, extern Dwfl_Error __libdwfl_cu_getsrclines (struct dwfl_cu *cu) internal_function;
+/* Look in ELF for an NT_GNU_BUILD_ID note. Store it to BUILD_ID_BITS, + its vaddr in ELF to BUILD_ID_VADDR (it is unrelocated, even if MOD is not + NULL) and store length to BUILD_ID_LEN. Returns -1 for errors, 1 if it was + stored and 0 if no note is found. MOD may be NULL, MOD must be non-NULL + only if ELF is ET_REL. */ +extern int __libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, + int *build_id_len); + /* Look in ELF for an NT_GNU_BUILD_ID note. If SET is true, store it in MOD and return its length. If SET is false, instead compare it to that stored in MOD and return 2 if they match, 1 if they do not. @@ -439,13 +449,29 @@ typedef bool Dwfl_Module_Callback (Dwfl_Module *mod, void **userdata, GElf_Off whole, GElf_Off contiguous, void *arg, Elf **elfp);
+/* One shared library (or executable) info from DT_DEBUG link map. */ +struct r_debug_info_module +{ + struct r_debug_info_module *next; + GElf_Addr l_ld; + char name[0]; +}; + +/* Information gathered from DT_DEBUG by dwfl_link_map_report hinted to + dwfl_segment_report_module. */ +struct r_debug_info +{ + struct r_debug_info_module *module; +}; + /* ... */ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg); + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info);
/* Report a module for entry in the dynamic linker's struct link_map list. For each link_map entry, if an existing module resides at its address, @@ -459,10 +485,14 @@ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, only find where to begin if the correct executable file was previously reported and preloaded as with dwfl_report_elf.
+ Fill in R_DEBUG_INFO if it is not NULL. It should be cleared by the + caller, this function does not touch fields it does not need to modify. + Returns the number of modules found, or -1 for errors. */ extern int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg); + void *memory_callback_arg, + struct r_debug_info *r_debug_info);
/* Avoid PLT entries. */ diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c index 5dbf17e..e752a5d 100644 --- a/libdwfl/link_map.c +++ b/libdwfl/link_map.c @@ -33,6 +33,7 @@
#include <byteswap.h> #include <endian.h> +#include <fcntl.h>
/* This element is always provided and always has a constant value. This makes it an easy thing to scan for to discern the format. */ @@ -222,7 +223,8 @@ addrsize (uint_fast8_t elfclass) }
/* Report a module for each struct link_map in the linked list at r_map - in the struct r_debug at R_DEBUG_VADDR. + in the struct r_debug at R_DEBUG_VADDR. For r_debug_info description + see dwfl_link_map_report in libdwflP.h.
For each link_map entry, if an existing module resides at its address, this just modifies that module's name and suggested file name. If @@ -234,7 +236,8 @@ static int report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, Dwfl *dwfl, GElf_Addr r_debug_vaddr, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { /* Skip r_version, to aligned r_map field. */ GElf_Addr read_vaddr = r_debug_vaddr + addrsize (elfclass); @@ -349,12 +352,84 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, if (name != NULL && name[0] == '\0') name = NULL;
- /* We have to find the file's phdrs to compute along with l_addr - what its runtime address boundaries are. */ + if (iterations == 1 && dwfl->executable_for_core != NULL) + name = dwfl->executable_for_core;
- // XXX hook for sysroot - mod = INTUSE(dwfl_report_elf) (dwfl, basename (name), - name, -1, l_addr, true); + Dwfl_Module *mod = NULL; + if (name != NULL) + { + /* This code is mostly inlined dwfl_report_elf. */ + // XXX hook for sysroot + int fd = open64 (name, O_RDONLY); + if (fd >= 0) + { + Elf *elf; + Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false); + if (error == DWFL_E_NOERROR) + { + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + bool valid = true; + + /* FIXME: Bias L_ADDR should be computed from the prelink + state in memory (when the file got loaded), not against + the current on-disk file state as is computed below. + + This verification gives false positive if in-core ELF had + build-id but on-disk ELF does not have any. But we cannot + reliably find ELF header and/or the ELF build id just from + the link map (and checking core segments is also not + reliable). */ + + if (__libdwfl_find_elf_build_id (NULL, elf, &build_id_bits, + &build_id_elfaddr, + &build_id_len) > 0 + && build_id_elfaddr != 0) + { + GElf_Addr build_id_vaddr = build_id_elfaddr + l_addr; + release_buffer (0); + int segndx = INTUSE(dwfl_addrsegment) (dwfl, + build_id_vaddr, + NULL); + if (! (*memory_callback) (dwfl, segndx, + &buffer, &buffer_available, + build_id_vaddr, build_id_len, + memory_callback_arg) + || memcmp (build_id_bits, buffer, build_id_len) != 0) + { + /* File has valid build-id which cannot be verified + in memory. */ + valid = false; + } + } + + if (valid) + // XXX hook for sysroot + mod = __libdwfl_report_elf (dwfl, basename (name), name, + fd, elf, l_addr, true, true); + if (mod == NULL) + { + elf_end (elf); + close (fd); + } + } + } + } + if (r_debug_info != NULL && mod == NULL) + { + /* Save link map information about valid shared library (or + executable) which has not been found on disk. */ + const char *base = name == NULL ? "" : basename (name); + struct r_debug_info_module *module; + module = malloc (sizeof (*module) + strlen (base) + 1); + if (module == NULL) + return release_buffer (result); + module->l_ld = l_ld; + strcpy (module->name, base); + module->next = r_debug_info->module; + r_debug_info->module = module; + }
if (mod != NULL) { @@ -570,7 +645,8 @@ find_executable (Dwfl *dwfl, GElf_Addr at_phdr, GElf_Addr at_entry, int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { GElf_Addr r_debug_vaddr = 0;
@@ -668,8 +744,65 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, .d_size = phnum * phent, .d_buf = NULL }; - if ((*memory_callback) (dwfl, phdr_segndx, &in.d_buf, &in.d_size, - phdr, phnum * phent, memory_callback_arg)) + bool in_ok = (*memory_callback) (dwfl, phdr_segndx, &in.d_buf, + &in.d_size, phdr, phnum * phent, + memory_callback_arg); + if (! in_ok && dwfl->executable_for_core != NULL) + { + /* AUXV -> PHDR -> DYNAMIC + Both AUXV and DYNAMIC should be always present in a core file. + PHDR may be missing in core file, try to read it from + EXECUTABLE_FOR_CORE to find where DYNAMIC is located in the + core file. */ + + int fd = open (dwfl->executable_for_core, O_RDONLY); + Elf *elf; + Dwfl_Error error = DWFL_E_ERRNO; + if (fd != -1) + error = __libdw_open_file (&fd, &elf, true, false); + if (error != DWFL_E_NOERROR) + { + __libdwfl_seterrno (error); + return false; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + if (ehdr->e_phnum != phnum || ehdr->e_phentsize != phent) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_BADELF); + return false; + } + off_t off = ehdr->e_phoff; + assert (in.d_buf == NULL); + assert (in.d_size == phnum * phent); + in.d_buf = malloc (in.d_size); + if (unlikely (in.d_buf == NULL)) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + ssize_t nread = pread_retry (fd, in.d_buf, in.d_size, off); + elf_end (elf); + close (fd); + if (nread != (ssize_t) in.d_size) + { + free (in.d_buf); + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + in_ok = true; + } + if (in_ok) { union { @@ -831,6 +964,6 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size,
/* Now we can follow the dynamic linker's library list. */ return report_r_debug (elfclass, elfdata, dwfl, r_debug_vaddr, - &integrated_memory_callback, &mcb); + &integrated_memory_callback, &mcb, r_debug_info); } INTDEF (dwfl_link_map_report) diff --git a/tests/Makefile.am b/tests/Makefile.am index 6327edb..2d819c5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -199,7 +199,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ testfile70.core.bz2 testfile70.exec.bz2 \ run-dwfllines.sh run-dwfl-report-elf-align.sh \ testfile-dwfl-report-elf-align-shlib.so.bz2 \ - testfilenolines + testfilenolines test-core-lib.so.bz2 test-core.core.bz2 \ + test-core.exec.bz2
if USE_VALGRIND valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no' diff --git a/tests/run-addrname-test.sh b/tests/run-addrname-test.sh index 9351fb2..8624074 100755 --- a/tests/run-addrname-test.sh +++ b/tests/run-addrname-test.sh @@ -316,4 +316,10 @@ main+0x9 ??:0 EOF
+testfiles test-core-lib.so test-core.core test-core.exec +testrun_compare ${abs_top_builddir}/src/addr2line -S -e test-core.exec --core=test-core.core 0x7f67f2aaf619 <<\EOF +libfunc+0x9 +??:0 +EOF + exit 0 diff --git a/tests/run-unstrip-n.sh b/tests/run-unstrip-n.sh index 9c2b43b..6ede4c7 100755 --- a/tests/run-unstrip-n.sh +++ b/tests/run-unstrip-n.sh @@ -35,20 +35,35 @@ testfiles testcore-rtlib testcore-rtlib-ppc
testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib <<\EOF 0x8048000+0x2000 f1c600bc36cb91bf01f9a63a634ecb79aa4c3199@0x8048178 . - [exe] +0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 - - libpthread.so.0 +0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 - - libc.so.6 +0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 - - librt.so.1 0xf77d6000+0x1000 676560b1b765cde9c2e53f134f4ee354ea894747@0xf77d6210 . - linux-gate.so.1 -0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 /lib/librt.so.1 - librt.so.1 -0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 /lib/libc.so.6 - libc.so.6 -0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 /lib/libpthread.so.0 - libpthread.so.0 -0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 /lib/ld-linux.so.2 - ld-linux.so.2 +0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 - - ld-linux.so.2 EOF
testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib-ppc <<\EOF -0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] 0x100000+0x10000 708b900b05176964512a6b0fe90c2a0c9d73d726@0x100334 . - linux-vdso32.so.1 -0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 /lib/librt.so.1 - librt.so.1 -0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 /lib/libc.so.6 - libc.so.6 -0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 /lib/libpthread.so.0 - libpthread.so.0 -0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 /lib/ld.so.1 - ld.so.1 +0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 . - librt.so.1 +0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 - - libpthread.so.0 +0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 - - libc.so.6 +0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 - - ld.so.1 +0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] +EOF + +# FAIL was 0x7f67f2caf000 for test-core-lib.so . +# /lib64/libc.so.6 and /lib64/ld-linux-x86-64.so.2 from link map +# do not have ELF header stored in the core file. +# ELF headers in the core file: +# Offset VirtAddr +# 0x014000 0x00007f67f2caf000 ./test-core-lib.so +# 0x03a000 0x00007fff1596c000 linux-vdso.so.1 +testfiles test-core.core test-core.exec +rm -f test-core-lib.so +testrun_compare ${abs_top_builddir}/src/unstrip -n -e test-core.exec --core=test-core.core <<\EOF +0x400000+0x202038 - test-core.exec - test-core.exec +0x7f67f2aaf000+0x202000 - . - test-core-lib.so +0x7fff1596c000+0x1000 a9cf37f53897b5468ee018655760be61b8633d3c@0x7fff1596c340 . - linux-vdso.so.1 EOF
test_cleanup diff --git a/tests/test-core-lib.so.bz2 b/tests/test-core-lib.so.bz2 new file mode 100755 index 0000000..bb2da88 Binary files /dev/null and b/tests/test-core-lib.so.bz2 differ diff --git a/tests/test-core.core.bz2 b/tests/test-core.core.bz2 new file mode 100644 index 0000000..4d4346b Binary files /dev/null and b/tests/test-core.core.bz2 differ diff --git a/tests/test-core.exec.bz2 b/tests/test-core.exec.bz2 new file mode 100755 index 0000000..49ce551 Binary files /dev/null and b/tests/test-core.exec.bz2 differ
On Tue, 2013-05-28 at 20:36 +0200, Jan Kratochvil wrote:
On Mon, 27 May 2013 16:05:46 +0200, Mark Wielaard wrote:
Since it doesn't actually free the struct itself, lets call it clear_r_debug_info to avoid confusion.
Done.
Thanks.
Reporting the full file names was arguably wrong to begin with. I don't think we should restore that, but if people do feel this is a regression we could add a guesser to unstrip.c itself (but I don't think we should).
The full pathnames were clearly a bug for this patchset.
Yes, I agree, and I don't think anybody should rely on them, but maybe something did anyway. I am fine with calling that broken behavior though.
But when we talk about it in general, as a new patchset, I rather find a bug dropping the path, when core file already contains the absolute pathname why to hide it from the user?
Right, if we can guarantee that path is the real one and not just an arbitrary guess.
But when we talk about some possible further extensions we should rather forget about the whole link map and just use NT_FILE. Or one can just already display the full pathnames easily (not in eu-readelf): $ readelf -n test-core.core Start End Page Offset 0x0000000000400000 0x0000000000402000 0x0000000000000000 /home/jkratoch/redhat/elfutils-libregr/test-core-main 0x00000037ef400000 0x00000037ef420000 0x0000000000000000 /usr/lib64/ld-2.16.so
Yes that is nice, we should have support for that too, in the future, in some separate patch. Found the ELF NOTE description here: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=2... Don't know if there is a more "official" documentation. Does gdb/gcore also add this NT_FILE note now?
Document that count gets updated on success.
The report_module helper is gone now.
The clearing of r_debug_info on failure is a nice because it prevents accidental memory leaks, but also confused me on first read of the patch (because I forgot the comment when reading the later code).
The report_module helper is gone now.
That is a nice cleanup.
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an
error rather than just nothing found. If link_map handling
failed, we still have the sniffed modules. */
- return sniffed == 0 || listed > sniffed ? listed : sniffed;
error rather than just nothing found. */
- return sniffed || listed >= 0 ? listed + sniffed : listed;
What about the case of sniffed == 0 && listed == 0? Should that return -1?
It did not return -1 before so why we should now? There may exist valid core file which just does not contain any ELF module (=does not contain any identifiable ELF module).
A different case is when there is some real unexpected failure processing the data structures, in such case the function still returns -1, either be an early return -1 call or due to LISTED == -1 (from report_r_debug() returning -1).
Aha. Sorry, I misread the comment "if there was an error". So if there is no error and nothing found, just report zero. OK.
- /* Try to match up DYN_VADDR against L_LD as found in link map.
Segments sniffing may guess invalid address as the first read-only memory
mapping may not be dumped to the core file (if ELF headers are not dumped)
and the ELF header is dumped first with the read/write mapping of the same
file at higher addresses. */
- if (r_debug_info != NULL)
- for (const struct r_debug_info_module *module = r_debug_info->module;
module != NULL; module = module->next)
if (module_start <= module->l_ld && module->l_ld < module_end)
- {
GElf_Addr fixup = module->l_ld - dyn_vaddr;
[...]
fixup might need a comment.
added: /* L_LD read from link map must be right while DYN_VADDR is unsafe. Therefore subtract DYN_VADDR and add L_LD to get a possibly corrective displacement for all addresses computed so far. */
It wasn't immediately clear to me. The fixup is the difference between the PT_LOAD address that the link-map know is the correct one and the PT_DYNAMIC address (of which there can be only one). And module_start points to the PT_LOAD address that was just gotten from the phdrs. Right?
Right.
Thanks.
Actually now that I do understand, that is kind of what the comment above already says. I just didn't immediately made the connection with the offset/fixup being static. Is fixup always positive?
At least in the supplied testcase it is negative.
In fact I did not spent time thinking if it is positive or negative, I was following the logic which address is safe and which unsafe above.
OK, then I am wondering about the first check before the fixup.
- if (module_start <= module->l_ld && module->l_ld < module_end)
Should the fixup not always be calculated and then the next check takes care of everything?
GElf_Addr fixup = module->l_ld - dyn_vaddr;
if ((fixup & (dwfl->segment_align - 1)) == 0
&& module_start + fixup <= module->l_ld
&& module->l_ld < module_end + fixup)
Since the fixup could just be zero?
BTW. Why does link-map set module->name to the empty string when no name (NULL) was found? Just convenience like here, so no NULL check is needed, or is the empty name significant?
'name' is a GCC zero-length array, I am sorry but I cannot make it NULL: struct r_debug_info_module { [...] char name[0]; };
Ah, OK then.
diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c @@ -352,9 +355,108 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, /* We have to find the file's phdrs to compute along with l_addr what its runtime address boundaries are. */
// XXX hook for sysroot
mod = INTUSE(dwfl_report_elf) (dwfl, basename (name),
name, -1, l_addr, true);
Dwfl_Module *mod = NULL;
if (iterations == 1 && dwfl->executable_for_core != NULL)
- {
/* Find the main executable.
FIXME: Try to also find it via build-id. */
name = dwfl->executable_for_core;
- }
To solve that FIXME we should do somewhat the same as is done below with the "inlined dwfl_report_elf" snippet? And we already do try to open the executable if we cannot read the phdrs. Maybe this can be factored out a bit?
In fact when we do not have any executable filename or we failed to open the executable filename the only thing we can do is currently already done by the second pass of dwfl_segment_report_module calls with the r_debug_info hints.
This code was there before I implemented the second pass even after the link map pass succeeded (found >= 1 shared libraries).
So I have removed the FIXME comment and simplified it all a bit.
if (iterations != 2 && name != NULL)
- {
/* Find a shared library or main executable. Do not try to
find a file for vDSO (where ITERATIONS equals 2). */
I couldn't find where the iteration == 1 => exe, iteration == 2 => vDSO comes from. Shouldn't the second check be iterations > 2 ?
As discussed on #elfutils this ITERATIONS == 2 condition was unsafe, while currently vDSO is the 2nd entry with glibc at least with older or non-Linux core files 2nd entry is a regular shared library.
One could make some name-based exceptions ("linux-vdso.so.1") like in http://pkgs.fedoraproject.org/cgit/gdb.git/tree/gdb-glibc-vdso-workaround.pa...
I found also this case is now obsoleted by the second dwfl_segment_report_module pass, finding the vDSO did not do anything else than just to call dwfl_segment_report_module for its segment.
All this certainly makes things a lot cleaner. Thanks.
Fun is this all is now worthless - only for backward compatibility which has discutable value for core files - as it has been obsoleted by NT_FILE; probably, not sure, NT_FILE contains the list of any mapped files, not just the files really opened as shared libraries.
It certainly isn't worthless! We still would like to handle old/incomplete core files from older systems (on newer systems). We don't support NT_FILE yet. There are other ways core files are generated than with the latest kernel version, like with gdb/gcore, which we also like to handle.
Attaching significantly simplified patch.
commit cd584cc822842862923670ebc44d40bf55485a69 Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 23 20:19:16 2013 +0200
Use DT_DEBUG library search first. libdwfl/ 2013-05-28 Jan Kratochvil <jan.kratochvil@redhat.com> * argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report. * core-file.c (clear_r_debug_info): New function. (dwfl_core_file_report): Move raw segments reporting lower. New variable r_debug_info, pass it to dwfl_segment_report_module. Call clear_r_debug_info in the end. Return sum of LISTED and SNIFFED. * dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. * dwfl_segment_report_module.c (dwfl_segment_report_module): New parameter r_debug_info. New variable name_is_final. Adjust addresses according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. Do not overwrite NAME by SONAME if NAME_IS_FINAL. * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (struct r_debug_info_module, struct r_debug_info): New definitions. (dwfl_segment_report_module, dwfl_link_map_report): Add parameter r_debug_info. * link_map.c: Include fcntl.h. (report_r_debug): Add parameter r_debug_info, describe it in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it. Store r_debug_info->module info when appropriate. (dwfl_link_map_report): Add parameter r_debug_info. New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update report_r_debug caller parameters. tests/ 2013-05-28 Jan Kratochvil <jan.kratochvil@redhat.com> * Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. New test for these files. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file. Signed-off-by: Jan Kratochvil <jan.kratochvil@redhat.com>
Looks good to me. But please merge this commit and the code movement commit into one, since those are really one change.
Thanks,
Mark
On Wed, 29 May 2013 11:50:26 +0200, Mark Wielaard wrote:
On Tue, 2013-05-28 at 20:36 +0200, Jan Kratochvil wrote:
But when we talk about it in general, as a new patchset, I rather find a bug dropping the path, when core file already contains the absolute pathname why to hide it from the user?
Right, if we can guarantee that path is the real one and not just an arbitrary guess.
It always was the real pathname - why it would not be? Or what have I forgot about?
$ readelf -n test-core.core Start End Page Offset
[...]
0x00000037ef400000 0x00000037ef420000 0x0000000000000000 /usr/lib64/ld-2.16.so
Yes that is nice, we should have support for that too, in the future, in some separate patch. Found the ELF NOTE description here: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=2... Don't know if there is a more "official" documentation.
Me neither.
Does gdb/gcore also add this NT_FILE note now?
Yes, for both 'info proc mappings' and 'gcore': commit 1f82969cb44bf57e2f05f725403cc37f6d2fa5c5 Author: Tom Tromey tromey@redhat.com Date: Fri Dec 14 15:30:25 2012 +0000
Actually now that I do understand, that is kind of what the comment above already says. I just didn't immediately made the connection with the offset/fixup being static. Is fixup always positive?
At least in the supplied testcase it is negative.
In fact I did not spent time thinking if it is positive or negative, I was following the logic which address is safe and which unsafe above.
OK, then I am wondering about the first check before the fixup.
+ /* Try to match up DYN_VADDR against L_LD as found in link map. + Segments sniffing may guess invalid address as the first read-only memory + mapping may not be dumped to the core file (if ELF headers are not dumped) + and the ELF header is dumped first with the read/write mapping of the same + file at higher addresses. */
I have one such example I have seen where it is helpful. But the principle seems correct to me no matter if it is positive or negative.
- if (module_start <= module->l_ld && module->l_ld < module_end)
Should the fixup not always be calculated and then the next check takes care of everything?
I do not find that safe; this could match unrelated L_LD from the other end of address space just due to a "luck" it has the same '% PAGE_SIZE' offset.
IIUC MODULE_START and MODULE_END are always safely located inside the segment (considering they have BIAS already added); the segment address may be wrong but it only matters there cannot be other shared library located at the same address range. L_LD is also always safe as it comes from link map. Therefore the first conditional is always safe: if (module_start <= module->l_ld && module->l_ld < module_end)
I cannot much imagine how the second conditional after FIXUP adjustment && module_start + fixup <= module->l_ld && module->l_ld < module_end + fixup) could fail but it is just a sanity check of FIXUP.
This is why I believe the first conditional should be there; the second conditional may be redundant but it cannot hurt.
Or do you see some inconsistency in my safe / unsafe considerations above?
It is a bit pity to spend time on all of this when we have NT_FILE already. Just the current code did not work for my unwinder test cases and with some of the fixes I was accused of regressing elfutils so I just try to make a feeling there are no regressions after all the patches of mine.
GElf_Addr fixup = module->l_ld - dyn_vaddr;
if ((fixup & (dwfl->segment_align - 1)) == 0
&& module_start + fixup <= module->l_ld
&& module->l_ld < module_end + fixup)
Since the fixup could just be zero?
FIXUP will be in 99% of cases zero, what's the problem with it? I did not get your comment here.
There are other ways core files are generated than with the latest kernel version, like with gdb/gcore, which we also like to handle.
gdb/gcore does produce NT_FILE. :-)
Looks good to me. But please merge this commit and the code movement commit into one, since those are really one change.
Yes, sure, it would break git bisect as it does not build with just the first part applied.
Not checked in yet until the discussion gets finished.
Thanks, Jan
On Wed, 2013-05-29 at 17:57 +0200, Jan Kratochvil wrote:
On Wed, 29 May 2013 11:50:26 +0200, Mark Wielaard wrote:
On Tue, 2013-05-28 at 20:36 +0200, Jan Kratochvil wrote:
But when we talk about it in general, as a new patchset, I rather find a bug dropping the path, when core file already contains the absolute pathname why to hide it from the user?
Right, if we can guarantee that path is the real one and not just an arbitrary guess.
It always was the real pathname - why it would not be? Or what have I forgot about?
You forgot Nothing. I was just not very clear. I just meant that the full path is there in eu-unstrip -n output if the actual file on the file system is the one matching. It is now just missing in the output if the file on the system is not a match. But that is fine.
- if (module_start <= module->l_ld && module->l_ld < module_end)
Should the fixup not always be calculated and then the next check takes care of everything?
I do not find that safe; this could match unrelated L_LD from the other end of address space just due to a "luck" it has the same '% PAGE_SIZE' offset.
IIUC MODULE_START and MODULE_END are always safely located inside the segment (considering they have BIAS already added); the segment address may be wrong but it only matters there cannot be other shared library located at the same address range. L_LD is also always safe as it comes from link map. Therefore the first conditional is always safe: if (module_start <= module->l_ld && module->l_ld < module_end)
I cannot much imagine how the second conditional after FIXUP adjustment && module_start + fixup <= module->l_ld && module->l_ld < module_end + fixup) could fail but it is just a sanity check of FIXUP.
This is why I believe the first conditional should be there; the second conditional may be redundant but it cannot hurt.
Or do you see some inconsistency in my safe / unsafe considerations above?
No, that seems logical. Thanks.
GElf_Addr fixup = module->l_ld - dyn_vaddr;
if ((fixup & (dwfl->segment_align - 1)) == 0
&& module_start + fixup <= module->l_ld
&& module->l_ld < module_end + fixup)
Since the fixup could just be zero?
FIXUP will be in 99% of cases zero, what's the problem with it? I did not get your comment here.
There is no problem. As you said above the extra check might be redundant. But that is fine.
Looks good to me. But please merge this commit and the code movement commit into one, since those are really one change.
Yes, sure, it would break git bisect as it does not build with just the first part applied.
Not checked in yet until the discussion gets finished.
I think it is finished now.
Thanks,
Mark
On Thu, 30 May 2013 11:52:55 +0200, Mark Wielaard wrote:
I think it is finished now.
Checked in.
Thanks, Jan
commit 8ff862960efb648cdff647d7fad1be5acffe9b11 Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 30 13:21:20 2013 +0200
Use DT_DEBUG library search first.
libdwfl/ 2013-05-30 Jan Kratochvil jan.kratochvil@redhat.com
* argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set executable_for_core before calling dwfl_core_file_report. * core-file.c (clear_r_debug_info): New function. (dwfl_core_file_report): Move raw segments reporting lower. New variable r_debug_info, pass it to dwfl_segment_report_module. Call clear_r_debug_info in the end. Return sum of LISTED and SNIFFED. * dwfl_module_build_id.c (check_notes): Move into __libdwfl_find_elf_build_id. (__libdwfl_find_build_id): Rename to ... (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, build_id_elfaddr and build_id_len. Verify MOD vs. ELF. (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and set, rename data_vaddr to data_elfaddr. Do not call found_build_id. (__libdwfl_find_elf_build_id): Update the check_notes caller, do not adjust its data_elfaddr parameter. (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. * dwfl_segment_report_module.c (dwfl_segment_report_module): New parameter r_debug_info. New variable name_is_final. Adjust addresses according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. Do not overwrite NAME by SONAME if NAME_IS_FINAL. * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. (struct r_debug_info_module, struct r_debug_info): New definitions. (dwfl_segment_report_module, dwfl_link_map_report): Add parameter r_debug_info. * link_map.c: Include fcntl.h. (report_r_debug): Add parameter r_debug_info, describe it in the function comment. Delete dwfl_addrmodule call and its dependent code. Verify build-id before calling dwfl_report_elf, also supply executable_for_core to it. Store r_debug_info->module info when appropriate. (dwfl_link_map_report): Add parameter r_debug_info. New variable in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update report_r_debug caller parameters.
tests/ 2013-05-30 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, test-core.core.bz2 and test-core.exec.bz2. * run-addrname-test.sh: New test for these files. * run-unstrip-n.sh: Update expected output. New test for these files. * test-core-lib.so.bz2: New file. * test-core.core.bz2: New file. * test-core.exec.bz2: New file.
Signed-off-by: Jan Kratochvil jan.kratochvil@redhat.com
diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog index 74ede01..9ee0ab1 100644 --- a/libdwfl/ChangeLog +++ b/libdwfl/ChangeLog @@ -1,3 +1,39 @@ +2013-05-30 Jan Kratochvil jan.kratochvil@redhat.com + + * argp-std.c (parse_opt) <ARGP_KEY_SUCCESS> <opt->core> <opt->e>: Set + executable_for_core before calling dwfl_core_file_report. + * core-file.c (clear_r_debug_info): New function. + (dwfl_core_file_report): Move raw segments reporting lower. New + variable r_debug_info, pass it to dwfl_segment_report_module. Call + clear_r_debug_info in the end. Return sum of LISTED and SNIFFED. + * dwfl_module_build_id.c (check_notes): Move into + __libdwfl_find_elf_build_id. + (__libdwfl_find_build_id): Rename to ... + (__libdwfl_find_elf_build_id): ... here. Add parameters build_id_bits, + build_id_elfaddr and build_id_len. Verify MOD vs. ELF. + (__libdwfl_find_elf_build_id) (check_notes): Remove parameters mod and + set, rename data_vaddr to data_elfaddr. Do not call found_build_id. + (__libdwfl_find_elf_build_id): Update the check_notes caller, do not + adjust its data_elfaddr parameter. + (__libdwfl_find_build_id): New wrapper of __libdwfl_find_elf_build_id. + * dwfl_segment_report_module.c (dwfl_segment_report_module): New + parameter r_debug_info. New variable name_is_final. Adjust addresses + according to R_DEBUG_INFO->MODULE. Check conflicts against DWFL. + Do not overwrite NAME by SONAME if NAME_IS_FINAL. + * libdwflP.h (__libdwfl_find_elf_build_id): New declaration. + (struct r_debug_info_module, struct r_debug_info): New definitions. + (dwfl_segment_report_module, dwfl_link_map_report): Add parameter + r_debug_info. + * link_map.c: Include fcntl.h. + (report_r_debug): Add parameter r_debug_info, describe it in the + function comment. Delete dwfl_addrmodule call and its dependent code. + Verify build-id before calling dwfl_report_elf, also supply + executable_for_core to it. Store r_debug_info->module info when + appropriate. + (dwfl_link_map_report): Add parameter r_debug_info. New variable + in_ok. Try to read IN from EXECUTABLE_FOR_CORE. Update report_r_debug + caller parameters. + 2013-04-30 Jan Kratochvil jan.kratochvil@redhat.com
* dwfl_report_elf.c (__libdwfl_report_elf): Add parameter add_p_vaddr. diff --git a/libdwfl/argp-std.c b/libdwfl/argp-std.c index e54f720..c884390 100644 --- a/libdwfl/argp-std.c +++ b/libdwfl/argp-std.c @@ -295,6 +295,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
if (opt->core) { + if (opt->e) + dwfl->executable_for_core = strdup (opt->e); + int fd = open64 (opt->core, O_RDONLY); if (fd < 0) { @@ -330,9 +333,6 @@ parse_opt (int key, char *arg, struct argp_state *state) _("No modules recognized in core file")); return ENOENT; } - - if (opt->e) - dwfl->executable_for_core = strdup (opt->e); } else if (opt->e) { diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index 1545ca8..d5e340c 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -381,6 +381,19 @@ dwfl_elf_phdr_memory_callback (Dwfl *dwfl, int ndx, return true; }
+/* Free the contents of R_DEBUG_INFO without the R_DEBUG_INFO memory itself. */ + +static void +clear_r_debug_info (struct r_debug_info *r_debug_info) +{ + while (r_debug_info->module != NULL) + { + struct r_debug_info_module *module = r_debug_info->module; + r_debug_info->module = module->next; + free (module); + } +} + int dwfl_core_file_report (Dwfl *dwfl, Elf *elf) { @@ -397,26 +410,6 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) if (unlikely (ndx <= 0)) return ndx;
- /* Now sniff segment contents for modules. */ - int sniffed = 0; - ndx = 0; - do - { - int seg = dwfl_segment_report_module (dwfl, ndx, NULL, - &dwfl_elf_phdr_memory_callback, elf, - core_file_read_eagerly, elf); - if (unlikely (seg < 0)) - return seg; - if (seg > ndx) - { - ndx = seg; - ++sniffed; - } - else - ++ndx; - } - while (ndx < (int) phnum); - /* Next, we should follow the chain from DT_DEBUG. */
const void *auxv = NULL; @@ -451,13 +444,43 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf) /* Now we have NT_AUXV contents. From here on this processing could be used for a live process with auxv read from /proc. */
+ struct r_debug_info r_debug_info; + memset (&r_debug_info, 0, sizeof r_debug_info); int listed = dwfl_link_map_report (dwfl, auxv, auxv_size, - dwfl_elf_phdr_memory_callback, elf); + dwfl_elf_phdr_memory_callback, elf, + &r_debug_info); + + /* Now sniff segment contents for modules hinted by information gathered + from DT_DEBUG. */ + + int sniffed = 0; + ndx = 0; + do + { + int seg = dwfl_segment_report_module (dwfl, ndx, NULL, + &dwfl_elf_phdr_memory_callback, elf, + core_file_read_eagerly, elf, + &r_debug_info); + if (unlikely (seg < 0)) + { + clear_r_debug_info (&r_debug_info); + return seg; + } + if (seg > ndx) + { + ndx = seg; + ++sniffed; + } + else + ++ndx; + } + while (ndx < (int) phnum); + + clear_r_debug_info (&r_debug_info);
/* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an - error rather than just nothing found. If link_map handling - failed, we still have the sniffed modules. */ - return sniffed == 0 || listed > sniffed ? listed : sniffed; + error rather than just nothing found. */ + return sniffed || listed >= 0 ? listed + sniffed : listed; } INTDEF (dwfl_core_file_report) diff --git a/libdwfl/dwfl_module_build_id.c b/libdwfl/dwfl_module_build_id.c index 660c733..cae007b 100644 --- a/libdwfl/dwfl_module_build_id.c +++ b/libdwfl/dwfl_module_build_id.c @@ -54,28 +54,41 @@ found_build_id (Dwfl_Module *mod, bool set,
#define NO_VADDR ((GElf_Addr) -1l)
-static int -check_notes (Dwfl_Module *mod, bool set, Elf_Data *data, GElf_Addr data_vaddr) -{ - size_t pos = 0; - GElf_Nhdr nhdr; - size_t name_pos; - size_t desc_pos; - 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 found_build_id (mod, set, - data->d_buf + desc_pos, nhdr.n_descsz, - data_vaddr == NO_VADDR ? 0 - : data_vaddr + desc_pos); - return 0; -} - int internal_function -__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +__libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, int *build_id_len) { + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (unlikely (ehdr == NULL)) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return -1; + } + // MOD->E_TYPE is zero here. + assert (ehdr->e_type != ET_REL || mod != NULL); + + int check_notes (Elf_Data *data, GElf_Addr data_elfaddr) + { + size_t pos = 0; + GElf_Nhdr nhdr; + size_t name_pos; + size_t desc_pos; + 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")) + { + *build_id_bits = data->d_buf + desc_pos; + *build_id_elfaddr = (data_elfaddr == NO_VADDR + ? 0 : data_elfaddr + desc_pos); + *build_id_len = nhdr.n_descsz; + return 1; + } + return 0; + } + size_t shstrndx = SHN_UNDEF; int result = 0;
@@ -84,11 +97,8 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) if (scn == NULL) { /* No sections, have to look for phdrs. */ - GElf_Ehdr ehdr_mem; - GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); size_t phnum; - if (unlikely (ehdr == NULL) - || unlikely (elf_getphdrnum (elf, &phnum) != 0)) + if (unlikely (elf_getphdrnum (elf, &phnum) != 0)) { __libdwfl_seterrno (DWFL_E_LIBELF); return -1; @@ -98,12 +108,11 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Phdr phdr_mem; GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); if (likely (phdr != NULL) && phdr->p_type == PT_NOTE) - result = check_notes (mod, set, - elf_getdata_rawchunk (elf, + result = check_notes (elf_getdata_rawchunk (elf, phdr->p_offset, phdr->p_filesz, ELF_T_NHDR), - dwfl_adjusted_address (mod, phdr->p_vaddr)); + phdr->p_vaddr); } } else @@ -117,12 +126,12 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) GElf_Addr vaddr = 0; if (!(shdr->sh_flags & SHF_ALLOC)) vaddr = NO_VADDR; - else if (mod->e_type != ET_REL) - vaddr = dwfl_adjusted_address (mod, shdr->sh_addr); + else if (mod == NULL || ehdr->e_type != ET_REL) + vaddr = shdr->sh_addr; else if (__libdwfl_relocate_value (mod, elf, &shstrndx, elf_ndxscn (scn), &vaddr)) vaddr = NO_VADDR; - result = check_notes (mod, set, elf_getdata (scn, NULL), vaddr); + result = check_notes (elf_getdata (scn, NULL), vaddr); } } while (result == 0 && (scn = elf_nextscn (elf, scn)) != NULL); @@ -131,6 +140,24 @@ __libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) }
int +internal_function +__libdwfl_find_build_id (Dwfl_Module *mod, bool set, Elf *elf) +{ + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + + int result = __libdwfl_find_elf_build_id (mod, elf, &build_id_bits, + &build_id_elfaddr, &build_id_len); + if (result <= 0) + return result; + + GElf_Addr build_id_vaddr = build_id_elfaddr + (build_id_elfaddr != 0 + ? mod->main_bias : 0); + return found_build_id (mod, set, build_id_bits, build_id_len, build_id_vaddr); +} + +int dwfl_module_build_id (Dwfl_Module *mod, const unsigned char **bits, GElf_Addr *vaddr) { diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 7cf7499..d454ccb 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -83,7 +83,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg) + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info) { size_t segment = ndx;
@@ -433,6 +434,47 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
dyn_vaddr += bias;
+ /* NAME found from link map has precedence over DT_SONAME possibly read + below. */ + bool name_is_final = false; + + /* Try to match up DYN_VADDR against L_LD as found in link map. + Segments sniffing may guess invalid address as the first read-only memory + mapping may not be dumped to the core file (if ELF headers are not dumped) + and the ELF header is dumped first with the read/write mapping of the same + file at higher addresses. */ + if (r_debug_info != NULL) + for (const struct r_debug_info_module *module = r_debug_info->module; + module != NULL; module = module->next) + if (module_start <= module->l_ld && module->l_ld < module_end) + { + /* L_LD read from link map must be right while DYN_VADDR is unsafe. + Therefore subtract DYN_VADDR and add L_LD to get a possibly + corrective displacement for all addresses computed so far. */ + GElf_Addr fixup = module->l_ld - dyn_vaddr; + if ((fixup & (dwfl->segment_align - 1)) == 0 + && module_start + fixup <= module->l_ld + && module->l_ld < module_end + fixup) + { + module_start += fixup; + module_end += fixup; + dyn_vaddr += fixup; + bias += fixup; + if (module->name[0] != '\0') + { + name = module->name; + name_is_final = true; + } + break; + } + } + + /* Ignore this found module if it would conflict in address space with any + already existing module of DWFL. */ + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + if (module_end > mod->low_addr && module_start < mod->high_addr) + return finish (); + /* Our return value now says to skip the segments contained within the module. */ ndx = addr_segndx (dwfl, segment, module_end, true); @@ -518,7 +560,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
void *soname = NULL; size_t soname_size = 0; - if (dynstrsz != 0 && dynstr_vaddr != 0) + if (! name_is_final && dynstrsz != 0 && dynstr_vaddr != 0) { /* We know the bounds of the .dynstr section.
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 37a9dff..69e6e12 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -362,6 +362,16 @@ extern Dwfl_Error __libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, extern Dwfl_Error __libdwfl_cu_getsrclines (struct dwfl_cu *cu) internal_function;
+/* Look in ELF for an NT_GNU_BUILD_ID note. Store it to BUILD_ID_BITS, + its vaddr in ELF to BUILD_ID_VADDR (it is unrelocated, even if MOD is not + NULL) and store length to BUILD_ID_LEN. Returns -1 for errors, 1 if it was + stored and 0 if no note is found. MOD may be NULL, MOD must be non-NULL + only if ELF is ET_REL. */ +extern int __libdwfl_find_elf_build_id (Dwfl_Module *mod, Elf *elf, + const void **build_id_bits, + GElf_Addr *build_id_elfaddr, + int *build_id_len); + /* Look in ELF for an NT_GNU_BUILD_ID note. If SET is true, store it in MOD and return its length. If SET is false, instead compare it to that stored in MOD and return 2 if they match, 1 if they do not. @@ -439,13 +449,29 @@ typedef bool Dwfl_Module_Callback (Dwfl_Module *mod, void **userdata, GElf_Off whole, GElf_Off contiguous, void *arg, Elf **elfp);
+/* One shared library (or executable) info from DT_DEBUG link map. */ +struct r_debug_info_module +{ + struct r_debug_info_module *next; + GElf_Addr l_ld; + char name[0]; +}; + +/* Information gathered from DT_DEBUG by dwfl_link_map_report hinted to + dwfl_segment_report_module. */ +struct r_debug_info +{ + struct r_debug_info_module *module; +}; + /* ... */ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, Dwfl_Memory_Callback *memory_callback, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, - void *read_eagerly_arg); + void *read_eagerly_arg, + const struct r_debug_info *r_debug_info);
/* Report a module for entry in the dynamic linker's struct link_map list. For each link_map entry, if an existing module resides at its address, @@ -459,10 +485,14 @@ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, only find where to begin if the correct executable file was previously reported and preloaded as with dwfl_report_elf.
+ Fill in R_DEBUG_INFO if it is not NULL. It should be cleared by the + caller, this function does not touch fields it does not need to modify. + Returns the number of modules found, or -1 for errors. */ extern int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg); + void *memory_callback_arg, + struct r_debug_info *r_debug_info);
/* Avoid PLT entries. */ diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c index 9f1b867..e752a5d 100644 --- a/libdwfl/link_map.c +++ b/libdwfl/link_map.c @@ -33,6 +33,7 @@
#include <byteswap.h> #include <endian.h> +#include <fcntl.h>
/* This element is always provided and always has a constant value. This makes it an easy thing to scan for to discern the format. */ @@ -222,7 +223,8 @@ addrsize (uint_fast8_t elfclass) }
/* Report a module for each struct link_map in the linked list at r_map - in the struct r_debug at R_DEBUG_VADDR. + in the struct r_debug at R_DEBUG_VADDR. For r_debug_info description + see dwfl_link_map_report in libdwflP.h.
For each link_map entry, if an existing module resides at its address, this just modifies that module's name and suggested file name. If @@ -234,7 +236,8 @@ static int report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, Dwfl *dwfl, GElf_Addr r_debug_vaddr, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { /* Skip r_version, to aligned r_map field. */ GElf_Addr read_vaddr = r_debug_vaddr + addrsize (elfclass); @@ -349,42 +352,83 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t elfdata, if (name != NULL && name[0] == '\0') name = NULL;
- /* If content-sniffing already reported a module covering - the same area, find that existing module to adjust. - The l_ld address is the only one we know for sure - to be within the module's own segments (its .dynamic). */ - Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (dwfl, l_ld); - if (mod != NULL) + if (iterations == 1 && dwfl->executable_for_core != NULL) + name = dwfl->executable_for_core; + + Dwfl_Module *mod = NULL; + if (name != NULL) { - /* We have a module. We can give it a better name from l_name. */ - if (name != NULL && mod->name[0] == '[') + /* This code is mostly inlined dwfl_report_elf. */ + // XXX hook for sysroot + int fd = open64 (name, O_RDONLY); + if (fd >= 0) { - char *newname = strdup (basename (name)); - if (newname != NULL) + Elf *elf; + Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false); + if (error == DWFL_E_NOERROR) { - free (mod->name); - mod->name = newname; + const void *build_id_bits; + GElf_Addr build_id_elfaddr; + int build_id_len; + bool valid = true; + + /* FIXME: Bias L_ADDR should be computed from the prelink + state in memory (when the file got loaded), not against + the current on-disk file state as is computed below. + + This verification gives false positive if in-core ELF had + build-id but on-disk ELF does not have any. But we cannot + reliably find ELF header and/or the ELF build id just from + the link map (and checking core segments is also not + reliable). */ + + if (__libdwfl_find_elf_build_id (NULL, elf, &build_id_bits, + &build_id_elfaddr, + &build_id_len) > 0 + && build_id_elfaddr != 0) + { + GElf_Addr build_id_vaddr = build_id_elfaddr + l_addr; + release_buffer (0); + int segndx = INTUSE(dwfl_addrsegment) (dwfl, + build_id_vaddr, + NULL); + if (! (*memory_callback) (dwfl, segndx, + &buffer, &buffer_available, + build_id_vaddr, build_id_len, + memory_callback_arg) + || memcmp (build_id_bits, buffer, build_id_len) != 0) + { + /* File has valid build-id which cannot be verified + in memory. */ + valid = false; + } + } + + if (valid) + // XXX hook for sysroot + mod = __libdwfl_report_elf (dwfl, basename (name), name, + fd, elf, l_addr, true, true); + if (mod == NULL) + { + elf_end (elf); + close (fd); + } } } - - if (name == NULL && mod->name[0] == '/') - name = mod->name; - - /* If we don't have a file for it already, we can pre-install - the full file name from l_name. Opening the file by this - name will be the fallback when no build ID match is found. - XXX hook for sysroot */ - if (name != NULL && mod->main.name == NULL) - mod->main.name = strdup (name); } - else if (name != NULL) + if (r_debug_info != NULL && mod == NULL) { - /* We have to find the file's phdrs to compute along with l_addr - what its runtime address boundaries are. */ - - // XXX hook for sysroot - mod = INTUSE(dwfl_report_elf) (dwfl, basename (name), - name, -1, l_addr, true); + /* Save link map information about valid shared library (or + executable) which has not been found on disk. */ + const char *base = name == NULL ? "" : basename (name); + struct r_debug_info_module *module; + module = malloc (sizeof (*module) + strlen (base) + 1); + if (module == NULL) + return release_buffer (result); + module->l_ld = l_ld; + strcpy (module->name, base); + module->next = r_debug_info->module; + r_debug_info->module = module; }
if (mod != NULL) @@ -601,7 +645,8 @@ find_executable (Dwfl *dwfl, GElf_Addr at_phdr, GElf_Addr at_entry, int dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, Dwfl_Memory_Callback *memory_callback, - void *memory_callback_arg) + void *memory_callback_arg, + struct r_debug_info *r_debug_info) { GElf_Addr r_debug_vaddr = 0;
@@ -699,8 +744,65 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size, .d_size = phnum * phent, .d_buf = NULL }; - if ((*memory_callback) (dwfl, phdr_segndx, &in.d_buf, &in.d_size, - phdr, phnum * phent, memory_callback_arg)) + bool in_ok = (*memory_callback) (dwfl, phdr_segndx, &in.d_buf, + &in.d_size, phdr, phnum * phent, + memory_callback_arg); + if (! in_ok && dwfl->executable_for_core != NULL) + { + /* AUXV -> PHDR -> DYNAMIC + Both AUXV and DYNAMIC should be always present in a core file. + PHDR may be missing in core file, try to read it from + EXECUTABLE_FOR_CORE to find where DYNAMIC is located in the + core file. */ + + int fd = open (dwfl->executable_for_core, O_RDONLY); + Elf *elf; + Dwfl_Error error = DWFL_E_ERRNO; + if (fd != -1) + error = __libdw_open_file (&fd, &elf, true, false); + if (error != DWFL_E_NOERROR) + { + __libdwfl_seterrno (error); + return false; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + if (ehdr->e_phnum != phnum || ehdr->e_phentsize != phent) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_BADELF); + return false; + } + off_t off = ehdr->e_phoff; + assert (in.d_buf == NULL); + assert (in.d_size == phnum * phent); + in.d_buf = malloc (in.d_size); + if (unlikely (in.d_buf == NULL)) + { + elf_end (elf); + close (fd); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + ssize_t nread = pread_retry (fd, in.d_buf, in.d_size, off); + elf_end (elf); + close (fd); + if (nread != (ssize_t) in.d_size) + { + free (in.d_buf); + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + in_ok = true; + } + if (in_ok) { union { @@ -862,6 +964,6 @@ dwfl_link_map_report (Dwfl *dwfl, const void *auxv, size_t auxv_size,
/* Now we can follow the dynamic linker's library list. */ return report_r_debug (elfclass, elfdata, dwfl, r_debug_vaddr, - &integrated_memory_callback, &mcb); + &integrated_memory_callback, &mcb, r_debug_info); } INTDEF (dwfl_link_map_report) diff --git a/tests/ChangeLog b/tests/ChangeLog index 6df580a..eca60c3 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,13 @@ +2013-05-30 Jan Kratochvil jan.kratochvil@redhat.com + + * Makefile.am (EXTRA_DIST): Add test-core-lib.so.bz2, + test-core.core.bz2 and test-core.exec.bz2. + * run-addrname-test.sh: New test for these files. + * run-unstrip-n.sh: Update expected output. New test for these files. + * test-core-lib.so.bz2: New file. + * test-core.core.bz2: New file. + * test-core.exec.bz2: New file. + 2013-05-03 Mark Wielaard mjw@redhat.com
* testfilenolines.bz2: New test file. diff --git a/tests/Makefile.am b/tests/Makefile.am index 6327edb..2d819c5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -199,7 +199,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ testfile70.core.bz2 testfile70.exec.bz2 \ run-dwfllines.sh run-dwfl-report-elf-align.sh \ testfile-dwfl-report-elf-align-shlib.so.bz2 \ - testfilenolines + testfilenolines test-core-lib.so.bz2 test-core.core.bz2 \ + test-core.exec.bz2
if USE_VALGRIND valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no' diff --git a/tests/run-addrname-test.sh b/tests/run-addrname-test.sh index 9351fb2..8624074 100755 --- a/tests/run-addrname-test.sh +++ b/tests/run-addrname-test.sh @@ -316,4 +316,10 @@ main+0x9 ??:0 EOF
+testfiles test-core-lib.so test-core.core test-core.exec +testrun_compare ${abs_top_builddir}/src/addr2line -S -e test-core.exec --core=test-core.core 0x7f67f2aaf619 <<\EOF +libfunc+0x9 +??:0 +EOF + exit 0 diff --git a/tests/run-unstrip-n.sh b/tests/run-unstrip-n.sh index 9c2b43b..6ede4c7 100755 --- a/tests/run-unstrip-n.sh +++ b/tests/run-unstrip-n.sh @@ -35,20 +35,35 @@ testfiles testcore-rtlib testcore-rtlib-ppc
testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib <<\EOF 0x8048000+0x2000 f1c600bc36cb91bf01f9a63a634ecb79aa4c3199@0x8048178 . - [exe] +0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 - - libpthread.so.0 +0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 - - libc.so.6 +0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 - - librt.so.1 0xf77d6000+0x1000 676560b1b765cde9c2e53f134f4ee354ea894747@0xf77d6210 . - linux-gate.so.1 -0xf77b3000+0x9000 c6c5b5e35ab9589d4762ac85b4bd56b1b2720e37@0xf77b3164 /lib/librt.so.1 - librt.so.1 -0xf7603000+0x1b0000 0b9bf374699e141e5dfc14757ff42b8c2373b4de@0xf7603184 /lib/libc.so.6 - libc.so.6 -0xf75e9000+0x1a000 29a103420abe341e92072fb14274e250e4072148@0xf75e9164 /lib/libpthread.so.0 - libpthread.so.0 -0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 /lib/ld-linux.so.2 - ld-linux.so.2 +0xf77d7000+0x21000 6d2cb32650054f1c176d01d48713a4a5e5e84c1a@0xf77d7124 - - ld-linux.so.2 EOF
testrun_compare ${abs_top_builddir}/src/unstrip -n --core=testcore-rtlib-ppc <<\EOF -0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] 0x100000+0x10000 708b900b05176964512a6b0fe90c2a0c9d73d726@0x100334 . - linux-vdso32.so.1 -0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 /lib/librt.so.1 - librt.so.1 -0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 /lib/libc.so.6 - libc.so.6 -0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 /lib/libpthread.so.0 - libpthread.so.0 -0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 /lib/ld.so.1 - ld.so.1 +0xfd50000+0x30000 3f7d21508470322d2f47acddc20ab10516edba99@0xfd50164 . - librt.so.1 +0xfdb0000+0x40000 f6ee91d4c629bc7dacc10534cb30056914e7e0b5@0xfdb0164 - - libpthread.so.0 +0xfdf0000+0x1c0000 edf3dd232e09d01b90683889bd16b9406c52d4de@0xfdf0184 - - libc.so.6 +0xffb0000+0x50000 edec437a85026a1cf8cda94003706202733130c1@0xffb0124 - - ld.so.1 +0x10000000+0x20000 979b7a26747cc09bd84a42b311b5288c704baea5@0x10000174 . - [exe] +EOF + +# FAIL was 0x7f67f2caf000 for test-core-lib.so . +# /lib64/libc.so.6 and /lib64/ld-linux-x86-64.so.2 from link map +# do not have ELF header stored in the core file. +# ELF headers in the core file: +# Offset VirtAddr +# 0x014000 0x00007f67f2caf000 ./test-core-lib.so +# 0x03a000 0x00007fff1596c000 linux-vdso.so.1 +testfiles test-core.core test-core.exec +rm -f test-core-lib.so +testrun_compare ${abs_top_builddir}/src/unstrip -n -e test-core.exec --core=test-core.core <<\EOF +0x400000+0x202038 - test-core.exec - test-core.exec +0x7f67f2aaf000+0x202000 - . - test-core-lib.so +0x7fff1596c000+0x1000 a9cf37f53897b5468ee018655760be61b8633d3c@0x7fff1596c340 . - linux-vdso.so.1 EOF
test_cleanup diff --git a/tests/test-core-lib.so.bz2 b/tests/test-core-lib.so.bz2 new file mode 100755 index 0000000..bb2da88 Binary files /dev/null and b/tests/test-core-lib.so.bz2 differ diff --git a/tests/test-core.core.bz2 b/tests/test-core.core.bz2 new file mode 100644 index 0000000..4d4346b Binary files /dev/null and b/tests/test-core.core.bz2 differ diff --git a/tests/test-core.exec.bz2 b/tests/test-core.exec.bz2 new file mode 100755 index 0000000..49ce551 Binary files /dev/null and b/tests/test-core.exec.bz2 differ
elfutils-devel@lists.fedorahosted.org