当前位置: 首页 > 工具软件 > Watchpoints > 使用案例 >

GDB Watchpoints

鲜于意
2023-12-01

目录

Watchpoints

Watchpoints and Threads

x86 Watchpoints



https://github.com/Rtoax/test/blob/master/c/glibc/bfd/demo0-breakpoint.c

#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <bfd.h>

//调试的程序需要有自定义的断点

#if 0
#include <stdio.h>
    
#if __SIZEOF_POINTER__ > 4
#define RTOAX_BREAKPOINT_PTR ".quad"
#else
#define RTOAX_BREAKPOINT_PTR ".long"
#endif
#define RTOAX_BREAKPOINT \
    asm("0:"                              \
        ".pushsection embed-breakpoints;" \
        RTOAX_BREAKPOINT_PTR" 0b;"       \
        ".popsection;")

int main() {
    printf("Hello,\n");
    RTOAX_BREAKPOINT;
    printf("world!\n");
    RTOAX_BREAKPOINT;
    return 0;
}
#endif



#define die(msg, ...) \
    do { fprintf(stderr, msg, ## __VA_ARGS__); exit(1); } while (0)

#define errno_die(msg) \
    do { perror(msg); exit(1); } while(0)

#define bfd_die(msg) \
    do { bfd_perror(msg); exit(1); } while(0)


int main(int argc, char *argv[]) {
    if (argc < 2)
        die("Usage: %s <other gdb args> ... <program>\n", argv[0]);

    size_t i;

    // Get contents of the embed-breakpoints section

    bfd *abfd = bfd_openr(argv[argc-1], NULL);
    if (!abfd)
        bfd_die("bfd_openr");

    // We must call this to load the data!
    if (!bfd_check_format(abfd, bfd_object))
        bfd_die("bfd_check_format");

    asection *sec = bfd_get_section_by_name(abfd, "embed-breakpoints");
    if (!sec)
        bfd_die("bfd_get_section_by_name");

    size_t contents_size = sec->size;
    char *contents = malloc(contents_size);
    if (!contents)
        die("Could not allocate contents\n");

    if (!bfd_get_section_contents(abfd, sec, contents, 0, contents_size))
        bfd_die("bfd_get_section_contents");

    if (!bfd_close(abfd))
        bfd_die("bfd_close");


    for (i = 0; i < contents_size; i += sizeof(void*)) {
        fprintf(stderr, "break *%p\n", *( (void**) (contents+i) ));
    }
}

 

Watchpoints

https://sourceware.org/gdb/wiki/Internals%20Watchpoints


Watchpoints are a special kind of breakpoints (see breakpoints) which break when data is accessed rather than when some instruction is executed. When you have data which changes without your knowing what code does that, watchpoints are the silver bullet to hunt down and kill such bugs.

Watchpoints can be either hardware-assisted or not; the latter type is known as “software watchpoints.” GDB always uses hardware-assisted watchpoints if they are available, and falls back on software watchpoints otherwise. Typical situations where GDB will use software watchpoints are:

  • The watched memory region is too large for the underlying hardware watchpoint support. For example, each x86 debug register can watch up to 4 bytes of memory, so trying to watch data structures whose size is more than 16 bytes will cause GDB to use software watchpoints.
  • The value of the expression to be watched depends on data held in registers (as opposed to memory).
  • Too many different watchpoints requested. (On some architectures, this situation is impossible to detect until the debugged program is resumed.) Note that x86 debug registers are used both for hardware breakpoints and for watchpoints, so setting too many hardware breakpoints might cause watchpoint insertion to fail.
  • No hardware-assisted watchpoints provided by the target implementation.

Software watchpoints are very slow, since GDB needs to single-step the program being debugged and test the value of the watched expression(s) after each instruction. The rest of this section is mostly irrelevant for software watchpoints.

When the inferior stops, GDB tries to establish, among other possible reasons, whether it stopped due to a watchpoint being hit. It first uses STOPPED_BY_WATCHPOINT to see if any watchpoint was hit. If not, all watchpoint checking is skipped.

Then GDB calls target_stopped_data_address exactly once. This method returns the address of the watchpoint which triggered, if the target can determine it. If the triggered address is available, GDB compares the address returned by this method with each watched memory address in each active watchpoint. For data-read and data-access watchpoints, GDB announces every watchpoint that watches the triggered address as being hit. For this reason, data-read and data-access watchpoints require that the triggered address be available; if not, read and access watchpoints will never be considered hit. For data-write watchpoints, if the triggered address is available, GDB considers only those watchpoints which match that address; otherwise, GDB considers all data-write watchpoints. For each data-write watchpoint that GDB considers, it evaluates the expression whose value is being watched, and tests whether the watched value has changed. Watchpoints whose watched values have changed are announced as hit.

GDB uses several macros and primitives to support hardware watchpoints:

  • TARGET_CAN_USE_HARDWARE_WATCHPOINT (''type'', ''count'', ''other'')

Return the number of hardware watchpoints of type type that are possible to be set. The value is positive if count watchpoints of this type can be set, zero if setting watchpoints of this type is not supported, and negative if count is more than the maximum number of watchpoints of type type that can be set. other is non-zero if other types of watchpoints are currently enabled (there are architectures which cannot set watchpoints of different types at the same time).

  • TARGET_REGION_OK_FOR_HW_WATCHPOINT (''addr'', ''len'')

Return non-zero if hardware watchpoints can be used to watch a region whose address is addr and whose length in bytes is len.

  • target_insert_watchpoint (''addr'', ''len'', ''type'') target_remove_watchpoint (''addr'', ''len'', ''type'')

Insert or remove a hardware watchpoint starting at addr, for len bytes. type is the watchpoint type, one of the possible values of the enumerated data type target_hw_bp_type, defined by breakpoint.h as follows:

 enum target_hw_bp_type
   {
     hw_write   = 0, /* Common (write) HW watchpoint */
     hw_read    = 1, /* Read    HW watchpoint */
     hw_access  = 2, /* Access (read or write) HW watchpoint */
     hw_execute = 3  /* Execute HW breakpoint */
   };

These two macros should return 0 for success, non-zero for failure.

  • target_stopped_data_address (''addr_p'')

If the inferior has some watchpoint that triggered, place the address associated with the watchpoint at the location pointed to by addr_p and return non-zero. Otherwise, return zero. This is required for data-read and data-access watchpoints. It is not required for data-write watchpoints, but GDB uses it to improve handling of those also.

GDB will only call this method once per watchpoint stop, immediately after calling STOPPED_BY_WATCHPOINT. If the target’s watchpoint indication is sticky, i.e., stays set after resuming, this method should clear it. For instance, the x86 debug control register has sticky triggered flags.

  • target_watchpoint_addr_within_range (''target'', ''addr'', ''start'', ''length'')

Check whether addr (as returned by target_stopped_data_address) lies within the hardware-defined watchpoint region described by start and length. This only needs to be provided if the granularity of a watchpoint is greater than one byte, i.e., if the watchpoint can also trigger on nearby addresses outside of the watched region.

  • HAVE_STEPPABLE_WATCHPOINT

If defined to a non-zero value, it is not necessary to disable a watchpoint to step over it. Like gdbarch_have_nonsteppable_watchpoint, this is usually set when watchpoints trigger at the instruction which will perform an interesting read or write. It should be set if there is a temporary disable bit which allows the processor to step over the interesting instruction without raising the watchpoint exception again.

  • int gdbarch_have_nonsteppable_watchpoint (''gdbarch'')

If it returns a non-zero value, GDB should disable a watchpoint to step the inferior over it. This is usually set when watchpoints trigger at the instruction which will perform an interesting read or write.

  • HAVE_CONTINUABLE_WATCHPOINT

If defined to a non-zero value, it is possible to continue the inferior after a watchpoint has been hit. This is usually set when watchpoints trigger at the instruction following an interesting read or write.

  • STOPPED_BY_WATCHPOINT (''wait_status'')

Return non-zero if stopped by a watchpoint. wait_status is of the type struct target_waitstatus, defined by target.h. Normally, this macro is defined to invoke the function pointed to by the to_stopped_by_watchpoint member of the structure (of the type target_ops, defined on target.h) that describes the target-specific operations; to_stopped_by_watchpoint ignores the wait_status argument.

GDB does not require the non-zero value returned by STOPPED_BY_WATCHPOINT to be 100% correct, so if a target cannot determine for sure whether the inferior stopped due to a watchpoint, it could return non-zero “just in case”. [Watchpoints-and-Threads ]

 

Watchpoints and Threads


GDB only supports process-wide watchpoints, which trigger in all threads. GDB uses the thread ID to make watchpoints act as if they were thread-specific, but it cannot set hardware watchpoints that only trigger in a specific thread. Therefore, even if the target supports threads, per-thread debug registers, and watchpoints which only affect a single thread, it should set the per-thread debug registers for all threads to the same value. On GNU/Linux native targets, this is accomplished by using ALL_LWPS in target_insert_watchpoint and target_remove_watchpoint and by using linux_set_new_thread to register a handler for newly created threads.

GDB’s GNU/Linux support only reports a single event at a time, although multiple events can trigger simultaneously for multi-threaded programs. When multiple events occur, linux-nat.c queues subsequent events and returns them the next time the program is resumed. This means that STOPPED_BY_WATCHPOINT and target_stopped_data_address only need to consult the current thread’s state—the thread indicated by inferior_ptid. If two threads have hit watchpoints simultaneously, those routines will be called a second time for the second thread. [x86-Watchpoints ]

 

x86 Watchpoints


The 32-bit Intel x86 (a.k.a. ia32) processors feature special debug registers designed to facilitate debugging. GDB provides a generic library of functions that x86-based ports can use to implement support for watchpoints and hardware-assisted breakpoints. This subsection documents the x86 watchpoint facilities in GDB.

(At present, the library functions read and write debug registers directly, and are thus only available for native configurations.)

To use the generic x86 watchpoint support, a port should do the following:

  • Define the macro I386_USE_GENERIC_WATCHPOINTS somewhere in the target-dependent headers.

  • Include the config/i386/nm-i386.h header file after defining I386_USE_GENERIC_WATCHPOINTS.

  • Add i386-nat.o to the value of the Make variable NATDEPFILES (see NATDEPFILES).

  • Provide implementations for the I386_DR_LOW_* macros described below. Typically, each macro should call a target-specific function which does the real work.

The x86 watchpoint support works by maintaining mirror images of the debug registers. Values are copied between the mirror images and the real debug registers via a set of macros which each target needs to provide:

  • I386_DR_LOW_SET_CONTROL (''val'')

Set the Debug Control (DR7) register to the value val.

  • I386_DR_LOW_SET_ADDR (''idx'', ''addr'')

Put the address addr into the debug register number idx.

  • I386_DR_LOW_RESET_ADDR (''idx'')

Reset (i.e. zero out) the address stored in the debug register number idx.

  • I386_DR_LOW_GET_STATUS

Return the value of the Debug Status (DR6) register. This value is used immediately after it is returned by I386_DR_LOW_GET_STATUS, so as to support per-thread status register values.

For each one of the 4 debug registers (whose indices are from 0 to 3) that store addresses, a reference count is maintained by GDB, to allow sharing of debug registers by several watchpoints. This allows users to define several watchpoints that watch the same expression, but with different conditions and/or commands, without wasting debug registers which are in short supply. GDB maintains the reference counts internally, targets don’t have to do anything to use this feature.

The x86 debug registers can each watch a region that is 1, 2, or 4 bytes long. The ia32 architecture requires that each watched region be appropriately aligned: 2-byte region on 2-byte boundary, 4-byte region on 4-byte boundary. However, the x86 watchpoint support in GDB can watch unaligned regions and regions larger than 4 bytes (up to 16 bytes) by allocating several debug registers to watch a single region. This allocation of several registers per a watched region is also done automatically without target code intervention.

The generic x86 watchpoint support provides the following API for the GDB’s application code:

  • i386_region_ok_for_watchpoint (''addr'', ''len'')

The macro TARGET_REGION_OK_FOR_HW_WATCHPOINT is set to call this function. It counts the number of debug registers required to watch a given region, and returns a non-zero value if that number is less than 4, the number of debug registers available to x86 processors.

  • i386_stopped_data_address (''addr_p'')

The target function target_stopped_data_address is set to call this function. This function examines the breakpoint condition bits in the DR6 Debug Status register, as returned by the I386_DR_LOW_GET_STATUS macro, and returns the address associated with the first bit that is set in DR6.

  • i386_stopped_by_watchpoint (void)

The macro STOPPED_BY_WATCHPOINT is set to call this function. The argument passed to STOPPED_BY_WATCHPOINT is ignored. This function examines the breakpoint condition bits in the DR6 Debug Status register, as returned by the I386_DR_LOW_GET_STATUS macro, and returns true if any bit is set. Otherwise, false is returned.

  • i386_insert_watchpoint (''addr'', ''len'', ''type'') i386_remove_watchpoint (''addr'', ''len'', ''type'')

Insert or remove a watchpoint. The macros target_insert_watchpoint and target_remove_watchpoint are set to call these functions. i386_insert_watchpoint first looks for a debug register which is already set to watch the same region for the same access types; if found, it just increments the reference count of that debug register, thus implementing debug register sharing between watchpoints. If no such register is found, the function looks for a vacant debug register, sets its mirrored value to addr, sets the mirrored value of DR7 Debug Control register as appropriate for the len and type parameters, and then passes the new values of the debug register and DR7 to the inferior by calling I386_DR_LOW_SET_ADDR and I386_DR_LOW_SET_CONTROL. If more than one debug register is required to cover the given region, the above process is repeated for each debug register.

i386_remove_watchpoint does the opposite: it resets the address in the mirrored value of the debug register and its read/write and length bits in the mirrored value of DR7, then passes these new values to the inferior via I386_DR_LOW_RESET_ADDR and I386_DR_LOW_SET_CONTROL. If a register is shared by several watchpoints, each time a i386_remove_watchpoint is called, it decrements the reference count, and only calls I386_DR_LOW_RESET_ADDR and I386_DR_LOW_SET_CONTROL when the count goes to zero.

  • i386_insert_hw_breakpoint (''bp_tgt'') i386_remove_hw_breakpoint (''bp_tgt'')

These functions insert and remove hardware-assisted breakpoints. The macros target_insert_hw_breakpoint and target_remove_hw_breakpoint are set to call these functions. The argument is a struct bp_target_info *, as described in the documentation for target_insert_breakpoint. These functions work like i386_insert_watchpoint and i386_remove_watchpoint, respectively, except that they set up the debug registers to watch instruction execution, and each hardware-assisted breakpoint always requires exactly one debug register.

  • i386_cleanup_dregs (void)

This function clears all the reference counts, addresses, and control bits in the mirror images of the debug registers. It doesn’t affect the actual debug registers in the inferior process.

Notes:

  1. x86 processors support setting watchpoints on I/O reads or writes. However, since no target supports this (as of March 2001), and since enum target_hw_bp_type doesn’t even have an enumeration for I/O watchpoints, this feature is not yet available to GDB running on x86.

  2. x86 processors can enable watchpoints locally, for the current task only, or globally, for all the tasks. For each debug register, there’s a bit in the DR7 Debug Control register that determines whether the associated address is watched locally or globally. The current implementation of x86 watchpoint support in GDB always sets watchpoints to be locally enabled, since global watchpoints might interfere with the underlying OS and are probably unavailable in many platforms.

 类似资料:

相关阅读

相关文章

相关问答