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-2021 The strace developers.
12 * All rights reserved.
13 *
14 * SPDX-License-Identifier: LGPL-2.1-or-later
15 */
16
17 #include "defs.h"
18 #include "unwind.h"
19 #include "mmap_notify.h"
20 #include "static_assert.h"
21 #include <elfutils/libdwfl.h>
22
23 #define STRACE_UW_CACHE_SIZE 2048
24 #define STRACE_UW_CACHE_ASSOC 32
25 static_assert(STRACE_UW_CACHE_SIZE % STRACE_UW_CACHE_ASSOC == 0,
26 "STRACE_UW_CACHE_SIZE % STRACE_UW_CACHE_ASSOC != 0");
27
28 struct cache_entry {
29 /* key */
30 Dwarf_Addr pc;
31 unsigned long long generation;
32
33 /* value */
34 const char *modname;
35 const char *symname;
36 GElf_Off off;
37 Dwarf_Addr true_offset;
38
39 /* replacement */
40 unsigned long long last_use;
41 };
42
43 struct ctx {
44 Dwfl *dwfl;
45 unsigned long long last_proc_updating;
46 struct cache_entry cache[STRACE_UW_CACHE_SIZE];
47 };
48
49 static unsigned long long mapping_generation = 1;
50 static unsigned long long uwcache_clock;
51
52 static void
53 update_mapping_generation(struct tcb *tcp, void *unused)
54 {
55 mapping_generation++;
56 }
57
58 static void
59 init(void)
60 {
61 mmap_notify_register_client(update_mapping_generation, NULL);
62 }
63
64 static void *
65 tcb_init(struct tcb *tcp)
66 {
67 static const Dwfl_Callbacks proc_callbacks = {
68 .find_elf = dwfl_linux_proc_find_elf,
69 .find_debuginfo = dwfl_standard_find_debuginfo
70 };
71
72 Dwfl *dwfl = dwfl_begin(&proc_callbacks);
73 if (dwfl == NULL) {
74 error_msg("dwfl_begin: %s", dwfl_errmsg(-1));
75 return NULL;
76 }
77
78 int r = dwfl_linux_proc_attach(dwfl, tcp->pid, true);
79 if (r) {
80 const char *msg = NULL;
81
82 if (r < 0)
83 msg = dwfl_errmsg(-1);
84 else if (r > 0)
85 msg = strerror(r);
86
87 error_msg("dwfl_linux_proc_attach returned an error"
88 " for process %d: %s", tcp->pid, msg);
89 dwfl_end(dwfl);
90 return NULL;
91 }
92
93 struct ctx *ctx = xmalloc(sizeof(*ctx));
94 ctx->dwfl = dwfl;
95 ctx->last_proc_updating = mapping_generation - 1;
96 memset(ctx->cache, 0, sizeof(ctx->cache));
97 return ctx;
98 }
99
100 static void
101 tcb_fin(struct tcb *tcp)
102 {
103 struct ctx *ctx = tcp->unwind_ctx;
104 if (ctx) {
105 dwfl_end(ctx->dwfl);
106 free(ctx);
107 }
108 }
109
110 static void
111 flush_cache_maybe(struct tcb *tcp)
112 {
113 struct ctx *ctx = tcp->unwind_ctx;
114 if (!ctx)
115 return;
116
117 if (ctx->last_proc_updating == mapping_generation)
118 return;
119
120 int r = dwfl_linux_proc_report(ctx->dwfl, tcp->pid);
121
122 if (r < 0)
123 error_msg("dwfl_linux_proc_report returned an error"
124 " for pid %d: %s", tcp->pid, dwfl_errmsg(-1));
125 else if (r > 0)
126 error_msg("dwfl_linux_proc_report returned an error"
127 " for pid %d", tcp->pid);
128 else if (dwfl_report_end(ctx->dwfl, NULL, NULL) != 0)
129 error_msg("dwfl_report_end returned an error"
130 " for pid %d: %s", tcp->pid, dwfl_errmsg(-1));
131
132 ctx->last_proc_updating = mapping_generation;
133 }
134
135 struct frame_user_data {
136 unwind_call_action_fn call_action;
137 unwind_error_action_fn error_action;
138 void *data;
139 int stack_depth;
140 struct ctx *ctx;
141 };
142
143 static bool
144 find_bucket(struct ctx *ctx, Dwarf_Addr pc, struct cache_entry **res) {
145 unsigned int idx = pc & ((STRACE_UW_CACHE_SIZE-1) &
146 ~(STRACE_UW_CACHE_ASSOC-1));
147 struct cache_entry *unused = NULL;
148 struct cache_entry *lru = ctx->cache + idx;
149 for (unsigned int i = 0; i < STRACE_UW_CACHE_ASSOC; ++i) {
150 struct cache_entry *ce = ctx->cache + (idx + i);
151 if (ce->generation == mapping_generation && ce->pc == pc) {
152 ce->last_use = uwcache_clock++;
153 *res = ce;
154 return true;
155 }
156 if (ce->generation != mapping_generation) {
157 unused = ce;
158 continue;
159 }
160 if (ce->last_use < lru->last_use)
161 lru = ce;
162 }
163 *res = unused ? unused : lru;
164
165 return false;
166 }
167
168 static int
169 frame_callback(Dwfl_Frame *state, void *arg)
170 {
171 struct frame_user_data *user_data = arg;
172 Dwarf_Addr pc;
173 bool isactivation;
174
175 if (!dwfl_frame_pc(state, &pc, &isactivation)) {
176 /* Propagate the error to the caller. */
177 return -1;
178 }
179
180 if (!isactivation)
181 pc--;
182
183 struct cache_entry *ce;
184 if (find_bucket(user_data->ctx, pc, &ce)) {
185 user_data->call_action(user_data->data,
186 ce->modname, ce->symname,
187 ce->off, ce->true_offset);
188 } else {
189 Dwfl *dwfl = dwfl_thread_dwfl(dwfl_frame_thread(state));
190 Dwfl_Module *mod = dwfl_addrmodule(dwfl, pc);
191 GElf_Off off = 0;
192
193 if (mod != NULL) {
194 const char *modname = NULL;
195 const char *symname = NULL;
196 GElf_Sym sym;
197 Dwarf_Addr true_offset = pc;
198
199 modname = dwfl_module_info(mod, NULL, NULL, NULL, NULL,
200 NULL, NULL, NULL);
201 symname = dwfl_module_addrinfo(mod, pc, &off, &sym,
202 NULL, NULL, NULL);
203 dwfl_module_relocate_address(mod, &true_offset);
204 user_data->call_action(user_data->data, modname, symname,
205 off, true_offset);
206
207 ce->generation = mapping_generation;
208 ce->pc = pc;
209 ce->modname = modname;
210 ce->symname = symname;
211 ce->off = off;
212 ce->true_offset = true_offset;
213 ce->last_use = uwcache_clock++;
214 }
215 }
216
217 /* Max number of frames to print reached? */
218 if (user_data->stack_depth-- == 0)
219 return DWARF_CB_ABORT;
220
221 return DWARF_CB_OK;
222 }
223
224 static void
225 tcb_walk(struct tcb *tcp,
226 unwind_call_action_fn call_action,
227 unwind_error_action_fn error_action,
228 void *data)
229 {
230 struct ctx *ctx = tcp->unwind_ctx;
231 if (!ctx)
232 return;
233
234 struct frame_user_data user_data = {
235 .call_action = call_action,
236 .error_action = error_action,
237 .data = data,
238 .stack_depth = 256,
239 .ctx = ctx,
240 };
241
242 flush_cache_maybe(tcp);
243
244 int r = dwfl_getthread_frames(ctx->dwfl, tcp->pid, frame_callback,
245 &user_data);
246 if (r)
247 error_action(data,
248 r < 0 ? dwfl_errmsg(-1) : "too many stack frames",
249 0);
250 }
251
252 const struct unwind_unwinder_t unwinder = {
253 .name = "libdw",
254 .init = init,
255 .tcb_init = tcb_init,
256 .tcb_fin = tcb_fin,
257 .tcb_walk = tcb_walk,
258 };