diff options
author | Stephen Webb <swebb@blackberry.com> | 2020-06-26 11:02:20 -0400 |
---|---|---|
committer | Dave Watson <dade.watson@gmail.com> | 2020-07-02 16:14:18 -0700 |
commit | 2719aa05d3e9faeff6f7090fef05712c303a8ba1 (patch) | |
tree | 4894b6550a37b38e9b7aed191ee68912a3e9f461 | |
parent | d9b061db93d777650d7d16b5ce687639a84a22c0 (diff) | |
download | libunwind-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.
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/coredump/_UCD_corefile_elf.c | 126 | ||||
-rw-r--r-- | src/coredump/_UCD_create.c | 108 | ||||
-rw-r--r-- | src/coredump/_UCD_get_threadinfo_linux.c | 121 | ||||
-rw-r--r-- | src/coredump/_UCD_internal.h | 14 |
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 |