2012-01-20 26 views
10

Chcę ustawić tymczasowo punkt obserwacyjny (przerwę na zapis sprzętowy) w moim programie C++, aby znaleźć uszkodzenie pamięci.Czy można ustawić programowo punkt kontrolny gdb?

Widziałem wszystkie sposoby, aby zrobić to ręcznie za pomocą gdb, ale chciałbym ustawić punkt obserwacyjny za pomocą jakiejś metody w moim kodzie, więc nie muszę się włamać do gdb, znaleźć adresu, ustawić punkt obserwacyjny, a następnie kontynuuj.

Coś jak:

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

Odpowiedz

3

W GDB, istnieją dwa rodzaje watchpoints, sprzętu i oprogramowania.

  • nie można wdrożyć łatwo watchpoints oprogramowania (por GDB Internals)

watchpoints oprogramowania są bardzo powolne, ponieważ gdb musi pojedynczego kroku program jest pozbawione błędów i testowanie wartości obserwowane wyrażenie (wyrażenia) po każdej instrukcji.

EDIT:

Wciąż próbuję zrozumieć, co to sprzętowy watchpoint.

  • dla przerwań sprzętowych, this article daje pewne techniki:

chcemy oglądać odczytywania lub zapisywania danych na 1 qword pod adresem 100005120h (adres zakresu 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 

Zrobione, teraz możemy poczekać, aż kod zostanie uszkodzony Wpadnij w pułapkę! Po wejściu do dowolnego bajt w zakresie pamięci 100005120h-100005127h, int 1 nastąpi i DR6.B0 bit zostanie ustawiony na 1.

Można również spojrzeć na GDB plików low-end (np amd64-linux-nat.c), ale to (oczywiście) obejmuje 2 procesy: 1/jeden chcesz oglądać 2/lekki debugger, który przywiązuje do pierwszego z ptrace i zastosowań:

ptrace (PTRACE_POKEUSER, tid, __regnum__offset__, address); 

celu ustawienia i obsługi watchpoint.

+0

ze swojego linku: ** punkty przerwania sprzętowe są czasami dostępne jako wbudowanych funkcji debugowania z niektórych chipów.Zazwyczaj te działania mają dedykowany rejestr, w którym można zapisać adres punktu przerwania. ** – Neil

+0

@Neil tak, miałem na myśli sposób, w jaki został on wdrożony; Zaktualizowałem odpowiedź: – Kevin

+0

GDB może jej nie obsługiwać, ale x86 na pewno to robi. Zainfekuj plik GDB, który łączysz, pokazuje, jak to zrobić! Zobacz funkcję ** i386_insert_aligned_watchpoint **. Wydaje się, że skutecznie ustawia rejestr DR7, ale zakładam, że jest to instrukcja uprzywilejowana, więc nie mogę jej używać z trybu innego niż jądro. – Neil

0

Sam program może dostarczać polecenia do GDB. Do uruchomienia GDB potrzebny jest jednak specjalny skrypt powłoki.

skopiować ten kod do pliku o nazwie untee i wykonać 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 

A teraz kod 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; 
} 

Zrozumiałem do pliku o nazwie Test .c, skompilować za pomocą polecenia gcc test.c -O0 -g -o test następnie wykonać ./untee/tmp/dbgpipe | GDB -EX "run" ./test

To działa na moim 64-bitowe Ubuntu, z GDB 7.3 (starsze wersje GDB może odmówić czytać polecenia z non-terminal)

10

zestaw sprzętowy watchpoint z procesu potomnego .

#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

Dzięki, będzie to bardzo przydatne do debugowania złych zapisów pamięci. Pytanie, jaki jest cel oświadczenia snu (1)? Bez niego to nie działa, ale ponieważ będę miał pętlę, która wielokrotnie ustawia i czyści punkty, nie chcę czekać tak długo. Czy możliwe jest ustawienie punktu obserwacji bez procesu potomnego? Próbowałem po prostu przenieść wywołania ptrace do procesu nadrzędnego, ale zawodzą? –

1

Jeśli zdarzy się przy użyciu Xcode, możesz osiągnąć wymaganą efekt (Automatyczne ustawianie watchpoints) za pomocą działania na innym przerwania ustawić watchpoint:

  1. Skonfiguruj punkt przerwania w miejscu, w którym zmienna, którą chcesz obejrzeć, znajdzie się w zasięgu, który zostanie trafiony, zanim zaczniesz oglądać zmienną,
  2. Kliknij prawym przyciskiem myszy punkt przerwania i wybierz opcję Edytuj punkt przerwania ...,
  3. Kliknij Dodaj działanie i dodać Debugger Komenda z polecenia LLDB jak: watchpoint set variable <variablename> (lub jeśli używasz GDB , poleceniem: watch <variablename>)
  4. Sprawdź Automatycznie kontynuować po dokonaniu oceny działań pole wyboru.

enter image description here

1: GDB nie jest już obsługiwana w nowszych wersjach Xcode, ale uważam, że jest nadal możliwe, aby ustawić go ręcznie.

+1

Gdybym programował w systemie Windows, byłbym w stanie to zrobić od połowy lat 90-tych. Nadal czekam, aż Linux się dogoni! – Neil

0

oparciu o wielkiej odpowiedź user512106 za, mam zakodowane się trochę „biblioteki”, że ktoś może się przydać:

to na github na https://github.com/whh8b/hwbp_lib. Chciałbym móc skomentować bezpośrednio jego odpowiedź, ale nie mam jeszcze wystarczającej liczby przedstawicieli.

Na podstawie informacji zwrotnych od społeczności, mam zamiar skopiować/wkleić odpowiedni kod tutaj:

#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; 
} 

Mam nadzieję, że ktoś pomoże!

Will

+0

Podczas gdy ten link może odpowiedzieć na pytanie, lepiej umieścić w nim istotne części odpowiedzi i podać link do odsyłacza. Odpowiedzi dotyczące linków mogą stać się nieprawidłowe, jeśli strona z linkami się zmieni. - [Z recenzji] (/ opinia/niskiej jakości-posts/17842042) –

+0

Dzięki za opinię. Nie wiedziałem, że powinienem był to zrobić w ten sposób. Zaktualizowałem swoją odpowiedź. Przepraszam! –

Powiązane problemy