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