在调试程序的时候,可能经常会有这样的需求,让一个线程在特定的时候才让其开始执行或者暂停执行。比如复杂的多线程导致死锁的问题,又或者多线程中的Race Condition 导致程序执行异常等。
很多时候,我们可以借助编写调试代码来达到多线程的调试,可是有些情况下调试的执行粒度是指令级别的,那么这个时候我们得借助调试利器Windbg了。本文我们将以《C/C++编程教训—-函数内静态类对象初始化非线程安全(C++11之前)》为调试例子 (没看过的同学,可以先看一下这篇博文)。个人觉得这个例子不错,这个调试的执行力度是指令级别的,因为其存在的线程安全问题的代码是编译器生成的,并不是程序员自己的代码。
以下这段代码,创建两个线程,这两个线程等待主线程触发的event,然后打印TestFunction
返回的对象的成员变量m_iVal
。**正常情况下,两个线程的打印的m_iVal
都为4。可是在之前博客有提到过,TestFunction()
是非线程安全的,而我们也无法通过增加调试代码来触发这个非线程安全的问题。那么下一章节,我们将通过Windbg来控制线程的执行来触发这个非线程安全的问题。
TestObject TestFunction()
{
static TestObject obj;
return obj;
}
HANDLE hEvent = NULL;
DWORD WINAPI ThreadFunction(LPVOID lpParam)
{
if (hEvent)
{
WaitForSingleObject(hEvent, INFINITE);
}
TestObject obj = TestFunction();
printf("Thread [%x] obj value [%d]\n", GetCurrentThreadId(), obj.m_iVal);
return 0;
}
int main(int argc, char **argv)
{
hEvent = CreateEvent(NULL, true, false, NULL);
if (!hEvent)
{
fprintf(stderr, "error\n");
return 0;
}
DWORD dwId;
for (int i = 0; i < 2; i++)
{
HANDLE handle = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &dwId);
}
Sleep(5*1000);
SetEvent(hEvent);
system("pause");
}
一下是Windbg的调试过程,博主加上了自己的解释。
Opened log file 'D:\debug.txt'
//为了让创建的两个线程,处于等待Event的状态,做一些线程控制的操作
//于是先查看main函数汇编,找到main 函数中触发event的指令地址00000001`40008543
0:000> uf icekingtest!main
icekingtest!main [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 148]:
148 00000001`40008480 4889542410 mov qword ptr [rsp+10h],rdx
148 00000001`40008485 894c2408 mov dword ptr [rsp+8],ecx
148 00000001`40008489 57 push rdi
148 00000001`4000848a 4883ec50 sub rsp,50h
148 00000001`4000848e 488bfc mov rdi,rsp
148 00000001`40008491 48b91400000000000000 mov rcx,14h
148 00000001`4000849b b8cccccccc mov eax,0CCCCCCCCh
148 00000001`400084a0 f3ab rep stos dword ptr [rdi]
148 00000001`400084a2 8b4c2460 mov ecx,dword ptr [rsp+60h]
149 00000001`400084a6 4533c9 xor r9d,r9d
149 00000001`400084a9 4533c0 xor r8d,r8d
149 00000001`400084ac ba01000000 mov edx,1
149 00000001`400084b1 33c9 xor ecx,ecx
149 00000001`400084b3 ff1597900000 call qword ptr [icekingtest!_imp_CreateEventW (00000001`40011550)]
149 00000001`400084b9 488905d0730000 mov qword ptr [icekingtest!hEvent (00000001`4000f890)],rax
150 00000001`400084c0 48833dc873000000 cmp qword ptr [icekingtest!hEvent (00000001`4000f890)],0
150 00000001`400084c8 751e jne icekingtest!main+0x68 (00000001`400084e8)
icekingtest!main+0x4a [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 152]:
152 00000001`400084ca ff1540930000 call qword ptr [icekingtest!_imp___iob_func (00000001`40011810)]
152 00000001`400084d0 4883c060 add rax,60h
152 00000001`400084d4 488d15a9440000 lea rdx,[icekingtest!`string'+0x54 (00000001`4000c984)]
152 00000001`400084db 488bc8 mov rcx,rax
152 00000001`400084de ff1534930000 call qword ptr [icekingtest!_imp_fprintf (00000001`40011818)]
153 00000001`400084e4 33c0 xor eax,eax
153 00000001`400084e6 eb74 jmp icekingtest!main+0xdc (00000001`4000855c)
icekingtest!main+0x68 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 157]:
157 00000001`400084e8 c744244400000000 mov dword ptr [rsp+44h],0
157 00000001`400084f0 eb0b jmp icekingtest!main+0x7d (00000001`400084fd)
icekingtest!main+0x72 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 157]:
157 00000001`400084f2 8b442444 mov eax,dword ptr [rsp+44h]
157 00000001`400084f6 83c001 add eax,1
157 00000001`400084f9 89442444 mov dword ptr [rsp+44h],eax
icekingtest!main+0x7d [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 157]:
157 00000001`400084fd 837c244402 cmp dword ptr [rsp+44h],2
157 00000001`40008502 7d2d jge icekingtest!main+0xb1 (00000001`40008531)
icekingtest!main+0x84 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 159]:
159 00000001`40008504 488d442434 lea rax,[rsp+34h]
159 00000001`40008509 4889442428 mov qword ptr [rsp+28h],rax
159 00000001`4000850e c744242000000000 mov dword ptr [rsp+20h],0
159 00000001`40008516 4533c9 xor r9d,r9d
159 00000001`40008519 4c8d05d98cffff lea r8,[icekingtest!ILT+500(?ThreadFunctionYAKPEAXZ) (00000001`400011f9)]
159 00000001`40008520 33d2 xor edx,edx
159 00000001`40008522 33c9 xor ecx,ecx
159 00000001`40008524 ff152e900000 call qword ptr [icekingtest!_imp_CreateThread (00000001`40011558)]
159 00000001`4000852a 4889442448 mov qword ptr [rsp+48h],rax
160 00000001`4000852f ebc1 jmp icekingtest!main+0x72 (00000001`400084f2)
icekingtest!main+0xb1 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 161]:
161 00000001`40008531 b988130000 mov ecx,1388h
161 00000001`40008536 ff1504900000 call qword ptr [icekingtest!_imp_Sleep (00000001`40011540)]
162 00000001`4000853c 488b0d4d730000 mov rcx,qword ptr [icekingtest!hEvent (00000001`4000f890)]
162 00000001`40008543 ff1517900000 call qword ptr [icekingtest!_imp_SetEvent (00000001`40011560)]
163 00000001`40008549 488d0d3c440000 lea rcx,[icekingtest!`string'+0x5c (00000001`4000c98c)]
163 00000001`40008550 ff15b2920000 call qword ptr [icekingtest!_imp_system (00000001`40011808)]
163 00000001`40008556 eb02 jmp icekingtest!main+0xda (00000001`4000855a)
icekingtest!main+0xda [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 164]:
164 00000001`4000855a 33c0 xor eax,eax
icekingtest!main+0xdc [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 164]:
164 00000001`4000855c 488bf8 mov rdi,rax
164 00000001`4000855f 488bcc mov rcx,rsp
164 00000001`40008562 488d1527450000 lea rdx,[icekingtest!`string'+0x160 (00000001`4000ca90)]
164 00000001`40008569 e842d7ffff call icekingtest!_RTC_CheckStackVars (00000001`40005cb0)
164 00000001`4000856e 488bc7 mov rax,rdi
164 00000001`40008571 4883c450 add rsp,50h
164 00000001`40008575 5f pop rdi
164 00000001`40008576 c3 ret
//在main 函数中触发event的指令地址00000001`40008543处设置断点
0:000> bp 00000001`40008543
//开始执行程序, 并且命中了之前设置的断点
0:000> g
Breakpoint 0 hit
icekingtest!main+0xc3:
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\kernel32.dll -
00000001`40008543 ff1517900000 call qword ptr [icekingtest!_imp_SetEvent (00000001`40011560)] ds:00000001`40011560={kernel32!SetEvent (00000000`771e3f00)}
//这时候两个线程ThreadFunction应该都创建好了
//这时候查看TestFunction汇编
//并且找到表示TestFunction中obj是否已经初始化的标记 设置为1的地方,在其下一条指令设置断点 (注意此时,实际上TestFunction中的obj并未调用构造函数)
0:000> uf TestFunction
icekingtest!TestFunction [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 130]:
130 00000001`40008350 48894c2408 mov qword ptr [rsp+8],rcx
130 00000001`40008355 57 push rdi
130 00000001`40008356 4883ec30 sub rsp,30h
130 00000001`4000835a 488bfc mov rdi,rsp
130 00000001`4000835d 48b90c00000000000000 mov rcx,0Ch
130 00000001`40008367 b8cccccccc mov eax,0CCCCCCCCh
130 00000001`4000836c f3ab rep stos dword ptr [rdi]
130 00000001`4000836e 488b4c2440 mov rcx,qword ptr [rsp+40h]
130 00000001`40008373 48c7442420feffffff mov qword ptr [rsp+20h],0FFFFFFFFFFFFFFFEh
131 00000001`4000837c 8b05226f0000 mov eax,dword ptr [icekingtest!$S1 (00000001`4000f2a4)]
131 00000001`40008382 83e001 and eax,1
131 00000001`40008385 85c0 test eax,eax
131 00000001`40008387 751c jne icekingtest!TestFunction+0x55 (00000001`400083a5)
icekingtest!TestFunction+0x39 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 131]:
131 00000001`40008389 8b05156f0000 mov eax,dword ptr [icekingtest!$S1 (00000001`4000f2a4)]
131 00000001`4000838f 83c801 or eax,1
131 00000001`40008392 89050c6f0000 mov dword ptr [icekingtest!$S1 (00000001`4000f2a4)],eax
131 00000001`40008398 488d0d016f0000 lea rcx,[icekingtest!obj (00000001`4000f2a0)]
131 00000001`4000839f e84b8effff call icekingtest!ILT+490(??0TestObjectQEAAXZ) (00000001`400011ef)
131 00000001`400083a4 90 nop
icekingtest!TestFunction+0x55 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 132]:
132 00000001`400083a5 488b442440 mov rax,qword ptr [rsp+40h]
132 00000001`400083aa 8b0df06e0000 mov ecx,dword ptr [icekingtest!obj (00000001`4000f2a0)]
132 00000001`400083b0 8908 mov dword ptr [rax],ecx
132 00000001`400083b2 488b442440 mov rax,qword ptr [rsp+40h]
133 00000001`400083b7 4883c430 add rsp,30h
133 00000001`400083bb 5f pop rdi
133 00000001`400083bc c3 ret
//设置有Race Condition 地方的断点
0:000> bp 00000001`40008398
//查看线程,可以看到1,2号线程,则是我们创建的两个ThreadFunction线程
0:000> ~*
. 0 Id: 1104.fb4 Suspend: 1 Teb: 000007ff`fffdc000 Unfrozen
Start: icekingtest!mainCRTStartup (00000001`400061a0)
Priority: 0 Priority class: 32 Affinity: ff
1 Id: 1104.11b4 Suspend: 1 Teb: 000007ff`fffda000 Unfrozen
Start: icekingtest!ILT+500(?ThreadFunctionYAKPEAXZ) (00000001`400011f9)
Priority: 0 Priority class: 32 Affinity: ff
2 Id: 1104.1020 Suspend: 1 Teb: 000007ff`fffd8000 Unfrozen
Start: icekingtest!ILT+500(?ThreadFunctionYAKPEAXZ) (00000001`400011f9)
Priority: 0 Priority class: 32 Affinity: ff
//大概看一下各个线程函数调用栈
0:000> ~*k
. 0 Id: 1104.fb4 Suspend: 1 Teb: 000007ff`fffdc000 Unfrozen
Child-SP RetAddr Call Site
00000000`0012fe60 00000001`4000635c icekingtest!main+0xc3 [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 162]
00000000`0012fec0 00000001`400061ae icekingtest!__tmainCRTStartup+0x19c [f:\sp\vctools\crt_bld\self_64_amd64\crt\src\crtexe.c @ 597]
00000000`0012ff30 00000000`771e652d icekingtest!mainCRTStartup+0xe [f:\sp\vctools\crt_bld\self_64_amd64\crt\src\crtexe.c @ 414]
00000000`0012ff60 00000000`7741c521 kernel32!BaseThreadInitThunk+0xd
00000000`0012ff90 00000000`00000000 ntdll!RtlUserThreadStart+0x21
1 Id: 1104.11b4 Suspend: 1 Teb: 000007ff`fffda000 Unfrozen
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\KERNELBASE.dll -
Child-SP RetAddr Call Site
00000000`0205fe68 000007fe`fd6a10ac ntdll!NtWaitForSingleObject+0xa
00000000`0205fe70 00000001`4000842f KERNELBASE!WaitForSingleObjectEx+0x9c
00000000`0205ff10 00000000`771e652d icekingtest!ThreadFunction+0x3f [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 142]
00000000`0205ff60 00000000`7741c521 kernel32!BaseThreadInitThunk+0xd
00000000`0205ff90 00000000`00000000 ntdll!RtlUserThreadStart+0x21
2 Id: 1104.1020 Suspend: 1 Teb: 000007ff`fffd8000 Unfrozen
Child-SP RetAddr Call Site
00000000`0215fe68 000007fe`fd6a10ac ntdll!NtWaitForSingleObject+0xa
00000000`0215fe70 00000001`4000842f KERNELBASE!WaitForSingleObjectEx+0x9c
00000000`0215ff10 00000000`771e652d icekingtest!ThreadFunction+0x3f [d:\tmufe_demo\tmufe_demo\icekingtest\test.cpp @ 142]
00000000`0215ff60 00000000`7741c521 kernel32!BaseThreadInitThunk+0xd
00000000`0215ff90 00000000`00000000 ntdll!RtlUserThreadStart+0x21
//此时暂停1号线程的执行
0:000> ~1 n
//然后继续运行,并且命中设置的有Race Condition地方的断点
0:000> g
ModLoad: 000007fe`fd280000 000007fe`fd2d7000 C:\Windows\system32\apphelp.dll
Breakpoint 1 hit
icekingtest!TestFunction+0x48:
00000001`40008398 488d0d016f0000 lea rcx,[icekingtest!obj (00000001`4000f2a0)]
//此时暂停2号线程
0:002> ~2 n
//继续执行1号线程
0:002> ~1 m
//继续执行一段时间后,再中断调试器
0:002> g
(1104.c30): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00000000`77440530 cc int 3
//让2号进程继续执行
0:001> ~2 m
0:001> g
Breakpoint 1 hit
icekingtest!TestFunction+0x48:
00000001`40008398 488d0d016f0000 lea rcx,[icekingtest!obj (00000001`4000f2a0)]
0:002> g
(1104.e08): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00000000`77440530 cc int 3
0:001> .logclose
Closing open log file D:\debug.txt
上面给出了完整的调试步骤,然后我们看下输出,线程0x11b4
即1号线程输出为0 (TestFunction中的obj还未完成初始化),2号线程即0x1120
则输出正常(TestFunction中的obj完成了初始化)。
Thread [11b4] obj value [0]
Thread [1120] obj value [4]
以上就是有个简单的多线程控制调试方法用于实践,欢迎大家一起讨论。