Elliott Hughes | 03a418e | 2018-06-15 13:11:40 -0700 | [diff] [blame] | 1 | /* |
| 2 | * This file is based on a patch submitted by Mark Wielaard <mjw@redhat.com> |
| 3 | * to ltrace project: |
| 4 | * https://anonscm.debian.org/cgit/collab-maint/ltrace.git/commit/?id=dfefa9f057857735a073ea655f5cb34351032c8e |
| 5 | * |
| 6 | * It was re-licensed for strace by the original author: |
| 7 | * https://lists.strace.io/pipermail/strace-devel/2018-March/008063.html |
| 8 | * |
| 9 | * Copyright (c) 2014-2018 Mark Wielaard <mjw@redhat.com> |
| 10 | * Copyright (c) 2018 Masatake YAMATO <yamato@redhat.com> |
| 11 | * Copyright (c) 2018 The strace developers. |
| 12 | * All rights reserved. |
| 13 | * |
| 14 | * Redistribution and use in source and binary forms, with or without |
| 15 | * modification, are permitted provided that the following conditions |
| 16 | * are met: |
| 17 | * 1. Redistributions of source code must retain the above copyright |
| 18 | * notice, this list of conditions and the following disclaimer. |
| 19 | * 2. Redistributions in binary form must reproduce the above copyright |
| 20 | * notice, this list of conditions and the following disclaimer in the |
| 21 | * documentation and/or other materials provided with the distribution. |
| 22 | * 3. The name of the author may not be used to endorse or promote products |
| 23 | * derived from this software without specific prior written permission. |
| 24 | * |
| 25 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| 26 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| 27 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| 28 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| 30 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 31 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 32 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 33 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 34 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 35 | */ |
| 36 | |
| 37 | #include "defs.h" |
| 38 | #include "unwind.h" |
| 39 | #include "mmap_notify.h" |
| 40 | #include <elfutils/libdwfl.h> |
| 41 | |
| 42 | struct ctx { |
| 43 | Dwfl *dwfl; |
| 44 | unsigned int last_proc_updating; |
| 45 | }; |
| 46 | |
| 47 | static unsigned int mapping_generation; |
| 48 | |
| 49 | static void |
| 50 | update_mapping_generation(struct tcb *tcp, void *unused) |
| 51 | { |
| 52 | mapping_generation++; |
| 53 | } |
| 54 | |
| 55 | static void |
| 56 | init(void) |
| 57 | { |
| 58 | mmap_notify_register_client(update_mapping_generation, NULL); |
| 59 | } |
| 60 | |
| 61 | static void * |
| 62 | tcb_init(struct tcb *tcp) |
| 63 | { |
| 64 | static const Dwfl_Callbacks proc_callbacks = { |
| 65 | .find_elf = dwfl_linux_proc_find_elf, |
| 66 | .find_debuginfo = dwfl_standard_find_debuginfo |
| 67 | }; |
| 68 | |
| 69 | Dwfl *dwfl = dwfl_begin(&proc_callbacks); |
| 70 | if (dwfl == NULL) { |
| 71 | error_msg("dwfl_begin: %s", dwfl_errmsg(-1)); |
| 72 | return NULL; |
| 73 | } |
| 74 | |
| 75 | int r = dwfl_linux_proc_attach(dwfl, tcp->pid, true); |
| 76 | if (r) { |
| 77 | const char *msg = NULL; |
| 78 | |
| 79 | if (r < 0) |
| 80 | msg = dwfl_errmsg(-1); |
| 81 | else if (r > 0) |
| 82 | msg = strerror(r); |
| 83 | |
| 84 | error_msg("dwfl_linux_proc_attach returned an error" |
| 85 | " for process %d: %s", tcp->pid, msg); |
| 86 | dwfl_end(dwfl); |
| 87 | return NULL; |
| 88 | } |
| 89 | |
| 90 | struct ctx *ctx = xmalloc(sizeof(*ctx)); |
| 91 | ctx->dwfl = dwfl; |
| 92 | ctx->last_proc_updating = 0; |
| 93 | return ctx; |
| 94 | } |
| 95 | |
| 96 | static void |
| 97 | tcb_fin(struct tcb *tcp) |
| 98 | { |
| 99 | struct ctx *ctx = tcp->unwind_ctx; |
| 100 | if (ctx) { |
| 101 | dwfl_end(ctx->dwfl); |
| 102 | free(ctx); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | static void |
| 107 | flush_cache_maybe(struct tcb *tcp) |
| 108 | { |
| 109 | struct ctx *ctx = tcp->unwind_ctx; |
| 110 | if (!ctx) |
| 111 | return; |
| 112 | |
| 113 | if (ctx->last_proc_updating == mapping_generation) |
| 114 | return; |
| 115 | |
| 116 | int r = dwfl_linux_proc_report(ctx->dwfl, tcp->pid); |
| 117 | |
| 118 | if (r < 0) |
| 119 | error_msg("dwfl_linux_proc_report returned an error" |
| 120 | " for pid %d: %s", tcp->pid, dwfl_errmsg(-1)); |
| 121 | else if (r > 0) |
| 122 | error_msg("dwfl_linux_proc_report returned an error" |
| 123 | " for pid %d", tcp->pid); |
| 124 | else if (dwfl_report_end(ctx->dwfl, NULL, NULL) != 0) |
| 125 | error_msg("dwfl_report_end returned an error" |
| 126 | " for pid %d: %s", tcp->pid, dwfl_errmsg(-1)); |
| 127 | |
| 128 | ctx->last_proc_updating = mapping_generation; |
| 129 | } |
| 130 | |
| 131 | struct frame_user_data { |
| 132 | unwind_call_action_fn call_action; |
| 133 | unwind_error_action_fn error_action; |
| 134 | void *data; |
| 135 | int stack_depth; |
| 136 | }; |
| 137 | |
| 138 | static int |
| 139 | frame_callback(Dwfl_Frame *state, void *arg) |
| 140 | { |
| 141 | struct frame_user_data *user_data = arg; |
| 142 | Dwarf_Addr pc; |
| 143 | bool isactivation; |
| 144 | |
| 145 | if (!dwfl_frame_pc(state, &pc, &isactivation)) { |
| 146 | /* Propagate the error to the caller. */ |
| 147 | return -1; |
| 148 | } |
| 149 | |
| 150 | if (!isactivation) |
| 151 | pc--; |
| 152 | |
| 153 | Dwfl *dwfl = dwfl_thread_dwfl(dwfl_frame_thread(state)); |
| 154 | Dwfl_Module *mod = dwfl_addrmodule(dwfl, pc); |
| 155 | GElf_Off off = 0; |
| 156 | |
| 157 | if (mod != NULL) { |
| 158 | const char *modname = NULL; |
| 159 | const char *symname = NULL; |
| 160 | GElf_Sym sym; |
| 161 | Dwarf_Addr true_offset = pc; |
| 162 | |
| 163 | modname = dwfl_module_info(mod, NULL, NULL, NULL, NULL, |
| 164 | NULL, NULL, NULL); |
| 165 | symname = dwfl_module_addrinfo(mod, pc, &off, &sym, |
| 166 | NULL, NULL, NULL); |
| 167 | dwfl_module_relocate_address(mod, &true_offset); |
| 168 | user_data->call_action(user_data->data, modname, symname, |
| 169 | off, true_offset); |
| 170 | } |
| 171 | /* Max number of frames to print reached? */ |
| 172 | if (user_data->stack_depth-- == 0) |
| 173 | return DWARF_CB_ABORT; |
| 174 | |
| 175 | return DWARF_CB_OK; |
| 176 | } |
| 177 | |
| 178 | static void |
| 179 | tcb_walk(struct tcb *tcp, |
| 180 | unwind_call_action_fn call_action, |
| 181 | unwind_error_action_fn error_action, |
| 182 | void *data) |
| 183 | { |
| 184 | struct ctx *ctx = tcp->unwind_ctx; |
| 185 | if (!ctx) |
| 186 | return; |
| 187 | |
| 188 | struct frame_user_data user_data = { |
| 189 | .call_action = call_action, |
| 190 | .error_action = error_action, |
| 191 | .data = data, |
| 192 | .stack_depth = 256, |
| 193 | }; |
| 194 | |
| 195 | flush_cache_maybe(tcp); |
| 196 | |
| 197 | int r = dwfl_getthread_frames(ctx->dwfl, tcp->pid, frame_callback, |
| 198 | &user_data); |
| 199 | if (r) |
| 200 | error_action(data, |
| 201 | r < 0 ? dwfl_errmsg(-1) : "too many stack frames", |
| 202 | 0); |
| 203 | } |
| 204 | |
| 205 | const struct unwind_unwinder_t unwinder = { |
| 206 | .name = "libdw", |
| 207 | .init = init, |
| 208 | .tcb_init = tcb_init, |
| 209 | .tcb_fin = tcb_fin, |
| 210 | .tcb_walk = tcb_walk, |
| 211 | }; |