On Tue, 2014-09-09 at 23:00 +0200, Jan Kratochvil wrote:
Martin Milata:
RFE: dwfl_core_file_report: use NT_FILE core note if the link_map chain is broken https://bugzilla.redhat.com/show_bug.cgi?id=1129777
The dwfl_core_file_report function follows dynamic linker's link_map chain in order to determine the shared libraries used by the executable. As this data structure is located in writable memory it can be overwritten by garbage, which is sometimes the case. https://github.com/abrt/satyr/issues/127#issuecomment-46957546
Since version 3.7 (commit 2aa362c49), Linux kernel adds NT_FILE note to core files which contains the files mapped by the process, including shared libraries.
dwfl_core_file_report now tries to fall back on NT_FILE if the link_map chain is broken.
elfutils would already find the appropriate binary file from /usr/lib/debug/.build-id/ symbolic links. But those symbolic links do not have to be present on the system while NT_FILE still points to the correct binaries.
Filenames from the note NT_FILE are used only if link_map filenames failed to locate matching binaries.
This makes sense to me.
tests/test-core.core.bz2 had to have its NT_FILE disabled as run-unstrip-n.sh otherwise FAILs: FAIL: 0x7f67f2aaf000+0x202000 - . - /home/jkratoch/redhat/elfutils-libregr/test-core-lib.so PASS: 0x7f67f2aaf000+0x202000 - . - test-core-lib.so As test-core-lib.so is found in link_map but it is not present on the disk elfutils now chooses the more reliable filename from NT_FILE (although that filename is also not found on the disk). Updating the expected text would be also sufficient.
I think it is better to just change the expected text instead of updating the binary core file.
libdwfl/ 2014-09-09 Jan Kratochvil jan.kratochvil@redhat.com
Support NT_FILE for locating files.
- core-file.c (dwfl_core_file_report): New variables note_file and
note_file_size, set them and pass them to dwfl_segment_report_module.
OK.
- dwfl_segment_report_module.c: Include common.h and fcntl.h.
(buf_has_data, buf_read_ulong, handle_file_note): New functions.
OK, with some small comments, see below.
(invalid_elf): New function from code of dwfl_segment_report_module. (dwfl_segment_report_module): Add parameters note_file and note_file_size. New variables elf and fd, clean them up in finish. Move some code to invalid_elf. Call handle_file_note, if it found a name verify the file by invalid_elf. Protect elf and fd against cleanup by finish if we found the file for new Dwfl_Module.
- libdwflP.h (dwfl_segment_report_module): Add parameters note_file and
note_file_size.
OK.
tests/ 2014-09-09 Jan Kratochvil jan.kratochvil@redhat.com
Support NT_FILE for locating files.
- Makefile.am (TESTS): Add run-linkmap-cut.sh.
(EXTRA_DIST): Add run-linkmap-cut.sh, linkmap-cut-lib.so.bz2, linkmap-cut.bz2 and linkmap-cut.core.bz2 .
- linkmap-cut-lib.so.bz2: New file.
- linkmap-cut.bz2: New file.
- linkmap-cut.core.bz2: New file.
- run-linkmap-cut.sh: New file.
OK, with one comment.
- test-core.core.bz2: Disable its NT_FILE note.
Please just adjust the expected output.
diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index 4ce63c4..50031ae 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -451,7 +451,9 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable) /* Next, we should follow the chain from DT_DEBUG. */
const void *auxv = NULL;
- const void *note_file = NULL; size_t auxv_size = 0;
- size_t note_file_size = 0; if (likely (notes_phdr.p_type == PT_NOTE)) { /* PT_NOTE -> NT_AUXV -> AT_PHDR -> PT_DYNAMIC -> DT_DEBUG */
@@ -468,13 +470,19 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable) size_t desc_pos; while ((pos = gelf_getnote (notes, pos, &nhdr, &name_pos, &desc_pos)) > 0)
if (nhdr.n_type == NT_AUXV
&& nhdr.n_namesz == sizeof "CORE"
&& !memcmp (notes->d_buf + name_pos, "CORE", sizeof "CORE")) {if (nhdr.n_namesz == sizeof "CORE"
auxv = notes->d_buf + desc_pos;
auxv_size = nhdr.n_descsz;
break;
if (nhdr.n_type == NT_AUXV)
{
auxv = notes->d_buf + desc_pos;
auxv_size = nhdr.n_descsz;
}
if (nhdr.n_type == NT_FILE)
{
note_file = notes->d_buf + desc_pos;
note_file_size = nhdr.n_descsz;
} }} }
@@ -498,6 +506,7 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable) int seg = dwfl_segment_report_module (dwfl, ndx, NULL, &dwfl_elf_phdr_memory_callback, elf, core_file_read_eagerly, elf,
if (unlikely (seg < 0)) {note_file, note_file_size, &r_debug_info);
diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 3393b08..3822e32 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -30,6 +30,7 @@ #include "../libelf/libelfP.h" /* For NOTE_ALIGN. */ #undef _ #include "libdwflP.h" +#include "common.h"
#include <elf.h> #include <gelf.h> @@ -38,6 +39,7 @@ #include <alloca.h> #include <endian.h> #include <unistd.h> +#include <fcntl.h>
/* A good size for the initial read from memory, if it's not too costly. @@ -79,12 +81,161 @@ addr_segndx (Dwfl *dwfl, size_t segment, GElf_Addr addr, bool next) return ndx; }
+/* Return whether there is SZ bytes available at PTR till END.
- Function comes from src/readelf.c . */
function is small enough that comment where it came from is not really necessary.
+static bool +buf_has_data (const void *ptr, const void *end, size_t sz) +{
- return ptr < end && (size_t) (end - ptr) >= sz;
+}
+/* Read SZ bytes into *RETP from *PTRP (limited by END) in format E32.
- Function comes from src/readelf.c . */
+static bool +buf_read_ulong (const Elf32_Ehdr *e32, size_t sz,
const void **ptrp, const void *end, uint64_t *retp)
I appreciate the old code did this, but since this is new code lets clean it up here. Passing in a pointer to an Elf32_Ehdr is slightly confusing. Just pass in the ei_data byte since that is all you are using.
+{
- if (! buf_has_data (*ptrp, end, sz))
- return false;
- union
- {
- uint64_t u64;
- uint32_t u32;
- } u;
- memcpy (&u, *ptrp, sz);
- (*ptrp) += sz;
- if (retp == NULL)
- return true;
- if (MY_ELFDATA != e32->e_ident[EI_DATA])
And just check ei_data as passed in here.
- {
if (sz == 4)
- CONVERT (u.u32);
else
- CONVERT (u.u64);
- }
- if (sz == 4)
- *retp = u.u32;
- else
- *retp = u.u64;
- return true;
+}
+/* Try to find matching entry for module from address MODULE_START to
- MODULE_END in NT_FILE note located at NOTE_FILE of NOTE_FILE_SIZE
- bytes in format E32. */
+static const char * +handle_file_note (GElf_Addr module_start, GElf_Addr module_end,
const Elf32_Ehdr *e32,
const void *note_file, size_t note_file_size)
Same here. Just pass in the ei_class (or ptrsize) and ei_data or a pointer to the e_ident and use that.
+{
- if (note_file == NULL)
- return NULL;
- size_t sz;
- switch (e32->e_ident[EI_CLASS])
use passed in size or ei_class here.
- {
- case ELFCLASS32:
sz = 4;
break;
- case ELFCLASS64:
sz = 8;
break;
- default:
return NULL;
- }
- const void *ptr = note_file;
- const void *end = note_file + note_file_size;
- uint64_t count;
- if (! buf_read_ulong (e32, sz, &ptr, end, &count))
- return NULL;
- if (! buf_read_ulong (e32, sz, &ptr, end, NULL)) // page_size
- return NULL;
Pass ei_data to buf_read_ulong here.
- /* Where file names are stored. */
- const char *fptr = ptr + 3 * count * sz;
- ssize_t firstix = -1;
- ssize_t lastix = -1;
- for (size_t mix = 0; mix < count; mix++)
- {
uint64_t mstart, mend, moffset;
if (! buf_read_ulong (e32, sz, &ptr, fptr, &mstart)
|| ! buf_read_ulong (e32, sz, &ptr, fptr, &mend)
|| ! buf_read_ulong (e32, sz, &ptr, fptr, &moffset))
- return NULL;
Likewise.
if (mstart == module_start && moffset == 0)
- firstix = lastix = mix;
if (firstix != -1 && mstart < module_end)
- lastix = mix;
if (mend >= module_end)
- break;
- }
- if (firstix == -1)
- return NULL;
- const char *retval = NULL;
- for (ssize_t mix = 0; mix <= lastix; mix++)
- {
const char *fnext = memchr (fptr, 0, (const char *) end - fptr);
if (fnext == NULL)
- return NULL;
if (mix == firstix)
- retval = fptr;
if (firstix < mix && mix <= lastix && strcmp (fptr, retval) != 0)
- return NULL;
fptr = fnext + 1;
- }
- return retval;
+}
OK.
+/* Return true iff we are certain ELF cannot match BUILD_ID of
- BUILD_ID_LEN bytes. Pass DISK_FILE_HAS_BUILD_ID as false if it is
- certain ELF does not contain build-id (it is only a performance hit
- to pass it always as true). */
+static bool +invalid_elf (Elf *elf, bool disk_file_has_build_id,
const void *build_id, size_t build_id_len)
+{
- if (! disk_file_has_build_id && build_id_len > 0)
- {
/* Module found in segments with build-id is more reliable
than a module found via DT_DEBUG on disk without any
build-id. */
return true;
- }
- if (disk_file_has_build_id && build_id_len > 0)
- {
const void *elf_build_id;
ssize_t elf_build_id_len;
/* If there is a build id in the elf file, check it. */
elf_build_id_len = INTUSE(dwelf_elf_gnu_build_id) (elf, &elf_build_id);
if (elf_build_id_len > 0)
- {
if (build_id_len != (size_t) elf_build_id_len
|| memcmp (build_id, elf_build_id, build_id_len) != 0)
return true;
- }
- }
- return false;
+}
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,
const void *note_file, size_t note_file_size, const struct r_debug_info *r_debug_info)
{ size_t segment = ndx; @@ -121,10 +272,16 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
void *buffer = NULL; size_t buffer_available = INITIAL_READ;
Elf *elf = NULL;
int fd = -1;
inline int finish (void) { release_buffer (&buffer, &buffer_available);
if (elf != NULL)
elf_end (elf);
if (fd != -1)
close (fd);
return ndx; }
@@ -481,32 +638,9 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, if ((module_end > module->start && module_start < module->end) || dyn_vaddr == module->l_ld) {
bool close_elf = false;
if (! module->disk_file_has_build_id && build_id_len > 0)
{
/* Module found in segments with build-id is more reliable
than a module found via DT_DEBUG on disk without any
build-id. */
if (module->elf != NULL)
close_elf = true;
} if (module->elf != NULL
&& module->disk_file_has_build_id && build_id_len > 0)
{
const void *elf_build_id;
ssize_t elf_build_id_len;
/* If there is a build id in the elf file, check it. */
elf_build_id_len = INTUSE(dwelf_elf_gnu_build_id) (module->elf,
&elf_build_id);
if (elf_build_id_len > 0)
{
if (build_id_len != (size_t) elf_build_id_len
|| memcmp (build_id, elf_build_id, build_id_len) != 0)
close_elf = true;
}
}
if (close_elf)
&& invalid_elf (module->elf, module->disk_file_has_build_id,
elf_end (module->elf); close (module->fd);build_id, build_id_len)) {
@@ -527,6 +661,29 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, } }
- const char *file_note_name = handle_file_note (module_start, module_end,
&ehdr.e32,
note_file, note_file_size);
- if (file_note_name)
- {
name = file_note_name;
name_is_final = true;
bool invalid = false;
fd = open64 (name, O_RDONLY);
if (fd >= 0)
- {
Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false);
if (error == DWFL_E_NOERROR)
invalid = invalid_elf (elf, true /* disk_file_has_build_id */,
build_id, build_id_len);
- }
if (invalid)
- {
free (build_id);
return finish ();
- }
- }
- /* Our return value now says to skip the segments contained within the module. */ ndx = addr_segndx (dwfl, segment, module_end, true);
@@ -683,10 +840,10 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, : dynstr_vaddr + dynstrsz - start); const GElf_Off whole = MAX (file_trimmed_end, shdrs_end);
- Elf *elf = NULL;
- if ((*read_eagerly) (MODCB_ARGS (mod), &buffer, &buffer_available,
cost, worthwhile, whole, contiguous,
read_eagerly_arg, &elf)
- if (elf == NULL
&& (*read_eagerly) (MODCB_ARGS (mod), &buffer, &buffer_available,
cost, worthwhile, whole, contiguous,
&& elf == NULL) { /* The caller wants to read the whole file in right now, but hasn'tread_eagerly_arg, &elf)
@@ -748,6 +905,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, { /* Install the file in the module. */ mod->main.elf = elf;
elf = NULL;
fd = -1; mod->main.vaddr = module_start - bias; mod->main.address_sync = module_address_sync; mod->main_bias = bias;
OK.
+testfiles linkmap-cut-lib.so linkmap-cut linkmap-cut.core +tempfiles bt +# It may have non-zero exit code with: +# .../elfutils/src/stack: dwfl_thread_getframes tid 3130 at 0x3fdf821d64 in /usr/lib64/libc-2.18.so: no matching address range +testrun ${abs_top_builddir}/src/stack --core=linkmap-cut.core -e linkmap-cut >bt || true +cat bt +grep -qw libfunc bt +grep -qw main bt
Maybe you want to use -m to check it got the module name correct? The NT_FILE notes seem to have lots of slashed in them, is that deliberate? The comments don't say how the binaries were generated, please add that in case someone wants to regenerate them one day.
Looks good with those small issues fixed.
Thanks,
Mark
On Tue, 23 Sep 2014 16:04:58 +0200, Mark Wielaard wrote:
On Tue, 2014-09-09 at 23:00 +0200, Jan Kratochvil wrote:
tests/test-core.core.bz2 had to have its NT_FILE disabled as run-unstrip-n.sh otherwise FAILs: FAIL: 0x7f67f2aaf000+0x202000 - . - /home/jkratoch/redhat/elfutils-libregr/test-core-lib.so PASS: 0x7f67f2aaf000+0x202000 - . - test-core-lib.so As test-core-lib.so is found in link_map but it is not present on the disk elfutils now chooses the more reliable filename from NT_FILE (although that filename is also not found on the disk). Updating the expected text would be also sufficient.
I think it is better to just change the expected text instead of updating the binary core file.
Done. Just the testcase now unsuccessfully does: open("/home/jkratoch/redhat/elfutils-libregr/test-core-lib.so", O_RDONLY) = -1 ENOENT (No such file or directory) and it may have some unexpected result if such file exists (sure unlikely).
+/* Return whether there is SZ bytes available at PTR till END.
- Function comes from src/readelf.c . */
function is small enough that comment where it came from is not really necessary.
Done.
Just pass in the ei_data byte since that is all you are using.
Done.
Same here. Just pass in the ei_class (or ptrsize) and ei_data or a pointer to the e_ident and use that.
Done.
+testrun ${abs_top_builddir}/src/stack --core=linkmap-cut.core -e linkmap-cut >bt || true +cat bt +grep -qw libfunc bt +grep -qw main bt
Maybe you want to use -m to check it got the module name correct?
OK, used that and updated the match: grep -q '^#0 0x00007f08bc24d681 libfunc - .////////////////////////////////////linkmap-cut-lib.so$' bt grep -q '^#1 0x00000000004006b4 main - linkmap-cut$' bt
The NT_FILE notes seem to have lots of slashed in them, is that deliberate?
There is always absolute filename from Linux kernel. That would never work for a testcase, therefore I had to patch the bytes by hexedit from absolute pathname to a relative one: 7f08bc24d000-7f08bc24e000 00000000 4096 /home/jkratoch/redhat/elfutils/tests/linkmap-cut-lib.so -> 7f08bc24d000-7f08bc24e000 00000000 4096 .////////////////////////////////////linkmap-cut-lib.so
The comments don't say how the binaries were generated, please add that in case someone wants to regenerate them one day.
I agree, added: # The core file has patched: # * _r_debug.r_map.l_next.l_next (vma 0x3fdf621718, offset 0x7718) = NULL, # therefore all libraries after the main executable and vDSO are removed. # * NT_FILE absolute filenames are relativized to: ./////basename
Checked in: 6d48bbf406851b56920582b8ce29db13ad4f4e2e
Thanks, Jan
elfutils-devel@lists.fedorahosted.org