使用 GDB 调试程序 打开 C 程序的调试功能
编译程序, 我们可以使用 gcc -S main.c 这样来打开调试并且这样也能见到二进制的汇编. 编译程序时使用 -g 更加方便不但有二进制汇编,还有代码本身 (注, 这时我们想看二进制结构,可以使用 objdump 加 -dS 参数). 测试样例代码 #include int add_range(int low, int high) { int i, sum; for (i = low; i <= high; i++) { sum = sum + i; } return sum; } int main(void) { int result[100]; result[0] = add_range(1, 10); result[1] = add_range(1, 100); printf("result[0]=%d\n result[1]=%d\n", result[0], result[1]); return 0; } 常用 GDB 的调试命令 计算机在分配变量的时候, 局部变量是存储在栈中, 全局变量是存储在全局量段。进入函数时, 变量会放到栈顶,退出时会从栈顶拿掉。它是从存储器顶部开始向下增长的。 启动 GDB 的时候, 一定要保证加了 -g 来增加代码到二进制文件中来. 代码是在指定路径,所以代码文件不存在并不行。 启动调试: # gcc 11.c -g -o 11 # gdb main GNU gdb (GDB) Red Hat Enterprise Linux (7.2-64.el6_5.2) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: ... Reading symbols from /root/fukai/c/11...done. 帮助和显示 gdb 提供了一个类似 shell 一样的命令行环境, 可以在这个提示符下输入 help 命令来查看可用的命令类别。我们可以进一步查看看 help 显示的内容类别下有哪些类别, 比如 files 只需要输入 help files 就可以了。 (gdb) help files Specifying and examining files. List of commands: add-symbol-file -- Load symbols from FILE add-symbol-file-from-memory -- Load the symbols out of memory from a dynamically loaded object file cd -- Set working directory to DIR for debugger and program being debugged core-file -- Use FILE as core dump for examining memory and registers directory -- Add directory DIR to beginning of search path for source files edit -- Edit specified file or function exec-file -- Use FILE as program for getting contents of pure memory file -- Use FILE as program to be debugged forward-search -- Search for regular expression (see regex(3)) from last line listed generate-core-file -- Save a core file with the current state of the debugged process list -- List specified function or line load -- Dynamically load FILE into the running program nosharedlibrary -- Unload all shared object library symbols path -- Add directory DIR(s) to beginning of search path for object files pwd -- Print working directory 进入后,就可以使用 list 1 来列出源代码。一次只列出 10 行。如果要在显示第 11 行开始, 需要在次输入 list, 也可以什么都不输入直接回车。 列出指定函数代码: l 函数名 GDB 基本命令 进入后, 输入 start 来启动程序. 启动后我们可以使用 next (简写为 n) 来控制代码一条一条执行, 注意 n 并不会进入函数内部. 这时如果执行到一个指定的函数上, 想进入,可以使用 s ,然后就会进入这个函数的内部。 (gdb) start The program being debugged has been started already. Start it from the beginning? (y or n) y Temporary breakpoint 2 at 0x4004f8: file 11.c, line 13. Starting program: /root/fukai/c/11 Temporary breakpoint 2, main () at 11.c:13 13 result[0] = add_range(1, 10); (gdb) s add_range (low=1, high=10) at 11.c:5 5 for (i = low; i <= high; i++) { 进入函数后状态的查看 上面讲到使用 step 进入当前的函数后, 下面是怎么样来查看看函数内部的内容。 命令 backtrace (简写 bt)命令可以查看函数调用的栈帧 (gdb) bt #0 add_range (low=1, high=10) at 11.c:5 #1 0x0000000000400507 in main () at 11.c:13 这时可以见到 main 函数的栈帧是 1 , add_range 的栈帧是 0,我们可以使用 info 命令来查看函数内部的局部变量的值。 (gdb) i locals i = 0 sum = 0 这时想查看 main 函数内的局部变量也可以需要使用 fram 1(简写 f) 来转到 1 号栈帧。接下来基本上都是使用 p 来显示变量的内容 (gdb) p sum $5 = 6 (gdb) finish Run till exit from #0 add_range (low=1, high=10) at 11.c:6 0x0000000000400507 in main () at 11.c:13 13 result[0] = add_range(1, 10); Value returned is $6 = 55
如果要退出当前函数,就象上面一样,执行 finish 就可以退出当前函数来执行其它的了。 在使用过程中想修改变量的话,可以象下面这样操作 (gdb) set var sum=0 (gdb) p sum $8 = 0 (gdb) finish Run till exit from #0 add_range (low=1, high=100) at 11.c:6 0x000000000040051c in main () at 11.c:14 14 result[1] = add_range(1, 100); Value returned is $9 = 5050 gdb 基本命令 brcktrace 或 bt 查看各级函数调用和参数 finish 连续运行到当前函数结束,然后等待下一个命令。 frame 或 f 帧编号 选择指定栈帧 info 或 locals 查看当前栈帧局部变量 list 或 l 列出上次位置以下的 10 行源码 list 行号 列出第几行开始的 10 行源码 list 函数名 列出指定函数源码 next 或 n 执行下一语句 print 或 p 打印表达式, 通过表达式可以修改变量值或者调用函数 quit 或 q 退出 gdb 环境 set var 修改变量值 start 开始执行到 main 函数第一行语句 step 或 s 执行下一语句, 如果有函数就进入 高级调试 如果我们想每次查看指定的变量的变化, 我们不需要每次都使用 printf 来打印, 我们只需要使用 display 就行。 (gdb) display sum 1: sum = 1634469985 (gdb) n 9 sum = sum*10 + input[1] - Ɔ' 1: sum = 1634469985 (gdb) n 8 for (i = 0; input[i] != '\0' i++) 1: sum = -835169374 这样每就到了 sum 都会打印出来给我们显示.不想显示时可以使用 undisplay 来取消跟踪显示。 每次这样显示很累, 我们可以直接使用 break 命令来在指定行上打外断点,如
(gdb) l 3 int main(void){ 4 int sum = 0, i = 0; 5 char input[5]; 6 while (1) { 7 scanf("%s", input); 8 for (i = 0; input[i] != '\0' i++) 9 sum = sum*10 + input[1] - Ɔ' 10 printf("input=%d\n", sum); 11 } 12 return 0; (gdb) b 9 Breakpoint 2 at 0x40056c: file 12.c, line 9. (gdb) c Continuing. Breakpoint 2, main () at 12.c:9 9 sum = sum*10 + input[1] - Ɔ' 1: sum = -1254259506 break 可以指定行,也可以指定函数名。这个时候,在内部需要使用 continue 命令来连续运行,而非单步运行, 程序达到断点就会自动停止下来。这样可以自动停止在下一次循环内。 一次调试可以设置多个断点, 可以使用 info 来查看设置好的断点 (gdb) i break Num Type Disp Enb Address What 2 breakpoint keep y 0x000000000040056c in main at 12.c:9 breakpoint already hit 6 times
可以使用 delete break 2 来删除指定的断点。 (gdb) disable b 3 (gdb) i b Num Type Disp Enb Address What 3 breakpoint keep n 0x000000000040059c in main at 12.c:10 4 breakpoint keep y 0x0000000000400563 in main at 12.c:8 在这通过 disable b 3 来禁用指定 Num 的断点,如上所显示在 Enb 下禁用的会变成 n. 这时也可以使用 enable 3 来启用断点。 条件断点。 我们可以根据一些条件来选择指定条件启用断点, 如我们要 sum 不为 0 时显示。我们现在使用 run 命令重新启动程序,然后我们可以这样。 (gdb)break 9 if sum != 0 Breakpoint 4, main () at 12.c:8 本节 gdb 命令 break 或 b 行号 在指定行设置断点 break 函数名 在指定函数开头设置断点 break … if … 设置断点条件 cotinue 或 c 从当前位置开始连续运行程序 delete breakpoints 断点号 删除断点 display 变量名 跟踪查看变量,每次到这都会显示它的值 disable breakpoints 断点号 禁用断点 enable 断点号 启用断点 info 或 i breakpoints 查看当前设置了哪些断点 run 或 r 从头开始连接运行程序 undisplay 显示跟踪号 取消跟踪显示 除了断点外, 我们有时可能想知道, 当程序访问有些存储单元时中断了, 我们不知道是哪个地方修改了这个存储单元, 这时我们可以使用观察点。 (gdb) watch input[5] Hardware watchpoint 2: input[5] (gdb) c Continuing. 123 input=123 12312 Hardware watchpoint 2: input[5] Old value = 127 '\177' New value = 0 '\000' 0x00007ffff7aa1e09 in _IO_vfscanf_internal () from /lib64/libc.so.6 这样有修改就会显示。注意上面, 上面这个地方出现段错误, gdb 每次会在段错误时会停止下来。这时我们可以看看到底哪行引起的段错误, gdb 上面显示在 _IO_vfscanf 的函数上出错, 我们使用 bt 命令看看是行引起的 Old value = 127 '\177' New value = 0 '\000' 0x00007ffff7aa1e09 in _IO_vfscanf_internal () from /lib64/libc.so.6 (gdb) bt #0 0x00007ffff7aa1e09 in _IO_vfscanf_internal () from /lib64/libc.so.6 #1 0x00007ffff7aad44d in __isoc99_scanf () from /lib64/libc.so.6 #2 0x000000000040056a in main () at 13.c:10 上面显示在 13.c 的第 10 行出错。 有时我们调试, 查出段错误的时候,并没有详细内容, 段错误发生在 } 括号结束的位置, 这可是常见问题,如果一个函数局部变量发生访问越界, 有可能并不立刻产生段错误, 而是在函数返回的时候生产段错误。 如果对于数组,我们想显示整组的信息可以象下如下操作。 (gdb) x/7b input 0x7fffffffe5c0: 49 50 51 0 -1 127 0
这我们可以使用 x 命令来打印指定的存储单元的内容, 7b 是打印格式, b 表示每个字节一组, 7 表示打印 7 组。 从 input 数组的第一个字节开始连续打印 7 组。 watch 设置观察点 info 或 i watchpoints 查看所有的观察点 x 从指定位置的存储单元开始打印内容,给内容会部当成字节来看,并不区分是哪个变量。 反汇编: disassemble 默认这个是反汇编当前的函数, 如果要查看其它的,需要指定函数名或地址. 显示寄存器信息: info registers 注意在 gdb 中表示寄存器需要前面加 $ .如 p $esp 可以打印这个寄存器的值 查看当前程序栈的内容: x/10x $sp
|