是否可以通过编程方式设置gdb观察点?

时间:2012-01-20 12:48:06

标签: c++ gdb watchpoint

我想在我的C ++程序中暂时设置一个观察点(硬件写入中断)来查找内存损坏。

我已经看过所有通过gdb手动完成的方法,但我想通过我的代码中的某些方法实际设置watchpoint,所以我不必闯入gdb,查找地址,设置观察点然后继续。

类似的东西:

#define SET_WATCHPOINT(addr) asm ("set break on hardware write %addr")

5 个答案:

答案 0 :(得分:12)

从子进程设置硬件观察点。

#include <signal.h>
#include <syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/user.h>

enum {
    DR7_BREAK_ON_EXEC  = 0,
    DR7_BREAK_ON_WRITE = 1,
    DR7_BREAK_ON_RW    = 3,
};

enum {
    DR7_LEN_1 = 0,
    DR7_LEN_2 = 1,
    DR7_LEN_4 = 3,
};

typedef struct {
    char l0:1;
    char g0:1;
    char l1:1;
    char g1:1;
    char l2:1;
    char g2:1;
    char l3:1;
    char g3:1;
    char le:1;
    char ge:1;
    char pad1:3;
    char gd:1;
    char pad2:2;
    char rw0:2;
    char len0:2;
    char rw1:2;
    char len1:2;
    char rw2:2;
    char len2:2;
    char rw3:2;
    char len3:2;
} dr7_t;

typedef void sighandler_t(int, siginfo_t*, void*);

int watchpoint(void* addr, sighandler_t handler)
{
    pid_t child;
    pid_t parent = getpid();
    struct sigaction trap_action;
    int child_stat = 0;

    sigaction(SIGTRAP, NULL, &trap_action);
    trap_action.sa_sigaction = handler;
    trap_action.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;
    sigaction(SIGTRAP, &trap_action, NULL);

    if ((child = fork()) == 0)
    {
        int retval = EXIT_SUCCESS;

        dr7_t dr7 = {0};
        dr7.l0 = 1;
        dr7.rw0 = DR7_BREAK_ON_WRITE;
        dr7.len0 = DR7_LEN_4;

        if (ptrace(PTRACE_ATTACH, parent, NULL, NULL))
        {
            exit(EXIT_FAILURE);
        }

        sleep(1);

        if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[0]), addr))
        {
            retval = EXIT_FAILURE;
        }

        if (ptrace(PTRACE_POKEUSER, parent, offsetof(struct user, u_debugreg[7]), dr7))
        {
            retval = EXIT_FAILURE;
        }

        if (ptrace(PTRACE_DETACH, parent, NULL, NULL))
        {
            retval = EXIT_FAILURE;
        }

        exit(retval);
    }

    waitpid(child, &child_stat, 0);
    if (WEXITSTATUS(child_stat))
    {
        printf("child exit !0\n");
        return 1;
    }

    return 0;
}

int var;

void trap(int sig, siginfo_t* info, void* context)
{
    printf("new value: %d\n", var);
}

int main(int argc, char * argv[])
{
    int i;

    printf("init value: %d\n", var);

    watchpoint(&var, trap);

    for (i = 0; i < 100; i++) {
        var++;
        sleep(1);
    }

    return 0;
}

答案 1 :(得分:3)

在GDB中,有两种类型的观察点,硬件和软件。

  

软件观察点非常慢,因为gdb需要单步执行正在调试的程序,并在每条指令后测试监视表达式的值。

修改

我仍在尝试了解什么是硬件观察点。

  

我们想在地址100005120h(地址范围100005120h-100005127h)观看1字qword的读写或写入

 lea rax, [100005120h]
 mov dr0, rax
 mov rax, dr7
 and eax, not ((1111b shl 16) + 11b)    ; mask off all
 or eax, (1011b shl 16) + 1     ; prepare to set what we want
 mov 
 dr7, rax               ; set it finally
  

完成,现在我们可以等到代码落入陷阱!访问存储器范围100005120h-100005127h的任何字节后,将发生int 1,DR6.B0位将设置为1.

您还可以查看GDB低端文件(例如amd64-linux-nat.c),但它(当然)涉及2个进程:1 /您想要观看的2 /轻量级调试器附加到第一个使用ptrace,并使用:

ptrace (PTRACE_POKEUSER, tid, __regnum__offset__, address);

设置和处理观察点。

答案 2 :(得分:3)

根据user512106的优秀答案,我编写了一个有人可能会觉得有用的“库”:

它位于https://github.com/whh8b/hwbp_lib的github上。我希望我能直接评论他的答案,但我还没有足够的代表。

根据社区的反馈,我将在此处复制/粘贴相关代码:

#include <stdio.h>
#include <stddef.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/prctl.h>
#include <stdint.h>
#include <errno.h>
#include <stdbool.h>

extern int errno;

enum {
    BREAK_EXEC = 0x0,
    BREAK_WRITE = 0x1,
    BREAK_READWRITE = 0x3,
};

enum {
    BREAK_ONE = 0x0,
    BREAK_TWO = 0x1,
    BREAK_FOUR = 0x3,
    BREAK_EIGHT = 0x2,
};

#define ENABLE_BREAKPOINT(x) (0x1<<(x*2))
#define ENABLE_BREAK_EXEC(x) (BREAK_EXEC<<(16+(x*4)))
#define ENABLE_BREAK_WRITE(x) (BREAK_WRITE<<(16+(x*4)))
#define ENABLE_BREAK_READWRITE(x) (BREAK_READWRITE<<(16+(x*4)))

/*
 * This function fork()s a child that will use
 * ptrace to set a hardware breakpoint for 
 * memory r/w at _addr_. When the breakpoint is
 * hit, then _handler_ is invoked in a signal-
 * handling context.
 */
bool install_breakpoint(void *addr, int bpno, void (*handler)(int)) {
    pid_t child = 0;
    uint32_t enable_breakpoint = ENABLE_BREAKPOINT(bpno);
    uint32_t enable_breakwrite = ENABLE_BREAK_WRITE(bpno);
    pid_t parent = getpid();
    int child_status = 0;

    if (!(child = fork()))
    {
        int parent_status = 0;
        if (ptrace(PTRACE_ATTACH, parent, NULL, NULL))
            _exit(1);

        while (!WIFSTOPPED(parent_status))
            waitpid(parent, &parent_status, 0);

        /*
         * set the breakpoint address.
         */
        if (ptrace(PTRACE_POKEUSER,
                   parent,
                   offsetof(struct user, u_debugreg[bpno]),
                   addr))
            _exit(1);

        /*
         * set parameters for when the breakpoint should be triggered.
         */
        if (ptrace(PTRACE_POKEUSER,
                   parent,
                   offsetof(struct user, u_debugreg[7]),
                   enable_breakwrite | enable_breakpoint))
            _exit(1);

        if (ptrace(PTRACE_DETACH, parent, NULL, NULL))
            _exit(1);

        _exit(0);
    }

    waitpid(child, &child_status, 0);

    signal(SIGTRAP, handler);

    if (WIFEXITED(child_status) && !WEXITSTATUS(child_status))
        return true;
    return false;
}

/*
 * This function will disable a breakpoint by
 * invoking install_breakpoint is a 0x0 _addr_
 * and no handler function. See comments above
 * for implementation details.
 */
bool disable_breakpoint(int bpno) 
{
    return install_breakpoint(0x0, bpno, NULL);
}

/*
 * Example of how to use this /library/.
 */
int handled = 0;

void handle(int s) {
    handled = 1;
    return;
}

int main(int argc, char **argv) {
    int a = 0;

    if (!install_breakpoint(&a, 0, handle))
        printf("failed to set the breakpoint!\n");

    a = 1;
    printf("handled: %d\n", handled);

    if (!disable_breakpoint(0))
        printf("failed to disable the breakpoint!\n");

    return 1;
}

我希望这有助于某人!

威尔

答案 3 :(得分:1)

如果您正在使用Xcode,则可以通过在另一个断点上使用操作来设置观察点来实现所需的效果(观察点的自动设置):

  1. 在您需要开始观看变量之前,您想要观看的变量将在某个范围内设置断点,
  2. 右键单击断点并选择编辑断点...
  3. 单击添加操作并使用LLDB命令添加调试器命令,如:watchpoint set variable <variablename>(或者如果您正在使用GDB 1 ,命令如:watch <variablename>),
  4. 选中评估操作后自动继续复选框。
  5. enter image description here

    1:Xcode的更新版本不再支持GDB,但我相信仍然可以手动设置它。

答案 4 :(得分:0)

程序本身可以向GDB提供命令。您需要一个特殊的shell脚本来运行GDB。

将此代码复制到名为 untee 的文件中,然后执行 chmod 755 untee

#!/bin/bash

if [ -z "$1" ]; then
    echo "Usage: $0 PIPE | COMMAND"
    echo "This script will read the input from both stdin and PIPE, and supply it to the COMMAND."
    echo "If PIPE does not exist it will be created with mkfifo command."
    exit 0
fi

PIPE="$1"

if [ \! -e "$PIPE" ]; then
    mkfifo "$PIPE"
fi

if [ \! -p "$PIPE" ]; then
    echo "File $PIPE does not exist or is not a named pipe" > /dev/stderr
    exit 1
fi

# Open the pipe as a FD 3
echo "Waiting for $PIPE to be opened by another process" > /dev/stderr
exec 3<"$PIPE"
echo "$PIPE opened" > /dev/stderr
OPENED=true

while true; do
    read -t 1 INPUT
    RET=$?
    if [ "$RET" = 0 ]; then
        echo "$INPUT"
    elif [ "$RET" -lt 128 ]; then
        echo "stdin closed, exiting" > /dev/stderr
        break
    fi

    if $OPENED; then
        while read -t 1 -u 3 INPUT; do
            RET=$?
            if [ "$RET" = 0 ]; then
                echo "$INPUT"
            else
                if [ "$RET" -lt 128 ]; then
                    echo "$PIPE closed, ignoring" > /dev/stderr
                    OPENED=false
                fi
                break
            fi
        done
    fi
done

现在是C代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>

void gdbCommand(const char *c)
{
    static FILE * dbgpipe = NULL;
    static const char * dbgpath = "/tmp/dbgpipe";
    struct stat st;

    if( !dbgpipe && stat(dbgpath, &st) == 0 && S_ISFIFO(st.st_mode) )
            dbgpipe = fopen(dbgpath, "w");
    if( !dbgpipe )
        return;
    fprintf(dbgpipe, "%s\n", c);
    fflush(dbgpipe);
}

void gdbSetWatchpoint(const char *var)
{
    char buf[256];
    snprintf(buf, sizeof(buf), "watch %s", var);

    gdbCommand("up"); /* Go up the stack from the kill() system call - this may vary by the OS, you may need to walk the stack more times */
    gdbCommand("up"); /* Go up the stack from the gdbSetWatchpoint() function */
    gdbCommand(buf);
    gdbCommand("continue");
    kill(getpid(), SIGINT); /* Make GDB pause our process and execute commands */
}

int subfunc(int *v)
{
    *v += 5; /* GDB should pause after this line, and let you explore stack etc */
    return v;
}

int func()
{
    int i = 10;
    printf("Adding GDB watch for var 'i'\n");
    gdbSetWatchpoint("i");

    subfunc(&i);
    return i;
}

int func2()
{
    int j = 20;
    return j + func();
}


int main(int argc, char ** argv)
{
    func();
    func2();
    return 0;
}

将其复制到名为 test.c 的文件中,使用命令 gcc test.c -O0 -g -o test 进行编译,然后执行 ./entee/ tmp / dbgpipe | gdb -ex“run”./ test

这适用于我的64位Ubuntu,带有GDB 7.3(较旧的GDB版本可能拒绝从非终端读取命令)