2012-01-20 35 views
10

Tôi muốn đặt điểm giám sát (tạm dừng ghi phần cứng) tạm thời trong chương trình C++ của mình để tìm lỗi bộ nhớ.Có thể thiết lập điểm theo dõi gdb theo chương trình không?

Tôi đã xem tất cả các cách để thực hiện thủ công thông qua gdb, nhưng tôi thực sự muốn đặt điểm quan sát thông qua một số phương pháp trong mã của tôi để tôi không phải đột nhập vào gdb, tìm địa chỉ, bộ điểm quan sát và sau đó tiếp tục.

Cái gì như:

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

Trả lời

3

Trong GDB, có hai loại watchpoints, phần cứng và phần mềm.

  • bạn không thể thực hiện dễ dàng watchpoints phần mềm: (x GDB Internals)

watchpoints phần mềm là rất chậm, vì gdb cần một bước chương trình đang được gỡ rối và kiểm tra giá trị của (các) biểu thức được theo dõi sau mỗi lệnh.

EDIT:

tôi vẫn đang cố gắng để hiểu watchpoint phần cứng là gì.

  • cho breakpoint phần cứng, this article đưa ra một số kỹ thuật:

Chúng tôi muốn xem đọc từ hoặc viết vào 1 qword tại địa chỉ 100005120h (địa chỉ phạm vi 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 

Xong, bây giờ chúng tôi có thể đợi cho đến khi mã ls vào cái bẫy! Sau khi truy cập vào bất kỳ byte ở cự ly nhớ 100005120h-100005127h, int 1 sẽ xảy ra và chút DR6.B0 sẽ được thiết lập để 1.

Bạn cũng có thể xem xét các file low-end GDB (ví dụ, amd64-linux-nat.c) nhưng nó (chắc chắn) bao gồm 2 quá trình: 1/một trong những bạn muốn xem 2/a debugger nhẹ người gắn vào một đầu tiên với ptrace, và sử dụng:

ptrace (PTRACE_POKEUSER, tid, __regnum__offset__, address); 

để thiết lập và xử lý các watchpoint.

+0

Từ liên kết của bạn: ** breakpoint phần cứng đôi khi có sẵn như là một tính năng gỡ lỗi BUILTIN với một số chip.Thông thường các tác phẩm này có đăng ký chuyên dụng vào đó địa chỉ điểm ngắt có thể được lưu trữ. ** – Neil

+0

@Neil yes, tôi có nghĩa là nó đã được triển khai như thế nào; Tôi đã cập nhật câu trả lời – Kevin

+0

GDB có thể không hỗ trợ nó, nhưng chắc chắn là x86. Infact tập tin gdb bạn liên kết để cho thấy làm thế nào để làm điều đó! Xem hàm ** i386_insert_aligned_watchpoint **. Nó có vẻ hiệu quả thiết lập thanh ghi DR7, nhưng tôi đoán đó là một hướng dẫn đặc quyền vì vậy tôi không thể sử dụng nó từ chế độ không phải hạt nhân. – Neil

0

Bản thân chương trình có thể cung cấp lệnh cho GDB. Bạn sẽ cần một kịch bản shell đặc biệt để chạy GDB.

sao chép mã này vào file có tên untee, và thực hiện 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 

Và bây giờ mã 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; 
} 

Sao chép rằng vào file có tên thử nghiệm .c, biên dịch bằng lệnh gcc test.c -O0 -g -o test rồi thực hiện ./untee/tmp/dbgpipe | gdb -ex "chạy" ./test

này hoạt động trên 64-bit Ubuntu của tôi, với GDB 7.3 (phiên bản cũ GDB có thể từ chối để đọc lệnh từ phi terminal)

10

Set watchpoint phần cứng từ tiến trình con .

#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

Cảm ơn, điều này sẽ rất hữu ích để gỡ lỗi ghi bộ nhớ xấu. Câu hỏi, mục đích của câu lệnh ngủ (1) là gì? Nếu không có nó, nó không hoạt động, nhưng vì tôi sẽ có một vòng lặp liên tục thiết lập và xóa các điểm quan sát, tôi không muốn chờ đợi lâu. Ngoài ra, có thể đặt điểm quan sát mà không có quy trình con không? Tôi đã thử chỉ đơn giản là di chuyển các cuộc gọi ptrace đến quá trình cha mẹ, nhưng họ thất bại? –

1

Nếu bạn tình cờ được sử dụng Xcode, bạn có thể đạt được các yêu cầu hiệu lực thi hành (thiết lập tự động watchpoints) bằng cách sử dụng một hành động trên breakpoint khác để thiết lập watchpoint của bạn:

  1. Thiết lập điểm ngắt ở đâu đó mà biến bạn muốn xem sẽ nằm trong phạm vi sẽ được nhấn trước khi bạn cần bắt đầu xem biến,
  2. Nhấp chuột phải vào điểm ngắt và chọn Chỉnh sửa điểm ngắt ...,
  3. Bấm vào Thêm Action và thêm một Debugger lệnhvới một lệnh LLĐB như: watchpoint set variable <variablename> (hoặc nếu bạn đang sử dụng GDB , một lệnh như: watch <variablename>),
  4. Kiểm tra Tự động tiếp tục sau khi đánh giá các hành động.

enter image description here

1: GDB không còn được hỗ trợ trong nhiều phiên bản gần đây của Xcode, nhưng tôi tin rằng nó vẫn còn có thể thiết lập nó bằng tay.

+1

Nếu tôi đang lập trình trên Windows, tôi đã có thể thực hiện việc này từ giữa những năm 90. Vẫn đang chờ Linux bắt kịp! – Neil

0

Dựa trên câu trả lời tuyệt vời user512106, tôi mã hóa lên một chút "thư viện" rằng ai đó có thể tìm thấy hữu ích:

Đó là trên github tại https://github.com/whh8b/hwbp_lib. Tôi ước gì tôi có thể nhận xét trực tiếp câu trả lời của anh ấy, nhưng tôi chưa có đủ đại diện.

Dựa trên phản hồi từ cộng đồng, tôi sẽ sao chép/dán mã liên quan ở đây:

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

Tôi hy vọng rằng điều này sẽ giúp người!

Will

+0

Trong khi liên kết này có thể trả lời câu hỏi, tốt hơn nên bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở thành không hợp lệ nếu trang được liên kết thay đổi. - [Từ đánh giá] (/ đánh giá/bài đăng chất lượng thấp/17842042) –

+0

Cảm ơn phản hồi. Tôi không nhận ra mình nên làm theo cách đó. Tôi đã cập nhật câu trả lời của mình. Tôi xin lỗi! –

Các vấn đề liên quan