Makefile 4、GDB基本调试 多进程调试

1、准备工作

1、 “-o”,它后面的参数表示要输出的目标文件(起到给文件重命名的作用)2、 “-c”,表示仅编译(Compile),不会进行链接(Make),如果没有”-c”参数,那么就表示编译并链接
3、 “-g”,利用gdb调试程序的话,编译时,必须加上-g选项

gcc –c test.c			表示只编译test.c文件,成功时输出目标文件test.o
gcc –c test.c –o test.o	与上一条命令完全相同

gcc –o test test.o		将test.o连接成可执行的二进制文件test
gcc –o test test.c		将test.c编译并连接成可执行的二进制文件test
gcc test.c –o test		与上一条命令相同

2、GDB基本调试流程和各种GDB调试参数

gdb execute	core		//启动gdb	

start					//运行gdb

list/l					//打印10行源代码,按enter后会持续这一过过程
list/l	函数名			//列出某个函数的源代码

run/r 					//一直运行,直至遇到断点/从开始连续而非单步执行程序
continue/cont/c				//继续运行,直到下一个断点/从当前位置开始连续而非单步执行程序

step/s 					//单步执行(进入函数体)(step into)
next/n					//单步执行(函数直接执行,不进入函数体)


break/b 行号				//在源代码对应行上打断点
break/b 函数名			//在源代码对应函数入口上打断点
break 文件名:行号		//在源代码对应文件名:行号上打断点
break 文件名:函数名		//在源代码对应文件名:函数入口上打断点

breaktrace/bt			//遇到断点之后,用于查看堆栈信息

print/p 变量名 			//打印变量值/地址
whatis 	变量名			//打印变量的类型
set 变量			        //设置变量的值

info/i					//查看各种信息
i b/break    			//查看当前有哪些断点
i locals				//查看所有局部变量当前的值/查看当前栈帧局部变量的值
i watch					//查看当前有哪些监视
info inferiors          //查看进程  

delete/d 断点或者监视的编号id	//删除这个断点或者监视
delete						//删除所有的断点
disable break_id            //禁用断点
enable break_id             //启用断点

finish 					//退出函数/执行到当前函数返回,然后停下来等待命令

disassemble/disas		//反汇编当前代码

clean  断点编号				//清除断点

frame .. 		//切换上下文



watch 变量名 	//设置监视这个变量,当值变换时,告诉我old value和new value

x 变量名         //显示变量的当前值(十六进制形式)
x/b 变量的地址    //显示下一个字节的值
x/10D 变量的地址  //连续显示是10个字节(十六进制形式)
x/20i $pc 		//调出汇编代码(此处的'/'不像别处表示或者的意思,而是需要输入的字符)

quit/q 			//退出gbd调试
正在gdb调试时:直接回车表示,重复执行刚刚的命令
Just a quick overview of some often-used gdb (and related) commands, including:

gcc -g    to compile a program and include debugging information
start - begin exexuting your program

list - examine your source code from within the debugger

step - execute the next line of your program
next - execute the next line of your program, but if it's a subroutine call, treat the entire subroutine as a single line

print - examine the contents of a variable
x - examine memory directly

watch, rwatch - set a watch for when a variable is written or read: return to the debugger once this happens
break - set a breakpoint: return to the debugger when this line of code is about to be executed
info watch - show info on watchpoints
info break - show info on breakpoints
delete # - delete watchpoint or breakpoint "#"

cont - continue from a breakpoint, watchpoint, step, next, etc.; basically begin running your program from where it left off

set var name=value   set the value of variable "name" to "value"

bt - show the call frames for your program
frame # - set the current frame to #. Variables you reference etc. will be those within that context.

quit - leave the debugger

如何用GDB来查看C/C++程序中函数调用栈(call stack)的相关信息,通过一些相关的命令及其用法,来循序渐进了解调用栈的各个方面,更好的驾驭程序。
我们知道,通常一个程序的运行,不外乎是A函数调用B,B函数调用C等等,等所有的调用都完成后,整个程序的运行也就ok了。在这个过程中,每当有新的函数调用,系统都会把该函数的一些信息,包括函数的参数,以及一些寄存器的值等,保存到调用栈(call stack)上。等该函数运行完成后,这些信息再从调用栈上弹出(pop)。如下所示,是一个完整的调用栈:

整体叫做调用栈(call stack),每一行叫做一桢(frame)。我们来看看桢信息的组成有哪些:
(1)桢号:调用栈中对桢的一个编号,从0开始,依次增大
(2)PC:Program counter寄存器,指向当前桢中下一条要执行的指令的地址
(3)函数名:当前桢中被调用的函数的名字
(4)参数及传入的值:当前桢中被调用的函数在调用时传入的参数及其值
(5)源码位置:当前桢执行到的源码位置,格式为 file:linenum
这里还有一点需要说明,正在执行到的桢 那一桢没有PC的地址,GDB通过这样来标示该桢是当前正在执行到的桢,因此我们通过看调用栈的信息,便可得知程序执行到哪里了。

下面介绍一些GDB命令,通过这些命令你便可以查看到函数调用的过程,上面介绍的这些信息,甚至更加详细的信息。

############ 1.查看调用栈信息:############
backtrace             # 显示程序的调用栈信息,可以用bt缩写
backtrace n	          # 显示程序的调用栈信息,只显示栈顶n桢(frame)
backtrace -n          # 显示程序的调用栈信息,只显示栈底部n桢(frame)
set backtrace limit n # 设置bt显示的最大桢层数
where, info stack     # 都是bt的别名,功能一样

############ 2.查看桢信息:############
frame n		# 查看第n桢的信息, frame可以用f缩写
frame addr	# 查看pc地址为addr的桢的相关信息
up n		# 查看当前桢上面第n桢的信息
down n		# 查看当前桢下面第n桢的信息

############ 3.查看更加详细的信息:############
info frame
info frame n
# 或者
info frame addr
#查看指定桢的详细信息,

# 关于详细信息的内容,这里有必要做一个介绍,如下所示:
# a. 当前桢的地址: 0xbffff400
# b. 当前桢PC: eip = 0×8048516
# c. 当前桢函数: bar (test.cpp:16)
# d. caller桢的PC: saved eip 0×8048535
# e. caller桢的地址: called by frame at 0xbffff420
# f. callee桢的地址: caller of frame at 0xbffff3e0
# g. 源代码所用的程序的语言(c/c++): source language c++
# h. 当前桢的参数的地址及值: Arglist at 0xbffff3f8, args: name=0×8048621 “jessie”, myname=0x804861c “jack”
# i. 当前相中局部变量的地址:Locals at 0xbffff3f8, Previous frame’s sp is 0xbffff400
# k. 当前桢中存储的寄存器: Saved registers: ebp at 0xbffff3f8, eip at 0xbffff3fc

info args 		# 查看当前桢中的参数
info locals		# 查看当前桢中的局部变量
info catch		# 查看当前桢中的异常处理器(exception handlers)

3、core文件

ulimit -c 				//查看core文件的大小
ulimit -c -size			//设置core文件的大小,size的单位是Kb
./main-core.xxx			//生成一个文件后缀式pid
gdb core.xxx			//利用core文件进行gdb调试
file ./main
bt						//查看堆栈--(即源文件哪里出错了)
找到断点					//低位到源文件的错误代码

3.1 core文件的由来和作用

当程序运行过程中出现段错误(Segmentation Fault),程序将停止运行,由操作系统把程序当前的内存状况存储在一个 core 文件中,即核心转储文件(Coredump File),它是进程运行时在突然崩溃的那一刻的一个内存快照。操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下,会把进程此刻内存、寄存器状态、运行堆栈等信息转储保存在一个文件里。该文件为二进制文件,linux使用gdb分析,windows下使用windebug分析。

使用 gdb 调试 core 文件,可以帮助我们快速定位程序出现段错误的位置。当然,可执行程序编译时应加上 -g 编译选项,生成调试信息。

当程序访问非法内存会产生段错误,段错误产生的常见情况有:
(1)访问不存在的内存地址;
(2)访问系统保护的内存地址;
(3)数组访问越界等。

3.2 控制 core 文件是否生成

  1. 使用ulimit -c命令可查看core文件的生成开关。若结果为0,则表示关闭了此功能,不会生成core文件。
  2. 使用ulimit -c filesize命令,可以设置core文件的大小(filesize的单位为KB)。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文 件的时候,gdb会提示错误。比如:ulimit -c 1024。
  3. 使用ulimit -c unlimited,则表示core文件的大小不受限制 , 这是常用的做法。

在终端通过命令ulimit -c unlimited只是临时修改,重启后无效 ,要想永久修改有三种方式:
(1)在/etc/rc.local 中增加一行 ulimit -c unlimited
(2)在/etc/profile 中增加一行 ulimit -c unlimited
(3)在/etc/security/limits.conf 最后增加如下两行记录:

@root soft core unlimited
@root hard core unlimited

3.3 core文件的名称和生成路径(了解即可)

默认名称:core.pid;pid指的是产生段错误的程序的进程号。
默认路径:产生段错误的程序的当前目录。
注意:为了方便,我们通常会自己命名和指定保存路径

  • 如果想修改core文件的名称和生成路径,相关的配置文件为
#控制产生的core文件的文件名中是否添加pid作为扩展,如果添加则文件内容为1,否则为0
/proc/sys/kernel/core_uses_pid: 

#可以设置格式化的core文件保存的位置和文件名,比如原来文件内容是core-%e
/proc/sys/kernel/core_pattern: 

#可以这样修改:
echo “/corefile/core-%e-%p-%t” > /proc/sys/kernel/core_pattern
#将会控制所产生的core文件会存放到/corefile目录下,产生的文件名为:core-命令名-pid-时间戳。

#注意:
#如果有:sudo echo x > 时的 Permission denied错误,则用下面命令
sudo sh -c "echo corefile/core-%e-%p-%t >/proc/sys/kernel/core_pattern"

%p - insert pid into filename         #添加pid
%u - insert current uid into filename #添加当前uid
%g - insert current gid into filename #添加当前gid
%s - insert signal that caused the coredump into the filename  #添加导致产生core的信号
%t - insert UNIX time that the coredump occurred into filename #添加core文件生成时的unix时间
%h - insert hostname where the coredump happened into filename #添加主机名
%e - insert coredumping executable name into filename          #添加命令名。

#一般情况下,无需修改,按照默认的方式即可。

4.gdb调试core文件的步骤(重要)

使用gdb调试core文件来查找程序中出现段错误的位置时,要注意的是可执行程序在编译的时候需要加上-g编译命令选项。
gdb调试core文件的步骤常见的有如下几种,推荐第一种。

一、

  1. gdb [execute file] [core file]
  2. 执行 bt 或 where 查看堆栈信息/段错误位置

二、

  1. gdb -c [core file]//或 gdb --core=[core file]
  2. file [exec file]
  3. 执行 bt 或 where 查看堆栈信息/段错误位置

gdb execute PID
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。
gdb会自动attach上去,并调试它。execute 应该在PATH环境变量中搜索的到。

GDB启动时,可以加上一些GDB的启动开关

GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb –help来查看
	-symbols/s <file>
		从指定文件中读取符号表信息,并把他用在可执行文件中。
	-core/c <core-file>
		调试core dump的core文件。
	-directory/d <directory>
		加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。
(gdb) help //列出所有的命令种类

help <class>//看种类中的命令  
如:help breakpoints,查看设置断点的所有命令。
也可以直接help <command>来查看命令的帮助。

在linux下,可以敲击两次TAB键来补齐命令的全称,如果有重复的,gdb会把其列出来。

只记得函数的前缀,可以这样:
(gdb) b make_<按TAB键> // GDB把所有make_开头的函数全部列出来给你查看。

4. GDB调试多进程(process)

4.1 准备工作

GDB默认调试的时候只调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。
所以在GDB下只要对follow-fork-mode与detach-on-fork这两个选项进行设置,这时候就可以对多进程进行调试。
首先我们先写一个多进程代码,作为测试用例。

// test2.cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    
    
    pid_t pid = fork();

    if (pid == 0)     //child process
    {
    
    
        printf("I am child process, my pid is %d, my father's pid is %d!\n", getpid(), getppid());
    }
    else if (pid > 0) //father process
    {
    
    
        printf("I am father process, my pid is %d!\n", getpid());
        wait(NULL);
    }
    else              //error: pid < 0 
    {
    
     
        printf("fork erro\n");
    }

    return 0;
}
#-------------------------Makefile--------------------------------
# 生成可执行文件 execute
PROJECT     = execute
#---------------------------------------------------------
# .o文件
SrcSuf      = c
SrcSuf2     = cpp
ObjSuf      = o
LibSuf      = so
LibSuf2     = a


OBJFILES    = ./test2.$(ObjSuf)

# 头文件目录
INCLUDEPATH =  -I /usr/local/include/hiredis/

# 库目录
LIBPATH     =  -L /usr/local/lib/
#---------------------------------------------------------
CC          =   g++

# 编译选项
CFlag       =   $(INCLUDEPATH) -w -g -ggdb -fshort-wchar -std=c++11

# 链接选项
LDFLAGS     +=  $(LIBPATH) -l hiredis  -l pthread
#---------------------------------------------------------
.SUFFIXES: .$(SrcSuf) .$(ObjSuf) .$(LibSuf) .$(SrcSuf2) .$(LibSuf2)

all:  $(PROJECT) clean
# 生成可执行文件
$(PROJECT):$(OBJFILES)
	@echo "creating $(PROJECT) start..."
	$(CC) $(LDFLAGS) $(OBJFILES) -o $(PROJECT) 
	@echo "creating $(PROJECT) end"
#---------------------------------------------------------
# .c 生成 .o 文件
.$(SrcSuf).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CC) $(CFlag) -c $< -o $@ 
#---------------------------------------------------------
# .cpp 生成 .o 文件
.$(SrcSuf2).$(ObjSuf):
	@echo "Compiling $(PROJECT) $<"
	$(CXX) $(CFlag) -c $< -o $@
#---------------------------------------------------------
# 删除 .o 文件
clean:
	@echo "Cleaning $(PROJECT) project files"
	@rm -f $(OBJFILES) core

#---------------------------------------------------------
# make cleanall 删除 .o 文件 execute文件
.PHONY:cleanobj
cleanobj:
	-rm -f $(OBJFILES) 
.PHONY:cleanexe
cleanexe:
	-rm -f $(PROJECT) 
.PHONY:cleanall
cleanall:cleanobj cleanexe
[root@lwh test]# make
Compiling execute test2.cpp
g++ -c -I /usr/local/include/hiredis/ -w -g -ggdb -fshort-wchar -std=c++11   test2.cpp -o test2.o
creating execute start...
g++ -L /usr/local/lib/ -l hiredis  -l pprocess ./test2.o -o execute 
creating execute end
Cleaning execute project files
[root@lwh test]# gdb execute 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 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.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/lwh/Desktop/study/test/execute...done.
(gdb) 

4.2 GDB multi-process 设置

用show指令先看一下刚才说的follow-fork-mode与detach-on-fork的默认选项是什么

(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is on.
(gdb) 

我们发现,默认的follow-fork-mode与detach-on-fork选项分别设置为parent与on,这样的意思为调试时分离进程,只调试主进程。
即默认情况下只调试主进程

4.2.1 follow-fork-mode detach-on-fork

follow-fork-mode detach-on-fork 所起到的效果
parent on 只调试主进程(gdb默认)
child on 只调试子进程
parent off 同时调试两个进程,gdb跟主进程,子进程block(阻塞)在fork位置
child off 同时调试两个进程,gdb跟子进程,主进程block(阻塞)在fork位置

利用set将follow-fork-mode与detach-on-fork的选项分别设置为child与off,就可以对子进程调试了。
set follow-fork-mode [parent|child]
set detach-on-fork [on|off]

(gdb) set follow-fork-mode child
(gdb) set detach-on-fork off
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is off.
(gdb) 

接下来分别在父子进程处用break(b)打下两个断点,然后run(r)运行代码。这时候代码会在第一个断点处停下。我们这里选择在第九行打一个断点,也就是fork之后。fork完毕后,会创建一个子进程。此时我们用info inferiors这个指令,可以查看现在程序中的多个进程,其中gdb会默认给它们编号,这个编号便于我们在调试的时候对进程进行切换。由于我们follow-fork-mode与detach-on-fork选项分别设置的是child与off,所以这个时候gdb在调试的时候跟随子进程。如果gdb跟随某个进程,那么info inferiors查看的时候,这个进程编号前会有*这表示gdb跟子进程。

4.2.2 info inferiors

command instruction
info inferiors 查询正在调试的进程
inferior 切换调试的进程
add-inferior [-copies n] [-exec executable] 添加新的调试进程 ,可以用file executable来分配给inferior可执行文件。
remove-inferiors infno, detach inferior 其他
(gdb) b 9
Breakpoint 1 at 0x4007ce: file test2.cpp, line 9.
(gdb) r
Starting program: /home/lwh/Desktop/study/test/execute 
[process debugging using libprocess_db enabled]
Using host libprocess_db library "/lib64/libprocess_db.so.1".
[New process 13969]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
[process debugging using libprocess_db enabled]
Using host libprocess_db library "/lib64/libprocess_db.so.1".
[Switching to process 0x7ffff7fdc740 (LWP 13969)]

Breakpoint 1, main () at test2.cpp:10
10          if (pid == 0)     //child process
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
(gdb) info inferiors
  Num  Description       Executable        
* 2    process 13969     /home/lwh/Desktop/study/test/execute 
  1    process 13965     /home/lwh/Desktop/study/test/execute 
(gdb) 

此时,我们可以看到在这个时候*在编号为2的进程旁边, 也就是说编号2是子进程。其实说通俗点,就是*在谁前面,现在gdb调试的就是谁。可以利用 “ inferior加编号 ” ,来切换要调试的进程。

(gdb) inferior 1
[Switching to inferior 1 [process 13965] (/home/lwh/Desktop/study/test/execute)]
[Switching to process 1 (process 13965)] 
#0  0x00007ffff6e85922 in fork () from /lib64/libc.so.6
(gdb) info inferiors
  Num  Description       Executable        
  2    process 13969     /home/lwh/Desktop/study/test/execute 
* 1    process 13965     /home/lwh/Desktop/study/test/execute 

我们发现,这个时候*到了编号为1的进程旁边,也就是说现在调试的是主进程。
能够切换进程了,那么这个时候利用我们前面gdb的基本操作就可以随心所欲的调试进程了。
其实作为程序员,这还是有点难度的。。。。一般调试都用printf大法!!!

(gdb) inferior 2
[Switching to inferior 2 [process 13969] (/home/lwh/Desktop/study/test/execute)]
[Switching to process 2 (process 0x7ffff7fdc740 (LWP 13969))] 
#0  main () at test2.cpp:10
10          if (pid == 0)     //child process
(gdb) info inferiors
  Num  Description       Executable        
* 2    process 13969     /home/lwh/Desktop/study/test/execute 
  1    process 13965     /home/lwh/Desktop/study/test/execute 
(gdb) 

4.3 完整多进程调试Demo如下

[root@lwh test]# gdb execute 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 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.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/lwh/Desktop/study/test/execute...done.
(gdb) b 9
Breakpoint 1 at 0x4007ce: file test2.cpp, line 9.
(gdb) b 12
Breakpoint 2 at 0x4007d4: file test2.cpp, line 12.
(gdb) b 16
Breakpoint 3 at 0x4007fb: file test2.cpp, line 16.
(gdb) b 24
Breakpoint 4 at 0x400827: file test2.cpp, line 24.
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004007ce in main() at test2.cpp:9
2       breakpoint     keep y   0x00000000004007d4 in main() at test2.cpp:12
3       breakpoint     keep y   0x00000000004007fb in main() at test2.cpp:16
4       breakpoint     keep y   0x0000000000400827 in main() at test2.cpp:24
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork-mode child
(gdb) set detach-on-fork off
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
(gdb) info inferiors
  Num  Description       Executable        
* 1    <null>            /home/lwh/Desktop/study/test/execute 
(gdb) r
Starting program: /home/lwh/Desktop/study/test/execute 
[process debugging using libprocess_db enabled]
Using host libprocess_db library "/lib64/libprocess_db.so.1".
[New process 14127]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
[process debugging using libprocess_db enabled]
Using host libprocess_db library "/lib64/libprocess_db.so.1".
[Switching to process 0x7ffff7fdc740 (LWP 14127)]

Breakpoint 1, main () at test2.cpp:10
10          if (pid == 0)     //child process
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
(gdb) info inferiors
  Num  Description       Executable        
* 2    process 14127     /home/lwh/Desktop/study/test/execute 
  1    process 14123     /home/lwh/Desktop/study/test/execute 
(gdb) s

Breakpoint 2, main () at test2.cpp:12
12              printf("I am child process, my pid is %d, my father's pid is %d!\n", getpid(), getppid());
(gdb) s
I am child process, my pid is 14127, my father's pid is 14123!

Breakpoint 4, main () at test2.cpp:24
24          return 0;
(gdb) inferior 1
[Switching to inferior 1 [process 14123] (/home/lwh/Desktop/study/test/execute)]
[Switching to process 1 (process 14123)] 
#0  0x00007ffff6e85922 in fork () from /lib64/libc.so.6
(gdb) s
Single stepping until exit from function fork,
which has no line number information.

Breakpoint 1, main () at test2.cpp:10
10          if (pid == 0)     //child process
(gdb) 
14          else if (pid > 0) //father process
(gdb) 

Breakpoint 3, main () at test2.cpp:16
16              printf("I am father process, my pid is %d!\n", getpid());
(gdb) 
I am father process, my pid is 14123!
17              wait(NULL);
(gdb) 

4.4 GDB中关于子进程回收问题

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    
    
    pid_t pid = fork();

    if (pid == 0) //child process
    {
    
    
        printf("I am child process, my pid is %d, my father's pid is %d!\n", getpid(), getppid());
    }
    else if (pid > 0) //father process
    {
    
    
        while (1)
        {
    
    
            int ret = waitpid(-1, NULL, WNOHANG);
            printf("I am father process, my pid is %d!\n", getpid());
            printf("Recycle child process, child process 's  pid is %d!\n", ret);
            sleep(1);
        }
    }
    else //error: pid < 0
    {
    
    
        printf("fork erro\n");
    }

    return 0;
}

在shell中正常执行,线程可以正常回收

但是在gdb中调试时,运行主进程时,是回收不到子线程的,每次运行到父进程的waitpid()返回值一直是0
而且每次重新run,就会多fork一个子进程然后info inferiors时,看到多了一个子线程

我认为是:GDB暂停了子进程,但是并没有让子进程死掉,所以导致父进程的waitpid()返回值一直是0

5. 修改源代码后,GDB不重启继续调试

使用gdb调试程序时,当发现问题想要修改源代码时
在另一个终端下,

  1. 修改源代码
  2. 重新编译生成可执行文件(一定要记住需要重新编译,否则你在gdb中单步调试或者list看到的源代码已经改过来了,但是事实上并没有编译。)。
  3. 然后在gdb中输入r即可重新运行,这里不需要从gdb中退出,之前设置的断点也可以继续使用。

6. GDB多线程调试(thread)

GDB默认支持调试多线程,跟主线程,子线程block在create thread。
查询线程:info threads
切换调试线程:thread <thread number>

6.1 准备工作

#include <stdio.h>// test2.cpp
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

void processParent();
void processChild();
void *processParentWorker(void *arg);

int main(int argc, const char *argv[])
{
    
    
    int pid = -1;
    pid = fork();

    if (pid > 0)
        processParent();
    else if (pid == 0)
        processChild();
    else
        printf("Call fork() Error!\n");

    return 0;
}

void processParent()
{
    
    
    pid_t pid = getpid();
    char prefix[] = "processParent: ";
    char tprefix[] = "thread ";
    int tstatus;
    pthread_t pt;

    printf("%s%lu %s\n", prefix, pid, "step1");

    tstatus = pthread_create(&pt, NULL, processParentWorker, NULL);

    if (tstatus != 0)
    {
    
    
        printf("processParent: Can not create new thread.");
    }
    processParentWorker(NULL);
    sleep(1);
}

void *processParentWorker(void *arg)
{
    
    
    pid_t pid = getpid();
    pthread_t tid = pthread_self();
    char prefix[] = "processParent: ";
    char tprefix[] = "thread ";

    printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");
    printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");

    return NULL;
}

void processChild()
{
    
    
    pid_t pid = getpid();
    char prefix[] = "processChild: ";
    printf("%s%lu %s\n", prefix, pid, "step1");
    printf("%s%lu %s\n", prefix, pid, "step2");
    printf("%s%lu %s\n", prefix, pid, "step3");
}

Makefile文件,同上

# 输出

[root@lwh test]# ./execute 
processParent: 17068 step1
processChild: 17069 step1
processChild: 17069 step2
processChild: 17069 step3
processParent: 17068 thread 140285920204608 step2
processParent: 17068 thread 140285920204608 step3
processParent: 17068 thread 140285901203200 step2
processParent: 17068 thread 140285901203200 step3

6.2 GDB multi-thread 设置

command instructions
info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。
thread ID 切换当前调试的线程为指定ID的线程。
break thread_test.c:123 thread all 在所有线程中相应的行上设置断点
thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command
thread apply all command 让所有被调试线程执行GDB命令command
set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

6.3 调试thread

6.3.1. 调试主进程 block(阻塞)子进程

[root@lwh test]# gdb execute    ############启动gdb###########
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 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.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/lwh/Desktop/study/test/execute...done.
(gdb) b 53    ############设置断点###########
Breakpoint 1 at 0x40094e: file test2.cpp, line 53.
(gdb) set detach-on-fork off############调试主进程 block(阻塞)子进程###########
(gdb) show detach-on-fork 
Whether gdb will detach the child of a fork is off.
(gdb) catch fork############Catchpoint: 的作用是让程序在发生某种事件的时候停止运行###########
Catchpoint 2 (fork)
(gdb) run    ############运行gdb###########
Starting program: /home/lwh/Desktop/study/test/execute 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Catchpoint 2 (forked process 17336), 0x00007ffff6e85922 in fork () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
(gdb) cont    ############继续运行,直到遇到断点###########
Continuing.
[New process 17336]    ############创建子进程:17336###########
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
processParent: 17332 step1    ############主进程号:17332###########
[New Thread 0x7ffff6dbf700 (LWP 17337)]    ############创建子线程:17337###########
[Switching to Thread 0x7ffff6dbf700 (LWP 17337)]

Breakpoint 1, processParentWorker (arg=0x0) at test2.cpp:53
53          printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
(gdb) info inferiors    ############查看当前有哪些进程###########
  Num  Description       Executable        
  2    process 17336     /home/lwh/Desktop/study/test/execute     ############子进程17336###########
* 1    process 17332     /home/lwh/Desktop/study/test/execute     ############主进程17332########### 

直接打断点,
然后run,
main中fork(),
那么主进程,子进程都有断点

catch fork() # 程序运行到此处会先暂停
然后 run()
然后切换到子进程,
然后打断点,就可以在子进程中有断点

6.3.2 进程间切换

(gdb) inferior 2    ############切换到子进程###########
[Switching to inferior 2 [process 17336] (/home/lwh/Desktop/study/test/execute)]
[Switching to thread 2 (Thread 0x7ffff7fdc740 (LWP 17336))] 
#0  0x00007ffff6e85922 in fork () from /lib64/libc.so.6
(gdb) info inferiors
  Num  Description       Executable        
* 2    process 17336     /home/lwh/Desktop/study/test/execute     ############已经切换到子进程###########
  1    process 17332     /home/lwh/Desktop/study/test/execute 
(gdb) inferior 1    ############切换到主进程###########
[Switching to inferior 1 [process 17332] (/home/lwh/Desktop/study/test/execute)]
[Switching to thread 3 (Thread 0x7ffff6dbf700 (LWP 17337))] 
#0  processParentWorker (arg=0x0) at test2.cpp:53
53          printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");

6.3.3 线程间切换

可以看到有两个子线程

线程2 是前面fork()产生的子进程
线程3 是pthread_create()产生的子线程

(gdb) step    ############单步执行(进入函数)###########
processParent: 17332 thread 140737334998784 step2
54          printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");
(gdb) info threads    ############查看有哪些线程###########
  Id   Target Id         Frame 
* 3    Thread 0x7ffff6dbf700 (LWP 17337) "execute" processParentWorker (arg=0x0) at test2.cpp:54    ############pthread_create()创建的子线程3###########
  2    Thread 0x7ffff7fdc740 (LWP 17336) "execute" 0x00007ffff6e85922 in fork ()
   from /lib64/libc.so.6    ############子进程:fork()创建得到的,被当成了子线程2###########
  1    Thread 0x7ffff7fdc740 (LWP 17332) "execute" processParentWorker (arg=0x0) at test2.cpp:53    ############主进程:当成了线程1 即主线程###########
(gdb) thread 3    ############切换到刚刚创建的子线程###########
[Switching to thread 3 (Thread 0x7ffff6dbf700 (LWP 17337))]
#0  processParentWorker (arg=0x0) at test2.cpp:54
54          printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");
(gdb) info threads
  Id   Target Id         Frame 
* 3    Thread 0x7ffff6dbf700 (LWP 17337) "execute" processParentWorker (arg=0x0) at test2.cpp:54    ############成功切切换到子线程###########
  2    Thread 0x7ffff7fdc740 (LWP 17336) "execute" 0x00007ffff6e85922 in fork ()
   from /lib64/libc.so.6
  1    Thread 0x7ffff7fdc740 (LWP 17332) "execute" processParentWorker (arg=0x0) at test2.cpp:53

6.3.4 切换到主线程或者子线程单步执行

(gdb) thread 1    ############切换到主线程,即主进程###########
[Switching to thread 1 (Thread 0x7ffff7fdc740 (LWP 17332))]
#0  processParentWorker (arg=0x0) at test2.cpp:53
53          printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");
(gdb) step        ############单步执行###########
processParent: 17332 thread 140737353992000 step2
54          printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");
(gdb)             ############单步执行###########
processParent: 17332 thread 140737334998784 step3
processParent: 17332 thread 140737353992000 step3
[Thread 0x7ffff6dbf700 (LWP 17337) exited]     ############子线程退出 ###########
56          return NULL;
(gdb)             ############单步执行###########
57      }
(gdb)             ############单步执行###########
processParent () at test2.cpp:43
43          sleep(1);
(gdb)             ############单步执行###########
44      }         ############主进程,即主线程 执行完毕 ###########
(gdb) 
main (argc=1, argv=0x7fffffffe0a8) at test2.cpp:23
23          return 0;
(gdb)             ############单步执行###########
24      }         ############main()函数中的代码,执行完毕###########
(gdb) 
0x00007ffff6de2505 in __libc_start_main () from /lib64/libc.so.6
(gdb) 
Single stepping until exit from function __libc_start_main,
which has no line number information.############main()函数中的代码,完全执行结束###########
[Inferior 1 (process 17332) exited normally]############正常退出main()函数,正常退出主进程###########
(gdb) 
The program is not being run.
  • 调试步骤可以多参考几篇博客:多调试记下就学会了
    Linux环境下的GDB调试方法 https://blog.csdn.net/horotororensu/article/details/82256832
    linux下gdb调试方法与技巧整理 https://blog.csdn.net/niyaozuozuihao/article/details/91802994

猜你喜欢

转载自blog.csdn.net/liangwenhao1108/article/details/105109478