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
|
||||||
|
|
||||||
Object traces are somewhat minimal stack traces with basic information on which binary a frame corresponds to, any
|
Object traces contain the most basic information needed to construct a stack trace outside the currently running
|
||||||
symbol name libdl (in linux/macos) was able to resolve, the raw program counter and the program counter translated to
|
executable. It contains the raw address, the address in the binary (ASLR and the object file's memory space and whatnot
|
||||||
the corresponding object file's memory space.
|
is resolved), and the path to the object the instruction pointer is located in.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
struct object_frame {
|
struct object_frame {
|
||||||
std::string obj_path;
|
std::string obj_path;
|
||||||
std::string symbol;
|
|
||||||
frame_ptr raw_address;
|
frame_ptr raw_address;
|
||||||
frame_ptr obj_address;
|
frame_ptr obj_address;
|
||||||
};
|
};
|
||||||
@ -409,19 +408,19 @@ The safe API is as follows:
|
|||||||
namespace cpptrace {
|
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 = 0);
|
||||||
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
|
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 raw_address;
|
||||||
frame_ptr address_relative_to_object_base_in_memory;
|
frame_ptr address_relative_to_object_base_in_memory;
|
||||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||||
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
|
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
|
**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
|
`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
|
**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
|
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 {
|
struct CPPTRACE_EXPORT object_frame {
|
||||||
std::string obj_path;
|
std::string obj_path;
|
||||||
std::string symbol;
|
|
||||||
frame_ptr raw_address;
|
frame_ptr raw_address;
|
||||||
frame_ptr obj_address;
|
frame_ptr obj_address;
|
||||||
};
|
};
|
||||||
@ -194,7 +193,7 @@ namespace cpptrace {
|
|||||||
std::size_t skip,
|
std::size_t skip,
|
||||||
std::size_t max_depth
|
std::size_t max_depth
|
||||||
);
|
);
|
||||||
struct CPPTRACE_EXPORT minimal_object_frame {
|
struct CPPTRACE_EXPORT safe_object_frame {
|
||||||
frame_ptr raw_address;
|
frame_ptr raw_address;
|
||||||
frame_ptr address_relative_to_object_base_in_memory;
|
frame_ptr address_relative_to_object_base_in_memory;
|
||||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||||
@ -202,7 +201,7 @@ namespace cpptrace {
|
|||||||
object_frame resolve() const;
|
object_frame resolve() const;
|
||||||
};
|
};
|
||||||
// signal-safe
|
// 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:
|
// utilities:
|
||||||
CPPTRACE_EXPORT std::string demangle(const std::string& name);
|
CPPTRACE_EXPORT std::string demangle(const std::string& name);
|
||||||
|
|||||||
@ -44,7 +44,7 @@ namespace cpptrace {
|
|||||||
// signal-safe
|
// 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);
|
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 raw_address;
|
||||||
frame_ptr address_relative_to_object_base_in_memory;
|
frame_ptr address_relative_to_object_base_in_memory;
|
||||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||||
@ -52,7 +52,7 @@ namespace cpptrace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// signal-safe
|
// 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
|
sufficient for resolving outside of the currently running process, unless there is no position-independent code or
|
||||||
shared-library code.
|
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.
|
object where the address is located as well as the address before address randomization.
|
||||||
|
|
||||||
# Strategy
|
# 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:
|
Signal-safe tracing can be done three ways:
|
||||||
- In a signal handler, call `safe_generate_raw_trace` and then outside a signal handler
|
- In a signal handler, call `safe_generate_raw_trace` and then outside a signal handler
|
||||||
construct a `cpptrace:raw_trace` and resolve.
|
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.
|
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
|
- 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.
|
wait for the child to exit.
|
||||||
|
|
||||||
It's not as simple as calling `cpptrace::generate_trace().print()`, I know, but these are truly the
|
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
|
**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
|
`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
|
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
|
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);
|
execl("signal_tracer", "signal_tracer", nullptr);
|
||||||
_exit(1);
|
_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++) {
|
for(std::size_t i = 0; i < count; i++) {
|
||||||
cpptrace::minimal_object_frame frame;
|
cpptrace::safe_object_frame frame;
|
||||||
cpptrace::get_minimal_object_frame(buffer[i], &frame);
|
cpptrace::get_safe_object_frame(buffer[i], &frame);
|
||||||
write(input_pipe.write_end, &frame, sizeof(frame));
|
write(input_pipe.write_end, &frame, sizeof(frame));
|
||||||
}
|
}
|
||||||
close(input_pipe.read_end);
|
close(input_pipe.read_end);
|
||||||
@ -175,8 +175,8 @@ void warmup_cpptrace() {
|
|||||||
// This is done for any dynamic-loading shenanigans
|
// This is done for any dynamic-loading shenanigans
|
||||||
cpptrace::frame_ptr buffer[10];
|
cpptrace::frame_ptr buffer[10];
|
||||||
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
|
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
|
||||||
cpptrace::minimal_object_frame frame;
|
cpptrace::safe_object_frame frame;
|
||||||
cpptrace::get_minimal_object_frame(buffer[0], &frame);
|
cpptrace::get_safe_object_frame(buffer[0], &frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@ -195,7 +195,7 @@ int main() {
|
|||||||
|
|
||||||
## In the tracer program
|
## 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`.
|
`cpptrace::object_frame`s, and resolve an `object_trace`.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@ -208,7 +208,7 @@ The tracer program is quite simple. It just has to read `cpptrace::minimal_objec
|
|||||||
int main() {
|
int main() {
|
||||||
cpptrace::object_trace trace;
|
cpptrace::object_trace trace;
|
||||||
while(true) {
|
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
|
// 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);
|
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
|
||||||
if(res == 0) {
|
if(res == 0) {
|
||||||
|
|||||||
@ -78,7 +78,6 @@ namespace detail {
|
|||||||
frame.obj_address = addr
|
frame.obj_address = addr
|
||||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||||
+ get_module_image_base(info.dli_fname);
|
+ get_module_image_base(info.dli_fname);
|
||||||
frame.symbol = info.dli_sname ?: "";
|
|
||||||
}
|
}
|
||||||
frames.push_back(frame);
|
frames.push_back(frame);
|
||||||
}
|
}
|
||||||
@ -151,10 +150,9 @@ namespace detail {
|
|||||||
}
|
}
|
||||||
#endif
|
#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 {
|
return {
|
||||||
frame.object_path,
|
frame.object_path,
|
||||||
"",
|
|
||||||
frame.raw_address,
|
frame.raw_address,
|
||||||
frame.address_relative_to_object_base_in_memory + get_module_image_base(frame.object_path)
|
frame.address_relative_to_object_base_in_memory + get_module_image_base(frame.object_path)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
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;
|
dl_find_object result;
|
||||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) {
|
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) {
|
||||||
out->raw_address = address;
|
out->raw_address = address;
|
||||||
@ -59,7 +59,7 @@ namespace detail {
|
|||||||
#else
|
#else
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
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->raw_address = address;
|
||||||
out->address_relative_to_object_base_in_memory = 0;
|
out->address_relative_to_object_base_in_memory = 0;
|
||||||
out->object_path[0] = 0;
|
out->object_path[0] = 0;
|
||||||
|
|||||||
@ -342,12 +342,12 @@ namespace cpptrace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_frame minimal_object_frame::resolve() const {
|
object_frame safe_object_frame::resolve() const {
|
||||||
return detail::resolve_minimal_object_frame(*this);
|
return detail::resolve_safe_object_frame(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_minimal_object_frame(frame_ptr address, minimal_object_frame* out) {
|
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||||
detail::get_minimal_object_frame(address, out);
|
detail::get_safe_object_frame(address, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string demangle(const std::string& name) {
|
std::string demangle(const std::string& name) {
|
||||||
|
|||||||
@ -272,7 +272,6 @@ namespace addr2line {
|
|||||||
trace[i].address = frames[i].raw_address;
|
trace[i].address = frames[i].raw_address;
|
||||||
// Set what is known for now, and resolutions from addr2line should overwrite
|
// Set what is known for now, and resolutions from addr2line should overwrite
|
||||||
trace[i].filename = frames[i].obj_path;
|
trace[i].filename = frames[i].obj_path;
|
||||||
trace[i].symbol = frames[i].symbol;
|
|
||||||
}
|
}
|
||||||
if(has_addr2line()) {
|
if(has_addr2line()) {
|
||||||
const auto entries = collate_frames(frames, trace);
|
const auto entries = collate_frames(frames, trace);
|
||||||
|
|||||||
@ -850,15 +850,13 @@ namespace libdwarf {
|
|||||||
frame_with_inlines resolve_frame(const object_frame& frame_info) {
|
frame_with_inlines resolve_frame(const object_frame& frame_info) {
|
||||||
stacktrace_frame frame = null_frame;
|
stacktrace_frame frame = null_frame;
|
||||||
frame.filename = frame_info.obj_path;
|
frame.filename = frame_info.obj_path;
|
||||||
frame.symbol = frame_info.symbol;
|
|
||||||
frame.address = frame_info.raw_address;
|
frame.address = frame_info.raw_address;
|
||||||
if(trace_dwarf) {
|
if(trace_dwarf) {
|
||||||
std::fprintf(
|
std::fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"Starting resolution for %s %08llx %s\n",
|
"Starting resolution for %s %08llx\n",
|
||||||
obj_path.c_str(),
|
obj_path.c_str(),
|
||||||
to_ull(frame_info.obj_address),
|
to_ull(frame_info.obj_address)
|
||||||
frame_info.symbol.c_str()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
std::vector<stacktrace_frame> inlines;
|
std::vector<stacktrace_frame> inlines;
|
||||||
|
|||||||
@ -55,8 +55,8 @@ void handler(int signo, siginfo_t* info, void* context) {
|
|||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
for(std::size_t i = 0; i < count; i++) {
|
for(std::size_t i = 0; i < count; i++) {
|
||||||
cpptrace::minimal_object_frame frame;
|
cpptrace::safe_object_frame frame;
|
||||||
cpptrace::get_minimal_object_frame(buffer[i], &frame);
|
cpptrace::get_safe_object_frame(buffer[i], &frame);
|
||||||
write(input_pipe.write_end, &frame, sizeof(frame));
|
write(input_pipe.write_end, &frame, sizeof(frame));
|
||||||
}
|
}
|
||||||
close(input_pipe.read_end);
|
close(input_pipe.read_end);
|
||||||
@ -68,8 +68,8 @@ void handler(int signo, siginfo_t* info, void* context) {
|
|||||||
void warmup_cpptrace() {
|
void warmup_cpptrace() {
|
||||||
cpptrace::frame_ptr buffer[10];
|
cpptrace::frame_ptr buffer[10];
|
||||||
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
|
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
|
||||||
cpptrace::minimal_object_frame frame;
|
cpptrace::safe_object_frame frame;
|
||||||
cpptrace::get_minimal_object_frame(buffer[0], &frame);
|
cpptrace::get_safe_object_frame(buffer[0], &frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
int main() {
|
int main() {
|
||||||
cpptrace::object_trace trace;
|
cpptrace::object_trace trace;
|
||||||
while(true) {
|
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 = read(STDIN_FILENO, &frame, sizeof(frame));
|
||||||
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
|
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
|
||||||
if(res == 0) {
|
if(res == 0) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user