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
32namespace c10 {
33
34#if SUPPORTS_BACKTRACE && defined(C10_ANDROID)
35
36struct 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
51void 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
88namespace {
89
90struct 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
105bool 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
110c10::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)
179namespace {
180const int max_name_len = 256;
181std::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}
202class 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
231std::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