GDB 调试多进程或者多线程应用
2017-10-22 13:57
239 查看
CSDN | GitHub |
---|---|
GDB 调试多进程或者多线程应用 | AderXCoding/system/tools/gdb/attach_on_fork |
![](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作
因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 也欢迎大家提供一些其他好的调试工具以供收录, 鄙人在此谢谢啦
GDB是
linux系统上常用的
c/c++调试工具, 功能十分强大. 对于较为复杂的系统, 比如多进程系统, 如何使用
GDB调试呢?
考虑下面这个三进程系统 :
进程
ProcessChild是
ProcessParent的子进程,
ProcessParentThread又是
ProcParent的子线程. 如何使用
GDB调试 子进程
ProcessChild或者子线程
ProcessParentThread呢 ?
实际上,
GDB没有对多进程程序调试提供直接支持. 例如, 使用
GDB调试某个进程, 如果该进程
fork了子进程,
GDB会继续调试该进程, 子进程会不受干扰地运行下去. 如果你事先在子进程代码里设定了断点, 子进程会收到
SIGTRAP信号并终止. 那么该如何调试子进程呢? 其实我们可以利用
GDB的特点或者其他一些辅助手段来达到目的. 此外,
GDB也在较新内核上加入一些多进程调试支持.
接下来我们详细介绍几种方法, 分别是
follow-fork-mode方法,
attach子进程方法和
GDB wrapper方法.
1 follow-fork-mode方法
参考 gdb调试多进程和多线程命令1.1 follow-fork-mode方法简介
默认设置下, 在调试多进程程序时GDB只会调试主进程. 但是
GDB > V7.0支持多进程的分别以及同时调试, 换句话说,
GDB可以同时调试多个程序. 只需要设置
follow-fork-mode(默认值
parent) 和
detach-on-fork(默认值
on)即可.
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 [parent|child] set detach-on-fork [on|off]
查询正在调试的进程
info inferiors
切换调试的进程
inferior <infer number>
添加新的调试进程
add-inferior [-copies n] [-exec executable]
可以用
file executable来分配给
inferior可执行文件.
其他 :
remove-inferiors infno detach inferior
GDB默认支持调试多线程, 跟主线程, 子线程
block在
create thread
查询线程
info threads
切换调试线程
thread <thread number>
1.2 示例程序例程
#include <stdio.h> #include <pthread.h> #include <unistd.h> void processParent( ); void processChild( ); void * processParentworker(void *arg); int main(int argc, const char *argv[]) { int pid; pid = fork( ); if(pid != 0) // fork return child pid in parent process processParent( ); else // fork return 0 in child process processChild( ); return 0; } void processParent( ) { pid_t pid = getpid(); char prefix[] = "ProcessParent: "; //char tprefix[] = "thread "; int tstatus; pthread_t pt; printf("%s%d %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[] = "ProcessParentThread: "; char tprefix[] = "thread "; printf("%s%d %s%ld %s\n", prefix, pid, tprefix, tid, "step2"); printf("%s%d %s%ld %s\n", prefix, pid, tprefix, tid, "step3"); return NULL; } void processChild( ) { pid_t pid = getpid( ); char prefix[] = "ProcessChild: "; printf("%s%d %s\n", prefix, pid, "step1"); printf("%s%d %s\n", prefix, pid, "step2"); printf("%s%d %s\n", prefix, pid, "step3"); }
主程序
fork出一个子进程, 然后父进程在执行的过程中通过
pthread_create创建出一个子线程.
输出:
./test ProcessParent: 7993 step1 ProcessChild: 7994 step1 ProcessChild: 7994 step2 ProcessChild: 7994 step3 ProcessParentThread: 7993 thread 140427861296960 step2 ProcessParentThread: 7993 thread 140427861296960 step3 ProcessParentThread: 7993 thread 140427853031168 step2 ProcessParentThread: 7993 thread 140427853031168 step3
1.3 调试
1.3.1 开始调试
首先调试主进程,block子进程在
fork的位置.
set follow-fork-mode parent set detach-on-fork off
show follow-fork-mode show detach-on-fork
![](show_follow_fork_mode.png)
接着在主进程
fork的时候和三个进程(线程)的内部均设置断电. 下面分别跟踪三个进程
# 在主进程fork的时候设置断点 b 16 # 在主进程pthread_create的时候设置断点 b 36 # 在父进程执行的开始位置设置断点 b 28 # 在子进程执行的开始位置设置断点 b 48 # 在子线程执行的开始位置设置断点 b 61
1.3.2 调试父进程
首先开始调试父进程# run 运行程序 r
进程停在了
fork的位置
查看进程和线程的信息, 由于进程停在了
fork的地址, 还没有创建子进程, 因此只有主进程
由于设置了
follow-fork-mode = parent和
detach-on-fork = off. 因此在
fork之后,
gdb进程会开始调试主进程, 而子进程会阻塞在
fork之后的位置. 而我们在每个进程的执行函数路径的开始都打了断点, 那么我们继续执行, 进程将运行主程序, 走到
ProcessParent的位置.
下面我们来验证一下
#继续程序的执行 c OR continue #也可以next执行 next
可以看到程序停在了主进程
ProcessParent的开始位置.
现在我们查看进程和线程的信息, 此时父进程(
pid = 9766)创建了子进程(
pid = 10036), gdb 目前正在调试父进程
info inferiors info threads
1.3.3 调试子进程
下面我们开始跟进子进程, 之前通过info inferiors/threads来查看进程信息的时候可以看到, 进程的编号信息, 父进程(
pid = 9766)编号为
1, 子进程(
pid = 10036)编号为
2.
我们接着将
gdbattach 到子进程.
inferiors 2
然后 continue 运行程序, 程序会停止在子进程
ProcessChild的位置.
continue
1.3.4 调试子线程
现在我们继续回到主线程然后待其创建子线程之后, 跟踪子线程.由于在父进程的程序逻辑处
pthread_create处设置了断点, 将会停在
pthread_create的地方
接着往下执行, 这样子线程就会被创建, 然后我们查看进程和线程的信息
info inferiors info threads
我们可以查看到
info inferiors不能看到父进程创建的子线程, 但是
info threads可以看到.
然后开始调试子线程
thread 3
2 Attach
子进程
https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/众所周知,
GDB有附着(
attach)到正在运行的进程的功能, 即
attach <pid>命令. 因此我们可以利用该命令
attach到子进程然后进行调试.
例如我们要调试某个进程
process, 首先得到该进程的
pid
ps -ef | grep process
然后可以通过
pstree可以看到该进程下的线程信息
pstree -H pid
启动
GDB attach到该进程
现在就可以调试了. 一个新的问题是, 子进程一直在运行,
attach上去后都不知道运行到哪里了. 有没有办法解决呢?
一个办法是, 在要调试的子进程初始代码中, 比如
main函数开始处, 加入一段特殊代码, 使子进程在某个条件成立时便循环睡眠等待,
attach到进程后在该代码段后设上断点, 再把成立的条件取消, 使代码可以继续执行下去.
至于这段代码所采用的条件, 看你的偏好了. 比如我们可以检查一个指定的环境变量的值, 或者检查一个特定的文件存不存在. 以文件为例, 其形式可以如下 :
void debug_wait(char *tag_file) { while(1) { if (tag_file存在) 睡眠一段时间; else break; } }
当
attach到进程后, 在该段代码之后设上断点, 再把该文件删除就
OK了. 当然你也可以采用其他的条件或形式, 只要这个条件可以设置检测即可.
Attach进程方法还是很方便的, 它能够应付各种各样复杂的进程系统, 比如孙子/曾孙进程, 比如守护进程(
daemon process), 唯一需要的就是加入一小段代码.
3 gdb swapper
Debugging with GDB学习记录(二)GDB调试及其调试脚本的使用
使用 GDB 调试多进程程序
本作品/博文 ( AderStep-紫夜阑珊-青伶巷草 Copyright ©2013-2017 ), 由 成坚(gatieme) 创作,
采用
![](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可. 欢迎转载、使用、重新发布, 但务必保留文章署名成坚gatieme ( 包含链接: http://blog.csdn.net/gatieme ), 不得用于商业目的
基于本文修改后的作品务必以相同的许可发布. 如有任何疑问,请与我联系.