调试——GDB、日志
调试——GDB、日志
- 1. gdb常用指令
- 2. 如何生成core文件并调试?
- 3. 如何调试正在运行的程序
- 4. 调试多进程程序
- 5. 调试多线程程序
- 6. log日志
- gcc编译器可以帮我们发现语法错误,但是对业务逻辑错误却无能为力。当我们想找出逻辑错误时,就需要调试,也就是要用gdb调试
- GDB(gdb)全称“GNU symbolic debugger”,是 Linux 下常用的程序调试器。 为了能够使用 gdb 调试,需要在代码编译的时候加上-g,如
g++ -g -o test test.cpp
1. gdb常用指令
b(break) 10 :在第10行打上断点
r(run) :开始运行程序,直到遇到断点,并在断点这一行停下来(第一个运行的命令)
c(continue) :继续运行,一般先停下来,再用continue继续运行,直到遇到下一个断点
n(next) :执行当前这一行(彻底执行完,如果当前这一行是函数调用,则执行完这个函数)
p(print) name :显示变量name的值
p name++ :后面可以写一行代码,也就是一个表达式(什么都行,包括改变变量、输出都行)
set var name="仲书颉" :set var可以设置变量的值,这里设置name的值为仲书颉
set args 参数1 参数2:set args可以设置main(args,argv[])函数的参数,参数1就是argv[1]的值,参数2就是argv[2]的值-注意,当参数1内有空格时,要这样表示:set args "参数1" 参数2
s(step) :执行当前这一行-这一行不是函数时,和n(next)一样-这一行是函数时,s会进入函数的内部,执行函数内部的第一行(想进入函数里面,必须要用step)-注意,s并不能进入库函数或第三方函数,只能进入我们自己定义的函数(有函数源码)
l(list) :显示10行代码(基本不用,因为我们会另开一个窗口看源代码,一般不用list来看)
bt :查看执行到目前这一行为止,函数的调用栈
info b :显示断点的信息(有几个,哪几个)
info threads :显示已创建的线程的信息,带*号的就是当前被调试的线程
thread 2 :切换到id为2的线程来调试
2. 如何生成core文件并调试?
当程序出现段错误(core dump)时,用gdb调试core文件,可以立即定位到段错误所在行****,非常方便。但是core文件默认是不生成的,如何生成core文件呢?
在程序出现段错误后,我们需要在Linux中输入:
ulimit -a
会显示这样的结果:
我们只需要关注第一行,上面显示core文件大小为0
将core文件大小改为unlimited
ulimit -c unlimited
再出入ulimit -a后,显示:
gdb打开core文件
设置完core文件大小后,我们重新运行源程序,就会生成core文件。我们可以通过 **ls **来查看。
然后就用gdb调试即可:
gdb 可执行文件 core文件
#如:gdb book core19356
会显示:
可见,直接显示在第7行出现段错误
注:什么是段错误
**访问未分配或权限不足的内存 所产生的错误,就是段错误。有以下几种情况:
**
3. 如何调试正在运行的程序
如果我们使用gdb调试一个这个在运行的程序,那么这个程序就会立即停止运行。此时为了查看程序运行到说明地方,我们需要用 bt 命令,来查看函数的调用栈——最上面的就是正在运行的函数。
我们可用使用其他调试命令,如n、s等,可以让原本停下的程序继续运行一行。通过组合使用 n、s 和 bt 命令,可以调试正在运行的程序:
4. 调试多进程程序
注:被调试的进程会停止运行,将在调试命令下一步一步执行。
调试父进程(默认)
默认就是调试父进程,或者也可以显示指定:
set follow-fork-mode parent
此时子进程将自动执行;而父进程会阻塞(因为先前设置了断点),等待我们的调试
调试子进程
需要显示指定:
set follow-fork-mode child
此时父进程将自动执行;而子进程会阻塞(因为先前设置了断点),等待我们的调试
调试模式
在确定调试子进程还是父进程后,就可以设置调试模式了,一共有两种调试模式:
调试当前进程时,其他进程继续运行
这是默认的情况,也可以显示指定:
set detach-on-fork on
调试当前进程时,其他进程被gdb挂起,不能运行
需要显示指定:
set detach-on-fork off
如果我们是在调试父进程时采用了第二种调试模式,那么在调试父进程时,子进程不会自动运行。即使父进程调试执行完,子进程也不会运行。
查看和切换被调试的进程
在设置第二种调试模式之后(即 set detach-on-fork off),我们才能查看和切换被调试的进程。
查看被调试的进程
info inferiors
序号1的前面有*号,表示我们正在调试的进程是 序号为1的进程
**切换要调试的进程**
inferior 进程id
#如 inferior 2,表示改为调试进程2,不再调试进程1
5. 调试多线程程序
注:如果使用的是POSIX线程库的话,在编译时还要加上 -l pthread
查看进程、线程以及线程之间的关系
//shell命令(非gdb命令):
ps aux | grep 过滤条件 #查看当前运行的进程ps -aL | grep 过滤条件 #查看当前运行的线程pstree -p 主线程id #查看主线程和子线程之间的关系
线程调试的基础命令
info threads #显示已创建的线程的信息,带*号的就是当前被调试的线程thread 3 #切换到id为3的线程来调试
设置线程调试模式
**调试当前线程时,其他线程继续运行**
默认的情况,也可以显示指定:
set scheduler-locking off
**调试当前线程时,其他线程全部阻塞**
需要显示指定:
set scheduler-locking on
让指定线程执行指定命令
thread apply 线程id 命令
#如:thread apply 2 n,表示让线程3往下执行一次(即使当前调试的不是线程2,也可以执行该语句)
另,也可以让所有线程执行同一个语句:
thread apply all 命令
6. log日志
设置断点和单步调试 会严重影响线程之间的竞争状态。因为当一个线程在断点处停住了,而让另一个线程跑,就会导致并发被破坏,此时我们看到的只是一个和谐的假象。而log日志,就可以避免断点和单步的副作用。我们可以输出log日志,让程序每一步运行的时间都可以在日志文件中查到。
开源日志框架——freecplus
项目里需要同时包含_cmpublic.h、_freecplus.h、_freecplus.cpp这三个文件。第一个里全是程序用到的头文件;第二个文件包含了第一个文件,它定义了函数和类的声明;第三个文件包含了第二个文件,它是对函数和类的具体实现。_cmpublic.h_freecplus.cpp_freecplus.h
具体使用过程:
- 包含#include<_freecplus.h>(第一个文件就不用写了,因为已经包含了)
- 定义日志文件类(全局):
CLogFile logfile;
- 创建日志文件
logfile.Open("/tmp/gdbfork.log","w+");
/*
/tmp/gdbfork.log是日志文件名,可以自己定义; w+是打开文件的方式,直接写上就好
*/
- 将所有的输出语句(printf、cout等)改为输出到日志文件
logfile.Write("Hello World");
- 编译并运行项目
g++ gdbfork.cpp _freecplus.cpp -o gdbfork
./gdbfork #此时日志已经把所有的内容 输出的日志文件里面了
- 最后打开日志文件来查看即可
vi /tmp/gdbfork.log
我们也可以一边让程序运行,一边打开事先创建的日志文件,这样就可以观测** 程序运行的实时状态**。