在软件开发的过程中,无论如何努力,bug几乎都是必不可少的。当某些bug发生时,该进程会产生coredump文件。通过这个coredump文件,开发人员可以找到bug的原因。但是coredump的产生,大都是因为程序crash了。
1. 死锁
有些bug是不会导致进程crash的,比如死锁——这时,程序已经不正常了,可是却没有coredump产生。如果环境又不允许gdb调试,难道我们就束手无策了吗?针对这种情况,一般情况下,对于这样的进程,可以利用watchdog监控它们,当发现这些进程很长时间没有更新其heartbeat时,可以给这些进程发送可以导致其产生coredump的信号。根据linux的信号默认的处理行为,SIGQUIT,SIGABRT, SIGFPE和SIGSEGV都可以让该进程产生coredump文件。这样我们可以通过coredump来得知死锁是否发生。当然,如果进程添加了这些信号的处理函数,那么就不会产生coredump了。
如何查看死锁
死锁:一种情形,此时执行程序中两个或多个线程发生永久堵塞(等待),每个线程都在等待被
其他线程占用并堵塞了的资源。例如,如果线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。
gdb调试死锁的方法:
gdb
attach pid
thread apply all bt
找到_lll_lock_wait 锁等待的地方。
然后查找该锁被哪个线程锁住了。
例如:
查看哪个线程拥有互斥体
(gdb) print AccountA_mutex
$1 = {__m_reserved = 2, __m_count = 0, __m_owner = 0x2527,
__m_kind = 0, __m_lock
= {__status = 1, __spinlock = 0}}
(gdb) print 0x2527
$2 = 9511
(gdb) print AccountB_mutex
$3 = {__m_reserved = 2, __m_count = 0, __m_owner = 0x2529,
__m_kind = 0, __m_lock = {__status = 1, __spinlock = 0}}
(gdb) print 0x2529
$4 = 9513
(gdb)
从上面的命令中,我们可以看出AccontA_mutex是被线程 5(LWP 9511)加锁(拥有)的,而AccontB_mutex是被线程 3(LWP 9513)加锁(拥有)的。
找出死锁的地方,对应检查代码就可以了。死锁大多是对锁的使用发生交叉所致的,
解决死锁的方法常有:
有序资源分配法
是操作系统中预防死锁的一种算法,这种算法资源按某种规则系统中的所有资源统一编号(例如打印机为1、磁带机为2、磁盘为3、等等),申请时必须以上升的次序。
系统要求申请进程:
1、对它所必须使用的而且属于同一类的所有资源,必须一次申请完;
2、在申请不同类资源时,必须按各类设备的编号依次申请。
例如:进程PA,使用资源的顺序是R1,R2;
进程PB,使用资源的顺序是R2,R1;
若采用动态分配有可能形成环路条件,造成死锁。
采用有序资源分配法:R1的编号为1,R2的编号为2;
PA:申请次序应是:R1,R2
PB:申请次序应是:R1,R2
这样就破坏了环路条件,避免了死锁的发生。
另外,还有死锁避免,死锁检测与恢复等。
银行家算法
我们可以把看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。
为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。
2.获取指定位置快照:
还有一种情况,进程并没有死锁或者block在某个位置,但是我们需要在某个指定位置进行调试,获取某些变量或者其它信息。但是,有可能是客户环境或者生产环境,不允许我们进行长时间的检测。那么,我们就需要通过coredump来获得进程在运行到该点时的快照。
这个时候,可以利用gdb来产生手工产生coredump。在attach上这个进程时,在指定位置打上断点,当断点触发时,使用gdb的命令gcore,可以立即产生一个coredump。这样,我们就拿到了这个位置的进程快照。
3. CPU占用率过高问题分析方法
====================================
shell下执行:ps -eLfP 找出cpu占用率高的线程
UID PID PPID LWP PSR C NLWP STIME TTY TIME
root 1431 1270 1751 5 90 64 Dec24 ? 00:00:00
^^^^ ^^ ^^
thread core cpurate
使用gdb:
gdb attach 1431 <==== 登录cpu占用率高的进程
set height 0
i thread <==== 打印该进程的所有线程
找到 : LWP 1751 线程
17 Thread 855356656 (LWP 1751) 0x2ac30994 in () from /lib/libpthread.so.0
^^ ^^^^^^^^
打出cpu占用率高的 任务的调用栈:
thread 17 <============ 切换到该线程
bt <============ 打印该线程的调用栈 看该线程当前正在调用的函数
一般影响性能的都是长时间占用cpu的 所以打印bt 大部分都是影响性能的地方
如果怀疑是死循环 可以尝试finish命令 退出当前函数 反复执行该命令 当某次finish 无响应时 说明该函数无法退出 即死循环
4. 宏定义调试
如下面的代码:
-
#include <stdlib.h>
-
#include <stdio.h>
-
-
#define MACRO1(x) (++(x))
-
#define MACRO2(x) (MACRO1(x)+100)
-
#define MACRO3(x) (MACRO2(x)+200)
-
-
-
int main(void)
-
{
-
int a = 0;
-
int b = 0;
-
-
b = MACRO3(a);
-
-
printf("%d\n", b);
-
-
return 0;
-
}
这里的MACRO3嵌套调用了MACRO2,MACRO1。
为了调试程序,需要使用-g选项,它的作用就是将调试信息加入到最后的二进制可执行文件中。但是你可知道-g 也通-o一样,是分级别的。当不指定级别的时候,其level为2。为了调试宏定义,我们可以使用更高的级别-g3。
下面为我使用-g3编译上面的代码,然后进行调试:
-
Breakpoint 1, main () at test.c:11
-
11 int a = 0;
-
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.i686
-
(gdb) n
-
12 int b = 0;
-
(gdb)
-
14 b = MACRO3(a);
-
(gdb)
-
16 printf("%d\n", b);
-
(gdb) macro expand MACRO3(a)
-
expands to: (((++(a))+100)+200)
-
(gdb) macro expand MACRO3(0)
-
expands to: (((++(0))+100)+200)
-
(gdb) macro exp MACRO3(0)
-
expands to: (((++(0))+100)+200)
-
(gdb)
在调试的过程中,可以使用macro expand/exp 来展开宏定义。从上面的调试过程中,可以直接看到宏定义展开后的结果。并且我们还可以给宏传入任何的一个值,如:
-
(gdb) macro exp MACRO3(3)
-
expands to: (((++(3))+100)+200)
-
(gdb)
=================================================================================================
1)打印该进程所有线程的调用栈
==============================
gdb attach 5325
set height 0
thread apply all bt
detach
q
3) 4) 5)一起使用 可以测试函数输出:
3)调用函数判断其返回值和输出:
gdb attach xxxx
set height 0
p malloc( XXXX) <=== 申请XXX字节的 内存 该语句会返回内存的的指 比如0xAAAABBBB
detach
q
4)初始化内存
gdb attach 7362
set height 0
p *(unsigned int *)0xAAAABBBB = 0 <=== 如果是四个字节 则按此方式初时化
detach
q
5)将申请的内存作为输出参数:
gdb attach 7362
set height 0
p Func(0xAAAABBBB) <==== 这里会打印Func的返回值
p *(VOS_UINT32 *)0xAAAABBBB <==== 这里打印出Func的输出
detach
q
6)断点自动执行动作:
gdb attach xxxx
set height 0
b haAgentAMUSendHB
command 1 <==============begin=========== 当达到断点 1 自动执行的操作
shell date 打印时间
p g_ulxxx 打印变量
c 继续执行
end <===============end ===========
c
使用watch来监测某个变量
7)内存改写问题
=================这部分是在引入 valgrind 之前写的 现在使用valgrind更方便=====================
可以使用gdb 的 watch 命令
但是watch 命令 只能监控全局变量 所以需要找个对系统无关紧要的全局变量a
将被改写的变量的地址赋给该变量a 然后对变量a进行watch
例如: pSession->ui_taskid 被改写 在未被改写时 打个断点(如果是全局变量 可以直接CTRL + C)
(gdb) p pSession->ui_taskid
$2 = 184549376 <=====================可以看出 这时的pSession->ui_taskid 为184549376
(gdb) p &(pSession->ui_taskid) <=====================看下被改写变量的地址
$3 = (unsigned int *) 0xb7bf0d30 <==================== 地址为0xb7bf0d30
(gdb) p g_iChildren = 0xb7bf0d3 <==================== 找个无关紧要的全局变量(一定要四个字节)将其赋值为被改写变量的地址
$4 = -1212216016
(gdb) p *g_iChildren <==================== 验证下是否构造成功 可见g_iChildren 就是 pSession->ui_taskid 的地址
$5 = 184549376 <==================== 由此可见 g_iChildren 目前是
(gdb) watch *(unsigned int *)g_iChildren <=========== watch 该地址的内容 即被改写的变量的值
Hardware watchpoint 3: *(unsigned int *) g_iChildren <= 断点成功
(gdb) c <================ 继续执行
Continuing.
RTSP_handleIngestReqMsg session_GetServiceType = 9
f_rtsp_ChannelBufMdt: shmdt error.
f_rtsp_ChannelBufFree: Invalid BufId.
Hardware watchpoint 3: *(unsigned int *) g_iChildren
Old value = 184549376 <=========== 被改写时自动停下 并记录改写前后的值
New value = 0
0xb7c710e7 in memset () from /lib/libc.so.6
(gdb) bt <=========== bt 打印调用栈 就知道被改写的位置了
#0 0xb7c710e7 in memset () from /lib/libc.so.6
#1 0x08051b0b in session_reset (ui_sessionid=1058815) at session.c:947
#2 0x08051b52 in session_free (ui_sessionid=1058815) at session.c:130
#3 0x0806c26a in RTPUDP_Client_Cancel (sessionID=1058815) at cdn_rtsp.cpp:177
#4 0x0806c675 in RTSP_handleIngestReqMsg (sessionID=1058815, optType=2, pRespFunc=0x8054b0d ) at cdn_rtsp.cpp:359
#5 0x0806cb0a in RTSP_MsgProc (pmsg=0x80e51d8, msgLength=40) at cdn_rtsp.cpp:571
#6 0x0806b900 in CDN_SocketChkRcv () at channel.c:307
#7 0x080a8365 in BasicTaskScheduler::SingleStep (this=0x80e4c18, maxDelayTime=0) at BasicTaskScheduler.cpp:250
#8 0x080a9877 in BasicTaskScheduler0::doEventLoop (this=0x80e4c18, watchVariable=0x0) at BasicTaskScheduler0.cpp:76
#9 0x0804a28b in main (argc=Cannot access memory at address 0xb5
) at live555MediaServer.cpp:218
(gdb)
阅读(1463) | 评论(0) | 转发(2) |