====程序格式(风格)的变换:indent ====
indent -kr -i8 main.c -kr选项表示K&R风格,-i8表示缩进8个空格的长度。如果没有指定-nut选项,则每8个缩进空格会自动用一个Tab代替。
====printf调试====
程序中除了一目了然的Bug之外都需要一定的调试手段来分析到底错在哪。到目前为止我们的调试手段只有一种:根据程序执行时的出错现象假设错误原因,然后在代码中适当的位置插入printf,执行程序并分析打印结果,如果结果和预期的一样,就基本上证明了自己假设的错误原因,就可以动手修正Bug了,如果结果和预期的不一样,就根据结果做进一步的假设和分析。
printf("%s, %d\n", __func__, __LINE__);
====gdb调试====
本章我们介绍一种很强大的调试工具gdb,可以完全操控程序的运行,使得程序就像你手里的玩具一样,叫它走就走,叫它停就停,并且随时可以查看程序中所有的内部状态,比如各变量的值、传给函数的参数、当前执行的代码行等。掌握了gdb的用法之后,调试手段就更加丰富了。但要注意,即使调试手段丰富了,调试的基本思想仍然是
“分析现象->假设错误原因->产生新的现象去验证假设”
这样一个循环,根据现象如何假设错误原因,以及如何设计新的现象去验证假设,这都需要非常严密的分析和思考,如果因为手里有了强大的工具就滥用而忽略了分析过程,往往会治标不治本地修正Bug,导致一个错误现象消失了但Bug仍然存在,甚至是把程序越改越错。本章通过初学者易犯的几个错误实例来讲解如何使用gdb调试程序,在每个实例后面总结一部分常用的gdb命令。
在编译时要加上-g选项,生成的可执行文件才能用gdb进行源码级调试:
$ gcc -g main.c -o main $ gdb main //这个main已是一个具有gdb功能的可执行文件 GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 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.
====> 小测试
-rwxr-xr-x 1 root root 6381 08-30 16:33 main1 //具有gdb功能的可执行文件明显要大些 -rwxr-xr-x 1 root root 4901 08-30 17:17 main2
结论:-g选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。
(gdb) help //查看命令的类别 List of classes of commands: aliases -------------- Aliases of other commands breakpoints ---------- Making program stop at certain points data ----------------- Examining data files ---------------- Specifying and examining files internals ------------ Maintenance commands obscure -------------- Obscure features running -------------- Running the program stack ---------------- Examining the stack status --------------- Status inquiries support -------------- Support facilities tracepoints ---------- Tracing of program execution without stopping the program user-defined --------- User-defined commands Type "help" followed by a class name for a list of commands in that class. Type "help all" for the list of all commands. Type "help" followed by command name for full documentation. Type "apropos word" to search for commands related to "word". Command name abbreviations are allowed if unambiguous.
==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== (gdb) help files //进一步查看某一类别中有哪些命令 Specifying and examining files. List of commands: add-shared-symbol-files -- Load the symbols from shared objects in the dynamic linker's link map 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 ...
==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== (gdb) list 1 //从第一行开始列出源代码 1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i, sum; 6 for (i = low; i <= high; i++) 7 sum = sum + i; 8 return sum; 9 } 10 (gdb) l add_range //列出该函数 1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i, sum; 6 for (i = low; i <= high; i++) 7 sum = sum + i; 8 return sum; 9 } 10
==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== $ gdb main //从main开始调试 ... (gdb) start Breakpoint 1 at 0x80483ad: file main.c, line 14. Starting program: /home/akaedu/main main () at main.c:14 14 result[0] = add_range(1, 10); //gdb停在main函数中“变量定义之后”的第一条语句处等待我们发命令 (gdb)
==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== (gdb) next //看下一条执行指令 15 result[1] = add_range(1, 100); (gdb) (直接回车) //表示重复之前的命令 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); (gdb) (直接回车) result[0]=55 result[1]=5105 17 return 0;
==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== (gdb) start //重新开始debug The program being debugged has been started already. Start it from the beginning? (y or n) y Breakpoint 2 at 0x80483ad: file main.c, line 14. Starting program: /home/akaedu/main main () at main.c:14 14 result[0] = add_range(1, 10); (gdb) step //钻入add_range函数 add_range (low=1, high=10) at main.c:6 6 for (i = low; i <= high; i++)
步骤:
1。查看栈桢:backtrace
2。选择栈桢:frame [数字]
3。局部变量:info locals
4。任意变量:print [变量名]
(gdb) backtrace //查看函数调用的栈帧 #0 add_range (low=1, high=10) at main.c:6 //add_range的 栈帧编号为0; 可见当前的add_range函数是被main函数调用的,main传进来的参数是low=1, high=10。 #1 0x080483c1 in main () at main.c:14 //main 函数的 栈帧编号为1。 (gdb) info locals //查看add_range函数局部变量的值: i = 0 sum = 0 (gdb) frame 1 //选择1号栈帧 #1 0x080483c1 in main () at main.c:14 14 result[0] = add_range(1, 10); (gdb) info locals result = {0, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, ... -1208623680} (gdb) print sum //打印出任意存在的变量名。 $1 = 3 //这里的$1表示gdb保存着这些中间结果,$后面的编号会自动增长,在命令中可以用$1、$2、$3等编号代替相应的值。
==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== (gdb) finish //用finish命令让程序一直运行到从当前函数返回为止 Run till exit from #0 add_range (low=1, high=10) at main.c:6 0x080483c1 in main () at main.c:14 14 result[0] = add_range(1, 10); Value returned is $2 = 55
调试之Boss级招数:
1。set var sum=?
2。print var=?
(gdb) set var sum=0 //可以立即改一变量的值,可以继续调试,看是否还有其他的BUG。 (gdb) finish Run till exit from #0 add_range (low=1, high=100) at main.c:6 0x080483db in main () at main.c:15 15 result[1] = add_range(1, 100); Value returned is $4 = 5050 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); (gdb) (直接回车) result[0]=55 result[1]=5050 17 return 0; (gdb) print result[2]=33 //后面需要跟的是表达式 $5 = 33 //返回值就是改变的变量值 (gdb) print printf("result[2]=%d\n", result[2]) //printf是个有点特殊的函数,返回值是打印出的字符数。 result[2]=33 $6 = 13
3。反汇编、寄存器查看:
(gdb) start ... main () at main.c:14 14 foo(2, 3);
(gdb) s foo (a=2, b=3) at main.c:9 9 return bar(a, b);
(gdb) s bar (c=2, d=3) at main.c:3 3 int e = c + d;
(gdb) disassemble //反汇编当前函数或者指定的函数 Dump of assembler code for function bar: 0x08048394 <bar+0>: push %ebp 0x08048395 <bar+1>: mov %esp,%ebp 0x08048397 <bar+3>: sub $0x10,%esp 0x0804839a <bar+6>: mov 0xc(%ebp),%edx 0x0804839d <bar+9>: mov 0x8(%ebp),%eax 0x080483a0 <bar+12>: add %edx,%eax 0x080483a2 <bar+14>: mov %eax,-0x4(%ebp) 0x080483a5 <bar+17>: mov -0x4(%ebp),%eax 0x080483a8 <bar+20>: leave 0x080483a9 <bar+21>: ret End of assembler dump.
(gdb) si //一条指令一条指令地单步调试 0x0804839d 3 int e = c + d; (gdb) si 0x080483a0 3 int e = c + d; (gdb) si 0x080483a2 3 int e = c + d; (gdb) si 4 return e; (gdb) si 5 }
(gdb) bt #0 bar (c=2, d=3) at main.c:5 #1 0x080483c2 in foo (a=2, b=3) at main.c:9 #2 0x080483e9 in main () at main.c:14
(gdb) info registers //显示所有寄存器的当前值 eax 0x5 5 ecx 0xbff1c440 -1074674624 edx 0x3 3 ebx 0xb7fe6ff4 -1208061964 esp 0xbff1c3f4 0xbff1c3f4 ebp 0xbff1c404 0xbff1c404 esi 0x8048410 134513680 edi 0x80482e0 134513376 eip 0x80483a8 0x80483a8 <bar+20> eflags 0x200206 [ PF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
(gdb) x/20 $esp 0xbff1c3f4: 0x00000000 0xbff1c6f7 0xb7efbdae 0x00000005 0xbff1c404: 0xbff1c414 0x080483c2 0x00000002 0x00000003 0xbff1c414: 0xbff1c428 0x080483e9 0x00000002 0x00000003 0xbff1c424: 0xbff1c440 0xbff1c498 0xb7ea3685 0x08048410 0xbff1c434: 0x080482e0 0xbff1c498 0xb7ea3685 0x00000001 (gdb)
====显示变量display====
(gdb) display sum //每次都显示 sum 的值 1: sum = -1208103488 (gdb) n 9 scanf("%s", input); 1: sum = 0 (gdb) 123 10 for (i = 0; input[i] != '\0'; i++) 1: sum = 0 undisplay 1 //跟踪号是1,取消它的跟踪显示
====断点====
****设置断点****
(gdb) break 9 //在第九行设置断点,参数也可以是函数名 Breakpoint 2 at 0x80483bc: file main.c, line 9. 和continue命令(简写为c)配合使用,连续运行而非单步运行,程序到达断点会自动停下来。 (gdb) continue Continuing. input=123 //程序阶段性输出 Breakpoint 2, main () at main.c:9 //断点信息 9 scanf("%s", input); //断点后的函数 1: sum = 123 //显示的变量
****查看断点****
(gdb) info breakpoints Num Type Disp Enb Address What 2 breakpoint keep y 0x080483c3 in main at main.c:9 //断点2 位置 breakpoint already hit 1 time 3 breakpoint keep y 0x08048411 in main at main.c:12 //断点3 位置
****删除断点****
(gdb) delete breakpoints 2 //删掉断点 (gdb) i breakpoints Num Type Disp Enb Address What 3 breakpoint keep y 0x08048411 in main at main.c:12
****暂停断点****
(gdb) disable breakpoints 3 //暂停断点 (gdb) info breakpoints Num Type Disp Enb Address What 3 breakpoint keep n(状态) 0x08048411 in main at main.c:12 (gdb) enable 3 //启用断点 (gdb) info breakpoints Num Type Disp Enb Address What 3 breakpoint keep y(状态) 0x08048411 in main at main.c:12 (gdb) delete breakpoints //删除断点 Delete all breakpoints? (y or n) y (gdb) info breakpoints No breakpoints or watchpoints.
****条件断点****
(gdb) break 9 if sum != 0 //if [条件表达式] Breakpoint 5 at 0x80483c3: file main.c, line 9. (gdb) info breakpoints Num Type Disp Enb Address What 5 breakpoint keep y 0x080483c3 in main at main.c:9 stop only if sum != 0 (gdb) run //重新从程序开头连续运行 The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/akaedu/main 123 input=123 Breakpoint 5, main () at main.c:9 9 scanf("%s", input); 1: sum = 123
====观察点====
**** 内存状态 ****
(gdb) x/13b input //x命令打印指定存储单元的内容。7b是打印格式,b表示每个字节一组,7表示打印7组2: i = 3 2: i = 3 1: sum = 123 0xbfffe727: 49 50 51 0 0 123 0 0 0xbfffe72f: 0 3 0 0 0
上述观察数据可以看出变量在内存中的方式。
**** 设置状态点 ****
(gdb) watch input[5] //观察点是当程序访问某个存储单元时中断。 Hardware watchpoint 2: input[5] (gdb) info watchpoints Num Type Disp Enb Address What 2 hw watchpoint keep y input[5]
**** 到达状态点 ****
(gdb) continue Continuing. Hardware watchpoint 2: input[5] Old value = 0 '\0' New value = 1 '\001' 0x0804840c in main () at main.c:11 11 for (i = 0; input[i] != '\0'; i++)
====段错误====
$ gdb main ... (gdb) r Starting program: /home/akaedu/main 123 Program received signal SIGSEGV, Segmentation fault. 0xb7e1404b in _IO_vfscanf () from /lib/tls/i686/cmov/libc.so.6 //段错误出现的原始调用 (gdb) bt #0 0xb7e1404b in _IO_vfscanf () from /lib/tls/i686/cmov/libc.so.6 #1 0xb7e1dd2b in scanf () from /lib/tls/i686/cmov/libc.so.6 //通过以上调用发现程序中的出错函数位置
http://hi.baidu.com/kebey2004/item/fa6392d333717f2838f6f7fe