2011年9月11日星期日

[轉載]調試筆記之thread


作 者: microdebug 
時 間: 2010-04-16,11:53:41
鏈接: http://bbs.pediy.com/showthread.php?t=111038

說明: 關於內核相關的,借助外部資料去閱讀理解,效果可能會更好點;如wrk,reactOS,windows internals
       必要的時候,也需要用調試器去跟踪內核,發現問題之所在,並體會其中的樂趣。
 
  關於線程切換是一個很基礎的話題,發這個帖子主要是為了分享一些實際調試經驗。很多人都是直接將調試結論寫出來,但是一般不會寫怎麼調試的,可能覺得調試的過程比較低級乏味吧。當然,結論的來源,無非就是兩點:第一,閱讀類系統內核代碼,如上;第二,就是調試。
 
  關於進程和線程切換的原理性說明,就不羅嗦,直接切入主題。

調試目的:
  1.調試線程控制塊TCB,獲知哪些字段指示了線程每獲得一次CPU時間片時,實際執行的時間;因為當線程獲得CPU的時候,並不一定都是在執行代碼。

  3.了解線程切換時的工作流程;

調試環境:host+vmware; 或host+1394(target)

調試方法:
 
  寫一個application console程序,名為dbgTCB,為了便於調試,程序內部只執行一個簡單的死循環,如下:
  int main(int argc, char ** argv)
  {
    int num = 0;

    _asm int 3;

    while(1)
    {
      num ++;
    }
  
    return 0;
  }


  先編譯這段程序(debug模式),然後將編譯後的EXE,.PDB文件拷貝到目標機(VM),用WINDBG聯機調試。在目標機裡執行exe,當執行到_asm int 3;時被中斷,控制權返還到windbg。調試開始:


  步驟:
  1.!analyze -v 分析
  如果在調試機上編譯,在VM(被調試機)上執行,windbg加載工程的符號表dbgTCB.pdb,那麼該命令執行過後,
  就會出現源代碼窗口,反彙編窗口,並且高亮顯示斷點位置。

  2.查看當前堆棧:kb
 
kd> kb
ChildEBP RetAddr Args to Child
0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x1f [e:\temp program\dbgtcb\dbgtcb.cpp @ 5]
0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23
 
  3.定位進程地址
  !process 0 0 注:用!process 0 7可以得到更加詳細的信息,但是耗時,在我們已經知道進程名字的情況下,選用前者,然後用命令:
  !process addr 7 得到更加詳細的信息,其中addr 是要關注的進程的內存加載地址


PROCESS 81f86520 SessionId: 0 Cid: 05a8 Peb: 7ffde000 ParentCid: 05e8
    DirBase: 04f002e0 ObjectTable: e143e718 HandleCount: 7.
    Image: dbgTCB.exe
  
//得到進程dbgTCB.exe的詳細信息
kd> !process 81f86520 7
PROCESS 81f86520 SessionId: 0 Cid: 05a8 Peb: 7ffde000 ParentCid: 05e8
    DirBase: 04f002e0 ObjectTable: e143e718 HandleCount: 7.
    Image: dbgTCB.exe
    VadRoot 820674b8 Vads 22 Clone 0 Private 43. Modified 0. Locked 0.
    DeviceMap e1dfb5c8
    Token e14988c0
    ElapsedTime 00:00:00.046
    UserTime 00:00:00.015
    KernelTime 00:00:00.000
    QuotaPoolUsage[PagedPool] 12364
    QuotaPoolUsage[NonPagedPool] 880
    Working Set Sizes (now,min,max) (174, 50, 345) (696KB, 200KB, 1380KB)
    PeakWorkingSetSize 174
    VirtualSize 7 Mb
    PeakVirtualSize 7 Mb
    PageFaultCount 167
    MemoryPriority BACKGROUND
    BasePriority 8
    CommitCharge 51
 
  //得到TCB地址
        THREAD 81f862a8 Cid 05a8.0588 Teb: 7ffdd000 Win32Thread: 00000000 RUNNING on processor ​​0
        Not impersonating
        DeviceMap e1dfb5c8
        Owning Process 0 Image: <Unknown>
        Attached Process 81f86520 Image: dbgTCB.exe
        Wait Start TickCount 9170 Ticks: 1 (0:00:00:00.015)
        Context Switch Count 23
        UserTime 00:00:00.000
        KernelTime 00:00:00.000
        Win32 Start Address dbgTCB!mainCRTStartup (0x004010b0)
        Start Address kernel32!BaseProcessStartThunk (0x7c8106f5)
        Stack Init f0a95000 Current f0a94b8c Base f0a95000 Limit f0a92000 Call 0
        Priority 10 BasePriority 8 PriorityDecrement 2 DecrementCount 16
        ChildEBP RetAddr Args to Child
        0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x1f [e:\temp program\dbgtcb\dbgtcb.cpp @ 5]
        0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
        0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])




    
  4. 查看線程ETHREAD信息

kd> dt nt!_ethread 81f862a8
   +0x000 Tcb : _KTHREAD
   +0x1c0 CreateTime : _LARGE_INTEGER 0xe543bc6`8be3c610
   +0x1c0 NestedFaultCount : 0y00
   +0x1c0 ApcNeeded : 0y0
   +0x1c8 ExitTime : _LARGE_INTEGER 0x81f86470`81f86470
   +0x1c8 LpcReplyChain : _LIST_ENTRY [ 0x81f86470 - 0x81f86470 ]
   +0x1c8 KeyedWaitChain : _LIST_ENTRY [ 0x81f86470 - 0x81f86470 ]
   +0x1d0 ExitStatus : 0
   +0x1d0 OfsChain : (null)
   +0x1d4 PostBlockList : _LIST_ENTRY [ 0x81f8647c - 0x81f8647c ]
   +0x1dc TerminationPort : 0xe11c8298 _TERMINATION_PORT
   +0x1dc ReaperLink : 0xe11c8298 _ETHREAD
   +0x1dc KeyedWaitValue : 0xe11c8298


  ... ...

  ETHREAD偏移0字節,查看_kthread數據結構

kd> dt nt!_kthread 81f862a8+0
   +0x000 Header : _DISPATCHER_HEADER
   +0x010 MutantListHead : _LIST_ENTRY [ 0x81f862b8 - 0x81f862b8 ]
   +0x018 InitialStack : 0xf0a95000
   +0x01c StackLimit : 0xf0a92000
   +0x020 Teb : 0x7ffdd000
   +0x024 TlsArray : (null)
   +0x028 KernelStack : 0xf0a94b8c
   +0x02c DebugActive : 0 ''
   +0x02d State : 0x2 ''
   +0x02e Alerted : [2] ""
   +0x030 Iopl : 0 ''
   +0x031 NpxState : 0xa ''
   +0x032 Saturation : 0 ''
   +0x033 Priority : 10 ''
   +0x034 ApcState : _KAPC_STATE
   +0x04c ContextSwitches : 0x17
   +0x050 IdleSwapBlock : 0 ''
   +0x051 Spare0 : [3] ""
   +0x054 WaitStatus : 0
   +0x058 WaitIrql : 0x1 ''
   +0x059 WaitMode : 1 ''
   +0x05a WaitNext : 0 ''
   +0x05b WaitReason : 0x11 ''
   +0x05c WaitBlockList : 0x81f86318 _KWAIT_BLOCK
   +0x060 WaitListEntry : _LIST_ENTRY [ 0x80554870 - 0x80554870 ]
   +0x060 SwapListEntry : _SINGLE_LIST_ENTRY
   +0x068 WaitTime : 0x23d2
   +0x06c BasePriority : 8 ''
   +0x06d DecrementCount : 0x10 ''
   +0x06e PriorityDecrement : 2 ''
   +0x06f Quantum : 4 ''
   +0x070 WaitBlock : [4] _KWAIT_BLOCK
   +0x0d0 LegoData : (null)
   +0x0d4 KernelApcDisable : 0
   +0x0d8 UserAffinity : 1
   +0x0dc SystemAffinityActive : 0 ''
   +0x0dd PowerState : 0 ''
   +0x0de NpxIrql : 0 ''
   +0x0df InitialNode : 0 ''
  ... ...



  5.線程工作狀態的決定因子nt!_ethread._kthread.State

  如上結構體,nt!_kthread 81f862a8+0+2d處,有一個State的字段,取值為0x2.
  此時,通過!process 81b68020 7 命令,得知,線程工作狀態為:running

  打開內存窗口,定位到地址81f862a8+0+2d ,將內存數據改為01

  此時用 !process 81b68020 7 觀察:

  ... ...
THREAD 81e03318 Cid 0204.04e4 Teb: 7ffdf000 Win32Thread: 00000000 READY
  ... ...

  線程狀態由running-->ready.
  那麼,現在可以確定,nt!_ethread._kthread.State 數據值就是指示當前線程工作狀態的因子
 
  6.跟踪線程狀態切換流程

  由前面的分析,得出nt!_ethread._kthread.State就是線程工作狀態的地址,當線程切換的時候,一定會改變線程狀態,那麼在狀態內存地址處下斷點,就可以跟踪到修改這一斷點的函數了,進而可能跟踪到其他線程切換時的函數了。
  一個線程獲取CPU,但並非一定處於執行狀態,我們通知跟踪這個狀態值,可以精確地得到線程在CPU上的執行時間。

  注:準備工作,單步執行,讓被調試程序進入while(1)循環:
  
  查看堆棧:
kd> kb
ChildEBP RetAddr Args to Child
0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x29 [e:\temp program\dbgtcb\dbgtcb.cpp @ 9]
0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23


  然後讓程序運行
  g
  在ctrl+break 中斷

 
  再次查看堆棧:
kd> kb
ChildEBP RetAddr Args to Child
f0a94d50 8054209d 00000001 00000000 000000d1 nt!RtlpBreakWithStatusInstruction
f0a94d50 0040103c 00000001 00000000 000000d1 nt!KeUpdateSystemTime+0x165
0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x2c [e:\temp program\dbgtcb\dbgtcb.cpp @ 9]
0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23

  在nt!_ethread._kthread.State地址處下斷點:

  ba w4 81f862d4 ,其實地址為:81f862d5,但是Data breakpoint must be aligned,所以取地址為:81f862d4


  OK,再次運行程序:g

  下面關係到線程切換的若干函數調用過程:理論上,程序會斷在修改nt!_ethread._kthread.State狀態值的地方,
  那麼,也就可以間接地知道是哪個函數在修改這個值了。

kd> g
Breakpoint 0 hit
nt!KiReadyThread+0x3a:
80501942 833d84bd548000 cmp dword ptr [nt!KiIdleSummary (8054bd84)],0

  !!!程序斷在了這裡,可以推測:nt!KiReadyThread這個函數就是改變線程工作狀態的函數

nt!KiReadyThread:
80501908 8bff mov edi,edi
8050190a 55 push ebp
8050190b 8bec mov ebp,esp
8050190d 51 push ecx
8050190e 51 push ecx
8050190f 8bc1 mov eax,ecx
80501911 8d8828010000 lea ecx,[eax+128h]
80501917 53 push ebx
80501918 8a19 mov bl,byte ptr [ecx]
8050191a c60100 mov byte ptr [ecx],0
8050191d 8b15a0bd5480 mov edx,dword ptr [nt!KeTickCount (8054bda0)]
80501923 0fbe4833 movsx ecx,byte ptr [eax+33h]
80501927 56 push esi
80501928 895068 mov dword ptr [eax+68h],edx
8050192b 8b5044 mov edx,dword ptr [eax+44h]
8050192e 57 push edi
8050192f eb5e jmp nt!KiReadyThread+0x87 (8050198f)
80501931 80b82a01000000 cmp byte ptr [eax+12Ah],0
80501938 0f84ac000000 je nt!KiReadyThread+0xe2 (805019ea)
      //2dh,狀態值為3是standby狀態,至此線程的四個狀態應該清楚:00 initialized,01 ready,02 running,03 standby
8050193e c6402d03 mov byte ptr [eax+2Dh],3

       //被中斷的指令處,在此之後p單步運行
80501942 833d84bd548000 cmp dword ptr [nt!KiIdleSummary (8054bd84)],0 ds:0023:8054bd84=00000000
80501949 8b3d403e5580 mov edi,dword ptr [nt!KiProcessorBlock (80553e40)]
8050194f 0f85cb000000 jne nt!KiReadyThread+0x118 (80501a20)
80501955 8b5708 mov edx,dword ptr [edi+8]
80501958 85d2 test edx,edx
8050195a 0f84c9000000 je nt!KiReadyThread+0x121 (80501a29)
80501960 0fbe7233 movsx esi,byte ptr [edx+33h]
80501964 3bce cmp ecx,esi
80501966 0f8ed4000000 jle nt!KiReadyThread+0x138 (80501a40)
8050196c 8db228010000 lea esi,[edx+128h]
80501972 c60601 mov byte ptr [esi],1
80501975 894708 mov dword ptr [edi+8],eax
80501978 8a1e mov bl,byte ptr [esi]
8050197a c60600 mov byte ptr [esi],0
8050197d 0fbe4a33 movsx ecx,byte ptr [edx+33h]
80501981 8b35a0bd5480 mov esi,dword ptr [nt!KeTickCount (8054bda0)]
80501987 8bc2 mov eax,edx
80501989 897268 mov dword ptr [edx+68h],esi
8050198c 8b5244 mov edx,dword ptr [edx+44h]
8050198f 807a6500 cmp byte ptr [edx+65h],0
80501993 749c je nt!KiReadyThread+0x29 (80501931)
80501995 c6402d01 mov byte ptr [eax+2Dh],1
80501999 c6802901000001 mov byte ptr [eax+129h],1
805019a0 83c060 add eax,60h
805019a3 8d4a40 lea ecx,[edx+40h]
805019a6 8b7104 mov esi,dword ptr [ecx+4]
805019a9 8908 mov dword ptr [eax],ecx
805019ab 897004 mov dword ptr [eax+4],esi
805019ae 8906 mov dword ptr [esi],eax
805019b0 894104 mov dword ptr [ecx+4],eax
805019b3 807a6501 cmp byte ptr [edx+65h],1
805019b7 0f85bb000000 jne nt!KiReadyThread+0x170 (80501a78)
805019bd c6426502 mov byte ptr [edx+65h],2
805019c1 a1e03d5580 mov eax,dword ptr [nt!KiProcessInSwapListHead (80553de0)]
805019c6 8d7248 lea esi,[edx+48h]
805019c9 8975f8 mov dword ptr [ebp-8],esi
805019cc 8945fc mov dword ptr [ebp-4],eax
805019cf 8906 mov dword ptr [esi],eax
805019d1 8bf8 mov edi,eax
805019d3 8b45fc mov eax,dword ptr [ebp-4]
805019d6 b9e03d5580 mov ecx,offset nt!KiProcessInSwapListHead (80553de0)
805019db 8b55f8 mov edx,dword ptr [ebp-8]
805019de 0fb111 cmpxchg dword ptr [ecx],edx
805019e1 3bc7 cmp eax,edi
805019e3 8945fc mov dword ptr [ebp-4],eax
805019e6 75e7 jne nt!KiReadyThread+0xc7 (805019cf)
805019e8 eb2f jmp nt!KiReadyThread+0x111 (80501a19)
805019ea 66ff4260 inc word ptr [edx+60h]
805019ee 8d7060 lea esi,[eax+60h]
805019f1 c6402d06 mov byte ptr [eax+2Dh],6
805019f5 a1d83d5580 mov eax,dword ptr [nt!KiStackInSwapListHead (80553dd8)]
805019fa 8975f8 mov dword ptr [ebp-8],esi
805019fd 8945fc mov dword ptr [ebp-4],eax
80501a00 8906 mov dword ptr [esi],eax
80501a02 8bf8 mov edi,eax
80501a04 8b45fc mov eax,dword ptr [ebp-4]
80501a07 b9d83d5580 mov ecx,offset nt!KiStackInSwapListHead (80553dd8)
80501a0c 8b55f8 mov edx,dword ptr [ebp-8]
80501a0f 0fb111 cmpxchg dword ptr [ecx],edx
80501a12 3bc7 cmp eax,edi
80501a14 8945fc mov dword ptr [ebp-4],eax
80501a17 75e7 jne nt!KiReadyThread+0xf8 (80501a00)
80501a19 e8446bffff call nt!KiSetSwapEvent (804f8562)
80501a1e eb58 jmp nt!KiReadyThread+0x170 (80501a78)
80501a20 832584bd548000 and dword ptr [nt!KiIdleSummary (8054bd84)],0
80501a27 eb12 jmp nt!KiReadyThread+0x133 (80501a3b)
80501a29 8b5704 mov edx,dword ptr [edi+4]
80501a2c 0fbe7233 movsx esi,byte ptr [edx+33h]
80501a30 3bce cmp ecx,esi
80501a32 7e0c jle nt!KiReadyThread+0x138 (80501a40)
80501a34 c6822801000001 mov byte ptr [edx+128h],1
80501a3b 894708 mov dword ptr [edi+8],eax
80501a3e eb38 jmp nt!KiReadyThread+0x170 (80501a78)
80501a40 c6402d01 mov byte ptr [eax+2Dh],1
80501a44 83c060 add eax,60h
80501a47 84db test bl,bl

       //將DispatcherReadyListHead 所有狀態為ready的線程鍊錶頭地址保存到edx
       //
80501a49 8d14cd20485580 lea edx,nt!KiDispatcherReadyListHead (80554820)[ecx*8]
       // [br =1]跳轉,
80501a50 740e je nt!KiReadyThread+0x158 (80501a60)
80501a52 8b32 mov esi,dword ptr [edx]
80501a54 8930 mov dword ptr [eax],esi
80501a56 895004 mov dword ptr [eax+4],edx
80501a59 894604 mov dword ptr [esi+4],eax
80501a5c 8902 mov dword ptr [edx],eax
80501a5e eb0d jmp nt!KiReadyThread+0x165 (80501a6d)
       //將狀態為ready的線程鍊錶的最頂層元素出棧(堆棧原理),發送到esi
80501a60 8b7204 mov esi,dword ptr [edx+4]
80501a63 8910 mov dword ptr [eax],edx
80501a65 897004 mov dword ptr [eax+4],esi
80501a68 8906 mov dword ptr [esi],eax
80501a6a 894204 mov dword ptr [edx+4],eax
80501a6d 33c0 xor eax,eax
80501a6f 40 inc eax
80501a70 d3e0 shl eax,cl
       // inc eax,函數KiReadySummary可能是統計有多少read線程
80501a72 09058cbd5480 or dword ptr [nt!KiReadySummary (8054bd8c)],eax
80501a78 5f pop edi
80501a79 5e pop esi
80501a7a 5b pop ebx
80501a7b c9 leave
80501a7c c3 ret
80501a7d cc int 3
80501a7e cc int 3
80501a7f cc int 3
80501a80 cc int 3
80501a81 cc int 3



  KiReadyThread這個函數返回以後呢,回到nt!KiDispatchInterrupt函數。


  查看堆棧:
kd> kb
ChildEBP RetAddr Args to Child
f0a94d48 806d3ca4 00000000 ffdff980 80542076 nt!KiDispatchInterrupt+0x78
f0a94d54 80542076 00000000 000000d1 0012ff80 hal!HalEndSystemInterrupt+0x54
f0a94d54 0040103c 00000000 000000d1 0012ff80 nt!KeUpdateSystemTime+0x13e
0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x2c [e:\temp program\dbgtcb\dbgtcb.cpp @ 9]
0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23


 
  KiDispatchInterrupt函數的部分反彙編代碼如下:
 

8054287a 89742408 mov dword ptr [esp+8],esi
8054287e 897c2404 mov dword ptr [esp+4],edi
80542882 892c24 mov dword ptr [esp],ebp
80542885 8bf0 mov esi,eax
80542887 8bbb24010000 mov edi,dword ptr [ebx+124h]
8054288d c7832801000000000000 mov dword ptr [ebx+128h],0
80542897 89b324010000 mov dword ptr [ebx+124h],esi
8054289d 8bcf mov ecx,edi
8054289f c6475001 mov byte ptr [edi+50h],1
    
      //調用nt!KiReadyThread 函數
805428a3 e860f0fbff call nt!KiReadyThread (80501908)
  
805428a8 b101 mov cl,1

      //下面調用nt!SwapContext,這裡才是線程切換的真正函數。
      //這裡我們要F11進去看看,線程切換的精華就是這個函數,下面會有分析,​​這裡暫時跳過。

805428aa e831000000 call nt!SwapContext (805428e0)
805428af 8b2c24 mov ebp,dword ptr [esp]
805428b2 8b7c2404 mov edi,dword ptr [esp+4]
805428b6 8b742408 mov esi,dword ptr [esp+8]
805428ba 83c40c add esp,0Ch
805428bd c3 ret
805428be c783ac09000000000000 mov dword ptr [ebx+9ACh],0
805428c8 e8e9e2fbff call nt!KiQuantumEnd (80500bb6)
805428cd 0bc0 or eax,eax
805428cf 75a6 jne nt!KiDispatchInterrupt+0x47 (80542877)
805428d1 c3 ret
805428d2 8da42400000000 lea esp,[esp]
805428d9 8da42400000000 lea esp,[esp]
nt!SwapContext:
805428e0 0ac9 or cl,cl
805428e2 26c6462d02 mov byte ptr es:[esi+2Dh],2
805428e7 9c pushfd
805428e8 8b0b mov ecx,dword ptr [ebx]
805428ea 83bb9409000000 cmp dword ptr [ebx+994h],0
805428f1 51 push ecx
805428f2 0f8535010000 jne nt!SwapContext+0x14d (80542a2d)
  ......
805428aa e831000000 call nt!SwapContext (805428e0)
      //前面f11跟進nt!SwapContext,走了一通,回來的時候ret在下面的指令上
      //也就是說,在這個函數里,根據不同情況,在不同位置調用swapcontext
      //保存堆棧棧頂到ebp
805428af 8b2c24 mov ebp,dword ptr [esp]
805428b2 8b7c2404 mov edi,dword ptr [esp+4]
805428b6 8b742408 mov esi,dword ptr [esp+8]
805428ba 83c40c add esp,0Ch
805428bd c3 ret
    
      //執行ret後,返回到
      hal!HalEndSystemInterrupt函數

      查看堆棧
kd> kb
ChildEBP RetAddr Args to Child
f0a94d54 80542076 00000000 000000d1 0012ff80 hal!HalEndSystemInterrupt+0x27
f0a94d54 0040103c 00000000 000000d1 0012ff80 nt!KeUpdateSystemTime+0x13e
0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x2c [e:\temp program\dbgtcb\dbgtcb.cpp @ 9]
0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23

      ret返回後,到函數nt!KeUpdateSystemTime

      查看堆棧:

kd> kb
ChildEBP RetAddr Args to Child
f0a94d64 0040103c badb0d00 00430a00 f0a94d98 nt!KeUpdateSystemTime+0x13e
0012ff80 00401199 00000001 00430aa0 00430a00 dbgTCB!main+0x2c [e:\temp program\dbgtcb\dbgtcb.cpp @ 9]
0012ffc0 7c817067 00390038 00370036 7ffde000 dbgTCB!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 004010b0 00000000 78746341 kernel32!BaseProcessStart+0x23

    
  接上,又進行一系列的工作指令之後,回到了nt!KiReadyThread函數,

  ..大致如上,可以看出線程切換的一個大概過程


  分析nt!SwapContext函數


  查看當前堆棧:
kd> kb
ChildEBP RetAddr Args to Child
f0a94d64 0040103c badb0d00 00430a00 f0a94d98 nt!SwapContext
f0a94ddc 80542dd2 f956ab85 82064280 00000000 dbgTCB!main+0x2c [e:\temp program\dbgtcb\dbgtcb.cpp @ 9]
f0a94e2c 7c93017b 7c9301bb 0000dbbc 00000000 nt!KiThreadStartup+0x16
WARNING: Process directory table base 04F00260 doesn't match CR3 04F002E0
WARNING: Process directory table base 04F00260 doesn't match CR3 04F002E0
00000000 00000000 00000000 00000000 00000000 ntdll!RtlAllocateHeap+0x1c2

  nt!SwapContex函數的部分反彙編如下:

  nt!SwapContext:
805428e0 0ac9 or cl,cl

       // 這裡就比較熟悉了,0x2狀態值應該就是線程工作狀態:running
       // esi應該就是指向一個新線程的_ethread.kthread結構,然後偏移2d,就是state字段了
       // edi指向舊線程的_ethread.kthread結構edx指向KPCR

805428e2 26c6462d02 mov byte ptr es:[esi+2Dh],2 es:0023:82025b5d=03
805428e7 9c pushfd
805428e8 8b0b mov ecx,dword ptr [ebx]
805428ea 83bb9409000000 cmp dword ptr [ebx+994h],0
805428f1 51 push ecx
805428f2 0f8535010000 jne nt!SwapContext+0x14d (80542a2d)
805428f8 833d0cbf558000 cmp dword ptr [nt!PPerfGlobalGroupMask (8055bf0c)],0
805428ff 0f85ff000000 jne nt!SwapContext+0x124 (80542a04)
80542905 0f20c5 mov ebp,cr0
80542908 8bd5 mov edx,ebp

       //和上面的分析一樣,2c偏移處的字段需要關注,DebugActive :UChar
8054290a 8a4e2c mov cl,byte ptr [esi+2Ch]
8054290d 884b50 mov byte ptr [ebx+50h],cl

      //這里關閉中斷,看來要進行一些修改屬性的操作了
80542910 fa cli

      //保存當前棧到舊線程的Kthread->KernelStack中
80542911 896728 mov dword ptr [edi+28h],esp

      // 18偏移nitialStack:Ptr32 Void 初始化堆棧新線程Kthread->InitialStack 系統空間棧底
80542914 8b4618 mov eax,dword ptr [esi+18h]

      // 1c偏移 StackLimit:Ptr32 Void
80542917 8b4e1c mov ecx,dword ptr [esi+1Ch]

8054291a 2d10020000 sub eax,210h
8054291f 894b08 mov dword ptr [ebx+8],ecx
80542922 894304 mov dword ptr [ebx+4],eax
80542925 33c9 xor ecx,ecx

      //31偏移NpxState:UChar 新線程KThread->NpxState
80542927 8a4e31 mov cl,byte ptr [esi+31h]
      ////清除NPX標誌位
8054292a 83e2f1 and edx,0FFFFFFF1h

8054292d 0bca or ecx,edx
8054292f 0b880c020000 or ecx,dword ptr [eax+20Ch]
80542935 3be9 cmp ebp,ecx
80542937 0f85bf000000 jne nt!SwapContext+0x11c (805429fc)
8054293d 8d4900 lea ecx,[ecx]
80542940 f740e400000200 test dword ptr [eax-1Ch],20000h
80542947 7503 jne nt!SwapContext+0x6c (8054294c)
80542949 83e810 sub eax,10h
8054294c 8b4b40 mov ecx,dword ptr [ebx+40h] //KPCR->TSS
8054294f 894104 mov dword ptr [ecx+4],eax
80542952 8b6628 mov esp,dword ptr [esi+28h] //開始切換,裝載新線程KTHREAD->KernelStack到esp

      //偏移20,Teb
80542955 8b4620 mov eax,dword ptr [esi+20h]
80542958 894318 mov dword ptr [ebx+18h],eax //KPCR->NT_TIB->Self,更新KPCR線程環境快(TEB)
  
      //執行完上面的操作後,重新開啟中斷,看來上面的操作過程中,不希望有任何操作打斷
      //從而保證這個原子操作的完整性

8054295b fb sti
    
8054295c 8b4744 mov eax,dword ptr [edi+44h] //舊線程的PROCESS
8054295f 3b4644 cmp eax,dword ptr [esi+44h] //比較新舊線程所屬的PROCESS是否一樣
80542962 c6475000 mov byte ptr [edi+50h],0
80542966 742c je nt!SwapContext+0xb4 (80542994) //一樣就跳轉
80542968 8b7e44 mov edi,dword ptr [esi+44h]
8054296b 66f74720ffff test word ptr [edi+20h],0FFFFh

80542971 755b jne nt!SwapContext+0xee (805429ce)
80542973 33c0 xor eax,eax
80542975 0f00d0 lldt ax
80542978 33c0 xor eax,eax
8054297a 8ee8 mov gs,ax
8054297c 8b4718 mov eax,dword ptr [edi+18h]
8054297f 8b6b40 mov ebp,dword ptr [ebx+40h]
80542982 8b4f30 mov ecx,dword ptr [edi+30h]
80542985 89451c mov dword ptr [ebp+1Ch],eax
80542988 0f22d8 mov cr3,eax ////當切換的線程屬於不同的進程時,切換CR3。
8054298b 66894d66 mov word ptr [ebp+66h],cx
8054298f eb03 jmp nt!SwapContext+0xb4 (80542994)
80542991 8d4900 lea ecx,[ecx]
80542994 8b4318 mov eax,dword ptr [ebx+18h]
80542997 8b4b3c mov ecx,dword ptr [ebx+3Ch]
8054299a 6689413a mov word ptr [ecx+3Ah],ax
8054299e c1e810 shr eax,10h
805429a1 88413c mov byte ptr [ecx+3Ch],al
805429a4 88613f mov byte ptr [ecx+3Fh],ah
805429a7 ff464c inc dword ptr [esi+4Ch]
805429aa ff831c060000 inc dword ptr [ebx+61Ch]
805429b0 59 pop ecx
805429b1 890b mov dword ptr [ebx],ecx

805429b3 807e4900 cmp byte ptr [esi+49h],0
805429b7 7504 jne nt!SwapContext+0xdd (805429bd)
805429b9 9d popfd
805429ba 33c0 xor eax,eax
805429bc c3 ret //切換結束

  簡單的分析了下swapcontext中感興趣的字段;
  綜上,swapcontext正如函數其名,就是將新老線程的ethread的某些狀態值改變,保存舊線程的堆棧,
  初始化新線程的工作環境,如此而已;其餘的細節可以自己​​詳細分析。


  現在查看堆棧:

  nt!SwapContext執行ret後,返回到nt!KiSwapContext

kd> kb
ChildEBP RetAddr Args to Child
f08ce974 80501cd6 00000000 82025b30 804faae2 nt!KiSwapContext+0x2e
f08ce980 804faae2 82025b30 00000001 00000000 nt!KiSwapThread+0x46
f08ce9b8 805b7418 00000001 f08cebec 00000001 nt!KeWaitForMultipleObjects+0x284
f08ced48 8053e638 00000001 00b8fea0 00000001 nt!NtWaitForMultipleObjects+0x2a2
f08ced48 7c92e4f4 00000001 00b8fea0 00000001 nt!KiFastCallEntry+0xf8
00b8fe74 7c92df2c 7c809574 00000001 00b8fea0 ntdll!KiFastSystemCallRet
00b8fe78 7c809574 00000001 00b8fea0 00000001 ntdll!NtWaitForMultipleObjects+0xc
00b8ff14 00401c39 00000001 003bad10 00000000 kernel32!WaitForMultipleObjectsEx+0x12c
00b8ff5c 004024f9 000186a0 00000000 003b75a0 dbgTCB!_XcptFilter+0x79 [winxfltr.c @ 267]
00b8ff98 0040e1fe 00158b50 77dc352b 00000001 dbgTCB!__crtGetEnvironmentStringsA+0xc9 [a_env.c @ 96]
00b8ffb4 7c80b713 00158b50 00000000 0012e814 dbgTCB!CloseHandle+0x2d52
00b8ffec 00000000 77dc3519 00158b50 00000000 kernel32!BaseThreadStart+0x37
 
  跳轉到nt!KiSwapContext函數之後,又ret到nt!KiSwapThread函數


  大概流程就這樣。細節並沒有照顧太多,主要是說明,如何根據自己的需要去調試系統內核,找到自己關心的。
 
  關於總結:
  1,我們關心的是一個線程獲得cpu以後,實際執行的時間;在用戶層,我們知道精確獲得一個線程獲取CPU的時間,可以用TLS機制;在內核裡,可以就可以更加精確地知道,當線程獲取CPU以後,只有nt!_kthread 81f862a8+0+2d處的State字段為running的時候,才算是實際實行;只要這個值改變,實際的一段執行時間就被切換或其他原因interrupt掉了。
  2. 關於線程切換,實在是一個大的流程,上面只是涉及到了一定如何跟踪的實際技巧。如果想詳細研究,建議先看看windows internal的線程切換相關部分,在自己調試,就容易多了。

  以上權作學習筆記,錯誤之處,勿見笑。

没有评论:

发表评论