调试的本质:确认错误的存在。
调试的原则:
1)从简单工作开始调试
(如果代码由大型循环组成,最容易出现错误的是第一次或第二次迭代引发的错误)
2)自顶向下的方法
举例:当遇到对f()的调用时,选择跟在函数调用后的语句;执行调用,然后检查依赖调用结果的变量的值,从而了解该函数是否正确运行。如果正确运行,一般就可避免单步调试。
3)使用调试工具确定段错误位置
发生段错误时,执行的第一步操作应该是在调试器中运行程序并重新产生段错误。调试器将指出发生这种错误的代码行。然后通过调用调试器的反向跟踪获得其他有用信息。
4)通过发出中断确定无限循环的位置
如果怀疑程序有无限循环,进入调试器运行程序,让该程序执行足够长的时间进入循环,然后,使用调试器的中断命令挂起该程序,并且执行反向跟踪,了解到达循环体的位置。
5)使用二分搜索
可用于迭代错误查询(二分迭代次数);用于代码段错误查询(折半删除代码查询)
GDB的 tui模式:
在GDB中使用ctrl+X+A组合键,进入tui模式。
TUI模式中,GDB窗口划分为两个子窗口
ctrl + p和ctrl + N组合键浏览以前GDB命令
使用list更改TUI的源代码子窗体显示代码的区域
基本操作
启动 GDB的方法有以下几种:
1、gdb <program>
program也就是你的执行文件,一般在当然目录下。
(有参数时,set arg XXX)
2、gdb <program> core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生
的文件。
3、gdb <program> <PID>
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gd
b会自动attach上去,并调试他。program应该在 PATH环境变量中搜索得到。
run 运行程序
break 行号 设置断点
break [可选/行号] if (条件) 组合命令
break function 在函数function()的入口指定断点
disable breakpoint-list 禁用断点(不带参数,禁用所有断点)
enable breakpoint-list 启用断点(不带参数,启用所有断点)
break filename:line_number 在源代码文件的line_number处设置断点(如果文件不再当前路径则给出全路径)
break filename.cpp:line_number 在源文件中设置断点
tbreak 行号 设置断点(首次到达时起作用)
clear 行号 删除断点(适用于已经到达断点)
delete 断点编号 删除断点、监视点及捕获点
info break 查找断点
next 可选参数 下一/几步
step 可选参数 下一/几步(会进入调用函数体)
finish(简写fin) 恢复执行,直到当前栈帧完成之后止
until(简写u) 恢复执行,退出循环体内部止
continue 恢复执行操作,直到下一个断点
print j 查看变量j的值
print *pointer@number_of_elements 打印动态数组
display 变量 (简写disp)每次有暂停时输出指定条目(适当时候可使用类型强制转换)
info breakpoints 列出所有断点命令
监视点设置
watch z 在变量z值改变时查看
watch (z>18) 通过设置表达式设置监视点
调用栈
frame 1 查看以前的帧(调用的函数的存储)
//执行frame命令时,当前正在执行的函数的帧被编号为0,其父帧编号为1,依次类推。
up 转到下一个父帧
backtrace 命令显示整个栈(当前存在的所有帧的集合)
联机帮助使用:
help breakpoints 显示关于断点的文档
break *address 在虚拟内存地址处设置断点。对于没有调试信息的部分(比如源代码不可用时,或者共享库文件)是必需的。
(缩写,b ->break,cond->condition,r->run,n->next,s->step,c->continue,p->print,bt->backtrace)
GDB启动文件:
默认名为:.gdbinit
启动文件1:放在主目录中,一般
启动文件2:放在包含该项目特有用途的特定目录中
gdb -command=z x表示要在可执行文件x上运行GDB,首先从文件z中读取命令
启动调试会话:
第一步:break main,在主函数设置断点
第二步:break function,调用函数设置断点,list列出需要看的代码
commands断点命令列表设置
commands 1 执行断点1列表
silent 静默执行
continue 命令列表中最后一个命令时continue,将继续自动执行程序
GDB的define命令创建宏
show user可以得到所有宏的列表
info locals 得到当前栈帧中所有局部变量列表
call printtree(结构体地址) 打印出结构体
x 命令,直接检查给定地址的内存
undisplay 编号 删除显示条目
set 变量=XXX 设置变量
info args检查当前函数参数
方便变量定义: set $gdb变量 = 程序变量
程序崩溃的处理:
访问禁止访问内存->导致segfault(linux,unix)/general protection fault(windows)
Unix平台上,虚拟地址布局:
箭头显示堆和栈的增长方向;
文本区域:由程序源代码中的编译器产生的机器指令组成。例如main()
数据区域:编译时分配的所有程序变量,即全局变量。
第一个数据子区域:.data,由初始化过的变量组成;
第二个数据子区域:.bss,未初始化的数据。
堆区域:当动态分配内存时(malloc()和new结构),从堆中分配,堆空间不够通过调用brk()来扩展堆。
栈区域:用来动态分配数据的空间。函数调用的数据(包括参数、局部变量和返回地址)都存储在栈上。每次进行函数调用时栈都会增长,每次返回到调用者,栈都会收缩。
cat 过程号,查看执行程序内存分布
段错误分析查找:
第一步:分析core文件
$ gdb cstring core
第二步:回溯输出
$ backtrace
第三步:把当前帧改为发生错误的帧
$ frame number(帧号)
第四步:输出我们的猜测
$ print XX (变量)
客户端、服务器调试
第一步:确认客户机是否成功连接到了服务器
print function(参数1,参数2...),去掉其中的强制转换,否则会输出错误
第二步:跟踪程序做过的所有系统调用,strace
调试多线程代码
info threads 获得每个线程运行信息(星号表示在当前进程中)
backtrace 查看当前线程在做什么
thread 线程号 切换到其他线程
backtrace 查看其他线程做什么
break Y thread X 线程X到达源代码Y行时停止
break Y thread X if i==j 线程X到达源代码Y行,并且变量i 和 j相等时停止执行
(调试多线程需要耐心和创意)
并行应用程序
并行架构:共享内存和消息传递
消息传递程序调试
$ ps ax 确定正在执行应用程序的进程(直接在命令行)
$ gdb 程序名 pid(进程号) 将GDB附加到正在运行的节点上
(gdb) backtrace 查看代码运行到何处
(gdb) frame #number 移动到问题栈帧处
无法编译或加载调试
1、“幽灵行号”问题
第一步:确认错误代码大概位置,方法:注释掉可能出问题代码;
第二步:恢复代码,重新编译,使错误再次出现;
第三步:二分搜索,反复缩小函数搜索区,直到找到出错点。
2、缺少库
$ ldd 程序 检查程序需要哪些库
第一步:创建或查找所需库文件
第二步:
解决问题方式之一:
添加库坐在搜索路径/Debug/z
%setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:/Debug/z
对于bash,执行如下命令:
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/Debug/z
$ export LD_LIBRARY_PATH
解决开源软件库找不到问题:设置环境变量
% setenv PKG_CONFIG_PATH /usr/lib/pkgconfig:/usr/local/lib/pkgconfig
调试GUI程序
$ tty 查看当前窗口终端号
如何使用splint
splint 有很多开关,可以打开或关闭某个功能(+打开,-关闭)