调试多线程 & 查死锁的bug & gcore命令 & gdb对多线程的调试 & gcore & pstack & 调试常用命令

时间:2020-11-28 07:03:13

gdb

thread apply all bt

如果你发现有那么几个栈停在 pthread_wait 或者类似调用上,大致就可以得出结论:就是它们几个儿女情长,耽误了整个进程。

注意gdb的版本要高于7.0,之前使用过gdb6.3调试多线程是不行的。

调试多线程 & 查死锁的bug & gcore命令 & gdb对多线程的调试 & gcore & pstack & 调试常用命令

从上图可以看出两个线程都阻塞在wait上,而且还给出了在哪一行代码中,很容易就定位到产生死锁的位置。

有时候线程太多,想写到文件里,可以:

(gdb)set logging file <文件名>

(gdb)set logging on

(gdb)thread apply all bt

(gdb)set logging off

(gdb)quit

继续gdb + 多线程

http://www.cnblogs.com/aixingfou/archive/2011/07/28/2119875.html

先介绍一下GDB多线程调试的基本命令。

info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。

thread ID 切换当前调试的线程为指定ID的线程。

break thread_test.c:123 thread all 在所有线程中相应的行上设置断点(watch也可以指定thread)

thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。

thread apply all command 让所有被调试线程执行GDB命令command。

set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试 程序执行呢?

(注:step是进入内部,next是外部过一下)

通过这个命令就可以实现这个需求。off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

gdb对于多线程程序的调试有如下的支持:

  • 线程产生通知:在产生新的线程时, gdb会给出提示信息

(gdb) r
Starting program: /root/thread 
[New Thread 1073951360 (LWP 12900)] 
[New Thread 1082342592 (LWP 12907)]---以下三个为新产生的线程
[New Thread 1090731072 (LWP 12908)]
[New Thread 1099119552 (LWP 12909)]

  • 查看线程:使用可以查看运行的线程。info threads

(gdb) info threads
   Thread 1099119552 (LWP )   0xffffe002 in ?? ()
  3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
  2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
*  Thread 1073951360 (LWP 12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)

注意,行首的蓝色文字为gdb分配的线程号,对线程进行切换时,使用该该号码,而不是上文标出的绿色数字。

另外,行首的红色星号标识了当前活动的线程

  • 切换线程:使用 thread THREADNUMBER 进行切换,THREADNUMBER 为上文提到的线程号。下例显示将活动线程从 1 切换至 4。

(gdb) info threads
   4 Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
   3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
   2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb) thread 4
[Switching to thread 4 (Thread 1099119552 (LWP 12940))]#0   0xffffe002 in ?? ()
(gdb) info threads
* 4 Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
   3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
   2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
   1 Thread 1073951360 (LWP 12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)

以上即为使用gdb提供的对多线程进行调试的一些基本命令。另外,gdb也提供对线程的断点设置以及对指定或所有线程发布命令的命令。

初次接触gdb下多线程的调试,往往会忽视gdb中活动线程的概念。一般来讲,在使用gdb调试的时候,只有一个线程为活动线程,如果希望得到其他的线程的输出结果,必须使用thread命令切换至指定的线程,才能对该线程进行调试或观察输出结果。

多线程如果dump,多为段错误,一般都涉及内存非法读写。可以这样处理,使用下面的命令打开系统开关,让其可以在死掉的时候生成core文件。 
ulimit -c unlimited

这样的话死掉的时候就可以在当前目录看到core.pid(pid为进程号)的文件。接着使用gdb:
gdb ./bin ./core.pid 
进去后,使用bt查看死掉时栈的情况,在使用frame命令。frame命令是切换到bt栈的各个层级。

gcore命令(好像也可以gdb attach到进程上,quit就是detach,不影响原进程)

遇到某个进程挺住,可以用gcore命令:

gcore pid (调试进程的pid号)

注意:不会退出的,亲自实验,有的文章说主动出Core并退出,是错的!程序正常运行。

$ gcore 

$ gdb -c core. ./errno_demo
GNU gdb Red Hat Linux (6.3.0.0-.96rh)
Copyright Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu"...Using host libthread_db library "/lib64/tls/libthread_db.so.1". Failed to read a valid object file image from memory.
Core was generated by `/home/work/data/code/errno_demo/errno_demo'.
Reading symbols from /usr/lib64/libstdc++.so....done.
Loaded symbols for /usr/lib64/libstdc++.so.
Reading symbols from /lib64/tls/libm.so....done.
Loaded symbols for /lib64/tls/libm.so.
Reading symbols from /lib64/libgcc_s.so....done.
Loaded symbols for /lib64/libgcc_s.so.
Reading symbols from /lib64/tls/libc.so....done.
Loaded symbols for /lib64/tls/libc.so.
Reading symbols from /lib64/ld-linux-x86-.so....done.
Loaded symbols for /lib64/ld-linux-x86-.so.
# 0x0000003f0b08f072 in __nanosleep_nocancel () from /lib64/tls/libc.so.
(gdb) bt
# 0x0000003f0b08f072 in __nanosleep_nocancel () from /lib64/tls/libc.so.
# 0x0000003f0b08ef10 in sleep () from /lib64/tls/libc.so.
# 0x0000000000400603 in main () at errno_demo.cpp:
(gdb) info thread
* process 0x0000003f0b08f072 in __nanosleep_nocancel () from /lib64/tls/libc.so.
(gdb)

而这个时候,原来sleep的程序正常在sleep的。在sleep再加一些语句,也是会正常运行的。

手动生成core文件,在使用pstack(linux下好像不好使)查看堆栈的情况。如果都看不出来,就仔细查看代码,看看是不是在if,return,break,continue这种语句操作是忘记解锁,还有嵌套锁的问题,都需要分析清楚了。

gdb attach

gdb 调试运行的进程

gdb -p <进程号>

或者:

gdb

attach <进程号>

gdb里面quit之后,

Detaching from program: /home/work/data/code/errno_demo/errno_demo, process 33386

原进程照样正常运行。

调试常用命令

gdb

gcore

pstack

$ pstack
# 0x0000003f0b08f072 in __nanosleep_nocancel () from /lib64/tls/libc.so.
# 0x0000003f0b08ef10 in sleep () from /lib64/tls/libc.so.
# 0x0000000000400603 in main ()

还能跟踪到线程级别:

pstack显示每个进程的栈跟踪: pstack  Thread  (Thread  (LWP )): # 0x000000302afc63dc in epoll_wait () from /lib64/tls/libc.so. # 0x00000000006f0730 in ub::EPollEx::poll () # 0x00000000006f172a in ub::NetReactor::callback () # 0x00000000006fbbbb in ub::UBTask::CALLBACK () # 0x000000302b80610a in start_thread () from /lib64/tls/libpthread.so. # 0x000000302afc6003 in clone () from /lib64/tls/libc.so. # 0x0000000000000000 in ?? () Thread  (Thread  (LWP )): # 0x000000302afc63dc in epoll_wait () from /lib64/tls/libc.so. # 0x00000000006f0730 in ub::EPollEx::poll () # 0x00000000006f172a in ub::NetReactor::callback () # 0x00000000006fbbbb in ub::UBTask::CALLBACK () # 0x000000302b80610a in start_thread () from /lib64/tls/libpthread.so. # 0x000000302afc6003 in clone () from /lib64/tls/libc.so. # 0x0000000000000000 in ?? ()

strace

# strace -o server.strace -Ttt -p 16739

常用的参数, -T是时间,tt是usecs

sleep函数里面的系统调用是这样的:

$ strace -Ttt -p
Process attached - interrupt to quit
::45.894785 restart_syscall(0x7fff19ca31f0, 0x7fff19ca31f0, , 0x8, 0x7fff19ca3050

过了一会儿,出来更多结果:

$ strace -Ttt -p
Process attached - interrupt to quit
::45.894785 restart_syscall(0x7fff19ca31f0, 0x7fff19ca31f0, , 0x8, 0x7fff19ca3050) = <39.413698>
::25.308576 write(, "bye\n", ) = <0.000014>
::25.308665 munmap(0x7f55029a8000, ) = <0.000019>
::25.308707 exit_group() = ?
Process detached

更多gdb选项

http://blog.csdn.net/hejinjing_tom_com/article/details/9472041

默认编译的时候,调试过程是看不见宏的值的。编译时候需要给选项。-g3 (没实验过)

tbreak 临时设置一次

break,tbreak可以根据行号、函数、条件生成断点。tbreak设置方法与break相同,只不过tbreak只在断点停一次,过后会自动将断点删除

附录:gdb  常用调试命令
-------------------------------------------------
(gdb) l :(字母l)列出源码
(gdb) b n :在第n行处设置断点
(gdb) b func:在函数func()的入口处设置断点

(gdb) 条件断点:条件可以是任何合法的c 表达式。 例如 b n if val1==val2

当已经设置了断点,可以用condition 命令对断点号添加条件, 例: condition 2 val1==val2 , 注意,没有if 单词

当对变量的改变更感兴趣时,可以用watch 命令

(gdb) info break: 查看断点信息  (更多断点类,见下)
(gdb) r:运行程序
(gdb) n:单步执行
(gdb) s:单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
(gdb) c:继续运行
(gdb) p 变量 :打印变量的值     也能够修改变量的值(用 = 赋值) // 打印寄存器值。 p $eax

(gdb) x /nfu <addr> 显示内存  // n为个数,f 为格式,u为每单元长度

命令:x /3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示输出三个单位,u表示按十六进制显示。

(gdb) bt:查看函数堆栈
(gdb) finish:退出函数

(gdb) display <var> 每次中断或单步都显示你关心的变量

(gdb)undisplay <编号>
(gdb) shell 命令行:执行shell命令行
(gdb) set args 参数:指定运行时的参数
(gdb) show args:查看设置好的参数
(gdb)info program: 来查看程序的是否在运行,进程号,被暂停的原因。 // 打印寄存器数组, info reg,  简写 i reg; info threads(注意有s)
(gdb)clear 行号n:清除第n行的断点
(gdb)delete 断点号n:删除第n个断点
(gdb)disable 断点号n:暂停第n个断点
(gdb)enable 断点号n:开启第n个断点

----------------------------------------
gdb 条件调试
----------------------------------------
“break ... if cond" 命令
例: b fun() if i==20

watch var    // 此时监测var的变化

发出 'info locals' 命令时,gdb 会打印出当前帧中的局部变量,缺省情况下,这个帧中的函数就是被中断的函数(0 号帧)。

要获取有关帧的进一步信息,如它的地址和程序语言,可以使用命令 'info frame'。

backtrace <n>, bt <n> n是一个正整数,表示只打印栈顶上n层的栈信息。
 backtrace <-n> ,bt <-n> -n表一个负整数,表示只打印栈底下n层的栈信息。

up <n>         表示向栈的上面移动n层,可以不打n,表示向上移动一层。
down <n> 表示向栈的下面移动n层,可以不打n,表示向下移动一层。

info frame,info f

(gdb) info f
       Stack level 0, frame at 0xbffff5d4:
         eip = 0x804845d in func (tst.c:6); saved eip 0x8048524
         called by frame at 0xbffff60c
         source language c.
         Arglist at 0xbffff5d4, args: n=250
         Locals at 0xbffff5d4, Previous frame's sp is 0x0
         Saved registers:
         ebp at 0xbffff5d4, eip at 0xbffff5d8              
     info args      打印出当前函数的参数名及其值。     
     info locals     打印出当前函数中所有局部变量及其值。        
     info catch      打印出当前的函数中的异常处理信息。

远程调试

没用过,是不是嵌入式用的?

gdb 内存断点设置。
rwatch, watch, awatch 分别代表读,写,读写内存断点,用的是硬件断点。

与调试控制相关的命令

  • continue    继续运行程序直到下一个断点(类似于VS里的F5)
  • next        逐过程步进,不会进入子函数(类似VS里的F10)
  • step        逐语句步进,会进入子函数(类似VS里的F11)
  • until        运行至当前语句块结束
  • finish  运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)

s : step in

fin: step out, 跳出函数

until 行号。 可用于跳出循环,加快了调试速度。

----------------------------------------
gdb 调试跟踪多进程程序
----------------------------------------
gdb只能跟踪一个进程(默认是跟踪父进程),而不能同时跟踪多个进程,
可以设置gdb跟踪父进程还是子进程, 命令如下:
set follow-fork-mode parent 跟踪父进程, 默认
set follow-fork-mode child  跟踪子进程

gdb断点

http://blog.csdn.net/yangzhongxuan/article/details/6897968

gdb断点分类:

以设置断点的命令分类:

breakpoint

可以根据行号、函数、条件生成断点。

watchpoint

监测变量或者表达式的值发生变化时产生断点。

catchpoint

监测信号的产生。例如c++的throw,或者加载库的时候。

gdb中的变量从1开始标号,不同的断点采用变量标号同一管理,可以 用enable、disable等命令管理,同时支持断点范围的操作,比如有些命令接受断点范围作为参数。

例如:disable 5-8

1、break及break变种详解:

相关命令有break,tbreak,rbreak,hbreak,thbreak,后两种是基于硬件的,先不介绍。

>>break 与 tbeak

break,tbreak可以根据行号、函数、条件生成断点。tbreak设置方法与break相同,只不过tbreak只在断点停一次,过后会自动将断点删除,break需要手动控制断点的删除和使能。

break 可带如下参数:

linenum                 本地行号,即list命令可见的行号

filename:linenum  制定个文件的行号

function                函数,可以是自定义函数也可是库函数,如open

filename:function  制定文件中的函数

condtion                条件

*address      地址,可是函数,变量的地址,此地址可以通过info add命令得到。

例如:

break 10

break test.c:10

break main

break test.c:main

break system

break open

如果想在指定的地址设置断点,比如在main函数的地址出设断点。

可用info add main 获得main的地址如0x80484624,然后用break *0x80484624.

条件断点就是在如上述指定断点的同时指定进入断点的条件。(要同时满足)

例如:(假如有int 类型变量 index)

break 10 if index == 3

tbreak 12 if index == 5

>>rbreak

rbreak 可以跟一个规则表达式。rbreak + 表达式的用法与grep + 表达式相似。即在所有与表达式匹配的函数入口都设置断点。

rbreak list_* 即在所有以 list_ 为开头字符的函数地方都设置断点。

rbreak ^list_ 功能与上同。

>>查看断点信息

info break [break num ]

info break 可列出所有断点信息,info break 后也可设置要查看的break num如:

info break 1 列出断点号是1的断点信息

watch

watch [-l|-location] expr [thread threadnum] [mask maskvalue]

-l 与 mask没有仔细研究,thread threadnum 是在多线程的程序中限定只有被线程号是threadnum的线程修改值后进入断点。

经常用到的如下命令:

watch <expr>

为表达式(变量)expr设置一个观察点。变量量表达式值有变化时,马上停住程序。

表达式可以是一个变量

例如:watch value_a

表达式可以是一个地址:

例如:watch *(int *)0x12345678 可以检测4个字节的内存是否变化。

表达式可以是一个复杂的语句表达式:

例如:watch a*b + c/d

watch 在有些操作系统支持硬件观测点,硬件观测点的运行速度比软件观测点的快。如果系统支持硬件观测的话,当设置观测点是会打印如下信息:

Hardware watchpoint num: expr

watch两个变种 rwatch,awatch,这两个命令只支持硬件观测点如果系统不支持硬件观测点会答应出不支持这两个命令的信息

rwatch <expr>

当表达式(变量)expr被读时,停住程序。

awatch <expr>

当表达式(变量)的值被读或被写时,停住程序。

info watchpoints

列出当前所设置了的所有观察点。

watch 所设置的断点也可以用控制断点的命令来控制。如 disable、enable、delete等。

为停止点设定运行命令

为断点号bnum指写一个命令列表。当程序被该断点停住时,gdb会依次运行命令列表中的命令。

例如:

break foo if x>0

commands

printf "x is %d/n",x

continue

end

gdb 断点(三)catch

在调试的时候通常用catchpoints来捕获事件,如c++的异常等。

捕获点的设置通过catch与tcatch两个命令。
    tcatch所设置的断点停止一次后自动删除,设置断点的方法与catch相同。

用法:catch event
    这些event事件如下:
    throw
        The throwing of a C++ exception.
    catch
        The catching of a C++ exception.
    exception
    
    exception unhandled
        An exception that was raised but is not handled by the program.
    assert
        Ada 语言 assert断言失败时,断点被踩到。
    exec
        调用exec时断点被踩到。
    syscall
    syscall [name | number] ...
        通过系统函数的名称和系统号,来设置捕获点,当所设定的系统调用时,断点被踩到。

因为经常在linux用c语言,所以主要用到的event是最后四个,其他的没有仔细研究。
     例如:
     catch syscall open
     catch syscall 5
     这两个捕获断点一样。

gdb断点(四)删除

断点的删除与断点的设置同样的重要。删除断点的命令有两个: delete , clear

delete
用法:delete [breakpoints num] [range...]
delete可删除单个断点,也可删除一个断点的集合,这个集合用连续的断点号来描述。
例如:
delete 5
delete 1-10

clear
用法:clear 
    删除所在行的多有断点。
    clear location
clear 删除所选定的环境中所有的断点
clear location location描述具体的断点。
例如:
clear list_insert         //删除函数的所有断点
clear list.c:list_delet   //删除文件:函数的所有断点
clear 12                  //删除行号的所有断点
clear list.c:12           //删除文件:行号的所有断点

clear 删除断点是基于行的,不是把所有的断点都删除。

gdb断点(五)激活与禁止

对断点的控制除了建立和删除外,还可以通过使能和禁止来控制,后一种方法更灵活。

enable [breakpoints] [range...] 完全使能
enable                //激活所有断点
enable 4            //激活4断点
enable 5-6            //激活5~6断点
disable [breakpoints] [range...] 禁止

用法举例:
diable                //禁止所有断点
disble 2            //禁止第二个断点
disable 1-5            //禁止第1到第5个断点

enable once [breakpoints] [range...] 使能一次,触发后禁止
enable delete [breakpoints] [range...]使能一次,触发后删除

gdb断点(六)condition 与ignore

同一个断点,因不同的条件(不同的地方调用)而停止程序,同时你也可以自定义命令行,来

打印所需要的信息。

设置断点的条件方式如下:

1、设置断点的时候加入条件
      break foo if value_a > value_b

2、用condition命令

condition bnum expression

例如: condition 6 if value_a == 10
      如果你设置的断点条件,无效会提示:(这于断点的上下文有关,关于断点的上下文会子专门章节阐述)
      No symbol "foo" in current context

3、取消断点条件

condition bnum

4、断点条件特殊用法
      断点条件的一个特殊用法是,程序只有在到达断点一定次数之后才会停止。这用一个特殊的命令可以实现。
      ignore bnum count

ignore 设置的触发条件在重新加载程序之后自动删除。

ignore 2 10  //触发断点10次后,才会停止,每次触发断点count自减1

如果一个断点及设置了条件,又设置了触发次数,在触发次数count为0之前,是不会判断断点的条件。
      ignore 命令对breakpoint watchpoint catchpoint都有效。