# CallTrace - High-Performance Function Call Tracing Library
[](https://crates.io/crates/calltrace)
[](https://docs.rs/calltrace)
[](https://github.com/signal-slot/calltrace)
[](https://github.com/signal-slot/calltrace/actions)
[](https://codecov.io/gh/signal-slot/calltrace)
[](https://www.rust-lang.org)
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:
```toml
[dependencies]
calltrace = "1.0"
```
### Using as Dynamic Library
Download the latest release from [GitHub Releases](https://github.com/signal-slot/calltrace/releases) or build from source:
```bash
git clone https://github.com/signal-slot/calltrace.git
cd calltrace
cargo build --release
```
The compiled library will be available at `target/release/libcalltrace.so`.
## Quick Start
### 1. Build the Library
```bash
cargo build --release
```
### 2. Compile Your Program with Instrumentation
โ ๏ธ **Important**: Use `-rdynamic` flag for proper symbol resolution:
```bash
gcc -rdynamic -finstrument-functions -g your_program.c -o your_program
```
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)
```bash
cmake -B build \
-DCMAKE_C_FLAGS="-finstrument-functions -rdynamic -g" \
-DCMAKE_CXX_FLAGS="-finstrument-functions -rdynamic -g" \
-DCMAKE_EXE_LINKER_FLAGS="-rdynamic"
cmake --build build
```
**Method 2: CMakeLists.txt Target-Specific**
```cmake
# 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**
```cmake
# 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
```bash
CALLTRACE_OUTPUT=trace.json LD_PRELOAD=./target/release/libcalltrace.so ./your_program
```
### 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:
```rust
use calltrace::{calltrace_init, calltrace_cleanup, CallTraceConfig};
// Initialize tracing with custom configuration
let config = CallTraceConfig {
output_file: Some("trace.json".to_string()),
capture_args: true,
max_depth: 1000,
pretty_json: true,
debug_mode: false,
};
unsafe {
calltrace_init(&config);
// Your instrumented code runs here
calltrace_cleanup();
}
```
### Environment Variables Configuration
For applications that can't modify CallTrace initialization, use environment variables:
| `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:
```bash
CALLTRACE_CAPTURE_ARGS=1 CALLTRACE_OUTPUT=trace.json LD_PRELOAD=./target/debug/libcalltrace.so ./your_program
```
## Example Output
```json
{
"metadata": {
"start_time": "1755336097.437215",
"end_time": "1755336097.437274",
"duration_ms": 0.059,
"process_info": {
"pid": 144031,
"architecture": "x86_64",
"executable_path": null
},
"calltrace_version": "1.0.0"
},
"threads": [
{
"thread_id": 144031,
"thread_name": "Thread-144031",
"is_main_thread": false,
"start_time": "1755336097.437220",
"call_tree": {
"id": 1,
"function": "main",
"address": "0x55b0242fb234",
"call_site": "0x7f1d550c23bb",
"start_time": "1755336097.437226",
"end_time": "1755336097.437265",
"duration_us": 39,
"call_depth": 0,
"arguments": [],
"return_value": null,
"children": [
{
"id": 2,
"function": "simple_return",
"address": "0x55b0242fb1d9",
"call_site": "0x55b0242fb26e",
"start_time": "1755336097.437249",
"end_time": "1755336097.437253",
"duration_us": 4,
"call_depth": 1,
"arguments": [],
"return_value": null,
"children": []
}
]
}
}
],
"statistics": {
"total_threads": 1,
"total_function_calls": 0,
"total_nodes": 2,
"session_duration_us": 59
}
}
```
## Crash Handling
CallTrace automatically installs signal handlers for common crash signals and provides comprehensive crash analysis:
### Supported Crash Signals
- `SIGSEGV` - Segmentation fault
- `SIGABRT` - Abort signal
- `SIGILL` - Illegal instruction
- `SIGFPE` - Floating point exception
- `SIGBUS` - Bus error
- `SIGTRAP` - 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
```json
{
"metadata": { /* normal session metadata */ },
"threads": [ /* normal thread traces */ ],
"statistics": { /* normal session statistics */ },
"crash": {
"signal": 11,
"signal_name": "SIGSEGV (Segmentation fault)",
"thread_id": 12345,
"register_context": {
"rdi": 140737080113288,
"rsi": 140737080115432,
"rsp": 140737080112384,
"rip": 140324016650424,
/* ... complete register dump ... */
},
"backtrace": [
{
"address": "0x0000558a4b2a1250",
"function_name": "cause_crash",
"library_name": "./my_program",
"offset": "0x15"
}
],
"crash_time": "2025-08-16 22:07:52 JST",
"crash_timestamp": 1755349672.89132
}
}
```
### Integration with Normal Tracing
Crash information is seamlessly integrated with normal function tracing:
1. **Thread Correlation**: The `thread_id` in crash info matches thread IDs in the `threads` array
2. **Call Tree Preservation**: Function call hierarchy is preserved up to the crash point
3. **Unified Output**: Single JSON file contains both normal trace and crash analysis
4. **Timing Continuity**: Crash timestamp aligns with session timing information
## Examples and Documentation
CallTrace includes comprehensive examples demonstrating various use cases:
- **[examples/basic_tracing.c](examples/basic_tracing.c)** - Simple function call demonstration
- **[examples/argument_capture.c](examples/argument_capture.c)** - Advanced argument capture features
- **[examples/multithreaded.c](examples/multithreaded.c)** - Thread safety and concurrent tracing
- **[examples/performance_test.c](examples/performance_test.c)** - Performance measurement and optimization
- **[examples/run_all_examples.sh](examples/run_all_examples.sh)** - Automated example runner
### Additional Resources
- **[๐ API Documentation](https://docs.rs/calltrace)** - Complete API reference
- **[๐ง Troubleshooting Guide](docs/TROUBLESHOOTING.md)** - Common issues and solutions
- **[โก Performance Tuning](docs/PERFORMANCE_TUNING.md)** - Optimization strategies
- **[๐ Development Plan](DEVELOPMENT_PLAN.md)** - Project roadmap and architecture
## Architecture
CallTrace uses:
- **GCC Instrumentation**: Hooks `__cyg_profile_func_enter/exit` for function tracing
- **Symbol Resolution**: Uses `dladdr()` for simple and reliable function name resolution
- **DWARF Analysis**: Parses debug information for function signatures (using `gimli` crate, 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 `serde` for 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
```bash
# Build the library
cargo build
# Compile test program with proper flags
gcc -rdynamic -finstrument-functions -g test_simple_return.c -o test_simple_return
# Run with tracing
CALLTRACE_OUTPUT=test_output.json LD_PRELOAD=./.target/debug/libcalltrace.so ./test_simple_return
# Run with argument capture enabled
CALLTRACE_CAPTURE_ARGS=1 CALLTRACE_OUTPUT=test_args.json LD_PRELOAD=./.target/debug/libcalltrace.so ./test_simple_return
```
### Creating Test Programs
Example test program (`test_simple_return.c`):
```c
#include <stdio.h>
int simple_return() {
printf("Inside simple_return, about to return 42\n");
return 42;
}
int main() {
printf("Starting simple return test\n");
int result = simple_return();
printf("Got result: %d\n", result);
return 0;
}
```
### 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:
```bash
gcc -rdynamic -finstrument-functions -g your_program.c -o your_program
```
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**:
1. Ensure DWARF debug information is available (use `-g` flag)
2. Verify function signatures are available in debug info
3. Check that the target architecture is x86_64
### Performance Impact
**Problem**: Tracing causes significant slowdown.
**Solutions**:
1. Disable argument capture if not needed (default behavior)
2. Use release build: `LD_PRELOAD=./target/release/libcalltrace.so`
3. Set `CALLTRACE_MAX_DEPTH` to limit deep recursion
4. Consider filtering functions at compile time
### Crash handling conflicts
**Problem**: CallTrace crash handlers not capturing crashes or conflicts with existing handlers.
**Solutions**:
1. CallTrace installs signal handlers early during library initialization
2. Existing handlers are preserved and restored after crash analysis
3. Use `CALLTRACE_DEBUG=1` to see handler installation messages
4. 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**:
1. Ensure running on Linux (uses `gettid()` system call)
2. Verify CallTrace is properly initialized before crash occurs
3. 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](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contributing
Contributions are welcome! To contribute to CallTrace:
1. **Fork the repository** and create a feature branch
2. **Follow conventional commits** - see [Commit Convention](.github/COMMIT_CONVENTION.md)
3. **Write tests** for your changes and ensure all tests pass: `cargo test`
4. **Update documentation** if you're adding new features
5. **Submit a pull request** with a clear description of your changes
### Development Setup
```bash
# Clone and build
git clone https://github.com/signal-slot/calltrace.git
cd calltrace
cargo build
# Run tests
cargo test
# Run examples
chmod +x examples/run_all_examples.sh
./examples/run_all_examples.sh
```
### 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.