|
3 | 3 |
|
4 | 4 | #include "core/common/common.h" |
5 | 5 |
|
6 | | -#if !defined(__ANDROID__) && !defined(__wasm__) && !defined(_OPSCHEMA_LIB_) && !defined(_AIX) |
7 | | -#include <execinfo.h> |
8 | | -#endif |
9 | 6 | #include <vector> |
10 | 7 |
|
| 8 | +#if !defined(NDEBUG) && !defined(__ANDROID__) && !defined(__wasm__) && !defined(_OPSCHEMA_LIB_) && !defined(_AIX) |
| 9 | +#include <cstdio> |
| 10 | +#include <cstring> |
| 11 | +#include <dlfcn.h> |
| 12 | +#include <sstream> |
| 13 | +#include <unordered_map> |
| 14 | +#include "absl/debugging/stacktrace.h" |
| 15 | +#include "absl/debugging/symbolize.h" |
| 16 | + |
| 17 | +namespace { |
| 18 | + |
| 19 | +// Resolve file offsets to file:line using addr2line, grouped by binary. |
| 20 | +// Returns a map from original address to "file:line" string. |
| 21 | +std::unordered_map<void*, std::string> ResolveWithAddr2Line( |
| 22 | + void** addresses, int depth) { |
| 23 | + std::unordered_map<void*, std::string> result; |
| 24 | + |
| 25 | + // Group addresses by their containing binary/shared-object. |
| 26 | + // Key: binary path, Value: vector of (address, file_offset) |
| 27 | + struct FrameInfo { |
| 28 | + void* addr; |
| 29 | + uintptr_t offset; |
| 30 | + }; |
| 31 | + std::unordered_map<std::string, std::vector<FrameInfo>> groups; |
| 32 | + |
| 33 | + for (int i = 0; i < depth; ++i) { |
| 34 | + Dl_info info; |
| 35 | + if (dladdr(addresses[i], &info) && info.dli_fname) { |
| 36 | + uintptr_t offset = reinterpret_cast<uintptr_t>(addresses[i]) - |
| 37 | + reinterpret_cast<uintptr_t>(info.dli_fbase); |
| 38 | + groups[info.dli_fname].push_back({addresses[i], offset}); |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + // Call addr2line once per binary with all offsets in batch. |
| 43 | + for (const auto& [binary, frames] : groups) { |
| 44 | + // Use addr2line without -f/-C/-p to get just "file:line" per address. |
| 45 | + std::string cmd = "addr2line -e " + binary; |
| 46 | + for (const auto& frame : frames) { |
| 47 | + char buf[32]; |
| 48 | + snprintf(buf, sizeof(buf), " 0x%lx", static_cast<unsigned long>(frame.offset)); |
| 49 | + cmd += buf; |
| 50 | + } |
| 51 | + cmd += " 2>/dev/null"; |
| 52 | + |
| 53 | + FILE* pipe = popen(cmd.c_str(), "r"); |
| 54 | + if (!pipe) continue; |
| 55 | + |
| 56 | + // Read one line per address from addr2line output. |
| 57 | + char line_buf[1024]; |
| 58 | + size_t frame_idx = 0; |
| 59 | + while (fgets(line_buf, sizeof(line_buf), pipe) && frame_idx < frames.size()) { |
| 60 | + // Remove trailing newline |
| 61 | + size_t len = strlen(line_buf); |
| 62 | + if (len > 0 && line_buf[len - 1] == '\n') line_buf[len - 1] = '\0'; |
| 63 | + |
| 64 | + std::string resolved(line_buf); |
| 65 | + // addr2line returns "?? ??:0" or similar for unknown frames — skip those |
| 66 | + if (resolved.find("??") == std::string::npos) { |
| 67 | + result[frames[frame_idx].addr] = resolved; |
| 68 | + } |
| 69 | + ++frame_idx; |
| 70 | + } |
| 71 | + pclose(pipe); |
| 72 | + } |
| 73 | + |
| 74 | + return result; |
| 75 | +} |
| 76 | + |
| 77 | +} // namespace |
| 78 | +#endif |
| 79 | + |
11 | 80 | namespace onnxruntime { |
12 | 81 |
|
13 | 82 | std::vector<std::string> GetStackTrace() { |
14 | 83 | std::vector<std::string> stack; |
15 | 84 |
|
16 | | -#if !defined(NDEBUG) && !defined(__ANDROID__) && !defined(__wasm__) && !defined(_OPSCHEMA_LIB_) |
17 | | - constexpr int kCallstackLimit = 64; // Maximum depth of callstack |
18 | | - |
19 | | - void* array[kCallstackLimit]; |
20 | | - char** strings = nullptr; |
21 | | - |
22 | | - int size = backtrace(array, kCallstackLimit); |
23 | | - stack.reserve(size); |
24 | | - strings = backtrace_symbols(array, size); |
25 | | - |
26 | | - // NOTE: To get meaningful info from the output, addr2line (or atos on osx) would need to be used. |
27 | | - // See https://gist.github.com/jvranish/4441299 for an example. |
28 | | - // |
29 | | - // To manually translate the output, use the value in the '()' after the executable name with addr2line |
30 | | - // e.g. |
31 | | - // Stacktrace: |
32 | | - // /home/me/src/github/onnxruntime/build/Linux/Debug/onnxruntime_test_all(+0x3f46cc) [0x559543faf6cc] |
33 | | - // |
34 | | - // >addr2line -f -C -e /home/me/src/github/onnxruntime/build/Linux/Debug/onnxruntime_test_all +0x3f46cc |
35 | | - |
36 | | - // hide GetStackTrace so the output starts with the 'real' location |
37 | | - constexpr int start_frame = 1; |
38 | | - for (int i = start_frame; i < size; i++) { |
39 | | - stack.push_back(strings[i]); |
40 | | - } |
| 85 | +#if !defined(NDEBUG) && !defined(__ANDROID__) && !defined(__wasm__) && !defined(_OPSCHEMA_LIB_) && !defined(_AIX) |
| 86 | + constexpr int kCallstackLimit = 64; |
| 87 | + void* addresses[kCallstackLimit]; |
| 88 | + |
| 89 | + // skip_count=2 hides GetStackTrace and its caller (ORT_ENFORCE macro internals) |
| 90 | + int depth = absl::GetStackTrace(addresses, kCallstackLimit, /*skip_count=*/2); |
| 91 | + stack.reserve(depth); |
41 | 92 |
|
42 | | - free(strings); |
| 93 | + // Attempt to resolve file:line via addr2line (best-effort, debug builds only). |
| 94 | + auto resolved = ResolveWithAddr2Line(addresses, depth); |
43 | 95 |
|
| 96 | + for (int i = 0; i < depth; ++i) { |
| 97 | + std::ostringstream oss; |
| 98 | + char symbol[1024]; |
| 99 | + if (absl::Symbolize(addresses[i], symbol, sizeof(symbol))) { |
| 100 | + oss << symbol; |
| 101 | + } else { |
| 102 | + oss << "[unknown]"; |
| 103 | + } |
| 104 | + |
| 105 | + // Append file:line if resolved, otherwise the raw address as fallback. |
| 106 | + auto it = resolved.find(addresses[i]); |
| 107 | + if (it != resolved.end()) { |
| 108 | + oss << " at " << it->second; |
| 109 | + } else { |
| 110 | + oss << " [" << addresses[i] << "]"; |
| 111 | + } |
| 112 | + stack.push_back(oss.str()); |
| 113 | + } |
44 | 114 | #endif |
45 | 115 |
|
46 | 116 | return stack; |
|
0 commit comments