| src | ||
| tests | ||
| vcpkg@3b9d086009 | ||
| .gitignore | ||
| .gitmodules | ||
| CMakeLists.txt | ||
| CMakePresets.json | ||
| README.md | ||
| test_access.c | ||
| test_with_args.c | ||
| vcpkg.json | ||
gwatch - Global Variable Watcher
A tool to monitor writes to global integer variables in Linux binaries using ptrace single-stepping.
Features
- Monitors specific global integer variables in a running program
- Detects and reports all writes to the watched variable
- Supports PIE (Position Independent Executable) binaries
- Passes command-line arguments to the target program
- Uses DWARF debug information to locate variables
- Tab-delimited output format for easy parsing
Building
cmake -B build
cmake --build build
Usage
gwatch --var <symbol> --exec <path> [-- arg1 arg2 ... argN]
Arguments:
--var <symbol>: Name of the global variable to watch (required)--exec <path>: Path to the executable to monitor (required)-- arg1 arg2 ...: Optional arguments to pass to the target program
Note: The target binary must be compiled with debug symbols (-g flag).
Examples:
# Watch global_counter in test_access
./build/gwatch --var global_counter --exec ./test_access
# Watch with program arguments
./build/gwatch --var global_counter --exec ./test_with_args -- hello world 123
Output Format
When watchpoints trigger, gwatch outputs access events in the following format:
For writes:
<symbol>\twrite\t<old_value>-><new_value>
For reads:
<symbol>\tread\t<value>
Example output:
global_counter read 0
global_counter write 0->42
global_counter read 42
global_counter write 42->52
Implementation Details
Variable Detection
- Uses libdwarf to parse DWARF debug information
- Identifies global integer variables (int, long, short, char, unsigned variants)
- Handles const/volatile type qualifiers
- Retrieves variable address and size from debug info
Single-Stepping Approach
The tool uses PTRACE_SINGLESTEP to monitor memory access:
- Executes the target program one instruction at a time
- After each instruction, reads the watched variable's value from memory
- Detects writes by comparing current value to previous value
- Reports changes in the specified tab-delimited format
PIE Binary Support
For Position Independent Executables, the tool:
- Reads
/proc/[pid]/mapsto find the base load address (lowest mapping) - Adds the load address to the DWARF-provided offset
- Monitors the calculated runtime address
Write Detection
When monitoring the variable:
- The current value is read from the traced process memory using
ptrace(PTRACE_PEEKDATA) - If the value has changed since the last check, it's reported as a write
- Values are formatted according to the variable size (1, 2, 4, or 8 bytes)
- Sign extension is applied for smaller integer types
Performance Considerations
The tool uses single-stepping which executes one CPU instruction at a time. This is:
- Very thorough: Catches every write to the watched variable
- Slow: Adds significant overhead compared to native execution
- Reliable: Works consistently across different systems and configurations
For programs with many instructions (100K+ steps), there will be noticeable slowdown.
Testing
Test programs are provided:
test_access.c: Program that reads and writes toglobal_countertest_with_args.c: Program that accepts command-line arguments and modifiesglobal_counter
Compile test programs with:
gcc -g -O0 -o test_access test_access.c
gcc -g -O0 -o test_with_args test_with_args.c
Run tests:
# Basic test
./build/gwatch --var global_counter --exec ./test_access
# Test with arguments
./build/gwatch --var global_counter --exec ./test_with_args -- hello world 123
Future Work
- Optimize performance (possibly using hardware watchpoints if system supports it)
- Add support for watching non-integer types (floats, structs, etc.)
- Support multiple simultaneous variables
- Add read detection (currently only writes are reported)
- Output instruction pointer (RIP) for each access
- Better error reporting and diagnostics
- Optional: Skip library code and focus on main program only