diff options
author | Stephen Webb <swebb@blackberry.com> | 2020-06-26 18:34:34 -0400 |
---|---|---|
committer | Dave Watson <dade.watson@gmail.com> | 2020-07-02 16:14:18 -0700 |
commit | 6b8202983e7cdde31a4857ebf6318d51fbbed474 (patch) | |
tree | 6f1273b3f339919d55f4aacb5aa4e72f9e242023 | |
parent | 2719aa05d3e9faeff6f7090fef05712c303a8ba1 (diff) | |
download | libunwind-6b8202983e7cdde31a4857ebf6318d51fbbed474.tar.gz |
Use NT_FILE to get backing file names if available
A Linux core dump passes backing file names for LOAD segments. The coredump
library should take advantage of them if they are present.
-rw-r--r-- | src/CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/coredump/_UCD_create.c | 41 | ||||
-rw-r--r-- | src/coredump/_UCD_get_mapinfo_generic.c | 34 | ||||
-rw-r--r-- | src/coredump/_UCD_get_mapinfo_linux.c | 132 | ||||
-rw-r--r-- | src/coredump/_UCD_get_threadinfo_prstatus.c (renamed from src/coredump/_UCD_get_threadinfo_linux.c) | 4 | ||||
-rw-r--r-- | src/coredump/_UCD_internal.h | 4 | ||||
-rw-r--r-- | tests/test-coredump-unwind.c | 13 |
8 files changed, 196 insertions, 45 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 43a61ff1..f295c866 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -94,7 +94,9 @@ if(UNW_CMAKE_TARGET_LINUX) SET(libunwind_la_SOURCES_x86_64_os_local x86_64/Los-linux.c) SET(libunwind_la_SOURCES_arm_os arm/Gos-linux.c) SET(libunwind_la_SOURCES_arm_os_local arm/Los-linux.c) - list(APPEND libunwind_coredump_la_SOURCES coredump/_UCD_access_reg_linux.c) + list(APPEND libunwind_coredump_la_SOURCES coredump/_UCD_access_reg_linux.c + coredump/_UCD_get_threadinfo_prstatus.c + coredump/_UCD_get_mapinfo_linux.c) elseif(UNW_CMAKE_TARGET_FREEBSD) SET(libunwind_la_SOURCES_os ${libunwind_la_SOURCES_os_freebsd}) SET(libunwind_la_SOURCES_os_local ${libunwind_la_SOURCES_os_freebsd_local}) @@ -105,7 +107,9 @@ elseif(UNW_CMAKE_TARGET_FREEBSD) SET(libunwind_la_SOURCES_x86_64_os_local x86_64/Los-freebsd.c) SET(libunwind_la_SOURCES_arm_os arm/Gos-freebsd.c) SET(libunwind_la_SOURCES_arm_os_local arm/Los-freebsd.c) - list(APPEND libunwind_coredump_la_SOURCES coredump/_UCD_access_reg_freebsd.c) + list(APPEND libunwind_coredump_la_SOURCES coredump/_UCD_access_reg_freebsd.c + coredump/_UCD_get_threadinfo_prstatus.c + coredump/_UCD_get_mapinfo_generic.c) elseif(UNW_CMAKE_HOST_SUNOS) SET(libunwind_la_SOURCES_os ${libunwind_la_SOURCES_os_solaris}) SET(libunwind_la_SOURCES_os_local ${libunwind_la_SOURCES_os_solaris_local}) diff --git a/src/Makefile.am b/src/Makefile.am index 4d47203e..4b146bcd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -521,7 +521,8 @@ if OS_LINUX libunwind_la_SOURCES_arm_os = arm/Gos-linux.c libunwind_la_SOURCES_arm_os_local = arm/Los-linux.c libunwind_coredump_la_SOURCES += coredump/_UCD_access_reg_linux.c - libunwind_coredump_la_SOURCES += coredump/_UCD_get_threadinfo_linux.c + libunwind_coredump_la_SOURCES += coredump/_UCD_get_threadinfo_prstatus.c + libunwind_coredump_la_SOURCES += coredump/_UCD_get_mapinfo_linux.c endif if OS_HPUX @@ -540,6 +541,8 @@ if OS_FREEBSD libunwind_la_SOURCES_arm_os = arm/Gos-freebsd.c libunwind_la_SOURCES_arm_os_local = arm/Los-freebsd.c libunwind_coredump_la_SOURCES += coredump/_UCD_access_reg_freebsd.c + libunwind_coredump_la_SOURCES += coredump/_UCD_get_threadinfo_prstatus.c + libunwind_coredump_la_SOURCES += coredump/_UCD_get_mapinfo_generic.c endif if OS_SOLARIS diff --git a/src/coredump/_UCD_create.c b/src/coredump/_UCD_create.c index 3c0345e2..afb300eb 100644 --- a/src/coredump/_UCD_create.c +++ b/src/coredump/_UCD_create.c @@ -181,6 +181,12 @@ _UCD_create(const char *filename) goto err; } + ret = _UCD_get_mapinfo(ui, phdrs, size); + if (ret != UNW_ESUCCESS) { + Debug(0, "failure retrieving file mapping from core file\n"); + goto err; + } + if (unwi_debug_level > 1) { coredump_phdr_t *cur = phdrs; @@ -295,41 +301,6 @@ int _UCD_add_backing_file_at_segment(struct UCD_info *ui, int phdr_no, const cha } //TODO: else loudly complain? Maybe even fail? - if (phdr->p_filesz != 0) - { -//TODO: loop and compare in smaller blocks - char *core_buf = malloc(phdr->p_filesz); - char *file_buf = malloc(phdr->p_filesz); - if (lseek(ui->coredump_fd, phdr->p_offset, SEEK_SET) != (off_t)phdr->p_offset - || (uoff_t)read(ui->coredump_fd, core_buf, phdr->p_filesz) != phdr->p_filesz - ) - { - Debug(0, "Error reading from coredump file\n"); - err_read: - free(core_buf); - free(file_buf); - goto err; - } - if ((uoff_t)read(fd, file_buf, phdr->p_filesz) != phdr->p_filesz) - { - Debug(0, "Error reading from '%s'\n", filename); - goto err_read; - } - int r = memcmp(core_buf, file_buf, phdr->p_filesz); - free(core_buf); - free(file_buf); - if (r != 0) - { - Debug(1, "Note: phdr[%u] first %lld bytes in core dump and in file do not match\n", - phdr_no, (unsigned long long)phdr->p_filesz - ); - } else { - Debug(1, "Note: phdr[%u] first %lld bytes in core dump and in file match\n", - phdr_no, (unsigned long long)phdr->p_filesz - ); - } - } - /* Success */ return 0; diff --git a/src/coredump/_UCD_get_mapinfo_generic.c b/src/coredump/_UCD_get_mapinfo_generic.c new file mode 100644 index 00000000..19945d72 --- /dev/null +++ b/src/coredump/_UCD_get_mapinfo_generic.c @@ -0,0 +1,34 @@ +/** + * Extract filemap info from a coredump (generic) + */ +/* + This file is part of libunwind. + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "_UCD_internal.h" + + +int +_UCD_get_mapinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size) +{ + int ret = UNW_ESUCCESS; /* it's OK if there are no file mappings */ + + return ret; +} diff --git a/src/coredump/_UCD_get_mapinfo_linux.c b/src/coredump/_UCD_get_mapinfo_linux.c new file mode 100644 index 00000000..8dc7709f --- /dev/null +++ b/src/coredump/_UCD_get_mapinfo_linux.c @@ -0,0 +1,132 @@ +/** + * Extract filemap info from a coredump (Linux and similar) + */ +/* + This file is part of libunwind. + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include "_UCD_internal.h" + + +/** + * The format of the NT_FILE note is not well documented, but it goes something + * like this. + * + * The note has a header containing the @i count of the number of file maps, plus a + * value of the size of the offset field in each map. Since we don;t care about + * the offset field in a core file, there is no further information available on + * exactly what the means. + * + * Following the header are @count mapinfo structures. The mapinfo structure consists of + * a start address, and end address, and some wacky offset thing. The start and + * end address are the virtual addresses of a LOAD segment that was mapped from + * the named file. + * + * Following the array of mapinfo structures is a block of null-terminated C strings + * containing the mapped file names. They are ordered correspondingly to each + * entry in the map structure array. + */ +typedef struct { + unsigned long count; + unsigned long pagesz; +} linux_mapinfo_hdr_t; + +typedef struct { + unsigned long start; + unsigned long end; + unsigned long offset; +} linux_mapinfo_t; + + +/** + * Map a file note to program headers + * + * If a NT_FILE note is recognized, parse it and add the resulting backing files + * to the program header list. + * + * Any file names that end in the string "(deleted)" are ignored. + */ +static int +_handle_file_note(uint32_t n_namesz, uint32_t n_descsz, uint32_t n_type, char *name, uint8_t *desc, void *arg) +{ + struct UCD_info *ui = (struct UCD_info *)arg; + if (n_type == NT_FILE) + { + Debug(0, "found a PT_FILE note\n"); + static const char * deleted = "(deleted)"; + size_t deleted_len = strlen(deleted); + static const size_t mapinfo_offset = sizeof(linux_mapinfo_hdr_t); + + linux_mapinfo_hdr_t *mapinfo = (linux_mapinfo_hdr_t *)desc; + linux_mapinfo_t *maps = (linux_mapinfo_t *)(desc + mapinfo_offset); + char *strings = (char *)(desc + mapinfo_offset + sizeof(linux_mapinfo_t)*mapinfo->count); + for (unsigned long i = 0; i < mapinfo->count; ++i) + { + size_t len = strlen(strings); + for (unsigned p = 0; p < ui->phdrs_count; ++p) + { + if (ui->phdrs[p].p_type == PT_LOAD + && maps[i].start >= ui->phdrs[p].p_vaddr + && maps[i].end <= ui->phdrs[p].p_vaddr + ui->phdrs[p].p_filesz) + { + if (len > deleted_len && memcmp(strings + len - deleted_len, deleted, deleted_len)) + { + _UCD_add_backing_file_at_segment(ui, p, strings); + } + break; + } + } + strings += (len + 1); + } + } + return UNW_ESUCCESS; +} + + +/** + * Get filemap info from core file (Linux and similar) + * + * If there is a mapinfo not in the core file, map its contents to the phdrs. + * + * Since there may or may not be any mapinfo notes it's OK for this function to + * fail. + */ +int +_UCD_get_mapinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size) +{ + int ret = UNW_ESUCCESS; /* it's OK if there are no file mappings */ + + for (unsigned i = 0; i < phdr_size; ++i) + { + if (phdrs[i].p_type == PT_NOTE) + { + uint8_t *segment; + size_t segment_size; + ret = _UCD_elf_read_segment(ui, &phdrs[i], &segment, &segment_size); + if (ret == UNW_ESUCCESS) + { + _UCD_elf_visit_notes(segment, segment_size, _handle_file_note, ui); + free(segment); + } + } + } + + return ret; +} diff --git a/src/coredump/_UCD_get_threadinfo_linux.c b/src/coredump/_UCD_get_threadinfo_prstatus.c index d218a079..45552566 100644 --- a/src/coredump/_UCD_get_threadinfo_linux.c +++ b/src/coredump/_UCD_get_threadinfo_prstatus.c @@ -1,5 +1,5 @@ /** - * Extract threadinfo from a coredump (Linux and similar) + * Extract threadinfo from a coredump (targets with NT_PRSTATUS) */ /* This file is part of libunwind. @@ -70,7 +70,7 @@ _save_thread_notes(uint32_t n_namesz, uint32_t n_descsz, uint32_t n_type, char * /** - * Get thread info from core file (Linux and similar) + * Get thread info from core file * * On Linux threads are emulated by cloned processes sharing an address space * and the process information is described by a note in the core file of type diff --git a/src/coredump/_UCD_internal.h b/src/coredump/_UCD_internal.h index 354dbc1d..f5150205 100644 --- a/src/coredump/_UCD_internal.h +++ b/src/coredump/_UCD_internal.h @@ -101,9 +101,11 @@ typedef int (*note_visitor_t)(uint32_t, uint32_t, uint32_t, char *, uint8_t *, v coredump_phdr_t * _UCD_get_elf_image(struct UCD_info *ui, unw_word_t ip); -int _UCD_get_threadinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size); + int _UCD_elf_read_segment(struct UCD_info *ui, coredump_phdr_t *phdr, uint8_t **segment, size_t *segment_size); int _UCD_elf_visit_notes(uint8_t *segment, size_t segment_size, note_visitor_t visit, void *arg); +int _UCD_get_threadinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size); +int _UCD_get_mapinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size); #endif diff --git a/tests/test-coredump-unwind.c b/tests/test-coredump-unwind.c index 1a3c130f..93d9a0ee 100644 --- a/tests/test-coredump-unwind.c +++ b/tests/test-coredump-unwind.c @@ -322,7 +322,7 @@ main(int argc UNUSED, char **argv) if (*colon != ':') error_msg_and_die("Bad format: '%s'", *argv); if (_UCD_add_backing_file_at_vaddr(ui, vaddr, colon + 1) < 0) - error_msg_and_die("Can't add backing file '%s'", colon + 1); + error_msg("Can't add backing file '%s'", colon + 1); argv++; } @@ -338,11 +338,16 @@ main(int argc UNUSED, char **argv) if (ret < 0) error_msg_and_die("unw_get_proc_info(ip=0x%lx) failed: ret=%d\n", (long) ip, ret); - if (!testcase) - printf("\tip=0x%08lx proc=%08lx-%08lx handler=0x%08lx lsda=0x%08lx\n", + if (!testcase) { + char proc_name[128]; + unw_word_t off; + unw_get_proc_name(&c, proc_name, sizeof(proc_name), &off); + + printf("\tip=0x%08lx proc=%08lx-%08lx handler=0x%08lx lsda=0x%08lx %s\n", (long) ip, (long) pi.start_ip, (long) pi.end_ip, - (long) pi.handler, (long) pi.lsda); + (long) pi.handler, (long) pi.lsda, proc_name); + } if (testcase && test_cur < TEST_FRAMES) { |