首页
社区
课程
招聘
[原创]Windbg跟踪临界区的bug
2015-10-23 20:56 10099

[原创]Windbg跟踪临界区的bug

2015-10-23 20:56
10099
最近跟踪了一个程序的界面卡死问题,用windbg载入dump文件后打印出函数调用堆栈后,一眼可以看出是临界区死锁了。
0:000:x86> kb
ChildEBP RetAddr  Args to Child              
0032dd0c 779ed993 00000710 00000000 00000000 ntdll_779b0000!NtWaitForSingleObject+0x15
0032dd70 779ed877 00000000 00000000 024023f0 ntdll_779b0000!RtlpWaitOnCriticalSection+0x13e
0032dd98 58a2fac3 02404c50 856fd57e 024023f0 ntdll_779b0000!RtlEnterCriticalSection+0x150
0032dffc 58a0d4d7 856fea8a 00000000 001c41a0 SogouSoftware_589d0000!CDownloadListUI::UpdateDownloadListUI+0x43

    输出该临界区的信息:
0:000:x86> !cs 02404c50
-----------------------------------------
Critical section   = 0x0000000002404c50 (+0x2404C50)
DebugInfo          = 0x0000000000611e08
LOCKED
LockCount          = 0xFFFFFFFF
WaiterWoken        = Yes
OwningThread       = 0x0000000000000710
RecursionCount     = 0x1A38
LockSemaphore      = 0x2433B08
SpinCount          = 0x0000000000000000

    Windbg指示是0x710号线程占有该临界区在,于是看看0x710号线程是那一个,结果发现报错,一般这种情况是线程已经退出或者被终止掉了。
0:000:x86> ~~[710]
                 ^ Illegal thread error in '~~[710]'

    该临界区死锁的位置部分代码如下:
void CDownloadListUI::UpdateDownloadListUI()
{
	m_vctLock.Lock();
	vector<int> vecDeleteItems(GetCount());	// record index to be delete
	std::iota(vecDeleteItems.begin(), vecDeleteItems.end(), 0);
	......
	m_vctLock.UnLock();
}

    m_vctLock是ATL一个对临界区简单封装的类,让同事仔细对m_vctLock所有加锁的位置进行检查,发现除了主线程外只有另外一个工作线程会使用,用windbg查了下改工作线程并未退出,线程ID也不为 0x710,难道又被Windbg给忽悠了?打印下该临界区的数据结构看看:
0:000:x86> dt _RTL_CRITICAL_SECTION 02404c50
DuiLib!_RTL_CRITICAL_SECTION
   +0x000 DebugInfo        : 0x00611e08 _RTL_CRITICAL_SECTION_DEBUG
   +0x004 LockCount        : 0n-6
   +0x008 RecursionCount   : 0n1
   +0x00c OwningThread     : 0x00001a38 Void
   +0x010 LockSemaphore    : 0x00000710 Void
   +0x014 SpinCount        : 0

    发现这里显示的拥有者线程号和上面不一致,试试看是那个线程:
0:000:x86> ~~[1a38]
   6  Id: 2058.1a38 Suspend: 0 Teb: 7ef94000 Unfrozen
      Start: SogouSoftware_589d0000!_threadstartex (58a5192d) 
      Priority: 0  Priority class: 32  Affinity: f
0:000:x86> ~6s
ntdll_779b0000!ZwWaitForMultipleObjects+0x15:
779d019d 83c404          add     esp,4
0:006:x86> kb
ChildEBP RetAddr  Args to Child              
0370fa5c 768615f7 00000002 0370faac 00000001 ntdll_779b0000!ZwWaitForMultipleObjects+0x15
0370faf8 773519f8 0370faac 0370fb20 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100
0370fb40 773541d8 00000002 7efde000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0
0370fb5c 589f6ba0 00000002 0370fb84 00000000 kernel32!WaitForMultipleObjects+0x18
0370fbd4 58a51907 58aab894 862df68e 00000000 SogouSoftware_589d0000!CThreadQueue<TagDownloadTask>::ThreadProc+0x100 
0370fc0c 58a51991 00000000 0370fc24 7735336a SogouSoftware_589d0000!_callthreadstartex+0x1b [f:\dd\vctools\crt_bld\self_x86\crt\src\threadex.c @ 314]
0370fc18 7735336a 023f5170 0370fc64 779e9882 SogouSoftware_589d0000!_threadstartex+0x64 [f:\dd\vctools\crt_bld\self_x86\crt\src\threadex.c @ 292]
0370fc24 779e9882 023f5170 771cc6bb 00000000 kernel32!BaseThreadInitThunk+0xe
0370fc64 779e9855 58a5192d 023f5170 00000000 ntdll_779b0000!__RtlUserThreadStart+0x70
0370fc7c 00000000 58a5192d 023f5170 00000000 ntdll_779b0000!_RtlUserThreadStart+0x1b

    6号线程正处于等待任务中,对照代码,6号线程有一个观察者的回调函数会调用到CDownloadListUI类中的m_vctLock锁,但是该回调函数已经执行完毕了。那么只有一种可能,就是锁泄露了,即Lock后没有解锁。再去看该回调函数,果然发现有一个很少出现的分支下没有调用Unlock释放临界区而直接返回了,这就造成了经典的锁泄露而引起死锁的bug。
   但 !cs 命令在此种情况下却给出了错误的信息,很容易造成误导而怀疑是一个拥有该锁的线程退出了而引起的。这应该算是Windbg的一个bug吧。
   进一步测试该情况,发现是当64位系统下的32位进程产生的dump会有此问题,32位系统下产生的dump使用 !cs 命令执行正常。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞1
打赏
分享
最新回复 (7)
雪    币: 204
活跃值: (906)
能力值: (RANK:1324 )
在线值:
发帖
回帖
粉丝
mratlatsn 10 2015-10-23 22:30
2
0
感谢楼主分享
雪    币: 6
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
影子不寂寞 2015-10-24 09:31
3
0
谢谢楼主分享技术贴
雪    币: 61
活跃值: (56)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
我只会易 2015-10-24 19:55
4
0
膜拜大神。
顺便想知道下m_vctLock是什么。
雪    币: 657
活跃值: (454)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
organic 3 2015-10-25 11:52
5
0
private:
	CComCriticalSection m_vctLock;


class CComCriticalSection
{
public:
	CComCriticalSection() throw()
	{
		memset(&m_sec, 0, sizeof(CRITICAL_SECTION));
	}
	~CComCriticalSection()
	{
	}
	HRESULT Lock() throw()
	{
		EnterCriticalSection(&m_sec);
		return S_OK;
	}
	HRESULT Unlock() throw()
	{
		LeaveCriticalSection(&m_sec);
		return S_OK;
	}
	HRESULT Init() throw()
	{
		HRESULT hRes = S_OK;

		if (!InitializeCriticalSectionAndSpinCount(&m_sec, 0))
		{
			hRes = HRESULT_FROM_WIN32(GetLastError());
		}

		return hRes;
	}

	HRESULT Term() throw()
	{
		DeleteCriticalSection(&m_sec);
		return S_OK;
	}
	CRITICAL_SECTION m_sec;
};

ATL一个以临界区的作为锁的一个简单的封装。
雪    币: 5
活跃值: (186)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
terryjie 2015-11-2 08:11
6
0
建议对CComCriticalSection再封装一个包装类,其构造函数调用CComCriticalSection.lock,析构调用CComCriticalSection.unlock.使用时只需要实例化包装类,方便一些
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yuesong 2015-11-4 14:35
7
0
搞手,其构造函数调用CComCriticalSection.lock,析构调用CComCriticalSection.unlock
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
csccc 2015-11-23 12:46
8
0
觉得有可能是符号匹配有问题
游客
登录 | 注册 方可回帖
返回