1 | #include <c10/util/Backtrace.h> |
2 | #include <c10/util/Optional.h> |
3 | #include <c10/util/Type.h> |
4 | #include <c10/util/irange.h> |
5 | |
6 | #include <functional> |
7 | #include <memory> |
8 | #include <sstream> |
9 | #include <string> |
10 | #include <vector> |
11 | |
12 | #ifdef _MSC_VER |
13 | #include <c10/util/win32-headers.h> |
14 | #include <iomanip> |
15 | #pragma comment(lib, "Dbghelp.lib") |
16 | #endif |
17 | |
18 | #if SUPPORTS_BACKTRACE |
19 | #include <cxxabi.h> |
20 | #ifdef C10_ANDROID |
21 | #include <dlfcn.h> |
22 | #include <unwind.h> |
23 | #else |
24 | #include <execinfo.h> |
25 | #endif |
26 | #endif |
27 | |
28 | #ifdef FBCODE_CAFFE2 |
29 | #include <common/process/StackTrace.h> |
30 | #endif |
31 | |
32 | namespace c10 { |
33 | |
34 | #if SUPPORTS_BACKTRACE && defined(C10_ANDROID) |
35 | |
36 | struct AndroidBacktraceState { |
37 | std::vector<void*> buffer; |
38 | }; |
39 | |
40 | _Unwind_Reason_Code android_unwind_callback( |
41 | struct _Unwind_Context* context, |
42 | void* arg) { |
43 | AndroidBacktraceState* state = (AndroidBacktraceState*)arg; |
44 | uintptr_t pc = _Unwind_GetIP(context); |
45 | if (pc) { |
46 | state->buffer.emplace_back(reinterpret_cast<void*>(pc)); |
47 | } |
48 | return _URC_NO_REASON; |
49 | } |
50 | |
51 | void dump_stack( |
52 | std::ostream& os, |
53 | size_t frames_to_skip, |
54 | size_t maximum_number_of_frames) { |
55 | AndroidBacktraceState state; |
56 | |
57 | _Unwind_Backtrace(android_unwind_callback, &state); |
58 | |
59 | int idx = 0; |
60 | char* demangled = nullptr; |
61 | size_t length = 0; |
62 | |
63 | for (const void* addr : state.buffer) { |
64 | const char* symbol = "" ; |
65 | |
66 | Dl_info info; |
67 | if (dladdr(addr, &info) && info.dli_sname) { |
68 | symbol = info.dli_sname; |
69 | } |
70 | |
71 | int status = 0; |
72 | demangled = __cxxabiv1::__cxa_demangle( |
73 | /*mangled_name*/ symbol, |
74 | /*output_buffer*/ demangled, |
75 | /*length*/ &length, |
76 | /*status*/ &status); |
77 | |
78 | os << " frame #" << idx++ << "\t" |
79 | << ((demangled != NULL && status == 0) ? demangled : symbol) << "[" |
80 | << addr << "]\t" << std::endl; |
81 | } |
82 | free(demangled); |
83 | } |
84 | |
85 | #endif /* SUPPORTS_BACKTRACE && defined(C10_ANDROID) */ |
86 | |
87 | #if SUPPORTS_BACKTRACE |
88 | namespace { |
89 | |
90 | struct FrameInformation { |
91 | /// If available, the demangled name of the function at this frame, else |
92 | /// whatever (possibly mangled) name we got from `backtrace()`. |
93 | std::string function_name; |
94 | /// This is a number in hexadecimal form (e.g. "0xdead") representing the |
95 | /// offset into the function's machine code at which the function's body |
96 | /// starts, i.e. skipping the "prologue" that handles stack manipulation and |
97 | /// other calling convention things. |
98 | std::string offset_into_function; |
99 | /// NOTE: In debugger parlance, the "object file" refers to the ELF file that |
100 | /// the symbol originates from, i.e. either an executable or a library. |
101 | std::string object_file; |
102 | }; |
103 | |
104 | #ifndef C10_ANDROID |
105 | bool is_python_frame(const FrameInformation& frame) { |
106 | return frame.object_file == "python" || frame.object_file == "python3" || |
107 | (frame.object_file.find("libpython" ) != std::string::npos); |
108 | } |
109 | |
110 | c10::optional<FrameInformation> parse_frame_information( |
111 | const std::string& frame_string) { |
112 | FrameInformation frame; |
113 | |
114 | // This is the function name in the CXX ABI mangled format, e.g. something |
115 | // like _Z1gv. Reference: |
116 | // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling |
117 | std::string mangled_function_name; |
118 | |
119 | #if defined(__GLIBCXX__) |
120 | // In GLIBCXX, `frame_string` follows the pattern |
121 | // `<object-file>(<mangled-function-name>+<offset-into-function>) |
122 | // [<return-address>]` |
123 | |
124 | auto function_name_start = frame_string.find('('); |
125 | if (function_name_start == std::string::npos) { |
126 | return c10::nullopt; |
127 | } |
128 | function_name_start += 1; |
129 | |
130 | auto offset_start = frame_string.find('+', function_name_start); |
131 | if (offset_start == std::string::npos) { |
132 | return c10::nullopt; |
133 | } |
134 | offset_start += 1; |
135 | |
136 | const auto offset_end = frame_string.find(')', offset_start); |
137 | if (offset_end == std::string::npos) { |
138 | return c10::nullopt; |
139 | } |
140 | |
141 | frame.object_file = frame_string.substr(0, function_name_start - 1); |
142 | frame.offset_into_function = |
143 | frame_string.substr(offset_start, offset_end - offset_start); |
144 | |
145 | // NOTE: We don't need to parse the return address because |
146 | // we already have it from the call to `backtrace()`. |
147 | |
148 | mangled_function_name = frame_string.substr( |
149 | function_name_start, (offset_start - 1) - function_name_start); |
150 | #elif defined(_LIBCPP_VERSION) |
151 | // In LIBCXX, The pattern is |
152 | // `<frame number> <object-file> <return-address> <mangled-function-name> + |
153 | // <offset-into-function>` |
154 | std::string skip; |
155 | std::istringstream input_stream(frame_string); |
156 | // operator>>() does not fail -- if the input stream is corrupted, the |
157 | // strings will simply be empty. |
158 | input_stream >> skip >> frame.object_file >> skip >> mangled_function_name >> |
159 | skip >> frame.offset_into_function; |
160 | #else |
161 | #warning Unknown standard library, backtraces may have incomplete debug information |
162 | return c10::nullopt; |
163 | #endif // defined(__GLIBCXX__) |
164 | |
165 | // Some system-level functions don't have sufficient debug information, so |
166 | // we'll display them as "<unknown function>". They'll still have a return |
167 | // address and other pieces of information. |
168 | if (mangled_function_name.empty()) { |
169 | frame.function_name = "<unknown function>" ; |
170 | return frame; |
171 | } |
172 | |
173 | frame.function_name = demangle(mangled_function_name.c_str()); |
174 | return frame; |
175 | } |
176 | #endif /* !defined(C10_ANDROID) */ |
177 | } // anonymous namespace |
178 | #elif defined(_MSC_VER) |
179 | namespace { |
180 | const int max_name_len = 256; |
181 | std::string get_module_base_name(void* addr) { |
182 | HMODULE h_module; |
183 | char module[max_name_len]; |
184 | strcpy(module, "" ); |
185 | GetModuleHandleEx( |
186 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | |
187 | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, |
188 | (LPCTSTR)addr, |
189 | &h_module); |
190 | if (h_module != NULL) { |
191 | GetModuleFileNameA(h_module, module, max_name_len); |
192 | } |
193 | char* last_slash_pos = strrchr(module, '\\'); |
194 | if (last_slash_pos) { |
195 | std::string module_base_name(last_slash_pos + 1); |
196 | return module_base_name; |
197 | } else { |
198 | std::string module_base_name(module); |
199 | return module_base_name; |
200 | } |
201 | } |
202 | class SymbolHelper { |
203 | public: |
204 | static SymbolHelper& getInstance() { |
205 | static SymbolHelper instance; |
206 | return instance; |
207 | } |
208 | bool inited = false; |
209 | HANDLE process; |
210 | |
211 | private: |
212 | SymbolHelper() { |
213 | process = GetCurrentProcess(); |
214 | DWORD flags = SymGetOptions(); |
215 | SymSetOptions(flags | SYMOPT_DEFERRED_LOADS); |
216 | inited = SymInitialize(process, NULL, TRUE); |
217 | } |
218 | ~SymbolHelper() { |
219 | if (inited) { |
220 | SymCleanup(process); |
221 | } |
222 | } |
223 | |
224 | public: |
225 | SymbolHelper(SymbolHelper const&) = delete; |
226 | void operator=(SymbolHelper const&) = delete; |
227 | }; |
228 | } // anonymous namespace |
229 | #endif // SUPPORTS_BACKTRACE |
230 | |
231 | std::string get_backtrace( |
232 | size_t frames_to_skip, |
233 | size_t maximum_number_of_frames, |
234 | bool skip_python_frames) { |
235 | #ifdef FBCODE_CAFFE2 |
236 | // For some reason, the stacktrace implementation in fbcode is |
237 | // better than ours, see https://github.com/pytorch/pytorch/issues/56399 |
238 | // When it's available, just use that. |
239 | facebook::process::StackTrace st; |
240 | return st.toString(); |
241 | |
242 | #elif SUPPORTS_BACKTRACE && !defined(C10_ANDROID) |
243 | |
244 | // We always skip this frame (backtrace). |
245 | frames_to_skip += 1; |
246 | |
247 | std::vector<void*> callstack( |
248 | frames_to_skip + maximum_number_of_frames, nullptr); |
249 | // backtrace() gives us a list of return addresses in the current call stack. |
250 | // NOTE: As per man (3) backtrace it can never fail |
251 | // (http://man7.org/linux/man-pages/man3/backtrace.3.html). |
252 | auto number_of_frames = |
253 | ::backtrace(callstack.data(), static_cast<int>(callstack.size())); |
254 | |
255 | // Skip as many frames as requested. This is not efficient, but the sizes here |
256 | // are small and it makes the code nicer and safer. |
257 | for (; frames_to_skip > 0 && number_of_frames > 0; |
258 | --frames_to_skip, --number_of_frames) { |
259 | callstack.erase(callstack.begin()); |
260 | } |
261 | |
262 | // `number_of_frames` is strictly less than the current capacity of |
263 | // `callstack`, so this is just a pointer subtraction and makes the subsequent |
264 | // code safer. |
265 | callstack.resize(static_cast<size_t>(number_of_frames)); |
266 | |
267 | // `backtrace_symbols` takes the return addresses obtained from `backtrace()` |
268 | // and fetches string representations of each stack. Unfortunately it doesn't |
269 | // return a struct of individual pieces of information but a concatenated |
270 | // string, so we'll have to parse the string after. NOTE: The array returned |
271 | // by `backtrace_symbols` is malloc'd and must be manually freed, but not the |
272 | // strings inside the array. |
273 | std::unique_ptr<char*, std::function<void(char**)>> raw_symbols( |
274 | ::backtrace_symbols(callstack.data(), static_cast<int>(callstack.size())), |
275 | /*deleter=*/free); |
276 | const std::vector<std::string> symbols( |
277 | raw_symbols.get(), raw_symbols.get() + callstack.size()); |
278 | |
279 | // The backtrace string goes into here. |
280 | std::ostringstream stream; |
281 | |
282 | // Toggles to true after the first skipped python frame. |
283 | bool has_skipped_python_frames = false; |
284 | |
285 | for (const auto frame_number : c10::irange(callstack.size())) { |
286 | const auto frame = parse_frame_information(symbols[frame_number]); |
287 | |
288 | if (skip_python_frames && frame && is_python_frame(*frame)) { |
289 | if (!has_skipped_python_frames) { |
290 | stream << "<omitting python frames>\n" ; |
291 | has_skipped_python_frames = true; |
292 | } |
293 | continue; |
294 | } |
295 | |
296 | // frame #<number>: |
297 | stream << "frame #" << frame_number << ": " ; |
298 | |
299 | if (frame) { |
300 | // <function_name> + <offset> (<return-address> in <object-file>) |
301 | stream << frame->function_name << " + " << frame->offset_into_function |
302 | << " (" << callstack[frame_number] << " in " << frame->object_file |
303 | << ")\n" ; |
304 | } else { |
305 | // In the edge-case where we couldn't parse the frame string, we can |
306 | // just use it directly (it may have a different format). |
307 | stream << symbols[frame_number] << "\n" ; |
308 | } |
309 | } |
310 | |
311 | return stream.str(); |
312 | |
313 | #elif SUPPORTS_BACKTRACE && defined(C10_ANDROID) |
314 | |
315 | std::ostringstream oss; |
316 | dump_stack(oss, frames_to_skip, maximum_number_of_frames); |
317 | return oss.str().c_str(); |
318 | |
319 | #elif defined(_MSC_VER) // !SUPPORTS_BACKTRACE |
320 | // This backtrace retrieval is implemented on Windows via the Windows |
321 | // API using `CaptureStackBackTrace`, `SymFromAddr` and |
322 | // `SymGetLineFromAddr64`. |
323 | // https://stackoverflow.com/questions/5693192/win32-backtrace-from-c-code |
324 | // https://stackoverflow.com/questions/26398064/counterpart-to-glibcs-backtrace-and-backtrace-symbols-on-windows |
325 | // https://docs.microsoft.com/en-us/windows/win32/debug/capturestackbacktrace |
326 | // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symfromaddr |
327 | // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symgetlinefromaddr64 |
328 | // TODO: Support skipping python frames |
329 | |
330 | // We always skip this frame (backtrace). |
331 | frames_to_skip += 1; |
332 | |
333 | DWORD64 displacement; |
334 | DWORD disp; |
335 | std::unique_ptr<IMAGEHLP_LINE64> line; |
336 | |
337 | char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; |
338 | PSYMBOL_INFO p_symbol = (PSYMBOL_INFO)buffer; |
339 | |
340 | std::unique_ptr<void*[]> back_trace(new void*[maximum_number_of_frames]); |
341 | bool with_symbol = false; |
342 | bool with_line = false; |
343 | |
344 | // The backtrace string goes into here. |
345 | std::ostringstream stream; |
346 | |
347 | // Get the frames |
348 | const USHORT n_frame = CaptureStackBackTrace( |
349 | static_cast<DWORD>(frames_to_skip), |
350 | static_cast<DWORD>(maximum_number_of_frames), |
351 | back_trace.get(), |
352 | NULL); |
353 | |
354 | // Initialize symbols if necessary |
355 | SymbolHelper& sh = SymbolHelper::getInstance(); |
356 | |
357 | for (USHORT i_frame = 0; i_frame < n_frame; ++i_frame) { |
358 | // Get the address and the name of the symbol |
359 | if (sh.inited) { |
360 | p_symbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
361 | p_symbol->MaxNameLen = MAX_SYM_NAME; |
362 | with_symbol = SymFromAddr( |
363 | sh.process, (ULONG64)back_trace[i_frame], &displacement, p_symbol); |
364 | } |
365 | |
366 | // Get the line number and the module |
367 | if (sh.inited) { |
368 | line.reset(new IMAGEHLP_LINE64()); |
369 | line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
370 | with_line = SymGetLineFromAddr64( |
371 | sh.process, (ULONG64)back_trace[i_frame], &disp, line.get()); |
372 | } |
373 | |
374 | // Get the module basename |
375 | std::string module = get_module_base_name(back_trace[i_frame]); |
376 | |
377 | // The pattern on Windows is |
378 | // `<return-address> <symbol-address> |
379 | // <module-name>!<demangled-function-name> [<file-name> @ <line-number>] |
380 | stream << std::setfill('0') << std::setw(16) << std::uppercase << std::hex |
381 | << back_trace[i_frame] << std::dec; |
382 | if (with_symbol) { |
383 | stream << std::setfill('0') << std::setw(16) << std::uppercase << std::hex |
384 | << p_symbol->Address << std::dec << " " << module << "!" |
385 | << p_symbol->Name; |
386 | } else { |
387 | stream << " <unknown symbol address> " << module << "!<unknown symbol>" ; |
388 | } |
389 | stream << " [" ; |
390 | if (with_line) { |
391 | stream << line->FileName << " @ " << line->LineNumber; |
392 | } else { |
393 | stream << "<unknown file> @ <unknown line number>" ; |
394 | } |
395 | stream << "]" << std::endl; |
396 | } |
397 | |
398 | return stream.str(); |
399 | #else // !SUPPORTS_BACKTRACE && !_WIN32 |
400 | return "(no backtrace available)" ; |
401 | #endif // SUPPORTS_BACKTRACE |
402 | } |
403 | |
404 | } // namespace c10 |
405 | |