GDB调试大师之路:解码Linux系统编程的奥秘

0. GDB调试命令手册:深入学习与解决问题

命令用法适用场景常见问题解决方案
breakb在指定位置设置断点在特定位置暂停执行未命中断点检查地址或函数名,如有需要重新编译带有调试符号的代码
runr启动程序或从头重新开始启动程序执行程序立即崩溃检查编译错误、缺失的库或不正确的程序参数
continuec在断点后继续程序执行在暂停后继续执行无限循环或意外行为检查代码逻辑或使用断点隔离问题
steps执行源代码的一行逐步执行程序程序行为不符合预期检查变量,检查逻辑错误
nextn执行到当前函数中的下一行在不进入函数调用的情况下移动程序跳过函数调用使用 step 或在函数内设置断点以进入函数
info breakpoints显示关于断点的信息检查断点的状态未命中断点确保断点正确设置,检查地址或函数名
printp打印变量或表达式的值在调试过程中查看变量值变量名或作用域不正确检查变量名并确保其在正确的作用域内
backtracebt显示当前调用堆栈查看函数调用层次结构无符号或调用堆栈信息不正确重新编译带有调试符号,检查编译/链接错误
list显示当前行周围的源代码检查源代码上下文未显示源代码确保源代码可用,GDB 能够访问到源代码
watch为变量或表达式设置监视点当指定变量更改时暂停执行观察点未触发检查变量名并确保其可访问
info locals显示本地变量的值查看当前函数中的本地变量未显示变量值确保程序在当前函数内执行,检查编译时是否启用了优化
info functions列出程序中的所有函数查看程序中定义的所有函数未显示函数列表检查是否正确加载了程序符号表
watch设置观察点,当表达式的值发生变化时中断监视特定变量或表达式的变化观察点未触发检查变量名,确保它在可见的作用域内
display持续显示表达式的值连续监视并显示表达式的值未显示期望的表达式值检查表达式是否正确,确保它在可见的作用域内
set设置变量的值修改程序中的变量值未成功设置变量值确保变量名正确,检查是否处于正确的上下文中
catch在特定事件发生时中断捕捉特定的异常或事件未捕捉到期望的事件检查异常类型或事件是否正确,可能需要使用 catch throwcatch catch
info threads显示当前线程信息查看多线程程序的线程信息未显示多线程信息保证程序是多线程的,检查是否正确编译和链接了多线程支持
set logging开启/关闭日志记录记录GDB会话,保存调试信息未成功记录日志文件检查文件路径和权限,确保GDB能够写入指定的文件
source执行GDB命令文件执行预定义的GDB命令脚本文件未执行预期的脚本检查脚本文件路径,确保脚本中的命令语法正确
reverse continue反向执行程序,直到下一个断点或结束反向执行程序,逆向调试未成功执行反向调试检查GDB版本是否支持反向调试,确认程序符号表是否可用
info registers显示所有寄存器的值查看寄存器值无符号或寄存器信息不正确重新编译带有调试符号,检查编译/链接错误
delete删除断点移除已设置的断点未成功删除断点使用正确的断点编号或 delete breakpoints 删除所有断点
info sharedlibrary显示已加载的共享库信息查看程序使用的共享库信息未显示共享库信息检查程序是否使用了共享库,确保符号表
info target显示目标程序和连接信息查看目标程序和连接信息未显示目标信息检查程序是否正确加载,确保调试信息和符号表可用
set follow-fork-mode设置子进程的调试模式调试多进程程序时设置子进程的调试模式无法跟踪子进程设置合适的调试模式,使用 set detach-on-fork off 在子进程中调试
info macro显示宏定义查看程序中的宏定义未显示宏定义检查是否启用了宏定义,程序是否包含了预处理指令
recordreplay记录和回放程序执行记录程序执行过程并回放未成功记录或回放检查GDB版本是否支持,确保程序和环境可重复执行
target sim设置目标仿真模式在仿真模式下调试程序无法进入仿真模式检查
monitor与GDB监视器交互在支持的调试监视器上执行特定命令未成功与监视器交互检查监视器是否正确连接,确保命令与监视器兼容
set pagination设置GDB分页输出的行数控制GDB输出是否分页显示输出过多导致难以阅读设置适当的分页大小,使用 set pagination off 关闭分页
set history设置GDB历史命令记录数量控制GDB保存的历史命令数量历史记录太短导致查找困难设置适当的历史记录数量,使用 show history 查看当前设置
set breakpoint pending设置断点是否等待符号加载在共享库加载时设置断点是否等待未在共享库加载前设置断点使用 set breakpoint pending on 等待共享库加载
tui enable启用文本用户界面在GDB中启用文本用户界面未启用文本用户界面检查GDB版本是否支持TUI,使用 tui disable 禁用TUI
set print pretty on设置打印结构体和类时的可读性打印结构体和类时更友好地显示结构体和类打印不易理解使用 set print pretty off 禁用可读性设置
maintenance info sources显示源文件的查找路径查看GDB用于查找源文件的路径未显示源文件查找路径检查源文件路径是否正确,使用 show directories 查看路径
set disassembly-flavor设置反汇编显示风格控制反汇编显示的格式反汇编显示不易理解使用 set disassembly-flavor 设置合适的反汇编显示风格
set prompt设置GDB命令行提示符定制GDB命令行提示符默认提示符不易辨识设置自定义的命令行提示符,如 set prompt (gdb)
set width设置GDB输出的行宽控制GDB输出的行宽行宽太宽或太窄导致显示不正常使用 show width 查看当前设置,使用 set width 设置合适的行宽

Linux系统编程GDB调试学习提纲

1. GDB简介

1.1 GDB是什么

GDB(GNU Debugger)是一个功能强大的开源调试器,主要用于调试程序,定位错误,以及对程序进行动态分析。它是GNU计划的一部分,支持多种编程语言,包括C、C++、Fortran等。GDB允许开发人员在程序运行的过程中观察变量、执行流程,并通过设置断点等功能进行交互式调试。

1.2 GDB的主要特性

  • 多语言支持: GDB支持多种编程语言,包括C、C++、Fortran等,使其成为一个通用的调试工具。

  • 交互式调试: GDB提供一个交互式的命令行界面,允许用户在程序执行时停下来,检查变量的值、执行单步操作等。

  • 断点和观察点: 用户可以在程序中设置断点,以便在特定位置停止执行,也可以设置观察点来监视变量的值变化。

  • 回溯和堆栈分析: GDB能够显示当前调用堆栈,帮助开发人员理解程序的执行流程,定位错误的源头。

  • 表达式求值: 用户可以在GDB中直接计算和显示表达式的值,有助于理解程序状态。

  • 多线程调试: 支持多线程程序的调试,允许用户查看和控制不同线程的状态。

1.3 GDB的用途

GDB主要用于以下方面:

  • 程序调试: 定位和修复程序中的错误,确保程序按照预期的方式执行。

  • 性能分析: 通过在程序中设置断点、观察点以及使用GDB的性能分析工具,开发人员可以评估程序的性能瓶颈。

  • 逆向工程: GDB可以用于逆向工程,帮助分析和理解已经编译的程序的运行过程。

  • 代码审查: 在程序执行的过程中,通过GDB查看变量、执行路径,有助于进行代码审查和调试。

2. 编译程序以支持GDB调试

2.1 使用调试信息

为了让GDB有效地进行调试,程序的可执行文件需要包含调试信息。调试信息包括源代码文件名、行号信息以及符号表等,这样GDB才能正确地映射机器代码和源代码。在编译程序时,需要确保包含了调试信息。

2.2 GCC编译选项

使用GCC编译器时,可以通过添加 -g 选项来生成调试信息。例如:

gcc -g -o my_program my_program.c

上述命令会编译源文件 my_program.c 并生成可执行文件 my_program,该文件包含了调试信息,便于后续使用GDB进行调试。

  • 2.3 Makefile配置
    如果使用Makefile进行项目管理,可以在Makefile中添加相应的规则,确保调试信息被正确生成。示例Makefile片段:
CC = gcc
CFLAGS = -g 

my_program: my_program.c
    $(CC) $(CFLAGS) -o my_program my_program.c

3. 启动和退出GDB

3.1 启动可执行文件

要在GDB中调试程序,首先需要启动GDB并加载相应的可执行文件。通过以下命令启动GDB:

gdb ./my_program

其中,./my_program 是你要调试的可执行文件的路径。GDB启动后,你可以使用各种调试命令来探查和操作程序的执行。

3.2 退出GDB

退出GDB很简单,可以使用 quit 或缩写的 q 命令。在GDB命令行中输入以下命令:

q

或者

quit

这会使GDB退出并返回到终端命令行。

4. 基本调试命令

  • 4.1 设置断点
    在GDB中,可以使用 break 或缩写的 b 命令来设置断点。例如,在函数入口处设置断点:
break main
  • 4.2 单步执行
    使用 step 或缩写的 s 命令进行单步执行,逐行执行程序并进入函数。
step
  • 4.3 继续执行
    使用 continue 或缩写的 c 命令继续执行程序,直到遇到下一个断点或程序结束。
continue
  • 4.4 查看变量值
    使用 print 或缩写的 p 命令查看变量的值。例如,查看变量 x 的值:
print x
  • 4.5 修改变量值
    使用 set 命令可以修改变量的值。例如,将变量 y 的值设置为 10:
set variable y = 10

以上是有关启动和退出GDB以及基本调试命令的详细信息,按照要求在markdown代码块中输出。如果有其他方面需要补充或有进一步的问题,随时告诉我。

5. 查看程序状态

  • 5.1 查看寄存器状态

    • 使用 info registers 命令可以查看当前所有寄存器的状态。
    • 可以通过 info registers register_name 查看特定寄存器的值,例如 info registers eax
  • 5.2 查看栈帧信息

    • 使用 info frame 命令可以查看当前栈帧的信息,包括函数调用栈的深度、局部变量、参数等信息。
    • 可以使用 btbacktrace 命令显示完整的函数调用栈。
  • 5.3 查看内存内容

    • 使用 x 命令可以查看内存的内容,语法为 x/[格式] [地址],例如 x/4xw 0x7fffffffec30 表示以十六进制格式查看地址 0x7fffffffec30 处的四个字(word)。
    • 可以使用 x/s 查看以空字符结尾的字符串,例如 x/s 0x7fffffffec30
  • 5.4 查看源代码

    • 使用 listl 命令可以查看源代码,语法为 list [函数名]:[行号],例如 list main:10
    • 可以使用 list 命令查看当前执行点所在的源代码。
  • 5.5 查看汇编代码

    • 使用 disassembledisas 命令可以查看当前函数的汇编代码,语法为 disassemble [函数名]disas [函数名]
    • 可以通过 layout asm 命令将源代码窗口切换为汇编代码视图。
  • 5.6 查看线程信息

    • 使用 info threads 命令可以查看当前所有线程的信息。
    • 可以使用 thread [线程号] 命令切换到特定线程进行调试。
  • 5.7 查看断点信息

    • 使用 info breakpoints 命令可以查看当前设置的断点信息。
    • 可以通过 delete [断点号] 删除特定断点,或者使用 delete 命令删除所有断点。
  • 5.8 查看变量值

    • 使用 printp 命令可以查看变量的值,语法为 print [变量名],例如 print x
    • 可以通过 display [表达式] 命令持续监视某个表达式的值,在每次程序停止时显示。
    • 示例代码:
      int main() {
          int x = 10;
          // ... some code ...   
      }
      
      在 GDB 中输入 print x 可以查看变量 x 的值。
  • 5.9 查看程序状态信息

    • 使用 info program 命令可以查看程序的一般状态信息,如程序计数器的值、程序文件等。
    • 使用 info sharedlibrary 可以查看已加载的共享库信息。
  • 5.10 查看信号处理

    • 使用 info signals 可以查看程序中当前设置的信号处理器。
    • 示例代码:
      #include <signal.h>
      #include <stdio.h>
      
      void signal_handler(int signo) {
          printf("Received signal: %d\n", signo);
      }
      
      int main() {
          signal(SIGINT, signal_handler);
          // ... some code ...
      }
      
      在 GDB 中使用 info signals 可以查看信号处理器的状态。
  • 5.11 查看文件信息

    • 使用 info files 可以查看与文件相关的信息,如源文件路径、可执行文件路径等。
    • 示例代码:
      #include <stdio.h>
      
      int main() {
          FILE *file = fopen("example.txt", "r");
          // ... some code ...
      }
      
      在 GDB 中使用 info files 可以查看文件相关信息。
  • 5.12 查看寄存器修改历史

    • 使用 display/i $pc 命令可以在每次停止时显示当前程序计数器的值,用于跟踪执行流程。
    • 示例代码:
      int main() {
          for (int i = 0; i < 5; ++i) {
              // ... some code ...
          }
      }
      
      在 GDB 中使用 display/i $pc 可以追踪程序计数器的变化。

6. 高级调试命令

  • 6.1 条件断点

    • 使用 break 命令时,可以通过条件表达式指定断点的触发条件,只有在条件满足时才会触发断点。
    • 示例代码:
      int main() {
          for (int i = 0; i < 10; ++i) {
              // ... some code ...
          }
          return 0;
      }
      
      在 GDB 中设置条件断点:break main if i == 5,当 i 的值为 5 时触发断点。
  • 6.2 确定程序崩溃原因

    • 使用 run 命令启动程序,当程序崩溃时,使用 btbacktrace 命令查看函数调用栈,定位崩溃位置。
    • 使用 info registers 查看寄存器状态,以获取更多关于崩溃原因的信息。
  • 6.3 追踪系统调用

    • 使用 catch syscall 命令可以在每次发生系统调用时停止程序的执行。
    • 示例代码:
      #include <unistd.h>
      
      int main() {
          write(1, "Hello, World!\n", 13);
          return 0;
      }
      
      在 GDB 中使用 catch syscall write 可以追踪 write 系统调用。
  • 6.4 处理信号

    • 使用 handle 命令可以控制 GDB 如何处理特定的信号,包括停止、继续执行等。
    • 示例代码:
      #include <signal.h>
      #include <unistd.h>
      
      void signal_handler(int signo) {
          // ... handle the signal ...
      }
      
      int main() {
          signal(SIGUSR1, signal_handler);
          // ... some code ...
      }
      
      在 GDB 中使用 handle SIGUSR1 nostop 可以设置 GDB 在收到 SIGUSR1 信号时不停止程序。

7. GDB脚本

  • 7.1 编写简单的GDB脚本

    • 使用 GDB 脚本可以自动执行一系列 GDB 命令,方便批量调试。
    • 示例代码:
      int main() {
          int x = 10;
          // ... some code ...
          return 0;
      }
      
      GDB 脚本示例:
      # gdb_script.gdb
      break main
      run
      print x
      
  • 7.2 自定义GDB命令

    • 使用 Python 编写 GDB 脚本,可以自定义 GDB 命令,以满足特定调试需求。
    • 示例代码:
      # gdb_script.py
      import gdb
      
      class AddNumbersCommand(gdb.Command):
          def __init__(self):
              super(AddNumbersCommand, self).__init__("add_numbers", gdb.COMMAND_USER)
      
          def invoke(self, arg, from_tty):
              args = gdb.string_to_argv(arg)
              if len(args) != 2:
                  print("Usage: add_numbers <num1> <num2>")
                  return
              result = int(args[0]) + int(args[1])
              print(f"Result: {result}")
      
      AddNumbersCommand()
      
      在 GDB 中加载 Python 脚本:source gdb_script.py,然后使用 add_numbers 命令执行自定义操作。

8. 多线程调试

  • 8.1 切换线程

    • 使用 thread [线程号] 命令可以切换当前活动线程,方便对多线程程序进行调试。
    • 示例代码:
      #include <pthread.h>
      #include <stdio.h>
      
      void* thread_func(void* arg) {
          int tid = *((int*)arg);
          for (int i = 0; i < 5; ++i) {
              printf("Thread %d: %d\n", tid, i);
          }
          return NULL;
      }
      
      int main() {
          pthread_t thread1, thread2;
          int tid1 = 1, tid2 = 2;
          pthread_create(&thread1, NULL, thread_func, (void*)&tid1);
          pthread_create(&thread2, NULL, thread_func, (void*)&tid2);
          // ... some code ...
          pthread_join(thread1, NULL);
          pthread_join(thread2, NULL);
          return 0;
      }
      
      在 GDB 中使用 info threads 查看线程信息,然后使用 thread [线程号] 切换到指定线程。
  • 8.2 查看线程信息

    • 使用 info threads 命令可以查看当前所有线程的信息,包括线程号、程序计数器等。
    • 示例代码:
      #include <pthread.h>
      #include <stdio.h>
      
      void* thread_func(void* arg) {
          int tid = *((int*)arg);
          for (int i = 0; i < 5; ++i) {
              printf("Thread %d: %d\n", tid, i);
          }
          return NULL;
      }
      
      int main() {
          pthread_t thread1, thread2;
          int tid1 = 1, tid2 = 2;
          pthread_create(&thread1, NULL, thread_func, (void*)&tid1);
          pthread_create(&thread2, NULL, thread_func, (void*)&tid2);
          // ... some code ...
          pthread_join(thread1, NULL);
          pthread_join(thread2, NULL);
          return 0;
      }
      
      在 GDB 中使用 info threads 查看线程信息。
  • 8.3 设置线程断点

    • 使用 break [函数名] thread [线程号] 可以在指定线程的特定函数上设置断点。
    • 示例代码:
      #include <pthread.h>
      #include <stdio.h>
      
      void* thread_func(void* arg) {
          int tid = *((int*)arg);
          printf("Thread %d: Entering thread_func\n", tid);
          // ... thread-specific code ...
          printf("Thread %d: Exiting thread_func\n", tid);
          return NULL;
      }
      
      int main() {
          pthread_t thread1, thread2;
          int tid1 = 1, tid2 = 2;
          pthread_create(&thread1, NULL, thread_func, (void*)&tid1);
          pthread_create(&thread2, NULL, thread_func, (void*)&tid2);
          // ... some code ...
          pthread_join(thread1, NULL);
          pthread_join(thread2, NULL);
          return 0;
      }
      
      在 GDB 中使用 break thread_func thread 1 可以在线程1的 thread_func 函数上设置断点。

9. 远程调试

  • 9.1 与GDB服务器连接

    • 使用 target extended-remote [主机名]:[端口号] 命令可以连接到远程 GDB 服务器。
    • 示例代码:
      $ gdb
      (gdb) target extended-remote 192.168.1.100:1234
      
      在本地 GDB 中通过 target extended-remote 命令连接到远程主机的 GDB 服务器。
  • 9.2 远程目标设置

    • 在远程目标上运行 GDB 服务器,等待 GDB 连接。
    • 示例代码:
      $ gdbserver :1234 ./your_remote_program
      
      在远程主机上运行 GDB 服务器,监听端口号 1234,并启动待调试的远程程序 your_remote_program

10. GDB调试常见问题与解决方法

  • 10.1 程序崩溃时的调试

    • 问题描述:程序崩溃时,需要获取更多信息进行调试。

    • 解决方法:

      • 使用 coredump 文件进行后续调试,使用 ulimit -c unlimited 命令设置核心转储文件大小。
      • 在 GDB 中加载核心转储文件:gdb ./your_program core
    • 示例代码:

      #include <stdlib.h>
      
      int main() {
          int* ptr = NULL;
          *ptr = 10;  // Cause a segmentation fault
          return 0;
      }
      

      编译时添加 -g 选项,运行后会生成 core 文件,然后在 GDB 中加载:gdb ./your_program core.

  • 10.2 无法设置断点

    • 问题描述:在某些情况下无法成功设置断点。

    • 解决方法:

      • 确保程序处于可执行状态,重新编译代码并尝试设置断点。
      • 针对特定函数,检查是否正确使用了函数原型。
      • 使用 info functions 确认函数名和地址是否匹配。
    • 示例代码:

      #include <stdio.h>
      
      void my_function() {
          printf("Hello from my_function!\n");
      }
      
      int main() {
          my_function();
          return 0;
      }
      

      在 GDB 中使用 break my_function 应该能成功设置断点。

  • 10.3 与优化代码一起使用

    • 问题描述:在优化代码的情况下,调试时可能遇到不符合源代码结构的问题。

    • 解决方法:

      • 使用 -O0 编译选项来关闭优化,以便更好地对应源代码。
      • 在需要调试的函数上使用 noinline 属性,避免被编译器内联。
    • 示例代码:

      #include <stdio.h>
      
      __attribute__((noinline)) void optimized_function() {
          // Some optimized code
          printf("Hello from optimized_function!\n");
      }
      
      int main() {
          optimized_function();
          return 0;
      }
      

      编译时使用 gcc -O0 -g your_program.c 可以关闭优化,更好地与源代码对应。

11. 参考资料

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值