This is the most robust method for resolving traces while a signal handler is running. It involves two parts: a main program that collects data and a tracer program that resolves it.
1. In the main program
- Warmup: Call
safe_generate_raw_trace and get_safe_object_frame in main() to ensure all shared libraries are loaded. - Handler: In the signal handler, generate a raw trace, then use
get_safe_object_frame to convert raw pointers into safe_object_frame structures. - Communication:
fork() a child process, pass the safe_object_frame data to it via a pipe, and exec() a separate tracer executable.
2. In the tracer program
- Read
safe_object_frame structures from stdin (the pipe). - Call
.resolve() on each frame to convert it to a standard object_frame. - Add them to a
cpptrace::object_trace and call .resolve().print() to output the final trace.
// --- MAIN PROGRAM SNIPPET ---
void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t count) {
pipe_t input_pipe;
pipe(input_pipe.data);
const pid_t pid = fork();
if(pid == 0) { // child
dup2(input_pipe.read_end, STDIN_FILENO);
close(input_pipe.read_end);
close(input_pipe.write_end);
execl("signal_tracer", "signal_tracer", nullptr);
_exit(1);
}
for(std::size_t i = 0; i < count; i++) {
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[i], &frame);
write(input_pipe.write_end, &frame, sizeof(frame));
}
close(input_pipe.read_end);
close(input_pipe.write_end);
waitpid(pid, nullptr, 0);
}
void warmup_cpptrace() {
cpptrace::frame_ptr buffer[10];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[0], &frame);
}
// --- TRACER PROGRAM SNIPPET ---
int main() {
cpptrace::object_trace trace;
while(true) {
cpptrace::safe_object_frame frame;
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if(res == 0) break;
else if(res == 1) {
trace.frames.push_back(frame.resolve());
} else {
break;
}
}
trace.resolve().print();
}