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:
parent
a04f19a484
commit
2a4a8066d3
13
README.md
13
README.md
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user