Можно ли программно установить точку наблюдения GDB? - PullRequest
14 голосов
/ 20 января 2012

Я хочу временно установить точку наблюдения (перерыв при аппаратной записи) в моей программе на C ++, чтобы найти повреждение памяти.

Я видел все способы сделать это вручную через gdb, но я бы хотел на самом деле установить точку наблюдения с помощью некоторого метода в моем коде, чтобы мне не нужно было взламывать gdb, узнавать адрес, устанавливать смотреть и затем продолжать.

Что-то вроде:

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

Ответы [ 5 ]

12 голосов
/ 03 августа 2012

Установить аппаратную точку наблюдения из дочернего процесса.

#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;
}
3 голосов
/ 05 ноября 2017

Основываясь на отличном ответе пользователя user512106, я написал небольшую «библиотеку», которую кто-то может счесть полезной:

Это на github в https://github.com/whh8b/hwbp_lib. Хотелось бы, чтобы я прокомментировал его ответ, но у меня пока недостаточно репов.

Основываясь на отзывах сообщества, я собираюсь скопировать / вставить соответствующий код здесь:

#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 голосов
/ 20 января 2012

В GDB есть два типа точек наблюдения, аппаратное и программное обеспечение.

Программные точки наблюдения очень медленные, так как gdb необходимо выполнить пошаговую отладку программы и проверять значение отслеживаемого выражения (й) после каждой инструкции.

РЕДАКТИРОВАТЬ:

Я все еще пытаюсь понять, что такое аппаратная точка наблюдения.

  • для аппаратных точек останова, эта статья дает немного техники:

Мы хотим наблюдать чтение или запись в 1 слово по адресу 100005120h (диапазон адресов 100005120h-100005127h)

 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);

для установки и управления точкой наблюдения.

1 голос
/ 24 июня 2015

Если вы используете XCode, вы можете достичь требуемого эффекта (автоматическая установка точек наблюдения), используя действие для другой точки останова для установки вашей точки наблюдения:

  1. Установите точку останова где-нибудь, где переменная, которую вы хотите просмотреть, будет находиться в области видимости, которая будет нажата до того, как вам нужно будет начать наблюдение за переменной,
  2. Щелкните правой кнопкой мыши по точке останова и выберите Редактировать точку останова.. ,
  3. Нажмите Добавить действие и добавьте Команда отладчика с помощью команды LLDB, например: watchpoint set variable <variablename> (или если вы используете GDB 1 , команда типа: watch <variablename>),
  4. Установите флажок Автоматически продолжать после оценки действий .

enter image description here

1: GDB больше не поддерживается в более поздних версиях Xcode, но я считаю, что все еще возможно установить его вручную.

0 голосов
/ 20 января 2012

Сама программа может подавать команды в GDB.Для запуска 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 проверить , затем выполнить . / untee / tmp / dbgpipe |gdb -ex "run" ./test

Это работает на моем 64-битном Ubuntu с GDB 7.3 (более старые версии GDB могут отказаться от чтения команд из нетерминала)

...