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