-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
325 lines (271 loc) · 11.3 KB
/
main.cpp
File metadata and controls
325 lines (271 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// MIT License
// Copyright (c) 2025 dbjwhs
#include <execinfo.h>
#include <iostream>
#include <memory>
#include <cxxabi.h>
#include <cstdlib>
#include <string>
#include "../../headers/project_utils.hpp"
// Debug Stack Trace Pattern with Integrated Logging
//
// This pattern combines traditional Unix backtrace functionality with modern C++ logging to create a comprehensive
// debugging solution. The approach has its roots in early Unix debugging tools like dbx and gdb, but modernizes
// the concept by integrating with structured logging systems. This pattern emerged from the need to capture
// stack traces at runtime for error reporting while maintaining clean, readable log output.
//
// The pattern works by wrapping the POSIX backtrace() and backtrace_symbols() functions with C++ RAII principles
// and integrating them with macro-based logging systems. The backtrace() function captures the current call stack
// by walking the stack frames, while backtrace_symbols() converts the raw addresses into human-readable symbols.
// The integration with logging macros allows for consistent formatting and output routing.
//
// Key components of this pattern include:
// 1. Stack frame capture using backtrace() - captures raw stack addresses
// 2. Symbol resolution using backtrace_symbols() - converts addresses to function names
// 3. C++ name demangling using abi::__cxa_demangle() - makes C++ function names readable
// 4. Integration with structured logging - ensures consistent output formatting
// 5. RAII memory management - automatic cleanup of backtrace_symbols() allocated memory
//
// Common usage patterns:
// - Exception handling: Capture stack traces when exceptions are thrown
// - Error reporting: Include stack context in error logs for debugging
// - Performance debugging: Track call paths in performance-critical code
// - Debug builds: Enable detailed tracing in development environments
// - Crash reporting: Generate stack traces for post-mortem analysis
//
// This pattern is particularly valuable in large codebases where understanding the call chain leading to an
// error is crucial for debugging. It provides a bridge between low-level debugging information and high-level
// application logging, making it easier to diagnose issues in production environments.
class stacktrace_capture {
private:
std::vector<void*> addresses_;
std::vector<std::string> raw_symbols_;
void capture_addresses(int max_frames = 20) {
addresses_.resize(max_frames);
const int actual_size = backtrace(addresses_.data(), max_frames);
addresses_.resize(actual_size);
}
void resolve_symbols() {
if (addresses_.empty()) return;
// Use RAII wrapper for backtrace_symbols
struct backtrace_symbols_deleter {
void operator()(char** ptr) const {
if (ptr) free(ptr);
}
};
const std::unique_ptr<char *, backtrace_symbols_deleter> symbols(
backtrace_symbols(addresses_.data(), static_cast<int>(addresses_.size()))
);
if (!symbols) {
LOG_ERROR_PRINT("Failed to resolve backtrace symbols");
return;
}
raw_symbols_.reserve(addresses_.size());
for (size_t i = 0; i < addresses_.size(); ++i) {
raw_symbols_.emplace_back(symbols.get()[i]);
}
}
[[nodiscard]] std::string demangle_symbol(const std::string& mangled_name) const {
if (mangled_name.empty() || mangled_name.substr(0, 2) == "0x") {
return mangled_name;
}
struct demangle_deleter {
void operator()(char* ptr) const {
if (ptr) {
free(ptr);
}
}
};
int status;
const std::unique_ptr<char, demangle_deleter> demangled(
abi::__cxa_demangle(mangled_name.c_str(), nullptr, nullptr, &status)
);
return (status == 0 && demangled) ? std::string(demangled.get()) : mangled_name;
}
[[nodiscard]] std::string extract_and_demangle_frame(const std::string& raw_frame) const {
// macOS format: "frame_number binary_name address mangled_name + offset"
const size_t plus_pos = raw_frame.rfind(" + ");
if (plus_pos == std::string::npos) {
return raw_frame; // Can't parse, return as-is
}
size_t name_start = raw_frame.rfind(' ', plus_pos - 1);
if (name_start == std::string::npos) {
return raw_frame; // Can't find name start
}
name_start++; // Move past the space
std::string mangled_name = raw_frame.substr(name_start, plus_pos - name_start);
// Clean whitespace
mangled_name.erase(0, mangled_name.find_first_not_of(" \t"));
mangled_name.erase(mangled_name.find_last_not_of(" \t") + 1);
// Reconstruct the frame with demangled name
if (const std::string demangled_name = demangle_symbol(mangled_name); demangled_name != mangled_name) {
return raw_frame.substr(0, name_start) + demangled_name + raw_frame.substr(plus_pos);
}
return raw_frame;
}
public:
stacktrace_capture() {
capture_addresses();
resolve_symbols();
}
[[nodiscard]] const std::vector<std::string>& get_raw_symbols() const {
return raw_symbols_;
}
[[nodiscard]] std::vector<std::string> get_demangled_frames() const {
std::vector<std::string> demangled_frames;
demangled_frames.reserve(raw_symbols_.size());
for (const auto& raw_frame : raw_symbols_) {
demangled_frames.push_back(extract_and_demangle_frame(raw_frame));
}
return demangled_frames;
}
[[nodiscard]] size_t frame_count() const {
return addresses_.size();
}
};
void print_stacktrace() {
const stacktrace_capture capture;
LOG_INFO_PRINT("Stack trace ({} frames):", capture.frame_count());
const auto& raw_symbols = capture.get_raw_symbols();
auto demangled_frames = capture.get_demangled_frames();
// Debug output showing the processing steps
for (size_t ndx = 0; ndx < raw_symbols.size(); ++ndx) {
LOG_INFO_PRINT(" Raw frame: {}", raw_symbols[ndx]);
// Extract mangled name for debug output
const std::string& raw_frame = raw_symbols[ndx];
if (const size_t plus_pos = raw_frame.rfind(" + "); plus_pos != std::string::npos) {
if (size_t name_start = raw_frame.rfind(' ', plus_pos - 1); name_start != std::string::npos) {
name_start++;
std::string mangled = raw_frame.substr(name_start, plus_pos - name_start);
mangled.erase(0, mangled.find_first_not_of(" \t"));
mangled.erase(mangled.find_last_not_of(" \t") + 1);
LOG_INFO_PRINT(" Extracted symbol: '{}'", mangled);
// Check if this looks like a mangled C++ name (starts with _Z)
if (!mangled.empty() && mangled.length() > 2 && mangled.substr(0, 2) == "_Z") {
LOG_INFO_PRINT(" [{}] Found mangled C++ symbol: {}", ndx, mangled);
} else if (!mangled.empty() && mangled.substr(0, 2) != "0x") {
LOG_INFO_PRINT(" [{}] Found C symbol or already demangled: {}", ndx, mangled);
} else {
LOG_INFO_PRINT(" [{}] Address only: {}", ndx, mangled);
}
}
}
LOG_INFO_PRINT(" [{}] {}", ndx, demangled_frames[ndx]);
}
// Output the final cleaned stack trace
LOG_INFO_PRINT("=== FINAL DEMANGLED STACK TRACE ===");
for (size_t ndx = 0; ndx < demangled_frames.size(); ++ndx) {
LOG_INFO_PRINT("[{}] {}", ndx, demangled_frames[ndx]);
}
LOG_INFO_PRINT("=== END STACK TRACE ===");
}
// Test functions to demonstrate C++ name mangling
namespace test_namespace {
template<typename T>
class complex_class {
public:
static void template_method(const std::vector<T>& data, std::string_view name) {
LOG_INFO_PRINT("In template_method with {} items", data.size());
print_stacktrace();
}
};
void overloaded_function(int x) {
LOG_INFO_PRINT("overloaded_function(int): {}", x);
complex_class<std::string>::template_method({"test", "data"}, "example");
}
void overloaded_function(double x, const std::string& name) {
LOG_INFO_PRINT("overloaded_function(double, string): {} {}", x, name);
print_stacktrace();
}
}
void function_c() {
LOG_INFO_PRINT("In function_c, calling C++ template function");
test_namespace::overloaded_function(42);
}
void function_b() {
LOG_INFO_PRINT("In function_b, calling function_c");
function_c();
}
void function_a() {
LOG_INFO_PRINT("In function_a, calling function_b");
function_b();
}
// some simple test cases
#define ENABLE_TESTS
int main() {
LOG_INFO_PRINT("Starting main, calling function_a");
function_a();
LOG_INFO_PRINT("--- Direct stack trace from main ---");
print_stacktrace();
#ifdef ENABLE_TESTS
void run_tests();
run_tests();
#endif
return 0;
}
#ifdef ENABLE_TESTS
#include <cassert>
#include <cstring>
// my test function to verify demangling works
void test_demangling() {
// Test with a known mangled name
auto test_mangled = "_Z16print_stacktracev";
int status;
if (auto demangled = abi::__cxa_demangle(test_mangled, nullptr, nullptr, &status); status == 0 && demangled) {
LOG_INFO_PRINT("Demangling test: '{}' -> '{}'", test_mangled, demangled);
free(demangled);
} else {
LOG_ERROR_PRINT("Demangling test failed for '{}', status: {}", test_mangled, status);
}
}
void test_stacktrace_capture() {
// Test that we can capture at least a few frames
constexpr int max_frames = 10;
std::vector<void*> array;
array.resize(max_frames);
const int size = backtrace(array.data(), max_frames);
array.resize(size);
assert(size > 0);
assert(size <= max_frames);
// Test that backtrace_symbols doesn't return null
//
// *note* we are using a C raw pointer here :-( I played around with using RAII to
// fix this, this is as far as I got, it too verbose and I don't like it for this
// example, so leaving in the raw pointer/free code.
//
// std::vector<std::string> get_backtrace() {
// void* buffer[256];
// int nptrs = backtrace(buffer, 256);
//
// std::unique_ptr<char*, decltype(&free)> symbols{
// backtrace_symbols(buffer, nptrs), &free
// };
//
// std::vector<std::string> result{};
// if (symbols) {
// result.reserve(nptrs);
// for (int i = 0; i < nptrs; ++i) {
// result.emplace_back(symbols.get()[i]);
// }
// }
//
// return result; // Memory automatically freed
// }
//
char** strings = backtrace_symbols(array.data(), size);
assert(strings != nullptr);
// Verify we get some meaningful output
for (int ndx = 0; ndx < size; ndx++) {
assert(strings[ndx] != nullptr);
assert(strlen(strings[ndx]) > 0);
}
free(strings);
LOG_INFO_PRINT("✓ Stacktrace capture test passed");
}
void run_tests() {
LOG_INFO_PRINT("=== Running Tests ===");
test_demangling();
test_stacktrace_capture();
LOG_INFO_PRINT("=== All Tests Passed ===");
}
#endif