Rework the object trace interface a bit and clarify their purpose. Also rename minimal_object_trace to safe_object_trace

This commit is contained in:
Jeremy 2023-11-20 23:01:19 -06:00
parent a04f19a484
commit 2a4a8066d3
No known key found for this signature in database
GPG Key ID: B4C8300FEC395042
10 changed files with 35 additions and 42 deletions

View File

@ -178,15 +178,14 @@ namespace cpptrace {
### Object Traces
Object traces are somewhat minimal stack traces with basic information on which binary a frame corresponds to, any
symbol name libdl (in linux/macos) was able to resolve, the raw program counter and the program counter translated to
the corresponding object file's memory space.
Object traces contain the most basic information needed to construct a stack trace outside the currently running
executable. It contains the raw address, the address in the binary (ASLR and the object file's memory space and whatnot
is resolved), and the path to the object the instruction pointer is located in.
```cpp
namespace cpptrace {
struct object_frame {
std::string obj_path;
std::string symbol;
frame_ptr raw_address;
frame_ptr obj_address;
};
@ -409,19 +408,19 @@ The safe API is as follows:
namespace cpptrace {
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip = 0);
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
struct minimal_object_frame {
struct safe_object_frame {
frame_ptr raw_address;
frame_ptr address_relative_to_object_base_in_memory;
char object_path[CPPTRACE_PATH_MAX + 1];
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
};
void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out);
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
}
```
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
way then `get_minimal_object_frame` will not populate fields beyond the `raw_address`.
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
**Another big note:** Calls to shared objects can be lazy-loaded where the first call to the shared object invokes
non-signal-safe functions such as `malloc()`. To avoid this, call these routines in `main()` ahead of a signal handler

View File

@ -55,7 +55,6 @@ namespace cpptrace {
struct CPPTRACE_EXPORT object_frame {
std::string obj_path;
std::string symbol;
frame_ptr raw_address;
frame_ptr obj_address;
};
@ -194,7 +193,7 @@ namespace cpptrace {
std::size_t skip,
std::size_t max_depth
);
struct CPPTRACE_EXPORT minimal_object_frame {
struct CPPTRACE_EXPORT safe_object_frame {
frame_ptr raw_address;
frame_ptr address_relative_to_object_base_in_memory;
char object_path[CPPTRACE_PATH_MAX + 1];
@ -202,7 +201,7 @@ namespace cpptrace {
object_frame resolve() const;
};
// signal-safe
CPPTRACE_EXPORT void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out);
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
// utilities:
CPPTRACE_EXPORT std::string demangle(const std::string& name);

View File

@ -44,7 +44,7 @@ namespace cpptrace {
// signal-safe
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
struct minimal_object_frame {
struct safe_object_frame {
frame_ptr raw_address;
frame_ptr address_relative_to_object_base_in_memory;
char object_path[CPPTRACE_PATH_MAX + 1];
@ -52,7 +52,7 @@ namespace cpptrace {
};
// signal-safe
void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out);
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
}
```
@ -61,7 +61,7 @@ information to resolve a stack trace in the currently running process after a si
sufficient for resolving outside of the currently running process, unless there is no position-independent code or
shared-library code.
To resolve outside the current process `minimal_object_frame` information is needed. This contains the path to the
To resolve outside the current process `safe_object_frame` information is needed. This contains the path to the
object where the address is located as well as the address before address randomization.
# Strategy
@ -69,10 +69,10 @@ object where the address is located as well as the address before address random
Signal-safe tracing can be done three ways:
- In a signal handler, call `safe_generate_raw_trace` and then outside a signal handler
construct a `cpptrace:raw_trace` and resolve.
- In a signal handler, call `safe_generate_raw_trace`, then write `cpptrace::minimal_object_frame`
- In a signal handler, call `safe_generate_raw_trace`, then write `cpptrace::safe_object_frame`
information to a file to be resolved later.
- In a signal handler, call `safe_generate_raw_trace`, `fork()` and `exec()` a process to handle the
resolution, pass `cpptrace::minimal_object_frame` information to that child through a pipe, and
resolution, pass `cpptrace::safe_object_frame` information to that child through a pipe, and
wait for the child to exit.
It's not as simple as calling `cpptrace::generate_trace().print()`, I know, but these are truly the
@ -82,7 +82,7 @@ only ways to do this safely as far as I can tell.
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
way then `get_minimal_object_frame` will not populate fields beyond the `raw_address`.
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
Currently the only back-end that can unwind safely is libunwind. Currently, the only way I know to get `dladdr`'s
information in a signal-safe manner is `_dl_find_object`, which doesn't exist on macos (or windows of course). If anyone
@ -146,10 +146,10 @@ void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t size) {
execl("signal_tracer", "signal_tracer", nullptr);
_exit(1);
}
// Resolve to minimal_object_frames and write those to the pipe
// Resolve to safe_object_frames and write those to the pipe
for(std::size_t i = 0; i < count; i++) {
cpptrace::minimal_object_frame frame;
cpptrace::get_minimal_object_frame(buffer[i], &frame);
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);
@ -175,8 +175,8 @@ void warmup_cpptrace() {
// This is done for any dynamic-loading shenanigans
cpptrace::frame_ptr buffer[10];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
cpptrace::minimal_object_frame frame;
cpptrace::get_minimal_object_frame(buffer[0], &frame);
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[0], &frame);
}
int main() {
@ -195,7 +195,7 @@ int main() {
## In the tracer program
The tracer program is quite simple. It just has to read `cpptrace::minimal_object_frame`s from the pipe, resolve to
The tracer program is quite simple. It just has to read `cpptrace::safe_object_frame`s from the pipe, resolve to
`cpptrace::object_frame`s, and resolve an `object_trace`.
```cpp
@ -208,7 +208,7 @@ The tracer program is quite simple. It just has to read `cpptrace::minimal_objec
int main() {
cpptrace::object_trace trace;
while(true) {
cpptrace::minimal_object_frame frame;
cpptrace::safe_object_frame frame;
// fread used over read because a read() from a pipe might not read the full frame
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if(res == 0) {

View File

@ -78,7 +78,6 @@ namespace detail {
frame.obj_address = addr
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ get_module_image_base(info.dli_fname);
frame.symbol = info.dli_sname ?: "";
}
frames.push_back(frame);
}
@ -151,10 +150,9 @@ namespace detail {
}
#endif
inline object_frame resolve_minimal_object_frame(const minimal_object_frame& frame) {
inline object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
return {
frame.object_path,
"",
frame.raw_address,
frame.address_relative_to_object_base_in_memory + get_module_image_base(frame.object_path)
};

View File

@ -21,7 +21,7 @@
namespace cpptrace {
namespace detail {
inline void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out) {
inline void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
dl_find_object result;
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) {
out->raw_address = address;
@ -59,7 +59,7 @@ namespace detail {
#else
namespace cpptrace {
namespace detail {
inline void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out) {
inline void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
out->raw_address = address;
out->address_relative_to_object_base_in_memory = 0;
out->object_path[0] = 0;

View File

@ -342,12 +342,12 @@ namespace cpptrace {
}
}
object_frame minimal_object_frame::resolve() const {
return detail::resolve_minimal_object_frame(*this);
object_frame safe_object_frame::resolve() const {
return detail::resolve_safe_object_frame(*this);
}
void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out) {
detail::get_minimal_object_frame(address, out);
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
detail::get_safe_object_frame(address, out);
}
std::string demangle(const std::string& name) {

View File

@ -272,7 +272,6 @@ namespace addr2line {
trace[i].address = frames[i].raw_address;
// Set what is known for now, and resolutions from addr2line should overwrite
trace[i].filename = frames[i].obj_path;
trace[i].symbol = frames[i].symbol;
}
if(has_addr2line()) {
const auto entries = collate_frames(frames, trace);

View File

@ -850,15 +850,13 @@ namespace libdwarf {
frame_with_inlines resolve_frame(const object_frame& frame_info) {
stacktrace_frame frame = null_frame;
frame.filename = frame_info.obj_path;
frame.symbol = frame_info.symbol;
frame.address = frame_info.raw_address;
if(trace_dwarf) {
std::fprintf(
stderr,
"Starting resolution for %s %08llx %s\n",
"Starting resolution for %s %08llx\n",
obj_path.c_str(),
to_ull(frame_info.obj_address),
frame_info.symbol.c_str()
to_ull(frame_info.obj_address)
);
}
std::vector<stacktrace_frame> inlines;

View File

@ -55,8 +55,8 @@ void handler(int signo, siginfo_t* info, void* context) {
_exit(1);
}
for(std::size_t i = 0; i < count; i++) {
cpptrace::minimal_object_frame frame;
cpptrace::get_minimal_object_frame(buffer[i], &frame);
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);
@ -68,8 +68,8 @@ void handler(int signo, siginfo_t* info, void* context) {
void warmup_cpptrace() {
cpptrace::frame_ptr buffer[10];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
cpptrace::minimal_object_frame frame;
cpptrace::get_minimal_object_frame(buffer[0], &frame);
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[0], &frame);
}
int main() {

View File

@ -8,7 +8,7 @@
int main() {
cpptrace::object_trace trace;
while(true) {
cpptrace::minimal_object_frame frame;
cpptrace::safe_object_frame frame;
// std::size_t res = read(STDIN_FILENO, &frame, sizeof(frame));
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if(res == 0) {