summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Webb <swebb@blackberry.com>2022-07-23 22:30:23 -0400
committerStephen M. Webb <stephen.webb@bregmasoft.ca>2022-07-26 21:44:26 -0400
commite96db56e586ad7a1c8d27593382b365f775a398f (patch)
treeab6bcc10a8504204d1f52b379d2ddc7cb1efb1ff
parent10e093db21b73e98ba8485b4b0696d330bf8dfdf (diff)
downloadlibunwind-e96db56e586ad7a1c8d27593382b365f775a398f.tar.gz
Stop assuming .text and .eh_frame segment
The coredump remote was architected on the assumption that the .text and .eh_frame sections were mapped onto the same segment, and that that segment was always the first PT_LOAD segment in an ELF file. Well, that was never a valid assumption, and moderns releases of various toolchains have started splitting the PT_LOAD segments for security reasons. This change implements an M:N mapping of PT_LOAD segments in a coredump file to backing ELF files and calculates and adjusts offsets appropriately. Because the backing files get mapped in a lot of file I/O operations have been replaced with simple memory reads. Once a backing file is memory mapped is stays mapped until the address space is destroyed. The ucd_*.[ch] files contain only functions that should not be exposed through the public API so they;re not mangled using the UB naming schedule because I just bring myself to write code with undefined behaviour. Reformatted some of the changed files using `astyle --style=gnu` for internal consistency withing the file. Fixes #363
-rw-r--r--src/Makefile.am1
-rw-r--r--src/coredump/_UCD_access_mem.c109
-rw-r--r--src/coredump/_UCD_create.c109
-rw-r--r--src/coredump/_UCD_destroy.c9
-rw-r--r--src/coredump/_UCD_elf_map_image.c24
-rw-r--r--src/coredump/_UCD_find_proc_info.c18
-rw-r--r--src/coredump/_UCD_get_mapinfo_linux.c144
-rw-r--r--src/coredump/_UCD_get_proc_name.c79
-rw-r--r--src/coredump/_UCD_internal.h21
-rw-r--r--src/coredump/ucd_file_table.c275
-rw-r--r--src/coredump/ucd_file_table.h83
-rw-r--r--tests/test-coredump-unwind.c11
12 files changed, 624 insertions, 259 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index cf788f27..8fed15e9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -61,6 +61,7 @@ libunwind_coredump_la_SOURCES = \
coredump/_UCD_find_proc_info.c \
coredump/_UCD_get_proc_name.c \
coredump/_UCD_corefile_elf.c \
+ coredump/ucd_file_table.c \
\
coredump/_UPT_elf.c \
coredump/_UPT_access_fpreg.c \
diff --git a/src/coredump/_UCD_access_mem.c b/src/coredump/_UCD_access_mem.c
index 1fdbd128..cdfc6220 100644
--- a/src/coredump/_UCD_access_mem.c
+++ b/src/coredump/_UCD_access_mem.c
@@ -23,76 +23,83 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include "_UCD_lib.h"
#include "_UCD_internal.h"
+#include "ucd_file_table.h"
int
-_UCD_access_mem(unw_addr_space_t as, unw_word_t addr, unw_word_t *val,
- int write, void *arg)
+_UCD_access_mem (unw_addr_space_t as,
+ unw_word_t addr,
+ unw_word_t *val,
+ int write,
+ void *arg)
{
if (write)
{
- Debug(0, "write is not supported\n");
+ Debug (0, "write is not supported\n");
return -UNW_EINVAL;
}
struct UCD_info *ui = arg;
- unw_word_t addr_last = addr + sizeof(*val)-1;
- coredump_phdr_t *phdr;
+ unw_word_t addr_last = addr + sizeof (*val) - 1;
+
unsigned i;
+
for (i = 0; i < ui->phdrs_count; i++)
{
- phdr = &ui->phdrs[i];
- if (phdr->p_vaddr <= addr && addr_last < phdr->p_vaddr + phdr->p_memsz)
+ coredump_phdr_t *phdr = &ui->phdrs[i];
+
+ /* First check the (in-memory) backup file image. */
+ if (phdr->p_backing_file_index != ucd_file_no_index)
{
- goto found;
- }
- }
- Debug(1, "addr 0x%llx is unmapped\n", (unsigned long long)addr);
- return -UNW_EINVAL;
+ ucd_file_t *ucd_file = ucd_file_table_at (&ui->ucd_file_table, phdr->p_backing_file_index);
- found: ;
+ if (ucd_file == NULL)
+ {
+ Debug (0, "invalid backing file index for phdr[%d]\n", i);
+ return -UNW_EINVAL;
+ }
- const char *filename UNUSED;
- off_t fileofs;
- int fd;
- if (addr_last >= phdr->p_vaddr + phdr->p_filesz)
- {
- /* This part of mapped address space is not present in coredump file */
- /* Do we have it in the backup file? */
- if (phdr->backing_fd < 0)
+ off_t image_offset = addr - phdr->p_vaddr;
+
+ if (phdr->p_vaddr <= addr && addr_last < phdr->p_vaddr + ucd_file->size)
+ {
+ memcpy (val, ucd_file->image + image_offset, sizeof (*val));
+ Debug (16, "%#010llx <- [addr:%#010llx file:%s]\n",
+ (unsigned long long) (*val),
+ (unsigned long long)image_offset,
+ ucd_file->filename);
+ return UNW_ESUCCESS;
+ }
+ }
+
+ /* Next, check the on-disk corefile. */
+ if (phdr->p_vaddr <= addr && addr_last < phdr->p_vaddr + phdr->p_memsz)
{
- Debug(1, "access to not-present data in phdr[%d]: addr:0x%llx\n",
- i, (unsigned long long)addr
- );
- return -UNW_EINVAL;
+ off_t fileofs = phdr->p_offset + (addr - phdr->p_vaddr);
+
+ if (lseek (ui->coredump_fd, fileofs, SEEK_SET) != fileofs)
+ {
+ Debug (0, "error %d in lseek(\"%s\", %lld): %s\n",
+ errno, ui->coredump_filename, (long long)fileofs, strerror (errno));
+ return -UNW_EINVAL;
+ }
+
+ if (read (ui->coredump_fd, val, sizeof (*val)) != sizeof (*val))
+ {
+ Debug (0, "error %d in read(\"%s\", %lld): %s\n",
+ errno, ui->coredump_filename, (long long)sizeof (*val), strerror (errno));
+ return -UNW_EINVAL;
+ }
+
+ Debug (16, "0x%llx <- [addr:0x%llx fileofs:0x%llx file:%s]\n",
+ (unsigned long long) (*val),
+ (unsigned long long)addr,
+ (unsigned long long)fileofs,
+ ui->coredump_filename);
+ return UNW_ESUCCESS;
}
- filename = phdr->backing_filename;
- fileofs = addr - phdr->p_vaddr;
- fd = phdr->backing_fd;
- goto read;
}
- filename = ui->coredump_filename;
- fileofs = phdr->p_offset + (addr - phdr->p_vaddr);
- fd = ui->coredump_fd;
- read:
- if (lseek(fd, fileofs, SEEK_SET) != fileofs)
- goto read_error;
- if (read(fd, val, sizeof(*val)) != sizeof(*val))
- goto read_error;
-
- Debug(1, "0x%llx <- [addr:0x%llx fileofs:0x%llx]\n",
- (unsigned long long)(*val),
- (unsigned long long)addr,
- (unsigned long long)fileofs
- );
- return 0;
-
- read_error:
- Debug(1, "access out of file: addr:0x%llx fileofs:%llx file:'%s'\n",
- (unsigned long long)addr,
- (unsigned long long)fileofs,
- filename
- );
+ Debug (0, "addr %#010llx is unmapped\n", (unsigned long long)addr);
return -UNW_EINVAL;
}
diff --git a/src/coredump/_UCD_create.c b/src/coredump/_UCD_create.c
index e5ccd356..784d0462 100644
--- a/src/coredump/_UCD_create.c
+++ b/src/coredump/_UCD_create.c
@@ -142,9 +142,7 @@ _UCD_create(const char *filename)
cur->p_filesz = hdr64.p_filesz;
cur->p_memsz = hdr64.p_memsz ;
cur->p_align = hdr64.p_align ;
- /* cur->backing_filename = NULL; - done by memset */
- cur->backing_fd = -1;
- cur->backing_filesize = hdr64.p_filesz;
+ cur->p_backing_file_index = -1;
i++;
cur++;
}
@@ -167,9 +165,7 @@ _UCD_create(const char *filename)
cur->p_filesz = hdr32.p_filesz;
cur->p_memsz = hdr32.p_memsz ;
cur->p_align = hdr32.p_align ;
- /* cur->backing_filename = NULL; - done by memset */
- cur->backing_fd = -1;
- cur->backing_filesize = hdr32.p_memsz;
+ cur->p_backing_file_index = -1;
i++;
cur++;
}
@@ -181,6 +177,10 @@ _UCD_create(const char *filename)
goto err;
}
+ ret = ucd_file_table_init(&ui->ucd_file_table);
+ if (ret != UNW_ESUCCESS) {
+ Debug(0, "error initializing backing file table\n");
+ }
ret = _UCD_get_mapinfo(ui, phdrs, size);
if (ret != UNW_ESUCCESS) {
Debug(0, "failure retrieving file mapping from core file\n");
@@ -192,23 +192,17 @@ _UCD_create(const char *filename)
{
if (cur->p_type == PT_LOAD)
{
- Debug(2, " ofs:%08llx va:%08llx filesize:%08llx memsize:%08llx flg:%x",
+ Debug(2, "phdr[%u] ofs:%#010llx va:%#010llx filesize:%#010llx memsize:%#010llx flg:%#04x bf_idx=%d %s %s\n",
+ i,
(unsigned long long) cur->p_offset,
(unsigned long long) cur->p_vaddr,
(unsigned long long) cur->p_filesz,
(unsigned long long) cur->p_memsz,
- cur->p_flags
- );
- if (cur->p_filesz < cur->p_memsz)
- {
- Debug(2, " partial");
- }
- if (cur->p_flags & PF_X)
- {
- Debug(2, " executable");
- }
+ cur->p_flags,
+ cur->p_backing_file_index,
+ (cur->p_filesz < cur->p_memsz)?"partial":" ",
+ (cur->p_flags & PF_X)?"exec":" ");
}
- Debug(2, "\n");
cur++;
}
@@ -263,82 +257,3 @@ int _UCD_get_cursig(struct UCD_info *ui)
#endif
}
-int _UCD_add_backing_file_at_segment(struct UCD_info *ui, int phdr_no, const char *filename)
-{
- if ((unsigned)phdr_no >= ui->phdrs_count)
- {
- Debug(0, "There is no segment %d in this coredump\n", phdr_no);
- return -1;
- }
-
- struct coredump_phdr *phdr = &ui->phdrs[phdr_no];
- if (phdr->backing_filename)
- {
- Debug(0, "Backing file already added to segment %d\n", phdr_no);
- return -1;
- }
-
- int fd = open(filename, O_RDONLY);
- if (fd < 0)
- {
- Debug(0, "Can't open '%s'\n", filename);
- return -1;
- }
-
- phdr->backing_fd = fd;
- phdr->backing_filename = strdup(filename);
-
- struct stat statbuf;
- if (fstat(fd, &statbuf) != 0)
- {
- Debug(0, "Can't stat '%s'\n", filename);
- goto err;
- }
- phdr->backing_filesize = (uoff_t)statbuf.st_size;
-
- if (phdr->p_flags != (PF_X | PF_R))
- {
- Debug(1, "Note: phdr[%u] is not r-x: flags are 0x%x\n",
- phdr_no, phdr->p_flags);
- }
-
- if (phdr->backing_filesize > phdr->p_memsz)
- {
- /* This is expected */
- Debug(2, "Note: phdr[%u] is %lld bytes, file is larger: %lld bytes\n",
- phdr_no,
- (unsigned long long)phdr->p_memsz,
- (unsigned long long)phdr->backing_filesize
- );
- }
-//TODO: else loudly complain? Maybe even fail?
-
- /* Success */
- return 0;
-
- err:
- if (phdr->backing_fd >= 0)
- {
- close(phdr->backing_fd);
- phdr->backing_fd = -1;
- }
- free(phdr->backing_filename);
- phdr->backing_filename = NULL;
- return -1;
-}
-
-int _UCD_add_backing_file_at_vaddr(struct UCD_info *ui,
- unsigned long vaddr,
- const char *filename)
-{
- unsigned i;
- for (i = 0; i < ui->phdrs_count; i++)
- {
- struct coredump_phdr *phdr = &ui->phdrs[i];
- if (phdr->p_vaddr != vaddr)
- continue;
- /* It seems to match. Add it. */
- return _UCD_add_backing_file_at_segment(ui, i, filename);
- }
- return -1;
-}
diff --git a/src/coredump/_UCD_destroy.c b/src/coredump/_UCD_destroy.c
index ddc36ec8..c767aa44 100644
--- a/src/coredump/_UCD_destroy.c
+++ b/src/coredump/_UCD_destroy.c
@@ -35,14 +35,7 @@ _UCD_destroy (struct UCD_info *ui)
invalidate_edi (&ui->edi);
- unsigned i;
- for (i = 0; i < ui->phdrs_count; i++)
- {
- struct coredump_phdr *phdr = &ui->phdrs[i];
- free(phdr->backing_filename);
- if (phdr->backing_fd >= 0)
- close(phdr->backing_fd);
- }
+ ucd_file_table_dispose(&ui->ucd_file_table);
free(ui->phdrs);
free(ui->note_phdr);
diff --git a/src/coredump/_UCD_elf_map_image.c b/src/coredump/_UCD_elf_map_image.c
index 99fd25e8..ea36745a 100644
--- a/src/coredump/_UCD_elf_map_image.c
+++ b/src/coredump/_UCD_elf_map_image.c
@@ -29,13 +29,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include "_UCD_lib.h"
#include "_UCD_internal.h"
+#include "ucd_file_table.h"
static coredump_phdr_t *
CD_elf_map_image(struct UCD_info *ui, coredump_phdr_t *phdr)
{
struct elf_image *ei = &ui->edi.ei;
- if (phdr->backing_fd < 0)
+ if (phdr->p_backing_file_index == ucd_file_no_index)
{
/* Note: coredump file contains only phdr->p_filesz bytes.
* We want to map bigger area (phdr->p_memsz bytes) to make sure
@@ -45,6 +46,7 @@ CD_elf_map_image(struct UCD_info *ui, coredump_phdr_t *phdr)
ei->image = mmap(NULL, phdr->p_memsz, PROT_READ, MAP_PRIVATE, ui->coredump_fd, phdr->p_offset);
if (ei->image == MAP_FAILED)
{
+ Debug(0, "error %d in mmap(): %s\n", errno, strerror(errno));
ei->image = NULL;
return NULL;
}
@@ -56,21 +58,19 @@ CD_elf_map_image(struct UCD_info *ui, coredump_phdr_t *phdr)
munmap(remainder_base, remainder_len);
}
} else {
- /* We have a backing file for this segment.
- * This file is always longer than phdr->p_memsz,
- * and if phdr->p_filesz !=0, first phdr->p_filesz bytes in coredump
- * are the same as first bytes in the file. (Thus no need to map coredump)
- * We map the entire file:
- * unwinding may need data which is past phdr->p_memsz bytes.
- */
+ ucd_file_t *ucd_file = ucd_file_table_at(&ui->ucd_file_table, phdr->p_backing_file_index);
+ if (ucd_file == NULL)
+ {
+ Debug(0, "error retrieving backing file for index %d\n", phdr->p_backing_file_index);
+ return NULL;
+ }
/* addr, length, prot, flags, fd, fd_offset */
- ei->image = mmap(NULL, phdr->backing_filesize, PROT_READ, MAP_PRIVATE, phdr->backing_fd, 0);
- if (ei->image == MAP_FAILED)
+ ei->image = ucd_file_map (ucd_file);
+ if (ei->image == NULL)
{
- ei->image = NULL;
return NULL;
}
- ei->size = phdr->backing_filesize;
+ ei->size = ucd_file->size;
}
/* Check ELF header for sanity */
diff --git a/src/coredump/_UCD_find_proc_info.c b/src/coredump/_UCD_find_proc_info.c
index 35e1624c..fae61d9f 100644
--- a/src/coredump/_UCD_find_proc_info.c
+++ b/src/coredump/_UCD_find_proc_info.c
@@ -53,6 +53,9 @@ get_unwind_info(struct UCD_info *ui, unw_addr_space_t as, unw_word_t ip)
&& ip >= ui->edi.di_debug.start_ip && ip < ui->edi.di_debug.end_ip))
return 0;
+ /* The invalidate_edi call unmaps memory it doesn't own, so just null it out
+ instead. */
+ ui->edi.ei.image = NULL;
invalidate_edi (&ui->edi);
/* Used to be tdep_get_elf_image() in ptrace unwinding code */
@@ -62,17 +65,22 @@ get_unwind_info(struct UCD_info *ui, unw_addr_space_t as, unw_word_t ip)
Debug(1, "returns error: _UCD_get_elf_image failed\n");
return -UNW_ENOINFO;
}
+
+ ucd_file_t *ucd_file = ucd_file_table_at(&ui->ucd_file_table, phdr->p_backing_file_index);
+ if (ucd_file == NULL)
+ {
+ Debug(0, "no backing file for index %d\n", phdr->p_backing_file_index);
+ return -UNW_ENOINFO;
+ }
+
/* segbase: where it is mapped in virtual memory */
- /* mapoff: offset in the file */
segbase = phdr->p_vaddr;
- /*mapoff = phdr->p_offset; WRONG! phdr->p_offset is the offset in COREDUMP file */
+ /* mapoff doesn't matter since the entire file is loaded */
mapoff = 0;
-///FIXME. text segment is USUALLY, not always, at offset 0 in the binary/.so file.
-// ensure that at initialization.
/* Here, SEGBASE is the starting-address of the (mmap'ped) segment
which covers the IP we're looking for. */
- if (tdep_find_unwind_table(&ui->edi, as, phdr->backing_filename, segbase, mapoff, ip) < 0)
+ if (tdep_find_unwind_table(&ui->edi, as, ucd_file->filename, segbase, mapoff, ip) < 0)
{
Debug(1, "returns error: tdep_find_unwind_table failed\n");
return -UNW_ENOINFO;
diff --git a/src/coredump/_UCD_get_mapinfo_linux.c b/src/coredump/_UCD_get_mapinfo_linux.c
index 0df2107f..ac2df590 100644
--- a/src/coredump/_UCD_get_mapinfo_linux.c
+++ b/src/coredump/_UCD_get_mapinfo_linux.c
@@ -3,17 +3,17 @@
*/
/*
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
@@ -43,60 +43,105 @@
* containing the mapped file names. They are ordered correspondingly to each
* entry in the map structure array.
*/
-typedef struct {
+struct core_nt_file_hdr_s
+{
unsigned long count;
unsigned long pagesz;
-} linux_mapinfo_hdr_t;
+};
+typedef struct core_nt_file_hdr_s core_nt_file_hdr_t;
-typedef struct {
+struct core_nt_file_entry_s
+{
unsigned long start;
unsigned long end;
unsigned long offset;
-} linux_mapinfo_t;
+};
+typedef struct core_nt_file_entry_s core_nt_file_entry_t;
+
+
+static const char deleted[] = "(deleted)";
+static const size_t deleted_len = sizeof (deleted);
+static const size_t mapinfo_offset = sizeof (core_nt_file_hdr_t);
/**
- * Map a file note to program headers
+ * Handle the CORE/NT_FILE note type.
+ * @param[in] desc The note-specific data
+ * @param[in] arg The user-supplied callback argument
*
- * If a NT_FILE note is recognized, parse it and add the resulting backing files
- * to the program header list.
+ * The CORE/NT_FILE note type contains a list of start/end virtual addresses
+ * within the core file and an associated filename. The purpose is to mape
+ * various segments loaded into memory from ELF files with the ELF file from
+ * which those segments were loaded.
+ *
+ * This function links the file namess mapped in the CORE/NT_FILE note with
+ * the program headers in the core file through the UCD_info file table.
*
* 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)
+_handle_nt_file_note (uint8_t *desc, void *arg)
{
struct UCD_info *ui = (struct UCD_info *)arg;
-#ifdef NT_FILE
- 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)
+ core_nt_file_hdr_t *mapinfo = (core_nt_file_hdr_t *)desc;
+ core_nt_file_entry_t *maps = (core_nt_file_entry_t *) (desc + mapinfo_offset);
+ char *strings = (char *) (desc + mapinfo_offset + sizeof (core_nt_file_entry_t) * mapinfo->count);
+
+ for (unsigned long i = 0; i < mapinfo->count; ++i)
{
- size_t len = strlen(strings);
+ 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;
- }
- }
+ {
+ 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_memsz)
+ {
+ if (len > deleted_len && memcmp (strings + len - deleted_len, deleted, deleted_len))
+ {
+ ui->phdrs[p].p_backing_file_index = ucd_file_table_insert (&ui->ucd_file_table, strings);
+ Debug (3, "adding '%s' at index %d\n", strings, ui->phdrs[p].p_backing_file_index);
+ }
+
+ break;
+ }
+ }
+
strings += (len + 1);
}
- }
+
+ return UNW_ESUCCESS;
+}
+
+/**
+ * Callback to handle notes.
+ * @param[in] n_namesz size of name data
+ * @param[in] n_descsz size of desc data
+ * @param[in] n_type type of note
+ * @param[in] name zero-terminated string, n_namesz bytes plus alignment padding
+ * @param[in] desc note-specific data, n_descsz bytes plus alignment padding
+ * @param[in] arg user-supplied callback argument
+ *
+ * Add additional note types here for fun and frolicks. Right now the only note
+ * type handled is the CORE/NT_FILE note used on GNU/Linux. FreeBSD uses a
+ * FreeBSD/NT_PROCSTAT_VMMAP note and QNX uses a QNX/QNT_DEBUG_LINK_MAP note for
+ * similar purposes. Other target OSes probably use something else.
+ *
+ * Note interpretation requires both name and type.
+ */
+static int
+_handle_pt_note_segment (uint32_t n_namesz,
+ uint32_t n_descsz,
+ uint32_t n_type,
+ char *name,
+ uint8_t *desc,
+ void *arg)
+{
+#ifdef NT_FILE
+ if (n_type == NT_FILE && strcmp (name, "CORE") == 0)
+ {
+ return _handle_nt_file_note (desc, arg);
+ }
#endif
return UNW_ESUCCESS;
}
@@ -111,24 +156,25 @@ _handle_file_note(uint32_t n_namesz, uint32_t n_descsz, uint32_t n_type, char *n
* fail.
*/
int
-_UCD_get_mapinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size)
+_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);
- }
+ 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_pt_note_segment, ui);
+ free (segment);
+ }
+ }
}
- }
return ret;
}
diff --git a/src/coredump/_UCD_get_proc_name.c b/src/coredump/_UCD_get_proc_name.c
index cd5ee892..aeec3c9b 100644
--- a/src/coredump/_UCD_get_proc_name.c
+++ b/src/coredump/_UCD_get_proc_name.c
@@ -24,6 +24,57 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include "_UCD_lib.h"
#include "_UCD_internal.h"
+#if defined(HAVE_ELF_H)
+# include <elf.h>
+#elif defined(HAVE_SYS_ELF_H)
+# include <sys/elf.h>
+#endif
+
+
+static off_t
+_get_text_offset (uint8_t *image)
+{
+ off_t offset = 0;
+ typedef union
+ {
+ Elf32_Ehdr h32;
+ Elf64_Ehdr h64;
+ } elf_header_t;
+
+ elf_header_t *elf_header = (elf_header_t *)image;
+ bool _64bits = (elf_header->h32.e_ident[EI_CLASS] == ELFCLASS64);
+ off_t e_phofs = _64bits ? elf_header->h64.e_phoff : elf_header->h32.e_phoff;
+ unsigned e_phnum = _64bits ? elf_header->h64.e_phnum : elf_header->h32.e_phnum;
+
+ for (unsigned i = 0; i < e_phnum; ++i)
+ {
+ if (_64bits)
+ {
+ Elf64_Phdr *phdr = (Elf64_Phdr *) (image + e_phofs);
+
+ if (phdr[i].p_type == PT_LOAD && (phdr[i].p_flags & PF_X) == PF_X)
+ {
+ offset = phdr[i].p_offset;
+ break;
+ }
+ }
+
+ else
+ {
+ Elf32_Phdr *phdr = (Elf32_Phdr *) (image + e_phofs);
+
+ if ((phdr[i].p_flags & PF_X) == PF_X)
+ {
+ offset = phdr[i].p_offset;
+ break;
+ }
+ }
+ }
+
+ Debug (4, "returning offset %ld\n", (long)offset);
+ return offset;
+}
+
/* Find the ELF image that contains IP and return the "closest"
procedure name, if there is one. With some caching, this could be
@@ -31,30 +82,29 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
sensitive to the performance of this routine, why bother... */
static int
elf_w (CD_get_proc_name) (struct UCD_info *ui, unw_addr_space_t as, unw_word_t ip,
- char *buf, size_t buf_len, unw_word_t *offp)
+ char *buf, size_t buf_len, unw_word_t *offp)
{
unsigned long segbase, mapoff;
int ret;
-
- /* We're about to map an elf image. If there is an elf image currently mapped,
- then make sure to unmap it. */
- invalidate_edi(&ui->edi);
-
+ /* We're about to map an elf image.
+ The call will unmap memory it doesn't own, so just null it out and avoid
+ that. */
+ ui->edi.ei.image = NULL;
+ invalidate_edi (&ui->edi);
/* Used to be tdep_get_elf_image() in ptrace unwinding code */
- coredump_phdr_t *cphdr = _UCD_get_elf_image(ui, ip);
+ coredump_phdr_t *cphdr = _UCD_get_elf_image (ui, ip);
+
if (!cphdr)
{
- Debug(1, "returns error: _UCD_get_elf_image failed\n");
+ Debug (1, "returns error: _UCD_get_elf_image failed\n");
return -UNW_ENOINFO;
}
- /* segbase: where it is mapped in virtual memory */
- /* mapoff: offset in the file */
- segbase = cphdr->p_vaddr;
- /*mapoff = phdr->p_offset; WRONG! phdr->p_offset is the offset in COREDUMP file */
- mapoff = 0;
+ segbase = 0; /* everything is relative to the beginning of the ELF file */
+ mapoff = 0;
+ /* Adjust IP to be relative to start of the .text section of the ELF file */
+ ip = ip - cphdr->p_vaddr + _get_text_offset (ui->edi.ei.image);
ret = elf_w (get_proc_name_in_image) (as, &ui->edi.ei, segbase, mapoff, ip, buf, buf_len, offp);
-
return ret;
}
@@ -63,7 +113,6 @@ _UCD_get_proc_name (unw_addr_space_t as, unw_word_t ip,
char *buf, size_t buf_len, unw_word_t *offp, void *arg)
{
struct UCD_info *ui = arg;
-
#if UNW_ELF_CLASS == UNW_ELFCLASS64
return _Uelf64_CD_get_proc_name (ui, as, ip, buf, buf_len, offp);
#elif UNW_ELF_CLASS == UNW_ELFCLASS32
diff --git a/src/coredump/_UCD_internal.h b/src/coredump/_UCD_internal.h
index d5a36f6b..3a9434ab 100644
--- a/src/coredump/_UCD_internal.h
+++ b/src/coredump/_UCD_internal.h
@@ -46,6 +46,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include <libunwind-coredump.h>
#include "libunwind_i.h"
+#include "ucd_file_table.h"
#if SIZEOF_OFF_T == 4
@@ -62,17 +63,14 @@ typedef uint64_t uoff_t;
*/
struct coredump_phdr
{
- uint32_t p_type;
- uint32_t p_flags;
- uoff_t p_offset;
- uoff_t p_vaddr;
- uoff_t p_filesz;
- uoff_t p_memsz;
- uoff_t p_align;
- /* Data for backing file. If backing_fd < 0, there is no file */
- uoff_t backing_filesize;
- char *backing_filename; /* for error meesages only */
- int backing_fd;
+ uint32_t p_type;
+ uint32_t p_flags;
+ uoff_t p_offset;
+ uoff_t p_vaddr;
+ uoff_t p_filesz;
+ uoff_t p_memsz;
+ uoff_t p_align;
+ ucd_file_index_t p_backing_file_index;
};
typedef struct coredump_phdr coredump_phdr_t;
@@ -102,6 +100,7 @@ struct UCD_info
char *coredump_filename; /* for error meesages only */
coredump_phdr_t *phdrs; /* array, allocated */
unsigned phdrs_count;
+ ucd_file_table_t ucd_file_table;
void *note_phdr; /* allocated or NULL */
UCD_proc_status_t *prstatus; /* points inside note_phdr */
#ifdef HAVE_ELF_FPREGSET_T
diff --git a/src/coredump/ucd_file_table.c b/src/coredump/ucd_file_table.c
new file mode 100644
index 00000000..e6d966be
--- /dev/null
+++ b/src/coredump/ucd_file_table.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2022 Blackberry Limited.
+ * Contributed by Stephen M. Webb <stephen.webb@bregmasoft.ca>
+ *
+ * This file is part of libunwind, a platform-independent unwind library.
+ *
+ * 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_file_table.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/**
+ * Initialize a UCD file object.
+ * @param[in] ucd_file The `ucd_file_t` object to initialize.
+ * @param[in] filename Name of a file.
+ *
+ * Stores the filename in the object and sets the fd to an uninitialized state.
+ *
+ * @returns UNW_ESUCCESS on success, a negated `unw_erro_t` code otherwise.
+ */
+unw_error_t
+ucd_file_init (ucd_file_t *ucd_file, char const *filename)
+{
+ size_t name_size = strlen (filename) + 1;
+ ucd_file->filename = malloc (name_size);
+ if (ucd_file->filename == NULL)
+ {
+ Debug (0, "error %d from malloc(): %s\n", errno, strerror (errno));
+ return (unw_error_t) - UNW_ENOMEM;
+ }
+ memcpy ((char *)ucd_file->filename, filename, name_size);
+ ucd_file->fd = -1;
+ ucd_file->size = 0;
+ ucd_file->image = NULL;
+
+ return UNW_ESUCCESS;
+}
+
+
+/**
+ * Dispose of a UCD file object.
+ * @param[in] ucd_file The UCD file to dispose.
+ *
+ * Releases any resources sued and sets the object to an uninitialized state.
+ *
+ * @returns UNW_ESUCCESS always.
+ */
+unw_error_t
+ucd_file_dispose (ucd_file_t *ucd_file)
+{
+ ucd_file_unmap(ucd_file);
+ if (ucd_file->filename != NULL)
+ {
+ free ((char *)ucd_file->filename);
+ ucd_file->filename = NULL;
+ }
+
+ return UNW_ESUCCESS;
+}
+
+
+/**
+ * Opens a UCD file and gets its size
+ */
+static void
+_ucd_file_open (ucd_file_t *ucd_file)
+{
+ ucd_file->fd = open(ucd_file->filename, O_RDONLY);
+ if (ucd_file->fd == -1)
+ {
+ Debug(0, "error %d in open(%s): %s\n", errno, ucd_file->filename, strerror(errno));
+ }
+
+ struct stat sbuf;
+ int sstat = fstat(ucd_file->fd, &sbuf);
+ if (sstat != 0)
+ {
+ Debug(0, "error %d in fstat(%s): %s\n", errno, ucd_file->filename, strerror(errno));
+ close(ucd_file->fd);
+ ucd_file->fd = -1;
+ }
+ ucd_file->size = sbuf.st_size;
+}
+
+
+/**
+ * Memory-maps a UCD file
+ */
+uint8_t *
+ucd_file_map (ucd_file_t *ucd_file)
+{
+ if (ucd_file->image != NULL)
+ {
+ Debug(0, "warning, file \"%s\" already loaded\n", ucd_file->filename);
+ return ucd_file->image;
+ }
+
+ if (ucd_file->fd == -1)
+ {
+ _ucd_file_open (ucd_file);
+ }
+
+ ucd_file->image = mmap(NULL, ucd_file->size, PROT_READ, MAP_PRIVATE, ucd_file->fd, 0);
+ if (ucd_file->image == MAP_FAILED)
+ {
+ Debug(0, "error %d in mmap(%s): %s\n", errno, ucd_file->filename, strerror(errno));
+ ucd_file->image = NULL;
+ return NULL;
+ }
+ return ucd_file->image;
+}
+
+
+void
+ucd_file_unmap (ucd_file_t *ucd_file)
+{
+ if (ucd_file->image != NULL)
+ {
+ munmap(ucd_file->image, ucd_file->size);
+ ucd_file->image = NULL;
+ ucd_file->size = 0;
+ }
+ if (ucd_file->fd != -1)
+ {
+ close(ucd_file->fd);
+ ucd_file->fd = -1;
+ }
+}
+
+
+/**
+ * Initialize a UCD file table.
+ * @param[in] ucd_file_table The UCD file table to initialize.
+ *
+ * @returns UNW_ESUCCESS on success, a negated `unw_erro_t` code otherwise.
+ */
+unw_error_t
+ucd_file_table_init (ucd_file_table_t *ucd_file_table)
+{
+ ucd_file_table->uft_count = 0;
+ ucd_file_table->uft_size = 2;
+ ucd_file_table->uft_files = calloc (ucd_file_table->uft_size,
+ sizeof (ucd_file_t));
+
+ if (ucd_file_table->uft_files == NULL)
+ {
+ Debug (0, "error %d from malloc(): %s\n", errno, strerror (errno));
+ return (unw_error_t) - UNW_ENOMEM;
+ }
+
+ return UNW_ESUCCESS;
+}
+
+
+/**
+ * Dispose of a UCD file table object.
+ * @param[in] ucd_file_table The UCD file table to dispose.
+ *
+ * Releases any resources used and sets the object to an uninitialized state.
+ *
+ * @returns UNW_ESUCCESS always.
+ */
+unw_error_t
+ucd_file_table_dispose (ucd_file_table_t *ucd_file_table)
+{
+ if (ucd_file_table->uft_files != NULL)
+ {
+ for (int i = 0; i < ucd_file_table->uft_count; ++i)
+ {
+ ucd_file_dispose(&ucd_file_table->uft_files[i]);
+ }
+ free (ucd_file_table->uft_files);
+ ucd_file_table->uft_files = NULL;
+ }
+
+ ucd_file_table->uft_count = 0;
+ ucd_file_table->uft_size = 0;
+
+ return UNW_ESUCCESS;
+}
+
+
+/**
+ * Insert a new entry in a UCD file table.
+ * @param[in] ucd_file_table A UCD file table
+ * @param[in] filename The filename to add.
+ *
+ * This table doe not allow duplicates: if a filename is already in the table,
+ * the index of that entry is returned, otherwise a new entry is created and
+ * the index of the new entry is returned.
+ *
+ * @returns the index of the newly-added UCD file, a negative `unw_error_t`
+ * code indicating failure otherwise.
+ */
+ucd_file_index_t ucd_file_table_insert (ucd_file_table_t *ucd_file_table,
+ char const *filename)
+{
+ for (int i = 0; i < (int)ucd_file_table->uft_count; ++i)
+ {
+ if (strcmp (ucd_file_table->uft_files[i].filename, filename) == 0)
+ {
+ return i;
+ }
+ }
+
+ ucd_file_index_t index = ucd_file_table->uft_count;
+ ++ucd_file_table->uft_count;
+
+ if (ucd_file_table->uft_count >= ucd_file_table->uft_size)
+ {
+ size_t new_size = ucd_file_table->uft_size * 2;
+ ucd_file_table->uft_files = realloc (ucd_file_table->uft_files,
+ new_size * sizeof (ucd_file_t));
+ if (ucd_file_table->uft_files == NULL)
+ {
+ Debug (0, "error %d from malloc(): %s\n", errno, strerror (errno));
+ return (unw_error_t) - UNW_ENOMEM;
+ }
+
+ ucd_file_table->uft_size = new_size;
+ }
+
+ unw_error_t err = ucd_file_init (&ucd_file_table->uft_files[index], filename);
+ if (err != UNW_ESUCCESS)
+ {
+ return err;
+ }
+ return index;
+}
+
+
+/**
+ * Get an indicated entry from a UCD file table.
+ * @param[in] ucd_file_table A UCD file table
+ * @param[in] index Indicate which entry to retrieve.
+ *
+ * @returns a pointer to the indicated UCD file, NULL if the index is out of
+ * range.
+ */
+ucd_file_t *
+ucd_file_table_at (ucd_file_table_t *ucd_file_table,
+ ucd_file_index_t index)
+{
+ if (0 <= index && index < (int)ucd_file_table->uft_count)
+ {
+ return &ucd_file_table->uft_files[index];
+ }
+
+ return NULL;
+}
+
diff --git a/src/coredump/ucd_file_table.h b/src/coredump/ucd_file_table.h
new file mode 100644
index 00000000..a22b15e1
--- /dev/null
+++ b/src/coredump/ucd_file_table.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2022 Blackberry Limited.
+ * Contributed by Stephen M. Webb <stephen.webb@bregmasoft.ca>
+ *
+ * This file is part of libunwind, a platform-independent unwind library.
+ *
+ * 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.
+ */
+#ifndef include_ucd_file_table_h_
+#define include_ucd_file_table_h_
+
+#include "libunwind_i.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+/**
+ * Describes a backing file.
+ *
+ * A *backing file* is usually the ELF image file that was the source of a
+ * particular PT_LOAD segment in memory: it could be the program executable or
+ * it could be a shared library, either resolved through the dynamic loader at
+ * program start or later through dl_open().
+ *
+ * There may be one or more in-memory segments associated with the same backing
+ * file.
+ */
+struct ucd_file_s
+ {
+ char const *filename; /**< Name of the file */
+ int fd; /**< File descriptor of the file if open, -1 otherwise */
+ off_t size; /**< File size in bytyes */
+ uint8_t *image; /**< Memory-mapped file image */
+ };
+
+typedef struct ucd_file_s ucd_file_t;
+
+HIDDEN unw_error_t ucd_file_init(ucd_file_t *ucd_file, char const *filename);
+HIDDEN unw_error_t ucd_file_dispose(ucd_file_t *ucd_file);
+HIDDEN uint8_t *ucd_file_map(ucd_file_t *ucd_file);
+HIDDEN void ucd_file_unmap(ucd_file_t *ucd_file);
+
+
+/**
+ * A table of backing files.
+ *
+ * Each entry in this table should be unique.
+ *
+ * This table is dynamically sized and should grow as required.
+ */
+struct ucd_file_table_s
+ {
+ size_t uft_count; /**< number of valid entries in table */
+ size_t uft_size; /**< size (in entries) of the table storage */
+ ucd_file_t *uft_files; /**< the table data */
+ };
+
+typedef struct ucd_file_table_s ucd_file_table_t;
+typedef int ucd_file_index_t;
+static const ucd_file_index_t ucd_file_no_index = -1;
+
+HIDDEN unw_error_t ucd_file_table_init(ucd_file_table_t *ucd_file_table);
+HIDDEN unw_error_t ucd_file_table_dispose(ucd_file_table_t *ucd_file_table);
+HIDDEN ucd_file_index_t ucd_file_table_insert(ucd_file_table_t *ucd_file_table, char const *filename);
+HIDDEN ucd_file_t *ucd_file_table_at(ucd_file_table_t *ucd_file_table, ucd_file_index_t index);
+
+#endif /* include_ucd_file_table_h_ */
diff --git a/tests/test-coredump-unwind.c b/tests/test-coredump-unwind.c
index 93d9a0ee..7db9cb86 100644
--- a/tests/test-coredump-unwind.c
+++ b/tests/test-coredump-unwind.c
@@ -315,17 +315,6 @@ main(int argc UNUSED, char **argv)
argv++;
}
- while (*argv)
- {
- char *colon;
- unsigned long vaddr = strtoul(*argv, &colon, 16);
- if (*colon != ':')
- error_msg_and_die("Bad format: '%s'", *argv);
- if (_UCD_add_backing_file_at_vaddr(ui, vaddr, colon + 1) < 0)
- error_msg("Can't add backing file '%s'", colon + 1);
- argv++;
- }
-
for (;;)
{
unw_word_t ip;