Libdw: Add libdw-based stack unwinding
[ghc.git] / rts / Libdw.c
1 /* ---------------------------------------------------------------------------
2 *
3 * (c) The GHC Team, 2014-2015
4 *
5 * Producing DWARF-based stacktraces with libdw.
6 *
7 * --------------------------------------------------------------------------*/
8
9 #ifdef USE_LIBDW
10
11 #include <elfutils/libdwfl.h>
12 #include <dwarf.h>
13 #include <unistd.h>
14
15 #include "Rts.h"
16 #include "Libdw.h"
17 #include "RtsUtils.h"
18
19 static BacktraceChunk *backtrace_alloc_chunk(BacktraceChunk *next) {
20 BacktraceChunk *chunk = stgMallocBytes(sizeof(BacktraceChunk),
21 "backtrace_alloc_chunk");
22 chunk->n_frames = 0;
23 chunk->next = next;
24 return chunk;
25 }
26
27 // Allocate a Backtrace
28 static Backtrace *backtrace_alloc(void) {
29 Backtrace *bt = stgMallocBytes(sizeof(Backtrace), "backtrace_alloc");
30 bt->n_frames = 0;
31 bt->last = backtrace_alloc_chunk(NULL);
32 return bt;
33 }
34
35 static void backtrace_push(Backtrace *bt, StgPtr pc) {
36 // Is this chunk full?
37 if (bt->last->n_frames == BACKTRACE_CHUNK_SZ)
38 bt->last = backtrace_alloc_chunk(bt->last);
39
40 // Push the PC
41 bt->last->frames[bt->last->n_frames] = pc;
42 bt->last->n_frames++;
43 bt->n_frames++;
44 }
45
46 void backtrace_free(Backtrace *bt) {
47 if (bt == NULL)
48 return;
49 BacktraceChunk *chunk = bt->last;
50 while (chunk != NULL) {
51 BacktraceChunk *next = chunk->next;
52 stgFree(chunk);
53 chunk = next;
54 }
55 stgFree(bt);
56 }
57
58 struct LibDwSession_ {
59 Dwfl *dwfl;
60 Backtrace *cur_bt; // The current backtrace we are collecting (if any)
61 };
62
63 typedef struct LibDwSession_ LibDwSession;
64
65 static const Dwfl_Thread_Callbacks thread_cbs;
66
67 void libdw_free(LibDwSession *session) {
68 if (session == NULL)
69 return;
70 dwfl_end(session->dwfl);
71 stgFree(session);
72 }
73
74 // Create a libdw session with DWARF information for all loaded modules
75 LibDwSession *libdw_init() {
76 LibDwSession *session = stgCallocBytes(1, sizeof(LibDwSession),
77 "libdw_init");
78 // Initialize ELF library
79 if (elf_version(EV_CURRENT) == EV_NONE) {
80 sysErrorBelch("libelf version too old!");
81 return NULL;
82 }
83
84 // Initialize a libdwfl session
85 static char *debuginfo_path;
86 static const Dwfl_Callbacks proc_callbacks =
87 {
88 .find_debuginfo = dwfl_standard_find_debuginfo,
89 .debuginfo_path = &debuginfo_path,
90 .find_elf = dwfl_linux_proc_find_elf,
91 };
92 session->dwfl = dwfl_begin (&proc_callbacks);
93 if (session->dwfl == NULL) {
94 sysErrorBelch("dwfl_begin failed: %s", dwfl_errmsg(dwfl_errno()));
95 free(session);
96 return NULL;
97 }
98
99 // Report the loaded modules
100 int ret = dwfl_linux_proc_report(session->dwfl, getpid());
101 if (ret < 0) {
102 sysErrorBelch("dwfl_linux_proc_report failed: %s",
103 dwfl_errmsg(dwfl_errno()));
104 goto fail;
105 }
106 if (dwfl_report_end (session->dwfl, NULL, NULL) != 0) {
107 sysErrorBelch("dwfl_report_end failed: %s", dwfl_errmsg(dwfl_errno()));
108 goto fail;
109 }
110
111 pid_t pid = getpid();
112 if (! dwfl_attach_state(session->dwfl, NULL, pid, &thread_cbs, NULL)) {
113 sysErrorBelch("dwfl_attach_state failed: %s",
114 dwfl_errmsg(dwfl_errno()));
115 goto fail;
116 }
117
118 return session;
119
120 fail:
121 dwfl_end(session->dwfl);
122 free(session);
123 return NULL;
124 }
125
126 int libdw_lookup_location(LibDwSession *session, Location *frame,
127 StgPtr pc) {
128 // Find the module containing PC
129 Dwfl_Module *mod = dwfl_addrmodule(session->dwfl, (Dwarf_Addr) pc);
130 if (mod == NULL)
131 return 1;
132 dwfl_module_info(mod, NULL, NULL, NULL, NULL, NULL,
133 &frame->object_file, NULL);
134
135 // Find function name
136 frame->function = dwfl_module_addrname(mod, (Dwarf_Addr) pc);
137
138 // Try looking up source location
139 Dwfl_Line *line = dwfl_module_getsrc(mod, (Dwarf_Addr) pc);
140 if (line != NULL) {
141 Dwarf_Addr addr;
142 int lineno, colno;
143 /* libdwfl owns the source_file buffer, don't free it */
144 frame->source_file = dwfl_lineinfo(line, &addr, &lineno,
145 &colno, NULL, NULL);
146 frame->lineno = lineno;
147 frame->colno = colno;
148 }
149
150 if (line == NULL || frame->source_file == NULL) {
151 frame->source_file = NULL;
152 frame->lineno = 0;
153 frame->colno = 0;
154 }
155 return 0;
156 }
157
158 int foreach_frame_outwards(Backtrace *bt,
159 int (*cb)(StgPtr, void*),
160 void *user_data)
161 {
162 int n_chunks = bt->n_frames / BACKTRACE_CHUNK_SZ;
163 if (bt->n_frames % BACKTRACE_CHUNK_SZ != 0)
164 n_chunks++;
165
166 BacktraceChunk **chunks =
167 stgMallocBytes(n_chunks * sizeof(BacktraceChunk *),
168 "foreach_frame_outwards");
169
170 // First build a list of chunks, ending with the inner-most chunk
171 int chunk_idx;
172 chunks[0] = bt->last;
173 for (chunk_idx = 1; chunk_idx < n_chunks; chunk_idx++) {
174 chunks[chunk_idx] = chunks[chunk_idx-1]->next;
175 }
176
177 // Now iterate back through the frames
178 int res = 0;
179 for (chunk_idx = n_chunks-1; chunk_idx >= 0 && res == 0; chunk_idx--) {
180 unsigned int i;
181 BacktraceChunk *chunk = chunks[chunk_idx];
182 for (i = 0; i < chunk->n_frames; i++) {
183 res = cb(chunk->frames[i], user_data);
184 if (res != 0) break;
185 }
186 }
187 free(chunks);
188 return res;
189 }
190
191 struct PrintData {
192 LibDwSession *session;
193 FILE *file;
194 };
195
196 static int print_frame(StgPtr pc, void *cbdata)
197 {
198 struct PrintData *pd = (struct PrintData *) cbdata;
199 Location loc;
200 libdw_lookup_location(pd->session, &loc, pc);
201 fprintf(pd->file, " %24p %s ",
202 (void*) pc, loc.function);
203 if (loc.source_file)
204 fprintf(pd->file, "(%s:%d.%d)\n",
205 loc.source_file, loc.lineno, loc.colno);
206 else
207 fprintf(pd->file, "(%s)\n", loc.object_file);
208 return 0;
209 }
210
211 void libdw_print_backtrace(LibDwSession *session, FILE *file, Backtrace *bt) {
212 if (bt == NULL) {
213 fprintf(file, "Warning: tried to print failed backtrace\n");
214 return;
215 }
216
217 struct PrintData pd = { session, file };
218 foreach_frame_outwards(bt, print_frame, &pd);
219 }
220
221 // Remember that we are traversing from the inner-most to the outer-most frame
222 static int frame_cb(Dwfl_Frame *frame, void *arg) {
223 LibDwSession *session = arg;
224 Dwarf_Addr pc;
225 bool is_activation;
226 if (! dwfl_frame_pc(frame, &pc, &is_activation)) {
227 // failed to find PC
228 backtrace_push(session->cur_bt, 0x0);
229 } else {
230 if (is_activation)
231 pc -= 1; // TODO: is this right?
232 backtrace_push(session->cur_bt, (StgPtr) pc);
233 }
234
235 if ((void *) pc == &stg_stop_thread_info)
236 return DWARF_CB_ABORT;
237 else
238 return DWARF_CB_OK;
239 }
240
241 Backtrace *libdw_get_backtrace(LibDwSession *session) {
242 if (session->cur_bt != NULL) {
243 sysErrorBelch("Already collecting backtrace. Uh oh.");
244 return NULL;
245 }
246
247 Backtrace *bt = backtrace_alloc();
248 session->cur_bt = bt;
249
250 int pid = getpid();
251 int ret = dwfl_getthread_frames(session->dwfl, pid, frame_cb, session);
252 if (ret == -1)
253 sysErrorBelch("Failed to get stack frames of current process: %s",
254 dwfl_errmsg(dwfl_errno()));
255
256 session->cur_bt = NULL;
257 return bt;
258 }
259
260 static pid_t next_thread(Dwfl *dwfl, void *arg, void **thread_argp) {
261 /* there is only the current thread */
262 if (*thread_argp != NULL)
263 return 0;
264
265 *thread_argp = arg;
266 return dwfl_pid(dwfl);
267 }
268
269 static bool memory_read(Dwfl *dwfl STG_UNUSED, Dwarf_Addr addr,
270 Dwarf_Word *result, void *arg STG_UNUSED) {
271 *result = *(Dwarf_Word *) addr;
272 return true;
273 }
274
275 static bool set_initial_registers(Dwfl_Thread *thread, void *arg);
276
277 #ifdef x86_64_HOST_ARCH
278 static bool set_initial_registers(Dwfl_Thread *thread,
279 void *arg STG_UNUSED) {
280 Dwarf_Word regs[17];
281 __asm__ ("movq %%rax, 0x00(%0)\n\t"
282 "movq %%rdx, 0x08(%0)\n\t"
283 "movq %%rcx, 0x10(%0)\n\t"
284 "movq %%rbx, 0x18(%0)\n\t"
285 "movq %%rsi, 0x20(%0)\n\t"
286 "movq %%rdi, 0x28(%0)\n\t"
287 "movq %%rbp, 0x30(%0)\n\t"
288 "movq %%rsp, 0x38(%0)\n\t"
289 "movq %%r8, 0x40(%0)\n\t"
290 "movq %%r9, 0x48(%0)\n\t"
291 "movq %%r10, 0x50(%0)\n\t"
292 "movq %%r11, 0x58(%0)\n\t"
293 "movq %%r12, 0x60(%0)\n\t"
294 "movq %%r13, 0x68(%0)\n\t"
295 "movq %%r14, 0x70(%0)\n\t"
296 "movq %%r15, 0x78(%0)\n\t"
297 "lea 0(%%rip), %%rax\n\t"
298 "movq %%rax, 0x80(%0)\n\t"
299 : /* no output */
300 :"r" (&regs[0]) /* input */
301 :"%rax" /* clobbered */
302 );
303 return dwfl_thread_state_registers(thread, 0, 17, regs);
304 }
305 #endif
306 #ifdef i386_HOST_ARCH
307 static bool set_initial_registers(Dwfl_Thread *thread,
308 void *arg STG_UNUSED) {
309 Dwarf_Word regs[9];
310 __asm__ ("movl %%eax, 0x00(%0)\n\t"
311 "movl %%ecx, 0x04(%0)\n\t"
312 "movl %%edx, 0x08(%0)\n\t"
313 "movl %%ebx, 0x0c(%0)\n\t"
314 "movl %%esp, 0x10(%0)\n\t"
315 "movl %%ebp, 0x14(%0)\n\t"
316 "movl %%esp, 0x18(%0)\n\t"
317 "movl %%edi, 0x1c(%0)\n\t"
318 "lea 0(%%eip), %%eax\n\t"
319 "movl %%eax, 0x20(%0)\n\t"
320 : /* no output */
321 :"r" (&regs[0]) /* input */
322 :"%eax" /* clobbered */
323 );
324 return dwfl_thread_state_registers(thread, 0, 9, regs);
325 }
326 #endif
327
328 static const Dwfl_Thread_Callbacks thread_cbs = {
329 .next_thread = next_thread,
330 .memory_read = memory_read,
331 .set_initial_registers = set_initial_registers,
332 };
333
334 #endif /* USE_LIBDW */