1 /* ---------------------------------------------------------------------------
3 * (c) The GHC Team, 2014-2015
5 * Producing DWARF-based stacktraces with libdw.
7 * --------------------------------------------------------------------------*/
15 #include <elfutils/libdwfl.h>
19 const int max_backtrace_depth
= 5000;
21 static BacktraceChunk
*backtraceAllocChunk(BacktraceChunk
*next
) {
22 BacktraceChunk
*chunk
= stgMallocBytes(sizeof(BacktraceChunk
),
23 "backtraceAllocChunk");
29 // Allocate a Backtrace
30 static Backtrace
*backtraceAlloc(void) {
31 Backtrace
*bt
= stgMallocBytes(sizeof(Backtrace
), "backtraceAlloc");
33 bt
->last
= backtraceAllocChunk(NULL
);
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
);
43 bt
->last
->frames
[bt
->last
->n_frames
] = pc
;
48 void backtraceFree(Backtrace
*bt
) {
51 BacktraceChunk
*chunk
= bt
->last
;
52 while (chunk
!= NULL
) {
53 BacktraceChunk
*next
= chunk
->next
;
60 struct LibdwSession_
{
63 // The current backtrace we are collecting (if any)
68 static const Dwfl_Thread_Callbacks thread_cbs
;
70 void libdwFree(LibdwSession
*session
) {
73 dwfl_end(session
->dwfl
);
77 // Create a libdw session with DWARF information for all loaded modules
78 LibdwSession
*libdwInit() {
79 LibdwSession
*session
= stgCallocBytes(1, sizeof(LibdwSession
),
81 // Initialize ELF library
82 if (elf_version(EV_CURRENT
) == EV_NONE
) {
83 sysErrorBelch("libelf version too old!");
87 // Initialize a libdwfl session
88 static char *debuginfo_path
;
89 static const Dwfl_Callbacks proc_callbacks
=
91 .find_debuginfo
= dwfl_standard_find_debuginfo
,
92 .debuginfo_path
= &debuginfo_path
,
93 .find_elf
= dwfl_linux_proc_find_elf
,
95 session
->dwfl
= dwfl_begin (&proc_callbacks
);
96 if (session
->dwfl
== NULL
) {
97 sysErrorBelch("dwfl_begin failed: %s", dwfl_errmsg(dwfl_errno()));
102 // Report the loaded modules
103 int ret
= dwfl_linux_proc_report(session
->dwfl
, getpid());
105 sysErrorBelch("dwfl_linux_proc_report failed: %s",
106 dwfl_errmsg(dwfl_errno()));
109 if (dwfl_report_end (session
->dwfl
, NULL
, NULL
) != 0) {
110 sysErrorBelch("dwfl_report_end failed: %s", dwfl_errmsg(dwfl_errno()));
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()));
124 dwfl_end(session
->dwfl
);
129 int libdwLookupLocation(LibdwSession
*session
, Location
*frame
,
131 Dwarf_Addr addr
= (Dwarf_Addr
) (uintptr_t) pc
;
132 // Find the module containing PC
133 Dwfl_Module
*mod
= dwfl_addrmodule(session
->dwfl
, addr
);
136 dwfl_module_info(mod
, NULL
, NULL
, NULL
, NULL
, NULL
,
137 &frame
->object_file
, NULL
);
139 // Find function name
140 frame
->function
= dwfl_module_addrname(mod
, addr
);
142 // Try looking up source location
143 Dwfl_Line
*line
= dwfl_module_getsrc(mod
, addr
);
147 /* libdwfl owns the source_file buffer, don't free it */
148 frame
->source_file
= dwfl_lineinfo(line
, &addr
, &lineno
,
150 frame
->lineno
= lineno
;
151 frame
->colno
= colno
;
154 if (line
== NULL
|| frame
->source_file
== NULL
) {
155 frame
->source_file
= NULL
;
162 int libdwForEachFrameOutwards(Backtrace
*bt
,
163 int (*cb
)(StgPtr
, void*),
166 int n_chunks
= bt
->n_frames
/ BACKTRACE_CHUNK_SZ
;
167 if (bt
->n_frames
% BACKTRACE_CHUNK_SZ
!= 0)
170 BacktraceChunk
**chunks
=
171 stgMallocBytes(n_chunks
* sizeof(BacktraceChunk
*),
172 "libdwForEachFrameOutwards");
174 // First build a list of chunks, ending with the inner-most chunk
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
;
181 // Now iterate back through the frames
183 for (chunk_idx
= n_chunks
-1; chunk_idx
>= 0 && res
== 0; chunk_idx
--) {
185 BacktraceChunk
*chunk
= chunks
[chunk_idx
];
186 for (i
= 0; i
< chunk
->n_frames
; i
++) {
187 res
= cb(chunk
->frames
[i
], user_data
);
196 LibdwSession
*session
;
200 static int printFrame(StgPtr pc
, void *cbdata
)
202 struct PrintData
*pd
= (struct PrintData
*) cbdata
;
204 libdwLookupLocation(pd
->session
, &loc
, pc
);
205 fprintf(pd
->file
, " %24p %s ",
206 (void*) pc
, loc
.function
);
208 fprintf(pd
->file
, "(%s:%d.%d)\n",
209 loc
.source_file
, loc
.lineno
, loc
.colno
);
211 fprintf(pd
->file
, "(%s)\n", loc
.object_file
);
215 void libdwPrintBacktrace(LibdwSession
*session
, FILE *file
, Backtrace
*bt
) {
217 fprintf(file
, "Warning: tried to print failed backtrace\n");
221 struct PrintData pd
= { session
, file
};
222 libdwForEachFrameOutwards(bt
, printFrame
, &pd
);
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
;
230 if (! dwfl_frame_pc(frame
, &pc
, &is_activation
)) {
232 backtracePush(session
->cur_bt
, 0x0);
235 pc
-= 1; // TODO: is this right?
236 backtracePush(session
->cur_bt
, (StgPtr
) (uintptr_t) pc
);
238 session
->max_depth
--;
239 if (session
->max_depth
== 0) {
240 return DWARF_CB_ABORT
;
246 Backtrace
*libdwGetBacktrace(LibdwSession
*session
) {
247 if (session
->cur_bt
!= NULL
) {
248 sysErrorBelch("Already collecting backtrace. Uh oh.");
252 Backtrace
*bt
= backtraceAlloc();
253 session
->cur_bt
= bt
;
254 session
->max_depth
= max_backtrace_depth
;
257 int ret
= dwfl_getthread_frames(session
->dwfl
, pid
,
258 getBacktraceFrameCb
, session
);
260 sysErrorBelch("Failed to get stack frames of current process: %s",
261 dwfl_errmsg(dwfl_errno()));
263 session
->cur_bt
= NULL
;
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
)
273 return dwfl_pid(dwfl
);
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
;
282 static bool set_initial_registers(Dwfl_Thread
*thread
, void *arg
);
284 #if defined(x86_64_HOST_ARCH)
285 static bool set_initial_registers(Dwfl_Thread
*thread
,
286 void *arg STG_UNUSED
) {
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"
307 :"r" (®s
[0]) /* input */
308 :"%rax" /* clobbered */
310 return dwfl_thread_state_registers(thread
, 0, 17, regs
);
312 #elif defined(i386_HOST_ARCH)
313 static bool set_initial_registers(Dwfl_Thread
*thread
,
314 void *arg STG_UNUSED
) {
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"
325 "movl here, %%eax\n\t"
326 "movl %%eax, 0x20(%0)\n\t"
328 :"r" (®s
[0]) /* input */
329 :"%eax" /* clobbered */
331 return dwfl_thread_state_registers(thread
, 0, 9, regs
);
334 # error "Please implement set_initial_registers() for your arch"
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
,
343 #else /* !USE_LIBDW */
345 void backtraceFree(Backtrace
*bt STG_UNUSED
) { }
347 Backtrace
*libdwGetBacktrace(LibdwSession
*session STG_UNUSED
) {
351 int libdwLookupLocation(LibdwSession
*session STG_UNUSED
,
352 Location
*loc STG_UNUSED
,
353 StgPtr pc STG_UNUSED
) {
357 #endif /* USE_LIBDW */