C++调试技术-详解
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C++调试技术
CONTENTS
1 调试利器gdb 2 常见调试场景 3 调试工具介绍
1、调试利器gdb
调试意义
搞电子都知道,电路不是焊接出来的,是调试出来的。程序员也一定认同, 程序不是写出来的,是调试出来的。那么调试工具就显得尤为重要,linux作 为笔者重要的开发平台,在linux中讨论调试工具主要是为那些入门者提供一 些帮助。调试工具能让我们能够监测、控制和纠正正在运行的程序。我们在 运行一些程序的时候,可能被卡住或出现错误,或者运行过程或结果,没能 如我们预期,此时,最迫切需要明白究竟发生了什么。为了修复程序,剖析 和了解程序运行的细节, 调试工具就成为了我们的必备工具,工于善其事, 必先利其器。
info inferiors;inferior processnum
切换进程
follow-fork-mode detach-on-fork
parent
on
child
onHale Waihona Puke parentoff
child
off
2、常见调试场景
2.1. 程序段错误
2.1.1.段错误是什么 段错误是指访问的内存超出了系统给这个程序所设定的内存空间。 2.1.2.段错误产生的原因
at EncodeReadWrite/StorageService.cpp:627 627 l_s32Ret = l_pAudioQue[i]->ReadQueue(i,(void*)l_pAudioDataOut[i]); [Current thread is 1 (Thread 0x7f32e0fa5700 (LWP 7445))] (gdb) printf i Bad format string, missing '"'. (gdb) print i $1 = 6 (gdb) q
error 4 in libc-2.4.so[7f6ddc3ca000+127000] 指令的实际地址是:00007f6ddc43c650 - 7f6ddc3ca000 = 0x72650 1> 可以通过 addr2line -e libc-2.4.so 0x412b13 -f 具体定位到libc-2.4.so中的那个 函数。
1.4.4.单步命令 step count;next count;finish;until 退出循环体 1.4.5.continue命令 1.4.6.print(p)命令 1.4.7.watch命令 watch expr;rwatch expr;awatch expr;info watchpoints 1.4.8.examine命令 x/u addr addr表示一个内存地址 1.4.9.jump命令 jump linespec(linespec,linenum,filename+linenum);jump address 1.4.10.signal命令 1.4.11.set命令 set args;set env environmentVarname=value 1.4.12.call命令 call function 1.4.13.disassemble命令
1.2 gdb命令参数
backtrace(或bt) finish frame(或f)帧编号 info(或i) locals list(或l) list 行号 list 函数名 next(或n) print(或p) quit(或q) set var start step(或s)
查看各级函数调用及参数 连续运行到当前函数返回为止,然后停下来等待命令 选择栈帧 查看当前栈帧局部变量的值 列出源代码,接着上次的位置往下列,每次列10行 列出从第几行开始的源代码 列出某个函数的源代码 执行下一行语句 打印表达式的值,通过表达式可以修改变量的值或者调用函数 退出gdb调试环境 修改变量的值 开始执行程序,停在main函数第一行语句前面等待命令 执行下一行语句,如果有函数调用则进入到函数中
1.3.5.检查源码 源码可以在GDB中打印。默认情况下,'list'命令会打印10行代码。
list: 列出'linenum'行周围的源码 list: 从'function'开始列出源码 1.3.6.停止和恢复程序
break: watch: catch: disable: enable: delete: step: continue: 1.3.7.退出 GDB 用'quit'命令还从GDB中退出。
00007fffaab744e0 error 6 in process_name[400000+6e000] 可以通过 addr2line -e process_name 0x412b13 -f 具体定位到源码中的那行那个 函数。
可以通过反汇编 objdump -dgS process_name > output 然后在output文件中搜 索412b13,同样也可以定位到具体的函数。 2.1.4.2.程序崩溃在共享库 process_name[10678]: segfault at 1 ip 00007f6ddc43c650 sp 00000000461b41a8
1.5. 多线程调试
gdb:all-stop模式和no-stop模式 1.5.1. all-stop模式 在这种模式下,当你的程序在gdb由于任何原因而停止,所有的线程都会停止, 而不仅仅是当前的线程。 1.5.2. no-stop模式(网络编程常用) 当程序在gdb中停止,只有当前的线程会被停止,而其他线程将会继续运行 1.5.3. 常用命令 info threads;thread id; break filename:linenum thread all set scheduler-locking off|on\step show scheduler-locking thread apply all command 主要是我们要用能用的上的,比如no-stop模式,一般多线程调试就很有用的。
如,执行 objdump -d bin/Test > testDump 如,执行 grep -n -A 10 -B 10 "0804f57f" ./testDump 2.1.3.5.gdb 使用gdb工具来对程序进行调试
2.1. 程序段错误
2.1.4.调试段错误第二种方法 2.1.4.1.程序崩溃在程序 SMH kernel: process_name[32183]: segfault at 24 ip 0000000000412b13 sp
1.3 gdb基本使用
1.3.1.启动程序 1.3.2.给程序传参数 使用'set args'给你的程序传参数,'show args'将显示传递给程序的参数。 1.3.3.检查堆栈
bt: 打印整个堆栈的回溯 bt 打印n个帧的回溯 frame : 切换到指定的帧,并打印该帧 up : 上移'n'个帧 down : 下移'n'个帧 ( n 默认是1) 1.3.4.检查数据 使用'print'命令进行检查。例如,如果'x'是调试程序内的变量,'print x'会打印x 的值。
2> 可以通过反汇编 objdump -dgS libc-2.4.so > output 然后在output文件中搜索 72650,同样也可以定位到具体的函数。
2.1. 程序段错误
2.1.5.core调试方法 2.1.5.1.ulimit 命令设置core文件的最大值
执行命令:ulimit -c unlimited 2.1.5.2.使用gdb调试 gdb test core Core was generated by `./test'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00000000004046c0 in StorageService::VideoWriteThread (this=0x71e470, arg=0x7fffac5cbccc)
2.1. 程序段错误
2.1.3.调试段错误的一种方法 2.1.3.1.dmesg .[91046.776582] Test[26966]: segfault at 4 ip 0804f57f sp bfa0a224 error 6 in Test[80 48000+1e000] 2.1.3.2.ldd 使用ldd命令查看二进制程序的共享链接库依赖,库的名称、起始地址 运行 ldd bin/Test 2.1.3.3.nm 使用nm命令列出二进制文件中的符号表,地址、类型、名等。 如,执行 nm bin/Test | grep 0804f5 在步骤1的时候,发生段错误的指令指针地址:0804f57f。结合以上信息,可以 定位出错误的发生应该是在执行 nwGtpv1uMsgAddIeTV1 函数的时候。 2.1.3.4.objdump objdump命令是Linux下的反汇编目标文件,使用objdump生成二进制的相关信息, 并重定向到文件中
访问不存在的内存地址 int *ptr = NULL; *ptr = 0;
访问系统保护的内存地址 int *ptr = (int *)0; *ptr = 100;
访问只读的内存地址 char *ptr = "test"; strcpy(ptr, "TEST");
栈溢出 void main() { main(); }
切换进程
follow-fork-mode detach-on-fork
parent
on
child
on
parent
off
child
off
1.6.多进程调试
1.6.1. 单独调试子进程
attach child-pid;dettach
1.6.2. 使用调试器选项follow-fork-mode
set follow-fork-mode mode (parent和child),分别表示调试父进程和子进程。
2.2. 程序死锁
2.2.1. 死锁条件 (1) 互斥条件(2) 请求与保持条件(3) 不剥夺条件(4) 循环等待条件 2.2.2.使用 pstack 和 gdb 工具对死锁程序进行分析 [dyu@xilinuxbldsrv purify]$ pstack 6721 Thread 5 (Thread 0x41e37940 (LWP 6722)): #0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0 Thread 4 (Thread 0x42838940 (LWP 6723)): #0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0 连续多次查看这个进程的函数调用关系堆栈进行分析:当进程吊死时,多次使 用 pstack 查看进程的函数调用堆栈,死锁线程将一直处于等锁的状态,对比多 次的函数调用堆栈输出结果 Gdb into thread输出: 然后通过 gdb attach 到死锁进程
1.6.多进程调试
1.6.1. 单独调试子进程
attach child-pid;dettach
1.6.2. 使用调试器选项follow-fork-mode
set follow-fork-mode mode (parent和child),分别表示调试父进程和子进程。
info inferiors;inferior processnum
1.4.普通命令
1.4.1.list命令 list linenum ;list function;list ;list 1.4.2.run(r) run args
1.4.3.break(b) b linenum;b +offset/-offset;b filename:linenum;b filename:function b *address;b ;b where if condition
CONTENTS
1 调试利器gdb 2 常见调试场景 3 调试工具介绍
1、调试利器gdb
调试意义
搞电子都知道,电路不是焊接出来的,是调试出来的。程序员也一定认同, 程序不是写出来的,是调试出来的。那么调试工具就显得尤为重要,linux作 为笔者重要的开发平台,在linux中讨论调试工具主要是为那些入门者提供一 些帮助。调试工具能让我们能够监测、控制和纠正正在运行的程序。我们在 运行一些程序的时候,可能被卡住或出现错误,或者运行过程或结果,没能 如我们预期,此时,最迫切需要明白究竟发生了什么。为了修复程序,剖析 和了解程序运行的细节, 调试工具就成为了我们的必备工具,工于善其事, 必先利其器。
info inferiors;inferior processnum
切换进程
follow-fork-mode detach-on-fork
parent
on
child
onHale Waihona Puke parentoff
child
off
2、常见调试场景
2.1. 程序段错误
2.1.1.段错误是什么 段错误是指访问的内存超出了系统给这个程序所设定的内存空间。 2.1.2.段错误产生的原因
at EncodeReadWrite/StorageService.cpp:627 627 l_s32Ret = l_pAudioQue[i]->ReadQueue(i,(void*)l_pAudioDataOut[i]); [Current thread is 1 (Thread 0x7f32e0fa5700 (LWP 7445))] (gdb) printf i Bad format string, missing '"'. (gdb) print i $1 = 6 (gdb) q
error 4 in libc-2.4.so[7f6ddc3ca000+127000] 指令的实际地址是:00007f6ddc43c650 - 7f6ddc3ca000 = 0x72650 1> 可以通过 addr2line -e libc-2.4.so 0x412b13 -f 具体定位到libc-2.4.so中的那个 函数。
1.4.4.单步命令 step count;next count;finish;until 退出循环体 1.4.5.continue命令 1.4.6.print(p)命令 1.4.7.watch命令 watch expr;rwatch expr;awatch expr;info watchpoints 1.4.8.examine命令 x/u addr addr表示一个内存地址 1.4.9.jump命令 jump linespec(linespec,linenum,filename+linenum);jump address 1.4.10.signal命令 1.4.11.set命令 set args;set env environmentVarname=value 1.4.12.call命令 call function 1.4.13.disassemble命令
1.2 gdb命令参数
backtrace(或bt) finish frame(或f)帧编号 info(或i) locals list(或l) list 行号 list 函数名 next(或n) print(或p) quit(或q) set var start step(或s)
查看各级函数调用及参数 连续运行到当前函数返回为止,然后停下来等待命令 选择栈帧 查看当前栈帧局部变量的值 列出源代码,接着上次的位置往下列,每次列10行 列出从第几行开始的源代码 列出某个函数的源代码 执行下一行语句 打印表达式的值,通过表达式可以修改变量的值或者调用函数 退出gdb调试环境 修改变量的值 开始执行程序,停在main函数第一行语句前面等待命令 执行下一行语句,如果有函数调用则进入到函数中
1.3.5.检查源码 源码可以在GDB中打印。默认情况下,'list'命令会打印10行代码。
list: 列出'linenum'行周围的源码 list: 从'function'开始列出源码 1.3.6.停止和恢复程序
break: watch: catch: disable: enable: delete: step: continue: 1.3.7.退出 GDB 用'quit'命令还从GDB中退出。
00007fffaab744e0 error 6 in process_name[400000+6e000] 可以通过 addr2line -e process_name 0x412b13 -f 具体定位到源码中的那行那个 函数。
可以通过反汇编 objdump -dgS process_name > output 然后在output文件中搜 索412b13,同样也可以定位到具体的函数。 2.1.4.2.程序崩溃在共享库 process_name[10678]: segfault at 1 ip 00007f6ddc43c650 sp 00000000461b41a8
1.5. 多线程调试
gdb:all-stop模式和no-stop模式 1.5.1. all-stop模式 在这种模式下,当你的程序在gdb由于任何原因而停止,所有的线程都会停止, 而不仅仅是当前的线程。 1.5.2. no-stop模式(网络编程常用) 当程序在gdb中停止,只有当前的线程会被停止,而其他线程将会继续运行 1.5.3. 常用命令 info threads;thread id; break filename:linenum thread all set scheduler-locking off|on\step show scheduler-locking thread apply all command 主要是我们要用能用的上的,比如no-stop模式,一般多线程调试就很有用的。
如,执行 objdump -d bin/Test > testDump 如,执行 grep -n -A 10 -B 10 "0804f57f" ./testDump 2.1.3.5.gdb 使用gdb工具来对程序进行调试
2.1. 程序段错误
2.1.4.调试段错误第二种方法 2.1.4.1.程序崩溃在程序 SMH kernel: process_name[32183]: segfault at 24 ip 0000000000412b13 sp
1.3 gdb基本使用
1.3.1.启动程序 1.3.2.给程序传参数 使用'set args'给你的程序传参数,'show args'将显示传递给程序的参数。 1.3.3.检查堆栈
bt: 打印整个堆栈的回溯 bt 打印n个帧的回溯 frame : 切换到指定的帧,并打印该帧 up : 上移'n'个帧 down : 下移'n'个帧 ( n 默认是1) 1.3.4.检查数据 使用'print'命令进行检查。例如,如果'x'是调试程序内的变量,'print x'会打印x 的值。
2> 可以通过反汇编 objdump -dgS libc-2.4.so > output 然后在output文件中搜索 72650,同样也可以定位到具体的函数。
2.1. 程序段错误
2.1.5.core调试方法 2.1.5.1.ulimit 命令设置core文件的最大值
执行命令:ulimit -c unlimited 2.1.5.2.使用gdb调试 gdb test core Core was generated by `./test'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00000000004046c0 in StorageService::VideoWriteThread (this=0x71e470, arg=0x7fffac5cbccc)
2.1. 程序段错误
2.1.3.调试段错误的一种方法 2.1.3.1.dmesg .[91046.776582] Test[26966]: segfault at 4 ip 0804f57f sp bfa0a224 error 6 in Test[80 48000+1e000] 2.1.3.2.ldd 使用ldd命令查看二进制程序的共享链接库依赖,库的名称、起始地址 运行 ldd bin/Test 2.1.3.3.nm 使用nm命令列出二进制文件中的符号表,地址、类型、名等。 如,执行 nm bin/Test | grep 0804f5 在步骤1的时候,发生段错误的指令指针地址:0804f57f。结合以上信息,可以 定位出错误的发生应该是在执行 nwGtpv1uMsgAddIeTV1 函数的时候。 2.1.3.4.objdump objdump命令是Linux下的反汇编目标文件,使用objdump生成二进制的相关信息, 并重定向到文件中
访问不存在的内存地址 int *ptr = NULL; *ptr = 0;
访问系统保护的内存地址 int *ptr = (int *)0; *ptr = 100;
访问只读的内存地址 char *ptr = "test"; strcpy(ptr, "TEST");
栈溢出 void main() { main(); }
切换进程
follow-fork-mode detach-on-fork
parent
on
child
on
parent
off
child
off
1.6.多进程调试
1.6.1. 单独调试子进程
attach child-pid;dettach
1.6.2. 使用调试器选项follow-fork-mode
set follow-fork-mode mode (parent和child),分别表示调试父进程和子进程。
2.2. 程序死锁
2.2.1. 死锁条件 (1) 互斥条件(2) 请求与保持条件(3) 不剥夺条件(4) 循环等待条件 2.2.2.使用 pstack 和 gdb 工具对死锁程序进行分析 [dyu@xilinuxbldsrv purify]$ pstack 6721 Thread 5 (Thread 0x41e37940 (LWP 6722)): #0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0 Thread 4 (Thread 0x42838940 (LWP 6723)): #0 0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x0000003d1a808e1a in _L_lock_1034 () from /lib64/libpthread.so.0 连续多次查看这个进程的函数调用关系堆栈进行分析:当进程吊死时,多次使 用 pstack 查看进程的函数调用堆栈,死锁线程将一直处于等锁的状态,对比多 次的函数调用堆栈输出结果 Gdb into thread输出: 然后通过 gdb attach 到死锁进程
1.6.多进程调试
1.6.1. 单独调试子进程
attach child-pid;dettach
1.6.2. 使用调试器选项follow-fork-mode
set follow-fork-mode mode (parent和child),分别表示调试父进程和子进程。
info inferiors;inferior processnum
1.4.普通命令
1.4.1.list命令 list linenum ;list function;list ;list 1.4.2.run(r) run args
1.4.3.break(b) b linenum;b +offset/-offset;b filename:linenum;b filename:function b *address;b ;b where if condition