summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Webb <swebb@blackberry.com>2020-06-26 18:34:34 -0400
committerDave Watson <dade.watson@gmail.com>2020-07-02 16:14:18 -0700
commit6b8202983e7cdde31a4857ebf6318d51fbbed474 (patch)
tree6f1273b3f339919d55f4aacb5aa4e72f9e242023
parent2719aa05d3e9faeff6f7090fef05712c303a8ba1 (diff)
downloadlibunwind-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.txt8
-rw-r--r--src/Makefile.am5
-rw-r--r--src/coredump/_UCD_create.c41
-rw-r--r--src/coredump/_UCD_get_mapinfo_generic.c34
-rw-r--r--src/coredump/_UCD_get_mapinfo_linux.c132
-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.h4
-rw-r--r--tests/test-coredump-unwind.c13
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)
{