[实践OK]Linux 下用gdb单步调试多进程方法,附带学习下调多进程下的epoll片段,也就是用双gdb调试多进程的crack代码,因为代码里有自动拉起的功能,在gdb下用follow-fork-mode=child方式调被拉起bt不了,以及直接gdb后attach子进程。

jackxiang 2015-1-7 15:22 | |
前置:Linux多进程和多线程的一次gdb调试实例:https://typecodes.com/cseries/multilprocessthreadgdb.html ,Linux C/C++开发中gdb进行多进程和多线程的调试一直比较麻烦,在CSDN上看到高科的一篇文章《gdb调试多进程和多线程命令》比较有启发,这里就自己重新整理并做了一个GDB多进程/线程的调试实践。
这个表很重要,两个参数一块用:


也可将前面的set添加到~/.gdbinit来打开non-stop模式:


查看相关命令:
set follow-fork-mode child
set detach-on-fork off
info inferiors                    #####显示正在调试的进程
inferior 2
show detach-on-fork #Whether gdb will detach the child of a fork is off.
show follow-fork-mode #Debugger response to a program call of fork or vfork is "child"
info b #查看断点
进程树:
pstree -pul
PS查看自己程序名运行的多进程:
ps -f -C multepoolser

break  if
要想设置一个条件断点,可以利用break if命令,如下所示:
[cpp] view plain copy
(gdb) break line-or-function if expr  
(gdb) break 46 if testsize==100  
clean number
清除原文件中某一代码行上的所有断点
注:number 为原文件的某个代码行的行号


断点的管理
1. 显示当前gdb的断点信息: info break
2. delete 删除指定的某个断点: delete breakpoint

单步执行
continue      继续运行程序直到下一个断点(类似于VS里的F5)
next            逐过程步进,不会进入子函数(类似VS里的F10)  #这个不进入函数里。
setp            逐语句步进,会进入子函数(类似VS里的F11) 的确能进入到函数里面。
until           运行至当前语句块结束  #没弄明白,试了试没找到规律
finish          运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)
next/n 不进入的单步执行 step 进入的单步执行
finish
如果已经进入了某函数,而想退出该函数返回到它的调用函数中,可使用命令finish
call name
调用和执行一个函数
(gdb) call gen_and_sork( 1234,1,0 )  
(gdb) call printf(“abcd”)  
$1=4  
finish 结束执行当前函数,显示其返回值(如果有的话)

原文件的搜索 search text
该命令可显示在当前文件中包含text串的下一行。
reverse-search text
该命令可以显示包含text 的前一行。

set variable 给变量赋值
signal 将一个信号发送到正在运行的进程
watch 在程序中设置一个监测点(即数据断点)
whatis 显示变量或函数类型

来自GDB调试精粹:http://blog.csdn.net/lwbeyond/article/details/7839225

GDB打印所有字符串:@ http://blog.csdn.net/shuizhizhiyin/article/details/53227913

如果出现No symbol "header_line" in current context.,你得切换到那个进程里面去,能切换进去的前提是你提前打了断点:
(gdb) p header_line
No symbol "header_line" in current context.
可:info thread 切换到对应的进程,再打印也就有了。


break FileName.cpp:LinuNum thread all:所有线程都在文件FileName.cpp的第LineNum行有断点。
thread apply ID1 ID2 IDN command:多个线程执行gdb命令command。
thread apply all command:所有线程都执行command命令。
set scheduler-locking off|on|step:在调式某一个线程时,其他线程是否执行。off,不锁定任何线程,默认值。on,锁定其他线程,只有当前线程执行。step,在step(单步)时,只有被调试线程运行。
set non-stop on/off:当调式一个线程时,其他线程是否运行。
set pagination on/off:在使用backtrace时,在分页时是否停止。
set target-async on/ff:同步和异步。同步,gdb在输出提示符之前等待程序报告一些线程已经终止的信息。而异步的则是直接返回。
来自:http://blog.csdn.net/helloktt/article/details/73252943
http://www.tolxs.com/?p=302
(gdb)  show detach-on-fork
Whether gdb will detach the child of a fork is off.
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child"

出现:
(gdb) n
Cannot execute this command while the selected thread is running.

(gdb) info threads
  Id   Target Id         Frame
  3    Thread 0x7fffece72700 (LWP 27660) "multepoolser" handleEpollRdMessage (threadNum=0) at multepoolser.c:262
* 2    Thread 0x7ffff7fde840 (LWP 21243) "multepoolser" (running)
  1    Thread 0x7ffff7fde840 (LWP 21239) "multepoolser" 0x00007ffff668774c in fork () from /lib64/libc.so.6

root     21239 16286  0 17:22 pts/1    00:00:00 [httpmut: master process] master process
apache   21243 21239  0 17:22 pts/1    00:00:00 [httpmut: worker process] worker process

目前在进程:2,前面有*号,我得到3上面去,怎么弄?
thread 3
(gdb) thread 3
[Switching to thread 3 (Thread 0x7fffece72700 (LWP 27660))]
#0  handleEpollRdMessage (threadNum=0) at multepoolser.c:262
262         printf("handleEpollRdMessage function 's threadNum=%d\n",threadNum);
(gdb) n
handleEpollRdMessage function 's threadNum=0
263         int recvlen=-1,epollRdQueRet=-1;//返回
info threads #此时就在3了吧
(gdb) info threads
  Id   Target Id         Frame
* 3    Thread 0x7fffece72700 (LWP 27660) "multepoolser" handleEpollRdMessage (threadNum=0) at multepoolser.c:263
  2    Thread 0x7ffff7fde840 (LWP 21243) "multepoolser" (running)
  1    Thread 0x7ffff7fde840 (LWP 21239) "multepoolser" 0x00007ffff668774c in fork () from /lib64/libc.so.6

卡这儿了,也就是主进程网络那边没有来Epoll句柄,getepollRdFromQue队列一直是空的,怎么办?
(gdb) p epollRdQueRet
$2 = -1
(gdb) n
455         }
(gdb)
302             epollRdQueRet = getepollRdFromQue(&sockRdQue[threadNum],&epollRd);//只要有生产的fd进来并该线程获取到了,就立即去epoll读队列里抢,一线程一队列,不用锁。
(gdb)
303             if(epollRdQueRet == -1) continue;//没有获取到epollRd,继续获取。
(gdb)
455         }

此时如果有的进程需要运行,怎么办?GDB命令:thread apply 3 1 continue #让线程3继续运行,注意我顾意把主线程1也continue。
thread apply 3 1 2 continue


需要实践,链接如下:
http://blog.sina.com.cn/s/blog_53fab15a0101g88r.html
https://www.cnblogs.com/frankbadpot/archive/2010/06/23/1762916.html
http://blog.csdn.net/wangyin159/article/details/47169267

=============================================================================

背景:当你在做一些多进程调试时,会出现一个问题,那就是子进程往往不太好调试,怎么办?如果子进程一直在while里飞快的运行着怎么办,这篇文章就是教会你进行双gdb调试,以实现了子进程的调试,这儿在没有调试时,其子进程是不断的打印,当然最好在while里加一个sleep,但第二个gdb进入后则会存在第一个gdb的打印变慢了,此时,可以在第二个gdb里打印相关变量,达到对子进程的调试目的,特别注意的是,第二个gdb一定要在那个子进程的行里break(这儿是31行)进行调试,而不是放在其它地方,否则是没法调试到子进程的。第三个是:进程中断点.然后用c(这里要用continue,因为attach的进程已经在运行了,不能用run)。最后,子进程死了父亲进程拉起,crack这块可能是return退出了,自己打印是crack,导致并不是真正的crack了,也就不是coredump了,特别注意这个问题。
     当你在程序中使用fork(),如果用gdb来调试.不管是你在子进程是否设置断点.你都只能在父进程单步调试,而没办法进入到子进程当中进行单步调试.因为gdb的所有处理(查看堆栈,内存,变量值)都是针对当前进程空间.

那么是否就没办法调试多进程程序的子进程代码呢?办法还是有的,一般的标准方法是再打开一个gdb用attach功能来调试子进程gdb attach 功能是不执行被调试程序,而是把gdb“挂”到一个已经运行的进程之上来进行调试,这挂载的动作称为attach.当然也包括挂载子进程。

注意两点:
0.当主gdb进来后进行断点后,子进程尽管断点了,但依然运行(为第二个gdb作暂停准备之用),而当第二个gdb对子进程进行attach后,其主gdb的子进程的打印动作也就暂停(为方便调试,可以在进程启动后,设定sleep一段时间,如30s,其实只要第二个gdb一attach到子进程,子进程就柱塞了。),此时子gdb再进行设置断点,按c运行,按n作next就行。
1.子进程的主gdb点和第二个gdb的break点要同一行?不必要,其主gdb只是起到在第二个gdb单独调该进程时起到暂停其主的子进程的作用罢了。
2.在子进程的gdb里,要用c来继续,(这里要用continue,因为attach的进程已经在运行了,不能用run!(也就是:主进程用r(先运行着,主进程不退出。),子进程用c,继续在原来那个停下来的点继续运行到后,再用n命令,即next一步一步查问题,n(next):显示的是即将运行这一行,也就是还没有运行。)
3.gdb调试epoll时遇到的Interrupted system call:
signal(SIGALRM,timer_handle);
最近写了个多线程数据处理的程序,其中用到sem_wait()在信号量为0的时候挂起程序. 程序全速执行的时候没有任何问题,但gdb单步调试的时候,因执行sem_wait()函数而挂起的线程经常出现Interrupted system call,不能正常执行.
    原因在于,gdb单步调试的时候会在断点处插入一条中断指令,当程序执行到该断点处的时候会发送一个SIGTRAP信号,程序转去执行中断相应,进而gdb让程序停下来进行调试. 对于sem_wait\wait\read等会阻塞的函数在调试时,如果阻塞,都可能会收到调试器发送的信号,而返回非0值.
    为了解决这个问题需要在代码中忽略由于接收调试信号而产生的"错误"返回:


到一篇文章《gdb常用命令及使用gdb调试多进程多线程程序》:http://www.cnblogs.com/33debug/p/7043437.html ,代码编译无法通过,代码调整如下,gdb_pthread.c:



关于GDB无法调试epoll的疑问?
这种交互式的网络程序最好不用gdb来调?
调试网络通讯程序最好别用GDB,epoll你可以用strace,gdb是用来调试 C数据结构逻辑之类的。
打日志是我的首选调试方法,如果程序崩溃就看core 文件,如果死锁就 attach上去,其他的错误我从不用GDB
不是epoll和gdb的问题,而是在gdb下epoll_wait 信号处理和正常运行有差异造成的,应该是这个。
摘自:http://bbs.chinaunix.net/thread-1551201-1-1.html

多进程调试如下所述即可,如下,特别是新的linux内核版本能支持到多进程了,相当好用:
在2.5.60版Linux内核及以后,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。
follow-fork-mode的用法为:
set follow-fork-mode [parent|child]
parent: fork之后继续调试父进程,子进程不受影响。
child: fork之后调试子进程,父进程不受影响。
因此如果需要调试子进程,在启动gdb后:
(gdb) set follow-fork-mode child

这块可以研究一下,新的内核及新的gdb有新功能很方便使用:
set follow-fork-mode child
//set detach-on-fork on //gdb控制父子进程,不让调试了: Can't attach LWP 24731: Operation not permitted,得打开:ON。
b 146 //main里的子进程行
b 192  //子进程调用的函数原型行
r //执行到子进程断点处
n //子进程单步执行

实践心得:一般情况下多线程的时候,由于是同时运行的,最好设置 set scheduler-locking on 这样的话,只调试当前线程 。
off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。
vi bp.list

gdb.sh

(一)这块在调试epoll时,在n单步执行卡后(c执行前先断点),得通过浏览器模拟socket请求才能过得去,否则会一直卡那儿,如下:


这块如果continue后,中间因为断点就一次是没法下次进入的,需要引入gdb判断(if {expression} else end条件判断语句, if 后面所带的表达式为一般的GDB表达式:http://blog.csdn.net/horkychen/article/details/9372039)及观察点,才能更加深入了解,下面是试图没有断点运行起来后实现ctrl+C退出,想继续n的尝试:
(二)再就是对c后一直卡那儿,可以按ctrl+C退出( SIGINT   :来自键盘的中断信号 ( ctrl + c ) .),SIGTERM:kill 命令发出 的信号.,继续n执行:
Program received signal SIGINT, Interrupt.
0x0000003c9a2d3fd3 in __epoll_wait_nocancel () from /lib64/libc.so.6
(gdb) n
Single stepping until exit from function __epoll_wait_nocancel,
which has no line number information.

这块不知怎么回事....需要进一步学习了解。

Process(listenFd); //运行至工作子进程的函数,;b 192进行单步执行。

直接gdb后attach子进程:
#ps -f -C multepoolser
UID        PID  PPID  C STIME TTY          TIME CMD
root      2150 19720  0 17:09 pts/1    00:00:00 [httpmut: master process] master process
apache    2151  2150 93 17:09 pts/1    00:00:01 [httpmut: worker process] worker process
2151就是子进程,怎么办?
gdb   #运行gdb
attach 2151
现在就可以调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪里了。有没有办法解决呢?

一个办法是,在要调试的子进程初始代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码可以继续执行下去。

至于这段代码所采用的条件,看你的偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。以文件为例,其形式可以如下:

1
2
3
4
5
6
7
8
9
10
void debug_wait(char *tag_file)
{
    while(1)
    {
        if (tag_file存在)
            睡眠一段时间;
        else
            break;
    }
}
当attach到进程后,在该段代码之后设上断点,再把该文件删除就OK了。当然你也可以采用其他的条件或形式,只要这个条件可以设置/检测即可。

Attach进程方法还是很方便的,它能够应付各种各样复杂的进程系统,比如孙子/曾孙进程,比如守护进程(daemon process),唯一需要的就是加入一小段代码。

https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/

学习参考:
http://blog.csdn.net/nyist327/article/details/40040011
http://blog.sina.cn/dpool/blog/s/blog_55d572ca0100v8e8.html

修改如下:


主gdb:
(gdb) b 167
Breakpoint 1 at 0x4025a1: file multipepollserver.cpp, line 167.
(gdb) b 147
Breakpoint 2 at 0x40250f: file multipepollserver.cpp, line 147.
(gdb) r
Starting program: /home/xiangdong/multepoolserver/multipepollserver
[Thread debugging using libthread_db enabled]
Detaching after fork from child process 27539.
[New Thread 0x2afb66031230 (LWP 27536)]

Breakpoint 1, main (argc=1, argv=0x7fff550003c8, envp=0x7fff550003d8) at multipepollserver.cpp:168
168    

辅助gdb:
(gdb) b 147
Breakpoint 1 at 0x40250f: file multipepollserver.cpp, line 147.
(gdb) c
Continuing.
n

以上代码来自,也就是上面的程序调试的整个代码放在:http://jackxiang.com/post/6937/  ,不完善进一步学习中。

来自:
    http://blog.sina.com.cn/s/blog_5cec1e1d0100guwf.html
    http://blog.csdn.net/qiaoliang328/article/details/7404032
    http://bbs.csdn.net/topics/380004392
首先我们看一个如下简单的多进程程序。



这个程序很简单,就是子进程在无限循环打印屏幕,而父进程在用wait等待.

编译 gcc test_fork.c -o test_fork -g

1.双gdb调试

首先用常规方法gdb test_fork.c 调试程序,分别在31行,41行设断点,然后用run执行程序,可以看到gdb在41行父进程的断点停下来.但是子进程在自行执行,无法在31断点停下.

[root@localhost src]# gdb test_fork
GNU gdb Fedora (6.8-27.el5)
(gdb) b 31
Breakpoint 1 at 0x8048541: file test_fork.c, line 31.
(gdb) b 41
Breakpoint 2 at 0x804858e: file test_fork.c, line 41.
(gdb) r
Starting program: /home/hxy/src/test_fork
process id 3959
Detaching after fork from child process 3962.
val=100
parent process id 3959

Breakpoint 2, main () at test_fork.c:41
41 printf("parent [%d]\n",i);
(gdb) val=100
child process id 3962,parent id 3959
val=200
child [0]
child [1]
child [2]
child [3]
child [4]
child [5]
child [6]
child [7]
nchild [8]
child [9]

parent [0]
39 for(i=0 ; i < 5 ; i++)
(gdb) child [10]
n


这时用gdb attach功能来调试子进程,首先用 ps -aux | grep test_fork找出子进程号.

然后用 gdb test_fork <进程号>挂入已经知进程.这时就可以看到在子进程的断点可以停下来,而且父进程的gdb窗口里,子进程输出停下并受子进程的gdb控制,这里你可以用常规调试手段来看程序了.(如看memory,watch,stack等)

操作步骤,进入gdb首先用b 31 设置子进程中断点.然后用c(这里要用continue,因为attach的进程已经在运行了,不能用run)

然后可以看到断点在生效了.至此可以常规调试方法即可

[root@localhost src]# ps -aux | grep test_fork
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.7/FAQ
root 3957 0.0 0.2 11012 4824 pts/7 S+ 13:18 0:00 gdb test_fork
root 3959 0.0 0.0 1516 328 pts/7 T 13:19 0:00 /home/hxy/src/test_fork
root 3962 0.0 0.0 1516 280 pts/7 S 13:19 0:00 /home/hxy/src/test_fork
root 3985 0.0 0.0 5020 672 pts/9 R+ 13:19 0:00 grep test_fork
[root@localhost src]# gdb test_fork 3962
GNU gdb Fedora (6.8-27.el5)
Copyright (C) 2008 Free Software Foundation, Inc.
(gdb) b 31
Breakpoint 1 at 0x8048541: file test_fork.c, line 31.
(gdb) c
Continuing.

Breakpoint 1, main () at test_fork.c:31
31 printf("child [%d]\n",i++);
(gdb) n
32 sleep(1);
(gdb) n
33 }
(gdb)

2.图形界面kdbg的调试

命令行界面gdb还是太麻烦了,一般我们还是采用界面前端来进行调试程序,一般用KDE自带KDbg最为方便.

2.1 首先用一个Kdbg打开程序

在图形界面设置断点,然后运行,可以看到主程序的断点已经进入并停下来了.
Linux 下用gdb单步调试多进程方法. - youdianluan - youdianluan的博客

2.2 再打开一个kdbg,并且打开test_fork,设置好子进程的断点,选择主菜单的Execution->Attach,这时会出现如下界面,从进程列表选择子进程或用ps查到子进程ID直接输入即可
Linux 下用gdb单步调试多进程方法. - youdianluan - youdianluan的博客
2.3 此时两个kdbg在同时调一个程序不同进程,注意所有标准输入输出都发生在调试主进程的kdbg的终端窗口里
Linux 下用gdb单步调试多进程方法. - youdianluan - youdianluan的博客

转自:http://blog.csdn.net/youdianluanluan/article/details/7006995


最后,查看断点,清除掉断点后,可继续c一下,第一个的gdb就又开始运行起来了:
(gdb) info break
Num Type           Disp Enb Address            What
2   breakpoint     keep y   0x00000000004006d4 in main at test_fork.c:35
      l
33      while(1)
34      {
35      printf("child [%d]\n",i++);
36      sleep(1);
37      }
38      }

(gdb) c

每次想要对子进程的断点清掉后,都得重新:gdb test_fork 3962 才行。
c : 是用来一直运行到断点。
n: 单步过行,一步步运行,当指向某行时它并没有运行,而是n后才会运行。

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/7727/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2018-2-28 16:40
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]