使用GDB调试(下篇:调试应用)

在 GDB(GNU Debugger)中,有一些常用的调试命令可以帮助你在调试过程中检查程序的状态、执行程序、设置断点等。以下是一些常用的 GDB 调试命令:

  1. 启动程序和执行控制:

    • run: 启动正在调试的程序。
    • continue (c): 继续执行程序,直到遇到下一个断点或者程序结束。
    • next (n): 执行程序的下一行,但是如果有函数调用,则会一次性执行完函数内部所有语句。
    • step (s): 执行程序的下一行,如果是函数调用,则进入函数内部。
    • finish: 执行完当前函数的剩余部分,并返回到调用该函数的地方。
  2. 断点:

    • break (b<line>: 在指定行设置断点。
    • break <function>: 在指定函数设置断点。
    • delete <breakpoint number>: 删除指定编号的断点。
    • clear <line>: 清除指定行的断点。
    • info breakpoints: 显示当前所有断点的信息。
  3. 查看变量和表达式:

    • print (p<variable>: 打印变量的值。
    • display <expression>: 持续打印表达式的值,每次停止时都会显示。
    • info locals: 显示当前作用域内的所有局部变量。
    • info args: 显示当前函数的参数列表和值。
    • info breakpoints: 显示当前所有断点的信息。
  4. 堆栈和调用信息:

    • backtrace (bt): 显示当前的函数调用堆栈。
    • frame (f<number>: 切换到指定堆栈帧。
    • up (u): 在堆栈中向上移动一帧。
    • down (d): 在堆栈中向下移动一帧。
  5. 控制程序执行:

    • kill: 终止正在调试的程序。
    • quit (q): 退出 GDB 调试器。
  6. 设置和配置:

    • set <variable> <value>: 设置 GDB 的变量或选项。
    • show <variable>: 显示当前设置的变量或选项。
  7. 其他常用命令:

    • help (h): 显示 GDB 帮助信息,可以查看特定命令的用法。
    • info <info-type>: 显示关于程序状态的特定信息,如 info breakpointsinfo registers 等。

这些命令覆盖了常见的调试需求,可以帮助你在 GDB 中有效地分析和调试程序

当涉及更复杂的调试场景或者需要更精细控制的时候,GDB 提供了一些高级的操作指令,可以帮助进一步深入调试程序。以下是一些高级操作指令的示例:

  1. 条件断点:

    • break <location> if <condition>: 在满足特定条件时设置断点。例如:
      Copy Code
      (gdb) break main if argc > 1
      
      这将在 main 函数入口处,且 argc 大于 1 时设置断点。
  2. 观察点(Watchpoints):

    • watch <expression>: 设置当表达式的值发生变化时停止程序执行的观察点。例如:
      Copy Code
      (gdb) watch x
      
      这将在变量 x 的值发生变化时停止程序执行。
  3. 信号处理:

    • handle <signal> <action>: 指定 GDB 如何处理特定信号。例如:
      Copy Code
      (gdb) handle SIGINT nostop
      
      这将使 GDB 在接收到 SIGINT 信号(如 Ctrl+C)时不暂停程序执行。
  4. 改变程序状态:

    • set variable <variable> <value>: 修改程序中的变量的值。例如:
      Copy Code
      (gdb) set variable x = 10
      
      这将将变量 x 的值设置为 10。
  5. 查看寄存器:

    • info registers: 显示当前所有寄存器的值。
    • print $register: 打印特定寄存器的值,如 $eax$ebx 等。
  6. 线程和并发调试:

    • info threads: 显示当前所有线程的信息。
    • thread <thread-id>: 切换到指定的线程。
    • thread apply <thread-id-list> <command>: 对指定的多个线程执行相同的 GDB 命令。
  7. 反汇编和内存查看:

    • disassemble <function>: 反汇编指定函数的代码。
    • x/<n><format> <address/expression>: 以特定格式查看内存内容。例如:
      Copy Code
      (gdb) x/4xw &variable
      
      这将以 4 个 32 位字的十六进制格式查看 variable 的内存内容。
  8. 宏和脚本:

    • define/end: 定义和执行简单的 GDB 宏。例如:
      Copy Code
      (gdb) define printargs
      > info args
      > end
      
      这将定义一个 printargs 宏,执行时会显示当前函数的参数。
  9. 远程调试:

    • target remote <hostname:port>: 连接到远程调试目标。
  10. Python 脚本支持:

    • GDB 支持 Python 扩展,可以编写更复杂的脚本来增强调试功能。

这些高级操作指令可以帮助你更精确地控制和分析程序的执行过程,在处理复杂调试任务时尤其有用。随着对 GDB 更深入的了解和经验积累,你可以根据具体的调试需求利用这些功能来更有效地解决问题。

 

当进行基本的 GDB 调试时,我们可以以一个简单的 C 语言程序为例来演示。假设我们有以下的 C 程序 example.c

cCopy Code
#include <stdio.h>

int main() {
    int a = 5;
    int b = 10;
    int sum = a + b;

    printf("Sum of %d and %d is %d\n", a, b, sum);

    return 0;
}

现在,我们将使用 GDB 来调试这个程序,跟踪变量的值,设置断点并执行。

  1. 编译程序并加入调试信息

首先,我们需要用 -g 选项来编译程序,以便生成调试信息。在命令行中执行:

bashCopy Code
gcc -g -o example example.c

这将生成可执行文件 example,其中包含了调试信息。

  1. 启动 GDB 并加载程序

现在,我们可以启动 GDB 并加载刚刚编译的 example 程序:

bashCopy Code
gdb ./example
  1. 在 GDB 中进行基本调试

现在我们进入了 GDB 的交互界面。接下来是一些基本的 GDB 操作:

  • 查看源代码

使用 list 命令查看当前位置附近的源代码:

Copy Code
(gdb) list
1       #include <stdio.h>
2       
3       int main() {
4           int a = 5;
5           int b = 10;
6           int sum = a + b;
7       
8           printf("Sum of %d and %d is %d\n", a, b, sum);
9       
10          return 0;
  • 设置断点

main 函数的某行上设置一个断点,例如在第 6 行:

Copy Code
(gdb) break 6
Breakpoint 1 at 0x1189: file example.c, line 6.
  • 运行程序

运行程序,GDB 将在设置的断点处停止:

Copy Code
(gdb) run
Starting program: /path/to/example 

Breakpoint 1, main () at example.c:6
6           int sum = a + b;
  • 查看变量的值

在程序暂停时,我们可以查看变量 absum 的值:

Copy Code
(gdb) print a
$1 = 5
(gdb) print b
$2 = 10
(gdb) print sum
$3 = 15
  • 单步执行

使用 next 命令进行单步执行,逐行查看程序的运行:

Copy Code
(gdb) next
7           printf("Sum of %d and %d is %d\n", a, b, sum);
(gdb) next
Sum of 5 and 10 is 15
8       
  • 继续执行

继续执行程序直到结束:

Copy Code
(gdb) continue
Continuing.
[Inferior 1 (process 1234) exited normally]

这就完成了基本的 GDB 调试过程。通过以上步骤,你可以学习如何在 GDB 中设置断点、查看变量、单步执行程序以及继续执行程序。这些是进行程序调试时常用的基本操作。

 

当需要进行更高级的 GDB 调试时,可能涉及到更复杂的程序结构、多线程、动态内存分配等情况。下面我将示例一个稍复杂的程序,并展示如何利用 GDB 的高级功能进行调试。

假设我们有以下的 C 程序 advanced_example.c,它包含了多线程和动态内存分配:

cCopy Code
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NUM_THREADS 3

struct ThreadData {
    int thread_id;
    double *array;
    int array_size;
};

void *thread_func(void *data) {
    struct ThreadData *my_data = (struct ThreadData *)data;
    int id = my_data->thread_id;
    double *array = my_data->array;
    int size = my_data->array_size;

    for (int i = 0; i < size; ++i) {
        array[i] = id * 0.5 * i;
    }

    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    struct ThreadData thread_data[NUM_THREADS];
    double *arrays[NUM_THREADS];

    // Create arrays dynamically
    for (int i = 0; i < NUM_THREADS; ++i) {
        arrays[i] = (double *)malloc(100 * sizeof(double));
    }

    // Launch threads
    for (int i = 0; i < NUM_THREADS; ++i) {
        thread_data[i].thread_id = i;
        thread_data[i].array = arrays[i];
        thread_data[i].array_size = 100;
        pthread_create(&threads[i], NULL, thread_func, (void *)&thread_data[i]);
    }

    // Wait for threads to complete
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(threads[i], NULL);
    }

    // Free allocated memory
    for (int i = 0; i < NUM_THREADS; ++i) {
        free(arrays[i]);
    }

    return 0;
}

高级 GDB 调试功能示例

  1. 编译程序并加入调试信息

与之前相同,使用 -g 选项编译程序:

bashCopy Code
gcc -g -o advanced_example advanced_example.c -pthread
  1. 启动 GDB 并加载程序
bashCopy Code
gdb ./advanced_example
  1. 设置断点和观察多线程
  • 设置断点 在 main 函数的某行设置断点,例如:
Copy Code
(gdb) break 39
  • 启动程序
Copy Code
(gdb) run
  • 查看线程信息

在程序执行过程中,可以使用 info threads 命令查看当前的线程信息:

Copy Code
(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0x7ffff7fb9740 (LWP 18272) "advanced_example" 0x0000000000400d49 in main () at advanced_example.c:39
  2    Thread 0x7ffff779a740 (LWP 18273) "advanced_example" thread_func (data=0x7fffffffdea0) at advanced_example.c:19
  3    Thread 0x7ffff6f99740 (LWP 18274) "advanced_example" thread_func (data=0x7fffffffdea0) at advanced_example.c:19

这里显示了三个线程的信息,包括线程 ID 和当前位置。

  • 切换线程

可以使用 thread <id> 命令切换到不同的线程进行调试:

Copy Code
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff779a740 (LWP 18273))]
#0  thread_func (data=0x7fffffffdea0) at advanced_example.c:19
19          for (int i = 0; i < size; ++i) {

现在可以查看当前线程的堆栈信息和变量值。

  1. 动态内存分配的调试
  • 观察动态内存

在 GDB 中可以使用 print 命令查看动态分配的内存地址和内容:

Copy Code
(gdb) print arrays[0]
$1 = (double *) 0x602010
(gdb) print *arrays[0]
$2 = {0, 0, 1, 1.5, ......}

这样可以检查动态分配数组的内容和内存地址。

  1. 其他高级功能
  • 使用条件断点 在特定条件下触发断点。
  • 监视点和表达式 使用 watch 命令监视变量,或者使用 display 命令在每次停止时显示表达式的值。
  • 调试优化 使用 GDB 的 -Og 选项进行与优化代码的调试。

通过这些高级功能,你可以更有效地调试复杂的程序,包括多线程、动态内存分配和复杂的数据结构。每个功能都能帮助你深入理解程序的运行状态和代码执行路径。

 

 

posted @ 2024-08-06 15:43  jest549  阅读(60)  评论(0编辑  收藏  举报