summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStephen Webb <swebb@blackberry.com>2020-06-26 11:02:20 -0400
committerDave Watson <dade.watson@gmail.com>2020-07-02 16:14:18 -0700
commit2719aa05d3e9faeff6f7090fef05712c303a8ba1 (patch)
tree4894b6550a37b38e9b7aed191ee68912a3e9f461 /src
parentd9b061db93d777650d7d16b5ce687639a84a22c0 (diff)
downloadlibunwind-2719aa05d3e9faeff6f7090fef05712c303a8ba1.tar.gz
Refactor Linux-specific codedump code
Moved Linux-specific core file code into its own separate source file. Moved some generic ELF-note handling code into its own separate source file, too, to replace some macros in anticipation of re-using them for non-Linux core files.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/coredump/_UCD_corefile_elf.c126
-rw-r--r--src/coredump/_UCD_create.c108
-rw-r--r--src/coredump/_UCD_get_threadinfo_linux.c121
-rw-r--r--src/coredump/_UCD_internal.h14
5 files changed, 293 insertions, 78 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index ff977446..4d47203e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -60,6 +60,7 @@ libunwind_coredump_la_SOURCES = \
coredump/_UCD_elf_map_image.c \
coredump/_UCD_find_proc_info.c \
coredump/_UCD_get_proc_name.c \
+ coredump/_UCD_corefile_elf.c \
\
coredump/_UPT_elf.c \
coredump/_UPT_access_fpreg.c \
@@ -520,6 +521,7 @@ 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
endif
if OS_HPUX
diff --git a/src/coredump/_UCD_corefile_elf.c b/src/coredump/_UCD_corefile_elf.c
new file mode 100644
index 00000000..045e0437
--- /dev/null
+++ b/src/coredump/_UCD_corefile_elf.c
@@ -0,0 +1,126 @@
+/**
+ * Support functions for ELF corefiles
+ */
+/*
+ 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"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+/**
+ * Read an ELF segment into an allocated memory buffer.
+ * @param[in] ui the unwind-coredump context
+ * @param[in] phdr pointer to the PHDR of the segment to load
+ * @param[out] segment pointer to the segment loaded
+ * @param[out] segment_size size of the @segment in bytes
+ *
+ * Allocates an appropriately-sized buffer to contain the segment of the
+ * coredump described by @phdr and reads it in from the core file.
+ *
+ * The caller is responsible for freeing the allocated segment memory.
+ *
+ * @returns UNW_SUCCESS on success, something else otherwise.
+ */
+HIDDEN int
+_UCD_elf_read_segment(struct UCD_info *ui, coredump_phdr_t *phdr, uint8_t **segment, size_t *segment_size)
+{
+ int ret = -UNW_EUNSPEC;
+ if (lseek(ui->coredump_fd, phdr->p_offset, SEEK_SET) != (off_t)phdr->p_offset)
+ {
+ Debug(0, "errno %d setting offset to %lu in '%s': %s\n",
+ errno, phdr->p_offset, ui->coredump_filename, strerror(errno));
+ return ret;
+ }
+
+ *segment_size = phdr->p_filesz;
+ *segment = malloc(*segment_size);
+ if (*segment == NULL)
+ {
+ Debug(0, "error %zu bytes of memory for segment\n", *segment_size);
+ return ret;
+ }
+
+ if (read(ui->coredump_fd, *segment, *segment_size) != (ssize_t)*segment_size)
+ {
+ Debug(0, "errno %d reading %zu bytes from '%s': %s\n",
+ errno, *segment_size, ui->coredump_filename, strerror(errno));
+ return ret;
+ }
+
+ ret = UNW_ESUCCESS;
+ return ret;
+}
+
+
+/**
+ * Parse a PT_NOTE segment into zero or more notes and visit each one
+ * @param[in] segment pointer to the PT_NOTE segment
+ * @param[in] segment_size size of @p segment in bytes
+ * @param[in] visit callback to process to the notes
+ * @param[in] arg context to forward to the callback
+ *
+ * One PT_NOTE segment might contain many variable-length notes. Parsing them
+ * out is just a matter of calculating the size of each note from the size
+ * fields contained in the (fixed-size) note header and adjusting for 4-byte
+ * alignment.
+ *
+ * For each note found the @p visit callback will be invoked. If the callback
+ * returns anything but UNW_ESUCCESS, traversal of the notes will be terminated
+ * and processing will return immediately, passing the return code through.
+ *
+ * @returns UNW_SUCCESS on success or the return value from @p visit otherwise.
+ */
+HIDDEN int
+_UCD_elf_visit_notes(uint8_t *segment, size_t segment_size, note_visitor_t visit, void *arg)
+{
+ int ret = UNW_ESUCCESS;
+ size_t parsed_size = 0;
+ while (parsed_size < segment_size)
+ {
+ /*
+ * Note that Elf32_Nhdr and Elf64_Nhdr are identical, so it doesn't matter which
+ * structure is chosen here. I chose the one with the larger number because
+ * bigger is better.
+ */
+ Elf64_Nhdr *note = (Elf64_Nhdr *)(segment + parsed_size);
+ unsigned header_size = sizeof(Elf64_Nhdr);
+ unsigned name_size = UNW_ALIGN(note->n_namesz, 4);
+ unsigned desc_size = UNW_ALIGN(note->n_descsz, 4);
+ unsigned note_size = header_size + name_size + desc_size;
+ char *name = (char *)(note) + header_size;
+ uint8_t *desc = (uint8_t *)(note) + header_size + name_size;
+
+ ret = visit(note->n_namesz, note->n_descsz, note->n_type, name, desc, arg);
+ if (ret != UNW_ESUCCESS)
+ {
+ break;
+ }
+
+ parsed_size += note_size;
+ }
+ return ret;
+}
+
diff --git a/src/coredump/_UCD_create.c b/src/coredump/_UCD_create.c
index 52ac781d..3c0345e2 100644
--- a/src/coredump/_UCD_create.c
+++ b/src/coredump/_UCD_create.c
@@ -41,11 +41,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#include "_UCD_lib.h"
#include "_UCD_internal.h"
-#define NOTE_DATA(_hdr) STRUCT_MEMBER_P((_hdr), sizeof (Elf32_Nhdr) + UNW_ALIGN((_hdr)->n_namesz, 4))
-#define NOTE_SIZE(_hdr) (sizeof (Elf32_Nhdr) + UNW_ALIGN((_hdr)->n_namesz, 4) + UNW_ALIGN((_hdr)->n_descsz, 4))
-#define NOTE_NEXT(_hdr) STRUCT_MEMBER_P((_hdr), NOTE_SIZE(_hdr))
-#define NOTE_FITS_IN(_hdr, _size) ((_size) >= sizeof (Elf32_Nhdr) && (_size) >= NOTE_SIZE (_hdr))
-#define NOTE_FITS(_hdr, _end) NOTE_FITS_IN((_hdr), (unsigned long)((char *)(_end) - (char *)(_hdr)))
struct UCD_info *
_UCD_create(const char *filename)
@@ -180,72 +175,39 @@ _UCD_create(const char *filename)
}
}
- unsigned i = 0;
- coredump_phdr_t *cur = phdrs;
- while (i < size)
- {
- Debug(2, "phdr[%03d]: type:%d", i, cur->p_type);
- if (cur->p_type == PT_NOTE)
- {
- Elf32_Nhdr *note_hdr, *note_end;
- unsigned n_threads;
-
- ui->note_phdr = malloc(cur->p_filesz);
- if (lseek(fd, cur->p_offset, SEEK_SET) != (off_t)cur->p_offset
- || (uoff_t)read(fd, ui->note_phdr, cur->p_filesz) != cur->p_filesz)
- {
- Debug(0, "Can't read PT_NOTE from '%s'\n", filename);
- goto err;
- }
-
- note_end = STRUCT_MEMBER_P (ui->note_phdr, cur->p_filesz);
-
- /* Count number of threads */
- n_threads = 0;
- note_hdr = (Elf32_Nhdr *)ui->note_phdr;
- while (NOTE_FITS (note_hdr, note_end))
- {
- if (note_hdr->n_type == NT_PRSTATUS)
- n_threads++;
-
- note_hdr = NOTE_NEXT (note_hdr);
- }
-
- ui->n_threads = n_threads;
- ui->threads = malloc(sizeof (void *) * n_threads);
-
- n_threads = 0;
- note_hdr = (Elf32_Nhdr *)ui->note_phdr;
- while (NOTE_FITS (note_hdr, note_end))
- {
- if (note_hdr->n_type == NT_PRSTATUS)
- ui->threads[n_threads++] = NOTE_DATA (note_hdr);
-
- note_hdr = NOTE_NEXT (note_hdr);
- }
- }
- if (cur->p_type == PT_LOAD)
- {
- Debug(2, " ofs:%08llx va:%08llx filesize:%08llx memsize:%08llx flg:%x",
- (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");
- }
- }
- Debug(2, "\n");
- i++;
- cur++;
- }
+ int ret = _UCD_get_threadinfo(ui, phdrs, size);
+ if (ret != UNW_ESUCCESS) {
+ Debug(0, "failure retrieving thread info from core file\n");
+ goto err;
+ }
+
+ if (unwi_debug_level > 1)
+ {
+ coredump_phdr_t *cur = phdrs;
+ for (unsigned i = 0; i < size; ++i)
+ {
+ if (cur->p_type == PT_LOAD)
+ {
+ Debug(2, " ofs:%08llx va:%08llx filesize:%08llx memsize:%08llx flg:%x",
+ (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");
+ }
+ }
+ Debug(2, "\n");
+ cur++;
+ }
+ }
if (ui->n_threads == 0)
{
@@ -253,7 +215,7 @@ _UCD_create(const char *filename)
goto err;
}
- ui->prstatus = ui->threads[0];
+ ui->prstatus = &ui->threads[0];
return ui;
@@ -270,7 +232,7 @@ int _UCD_get_num_threads(struct UCD_info *ui)
void _UCD_select_thread(struct UCD_info *ui, int n)
{
if (n >= 0 && n < ui->n_threads)
- ui->prstatus = ui->threads[n];
+ ui->prstatus = &ui->threads[n];
}
pid_t _UCD_get_pid(struct UCD_info *ui)
diff --git a/src/coredump/_UCD_get_threadinfo_linux.c b/src/coredump/_UCD_get_threadinfo_linux.c
new file mode 100644
index 00000000..d218a079
--- /dev/null
+++ b/src/coredump/_UCD_get_threadinfo_linux.c
@@ -0,0 +1,121 @@
+/**
+ * Extract threadinfo 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"
+
+#if defined(HAVE_ELF_H)
+# include <elf.h>
+#elif defined(HAVE_SYS_ELF_H)
+# include <sys/elf.h>
+#endif
+
+
+/**
+ * Accumulate a count of the number of thread notes
+ *
+ * This _UCD_elf_visit_notes() callback just increments a count for each
+ * NT_PRSTATUS note seen.
+ */
+static int
+_count_thread_notes(uint32_t n_namesz, uint32_t n_descsz, uint32_t n_type, char *name, uint8_t *desc, void *arg)
+{
+ size_t *thread_count = (size_t *)arg;
+ if (n_type == NT_PRSTATUS)
+ {
+ ++*thread_count;
+ }
+ return UNW_ESUCCESS;
+}
+
+
+/**
+ * Save a thread note to the unwind-coredump context
+ *
+ * This _UCD_elf_visit_notes() callback just copies the actual data structure
+ * from any NT_PRSTATUS note seen into an array of such structures and
+ * increments the count.
+ */
+static int
+_save_thread_notes(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_PRSTATUS)
+ {
+ memcpy(&ui->threads[ui->n_threads], desc, sizeof(struct PRSTATUS_STRUCT));
+ ++ui->n_threads;
+ }
+ return UNW_ESUCCESS;
+}
+
+
+/**
+ * Get thread info from core file (Linux and similar)
+ *
+ * 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
+ * NT_PRSTATUS. In fact, on Linux, the state of a thread is described by a
+ * CPU-dependent group of notes but right now we're only going to care about the
+ * one process-status note. This statement is also true for FreeBSD.
+ *
+ * Depending on how the core file is created, there may be one PT_NOTE segment
+ * with multiple NT_PRSTATUS notes in it, or multiple PT_NOTE segments. Just to
+ * be safe, it's better to assume there are multiple PT_NOTE segments each with
+ * multiple NT_PRSTATUS notes, as that covers all the cases.
+ */
+int
+_UCD_get_threadinfo(struct UCD_info *ui, coredump_phdr_t *phdrs, unsigned phdr_size)
+{
+ int ret = -UNW_ENOINFO;
+
+ for (unsigned i = 0; i < phdr_size; ++i)
+ {
+ Debug(8, "phdr[%03d]: type:%d", i, phdrs[i].p_type);
+ if (phdrs[i].p_type == PT_NOTE)
+ {
+ size_t thread_count = 0;
+ 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, _count_thread_notes, &thread_count);
+ Debug(2, "found %zu threads\n", thread_count);
+
+ size_t new_size = sizeof(struct PRSTATUS_STRUCT) * (ui->n_threads + thread_count);
+ ui->threads = realloc(ui->threads, new_size);
+ if (ui->threads == NULL)
+ {
+ Debug(0, "error allocating %zu bytes of memory \n", new_size);
+ free(segment);
+ return -UNW_EUNSPEC;
+ }
+ _UCD_elf_visit_notes(segment, segment_size, _save_thread_notes, ui);
+
+ free(segment);
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/src/coredump/_UCD_internal.h b/src/coredump/_UCD_internal.h
index 3c95a2a0..354dbc1d 100644
--- a/src/coredump/_UCD_internal.h
+++ b/src/coredump/_UCD_internal.h
@@ -92,14 +92,18 @@ struct UCD_info
void *note_phdr; /* allocated or NULL */
struct PRSTATUS_STRUCT *prstatus; /* points inside note_phdr */
int n_threads;
- struct PRSTATUS_STRUCT **threads;
-
+ struct PRSTATUS_STRUCT *threads;
struct elf_dyn_info edi;
};
-extern coredump_phdr_t * _UCD_get_elf_image(struct UCD_info *ui, unw_word_t ip);
-#define STRUCT_MEMBER_P(struct_p, struct_offset) ((void *) ((char*) (struct_p) + (long) (struct_offset)))
-#define STRUCT_MEMBER(member_type, struct_p, struct_offset) (*(member_type*) STRUCT_MEMBER_P ((struct_p), (struct_offset)))
+typedef int (*note_visitor_t)(uint32_t, uint32_t, uint32_t, char *, uint8_t *, void *);
+
+
+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);
+
#endif