calltrace-rs 1.1.4

High-performance function call tracing library for C/C++ applications using GCC instrumentation with Rust safety guarantees
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# CallTrace - High-Performance Function Call Tracing Library

[![Crates.io](https://img.shields.io/crates/v/calltrace.svg)](https://crates.io/crates/calltrace)
[![Documentation](https://docs.rs/calltrace/badge.svg)](https://docs.rs/calltrace)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](https://github.com/signal-slot/calltrace)
[![Build Status](https://github.com/signal-slot/calltrace/workflows/CI/badge.svg)](https://github.com/signal-slot/calltrace/actions)
[![Coverage](https://codecov.io/gh/signal-slot/calltrace/branch/main/graph/badge.svg)](https://codecov.io/gh/signal-slot/calltrace)
[![Rust](https://img.shields.io/badge/rust-1.70%2B-brightgreen.svg)](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:

| 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:

```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.