# 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 ```bash cmake -B build cmake --build build ``` ## Usage ```bash gwatch --var --exec [-- arg1 arg2 ... argN] ``` **Arguments:** - `--var `: Name of the global variable to watch (required) - `--exec `: 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:** ```bash # 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:** ``` \twrite\t-> ``` **For reads:** ``` \tread\t ``` 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: 1. Reads `/proc/[pid]/maps` to find the base load address (lowest mapping) 2. Adds the load address to the DWARF-provided offset 3. Monitors the calculated runtime address ### Write Detection When monitoring the variable: 1. The current value is read from the traced process memory using `ptrace(PTRACE_PEEKDATA)` 2. If the value has changed since the last check, it's reported as a write 3. Values are formatted according to the variable size (1, 2, 4, or 8 bytes) 4. 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 to `global_counter` - `test_with_args.c`: Program that accepts command-line arguments and modifies `global_counter` Compile test programs with: ```bash gcc -g -O0 -o test_access test_access.c gcc -g -O0 -o test_with_args test_with_args.c ``` Run tests: ```bash # 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