说明: 关于内核相关的,借助外部资料去阅读理解,效果可能会更好点;如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: 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的线程切换相关部分,在自己调试,就容易多了。 以上权作学习笔记,错误之处,勿见笑。