调试信息
Linux下的c++程序开发,makefile,cmake等编译工具最终都是调用gcc这一编译工具组。
一般要调试某个程序,为了清晰地看到调试的每一行代码,调用的堆栈信息,变量名和函数名等信息,需要调试程序含有调试符号信息。
那么判断一个可执行程序是否带有调试信息?
gdb hello_world查看显示结果。
这时候在显示信息的最后一行,如果带有调试信息,则显示
Reading symbols from /root/testclient/hello_server…done.
如果不带调试信息,则显示
Reading symbols from /root/testclient/hello_server2…(no debugging symbols found)…done.
当然,对于一个包含了调试信息的程序,我们也可以通过strip移除调试信息。
#strip hello_wrold
实际上生成调试程序时,同时建议关闭编译器的优化选项。因为不优化,可以让符号文件显示的调试变量等能和源代码完全对应起来。
编译器优化选项:O0-O4 5个级别,O0表示不优化,O1-O4表示优化级别越来越高。
启动调试
gdb调试程序分三种模式:
- gdb filename :直接调试目标程序
- gdb attach pid : 附加进程
- gdb filename corename :调试core文件
调试进程
使用场景:当我们一个程序正在运行,我们如何在不重启这个程序的情况下对这个程序进行调试呢(假如运行一段时间之后发现这个程序不再接受连接了)
gdb attack pid
当我们attach上目标进程后,调试器会暂停下来,这时候我们可以用continue继续运行程序。当然,也可以设置相应的断点。
调试结束后,我们可以用detach命令将进程与调试器分离。最后再quit退出调试器即可。
调试core文件
当我们的程序core dump的时候,我们可以用gdb帮助调试这个问题。我们的linux系统在程序发生core dump的时候,有产生core文件的机制。
- 通过ulimit -c 我们可以看到当前系统是否开启了core文件生成功能。
ulimit -c unlimited // core文件的大小不受限制。
ulimit -c 100 //设置文件大小最大为100K
- 当前设置只在本会话有效。
永久生效的方法:把ulimit -c unlimited加入/etc/profile
生成的core文件的默认命名方式是core.pid。
gdb filename corename
这时候我们可以看到core dump的位置。我们可以通过bt命令查看奔溃时的调用堆栈,进一步分析找到奔溃的原因。
但是,当程序core dump的时候,我们往往找不到PID,这时候我们就需要自定义core文件的名称了。
自定义core文件的名称
- 控制core文件是否添加pid作为扩展
/proc/sys/kernel/core_uses_pid 文件内容为1开启,0关闭。
- 格式化控制core文件的保存位置和文件名。
/proc/sys/kernel/core_pattern
eg: /root/testcore/core-%e-%p-%t
参数说明:
%p:添加pid
%e:添加程序名
%t:添加时间戳
当然还有其他的参数格式,这里只列举了常用的三个。
GDB调试命令
run
在gdb开始调试之后,并没有直接运行启动这个程序。r启动这个程序。
当然了,假如这个程序已经启动过,则再次输入r命令,则是重启这个程序。
continue
当程序触发断点,或者我们使用ctrl+c让程序中断下来,想让程序继续执行,c继续运行。
break
添加断点,添加断点的方式有三种:
b func-name:在函数入口处添加
b lineNo:在当前文件对应的行号处添加
b filename:lineNo:在对应文件行号处添加
backtrace 与 frame
bt命令用来查看当前程序调用的堆栈。f可以用来切换堆栈的层数
#0 anetListen (err=0x746bb0 <server+560> “”, s=10, sa=0x7e34e0, len=16, backlog=511) at anet.c:452
#1 0x0000000000426e35 in _anetTcpServer (err=err@entry=0x746bb0 <server+560> “”, port=port@entry=6379, bindaddr=bindaddr@entry=0x0, af=af@entry=10, backlog=511)
at anet.c:487
#2 0x000000000042793d in anetTcp6Server (err=err@entry=0x746bb0 <server+560> “”, port=port@entry=6379, bindaddr=bindaddr@entry=0x0, backlog=511)
at anet.c:510
#3 0x000000000042b0bf in listenToPort (port=6379, fds=fds@entry=0x746ae4 <server+356>, count=count@entry=0x746b24 <server+420>) at server.c:1728
#4 0x000000000042fa77 in initServer () at server.c:1852
#5 0x0000000000423803 in main (argc=1, argv=0x7fffffffe648) at server.c:3862
这里可以看到调用顺序是
server.c的main函数在3862调用了initServer
依次类推…
info break ,enable,disable,delete
info break:查看断点信息
enable + 断点编号:重新起用断点
disable + 断点编号:禁用断点
delete + 断点编号:删除某个断点,不加断点编号则直接删除所有断点
list
显示当前断点的前后10行代码,继续输入 list 指令会以递增行号的形式继续显示剩下的代码行。
list 指令还可以往前和往后显示代码,命令分别是“list + (加号)”和“list - (减号)”。
print
查看变量的值,也可以修改当前内存中的变量值。
- 查看变量
p this 显示当前对象的地址
p a + b + c 打印这三个变量的结果值
p &port 输出 port 的地址值
p func() 输出该变量的执行结果
p strerror(errno) 将这个错误码对应的文字信息打印出来
- 更改变量
p port=6400
ptype
顾名思义,其含义是“print type”,输出一个变量的类型.
info
流程控制命令:next、step、until、finish、return 和 jump
- next : 执行一条命令,即遇到函数调用直接跳过
- step : 遇到函数调用,进入函数内部。当函数调用的参数也是函数调用的时候,step实际上依次进入函数内部。
- until +行号 :快速执行完中间的代码。
- finish :直接执行完当前函数并回到上一层调用处
- return :结束执行当前函数,还可以指定该函数的返回值。
这里的return不同于finish,return不会继续执行下面的内容。而是直接返回。
- jump:程序执行流跳转到指定位置执行。被jump略过的部分则不执行。执行完跳转处的代码会继续执行
disassemble
查看某段代码的汇编指令去排查问题。
set args 和 show args
gdb启动之后,在run之前,使用“set args 参数内容”来设置命令行参数。
(gdb) set args "999 xx" "hu jj"
如果想清除掉已经设置好的命令行参数,使用 set args 不加任何参数即可
tbreak
临时断点,就是一旦该断点触发一次后就会自动删除
watch
监视一个变量或者一段内存,当这个变量或者该内存处的值发生变化时,GDB 就会中断下来。被监视的某个变量或者某个内存地址会产生一个 watch point(观察点)。
display
命令监视的变量或者内存地址,每次程序中断下来都会自动输出这些变量或内存的值。