Windbg调试----多线程控制调试

在调试程序的时候,可能经常会有这样的需求,让一个线程在特定的时候才让其开始执行或者暂停执行。比如复杂的多线程导致死锁的问题,又或者多线程中的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]

以上就是有个简单的多线程控制调试方法用于实践,欢迎大家一起讨论。

你可能感兴趣的:(Windows调试)