CallTrace - High-Performance Function Call Tracing Library
A Rust-based library for tracing function calls with argument capture and return value tracking, built using GCC's -finstrument-functions feature. Provides memory-safe, high-performance function tracing for C/C++ applications with comprehensive crash analysis.
Features
- ๐ฆ Memory Safe: Written in Rust with guaranteed memory safety
- โก High Performance: Zero-cost abstractions with minimal overhead
- ๐ Argument Capture: Captures function arguments using DWARF debug info
- ๐ Return Value Tracing: Captures function return values in RAX/XMM0 registers
- ๐ท๏ธ Symbol Resolution: Resolves function names using dladdr() for readable output
- ๐งต Thread Safe: Complete multi-threading support with thread relationship tracking
- ๐ Structured Output: JSON output with hierarchical call trees and precise timing
- ๐ก๏ธ Robust Error Handling: Graceful degradation with error tracking
- ๐ฅ Crash Analysis: Comprehensive crash reporting with thread identification and stack traces
- ๐งจ Signal Handling: Captures crashes (SIGSEGV, SIGABRT, etc.) with full context
Installation
Using the Prebuilt Library
Add CallTrace to your project dependencies:
[]
= "1.0"
Using as Dynamic Library
Download the latest release from GitHub Releases or build from source:
The compiled library will be available at target/release/libcalltrace.so.
Quick Start
1. Build the Library
2. Compile Your Program with Instrumentation
โ ๏ธ Important: Use -rdynamic flag for proper symbol resolution:
Without -rdynamic, function names will appear as addresses like 0x401234 instead of readable names like main.
CMake Integration
For CMake-based projects, there are several approaches to set the required compilation flags:
Method 1: Command Line Configuration (Recommended for entire projects)
Method 2: CMakeLists.txt Target-Specific
# Add to your CMakeLists.txt for specific targets
target_compile_options(your_target PRIVATE
-finstrument-functions
-rdynamic
-g
)
target_link_options(your_target PRIVATE -rdynamic)
# Or for all targets in the project
add_compile_options(-finstrument-functions -rdynamic -g)
add_link_options(-rdynamic)
Method 3: CMake Toolchain File
# Create a toolchain file (calltrace-toolchain.cmake)
set(CMAKE_C_FLAGS_INIT "-finstrument-functions -rdynamic -g")
set(CMAKE_CXX_FLAGS_INIT "-finstrument-functions -rdynamic -g")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-rdynamic")
# Use with: cmake -DCMAKE_TOOLCHAIN_FILE=calltrace-toolchain.cmake
โ ๏ธ Performance Note: For large applications (like Qt GUI apps), CallTrace may significantly increase startup time due to extensive function call tracing. Consider using with smaller, targeted applications or implementing function filtering.
3. Run with CallTrace
CALLTRACE_OUTPUT=trace.json LD_PRELOAD=./target/release/libcalltrace.so
4. Analyze the Results
The trace will be saved to trace.json with a complete hierarchical call tree including:
- Function names and addresses (resolved using symbol table)
- Call timing and duration (microsecond precision)
- Thread relationships and creation info
- Function arguments (when CALLTRACE_CAPTURE_ARGS=1)
- Return values (captured from RAX/XMM0 registers)
- Crash information with thread identification (if program crashes)
API Overview
CallTrace provides a simple C-compatible API for integration:
use ;
// Initialize tracing with custom configuration
let config = CallTraceConfig ;
unsafe
Environment Variables Configuration
For applications that can't modify CallTrace initialization, use environment variables:
| Variable | Description | Default |
|---|---|---|
CALLTRACE_OUTPUT |
Output file path | Required for file output |
CALLTRACE_CAPTURE_ARGS |
Enable argument capture | false |
CALLTRACE_MAX_DEPTH |
Maximum call depth | 100 |
CALLTRACE_PRETTY_JSON |
Pretty-print JSON | true |
CALLTRACE_DEBUG |
Enable debug output | false |
Example with argument capture:
CALLTRACE_CAPTURE_ARGS=1 CALLTRACE_OUTPUT=trace.json LD_PRELOAD=./target/debug/libcalltrace.so
Example Output
Crash Handling
CallTrace automatically installs signal handlers for common crash signals and provides comprehensive crash analysis:
Supported Crash Signals
SIGSEGV- Segmentation faultSIGABRT- Abort signalSIGILL- Illegal instructionSIGFPE- Floating point exceptionSIGBUS- Bus errorSIGTRAP- Trace/breakpoint trap
Crash Information
When a crash occurs, CallTrace captures:
- Thread Identification: Which thread crashed (using
gettid()) - Signal Details: Signal number and human-readable name
- Register Context: Complete x86_64 register dump at crash time
- Stack Backtrace: Function call stack with symbol resolution
- Call Tree State: Current function call hierarchy when crash occurred
- Timing Information: Precise crash timestamp
Example Crash Output
Integration with Normal Tracing
Crash information is seamlessly integrated with normal function tracing:
- Thread Correlation: The
thread_idin crash info matches thread IDs in thethreadsarray - Call Tree Preservation: Function call hierarchy is preserved up to the crash point
- Unified Output: Single JSON file contains both normal trace and crash analysis
- Timing Continuity: Crash timestamp aligns with session timing information
Examples and Documentation
CallTrace includes comprehensive examples demonstrating various use cases:
- examples/basic_tracing.c - Simple function call demonstration
- examples/argument_capture.c - Advanced argument capture features
- examples/multithreaded.c - Thread safety and concurrent tracing
- examples/performance_test.c - Performance measurement and optimization
- examples/run_all_examples.sh - Automated example runner
Additional Resources
- ๐ API Documentation - Complete API reference
- ๐ง Troubleshooting Guide - Common issues and solutions
- โก Performance Tuning - Optimization strategies
- ๐ Development Plan - Project roadmap and architecture
Architecture
CallTrace uses:
- GCC Instrumentation: Hooks
__cyg_profile_func_enter/exitfor function tracing - Symbol Resolution: Uses
dladdr()for simple and reliable function name resolution - DWARF Analysis: Parses debug information for function signatures (using
gimlicrate, when argument capture is enabled) - Register Reading: Captures x86_64 registers (RAX, XMM0) for argument and return value extraction
- Signal Handling: Installs crash handlers for comprehensive crash analysis with thread identification
- Thread-Safe Storage: Uses atomic operations and
Arc<RwLock<T>>for concurrent access - Structured Serialization: Uses
serdefor type-safe JSON output with precise timing
Performance Optimizations
- Atomic Fast-Path: Uses atomic flags to minimize overhead when features are disabled
- Function Info Caching: LRU cache for DWARF information to avoid repeated parsing
- Zero-Copy Operations: Minimal allocations in the hot path
- Inline Assembly: Direct register capture using inline assembly for maximum performance
Development
Running Tests
# Build the library
# Compile test program with proper flags
# Run with tracing
CALLTRACE_OUTPUT=test_output.json LD_PRELOAD=./.target/debug/libcalltrace.so
# Run with argument capture enabled
CALLTRACE_CAPTURE_ARGS=1 CALLTRACE_OUTPUT=test_args.json LD_PRELOAD=./.target/debug/libcalltrace.so
Creating Test Programs
Example test program (test_simple_return.c):
int
int
Cargo Features
dwarf_support: Enable DWARF debug information parsing (default)
Troubleshooting
Function names show as addresses (e.g., 0x401234)
Problem: Function names appear as hexadecimal addresses instead of readable names like main.
Solution: Compile your program with the -rdynamic flag:
The -rdynamic flag exports symbols to the dynamic symbol table, which allows dladdr() to resolve function names.
No argument capture
Problem: Arguments show as empty arrays even with CALLTRACE_CAPTURE_ARGS=1.
Solutions:
- Ensure DWARF debug information is available (use
-gflag) - Verify function signatures are available in debug info
- Check that the target architecture is x86_64
Performance Impact
Problem: Tracing causes significant slowdown.
Solutions:
- Disable argument capture if not needed (default behavior)
- Use release build:
LD_PRELOAD=./target/release/libcalltrace.so - Set
CALLTRACE_MAX_DEPTHto limit deep recursion - Consider filtering functions at compile time
Crash handling conflicts
Problem: CallTrace crash handlers not capturing crashes or conflicts with existing handlers.
Solutions:
- CallTrace installs signal handlers early during library initialization
- Existing handlers are preserved and restored after crash analysis
- Use
CALLTRACE_DEBUG=1to see handler installation messages - For applications with custom signal handlers, ensure CallTrace loads first
Missing thread identification in crashes
Problem: Thread ID shows as 0 or incorrect value in crash reports.
Solutions:
- Ensure running on Linux (uses
gettid()system call) - Verify CallTrace is properly initialized before crash occurs
- Check that crash occurs in instrumented code (compiled with
-finstrument-functions)
Technical Notes
- Return Value Limitations: Due to GCC's instrumentation timing, return values are captured after function cleanup, which may result in overwritten registers for some functions.
- Thread Safety: All operations are thread-safe and support multi-threaded applications.
- Memory Usage: Function info is cached with LRU eviction to prevent memory bloat.
- Crash Handler Behavior: Signal handlers are installed once and preserve existing handlers. Thread identification uses
gettid()for precise thread correlation. - Platform Support: Currently supports x86_64 Linux. Other platforms may require register capture adjustments.
License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributing
Contributions are welcome! To contribute to CallTrace:
- Fork the repository and create a feature branch
- Follow conventional commits - see Commit Convention
- Write tests for your changes and ensure all tests pass:
cargo test - Update documentation if you're adding new features
- Submit a pull request with a clear description of your changes
Development Setup
# Clone and build
# Run tests
# Run examples
Code Quality
The project maintains high code quality standards:
- All code is formatted with
cargo fmt - Lints are checked with
cargo clippy - Tests must pass on multiple Rust versions
- Security audits run automatically via CI/CD
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.