Skip to content

Commit 65086c2

Browse files
committed
refactor(platform): use abseil for POSIX stack traces
Replace glibc backtrace()/backtrace_symbols() with abseil's GetStackTrace()/Symbolize() in debug builds on Linux/POSIX. The previous implementation produced raw addresses requiring manual addr2line translation. The new implementation produces demangled function names (e.g. onnxruntime::InferenceSession::Run) directly in exception messages. No new dependency is introduced — absl::stacktrace and absl::symbolize are already linked via ABSEIL_LIBS. Windows stack traces (C++23 <stacktrace>) are unchanged. Closes #26257
1 parent 245118a commit 65086c2

2 files changed

Lines changed: 111 additions & 29 deletions

File tree

onnxruntime/core/platform/posix/stacktrace.cc

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,114 @@
33

44
#include "core/common/common.h"
55

6-
#if !defined(__ANDROID__) && !defined(__wasm__) && !defined(_OPSCHEMA_LIB_) && !defined(_AIX)
7-
#include <execinfo.h>
8-
#endif
96
#include <vector>
107

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+
1180
namespace onnxruntime {
1281

1382
std::vector<std::string> GetStackTrace() {
1483
std::vector<std::string> stack;
1584

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);
4192

42-
free(strings);
93+
// Attempt to resolve file:line via addr2line (best-effort, debug builds only).
94+
auto resolved = ResolveWithAddr2Line(addresses, depth);
4395

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+
}
44114
#endif
45115

46116
return stack;

onnxruntime/core/session/environment.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
#include "orttraining/core/optimizer/graph_transformer_registry.h"
6161
#endif
6262

63+
#if !defined(__ANDROID__) && !defined(__wasm__) && !defined(_AIX)
64+
#include "absl/debugging/symbolize.h"
65+
#endif
66+
6367
#if defined(USE_CUDA) || defined(USE_CUDA_PROVIDER_INTERFACE)
6468
#include "core/providers/cuda/cuda_provider_factory.h"
6569
#include "core/providers/cuda/cuda_execution_provider_info.h"
@@ -69,6 +73,7 @@ using namespace ::onnxruntime::common;
6973
using namespace ONNX_NAMESPACE;
7074

7175
std::once_flag schemaRegistrationOnceFlag;
76+
std::once_flag symbolizerInitOnceFlag;
7277
#if defined(USE_CUDA) || defined(USE_CUDA_PROVIDER_INTERFACE)
7378
ProviderInfo_CUDA& GetProviderInfo_CUDA();
7479
#endif // defined(USE_CUDA) || defined(USE_CUDA_PROVIDER_INTERFACE)
@@ -268,6 +273,13 @@ Status Environment::Initialize(std::unique_ptr<logging::LoggingManager> logging_
268273
ORT_RETURN_IF_ERROR(SetGlobalThreadingOptions(*tp_options));
269274
}
270275

276+
// Initialize abseil symbolizer for readable stack traces in debug builds.
277+
#if !defined(__ANDROID__) && !defined(__wasm__) && !defined(_AIX)
278+
std::call_once(symbolizerInitOnceFlag, []() {
279+
absl::InitializeSymbolizer(nullptr);
280+
});
281+
#endif
282+
271283
ORT_TRY {
272284
#if !defined(ORT_MINIMAL_BUILD)
273285
// Register Microsoft domain with min/max op_set version as 1/1.

0 commit comments

Comments
 (0)