├── .gitignore ├── AUTHORS ├── LICENSE ├── Makefile.am ├── README ├── autogen.sh ├── configure.ac └── src ├── backtrace.c ├── backtrace.h ├── mem_map.c ├── mem_map.h ├── proc.c ├── proc.h ├── snapshot.c ├── snapshot.h ├── tbstack.c └── unwind.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | 3 | /Makefile.in 4 | /autom4te.cache 5 | /aclocal.m4 6 | /compile 7 | /config.log 8 | /config.status 9 | /configure 10 | /depcomp 11 | /install-sh 12 | /missing 13 | /stamp-h1 14 | .deps 15 | .dirstamp 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Vladimir Nikulichev, 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Tbricks AB 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL TBRICKS AB BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CPPFLAGS = -Wall -Wextra 2 | 3 | bin_PROGRAMS = tbstack 4 | tbstack_SOURCES = \ 5 | src/tbstack.c \ 6 | src/backtrace.h \ 7 | src/backtrace.c \ 8 | src/mem_map.h \ 9 | src/mem_map.c \ 10 | src/proc.h \ 11 | src/proc.c \ 12 | src/snapshot.h \ 13 | src/snapshot.c \ 14 | src/unwind.c 15 | 16 | tbstack_CFLAGS = $(AM_CFLAGS) $(LIBUNWIND_CFLAGS) 17 | tbstack_LDADD = $(LIBUNWIND_LIBS) 18 | 19 | EXTRA_DIST = LICENSE 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | DESCRIPTION 3 | 4 | tbstack -- fast stack trace utility. 5 | 6 | A primary design goal is to minimize performance impact on a process to 7 | make it possible to run it in low-latency systems. It is achieved by 8 | copying contents of stack regions when the process is frozen and unwinding 9 | stack when the process continues working. The idea was inspired by perf 10 | tool. 11 | 12 | The program uses POSIX, Linux API, and libunwind. 13 | 14 | -------------------------------------------------------------------------------- 15 | COMPATIBILITY 16 | 17 | This utility is intended for use with Linux on ARM, x86, and x86_64. 18 | 19 | Libraries: libelf, libunwind. It is recommended to get the latest version of 20 | libunwind. 21 | 22 | -------------------------------------------------------------------------------- 23 | INSTALLATION 24 | 25 | The build system is autotools: 26 | 27 | $ ./autogen.sh # Only required if building from git 28 | $ ./configure 29 | $ make 30 | # make install 31 | 32 | Configure options are available: 33 | 34 | $ ./configure --disable-ptrace 35 | $ make DESTDIR=/opt/tbstack 36 | 37 | DESTDIR tbstack binary will be installed there 38 | --disable-ptrace disable support for libunwind-ptrace 39 | 40 | -------------------------------------------------------------------------------- 41 | USAGE 42 | 43 | usage: tbstack 44 | tbstack /,..., 45 | tbstack /RS 46 | 47 | options: --help show this 48 | --ignore-deleted try to open shared objects marked as deleted 49 | --use-waitpid-timeout set alarm to interrupt waitpid 50 | --proc-mem prefer reading /proc/pid/mem. default flavor 51 | is process_vm_readv 52 | --ptrace use libunwind-ptrace interface (slower) 53 | --show-rsp show %rsp in second column 54 | --show-state show thread states 55 | --stack-size maximum stack size to copy (default is current 56 | RLIMIT_STACK) 57 | --stop-timeout timeout for waiting the process to freeze, in 58 | milliseconds. default value is 1000 59 | --verbose verbose error messages 60 | --version output version information and exit 61 | 62 | -------------------------------------------------------------------------------- 63 | HOW IT WORKS 64 | 65 | Binaries in mainstream Linux distributions are built with frame pointers 66 | omitted, so stack unwinding becomes more complex than just traversing a linked 67 | list. In order to move to the next frame we have to find corresponding 68 | frame description entry (FDE) in .eh_frame ELF section. It imples at least two 69 | binary searches: for a binary or shared object containing the code, and the 70 | FDE. 71 | 72 | One of solutions is examining process memory by ptrace and resolving next 73 | frame addresses while the process is stopped. Libunwind-ptrace implements such 74 | approach. Tbstack supports this interface as a flavor which can be enabled by 75 | --ptrace command line argument. 76 | 77 | In order to minimize performance impact tbstack uses another approach. At first 78 | it examines process' memory layout reading /proc/pid/maps. When the process is 79 | frozen (PTRACE_ATTACH to the main thread + SIGSTOP to start freezing other 80 | threads) it copies all threads' general-purpose registers and contents of stack 81 | from %rsp to end of memory region or up to maximum stack size if specified 82 | with --stack-size argument. System call proc_vm_readv is used. If it is not 83 | supported, the program falls back to reading /proc/pid/mem. The process 84 | continues execution. The collected data is enough to trace stacks. It is 85 | arranged in structures snapshot and mem_map and can be accessed through callback 86 | routines passed to libunwind by unw_create_addr_space (see 87 | http://www.nongnu.org/libunwind/man/unw_create_addr_space(3).html). 88 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | autoreconf -i 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.60) 2 | AC_INIT([tbstack], [1.0]) 3 | AM_INIT_AUTOMAKE([ 4 | foreign 5 | 1.11 6 | subdir-objects 7 | -Wall 8 | -Wno-portability 9 | tar-pax 10 | no-dist-gzip 11 | dist-xz 12 | ]) 13 | AM_SILENT_RULES([yes]) 14 | 15 | AC_GNU_SOURCE 16 | AC_PROG_CC 17 | 18 | AC_SYS_LARGEFILE 19 | 20 | AC_SEARCH_LIBS([elf_begin], [elf], [], [ 21 | AC_MSG_ERROR([unable to find the elf_begin() in libelf]) 22 | ]) 23 | AC_ARG_ENABLE([ptrace], 24 | AS_HELP_STRING([--disable-ptrace], [Disable libunwind ptrace (default enabled)]), 25 | [use_ptrace=$enableval], [use_ptrace=yes]) 26 | 27 | AS_IF(test "$use_ptrace" == yes, 28 | [PKG_CHECK_MODULES([LIBUNWIND], [libunwind-ptrace])], 29 | [PKG_CHECK_MODULES([LIBUNWIND], [libunwind-generic]) 30 | AC_DEFINE([NO_LIBUNWIND_PTRACE], [1], [Don't use libunwind's ptrace support])] 31 | ) 32 | 33 | AC_MSG_CHECKING(for dwarf unwind support) 34 | SAVED_LIBS="$LIBS" 35 | LIBS="$LIBUNWIND_LIBS" 36 | SAVED_CFLAGS="$CFLAGS" 37 | CFLAGS="$LIBUNWIND_CFLAGS" 38 | AC_LINK_IFELSE([ 39 | AC_LANG_SOURCE([[ 40 | #include 41 | #include 42 | 43 | #define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame) 44 | 45 | extern int 46 | UNW_OBJ(dwarf_find_debug_frame)(int found, unw_dyn_info_t *di_debug, 47 | unw_word_t ip, 48 | unw_word_t segbase, 49 | const char *obj_name, unw_word_t start, 50 | unw_word_t end); 51 | int main(void) 52 | { 53 | dwarf_find_debug_frame(0, 0, 0, 0, 0, 0, 0); 54 | return 0; 55 | } 56 | ]])], [dwarf_debug_frame=yes]) 57 | LIBS="$SAVED_LIBS" 58 | CFLAGS="$SAVED_CFLAGS" 59 | 60 | if test "$dwarf_debug_frame" != yes; then 61 | AC_MSG_RESULT(no) 62 | else 63 | AC_MSG_RESULT(yes) 64 | AC_DEFINE([HAVE_DWARF],[1],[DWARF debug_frame support in libunwind]) 65 | fi 66 | 67 | AC_CHECK_DECLS([ELF_C_READ_MMAP], [], [], [#include ]) 68 | 69 | AC_CONFIG_FILES([ 70 | Makefile 71 | ]) 72 | AC_OUTPUT 73 | -------------------------------------------------------------------------------- /src/backtrace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #if !defined (NO_LIBUNWIND_PTRACE) 38 | #include 39 | #endif 40 | 41 | #include "backtrace.h" 42 | #include "proc.h" 43 | #include "snapshot.h" 44 | 45 | extern unw_accessors_t snapshot_addr_space_accessors; 46 | 47 | extern int opt_show_rsp; 48 | extern int opt_show_state; 49 | extern int opt_verbose; 50 | extern char *opt_thread_states; 51 | 52 | static int backtrace_thread(unw_accessors_t *accessors, void *arg) 53 | { 54 | unw_addr_space_t addr_space; 55 | unw_cursor_t cursor; 56 | int rc = 0, n = 0; 57 | 58 | if ((addr_space = unw_create_addr_space(accessors, 0)) == NULL) { 59 | fprintf(stderr, "failed to create address space for unwinding\n"); 60 | return -1; 61 | } 62 | 63 | if ((rc = unw_init_remote(&cursor, addr_space, arg)) < 0) { 64 | fprintf(stderr, "failed to init cursor for unwinding: rc=%d\n", rc); 65 | return -1; 66 | } 67 | 68 | do { 69 | unw_word_t ip, sp = -1, off; 70 | static char buf[512]; 71 | size_t len; 72 | int is_sig; 73 | 74 | if ((rc = unw_get_reg(&cursor, UNW_REG_IP, &ip)) < 0) { 75 | fprintf(stderr, "failed to get IP: rc=%d\n", rc); 76 | break; 77 | } 78 | 79 | buf[0] = '\0'; 80 | unw_get_proc_name(&cursor, buf, sizeof(buf), &off); 81 | 82 | if (buf[0] == '\0') { 83 | buf[0] = '?'; 84 | buf[1] = '\0'; 85 | len = 1; 86 | } else { 87 | len = strlen(buf); 88 | } 89 | 90 | if (len >= sizeof(buf) - 32) 91 | len = sizeof(buf) - 32; 92 | 93 | if (!ip) 94 | break; 95 | 96 | is_sig = unw_is_signal_frame(&cursor); 97 | if (is_sig > 0) { 98 | printf(" \n"); 99 | } 100 | 101 | if (off) { 102 | sprintf(buf + len, " + 0x%lx", (unsigned long)off); 103 | } 104 | if (!opt_show_rsp) { 105 | printf(" %016lx %s\n", (long)ip, buf); 106 | } else { 107 | unw_get_reg(&cursor, UNW_REG_SP, &sp); 108 | printf(" %016lx %016lx %s\n", (long)ip, (long)sp, buf); 109 | } 110 | 111 | if ((rc = unw_step(&cursor)) < 0) { 112 | if (!opt_show_rsp) 113 | printf(" ???????????????? \n"); 114 | else 115 | printf(" ???????????????? ???????????????? \n"); 116 | 117 | if (opt_verbose) { 118 | fprintf(stderr, "unwind step failed: n=%d rc=%d\n", n, rc); 119 | } 120 | break; 121 | } 122 | 123 | if (++n == 64 && rc) { 124 | puts(" ???????????????? \n"); 125 | break; 126 | } 127 | } while (rc > 0); 128 | 129 | unw_destroy_addr_space(addr_space); 130 | 131 | return rc; 132 | } 133 | 134 | void print_thread_heading(const int *index, const int *tids, 135 | const char states[], int i) 136 | { 137 | int ind = (index != NULL ? index[i] : i+1); 138 | if (opt_show_state) { 139 | assert(states != NULL); 140 | printf("-------------------- thread %d (%d) (%c) " 141 | "--------------------\n", 142 | ind, tids[i], states[i]); 143 | } else { 144 | printf("-------------------- thread %d (%d) --------------------\n", 145 | ind, tids[i]); 146 | } 147 | } 148 | 149 | int backtrace_snapshot(int pid, int *tids, int *index, int nr_tids) 150 | { 151 | int i, rc = 0; 152 | struct snapshot *snap; 153 | 154 | if ((snap = get_snapshot(pid, tids, index, nr_tids)) == NULL) 155 | return -1; 156 | 157 | for (i = 0; i < snap->num_threads; ++i) { 158 | print_thread_heading(index, snap->tids, snap->states, i); 159 | 160 | snap->cur_thr = i; 161 | if (backtrace_thread(&snapshot_addr_space_accessors, snap) < 0) 162 | rc = -1; 163 | } 164 | 165 | snapshot_destroy(snap); 166 | return rc; 167 | } 168 | 169 | int backtrace_ptrace(int pid, int *tids, int *index, int nr_tids) 170 | { 171 | #if !defined (NO_LIBUNWIND_PTRACE) 172 | int i, count, rc = 0; 173 | int *threads = NULL; 174 | char *states = NULL; 175 | 176 | count = get_threads(pid, &threads); 177 | if (!count || threads == NULL) 178 | return -1; 179 | 180 | if (tids != NULL) { 181 | if (adjust_threads(threads, count, tids, index, nr_tids) < 0) 182 | return -1; 183 | 184 | free(threads); 185 | count = nr_tids; 186 | threads = tids; 187 | } 188 | 189 | if (opt_show_state || opt_thread_states) 190 | states = get_thread_states(threads, count); 191 | 192 | if (opt_thread_states) 193 | count = filter_threads(threads, index, states, count, opt_thread_states); 194 | 195 | if (attach_process(pid) < 0) 196 | return -1; 197 | 198 | for (i = 0; i < count; ++i) { 199 | void *upt_info; 200 | 201 | print_thread_heading(index, threads, states, i); 202 | 203 | if (threads[i] != pid && attach_thread(threads[i]) < 0) { 204 | rc = -1; 205 | break; 206 | } 207 | 208 | upt_info = _UPT_create(threads[i]); 209 | 210 | if (backtrace_thread(&_UPT_accessors, upt_info) < 0) 211 | rc = -1; 212 | 213 | _UPT_destroy(upt_info); 214 | 215 | if (threads[i] != pid && detach_thread(threads[i])) 216 | rc = -1; 217 | if (rc < 0) 218 | break; 219 | } 220 | 221 | free(threads); 222 | 223 | if (detach_process(pid) < 0) 224 | return -1; 225 | 226 | free(states); 227 | 228 | return rc; 229 | 230 | #else 231 | return -1; 232 | #endif /* NO_LIBUNWIND_PTRACE */ 233 | } 234 | -------------------------------------------------------------------------------- /src/backtrace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #ifndef __4ed4cde8_b414_11e3_a420_007bd8de5bcc 32 | #define __4ed4cde8_b414_11e3_a420_007bd8de5bcc 33 | 34 | /* 35 | * get process' stack trace 36 | */ 37 | int backtrace_snapshot(int pid, int *tids, int *index, int nr_tids); 38 | 39 | /* 40 | * get process' stack trace using libunwind-ptrace 41 | */ 42 | int backtrace_ptrace(int pid, int *tids, int *index, int nr_tids); 43 | 44 | #endif 45 | 46 | -------------------------------------------------------------------------------- /src/mem_map.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #include "mem_map.h" 32 | #include "proc.h" 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #ifndef VSYSCALL_START 45 | #define VSYSCALL_START (-10UL << 20) 46 | #endif 47 | 48 | extern int opt_verbose; 49 | extern int opt_ignore_deleted; 50 | 51 | static int in(const void *point, const void *start, size_t size) 52 | { 53 | return ((point >= start) && 54 | ((const char *)point < ((const char *)start + size))); 55 | } 56 | 57 | static void mem_data_chunk_init(struct mem_data_chunk *chunk) 58 | { 59 | chunk->start = NULL; 60 | chunk->data = NULL; 61 | chunk->length = 0; 62 | chunk->next = NULL; 63 | } 64 | 65 | static int mem_data_chunk_read_word(struct mem_data_chunk *chunk, 66 | void *addr, uintptr_t *value) 67 | { 68 | size_t offset = (size_t)addr - (size_t)chunk->start; 69 | assert(offset < chunk->length); 70 | *value = *(uintptr_t *)(chunk->data + offset); 71 | return 0; 72 | } 73 | 74 | static void mem_data_chunk_destroy(struct mem_data_chunk *chunk, int type) 75 | { 76 | switch (type) 77 | { 78 | case MEM_REGION_TYPE_MALLOC: 79 | free(chunk->data); 80 | break; 81 | 82 | case MEM_REGION_TYPE_MMAP: 83 | munmap(chunk->data, chunk->length); 84 | break; 85 | 86 | default: 87 | break; 88 | } 89 | free(chunk); 90 | } 91 | 92 | static void mem_data_chunk_list_destroy(struct mem_data_chunk *chunk, int type) 93 | { 94 | struct mem_data_chunk *next; 95 | while (chunk != NULL) { 96 | next = chunk->next; 97 | mem_data_chunk_destroy(chunk, type); 98 | chunk = next; 99 | } 100 | } 101 | 102 | void mem_region_init(struct mem_region *region) 103 | { 104 | region->start = NULL; 105 | region->length = 0; 106 | region->offset = 0; 107 | 108 | region->data_head = NULL; 109 | region->data_index = NULL; 110 | region->num_data_chunks = 0; 111 | region->prev_accessed_chunk = NULL; 112 | 113 | region->labels = NULL; 114 | region->num_labels = 0; 115 | 116 | region->path = NULL; 117 | region->fd = -1; 118 | region->type = MEM_REGION_TYPE_EMPTY; 119 | 120 | region->next = NULL; 121 | } 122 | 123 | static void mem_region_add_label(struct mem_region *region, 124 | void *label, size_t reserve) 125 | { 126 | size_t i; 127 | 128 | if (region->labels == NULL) 129 | region->labels = malloc(sizeof(void *)*reserve); 130 | 131 | for (i = 0; i < region->num_labels; ++i) { 132 | if (region->labels[i] > label) { 133 | memmove(®ion->labels[i+1], ®ion->labels[i], 134 | sizeof(void*) * (region->num_labels - i)); 135 | break; 136 | } 137 | } 138 | 139 | region->labels[i] = label; 140 | ++region->num_labels; 141 | } 142 | 143 | static int mem_region_add_data_chunk(struct mem_region *region, 144 | struct mem_data_chunk *chunk) 145 | { 146 | size_t i; 147 | struct mem_data_chunk **cur = ®ion->data_head; 148 | void *chunk_ceil; 149 | 150 | chunk_ceil = (char *)chunk->start + chunk->length; 151 | region->type = MEM_REGION_TYPE_MALLOC; 152 | 153 | for (i = 0; i < region->num_data_chunks; ++i) { 154 | if (in(chunk->start, (*cur)->start, (*cur)->length) || 155 | in(chunk_ceil, (*cur)->start, (*cur)->length)) 156 | { 157 | fprintf(stderr, "error: overlapping chunks: existing: %p-%p " 158 | "new: %p-%p\n", 159 | (*cur)->start, 160 | (*cur)->start + (*cur)->length, 161 | chunk->start, 162 | chunk_ceil); 163 | return -1; 164 | } 165 | if ((*cur)->start > chunk->start) 166 | break; 167 | cur = &(*cur)->next; 168 | } 169 | 170 | chunk->next = *cur; 171 | *cur = chunk; 172 | ++region->num_data_chunks; 173 | return 0; 174 | } 175 | 176 | static struct mem_data_chunk *mem_region_alloc_chunk(struct mem_region *region, 177 | void *start, void *end, size_t align) 178 | { 179 | struct mem_data_chunk *chunk; 180 | int rc; 181 | 182 | chunk = malloc(sizeof(struct mem_data_chunk)); 183 | mem_data_chunk_init(chunk); 184 | 185 | chunk->start = start; 186 | chunk->length = (size_t)end - (size_t)start; 187 | rc = posix_memalign((void **)&chunk->data, align, chunk->length); 188 | if (rc < 0) { 189 | perror("posix_memalign"); 190 | return NULL; 191 | } 192 | 193 | mem_region_add_data_chunk(region, chunk); 194 | return chunk; 195 | } 196 | 197 | const char *str_mem_region_type(int type) 198 | { 199 | switch (type) 200 | { 201 | case MEM_REGION_TYPE_EMPTY: 202 | return "empty"; 203 | case MEM_REGION_TYPE_MALLOC: 204 | return "malloc"; 205 | case MEM_REGION_TYPE_MMAP: 206 | return "mmap"; 207 | case MEM_REGION_TYPE_VDSO: 208 | return "vdso"; 209 | case MEM_REGION_TYPE_VSYSCALL: 210 | return "vsyscall"; 211 | case MEM_REGION_TYPE_DELETED: 212 | return "deleted"; 213 | default: 214 | break; 215 | } 216 | return "unknown"; 217 | } 218 | 219 | static void mem_region_print(const struct mem_region *region) 220 | { 221 | fprintf(stderr, 222 | "region addr: %zx-%zx len: %zx off: %zx num_chunks: %zd " 223 | "path='%s' fd=%d type=%s\n", 224 | (size_t)region->start, 225 | (size_t)region->start+region->length, 226 | region->length, 227 | region->offset, 228 | region->num_data_chunks, 229 | region->path, 230 | region->fd, 231 | str_mem_region_type(region->type)); 232 | } 233 | 234 | static void mem_region_create_data_chunk_index(struct mem_region *region) 235 | { 236 | int i; 237 | struct mem_data_chunk *cur; 238 | 239 | if (!region->num_data_chunks) 240 | return; 241 | 242 | region->data_index = malloc(sizeof( 243 | struct mem_data_chunk*) * region->num_data_chunks); 244 | 245 | cur = region->data_head; 246 | for (i = 0; cur != NULL; cur = cur->next) { 247 | region->data_index[i++] = cur; 248 | 249 | if (i > (int)region->num_data_chunks) { 250 | fprintf(stderr, "region %p: num_data_chunks=%zd but cur != NULL\n", 251 | region, region->num_data_chunks); 252 | mem_region_print(region); 253 | break; 254 | } 255 | } 256 | } 257 | 258 | static char *addr_increment_clamped(char *start, char *end, size_t increment) 259 | { 260 | assert(end >= start); 261 | return ((size_t)(end - start) <= increment) ? 262 | end : start + increment; 263 | } 264 | 265 | static int mem_region_build_label_cover(struct mem_region *region, 266 | size_t generic_chunk_size, struct mem_data_chunk **chunks, size_t align) 267 | { 268 | size_t i, n = 0; 269 | 270 | if (region->num_labels == 0) 271 | return 0; 272 | 273 | for (i = 0; i < region->num_labels; ++i) { 274 | char *cur_start, *cur_end, *region_end; 275 | struct mem_data_chunk *new_chunk; 276 | 277 | region_end = (char *)region->start + region->length; 278 | cur_start = region->labels[i]; 279 | cur_end = addr_increment_clamped(cur_start, region_end, generic_chunk_size); 280 | 281 | for (++i; i < region->num_labels; ++i) { 282 | if ((size_t)region->labels[i] <= (size_t)cur_end) { 283 | cur_end = addr_increment_clamped((char *)region->labels[i], 284 | region_end, generic_chunk_size); 285 | if (cur_end == region_end) 286 | break; 287 | } 288 | } 289 | 290 | new_chunk = mem_region_alloc_chunk(region, cur_start, cur_end, align); 291 | chunks[n] = new_chunk; 292 | ++n; 293 | } 294 | 295 | mem_region_create_data_chunk_index(region); 296 | 297 | return n; 298 | } 299 | 300 | static int mem_region_map_file(struct mem_region *region) 301 | { 302 | void *data; 303 | struct stat stat_buf; 304 | size_t length = region->length; 305 | 306 | if (region->path == NULL || *region->path == '\0') { 307 | fprintf(stderr, "trying to map file for region %p-%p " 308 | "with empty path\n", 309 | region->start, region->start + region->length); 310 | return -1; 311 | } 312 | 313 | region->fd = open(region->path, O_RDONLY); 314 | if (region->fd < 0) { 315 | perror(region->path); 316 | return -1; 317 | } 318 | 319 | if (fstat(region->fd, &stat_buf) < 0) { 320 | int err = errno; 321 | fprintf(stderr, "failed to stat file %s: %s\n", region->path, strerror(err)); 322 | return -1; 323 | } 324 | 325 | if (region->offset > (size_t)stat_buf.st_size) { 326 | return -1; 327 | } 328 | 329 | // Accessing beyond the length of the file, even though we can map a 330 | // region larger than the size of the file, will cause a SIGBUS, so 331 | // truncate the length of the map to fit within the file. 332 | if (region->length > stat_buf.st_size - region->offset) { 333 | length = stat_buf.st_size - region->offset; 334 | } 335 | 336 | data = mmap(NULL, length, PROT_READ, MAP_SHARED, region->fd, 337 | region->offset); 338 | 339 | if (data == MAP_FAILED) { 340 | int err = errno; 341 | fprintf(stderr, "failed to mmap file %s (length 0x%zx, read, offset " 342 | "0x%zx): %s\n", region->path, region->length, region->offset, 343 | strerror(err)); 344 | return -1; 345 | } 346 | 347 | region->data_head = malloc(sizeof(struct mem_data_chunk)); 348 | mem_data_chunk_init(region->data_head); 349 | region->data_head->start = region->start; 350 | region->data_head->data = data; 351 | region->data_head->length = length; 352 | 353 | region->data_index = malloc(sizeof(struct mem_data_chunk**)); 354 | *region->data_index = region->data_head; 355 | ++region->num_data_chunks; 356 | 357 | region->prev_accessed_chunk = region->data_head; 358 | 359 | return 0; 360 | } 361 | 362 | static int mem_region_init_vdso(struct mem_region *region) 363 | { 364 | region->data_head = malloc(sizeof(struct mem_data_chunk)); 365 | mem_data_chunk_init(region->data_head); 366 | region->data_head->start = region->start; 367 | region->data_head->length = region->length; 368 | 369 | if ((region->data_head->data = (char *)get_vdso()) == NULL) 370 | return -1; 371 | 372 | region->data_index = malloc(sizeof(struct mem_data_chunk**)); 373 | *region->data_index = region->data_head; 374 | ++region->num_data_chunks; 375 | 376 | region->prev_accessed_chunk = region->data_head; 377 | 378 | return 0; 379 | } 380 | 381 | static int mem_region_init_vsyscall(struct mem_region *region) 382 | { 383 | region->data_head = malloc(sizeof(struct mem_data_chunk)); 384 | mem_data_chunk_init(region->data_head); 385 | region->data_head->start = region->start; 386 | region->data_head->data = (char *)VSYSCALL_START; 387 | region->data_head->length = region->length; 388 | 389 | region->data_index = malloc(sizeof(struct mem_data_chunk**)); 390 | *region->data_index = region->data_head; 391 | ++region->num_data_chunks; 392 | 393 | region->prev_accessed_chunk = region->data_head; 394 | 395 | return 0; 396 | } 397 | 398 | static int addr_data_chunk_compar(const void *key, const void *member) 399 | { 400 | const struct mem_data_chunk* const *chunk = member; 401 | if (key < (*chunk)->start) 402 | return -1; 403 | if (in(key, (*chunk)->start, (*chunk)->length)) 404 | return 0; 405 | return 1; 406 | } 407 | 408 | struct mem_data_chunk *mem_region_find_data_chunk( 409 | struct mem_region *region, void *addr) 410 | { 411 | struct mem_data_chunk **chunk_ptr, *chunk; 412 | 413 | chunk = region->prev_accessed_chunk; 414 | if (chunk != NULL && !addr_data_chunk_compar(addr, &chunk)) 415 | return chunk; 416 | 417 | if (region->data_index == NULL) { 418 | if (region->num_data_chunks) { 419 | fprintf(stderr, 420 | "error: region %p-%p is not indexed but " 421 | "attempting to read word\n", 422 | region->start, 423 | region->start + region->length); 424 | } 425 | return NULL; 426 | } 427 | 428 | chunk_ptr = (struct mem_data_chunk **)bsearch(addr, 429 | region->data_index, 430 | region->num_data_chunks, 431 | sizeof(struct mem_data_chunk*), 432 | addr_data_chunk_compar); 433 | 434 | if (chunk_ptr == NULL) 435 | return NULL; 436 | 437 | chunk = *chunk_ptr; 438 | region->prev_accessed_chunk = chunk; 439 | return chunk; 440 | } 441 | 442 | static int mem_region_read_word(struct mem_region *region, 443 | void *addr, uintptr_t *value) 444 | { 445 | struct mem_data_chunk *chunk; 446 | 447 | switch (region->type) { 448 | case MEM_REGION_TYPE_EMPTY: 449 | fprintf(stderr, 450 | "error: trying to read word from empty region %p-%p\n", 451 | region->start, 452 | region->start + region->length); 453 | return -1; 454 | 455 | case MEM_REGION_TYPE_DELETED: 456 | if (!opt_ignore_deleted) 457 | return -2; 458 | 459 | case MEM_REGION_TYPE_MMAP: 460 | if (region->fd < 0 && mem_region_map_file(region) < 0) 461 | return -1; 462 | break; 463 | 464 | case MEM_REGION_TYPE_VDSO: 465 | if (region->data_head == NULL && mem_region_init_vdso(region) < 0) 466 | return -1; 467 | break; 468 | 469 | case MEM_REGION_TYPE_VSYSCALL: 470 | if (region->data_head == NULL && mem_region_init_vsyscall(region) < 0) 471 | return -1; 472 | break; 473 | 474 | default: 475 | break; 476 | } 477 | 478 | if (value == NULL) 479 | return 0; 480 | 481 | chunk = mem_region_find_data_chunk(region, addr); 482 | 483 | if (chunk == NULL) { 484 | size_t i; 485 | 486 | if (!opt_verbose) 487 | return -1; 488 | 489 | fprintf(stderr, 490 | "no chunk of memory containing %p at region %p-%p\n", 491 | addr, region->start, region->start + region->length); 492 | mem_region_print(region); 493 | 494 | for (i = 0; i < region->num_data_chunks; ++i) { 495 | struct mem_data_chunk *chunk = region->data_index[i]; 496 | fprintf(stderr, "chunk %zd: start %p length 0x%zx data %p\n", 497 | i, chunk->start, chunk->length, chunk->data); 498 | } 499 | return -1; 500 | } 501 | 502 | return mem_data_chunk_read_word(chunk, 503 | addr, 504 | value); 505 | } 506 | 507 | static void mem_region_destroy(struct mem_region *region) 508 | { 509 | if (region->data_head != NULL) 510 | mem_data_chunk_list_destroy(region->data_head, region->type); 511 | free(region->data_index); 512 | if (region->fd >= 0) 513 | close(region->fd); 514 | free(region->labels); 515 | free(region->path); 516 | free(region); 517 | } 518 | 519 | static void mem_region_list_destroy(struct mem_region *region) 520 | { 521 | struct mem_region *next; 522 | while (region != NULL) { 523 | next = region->next; 524 | mem_region_destroy(region); 525 | region = next; 526 | } 527 | } 528 | 529 | void mem_map_init(struct mem_map *map) 530 | { 531 | map->list_head = NULL; 532 | map->list_index = NULL; 533 | map->num_regions = 0; 534 | map->prev_accessed_region = NULL; 535 | } 536 | 537 | int mem_map_add_region(struct mem_map *map, struct mem_region *region) 538 | { 539 | size_t i; 540 | struct mem_region **cur = &map->list_head; 541 | struct mem_region *prev = map->prev_accessed_region; 542 | void *region_ceil; 543 | 544 | region_ceil = (char *)region->start + region->length; 545 | 546 | if (prev != NULL && prev->next == NULL) { 547 | if ((char *)region->start >= ((char *)prev->start + prev->length)) { 548 | prev->next = region; 549 | ++map->num_regions; 550 | map->prev_accessed_region = region; 551 | return 0; 552 | } 553 | } 554 | 555 | for (i = 0; i < map->num_regions; ++i) { 556 | if (in(region->start, (*cur)->start, (*cur)->length) || 557 | in(region_ceil, (*cur)->start, (*cur)->length)) 558 | { 559 | fprintf(stderr, "error: overlapping regions: existing: %p-%p " 560 | "new: %p-%p\n", 561 | (*cur)->start, 562 | (*cur)->start+(*cur)->length, 563 | region->start, 564 | region_ceil); 565 | return -1; 566 | } 567 | if ((*cur)->start > region->start) 568 | break; 569 | cur = &(*cur)->next; 570 | } 571 | 572 | region->next = *cur; 573 | *cur = region; 574 | ++map->num_regions; 575 | map->prev_accessed_region = region; 576 | return 0; 577 | } 578 | 579 | void mem_map_create_region_index(struct mem_map *map) 580 | { 581 | int i; 582 | struct mem_region *cur; 583 | 584 | if (!map->num_regions) 585 | return; 586 | 587 | map->list_index = malloc(sizeof(struct mem_region*) * map->num_regions); 588 | cur = map->list_head; 589 | for (i = 0; cur != NULL; cur = cur->next) { 590 | map->list_index[i++] = cur; 591 | } 592 | } 593 | 594 | static int addr_region_compar(const void *key, const void *member) 595 | { 596 | const struct mem_region* const *region = member; 597 | 598 | if (key < (*region)->start) 599 | return -1; 600 | if (in(key, (*region)->start, (*region)->length)) 601 | return 0; 602 | return 1; 603 | } 604 | 605 | static struct mem_region *mem_map_find_region(struct mem_map *map, void *addr) 606 | { 607 | struct mem_region **region_ptr, *region; 608 | 609 | region = map->prev_accessed_region; 610 | if (region != NULL && !addr_region_compar(addr, ®ion)) 611 | return region; 612 | 613 | if (map->list_index == NULL) { 614 | if (map->num_regions) { 615 | fprintf(stderr, 616 | "error: map is not indexed but attempting to find region\n"); 617 | } 618 | return NULL; 619 | } 620 | 621 | region_ptr = (struct mem_region **)bsearch(addr, 622 | map->list_index, 623 | map->num_regions, 624 | sizeof(struct mem_region*), 625 | addr_region_compar); 626 | 627 | if (region_ptr == NULL) { 628 | fprintf(stderr, 629 | "cannot find region of memory containing %p\n", 630 | addr); 631 | region = NULL; 632 | } else { 633 | region = *region_ptr; 634 | map->prev_accessed_region = region; 635 | } 636 | 637 | return region; 638 | } 639 | 640 | struct mem_region *mem_map_get_file_region(struct mem_map *map, void *addr) 641 | { 642 | struct mem_region *region; 643 | 644 | if ((region = mem_map_find_region(map, addr)) == NULL) { 645 | fprintf(stderr, "cannot get file region\n"); 646 | return NULL; 647 | } 648 | 649 | if (region->type != MEM_REGION_TYPE_MMAP && 650 | region->type != MEM_REGION_TYPE_DELETED && 651 | region->type != MEM_REGION_TYPE_VDSO && 652 | region->type != MEM_REGION_TYPE_VSYSCALL) { 653 | fprintf(stderr, "get file region: unexpected region type %s\n", 654 | str_mem_region_type(region->type)); 655 | mem_region_print(region); 656 | return NULL; 657 | } 658 | 659 | if (region->fd < 0 && mem_region_read_word(region, addr, NULL) == -1) 660 | return NULL; 661 | 662 | return region; 663 | } 664 | 665 | int mem_map_add_label(struct mem_map *map, void *label, size_t reserve) 666 | { 667 | struct mem_region *region; 668 | 669 | region = mem_map_find_region(map, label); 670 | if (region == NULL) 671 | return -1; 672 | 673 | mem_region_add_label(region, label, reserve); 674 | return 0; 675 | } 676 | 677 | int mem_map_build_label_cover(struct mem_map *map, size_t generic_chunk_size, 678 | struct mem_data_chunk **chunks, size_t align) 679 | { 680 | struct mem_region *cur; 681 | int n = 0; 682 | 683 | cur = map->list_head; 684 | while (cur != NULL) { 685 | n += mem_region_build_label_cover( 686 | cur, generic_chunk_size, chunks + n, align); 687 | cur = cur->next; 688 | } 689 | 690 | return n; 691 | } 692 | 693 | int mem_map_read_word(struct mem_map *map, void *addr, uintptr_t *value) 694 | { 695 | struct mem_region *region; 696 | 697 | region = mem_map_find_region(map, addr); 698 | if (region == NULL) 699 | return -1; 700 | 701 | return mem_region_read_word(region, 702 | addr, 703 | value); 704 | } 705 | 706 | void mem_map_destroy(struct mem_map *map) 707 | { 708 | if (map->list_head != NULL) 709 | mem_region_list_destroy(map->list_head); 710 | free(map->list_index); 711 | free(map); 712 | } 713 | 714 | void mem_map_print(const struct mem_map *map) 715 | { 716 | struct mem_region *region; 717 | 718 | fprintf(stderr, "mem map with %zd regions\n", map->num_regions); 719 | region = map->list_head; 720 | for (; region != NULL; region = region->next) 721 | mem_region_print(region); 722 | } 723 | -------------------------------------------------------------------------------- /src/mem_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #ifndef __8a2a3b50_986f_11e3_aa30_efef7030cdbc 32 | #define __8a2a3b50_986f_11e3_aa30_efef7030cdbc 33 | 34 | #include 35 | #include 36 | 37 | #define MEM_REGION_TYPE_EMPTY 0 38 | #define MEM_REGION_TYPE_MALLOC 1 39 | #define MEM_REGION_TYPE_MMAP 2 40 | #define MEM_REGION_TYPE_VDSO 3 41 | #define MEM_REGION_TYPE_VSYSCALL 4 42 | #define MEM_REGION_TYPE_DELETED 5 43 | 44 | struct mem_data_chunk 45 | { 46 | /* start in process' address space */ 47 | void *start; 48 | /* allocated memory */ 49 | char *data; 50 | /* data size */ 51 | size_t length; 52 | /* next list element */ 53 | struct mem_data_chunk *next; 54 | }; 55 | 56 | struct mem_region 57 | { 58 | /* start in process' address space */ 59 | void *start; 60 | /* memory length */ 61 | size_t length; 62 | /* file offset */ 63 | size_t offset; 64 | 65 | /* list of copied data chunks */ 66 | struct mem_data_chunk *data_head; 67 | /* sorted index for binary search */ 68 | struct mem_data_chunk **data_index; 69 | /* number of data chunks in index */ 70 | size_t num_data_chunks; 71 | /* cached result of previous lookup */ 72 | struct mem_data_chunk *prev_accessed_chunk; 73 | 74 | /* points to build label cover and copy needed memory contents */ 75 | void **labels; 76 | /* number of points (normally 1) */ 77 | size_t num_labels; 78 | 79 | /* path of mmapped file */ 80 | char *path; 81 | /* file descriptor */ 82 | int fd; 83 | /* type of region */ 84 | int type; 85 | 86 | /* next list element */ 87 | struct mem_region *next; 88 | }; 89 | 90 | struct mem_map 91 | { 92 | /* list of regions */ 93 | struct mem_region *list_head; 94 | /* sorted index for binary search */ 95 | struct mem_region **list_index; 96 | /* number of regions in index */ 97 | size_t num_regions; 98 | /* cached result of previous lookup */ 99 | struct mem_region *prev_accessed_region; 100 | }; 101 | 102 | /* 103 | * mem region 104 | */ 105 | 106 | const char *str_mem_region_type(int type); 107 | 108 | void mem_region_init(struct mem_region *region); 109 | 110 | /* 111 | * mem map 112 | */ 113 | void mem_map_init(struct mem_map *map); 114 | 115 | int mem_map_add_region(struct mem_map *map, struct mem_region *region); 116 | 117 | void mem_map_create_region_index(struct mem_map *map); 118 | 119 | int mem_map_add_label(struct mem_map *map, void *label, size_t reserve); 120 | 121 | int mem_map_build_label_cover(struct mem_map *map, size_t generic_chunk_size, 122 | struct mem_data_chunk **chunks, size_t align); 123 | 124 | struct mem_region *mem_map_get_file_region(struct mem_map *map, void *addr); 125 | 126 | struct mem_data_chunk *mem_region_find_data_chunk( 127 | struct mem_region *region, void *addr); 128 | 129 | int mem_map_read_word(struct mem_map *map, void *addr, uintptr_t *value); 130 | 131 | void mem_map_destroy(struct mem_map *map); 132 | 133 | void mem_map_print(const struct mem_map *map); 134 | 135 | #endif 136 | 137 | -------------------------------------------------------------------------------- /src/proc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #include "mem_map.h" 32 | #include "proc.h" 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | 54 | #ifndef SYS_process_vm_readv 55 | #if defined(__i386) 56 | #define SYS_process_vm_readv 365 57 | #elif defined(__x86_64) 58 | #define SYS_process_vm_readv 310 59 | #else 60 | #error SYS_process_vm_readv is undefined 61 | #endif 62 | #endif 63 | 64 | #define SLEEP_WAIT 500 65 | 66 | int attached_pid = 0; 67 | 68 | /* timeout on waiting for process to stop (us) */ 69 | extern int stop_timeout; 70 | static int sleep_time = 0; 71 | 72 | /* for summary */ 73 | int sleep_count = 0; 74 | size_t total_length = 0; 75 | 76 | extern struct timeval freeze_time; 77 | extern struct timeval unfreeze_time; 78 | 79 | extern int opt_proc_mem; 80 | extern int opt_use_waitpid_timeout; 81 | extern int opt_verbose; 82 | 83 | int proc_state(int pid) 84 | { 85 | FILE *f; 86 | char buf[128]; 87 | char c; 88 | int res = -1; 89 | 90 | sprintf(buf, "/proc/%d/status", pid); 91 | if ((f = fopen(buf, "r")) == NULL) { 92 | fprintf(stderr, "cannot open %s: %s\n", buf, strerror(errno)); 93 | return -1; 94 | } 95 | 96 | while (fgets(buf, sizeof(buf), f)) { 97 | if (sscanf(buf, "State:\t%c", &c) == 1) { 98 | res = c; 99 | break; 100 | } 101 | } 102 | 103 | fclose(f); 104 | return res; 105 | } 106 | 107 | static int proc_stopped(int pid) 108 | { 109 | int c = proc_state(pid); 110 | if (c == -1) 111 | return -1; 112 | 113 | return (c == 't' || c == 'T'); 114 | } 115 | 116 | struct mem_map *create_maps(int pid) 117 | { 118 | FILE *f; 119 | char *buf = NULL, *str = NULL; 120 | size_t total_read, capacity; 121 | 122 | size_t addr_start, addr_end, offset, len; 123 | char r, w, x, p; 124 | int dev_major, dev_minor, inode; 125 | char path[PATH_MAX]; 126 | 127 | struct mem_map *map = NULL; 128 | struct mem_region *region; 129 | 130 | capacity = 0x100000; 131 | buf = calloc(1, capacity); 132 | 133 | sprintf(buf, "/proc/%d/maps", pid); 134 | if ((f = fopen(buf, "r")) == NULL) { 135 | fprintf(stderr, "cannot open %s: %s\n", buf, strerror(errno)); 136 | return NULL; 137 | } 138 | 139 | map = malloc(sizeof(struct mem_map)); 140 | mem_map_init(map); 141 | 142 | memset(buf, 0, capacity); 143 | total_read = 0; 144 | while (!feof(f)) { 145 | fread(&buf[total_read], capacity - total_read - 1, 1, f); 146 | if (errno) { 147 | perror("maps"); 148 | mem_map_destroy(map); 149 | map = NULL; 150 | goto create_maps_end; 151 | } 152 | 153 | total_read = strlen(buf); 154 | if ((total_read + 1) == capacity) { 155 | capacity *= 2; 156 | buf = realloc(buf, capacity); 157 | memset(&buf[total_read], 0, capacity - total_read); 158 | } else { 159 | buf[total_read] = '\0'; 160 | } 161 | } 162 | 163 | str = &buf[0]; 164 | while (*str) { 165 | int scan; 166 | char *next; 167 | 168 | next = strchr(str, '\n'); 169 | if (next != NULL) 170 | *next = '\0'; 171 | 172 | scan = sscanf(str, "%zx-%zx %c%c%c%c %zx %x:%x %d %[^\t\n]", 173 | &addr_start, &addr_end, 174 | &r, &w, &x, &p, 175 | &offset, 176 | &dev_major, &dev_minor, 177 | &inode, 178 | path); 179 | 180 | if (scan < 10) { 181 | fprintf(stderr, "warning: unable to parse maps " 182 | "entry '%s' (read %d)\n", str, scan); 183 | break; 184 | } 185 | 186 | region = malloc(sizeof(struct mem_region)); 187 | mem_region_init(region); 188 | 189 | region->start = (void *)addr_start; 190 | region->length = addr_end - addr_start; 191 | region->offset = offset; 192 | if (scan > 10 && path[0] != '\0') { 193 | if (!strcmp(path, "[vdso]")) { 194 | region->type = MEM_REGION_TYPE_VDSO; 195 | } else if (!strcmp(path, "[vsyscall]")) { 196 | region->type = MEM_REGION_TYPE_VSYSCALL; 197 | } else if ((len = strlen(path)) > 10 && 198 | !strcmp(path + len - 10, " (deleted)")) { 199 | *(path + len - 10) = '\0'; 200 | region->path = strdup(path); 201 | region->type = MEM_REGION_TYPE_DELETED; 202 | } else { 203 | region->path = strdup(path); 204 | region->type = MEM_REGION_TYPE_MMAP; 205 | } 206 | } 207 | 208 | if (mem_map_add_region(map, region) != 0) { 209 | mem_map_destroy(map); 210 | map = NULL; 211 | break; 212 | } 213 | 214 | if (next != NULL) 215 | str = next + 1; 216 | } 217 | 218 | if (map != NULL) 219 | mem_map_create_region_index(map); 220 | 221 | create_maps_end: 222 | fclose(f); 223 | free(buf); 224 | return map; 225 | } 226 | 227 | int print_proc_maps(int pid) 228 | { 229 | char cmd[32]; 230 | sprintf(cmd, "cat /proc/%d/maps 1>&2", pid); 231 | return system(cmd); 232 | } 233 | 234 | /* 235 | * filter for scandir(). choose only thread identifiers 236 | */ 237 | static int dir_select(const struct dirent *entry) 238 | { 239 | const char *c = entry->d_name; 240 | while (*c) 241 | if (!isdigit(*c++)) 242 | return 0; 243 | return 1; 244 | } 245 | 246 | int get_threads(int pid, int **tids) 247 | { 248 | char buf[32]; 249 | struct dirent **namelist; 250 | int cur, i, n; 251 | 252 | snprintf(buf, sizeof(buf), "/proc/%d/task", pid); 253 | 254 | n = scandir(buf, &namelist, dir_select, NULL); 255 | if (n < 0) { 256 | perror(buf); 257 | return -1; 258 | } else { 259 | *tids = malloc(sizeof(int)*n); 260 | i = 0; 261 | while (i < n) { 262 | cur = atoi(namelist[i]->d_name); 263 | (*tids)[i] = cur; 264 | free(namelist[i++]); 265 | } 266 | free(namelist); 267 | } 268 | 269 | return n; 270 | } 271 | 272 | char *get_thread_states(const int *tids, int n) 273 | { 274 | int i; 275 | char *res = calloc(1, n); 276 | 277 | for (i = 0; i < n; ++i) { 278 | int state = proc_state(tids[i]); 279 | if (state < 0) { 280 | fprintf(stderr, "warning: could not get state of thread %d\n", 281 | tids[i]); 282 | res[i] = '?'; 283 | continue; 284 | } 285 | res[i] = state; 286 | } 287 | 288 | return res; 289 | } 290 | 291 | int adjust_threads(int *tids, int nr_tids, int *user_tids, 292 | int *index, int nr_user) 293 | { 294 | int i, j, n = 0; 295 | for (i = 0; i < nr_user; ++i) { 296 | int found = 0; 297 | for (j = 0; j < nr_tids; ++j) { 298 | if (tids[j] == user_tids[i]) { 299 | found = 1; 300 | break; 301 | } 302 | } 303 | if (!found) { 304 | if (n || (user_tids[i] > nr_tids) || (user_tids[i] <= 0)) { 305 | fprintf(stderr, "unexpected thread %d\n", user_tids[i]); 306 | return -1; 307 | } 308 | } else { 309 | ++n; 310 | index[i] = j + 1; 311 | } 312 | } 313 | if (!n) { 314 | for (i = 0; i < nr_user; ++i) { 315 | index[i] = user_tids[i]; 316 | user_tids[i] = tids[user_tids[i]-1]; 317 | } 318 | } 319 | return 0; 320 | } 321 | 322 | int filter_threads(int tids[], int index[], char states[], int nr_tids, 323 | const char *user_states) 324 | { 325 | int i, j = 0; 326 | int nr_prev = nr_tids; 327 | for (i = 0; i < nr_prev; ++i) { 328 | if (strchr(user_states, states[i]) != NULL) { 329 | if (i != j) { 330 | tids[j] = tids[i]; 331 | states[j] = states[i]; 332 | if (index != NULL) 333 | index[j] = index[i]; 334 | } 335 | ++j; 336 | } else { 337 | --nr_tids; 338 | } 339 | } 340 | return nr_tids; 341 | } 342 | 343 | int attach_process(int pid) 344 | { 345 | int status = 0; 346 | 347 | gettimeofday(&freeze_time, NULL); 348 | 349 | attached_pid = pid; 350 | if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { 351 | perror("attach"); 352 | detach_process(pid); 353 | return -1; 354 | } 355 | if (!proc_stopped(pid)) { 356 | struct itimerval tm; 357 | 358 | if (opt_use_waitpid_timeout) { 359 | /* setup alarm to avoid long waiting on waitpid */ 360 | tm.it_interval.tv_sec = 0; 361 | tm.it_interval.tv_usec = 0; 362 | tm.it_value.tv_sec = 1; 363 | tm.it_value.tv_usec = stop_timeout % 1000000; 364 | setitimer(ITIMER_REAL, &tm, NULL); 365 | } 366 | 367 | if (waitpid(pid, &status, WUNTRACED) < 0) { 368 | if (errno == EINTR) { 369 | fprintf(stderr, "timeout on waitpid\n"); 370 | detach_process(pid); 371 | return -1; 372 | } 373 | fprintf(stderr, "waitpid %d: %s\n", pid, strerror(errno)); 374 | detach_process(pid); 375 | return -1; 376 | } 377 | 378 | if (opt_use_waitpid_timeout) { 379 | tm.it_value.tv_sec = 0; 380 | tm.it_value.tv_usec = 0; 381 | setitimer(ITIMER_REAL, &tm, NULL); 382 | } 383 | 384 | if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) 385 | fprintf(stderr, "warning: waitpid(%d) WIFSTOPPED=%d WSTOPSIG=%d\n", 386 | pid, WIFSTOPPED(status), WSTOPSIG(status)); 387 | } 388 | if (kill(pid, SIGSTOP) < 0) { 389 | perror("send SIGSTOP"); 390 | return -1; 391 | } 392 | return 0; 393 | } 394 | 395 | int attach_thread(int tid) 396 | { 397 | if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) < 0) { 398 | perror("PTRACE_ATTACH"); 399 | return -1; 400 | } 401 | if (wait_thread(tid) < 0) 402 | return -1; 403 | return 0; 404 | } 405 | 406 | int detach_process(int pid) 407 | { 408 | int rc = 0; 409 | if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) { 410 | perror("detach"); 411 | rc = -1; 412 | } 413 | if (kill(pid, SIGCONT) < 0) { 414 | perror("send SIGCONT"); 415 | rc = -1; 416 | } 417 | 418 | attached_pid = 0; 419 | gettimeofday(&unfreeze_time, NULL); 420 | return rc; 421 | } 422 | 423 | int detach_thread(int tid) 424 | { 425 | long rc = ptrace(PTRACE_DETACH, tid, NULL, NULL); 426 | if (rc < 0) { 427 | perror("PTRACE_DETACH"); 428 | return -1; 429 | } 430 | return 0; 431 | } 432 | 433 | int wait_thread(int tid) 434 | { 435 | int rc; 436 | while (!(rc = proc_stopped(tid))) { 437 | if (stop_timeout && sleep_time > stop_timeout) { 438 | fprintf(stderr, "timeout waiting for thread %d to stop", tid); 439 | return -1; 440 | } 441 | usleep(SLEEP_WAIT); 442 | sleep_time += SLEEP_WAIT; 443 | sleep_count++; 444 | } 445 | return (rc == -1 ? -1 : 0); 446 | } 447 | 448 | /* 449 | * copy memory contents using process_vm_readv(). reduces number 450 | * of system calls comparing to /proc/pid/mem 451 | * 452 | * return values: 453 | * 0 success 454 | * -1 fail 455 | * ENOSYS process_vm_readv() is not supported 456 | */ 457 | static int copy_memory_process_vm_readv(int pid, 458 | struct mem_data_chunk **frames, int n_frames) 459 | { 460 | struct iovec *local_iov, *remote_iov; 461 | ssize_t *frame_bytes; 462 | int i, rc = -1; 463 | ssize_t bytes_total = 0; 464 | int seg_count = 0; 465 | 466 | local_iov = malloc(sizeof(struct iovec)*n_frames); 467 | remote_iov = malloc(sizeof(struct iovec)*n_frames); 468 | frame_bytes = malloc(sizeof(ssize_t)*n_frames); 469 | 470 | for (i = 0; i < n_frames; ++i) { 471 | local_iov[i].iov_base = frames[i]->data; 472 | local_iov[i].iov_len = frames[i]->length; 473 | remote_iov[i].iov_base = frames[i]->start; 474 | remote_iov[i].iov_len = frames[i]->length; 475 | 476 | bytes_total += frames[i]->length; 477 | frame_bytes[i] = bytes_total; 478 | } 479 | 480 | bytes_total = 0; 481 | while (1) { 482 | ssize_t bytes_read; 483 | int frames_to_read = n_frames - seg_count; 484 | if (frames_to_read > IOV_MAX) 485 | frames_to_read = IOV_MAX; 486 | 487 | bytes_read = syscall(SYS_process_vm_readv, 488 | pid, 489 | local_iov + seg_count, 490 | frames_to_read, 491 | remote_iov + seg_count, 492 | frames_to_read, 493 | 0ULL); 494 | 495 | if (bytes_read < 0) { 496 | if (errno == ENOSYS) 497 | rc = ENOSYS; 498 | else 499 | perror("process_vm_readv"); 500 | 501 | goto process_vm_readv_end; 502 | } 503 | 504 | bytes_total += bytes_read; 505 | total_length = bytes_total; 506 | for (seg_count = n_frames-1; seg_count >= 0; --seg_count) { 507 | if (frame_bytes[seg_count] == bytes_total) 508 | break; 509 | } 510 | 511 | if (seg_count < 0) { 512 | fprintf(stderr, "unknown number of bytes returned by " 513 | "process_vm_readv: bytes_read=%zd " 514 | "bytes_total=%zd seg_count=%d\n", 515 | bytes_read, bytes_total, seg_count); 516 | goto process_vm_readv_end; 517 | } 518 | 519 | if (seg_count == (n_frames-1)) 520 | break; 521 | 522 | ++seg_count; 523 | } 524 | 525 | rc = 0; 526 | 527 | process_vm_readv_end: 528 | free(local_iov); 529 | free(remote_iov); 530 | free(frame_bytes); 531 | return rc; 532 | } 533 | 534 | /* 535 | * read the file /proc//mem 536 | */ 537 | static int copy_memory_proc_mem(int pid, struct mem_data_chunk **frames, 538 | int n_frames) 539 | { 540 | int i = 0; 541 | char fname[32]; 542 | int fd; 543 | int rc = -1; 544 | 545 | sprintf(fname, "/proc/%d/mem", pid); 546 | if ((fd = open(fname, O_RDONLY)) == -1) { 547 | fprintf(stderr, "cannot open %s\n", fname); 548 | perror(fname); 549 | return -1; 550 | } 551 | 552 | for (i = 0; i < n_frames; ++i) { 553 | off_t from = (off_t)(uintptr_t)frames[i]->start; 554 | char *to = frames[i]->data; 555 | size_t count = frames[i]->length; 556 | 557 | while (count > 0) { 558 | ssize_t rd = pread(fd, to, count, from); 559 | 560 | if (rd == -1) { 561 | fprintf(stderr, "pread() at %s:0x%jx (#%d) failed: %s [%d]\n", 562 | fname, from, i, strerror(errno), errno); 563 | goto proc_mem_end; 564 | } 565 | 566 | from += rd; 567 | to += rd; 568 | count -= rd; 569 | } 570 | 571 | total_length += frames[i]->length; 572 | } 573 | 574 | rc = 0; 575 | 576 | proc_mem_end: 577 | close(fd); 578 | return rc; 579 | } 580 | 581 | int copy_memory(int pid, struct mem_data_chunk **frames, int n_frames) 582 | { 583 | if (!opt_proc_mem) { 584 | int rc = copy_memory_process_vm_readv(pid, frames, n_frames); 585 | if (rc == ENOSYS) { 586 | if (opt_verbose) { 587 | fprintf(stderr, "process_vm_readv is not supported, falling " 588 | "back to /proc/pid/mem\n"); 589 | } 590 | } else { 591 | return rc; 592 | } 593 | } 594 | 595 | return copy_memory_proc_mem(pid, frames, n_frames); 596 | } 597 | 598 | void *get_vdso() 599 | { 600 | static const char *auxv = "/proc/self/auxv"; 601 | FILE *f; 602 | long entry[2]; 603 | 604 | f = fopen(auxv, "r"); 605 | if (f == NULL) { 606 | perror(auxv); 607 | return NULL; 608 | } 609 | 610 | while (!feof(f)) { 611 | if (fread(entry, sizeof(entry), 1, f) != 1) 612 | goto get_vdso_fail; 613 | 614 | if (entry[0] == AT_SYSINFO_EHDR) { 615 | fclose(f); 616 | return (void *)entry[1]; 617 | } 618 | } 619 | 620 | get_vdso_fail: 621 | perror(auxv); 622 | fclose(f); 623 | return NULL; 624 | } 625 | 626 | void quit_handler(int signum) 627 | { 628 | /* 629 | * We can't call PTRACE_DETACH here because we are in a signal handler. 630 | * Additionally ptrace will automatically detach when this process exits at 631 | * the end of this function. We do however always need to send the SIGCONT 632 | * if we have ptrace attached because when the ptrace automatically 633 | * detaches it will leave the process in a stopped state even if we had not 634 | * yet sent SIGSTOP to it. 635 | */ 636 | if (attached_pid) 637 | kill(attached_pid, SIGCONT); 638 | if (signum == SIGSEGV) { 639 | static volatile int *n = NULL; 640 | *n = 1969; 641 | } 642 | _exit(1); 643 | } 644 | -------------------------------------------------------------------------------- /src/proc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #ifndef __0383eba0_9883_11e3_82d0_c1c5dc4afb95 32 | #define __0383eba0_9883_11e3_82d0_c1c5dc4afb95 33 | 34 | struct mem_data_chunk; 35 | struct mem_map; 36 | 37 | /* 38 | * returns process state (R, S, D, T, ...) or -1 on error 39 | */ 40 | int proc_state(int pid); 41 | 42 | /* 43 | * parse /proc//maps file and create mem_map structure 44 | */ 45 | struct mem_map *create_maps(int pid); 46 | 47 | /* 48 | * simple routine to print process maps for 49 | * debugging or advanced error reporting 50 | */ 51 | int print_proc_maps(int pid); 52 | 53 | /* 54 | * get thread identifiers of the process 55 | */ 56 | int get_threads(int pid, int **tids); 57 | 58 | /* 59 | * returns a pointer to dynamically allocated array of characters representing 60 | * thread states as found in /proc//status 61 | */ 62 | char *get_thread_states(const int *tids, int n); 63 | 64 | /* 65 | * translate thread numbers to system lwp ids 66 | */ 67 | int adjust_threads(int *tids, int nr_tids, int *user_tids, 68 | int *index, int nr_user); 69 | 70 | /* 71 | * filter threads by state. returns new number of threads 72 | */ 73 | int filter_threads(int tids[], int index[], char states[], int nr_tids, 74 | const char *user_states); 75 | 76 | /* 77 | * attach to the process, wait until it's stopped, 78 | * send SIGSTOP to make all threads frozen 79 | */ 80 | int attach_process(int pid); 81 | 82 | /* 83 | * attach to process' thread 84 | */ 85 | int attach_thread(int tid); 86 | 87 | /* 88 | * detach from process, send SIGCONT 89 | */ 90 | int detach_process(int pid); 91 | 92 | /* 93 | * detach from process' thread 94 | */ 95 | int detach_thread(int tid); 96 | 97 | /* 98 | * wait for thread to stop. we cannot use waitpid() here because non-leader 99 | * group members don't become children of tracer 100 | */ 101 | int wait_thread(int tid); 102 | 103 | /* 104 | * copy process' memory contents 105 | */ 106 | int copy_memory(int pid, struct mem_data_chunk **frames, int n_frames); 107 | 108 | /* 109 | * resolve VDSO mapping address 110 | */ 111 | void *get_vdso(void); 112 | 113 | /* 114 | * detach from process and send SIGCONT when interrupt/termination occurs 115 | */ 116 | void quit_handler(int signum); 117 | 118 | #endif 119 | 120 | -------------------------------------------------------------------------------- /src/snapshot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #include "mem_map.h" 32 | #include "proc.h" 33 | #include "snapshot.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #ifndef PTRACE_GETREGSET 46 | #define PTRACE_GETREGSET 0x4204 47 | #endif 48 | 49 | extern size_t stack_size; 50 | extern int opt_show_state; 51 | extern int opt_verbose; 52 | extern char *opt_thread_states; 53 | 54 | void snapshot_destroy(struct snapshot *snap) 55 | { 56 | if (snap == NULL) 57 | return; 58 | 59 | if (snap->map != NULL) 60 | mem_map_destroy(snap->map); 61 | 62 | free(snap->regs); 63 | free(snap->tids); 64 | free(snap->states); 65 | free(snap); 66 | } 67 | 68 | /* 69 | * save process' memory maps, stack contents, thread identifiers and registers 70 | */ 71 | struct snapshot *get_snapshot(int pid, int *tids, int *index, int nr_tids) 72 | { 73 | struct snapshot *res; 74 | int attached_tid = 0; 75 | int i, n_frames; 76 | long page, label, rc; 77 | struct mem_data_chunk **stacks_cover = NULL; 78 | 79 | if ((page = sysconf(_SC_PAGESIZE)) < 0) { 80 | perror("get pagesize"); 81 | return NULL; 82 | } 83 | --page; 84 | 85 | res = calloc(1, sizeof(struct snapshot)); 86 | 87 | /* 88 | * create memory_map structure corresponding to process' maps 89 | */ 90 | res->map = create_maps(pid); 91 | if (res->map == NULL) 92 | goto get_snapshot_fail; 93 | 94 | /* 95 | * get process' threads 96 | */ 97 | res->num_threads = get_threads(pid, &res->tids); 98 | if (res->num_threads < 0 || res->tids == NULL) 99 | goto get_snapshot_fail; 100 | 101 | /* 102 | * user-provided list of threads 103 | */ 104 | if (tids != NULL) { 105 | if (adjust_threads(res->tids, res->num_threads, tids, index, nr_tids) < 0) 106 | goto get_snapshot_fail; 107 | 108 | free(res->tids); 109 | res->num_threads = nr_tids; 110 | res->tids = tids; 111 | } 112 | 113 | if (opt_show_state || opt_thread_states) 114 | res->states = get_thread_states(res->tids, res->num_threads); 115 | 116 | if (opt_thread_states) { 117 | assert(tids == NULL); 118 | res->num_threads = filter_threads(res->tids, index, res->states, 119 | res->num_threads, opt_thread_states); 120 | if (!res->num_threads) 121 | return res; 122 | } 123 | 124 | res->cur_thr = 0; 125 | 126 | res->regs = malloc(sizeof(res->regs[0])*res->num_threads); 127 | if (res->regs == NULL) { 128 | perror("malloc"); 129 | goto get_snapshot_fail; 130 | } 131 | 132 | /* FREEZE PROCESS */ 133 | if (attach_process(pid) < 0) 134 | goto get_snapshot_fail; 135 | 136 | for (i = 0; i < res->num_threads; ++i) { 137 | struct iovec iov; 138 | 139 | /* 140 | * we have already attached to main thread. call attach_thread() 141 | * for other ones 142 | */ 143 | attached_tid = res->tids[i]; 144 | if (res->tids[i] != pid && attach_thread(res->tids[i]) < 0) 145 | goto get_snapshot_fail_attached; 146 | 147 | /* 148 | * save thread's registers 149 | */ 150 | iov.iov_len = sizeof(res->regs[0]); 151 | iov.iov_base = &res->regs[i]; 152 | rc = ptrace(PTRACE_GETREGSET, res->tids[i], NT_PRSTATUS, &iov); 153 | if (rc < 0) { 154 | perror("PTRACE_GETREGSET"); 155 | goto get_snapshot_fail_attached; 156 | } 157 | 158 | /* 159 | * save label on memory region. it will indicate that memory contents 160 | * upper than this point (%rsp) will needed to unwind stacks 161 | */ 162 | label = SP_REG(&res->regs[i]) & ~page; 163 | rc = mem_map_add_label(res->map, (void *)label, res->num_threads); 164 | 165 | if (rc < 0) { 166 | fprintf(stderr, "failed to add label 0x%lx [rsp 0x%llx thread %d]\n", 167 | label, (long long unsigned int)SP_REG(&res->regs[i]), res->tids[i]); 168 | goto get_snapshot_fail_attached; 169 | } 170 | 171 | /* 172 | * detach from thread. it will still be frozen due to SIGSTOP 173 | */ 174 | if (res->tids[i] != pid && detach_thread(res->tids[i]) < 0) 175 | goto get_snapshot_fail_attached; 176 | } 177 | 178 | /* 179 | * arrange data chunks to copy memory contents. in most cases the chunks 180 | * will start from %rsp pointing somewhere in thread's stack 181 | * to the end of the stack region 182 | */ 183 | stacks_cover = malloc(sizeof(struct mem_data_chunk*) * res->num_threads); 184 | 185 | n_frames = mem_map_build_label_cover(res->map, stack_size, 186 | stacks_cover, page + 1); 187 | 188 | if (stacks_cover == NULL) { 189 | fprintf(stderr, "error: stacks cover == NULL, n_frames=%d\n", n_frames); 190 | goto get_snapshot_fail_attached; 191 | } 192 | 193 | /* 194 | * copy memory contents 195 | */ 196 | rc = copy_memory(pid, stacks_cover, n_frames); 197 | 198 | if (rc < 0) 199 | goto get_snapshot_fail_attached; 200 | 201 | /* UNFREEZE PROCESS */ 202 | if (detach_process(pid) < 0) 203 | goto get_snapshot_fail; 204 | 205 | if (opt_verbose) { 206 | for (i = 0; i < n_frames; ++i) { 207 | struct mem_data_chunk *chunk = stacks_cover[i]; 208 | printf("chunk #%d: %p-%p length: %zdK\n", 209 | i, chunk->start, 210 | chunk->start + chunk->length, 211 | chunk->length >> 10); 212 | } 213 | } 214 | 215 | free(stacks_cover); 216 | 217 | return res; 218 | 219 | get_snapshot_fail_attached: 220 | if (attached_tid) 221 | detach_thread(attached_tid); 222 | 223 | detach_process(pid); 224 | 225 | get_snapshot_fail: 226 | if (opt_verbose) { 227 | fprintf(stderr, "maps of %d:\n", pid); 228 | print_proc_maps(pid); 229 | } 230 | 231 | free(stacks_cover); 232 | snapshot_destroy(res); 233 | return NULL; 234 | } 235 | -------------------------------------------------------------------------------- /src/snapshot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #ifndef __d235bb20_9af6_11e3_8ab0_50ba7047f67f 32 | #define __d235bb20_9af6_11e3_8ab0_50ba7047f67f 33 | 34 | struct mem_map; 35 | 36 | #if defined(__arm__) 37 | struct user_regs; 38 | typedef struct user_regs regs_t; 39 | #else 40 | struct user_regs_struct; 41 | typedef struct user_regs_struct regs_t; 42 | #endif 43 | 44 | #if defined(__arm__) 45 | #define SP_REG(regs) ((regs)->uregs[13]) 46 | #elif defined(__aarch64__) 47 | #define SP_REG(r) ((r)->sp) 48 | #elif defined(__i386) 49 | #define SP_REG(regs) ((regs)->esp) 50 | #elif defined(__x86_64) 51 | #define SP_REG(regs) ((regs)->rsp) 52 | #else 53 | #error Need porting 54 | #endif 55 | 56 | struct snapshot 57 | { 58 | /* memory mapping, copied contents, open mmapped files */ 59 | struct mem_map *map; 60 | /* thread identifiers */ 61 | int *tids; 62 | /* thread states */ 63 | char *states; 64 | /* number of threads */ 65 | int num_threads; 66 | /* current thread (used when unwinding stack) */ 67 | int cur_thr; 68 | /* per-thread registers */ 69 | regs_t *regs; 70 | }; 71 | 72 | /* 73 | * fill up snapshot structure for a process 74 | */ 75 | struct snapshot *get_snapshot(int pid, int *tids, int *index, int nr_tids); 76 | 77 | /* 78 | * free resources 79 | */ 80 | void snapshot_destroy(struct snapshot *snap); 81 | 82 | #endif 83 | 84 | -------------------------------------------------------------------------------- /src/tbstack.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include "backtrace.h" 45 | #include "proc.h" 46 | 47 | #define GENERIC_STACK_SIZE 0xa00000 48 | 49 | struct timeval freeze_time = {0, 0}; 50 | struct timeval unfreeze_time = {0, 0}; 51 | 52 | extern int sleep_count; 53 | extern size_t total_length; 54 | 55 | static int pid = 0; 56 | static int nr_tids = 0; 57 | static int *tid_list = NULL; 58 | static int *tid_index = NULL; 59 | size_t stack_size = 0; 60 | int opt_proc_mem = 0; 61 | int opt_ptrace = 0; 62 | int opt_show_rsp = 0; 63 | int opt_show_state = 0; 64 | int opt_verbose = 0; 65 | int stop_timeout = 1000000; 66 | int opt_ignore_deleted = 0; 67 | int opt_use_waitpid_timeout = 0; 68 | char *opt_thread_states = NULL; 69 | 70 | static int usage(const char *name) 71 | { 72 | fprintf(stderr, 73 | "usage: %s \n" 74 | " %s /,...,\n" 75 | " %s /RS\n\n" 76 | "options: --help show this\n" 77 | " --ignore-deleted try to open shared objects marked as deleted\n" 78 | " --use-waitpid-timeout set alarm to interrupt waitpid\n" 79 | " --proc-mem prefer reading /proc/pid/mem. default flavor\n" 80 | " is process_vm_readv\n" 81 | #if !defined (NO_LIBUNWIND_PTRACE) 82 | " --ptrace use libunwind-ptrace interface (slower)\n" 83 | #endif 84 | " --show-rsp show %%rsp in second column\n" 85 | " --show-state show thread states\n" 86 | " --stack-size maximum stack size to copy (default is current\n" 87 | " RLIMIT_STACK)\n" 88 | " --stop-timeout timeout for waiting the process to freeze, in\n" 89 | " milliseconds. default value is %d\n" 90 | " --verbose verbose error messages\n" 91 | " --version output version information and exit\n", 92 | name, name, name, stop_timeout/1000); 93 | return 2; 94 | } 95 | 96 | static void parse_pid_arg(const char *prog, char *arg) 97 | { 98 | char *tstr, *pos; 99 | int nr_commas = 0; 100 | char c, prev = '\0'; 101 | int i = 0, j; 102 | int is_state_list = 1; 103 | 104 | tstr = strchr(arg, '/'); 105 | if (tstr != NULL) { 106 | *tstr++ = '\0'; 107 | if (*tstr == '\0') { 108 | fprintf(stderr, "empty thread list\n"); 109 | exit(usage(prog)); 110 | } 111 | } 112 | 113 | pid = atoi(arg); 114 | if (pid <= 0) { 115 | fprintf(stderr, "invalid pid %s\n", arg); 116 | exit(usage(prog)); 117 | } 118 | 119 | if (tstr == NULL) 120 | return; 121 | 122 | /* check if state list is provided */ 123 | pos = tstr; 124 | while ((c = *pos++)) { 125 | if (!isalpha(c)) { 126 | is_state_list = 0; 127 | break; 128 | } 129 | } 130 | 131 | if (is_state_list) { 132 | opt_thread_states = strdup(tstr); 133 | return; 134 | } 135 | 136 | pos = tstr; 137 | if (*pos == ',') 138 | goto parse_pid_arg_invalid_list; 139 | while ((c = *pos++)) { 140 | if (c == ',') { 141 | if (prev == ',' || prev == '\0') 142 | goto parse_pid_arg_invalid_list; 143 | ++nr_commas; 144 | } else if (!isdigit(c)) { 145 | goto parse_pid_arg_invalid_list; 146 | } 147 | prev = c; 148 | } 149 | if (prev == ',') 150 | goto parse_pid_arg_invalid_list; 151 | 152 | nr_tids = nr_commas + 1; 153 | tid_list = malloc(sizeof(int) * nr_tids); 154 | tid_index = malloc(sizeof(int) * nr_tids); 155 | 156 | tstr = strtok(tstr, ","); 157 | do { 158 | assert(i < nr_tids); 159 | tid_list[i++] = atoi(tstr); 160 | } while ((tstr = strtok(NULL, ",")) != NULL); 161 | 162 | 163 | for (i = 0; i < nr_tids; ++i) { 164 | for (j = i + 1; j < nr_tids; ++j) { 165 | if (tid_list[i] == tid_list[j]) { 166 | fprintf(stderr, "duplicate thread %d\n", tid_list[i]); 167 | exit(usage(prog)); 168 | } 169 | } 170 | } 171 | 172 | return; 173 | 174 | parse_pid_arg_invalid_list: 175 | fprintf(stderr, "invalid thread list string '%s'\n", tstr); 176 | exit(usage(prog)); 177 | } 178 | 179 | static void parse_options(int argc, char **argv) 180 | { 181 | char *endptr = ""; 182 | 183 | while (1) { 184 | int option_index = 0, c; 185 | static struct option long_options[] = { 186 | { "proc-mem", 0, NULL, 0 }, 187 | { "ptrace", 0, NULL, 0 }, 188 | { "stack-size", 1, NULL, 0}, 189 | { "show-rsp", 0, NULL, 0}, 190 | { "verbose", 0, NULL, 0}, 191 | { "help", 0, NULL, 0}, 192 | { "stop-timeout", 1, NULL, 0}, 193 | { "ignore-deleted", 0, NULL, 0}, 194 | { "use-waitpid-timeout", 0, NULL, 0 }, 195 | { "show-state", 0, NULL, 0 }, 196 | { "version", 0, NULL, 0 }, 197 | { 0, 0, 0, 0 } 198 | }; 199 | 200 | c = getopt_long(argc, argv, "", long_options, &option_index); 201 | if (c == -1) 202 | break; 203 | 204 | switch (c) { 205 | case 0: 206 | switch (option_index) { 207 | case 0: 208 | opt_proc_mem = 1; 209 | break; 210 | 211 | case 1: 212 | #if defined (NO_LIBUNWIND_PTRACE) 213 | fprintf(stderr, "support for libunwind-ptrace is disabled\n"); 214 | exit(1); 215 | #endif 216 | opt_ptrace = 1; 217 | break; 218 | 219 | case 2: 220 | if (optarg[0] == '0' && optarg[1] == 'x') { 221 | stack_size = strtol(optarg+2, &endptr, 16); 222 | } else { 223 | stack_size = strtol(optarg, &endptr, 10); 224 | } 225 | if (*endptr != '\0') { 226 | fprintf(stderr, "invalid value of option stack-size: %s\n", 227 | optarg); 228 | exit(2); 229 | } 230 | break; 231 | 232 | case 3: 233 | opt_show_rsp = 1; 234 | break; 235 | 236 | case 4: 237 | opt_verbose = 1; 238 | break; 239 | 240 | case 5: 241 | usage(argv[0]); 242 | exit(0); 243 | 244 | case 6: 245 | if ((stop_timeout = atoi(optarg) * 1000) < 0) { 246 | fprintf(stderr, "invalid value of stop-timeout: %s\n", 247 | optarg); 248 | exit(2); 249 | } 250 | break; 251 | 252 | case 7: 253 | opt_ignore_deleted = 1; 254 | break; 255 | 256 | case 8: 257 | opt_use_waitpid_timeout = 1; 258 | break; 259 | 260 | case 9: 261 | opt_show_state = 1; 262 | break; 263 | 264 | case 10: 265 | puts(PACKAGE_STRING); 266 | exit(0); 267 | 268 | default: 269 | break; 270 | } 271 | break; 272 | 273 | case '?': 274 | exit(usage(argv[0])); 275 | break; 276 | 277 | default: 278 | printf("?? getopt returned character code 0%o ??\n", c); 279 | } 280 | } 281 | 282 | if (optind == argc) 283 | exit(usage(argv[0])); 284 | 285 | parse_pid_arg(argv[0], argv[optind]); 286 | 287 | if (++optind < argc) { 288 | fprintf(stderr, "unknown command line argument %s\n", argv[optind]); 289 | exit(usage(argv[0])); 290 | } 291 | } 292 | 293 | static void check_libelf_version() 294 | { 295 | if (elf_version(EV_CURRENT) == EV_NONE) { 296 | fprintf(stderr, "elf initialization failed: %s\n", 297 | elf_errmsg(elf_errno())); 298 | exit(1); 299 | } 300 | } 301 | 302 | static void alarm_handler(int signo) 303 | { 304 | (void) signo; 305 | }; 306 | 307 | static void setup_signals() 308 | { 309 | sigset_t mask; 310 | struct sigaction act; 311 | 312 | sigemptyset(&mask); 313 | act.sa_handler = quit_handler; 314 | act.sa_mask = mask; 315 | act.sa_flags = 0; 316 | act.sa_restorer = NULL; 317 | 318 | if (sigaction(SIGINT, &act, NULL) < 0 || 319 | sigaction(SIGQUIT, &act, NULL) < 0 || 320 | sigaction(SIGPIPE, &act, NULL) < 0 || 321 | sigaction(SIGTERM, &act, NULL) < 0 || 322 | sigaction(SIGTSTP, &act, NULL) < 0 || 323 | sigaction(SIGABRT, &act, NULL) < 0 || 324 | sigaction(SIGSEGV, &act, NULL) < 0) 325 | goto sigaction_fail; 326 | 327 | act.sa_handler = alarm_handler; 328 | if (sigaction(SIGALRM, &act, NULL) < 0) 329 | goto sigaction_fail; 330 | 331 | return; 332 | 333 | sigaction_fail: 334 | perror("sigaction"); 335 | exit(1); 336 | } 337 | 338 | static void setup_stack_size() 339 | { 340 | struct rlimit lim; 341 | if (getrlimit(RLIMIT_STACK, &lim) < 0) { 342 | perror("getrlimit"); 343 | exit(1); 344 | } 345 | 346 | if (lim.rlim_cur == RLIM_INFINITY) { 347 | stack_size = GENERIC_STACK_SIZE; 348 | } else { 349 | stack_size = lim.rlim_cur; 350 | } 351 | } 352 | 353 | static void check_process() 354 | { 355 | if (kill(pid, 0) < 0) { 356 | fprintf(stderr, "%s\n", strerror(errno)); 357 | exit(1); 358 | } 359 | } 360 | 361 | static void summary() 362 | { 363 | long tm; 364 | 365 | if (!freeze_time.tv_sec) 366 | return; 367 | 368 | tm = unfreeze_time.tv_sec * 1000000 + unfreeze_time.tv_usec; 369 | tm -= freeze_time.tv_sec * 1000000 + freeze_time.tv_usec; 370 | 371 | printf("----------------------- summary --------------------------\n" 372 | " time the process was frozen: %ldms %ldus\n" 373 | " sleep count: %d\n" 374 | " total bytes copied: 0x%zx (%zdK)\n", 375 | tm/1000, tm%1000, sleep_count, total_length, total_length>>10); 376 | } 377 | 378 | int main(int argc, char **argv) 379 | { 380 | int rc = 0; 381 | 382 | parse_options(argc, argv); 383 | check_process(); 384 | check_libelf_version(); 385 | setup_signals(); 386 | 387 | if (!stack_size) 388 | setup_stack_size(); 389 | 390 | rc = !opt_ptrace ? 391 | backtrace_snapshot(pid, tid_list, tid_index, nr_tids) : 392 | backtrace_ptrace(pid, tid_list, tid_index, nr_tids); 393 | 394 | summary(); 395 | 396 | return (rc != 0); 397 | } 398 | -------------------------------------------------------------------------------- /src/unwind.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tbstack -- fast stack trace utility 3 | * 4 | * Copyright (c) 2014, Tbricks AB 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS 21 | * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | * DAMAGE. 29 | */ 30 | 31 | #include "mem_map.h" 32 | #include "snapshot.h" 33 | #include "unwind.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | /* 46 | * some libelf implementations do not provide ELF_C_READ_MMAP 47 | */ 48 | #if HAVE_DECL_ELF_C_READ_MMAP 49 | #define TBSTACK_ELF_C_READ ELF_C_READ_MMAP 50 | #else 51 | #define TBSTACK_ELF_C_READ ELF_C_READ 52 | #endif 53 | 54 | /* 55 | * search unwind table for a procedure (used by find_proc_info) 56 | */ 57 | #define search_unwind_table UNW_OBJ(dwarf_search_unwind_table) 58 | 59 | extern int search_unwind_table(unw_addr_space_t as, unw_word_t ip, 60 | unw_dyn_info_t *di, unw_proc_info_t *pip, 61 | int need_unwind_info, void *arg); 62 | 63 | 64 | #ifdef HAVE_DWARF 65 | #define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame) 66 | 67 | extern int dwarf_find_debug_frame(int found, 68 | unw_dyn_info_t *di_debug, 69 | unw_word_t ip, 70 | unw_word_t segbase, 71 | const char *obj_name, unw_word_t start, 72 | unw_word_t end); 73 | #endif 74 | 75 | 76 | /* 77 | * get dwarf encoded value 78 | */ 79 | static ssize_t dw_get_value(char *data, unsigned char enc, 80 | uint64_t cur, uint64_t *value) 81 | { 82 | int64_t number = 0; 83 | size_t size; 84 | 85 | if (enc == DW_EH_PE_omit) { 86 | *value = 0; 87 | return 0; 88 | } 89 | 90 | if (enc == DW_EH_PE_absptr) { 91 | *value = *(uint64_t *)data; 92 | return 8; 93 | } 94 | 95 | switch (enc & 0xf) 96 | { 97 | case DW_EH_PE_udata2: 98 | number = *(uint16_t *)data; 99 | size = 2; 100 | break; 101 | 102 | case DW_EH_PE_sdata2: 103 | number = *(int16_t *)data; 104 | size = 2; 105 | break; 106 | 107 | case DW_EH_PE_udata4: 108 | number = *(uint32_t *)data; 109 | size = 4; 110 | break; 111 | 112 | case DW_EH_PE_sdata4: 113 | number = *(int32_t *)data; 114 | size = 4; 115 | break; 116 | 117 | case DW_EH_PE_udata8: 118 | number = *(uint64_t *)data; 119 | size = 8; 120 | break; 121 | 122 | case DW_EH_PE_sdata8: 123 | number = *(int64_t *)data; 124 | size = 8; 125 | break; 126 | 127 | default: 128 | fprintf(stderr, "unsupported encoding in " 129 | ".eh_frame_hdr: %d\n", (int)enc); 130 | return -1; 131 | } 132 | 133 | switch (enc & 0xf0) { 134 | case DW_EH_PE_absptr: 135 | *value = number; 136 | break; 137 | 138 | case DW_EH_PE_pcrel: 139 | *value = cur + number; 140 | break; 141 | 142 | default: 143 | return -1; 144 | } 145 | 146 | return size; 147 | } 148 | 149 | /* 150 | * parse contents of .eh_frame_hdr 151 | */ 152 | static int parse_eh_frame_hdr(char *data, size_t pos, 153 | uint64_t *table_data, uint64_t *fde_count) 154 | { 155 | char version, eh_frame_ptr_enc, fde_count_enc; 156 | ssize_t size; 157 | uint64_t eh_frame_ptr; 158 | 159 | version = data[0]; 160 | eh_frame_ptr_enc = data[1]; 161 | fde_count_enc = data[2]; 162 | data += 4; 163 | pos += 4; 164 | 165 | if (version != 1) { 166 | fprintf(stderr, "unknown .ehf_frame_hdr version %d\n", version); 167 | return -1; 168 | } 169 | 170 | size = dw_get_value(data, eh_frame_ptr_enc, pos, &eh_frame_ptr); 171 | if (size < 0) 172 | return -1; 173 | pos += size; 174 | data += size; 175 | 176 | size = dw_get_value(data, fde_count_enc, pos, fde_count); 177 | if (size < 0) 178 | return -1; 179 | pos += size; 180 | *table_data = pos; 181 | 182 | return 0; 183 | } 184 | 185 | static Elf *elf_start(int fd, char *image, uint64_t size) 186 | { 187 | Elf *elf; 188 | 189 | if (fd > 0) { 190 | if ((elf = elf_begin(fd, TBSTACK_ELF_C_READ, NULL)) == NULL) 191 | fprintf(stderr, "elf_begin: %s\n", elf_errmsg(elf_errno())); 192 | } else { 193 | if ((elf = elf_memory(image, size)) == NULL) 194 | fprintf(stderr, "elf_memory: %s\n", elf_errmsg(elf_errno())); 195 | } 196 | 197 | return elf; 198 | } 199 | 200 | /* 201 | * find section .eh_frame_hdr in ELF binary 202 | */ 203 | static int find_eh_frame_hdr(int fd, char *image, uint64_t size, 204 | uint64_t *table_data, uint64_t *segbase, uint64_t *fde_count) 205 | { 206 | Elf *elf; 207 | GElf_Ehdr ehdr; 208 | Elf_Scn *scn = NULL; 209 | GElf_Shdr shdr; 210 | uint64_t offset = 0; 211 | 212 | if ((elf = elf_start(fd, image, size)) == NULL) 213 | return -1; 214 | 215 | if (gelf_getehdr(elf, &ehdr) == NULL) { 216 | fprintf(stderr, "elf_getehdr: %s\n", elf_errmsg(elf_errno())); 217 | goto elf_section_offset_end; 218 | } 219 | 220 | while ((scn = elf_nextscn(elf, scn)) != NULL) { 221 | char *str; 222 | 223 | if (gelf_getshdr(scn, &shdr) == NULL) { 224 | fprintf(stderr, "elf_getshdr: %s\n", elf_errmsg(elf_errno())); 225 | break; 226 | } 227 | 228 | str = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name); 229 | if (str != NULL && !strcmp(str, ".eh_frame_hdr")) { 230 | Elf_Data *data = NULL; 231 | 232 | if ((data = elf_getdata(scn, data)) == NULL) { 233 | fprintf(stderr, "elf_getdata: %s\n", elf_errmsg(elf_errno())); 234 | break; 235 | } 236 | 237 | offset = *segbase = shdr.sh_offset; 238 | parse_eh_frame_hdr(data->d_buf, offset, table_data, fde_count); 239 | break; 240 | } 241 | } 242 | 243 | if (!offset) 244 | goto elf_section_offset_end; 245 | 246 | elf_section_offset_end: 247 | elf_end(elf); 248 | return (offset ? 0 : -1); 249 | } 250 | 251 | /* 252 | * dynamic array of symbols 253 | */ 254 | struct symbols 255 | { 256 | GElf_Sym *s_data; 257 | size_t s_size; 258 | size_t s_cap; 259 | }; 260 | 261 | /* 262 | * add a symbol to array 263 | */ 264 | static void push_symbol(struct symbols *array, const GElf_Sym *s) 265 | { 266 | ++array->s_size; 267 | if (array->s_size > array->s_cap) { 268 | GElf_Sym *new_data; 269 | array->s_cap <<= 1; 270 | new_data = malloc(sizeof(GElf_Sym) * array->s_cap); 271 | memcpy(new_data, array->s_data, sizeof(GElf_Sym) * (array->s_size-1)); 272 | free(array->s_data); 273 | array->s_data = new_data; 274 | } 275 | memcpy(array->s_data + (array->s_size-1), s, sizeof(GElf_Sym)); 276 | } 277 | 278 | /* 279 | * symbol comparison function for qsort 280 | */ 281 | static int sym_compar(const void *v1, const void *v2) 282 | { 283 | const GElf_Sym *s1 = v1; 284 | const GElf_Sym *s2 = v2; 285 | 286 | if (s1->st_value < s2->st_value) 287 | return -1; 288 | if (s1->st_value > s2->st_value) 289 | return 1; 290 | return 0; 291 | } 292 | 293 | /* 294 | * get function name 295 | * 296 | * fd: open binary 297 | * load: mmap address 298 | * offset: file offset 299 | * addr: ip value 300 | * off: offset within the function 301 | */ 302 | static char *proc_name(int fd, char *image, size_t size, uint64_t load, 303 | uint64_t offset, uint64_t addr, unw_word_t *off) 304 | { 305 | Elf *elf; 306 | Elf_Scn *scn = NULL; 307 | char *str = NULL; 308 | int rc = 0; 309 | struct symbols all; 310 | size_t pnum, i; 311 | uint64_t vaddr = 0; 312 | 313 | /* 314 | * open ELF handle 315 | */ 316 | if ((elf = elf_start(fd, image, size)) == NULL) 317 | return NULL; 318 | 319 | /* 320 | * initialize dynamic array 321 | */ 322 | all.s_cap = 64; 323 | all.s_size = 0; 324 | all.s_data = malloc(all.s_cap * sizeof(GElf_Sym)); 325 | 326 | if (elf_getphdrnum (elf, &pnum)) 327 | goto proc_name_end; 328 | 329 | for (i = 0; i < pnum; ++i) { 330 | GElf_Phdr phdr; 331 | if (gelf_getphdr(elf, i, &phdr) == NULL) 332 | goto proc_name_end; 333 | if (phdr.p_type != PT_LOAD) 334 | continue; 335 | if (phdr.p_flags != (PF_X | PF_R)) 336 | continue; 337 | if ((phdr.p_offset & ~(phdr.p_align - 1)) != offset) 338 | continue; 339 | vaddr = phdr.p_vaddr; 340 | break; 341 | } 342 | 343 | /* 344 | * adjust address 345 | */ 346 | addr -= load; 347 | addr += offset; 348 | 349 | /* 350 | * search symtab or dynsym section 351 | */ 352 | while ((scn = elf_nextscn(elf, scn)) != NULL) { 353 | GElf_Shdr shdr; 354 | 355 | if (gelf_getshdr(scn, &shdr) == NULL) { 356 | fprintf(stderr, "elf_nextscn: %s\n", elf_errmsg(elf_errno())); 357 | goto proc_name_end; 358 | } 359 | 360 | if (shdr.sh_type == SHT_DYNSYM || shdr.sh_type == SHT_SYMTAB) { 361 | Elf_Data *data = NULL; 362 | int symbol_count; 363 | 364 | if ((data = elf_getdata(scn, data)) == NULL) { 365 | fprintf(stderr, "elf_getdata: %s\n", elf_errmsg(elf_errno())); 366 | goto proc_name_end; 367 | } 368 | 369 | symbol_count = shdr.sh_size / shdr.sh_entsize; 370 | for (i = 0; i < (size_t)symbol_count; ++i) { 371 | GElf_Sym s; 372 | 373 | if (gelf_getsym(data, i, &s) == NULL) { 374 | fprintf(stderr, "elf_getsym: %s\n", 375 | elf_errmsg(elf_errno())); 376 | rc = -1; 377 | goto proc_name_end; 378 | } 379 | 380 | if (ELF64_ST_TYPE(s.st_info) != STT_FUNC) 381 | continue; 382 | 383 | /* 384 | * adjust symbol value 385 | */ 386 | s.st_value -= vaddr; 387 | 388 | /* 389 | * exact match 390 | */ 391 | if (addr >= s.st_value && addr < (s.st_value + s.st_size)) { 392 | str = elf_strptr(elf, shdr.sh_link, s.st_name); 393 | if (str == NULL) { 394 | fprintf(stderr, "elf_strptr #1: %s\n", 395 | elf_errmsg(elf_errno())); 396 | rc = -1; 397 | goto proc_name_end; 398 | } 399 | str = strdup(str); 400 | *off = addr - s.st_value; 401 | goto proc_name_end; 402 | } 403 | 404 | /* store section link */ 405 | s.st_shndx = shdr.sh_link; 406 | /* 407 | * save symbol in array 408 | */ 409 | push_symbol(&all, &s); 410 | } 411 | } 412 | } 413 | 414 | /* 415 | * sometimes function symbols have zero size but contain the code. 416 | * common example is _start on most systems. 417 | * in this case we try to find two adjacent symbols with first 418 | * one of zero size 419 | */ 420 | if (!rc && str == NULL) { 421 | qsort(all.s_data, all.s_size, sizeof(GElf_Sym), sym_compar); 422 | for (i = 0; i < (all.s_size-1); ++i) { 423 | const GElf_Sym *cur = all.s_data + i; 424 | const GElf_Sym *next = all.s_data + i + 1; 425 | if (cur->st_size == 0) { 426 | if (cur->st_value <= addr && addr < next->st_value) { 427 | str = elf_strptr(elf, cur->st_shndx, cur->st_name); 428 | if (str == NULL) { 429 | fprintf(stderr, "elf_strptr #2: %s\n", 430 | elf_errmsg(elf_errno())); 431 | rc = -1; 432 | goto proc_name_end; 433 | } 434 | str = strdup(str); 435 | *off = addr - cur->st_value; 436 | goto proc_name_end; 437 | } 438 | } 439 | } 440 | } 441 | 442 | proc_name_end: 443 | free(all.s_data); 444 | elf_end(elf); 445 | return str; 446 | } 447 | 448 | /* 449 | * get mmapped ELF image info 450 | */ 451 | static int get_elf_image_info(struct mem_region *region, 452 | char **elf_image, uint64_t *elf_length, uintptr_t ip) 453 | { 454 | struct mem_data_chunk *chunk; 455 | 456 | if ((chunk = mem_region_find_data_chunk(region, (void *)ip)) == NULL) 457 | return -1; 458 | 459 | if (chunk->data == NULL) 460 | return -1; 461 | 462 | *elf_image = chunk->data; 463 | *elf_length = chunk->length; 464 | 465 | return 0; 466 | } 467 | 468 | #ifdef HAVE_DWARF 469 | static int elf_is_exec(int fd, char *image, uint64_t size) 470 | { 471 | Elf *elf; 472 | GElf_Ehdr ehdr; 473 | int ret = 0; 474 | 475 | if ((elf = elf_start(fd, image, size)) == NULL) 476 | return 0; 477 | 478 | if (gelf_getehdr(elf, &ehdr) == NULL) { 479 | fprintf(stderr, "elf_getehdr: %s\n", elf_errmsg(elf_errno())); 480 | goto elf_is_exec_end; 481 | } 482 | 483 | ret = ehdr.e_type == ET_EXEC; 484 | 485 | elf_is_exec_end: 486 | elf_end(elf); 487 | 488 | return ret; 489 | } 490 | 491 | static int elf_get_link_base(int fd, char *image, uint64_t size, 492 | uint64_t *link_base) 493 | { 494 | Elf *elf; 495 | GElf_Ehdr ehdr; 496 | GElf_Phdr phdr; 497 | int idx=0; 498 | uint64_t offset = UINT64_MAX; 499 | 500 | if ((elf = elf_start(fd, image, size)) == NULL) 501 | return -1; 502 | 503 | if (gelf_getehdr(elf, &ehdr) == NULL) { 504 | fprintf(stderr, "elf_getehdr: %s\n", elf_errmsg(elf_errno())); 505 | goto elf_section_offset_end; 506 | } 507 | 508 | /* Get the vaddr of the segment with 0 offset. This is the link base of 509 | * the shared object. */ 510 | while (gelf_getphdr(elf, idx, &phdr) && phdr.p_type != PT_NULL) { 511 | if (phdr.p_type != PT_LOAD) 512 | goto next; 513 | 514 | if (phdr.p_offset) 515 | goto next; 516 | 517 | offset = phdr.p_vaddr; 518 | break; 519 | 520 | next: 521 | idx++; 522 | } 523 | 524 | *link_base = offset; 525 | elf_end(elf); 526 | return 0; 527 | 528 | elf_section_offset_end: 529 | elf_end(elf); 530 | return -1; 531 | } 532 | 533 | #endif 534 | 535 | /* 536 | * find unwind info for function 537 | */ 538 | static int find_proc_info(unw_addr_space_t as, unw_word_t ip, 539 | unw_proc_info_t *pip, int need_unwind_info, void *arg) 540 | { 541 | struct snapshot *snap = arg; 542 | struct mem_region *region; 543 | char *elf_image = NULL; 544 | uint64_t elf_length = 0; 545 | unw_dyn_info_t di; 546 | uint64_t table_data = 0; 547 | uint64_t segbase, fde_count; 548 | int rc = -UNW_EINVAL; 549 | 550 | if (ip == 0) 551 | return -UNW_ENOINFO; 552 | 553 | if ((region = mem_map_get_file_region(snap->map, (void *)ip)) == NULL) 554 | return rc; 555 | 556 | if (region->fd < 0 && region->type != MEM_REGION_TYPE_VDSO 557 | && region->type != MEM_REGION_TYPE_VSYSCALL) 558 | return rc; 559 | 560 | if (region->fd < 0 && 561 | get_elf_image_info(region, &elf_image, &elf_length, ip) < 0) 562 | return rc; 563 | 564 | memset(&di, 0, sizeof(di)); 565 | 566 | if (!find_eh_frame_hdr(region->fd, elf_image, elf_length, 567 | &table_data, &segbase, &fde_count)) { 568 | 569 | di.format = UNW_INFO_FORMAT_REMOTE_TABLE; 570 | di.start_ip = (unw_word_t)region->start; 571 | di.end_ip = (unw_word_t)region->start + region->length; 572 | di.u.rti.segbase = (unw_word_t)(region->start - region->offset) + segbase; 573 | di.u.rti.table_data = (unw_word_t)(region->start - region->offset) + table_data; 574 | di.u.rti.table_len = 575 | fde_count * sizeof(uint32_t) * 2 / sizeof(unw_word_t); 576 | 577 | rc = search_unwind_table(as, ip, &di, pip, need_unwind_info, arg); 578 | } 579 | 580 | if (rc == 0) 581 | return rc; 582 | 583 | #ifdef HAVE_DWARF 584 | unw_word_t base = 0; 585 | if (!elf_is_exec(region->fd, elf_image, elf_length)) { 586 | uint64_t link_base; 587 | if (elf_get_link_base(region->fd, elf_image, elf_length, &link_base)) 588 | return -UNW_EINVAL; 589 | base = (uintptr_t)region->start - link_base; 590 | } 591 | 592 | if (dwarf_find_debug_frame(0, &di, ip, base, region->path, 593 | (unw_word_t) region->start, 594 | (unw_word_t) region->start + region->length)) 595 | return search_unwind_table(as, ip, &di, pip, need_unwind_info, arg); 596 | #endif 597 | 598 | return rc; 599 | } 600 | 601 | /* 602 | * put_unwind_info: do nothing 603 | */ 604 | static void put_unwind_info(unw_addr_space_t as, 605 | unw_proc_info_t *pip, void *arg) 606 | { 607 | (void) as; 608 | (void) pip; 609 | (void) arg; 610 | } 611 | 612 | /* 613 | * not used 614 | */ 615 | static int get_dyn_info_list_addr(unw_addr_space_t as, 616 | unw_word_t *dilap, void *arg) 617 | { 618 | (void) as; 619 | (void) dilap; 620 | (void) arg; 621 | return -UNW_ENOINFO; 622 | } 623 | 624 | /* 625 | * read a word from memory. we use mem_map for that 626 | */ 627 | static int access_mem(unw_addr_space_t as, unw_word_t addr, 628 | unw_word_t *valp, int write, void *arg) 629 | { 630 | struct snapshot *snap = arg; 631 | 632 | (void) as; 633 | 634 | if (write) { 635 | fprintf(stderr, "access_mem: requested write, rejecting\n"); 636 | return -UNW_EINVAL; 637 | } 638 | 639 | return mem_map_read_word(snap->map, (void *)(uintptr_t)addr, valp); 640 | } 641 | 642 | /* 643 | * get register value 644 | */ 645 | static int access_reg(unw_addr_space_t as, unw_regnum_t reg, 646 | unw_word_t *val, int write, void *arg) 647 | { 648 | struct snapshot *snap = arg; 649 | 650 | (void) as; 651 | 652 | if (write) { 653 | fprintf(stderr, "requested to write into register\n"); 654 | return -UNW_EINVAL; 655 | } 656 | 657 | switch (reg) { 658 | #if defined(UNW_TARGET_AARCH64) 659 | case UNW_AARCH64_X0 ... UNW_AARCH64_X30: 660 | /* 661 | * Currently this enum directly maps to the index so this is a no-op. 662 | * Assert just in case. 663 | */ 664 | reg -= UNW_AARCH64_X0; 665 | assert(reg>= 0 && reg <= 30); 666 | *val = snap->regs[snap->cur_thr].regs[reg]; 667 | break; 668 | case UNW_AARCH64_SP: 669 | *val = snap->regs[snap->cur_thr].sp; 670 | break; 671 | case UNW_AARCH64_PC: 672 | *val = snap->regs[snap->cur_thr].pc; 673 | break; 674 | case UNW_AARCH64_PSTATE: 675 | *val = snap->regs[snap->cur_thr].pstate; 676 | break; 677 | #elif defined(UNW_TARGET_ARM) 678 | case UNW_ARM_R0 ... UNW_ARM_R15: 679 | /* 680 | * Currently this enum directly maps to the index so this is a no-op. 681 | * Assert just in case. 682 | */ 683 | reg -= UNW_ARM_R0; 684 | assert(reg >= 0 && reg <= 15); 685 | *val = snap->regs[snap->cur_thr].uregs[reg]; 686 | break; 687 | #elif defined(UNW_TARGET_X86) 688 | case UNW_X86_EAX: 689 | *val = snap->regs[snap->cur_thr].eax; 690 | break; 691 | case UNW_X86_EDX: 692 | *val = snap->regs[snap->cur_thr].edx; 693 | break; 694 | case UNW_X86_ECX: 695 | *val = snap->regs[snap->cur_thr].ecx; 696 | break; 697 | case UNW_X86_EBX: 698 | *val = snap->regs[snap->cur_thr].ebx; 699 | break; 700 | case UNW_X86_ESI: 701 | *val = snap->regs[snap->cur_thr].esi; 702 | break; 703 | case UNW_X86_EDI: 704 | *val = snap->regs[snap->cur_thr].edi; 705 | break; 706 | case UNW_X86_EBP: 707 | *val = snap->regs[snap->cur_thr].ebp; 708 | break; 709 | case UNW_X86_ESP: 710 | *val = snap->regs[snap->cur_thr].esp; 711 | break; 712 | case UNW_X86_EIP: 713 | *val = snap->regs[snap->cur_thr].eip; 714 | break; 715 | #elif defined(UNW_TARGET_X86_64) 716 | case UNW_X86_64_RAX: 717 | *val = snap->regs[snap->cur_thr].rax; 718 | break; 719 | case UNW_X86_64_RDX: 720 | *val = snap->regs[snap->cur_thr].rdx; 721 | break; 722 | case UNW_X86_64_RCX: 723 | *val = snap->regs[snap->cur_thr].rcx; 724 | break; 725 | case UNW_X86_64_RBX: 726 | *val = snap->regs[snap->cur_thr].rbx; 727 | break; 728 | case UNW_X86_64_RSI: 729 | *val = snap->regs[snap->cur_thr].rsi; 730 | break; 731 | case UNW_X86_64_RDI: 732 | *val = snap->regs[snap->cur_thr].rdi; 733 | break; 734 | case UNW_X86_64_RBP: 735 | *val = snap->regs[snap->cur_thr].rbp; 736 | break; 737 | case UNW_X86_64_RSP: 738 | *val = snap->regs[snap->cur_thr].rsp; 739 | break; 740 | case UNW_X86_64_R8: 741 | *val = snap->regs[snap->cur_thr].r8; 742 | break; 743 | case UNW_X86_64_R9: 744 | *val = snap->regs[snap->cur_thr].r9; 745 | break; 746 | case UNW_X86_64_R10: 747 | *val = snap->regs[snap->cur_thr].r10; 748 | break; 749 | case UNW_X86_64_R11: 750 | *val = snap->regs[snap->cur_thr].r11; 751 | break; 752 | case UNW_X86_64_R12: 753 | *val = snap->regs[snap->cur_thr].r12; 754 | break; 755 | case UNW_X86_64_R13: 756 | *val = snap->regs[snap->cur_thr].r13; 757 | break; 758 | case UNW_X86_64_R14: 759 | *val = snap->regs[snap->cur_thr].r14; 760 | break; 761 | case UNW_X86_64_R15: 762 | *val = snap->regs[snap->cur_thr].r15; 763 | break; 764 | case UNW_X86_64_RIP: 765 | *val = snap->regs[snap->cur_thr].rip; 766 | break; 767 | #else 768 | #error Need porting to this arch 769 | #endif 770 | default: 771 | return -UNW_EBADREG; 772 | } 773 | 774 | return 0; 775 | } 776 | 777 | /* 778 | * floating point registers are not used 779 | */ 780 | static int access_fpreg(unw_addr_space_t as, unw_regnum_t regnum, 781 | unw_fpreg_t *fpvalp, int write, void *arg) 782 | { 783 | (void) as; 784 | (void) regnum; 785 | (void) fpvalp; 786 | (void) write; 787 | (void) arg; 788 | 789 | fprintf(stderr, "access_fpreg is not supported\n"); 790 | return -UNW_ENOINFO; 791 | } 792 | 793 | /* 794 | * not used 795 | */ 796 | static int resume(unw_addr_space_t as, unw_cursor_t *cp, void *arg) 797 | { 798 | (void) as; 799 | (void) cp; 800 | (void) arg; 801 | 802 | fprintf(stderr, "resume is not supported\n"); 803 | return -UNW_ENOINFO; 804 | } 805 | 806 | /* 807 | * get function name callback 808 | */ 809 | static int get_proc_name(unw_addr_space_t as, unw_word_t addr, char *bufp, 810 | size_t buf_len, unw_word_t *offp, void *arg) 811 | { 812 | struct snapshot *snap = arg; 813 | struct mem_region *region; 814 | char *name = NULL; 815 | 816 | (void) as; 817 | 818 | if (addr == 0) 819 | return -UNW_ENOINFO; 820 | 821 | if ((region = mem_map_get_file_region(snap->map, (void *)addr)) == NULL) 822 | return -UNW_ENOINFO; 823 | 824 | if (region->fd < 0 && region->type == MEM_REGION_TYPE_DELETED) { 825 | const char *base = basename(region->path); 826 | snprintf(bufp, buf_len, "?? (%s is deleted)", base); 827 | *offp = 0; 828 | return 0; 829 | } else if (region->type == MEM_REGION_TYPE_MMAP || 830 | region->type == MEM_REGION_TYPE_VDSO || 831 | region->type == MEM_REGION_TYPE_VSYSCALL) { 832 | char *elf_image = NULL; 833 | uint64_t elf_length = 0; 834 | 835 | if (region->fd < 0 && 836 | get_elf_image_info(region, &elf_image, &elf_length, addr) < 0) 837 | return -UNW_ENOINFO; 838 | 839 | name = proc_name(region->fd, elf_image, elf_length, 840 | (uint64_t)(uintptr_t)region->start, region->offset, addr, offp); 841 | } 842 | 843 | if (name == NULL) { 844 | /* 845 | * if name cannot be resolved, print binary file name or region type 846 | */ 847 | if (region->type == MEM_REGION_TYPE_MMAP) { 848 | const char *base = basename(region->path); 849 | snprintf(bufp, buf_len, "?? (%s)", base); 850 | } else { 851 | snprintf(bufp, buf_len, "?? [%s]", 852 | str_mem_region_type(region->type)); 853 | } 854 | *offp = 0; 855 | return 0; 856 | } 857 | 858 | strncpy(bufp, name, buf_len); 859 | free(name); 860 | 861 | return 0; 862 | } 863 | 864 | /* 865 | * libunwind remote callbacks 866 | */ 867 | unw_accessors_t snapshot_addr_space_accessors = { 868 | .find_proc_info = find_proc_info, 869 | .put_unwind_info = put_unwind_info, 870 | .get_dyn_info_list_addr = get_dyn_info_list_addr, 871 | .access_mem = access_mem, 872 | .access_reg = access_reg, 873 | .access_fpreg = access_fpreg, 874 | .resume = resume, 875 | .get_proc_name = get_proc_name, 876 | }; 877 | --------------------------------------------------------------------------------