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