master
c 187 lines 5.18 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "stacktrace-common.h"
4
5 #if defined(USE_LIBUNWIND)
6 #if !defined(STATIC_BUILD)
7 #define UNW_LOCAL_ONLY
8 #endif
9 #include <libunwind.h>
10 #include <dlfcn.h>
11
12 const char *stacktrace_backend(void) {
13 return "libunwind";
14 }
15
16 void impl_stacktrace_init(void) {
17 unw_set_caching_policy(unw_local_addr_space, UNW_CACHE_NONE);
18 }
19
20 void stacktrace_flush(void) {
21 unw_flush_cache(unw_local_addr_space, 0, 0);
22 }
23
24 bool stacktrace_capture_is_async_signal_safe(void) {
25 #if defined(STATIC_BUILD)
26 return false;
27 #else
28 return true;
29 #endif
30 }
31
32 bool stacktrace_available(void) {
33 return true;
34 }
35
36 NEVER_INLINE
37 void stacktrace_capture(BUFFER *wb) {
38 // this function is async-signal-safe, if the buffer has enough space to hold the stack trace
39
40 root_cause_function[0] = '\0';
41
42 unw_cursor_t cursor;
43 unw_context_t context;
44 size_t frames = 0;
45
46 // Initialize context for current thread
47 unw_getcontext(&context);
48 unw_init_local(&cursor, &context);
49
50 // Skip one frame to hide stacktrace_capture() itself
51 unw_step(&cursor);
52
53 size_t added = 0;
54 bool found_signal_handler = false;
55
56 while (unw_step(&cursor) > 0) {
57 unw_word_t offset, pc;
58 char sym[256];
59
60 unw_get_reg(&cursor, UNW_REG_IP, &pc);
61 if (!pc)
62 break;
63
64 const char *name = sym;
65 if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) != 0) {
66 name = "<unknown>";
67 offset = 0;
68 }
69
70 // Check if we found the signal handler frame
71 if (!found_signal_handler && stacktrace_is_signal_handler_function(name)) {
72 // We found the signal handler, reset the buffer
73 buffer_flush(wb);
74 added = 0;
75 frames = 0;
76 found_signal_handler = true;
77 continue; // Skip adding the signal handler itself
78 }
79
80 // Check for logging functions, but only if we haven't found a signal handler yet
81 if (!found_signal_handler && stacktrace_is_logging_function(name)) {
82 // Found a logging function, reset the buffer
83 buffer_flush(wb);
84 added = 0;
85 frames = 0;
86 // continue to add the function to the stack trace
87 }
88
89 if (frames++)
90 buffer_putc(wb, '\n');
91
92 buffer_putc(wb, '#');
93 buffer_print_uint64(wb, added);
94 buffer_putc(wb, ' ');
95 buffer_strcat(wb, name);
96
97 if(offset) {
98 buffer_putc(wb, '+');
99 buffer_print_uint64_hex(wb, offset);
100 }
101
102 added++;
103 }
104
105 if (!added)
106 buffer_strcat(wb, NO_STACK_TRACE_PREFIX "libunwind reports no frames");
107 }
108
109 // Collect frames using libunwind
110 static int collect_frames_libunwind(void **frames, int max_frames, int skip) {
111 unw_cursor_t cursor;
112 unw_context_t context;
113
114 unw_getcontext(&context);
115 unw_init_local(&cursor, &context);
116
117 // Collect all frames first, including the ones we'll skip
118 // This way we ensure we capture all frames, including inlined ones
119 void *all_frames[150]; // Allocate a larger buffer to hold all frames
120 int total_frames = 0;
121
122 // Collect as many frames as possible
123 while (total_frames < 150 && unw_step(&cursor) > 0) {
124 unw_word_t pc;
125 unw_get_reg(&cursor, UNW_REG_IP, &pc);
126 if (!pc)
127 break;
128
129 all_frames[total_frames++] = (void *)pc;
130 }
131
132 // Now copy the frames we want, skipping the requested number
133 int frame_count = 0;
134 for (int i = skip; i < total_frames && frame_count < max_frames; i++) {
135 frames[frame_count++] = all_frames[i];
136 }
137
138 return frame_count;
139 }
140
141 // Implementation-specific function to collect stack trace frames
142 int impl_stacktrace_get_frames(void **frames, int max_frames, int skip_frames) {
143 if (!frames || max_frames <= 0)
144 return 0;
145
146 // Add 1 to skip_frames to also skip this function itself
147 return collect_frames_libunwind(frames, max_frames, skip_frames + 1);
148 }
149
150 // Implementation-specific function to convert a stacktrace to a buffer
151 void impl_stacktrace_to_buffer(STACKTRACE trace, BUFFER *wb) {
152 struct stacktrace *st = (struct stacktrace *)trace;
153
154 // Format each frame
155 for (int i = 0; i < st->frame_count; i++) {
156 if (i > 0)
157 buffer_putc(wb, '\n');
158
159 buffer_putc(wb, '#');
160 buffer_print_uint64(wb, i);
161 buffer_putc(wb, ' ');
162
163 // Try to resolve symbol name
164 unw_cursor_t cursor;
165 unw_context_t context;
166 char sym[256] = "<unknown>";
167 unw_word_t offset = 0;
168
169 // We don't have the context anymore, so do the best we can
170 // Try to resolve the address to a symbol name
171 if (dladdr(st->frames[i], (Dl_info *)sym) == 0) {
172 buffer_strcat(wb, "<unknown>");
173 } else {
174 Dl_info *info = (Dl_info *)sym;
175 if (info->dli_sname)
176 buffer_strcat(wb, info->dli_sname);
177 else
178 buffer_strcat(wb, "<unknown>");
179 }
180
181 buffer_strcat(wb, " [");
182 buffer_print_uint64_hex(wb, (uint64_t)st->frames[i]);
183 buffer_putc(wb, ']');
184 }
185 }
186
187 #endif // USE_LIBUNWIND