2012-01-20 19 views
10

Desidero impostare temporaneamente un punto di controllo (interruzione su scrittura hardware) nel mio programma C++ per individuare il danneggiamento della memoria.È possibile impostare un watchpoint gdb in modo programmatico?

Ho visto tutti i modi per farlo manualmente tramite gdb, ma mi piacerebbe effettivamente impostare il watchpoint tramite un metodo nel mio codice in modo da non dover entrare in gdb, trovare l'indirizzo, impostare il punto di controllo e poi continuare.

Qualcosa di simile:

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

risposta

3

In GDB, ci sono due tipi di punti di controllo, hardware e software.

  • non è possibile implementare facilmente watchpoints software: (cfr GDB Internals)

watchpoints software sono molto lenti, in quanto gdb ha bisogno di un solo passo il programma in fase di debug e testare il valore di l'espressione guardata (s) dopo ogni istruzione.

EDIT:

Sto ancora cercando di capire quali sono watchpoint hardware.

  • per i punti di interruzione hardware, this article dà alcune tecniche:

vogliamo guardare la lettura o la scrittura in 1 QWORD all'indirizzo 100005120h (Indirizzo 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 

Fatto, ora possiamo aspettare fino al codice fal Sono nella trappola! Dopo l'accesso qualsiasi byte a intervallo di memoria 100005120h-100005127h, int 1 si verificherà e po DR6.B0 sarà impostato a 1.

Si può anche dare un'occhiata al file di fascia bassa GDB (ad esempio, amd64-linux-nat.c), ma esso (certamente) coinvolge 2 processi: 1/quello che si desidera vedere 2/a debugger leggero che si attacca al primo con ptrace, e usa:

ptrace (PTRACE_POKEUSER, tid, __regnum__offset__, address); 

per impostare e gestire il watchpoint.

+0

dal tuo link: ** i punti di interruzione hardware sono a volte disponibili come un builtin funzioni di debug con alcuni chip.In genere questi funzionano con un registro dedicato in cui è possibile memorizzare l'indirizzo di breakpoint. ** – Neil

+0

@Neil sì, intendevo come è stato implementato; Ho aggiornato la risposta – Kevin

+0

GDB potrebbe non supportarlo, ma x86 certamente lo fa. Infatti il ​​file gdb a cui ti colleghi mostra come farlo! Vedi la funzione ** i386_insert_aligned_watchpoint **. Sembra impostare in modo efficace il registro DR7, ma presumo che sia un'istruzione privilegiata, quindi non posso usarlo dalla modalità non kernel. – Neil

0

Il programma stesso può fornire comandi al GDB. Avrai però bisogno di uno script di shell speciale per eseguire GDB.

Copia questo codice in un file nominato untee, ed eseguire 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 

E ora il codice 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; 
} 

copia che per il file denominato di prova .c, compilare con il comando gcc test.c -O0 -g -o test quindi eseguire ./untee/tmp/dbgpipe | gdb -ex "run" ./test

Questo funziona sulla mia Ubuntu a 64 bit, con GDB 7.3 (versioni precedenti GDB potrebbero rifiutarsi di leggere i comandi da non terminale)

10

Set hardware watchpoint dal processo figlio .

#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

Grazie, questo sarà molto utile per il debug delle scritture di memoria cattiva. Domanda, qual è lo scopo dell'affermazione del sonno (1)? Senza di esso, non funziona, ma poiché avrò un ciclo che imposta e cancella ripetutamente i punti di osservazione, non voglio aspettare così a lungo. Inoltre, è possibile impostare il watchpoint senza un processo figlio? Ho provato semplicemente a spostare le chiamate ptrace sul processo genitore, ma falliscono? –

1

Se vi capita di utilizzare Xcode, è possibile ottenere la necessaria effetto (impostazione automatica di watchpoints) utilizzando un'azione su un altro punto di interruzione per impostare il watchpoint:

  1. Impostare una punto di interruzione da qualche parte in cui la variabile che si desidera vedere sarà nell'ambito che verrà colpito prima di iniziare a guardare la variabile,
  2. Fare clic con il tasto destro sul punto di interruzione e selezionare Modifica punto di interruzione ...,
  3. Cliccare sulla Aggiungi azione e aggiungere un debugger comandocon un comando LLDB come: watchpoint set variable <variablename> (o se si sta utilizzando GDB , un comando come: watch <variablename>),
  4. Controllare il automaticamente continuare dopo aver valutato le azioni casella di controllo.

enter image description here

1: GDB non è più supportato nelle versioni più recenti di Xcode, ma credo che sia ancora possibile configurarlo manualmente.

+1

Se stavo programmando su Windows, sarei stato in grado di farlo fin dalla metà degli anni '90. Sto ancora aspettando che Linux lo raggiunga! – Neil

0

Basato sul grande risposta di user512106, ho codificato un po ' "libreria" che qualcuno potrebbe trovare utile:

E' su github a https://github.com/whh8b/hwbp_lib. Vorrei poter commentare direttamente la sua risposta, ma non ho ancora abbastanza rep.

Sulla base del feedback da parte della comunità, ho intenzione di copiare/incollare il codice in questione qui:

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

Spero che questo aiuta qualcuno!

Will

+0

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il link per riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia. - [Dalla recensione] (/ recensione/post di bassa qualità/17842042) –

+0

Grazie per il feedback. Non avevo capito che avrei dovuto farlo in quel modo. Ho aggiornato la mia risposta. Mi dispiace! –

Problemi correlati