Address #11471 by putting RuntimeRep in kinds.
[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 #ifdef USE_LIBDW
14
15 #include <elfutils/libdwfl.h>
16 #include <dwarf.h>
17 #include <unistd.h>
18
19 static BacktraceChunk *backtraceAllocChunk(BacktraceChunk *next) {
20 BacktraceChunk *chunk = stgMallocBytes(sizeof(BacktraceChunk),
21 "backtraceAllocChunk");
22 chunk->n_frames = 0;
23 chunk->next = next;
24 return chunk;
25 }
26
27 // Allocate a Backtrace
28 static Backtrace *backtraceAlloc(void) {
29 Backtrace *bt = stgMallocBytes(sizeof(Backtrace), "backtraceAlloc");
30 bt->n_frames = 0;
31 bt->last = backtraceAllocChunk(NULL);
32 return bt;
33 }
34
35 static void backtracePush(Backtrace *bt, StgPtr pc) {
36 // Is this chunk full?
37 if (bt->last->n_frames == BACKTRACE_CHUNK_SZ)
38 bt->last = backtraceAllocChunk(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 backtraceFree(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 libdwFree(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 *libdwInit() {
74 LibdwSession *session = stgCallocBytes(1, sizeof(LibdwSession),
75 "libdwInit");
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 libdwLookupLocation(LibdwSession *session, Location *frame,
125 StgPtr pc) {
126 Dwarf_Addr addr = (Dwarf_Addr) (uintptr_t) pc;
127 // Find the module containing PC
128 Dwfl_Module *mod = dwfl_addrmodule(session->dwfl, addr);
129 if (mod == NULL)
130 return 1;
131 dwfl_module_info(mod, NULL, NULL, NULL, NULL, NULL,
132 &frame->object_file, NULL);
133
134 // Find function name
135 frame->function = dwfl_module_addrname(mod, addr);
136
137 // Try looking up source location
138 Dwfl_Line *line = dwfl_module_getsrc(mod, addr);
139 if (line != NULL) {
140 Dwarf_Addr addr;
141 int lineno, colno;
142 /* libdwfl owns the source_file buffer, don't free it */
143 frame->source_file = dwfl_lineinfo(line, &addr, &lineno,
144 &colno, NULL, NULL);
145 frame->lineno = lineno;
146 frame->colno = colno;
147 }
148
149 if (line == NULL || frame->source_file == NULL) {
150 frame->source_file = NULL;
151 frame->lineno = 0;
152 frame->colno = 0;
153 }
154 return 0;
155 }
156
157 int libdwForEachFrameOutwards(Backtrace *bt,
158 int (*cb)(StgPtr, void*),
159 void *user_data)
160 {
161 int n_chunks = bt->n_frames / BACKTRACE_CHUNK_SZ;
162 if (bt->n_frames % BACKTRACE_CHUNK_SZ != 0)
163 n_chunks++;
164
165 BacktraceChunk **chunks =
166 stgMallocBytes(n_chunks * sizeof(BacktraceChunk *),
167 "libdwForEachFrameOutwards");
168
169 // First build a list of chunks, ending with the inner-most chunk
170 int chunk_idx;
171 chunks[0] = bt->last;
172 for (chunk_idx = 1; chunk_idx < n_chunks; chunk_idx++) {
173 chunks[chunk_idx] = chunks[chunk_idx-1]->next;
174 }
175
176 // Now iterate back through the frames
177 int res = 0;
178 for (chunk_idx = n_chunks-1; chunk_idx >= 0 && res == 0; chunk_idx--) {
179 unsigned int i;
180 BacktraceChunk *chunk = chunks[chunk_idx];
181 for (i = 0; i < chunk->n_frames; i++) {
182 res = cb(chunk->frames[i], user_data);
183 if (res != 0) break;
184 }
185 }
186 free(chunks);
187 return res;
188 }
189
190 struct PrintData {
191 LibdwSession *session;
192 FILE *file;
193 };
194
195 static int printFrame(StgPtr pc, void *cbdata)
196 {
197 struct PrintData *pd = (struct PrintData *) cbdata;
198 Location loc;
199 libdwLookupLocation(pd->session, &loc, pc);
200 fprintf(pd->file, " %24p %s ",
201 (void*) pc, loc.function);
202 if (loc.source_file)
203 fprintf(pd->file, "(%s:%d.%d)\n",
204 loc.source_file, loc.lineno, loc.colno);
205 else
206 fprintf(pd->file, "(%s)\n", loc.object_file);
207 return 0;
208 }
209
210 void libdwPrintBacktrace(LibdwSession *session, FILE *file, Backtrace *bt) {
211 if (bt == NULL) {
212 fprintf(file, "Warning: tried to print failed backtrace\n");
213 return;
214 }
215
216 struct PrintData pd = { session, file };
217 libdwForEachFrameOutwards(bt, printFrame, &pd);
218 }
219
220 // Remember that we are traversing from the inner-most to the outer-most frame
221 static int getBacktraceFrameCb(Dwfl_Frame *frame, void *arg) {
222 LibdwSession *session = arg;
223 Dwarf_Addr pc;
224 bool is_activation;
225 if (! dwfl_frame_pc(frame, &pc, &is_activation)) {
226 // failed to find PC
227 backtracePush(session->cur_bt, 0x0);
228 } else {
229 if (is_activation)
230 pc -= 1; // TODO: is this right?
231 backtracePush(session->cur_bt, (StgPtr) (uintptr_t) pc);
232 }
233
234 return DWARF_CB_OK;
235 }
236
237 Backtrace *libdwGetBacktrace(LibdwSession *session) {
238 if (session->cur_bt != NULL) {
239 sysErrorBelch("Already collecting backtrace. Uh oh.");
240 return NULL;
241 }
242
243 Backtrace *bt = backtraceAlloc();
244 session->cur_bt = bt;
245
246 int pid = getpid();
247 int ret = dwfl_getthread_frames(session->dwfl, pid,
248 getBacktraceFrameCb, session);
249 if (ret == -1)
250 sysErrorBelch("Failed to get stack frames of current process: %s",
251 dwfl_errmsg(dwfl_errno()));
252
253 session->cur_bt = NULL;
254 return bt;
255 }
256
257 static pid_t next_thread(Dwfl *dwfl, void *arg, void **thread_argp) {
258 /* there is only the current thread */
259 if (*thread_argp != NULL)
260 return 0;
261
262 *thread_argp = arg;
263 return dwfl_pid(dwfl);
264 }
265
266 static bool memory_read(Dwfl *dwfl STG_UNUSED, Dwarf_Addr addr,
267 Dwarf_Word *result, void *arg STG_UNUSED) {
268 *result = *(Dwarf_Word *) (uintptr_t) addr;
269 return true;
270 }
271
272 static bool set_initial_registers(Dwfl_Thread *thread, void *arg);
273
274 #ifdef x86_64_HOST_ARCH
275 static bool set_initial_registers(Dwfl_Thread *thread,
276 void *arg STG_UNUSED) {
277 Dwarf_Word regs[17];
278 __asm__ ("movq %%rax, 0x00(%0)\n\t"
279 "movq %%rdx, 0x08(%0)\n\t"
280 "movq %%rcx, 0x10(%0)\n\t"
281 "movq %%rbx, 0x18(%0)\n\t"
282 "movq %%rsi, 0x20(%0)\n\t"
283 "movq %%rdi, 0x28(%0)\n\t"
284 "movq %%rbp, 0x30(%0)\n\t"
285 "movq %%rsp, 0x38(%0)\n\t"
286 "movq %%r8, 0x40(%0)\n\t"
287 "movq %%r9, 0x48(%0)\n\t"
288 "movq %%r10, 0x50(%0)\n\t"
289 "movq %%r11, 0x58(%0)\n\t"
290 "movq %%r12, 0x60(%0)\n\t"
291 "movq %%r13, 0x68(%0)\n\t"
292 "movq %%r14, 0x70(%0)\n\t"
293 "movq %%r15, 0x78(%0)\n\t"
294 "lea 0(%%rip), %%rax\n\t"
295 "movq %%rax, 0x80(%0)\n\t"
296 : /* no output */
297 :"r" (&regs[0]) /* input */
298 :"%rax" /* clobbered */
299 );
300 return dwfl_thread_state_registers(thread, 0, 17, regs);
301 }
302 #elif defined(i386_HOST_ARCH)
303 static bool set_initial_registers(Dwfl_Thread *thread,
304 void *arg STG_UNUSED) {
305 Dwarf_Word regs[9];
306 __asm__ ("movl %%eax, 0x00(%0)\n\t"
307 "movl %%ecx, 0x04(%0)\n\t"
308 "movl %%edx, 0x08(%0)\n\t"
309 "movl %%ebx, 0x0c(%0)\n\t"
310 "movl %%esp, 0x10(%0)\n\t"
311 "movl %%ebp, 0x14(%0)\n\t"
312 "movl %%esp, 0x18(%0)\n\t"
313 "movl %%edi, 0x1c(%0)\n\t"
314 "here:\n\t"
315 "movl here, %%eax\n\t"
316 "movl %%eax, 0x20(%0)\n\t"
317 : /* no output */
318 :"r" (&regs[0]) /* input */
319 :"%eax" /* clobbered */
320 );
321 return dwfl_thread_state_registers(thread, 0, 9, regs);
322 }
323 #else
324 # error "Please implement set_initial_registers() for your arch"
325 #endif
326
327 static const Dwfl_Thread_Callbacks thread_cbs = {
328 .next_thread = next_thread,
329 .memory_read = memory_read,
330 .set_initial_registers = set_initial_registers,
331 };
332
333 #else /* !USE_LIBDW */
334
335 void backtraceFree(Backtrace *bt STG_UNUSED) { }
336
337 Backtrace *libdwGetBacktrace(LibdwSession *session STG_UNUSED) {
338 return NULL;
339 }
340
341 int libdwLookupLocation(LibdwSession *session STG_UNUSED,
342 Location *loc STG_UNUSED,
343 StgPtr pc STG_UNUSED) {
344 return 1;
345 }
346
347 #endif /* USE_LIBDW */