libgunwinder is a Linux userspace stack unwinding library for long-running profilers and production diagnostics. It focuses on low-overhead unwinding from caller-provided register and stack snapshots, while reusing ELF, symbol, and CFI metadata across samples.
The project name expands to Global Unwinder.
- Features
- Supported Platforms
- Dependencies
- Build
- Install
- Quick Start
- Architecture
- Tools
- Validation
- Concurrency
- Documentation Links
- License
- Acknowledgements
- Lazy ELF, DWARF, CFI, and symbol loading for repeated unwinds.
- Reusable per-process and per-ELF caches to reduce allocation churn during sampling.
- DWARF CFI unwinding with a frame-pointer fast path when
GU_FLAG_HINT_SET_FPis provided. - Build-ID based ELF/debug-file matching and Go symbol handling options.
- PID lifecycle events for cache cleanup on process exit or module reload.
- Debug dump read/write helpers and a replay tool for offline investigation.
- Kernel symbol lookup helpers for diagnostics that need kernel address resolution.
- Linux
x86_64aarch64
The Makefile selects architecture-specific headers from include/arch/x86 or include/arch/arm64.
Build-time and link-time dependencies:
- GNU make
- A C compiler such as GCC or Clang
- elfutils libraries:
libelfandlibdw - binutils libraries:
libbfdandlibiberty - OpenSSL libraries:
libsslandlibcrypto - POSIX/Linux system headers
makeThe build creates:
lib/libgunwinder.alib/libgunwinder.sobin/bt_debug
Use V=1 to print the full compiler and linker commands:
make V=1make install DESTDIR=/path/to/stageInstalled files are placed under:
/usr/lib/usr/include/gunwinder
#include <gunwinder/unwinder.h>
#include <stdio.h>
#include <sys/types.h>
static void on_frame(const struct gu_frame_record *frame, void *user_ctx)
{
(void)user_ctx;
if (frame->symbol)
printf("%s+0x%lx\n", frame->symbol, frame->offset);
}
void unwind_sample(pid_t pid, void *regs, uint32_t regs_size, uint8_t *stack, size_t stack_size)
{
struct gu_init_cfg cfg = {
.debug_print = false,
.go_not_strip_name = false,
.go_buildid_only = false,
};
struct gu_context *ctx = gu_init(&cfg);
if (!ctx)
return;
uint64_t unique_id = gu_preload_pid_debug_info(ctx, pid);
struct gu_stack_info info = {
.pid = pid,
.unique_id = unique_id,
.regs = regs,
.regs_size = regs_size,
.stack_data = stack,
.stack_size = stack_size,
};
gu_unwind(ctx, &info, on_frame, NULL);
gu_event_occur(ctx, GU_EVENT_PROCESS_EXIT, &pid);
gu_cleanup(ctx);
}Callers own the register and stack buffers passed through struct gu_stack_info. libgunwinder reads them during gu_unwind() and does not take ownership.
caller snapshot
| regs + stack bytes
v
gu_unwind()
| per-pid executable intervals
| per-ELF symbols, build IDs, debug files
v
DWARF CFI / frame-pointer unwinding
|
v
gu_frame_callback_t
The main public entry points are declared in include/gunwinder/unwinder.h:
gu_init()andgu_cleanup()manage the root unwinder context.gu_preload_pid_debug_info()warms per-PID ELF/debug metadata before sampling.gu_unwind()walks a stack snapshot and emits frames through a callback.gu_event_occur()notifies the unwinder about process lifecycle events.gu_debug_dump_sample()andgu_read_stack_dump()support offline debug dumps.
bin/bt_debug replays stack dump files created by gu_debug_dump_sample():
bin/bt_debug [-v|--verbose] <dump-file-or-directory>For timing-oriented runs, redirect normal output to a file so terminal printing does not dominate measured unwind time.
The repository includes self-contained validation tools. They generate synthetic inputs or use caller-provided dump files; no private test data or internal services are required.
make clean all
bin/cfi_stress
bin/test_stable_fp_miss_reload
bin/cfi_bench --frames 100000 --set-size 100 --warmup 1000cfi_stresscovers DWARF expression and CFI edge cases.test_stable_fp_miss_reloadchecks PID map reload throttling behavior.cfi_benchis a synthetic CFI parser/evaluator microbenchmark. Treat its output as a local performance signal rather than a production workload model.
struct gu_context owns mutable caches. Serialize access to a context when calling gu_unwind(), gu_preload_pid_debug_info(), gu_event_occur(), and cache-related accessors. Use separate contexts if independent threads need to unwind without external locking.
- 中文 README
- Acknowledgements
- Project notice
- LGPLv3 license text
- GPLv3 base license text
- LGPLv3 additional permissions
- Authors
Unless a file states otherwise, libgunwinder is licensed under the GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later). See LICENSE, COPYING, and COPYING.LESSER.
Some vendored third-party headers carry their own notices. See NOTICE and ACKNOWLEDGEMENTS.md.
libgunwinder acknowledges:
- uthash for the vendored
uthash.handutlist.hheaders. - elfutils and its
libelf/libdwlibraries. Parts of libgunwinder's DWARF and unwinding behavior were cross-checked against elfutils/libdw. libgunwinder keeps its own data structures, parser/cache layout, and stack-snapshot unwind flow; no elfutils source code is copied into this project. The project usesLGPL-3.0-or-laterto align with the LGPLv3-or-later option available to elfutils libraries and backends.
See ACKNOWLEDGEMENTS.md for details.