如何在GDB中于断点后添加软件观察点以调试C结构体损坏问题
Got it, let's tackle this problem. When hardware watchpoints run out (most CPUs only support 2-4 of them), switching to software watchpoints is exactly the right move—they don't have the same limit, even if they do slow things down a bit (totally worth it for tracking down memory corruption!).
Here's how to modify your existing script to use software watchpoints instead:
Step 1: Adjust Allocation Breakpoint to Create Software Watchpoints
In your BreakpointAlloc class's stop() method, explicitly create a software watchpoint that monitors the entire memory range of the allocated struct. You'll need to target the full struct size (not just a single field) to catch any corruption across the instance.
Step 2: Track Watchpoints for Cleanup
Keep using your dictionary to map struct addresses to their watchpoint objects—this makes it trivial to look up and delete the watchpoint when the struct is freed.
Full Modified Script Example
Let's assume your struct is named MyStruct, your allocation function is my_struct_alloc() (returns a pointer to the new instance), and your free function is my_struct_free() (takes the struct pointer as an argument). Here's the adjusted script:
import gdb # Track watchpoints: key = struct address (integer), value = watchpoint object watchpoints = {} class BreakpointAlloc(gdb.Breakpoint): def __init__(self): # Set breakpoint at the end of your allocation function (right before return) super().__init__("my_struct_alloc", gdb.BP_BREAKPOINT) self.silent = True # Avoid spamming the console with breakpoint hits def stop(self): # Get the newly allocated struct pointer (return value of alloc function) # Use $eax for 32-bit systems instead of $rax struct_ptr = gdb.parse_and_eval("$rax") struct_addr = int(struct_ptr) if struct_addr == 0: return False # Skip null allocations # Get the size of MyStruct to monitor the entire instance struct_size = int(gdb.parse_and_eval("sizeof(MyStruct)")) # Create a software watchpoint for the full struct memory range wp = gdb.Breakpoint(f"*{struct_addr}", gdb.BP_WATCHPOINT, type=gdb.WP_SOFTWARE) wp.watch_write = True # Monitor writes to catch corruption wp.watch_read = False # Disable read monitoring to save performance wp.silent = True # Store the watchpoint for later cleanup watchpoints[struct_addr] = wp print(f"Set software watchpoint for MyStruct at 0x{struct_addr:x}") return False # Let the program continue execution class BreakpointFree(gdb.Breakpoint): def __init__(self): # Set breakpoint at the start of your free function super().__init__("my_struct_free", gdb.BP_BREAKPOINT) self.silent = True def stop(self): # Get the struct pointer being freed (first function argument) # Use $ebx for 32-bit systems instead of $rdi (depends on calling convention) struct_ptr = gdb.parse_and_eval("$rdi") struct_addr = int(struct_ptr) if struct_addr == 0 or struct_addr not in watchpoints: return False # Skip null or untracked instances # Remove and clean up the watchpoint wp = watchpoints.pop(struct_addr) wp.delete() print(f"Removed watchpoint for MyStruct at 0x{struct_addr:x}") return False # Let the program continue # Register breakpoints when the script loads BreakpointAlloc() BreakpointFree() print("Loaded allocation/free watchpoint manager (software watchpoints enabled)")
Key Adjustments & Notes
- Calling Convention: Double-check the register used to get function arguments/return values. For x86_64 System V (most Linux/macOS), return values live in
$raxand first arguments in$rdi. For 32-bit x86, use$eaxfor returns and either stack variables or$ebx(depending on your compiler's convention). Alternatively, usegdb.frame().read_var("param_name")if you know the exact parameter name in your free function. - Watchpoint Behavior: The example enables write-only monitoring to save performance—if you need to track reads too, set
wp.watch_read = True. - Performance Tradeoff: Software watchpoints work by having GDB periodically check the memory region, so your program will run slower. This is a necessary tradeoff for catching corruption when hardware watchpoints are exhausted.
- Struct Size: Using
gdb.parse_and_eval("sizeof(MyStruct)")ensures you always get the correct size, even if your struct definition changes later.
To use this script, load it into GDB with source your_script.py. It will automatically set software watchpoints for every new MyStruct instance on allocation, and remove them right before the instance is freed. Any write to a struct's memory will trigger the watchpoint, pausing the program so you can inspect what's causing the corruption.
内容的提问来源于stack exchange,提问作者FelipeFR

