精华内容
下载资源
问答
  • Windows-10-Toast-Notifications, python 显示 Windows 10 Toast通知 Windows 10 Toast通知 用于显示 Windows 10 Toast通知的easy-to-use python ,这对 Windows GUI开发非常有用。 安装pip install win
  • 文章目录Windows 10 动态并行加载浅析1. 问题2. Windows 10 并行加载Dll2.1 启动标记2.1 数据结构2.2 线程池的初始化2.3 LdrpMapAndSnapDependency2.4 LdrpWorkCallback2.5 LdrpProcessWork2.6 LdrpDrainWorkQueue...

    Windows 10 动态库并行加载浅析

    最近遇到一个比较有意思的问题,进程在退出的时候出现死锁了,并且这个问题涉及到Windows 10 的一个新特性,本文来探讨一下这个问题。

    1. 问题

    这个问题的现象是git在退出的时候一直卡死,具体情况如下:

    0:000> kb
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007ffc`c4ec26ee : 00000000`000002d4 00007ffc`c6e5c94c 00000000`00000000 00007ffc`c6e871e5 : ntdll!NtWaitForSingleObject+0x14
    01 00000000`004923a7 : 00000000`00492370 00000000`0000020c 00000000`00000000 00000000`00000114 : KERNELBASE!WaitForSingleObjectEx+0x8e
    02 00007ffc`c6e7a73b : 00000000`00000000 00000000`0049225d 00007ffc`c6ecfa30 00000000`00000000 : git+0x2023a7
    03 00000000`002925c4 : 00000000`004b8260 00000000`00000000 00000000`00000000 00000000`009ff450 : msvcrt!doexit+0xe7
    04 00000000`002929ac : 00000000`68ac8020 00000000`02f703c8 00000000`02f70010 00000000`004a6de0 : git+0x25c4
    05 00000000`004b92b3 : 00000000`00000006 00000000`02f70698 00007ffc`c6e87000 00000000`02f70698 : git+0x29ac
    06 00000000`004913f9 : 00000000`00000002 00007ffc`c6e5c430 00000000`02f706e8 00000000`00000008 : git+0x2292b3
    07 00000000`002913fe : 00000000`005a76b0 00000000`00000020 00000000`00596fa0 00000000`00000000 : git+0x2013f9
    08 00000000`0029153b : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : git+0x13fe
    09 00007ffc`c64e7034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : git+0x153b
    0a 00007ffc`c72bcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
    0b 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    

    先来看一下NtWaitForSingleObject等待的是什么?因为这里是x64的环境,第一个参数直接在rcx里面,等待的事件如下:

    0:000> rrcx
    rcx=0000000000000114
    0:000> !handle 0000000000000114 f
    Handle 0000000000000114
      Type         	Thread
      Attributes   	0
      GrantedAccess	0x1fffff:
             Delete,ReadControl,WriteDac,WriteOwner,Synch
             Terminate,Suspend,Alert,GetContext,SetContext,SetInfo,QueryInfo,SetToken,Impersonate,DirectImpersonate
      HandleCount  	4
      PointerCount 	163776
      Name         	<none>
      Object specific information
        Thread Id   43c0.3308
        Priority    10
        Base Priority 0
    

    从这里发现,这个线程是在等待一个线程退出来,我们看一下这是哪个线程,如下:

    0:000> ~
    .  0  Id: 43c0.2d40 Suspend: 1 Teb: 00000000`00627000 Unfrozen
       1  Id: 43c0.36dc Suspend: 1 Teb: 00000000`0062b000 Unfrozen
       2  Id: 43c0.3144 Suspend: 1 Teb: 00000000`0062d000 Unfrozen
       3  Id: 43c0.4108 Suspend: 1 Teb: 00000000`0062f000 Unfrozen
       4  Id: 43c0.47e0 Suspend: 1 Teb: 00000000`00631000 Unfrozen
       5  Id: 43c0.3308 Suspend: 1 Teb: 00000000`00633000 Unfrozen
       6  Id: 43c0.4bd8 Suspend: 0 Teb: 00000000`00635000 Unfrozen
    0:000> ~5s
    ntdll!NtWaitForSingleObject+0x14:
    00007ffc`c730be14 c3              ret
    0:005> kb
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007ffc`c72a36f2 : 00000000`00000000 00000000`00000014 00007ffc`c73db3f0 00000000`00000000 : ntdll!NtWaitForSingleObject+0x14
    01 00007ffc`c72a560d : 00000000`00633000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrpDrainWorkQueue+0x15e
    02 00007ffc`c72bcf2e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrShutdownThread+0x9d
    03 00007ffc`c64e703d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlExitUserThread+0x3e
    04 00007ffc`c72bcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x1d
    05 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    

    这里又是一个比较奇怪的线程,又在等一个东西,我们看一下这个等待事件

    0:005> rrcx
    rcx=0000000000000044
    0:005> !handle 0000000000000044 f
    Handle 0000000000000044
      Type         	Event
      Attributes   	0
      GrantedAccess	0x1f0003:
             Delete,ReadControl,WriteDac,WriteOwner,Synch
             QueryState,ModifyState
      HandleCount  	2
      PointerCount 	65524
      Name         	<none>
      Object specific information
        Event Type Auto Reset
        Event is Waiting
    

    这是在等待一个自动重置的事件,那么这应该猜测有两种可能:

    1. 设置这个事件的线程退出了。
    2. 设置这个事件的线程死锁了。

    这里很容易可以发现其实是第二种情况:

    0:004> kb
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007ffc`c72b619d : 00000000`00000000 00000000`00ad1440 00000000`00000000 00007ffc`c727ad5d : ntdll!NtWaitForAlertByThreadId+0x14
    01 00007ffc`c72b6052 : 00000000`00000000 00000000`00000000 00000000`02c2ec68 00007ffc`c6ecf508 : ntdll!RtlpWaitOnAddressWithTimeout+0x81
    02 00007ffc`c72b5e6d : 00007ffc`c6ecf500 00000000`00001722 00000000`00000000 00000000`8800058d : ntdll!RtlpWaitOnAddress+0xae
    03 00007ffc`c72915b4 : 00007ffc`c718ccd0 00000000`00000000 00000000`fffffffa 00000000`00000000 : ntdll!RtlpWaitOnCriticalSection+0xfd
    04 00007ffc`c72913e2 : 00000000`00000000 0000f52c`24d85f52 00007ffc`c718ccd0 00007ffc`c65c0000 : ntdll!RtlpEnterCriticalSectionContended+0x1c4
    05 00007ffc`c7183fd4 : 00007ffc`c718ccd0 00007ffc`c71fe358 00000000`00000100 00007ffc`c71fe350 : ntdll!RtlEnterCriticalSection+0x42
    06 00007ffc`c7184039 : 00000000`00000000 00000000`005e6950 00000000`00bb3660 00007ffc`c65f0000 : comdlg32!onexit+0x38
    07 00007ffc`c6e7a553 : 00000000`00000000 00007ffc`c71fe350 5868b66f`703fcc13 fb1f387f`dbe2d9dd : comdlg32!atexit+0x9
    08 00007ffc`c7183960 : 00000000`00000000 00000000`00000000 00007ffc`c71fe358 00000000`00000001 : msvcrt!initterm+0x43
    09 00007ffc`c7183ae5 : 00000000`00000001 00000000`02c2f318 00000000`00000001 00000000`00631000 : comdlg32!CRT_INIT+0x1c4
    0a 00007ffc`c72a7b3d : 00007ffc`c7150000 00000000`00000001 00000000`00000000 00000000`7ffe0385 : comdlg32!__DllMainCRTStartup+0xc1
    0b 00007ffc`c72d3523 : 00000000`00bc17b0 00007ffc`c7150000 00007ffc`00000001 00000000`00bc1ed0 : ntdll!LdrpCallInitRoutine+0x61
    0c 00007ffc`c72d32b6 : 00000000`00bad3c0 00000000`00bad300 00000000`02c2f301 00000000`00000001 : ntdll!LdrpInitializeNode+0x1d3
    0d 00007ffc`c72d333c : 00000000`02c2f300 00000000`00bad540 00000000`02c2f318 00000000`00bbd1c0 : ntdll!LdrpInitializeGraphRecurse+0x42
    0e 00007ffc`c72b0d13 : 00000000`00000000 00000000`00000000 00000000`02c2f3d0 00000000`02c2f318 : ntdll!LdrpInitializeGraphRecurse+0xc8
    0f 00007ffc`c72a4ca6 : 00000000`02c2f318 00000000`02c2f320 00000000`02c2f300 00000000`02c2f320 : ntdll!LdrpPrepareModuleForExecution+0xbf
    10 00007ffc`c72a5500 : 00000000`02c2f320 00000000`02c2f4c0 00000000`02c2f5b0 00000000`02c2f4b0 : ntdll!LdrpLoadDllInternal+0x19a
    11 00007ffc`c72a4464 : 00000000`00000000 00000000`00000009 00000000`00bb1ba0 00007ffc`c7285d21 : ntdll!LdrpLoadDll+0xa8
    12 00007ffc`c4ec8982 : 00000000`0000007e 00000000`00000008 00000000`02c2f5e0 00000000`00000000 : ntdll!LdrLoadDll+0xe4
    13 00007ffc`abaec329 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!LoadLibraryExW+0x162
    14 00007ffc`abaed39a : 00000000`00baf270 00000000`00000000 00000000`001847ba 00000000`00000000 : xxxx+0x1c329
    15 00007ffc`abaed3d9 : 00000000`00baf270 00000000`00000000 00000000`00000000 00000000`00000000 : xxxxx!PublicService+0x42a
    16 00007ffc`c64e7034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : xxxx!Init+0x9
    17 00007ffc`c72bcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
    18 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    

    这个线程在等待一个临界区,但是似乎这个临界区已经等不到了,看下这个临界区的状态:

    0:004> !cs -l
    -----------------------------------------
    DebugInfo          = 0x00007ffcc6ecf528
    Critical section   = 0x00007ffcc6ecf500 (msvcrt!CrtLock_Exit+0x0)
    LOCKED
    LockCount          = 0x1
    WaiterWoken        = No
    OwningThread       = 0x0000000000002d40
    RecursionCount     = 0x1
    LockSemaphore      = 0xFFFFFFFF
    SpinCount          = 0x0000000004000fa0
    -----------------------------------------
    DebugInfo          = 0x00007ffcc73d5978
    Critical section   = 0x00007ffcc73d54f8 (ntdll!LdrpLoaderLock+0x0)
    LOCKED
    LockCount          = 0x1
    WaiterWoken        = No
    OwningThread       = 0x00000000000047e0
    RecursionCount     = 0x1
    LockSemaphore      = 0xFFFFFFFF
    SpinCount          = 0x0000000004000000
    

    从上面的分析可以发现,三个线程形成了死锁状态:

    1. 第一个线程占用了临界区,然后等待第二个线程退出。
    2. 第二个线程等待第三个线程设置事件(暂时猜测如下)。
    3. 第三个线程需要等待第一个线程的临界区,然后设置第二个线程等待的事件。

    三个线程形成互相等待;我们知道只要有一个线程可以正常的话,那么,就可以退出死锁状态了,这里要分析出具体原因,我们就需要研究第二号线程等待事件的原因:

    0:005> kb
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007ffc`c72a36f2 : 00000000`00000000 00000000`00000014 00007ffc`c73db3f0 00000000`00000000 : ntdll!NtWaitForSingleObject+0x14
    01 00007ffc`c72a560d : 00000000`00633000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrpDrainWorkQueue+0x15e
    02 00007ffc`c72bcf2e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrShutdownThread+0x9d
    03 00007ffc`c64e703d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlExitUserThread+0x3e
    04 00007ffc`c72bcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x1d
    05 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    

    为了解释清楚这个问题,需要先了解Windows 并行加载Dll的过程。

    2. Windows 10 并行加载Dll

    为了提升进程启动的速度,Windows 10 引入了并行加载的技术,这种技术就是在进程启动的时候创建线程池,将进程依赖的所有Dll全部并行加载。

    2.1 启动标记

    首先默认情况下,并且加载技术是启动的,例如我们可以启动一个notepad.exe进程看看

    0:000> ~*kb
    
    .  0  Id: 4a0c.80b8 Suspend: 1 Teb: 000000c5`9375d000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f563aa2 : 00000000`00000000 00007fff`3f5c1a80 00007fff`3f5c1a80 00007fff`3f5c1a80 : ntdll!LdrpDoDebuggerBreak+0x30
    01 00007fff`3f55211b : 00000000`00000001 00000000`00000000 00000000`00000000 00000000`00000001 : ntdll!LdrpInitializeProcess+0x1f42
    02 00007fff`3f5047b3 : 00000000`00000000 00007fff`3f490000 00000000`00000000 000000c5`9375d000 : ntdll!_LdrpInitialize+0x4d94f
    03 00007fff`3f50475e : 000000c5`9355f300 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrpInitialize+0x3b
    04 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrInitializeThunk+0xe
    
       1  Id: 4a0c.87b0 Suspend: 1 Teb: 000000c5`9375f000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4a23d7 : 00000000`00000000 00000000`00000000 00000263`bfb94230 00000263`bfb97440 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
    01 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
    02 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       2  Id: 4a0c.4384 Suspend: 1 Teb: 000000c5`93761000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4a23d7 : 00000000`00000000 00000000`00000000 00000263`bfb94230 00000263`bfb97c10 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
    01 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
    02 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       3  Id: 4a0c.76a4 Suspend: 1 Teb: 000000c5`93763000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4a23d7 : 00000000`00000000 00000000`00000000 00000263`bfb94230 00000263`bfb98d10 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
    01 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
    02 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    

    这这里,我们就可以看到TppWorkerThread这个线程信息其实就是并行加载的线程池线程,这个线程的数目是通过一个标记来控制的,这个标记为Peb->ProcessParameters->LoaderThreads.

    我们有两种办法来设置这个LoaderThreads的值:

    1. 利用_RTL_USER_PROCESS_PARAMETERS这个结构的LoaderThreads在创建线程的时候指定,但是这个参数在CreateProcess中是没法指定的,只能通过ZwCreateUserProcess来指定,但是还是有办法可以做到。
    2. 利用HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<app name>这个注册表下面的MaxLoaderThreads=dword:xxxx来指定。

    这两个值的情况分别如下:

    0:000> dt ntdll!_RTL_USER_PROCESS_PARAMETERS 
       +0x000 MaximumLength    : Uint4B
       +0x004 Length           : Uint4B
       +0x008 Flags            : Uint4B
       +0x00c DebugFlags       : Uint4B
       +0x010 ConsoleHandle    : Ptr64 Void
       +0x018 ConsoleFlags     : Uint4B
       +0x020 StandardInput    : Ptr64 Void
       +0x028 StandardOutput   : Ptr64 Void
       +0x030 StandardError    : Ptr64 Void
       +0x038 CurrentDirectory : _CURDIR
       +0x050 DllPath          : _UNICODE_STRING
       +0x060 ImagePathName    : _UNICODE_STRING
       +0x070 CommandLine      : _UNICODE_STRING
       +0x080 Environment      : Ptr64 Void
       +0x088 StartingX        : Uint4B
       +0x08c StartingY        : Uint4B
       +0x090 CountX           : Uint4B
       +0x094 CountY           : Uint4B
       +0x098 CountCharsX      : Uint4B
       +0x09c CountCharsY      : Uint4B
       +0x0a0 FillAttribute    : Uint4B
       +0x0a4 WindowFlags      : Uint4B
       +0x0a8 ShowWindowFlags  : Uint4B
       +0x0b0 WindowTitle      : _UNICODE_STRING
       +0x0c0 DesktopInfo      : _UNICODE_STRING
       +0x0d0 ShellInfo        : _UNICODE_STRING
       +0x0e0 RuntimeData      : _UNICODE_STRING
       +0x0f0 CurrentDirectores : [32] _RTL_DRIVE_LETTER_CURDIR
       +0x3f0 EnvironmentSize  : Uint8B
       +0x3f8 EnvironmentVersion : Uint8B
       +0x400 PackageDependencyData : Ptr64 Void
       +0x408 ProcessGroupId   : Uint4B
       +0x40c LoaderThreads    : Uint4B
       +0x410 RedirectionDllName : _UNICODE_STRING
       +0x420 HeapPartitionName : _UNICODE_STRING
       +0x430 DefaultThreadpoolCpuSetMasks : Ptr64 Uint8B
       +0x438 DefaultThreadpoolCpuSetMaskCount : Uint4B
       +0x43c DefaultThreadpoolThreadMaximum : Uint4B
    

    以及注册表的配置

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\chrome.exe]
    "MaxLoaderThreads"=dword:00000001
    

    2.1 数据结构

    在了解Windows并行加载技术之前,先来看看与这种技术相关的几个数据结构;

    HANDLE LdrpWorkCompleteEvent, LdrpLoadCompleteEvent; 
    CRITICAL_SECTION LdrpWorkQueueLock; 
    LIST_ENTRY LdrpWorkQueue = { &LdrpWorkQueue, &LdrpWorkQueue };
    
    ULONG LdrpWorkInProgress;
    BOOLEAN LdrpDetourExist;
    PTP_POOL LdrpThreadPool;
    

    其中LdrpWorkQueue这个就是并行加载的队列。

    还有就是Teb->SameTebFlags这个标记的几个位的重要含义:

    0x0008	: USHORT SkipThreadAttach : 1;
    0x1000	: USHORT LoadOwner : 1;
    0x2000	: USHORT LoaderWorker : 1;
    

    这些数据结构初始化函数如下:

    __int64 LdrpInitParallelLoadingSupport()
    {
      LdrpWorkQueue.Blink = &LdrpWorkQueue;
      LdrpWorkQueue.Flink = &LdrpWorkQueue;
      LdrpRetryQueue.Blink = &LdrpRetryQueue;
      LdrpRetryQueue.Flink = &LdrpRetryQueue;
      RtlInitializeCriticalSectionEx(&LdrpWorkQueueLock, 0i64, 0i64);
      return LdrpCreateLoaderEvents();
    }
    
    __int64 LdrpCreateLoaderEvents()
    {
      char v0; // ST20_1
      __int64 result; // rax
      __int64 v2; // [rsp+20h] [rbp-18h]
    
      v0 = 0;
      result = ZwCreateEvent(&LdrpLoadCompleteEvent, EVENT_ALL_ACCESS, 0i64, SynchronizationEvent, v0);
      if ( (signed int)result < 0 )
        return result;
      LOBYTE(v2) = 0;
      result = ZwCreateEvent(&LdrpWorkCompleteEvent, EVENT_ALL_ACCESS, 0i64, SynchronizationEvent, v2);
      return result;
    }
    

    2.2 线程池的初始化

    在进程启动之后,就会开始初始化并行加载线程池,这个流程是在LdrpEnableParallelLoading这个函数中完成的,这个函数的流程如下:

    NTSTATUS LdrpEnableParallelLoading(ULONG LoaderThreads)
    {
    	LdrpDetectDetour();
    
    	if (LoaderThreads)
    	{
    		LoaderThreads = min(LoaderThreads, 16);// not more than 16 threads allowed
    		if (LoaderThreads <= 1) return STATUS_SUCCESS;
    	}
    	else
    	{
    		if (RtlGetSuiteMask() & 0x10000) return STATUS_SUCCESS;
    		LoaderThreads = 4;// default for 4 threads
    	}
    
    	if (LdrpDetourExist) return STATUS_SUCCESS;
    
    	NTSTATUS status = TpAllocPool(&LdrpThreadPool, 1);//CreateThreadpool
    
    	if (0 <= status)
    	{
    		TpSetPoolWorkerThreadIdleTimeout(LdrpThreadPool, -300000000);// 30 second idle timeout
    		TpSetPoolMaxThreads(LdrpThreadPool, LoaderThreads - 1);//SetThreadpoolThreadMaximum 
    		TP_CALLBACK_ENVIRON CallbackEnviron = { };
    		CallbackEnviron->CallbackPriority = TP_CALLBACK_PRIORITY_NORMAL;
    		CallbackEnviron->Size = sizeof(TP_CALLBACK_ENVIRON);
    		CallbackEnviron->Pool = LdrpThreadPool;
    		CallbackEnviron->Version = 3;
    
    		status = TpAllocWork(&LdrpMapAndSnapWork, LdrpWorkCallback, 0, &CallbackEnviron);//CreateThreadpoolWork
    	}
    
    	return status;
    }
    

    这里有几个流程:

    1. LdrpDetectDetour : 检查应用程序是否被挂钩;如果挂钩LdrpDetourExist被设置为TRUE。
    2. LoaderThreads : 这个代表线程池的中线程的数目,默认是[LoaderThreads, 16].
    3. 如果LoaderThreads : 只有一个线程,那么就不启用线程池。
    4. 如果LdrpDetourExist检测到被挂钩,那么也不启动线程池。
    5. 创建一个30S Idle Time的线程池LdrpMapAndSnapWork, 并且回调函数为LdrpWorkCallback.

    至于挂钩程序的简单,在本人电脑上面的检测函数如下:

    .rdata:000000018011CF00 LdrpCriticalLoaderFunctions dq offset NtOpenFile
    .rdata:000000018011CF08                 dq offset NtCreateSection
    .rdata:000000018011CF10                 dq offset ZwQueryAttributesFile
    .rdata:000000018011CF18                 dq offset NtOpenSection
    .rdata:000000018011CF20                 dq offset ZwMapViewOfSection
    .rdata:000000018011CF28                 dq offset NtWriteVirtualMemory
    .rdata:000000018011CF30                 dq offset ZwResumeThread
    .rdata:000000018011CF38                 dq offset ZwOpenSemaphore
    .rdata:000000018011CF40                 dq offset NtOpenMutant
    .rdata:000000018011CF48                 dq offset NtOpenEvent
    .rdata:000000018011CF50                 dq offset NtCreateUserProcess
    .rdata:000000018011CF58                 dq offset NtCreateSemaphore
    .rdata:000000018011CF60                 dq offset NtCreateMutant
    .rdata:000000018011CF68                 dq offset ZwCreateEvent
    .rdata:000000018011CF70                 dq offset NtSetDriverEntryOrder
    .rdata:000000018011CF78                 dq offset NtQueryDriverEntryOrder
    .rdata:000000018011CF80                 dq offset ZwAccessCheck
    .rdata:000000018011CF88                 dq offset NtCreateThread
    .rdata:000000018011CF90                 dq offset NtCreateThreadEx
    .rdata:000000018011CF98                 dq offset NtCreateProcess
    .rdata:000000018011CFA0                 dq offset ZwCreateProcessEx
    .rdata:000000018011CFA8                 dq offset ZwCreateJobObject
    .rdata:000000018011CFB0                 dq offset ZwCreateEnclave
    .rdata:000000018011CFB8                 dq offset ZwOpenThread
    .rdata:000000018011CFC0                 dq offset NtOpenProcess
    .rdata:000000018011CFC8                 dq offset NtOpenJobObject
    .rdata:000000018011CFD0                 dq offset NtSetInformationThread
    .rdata:000000018011CFD8                 dq offset NtSetInformationProcess
    .rdata:000000018011CFE0                 dq offset NtDeleteFile
    .rdata:000000018011CFE8                 dq offset ZwCreateFile
    .rdata:000000018011CFF0                 dq offset ZwMapViewOfSectionEx
    .rdata:000000018011CFF8                 dq offset NtExtendSection
    

    2.3 LdrpMapAndSnapDependency

    这个函数是用来加载导入表中依赖的Dll使用的,在这个函数中我们发现如下的代码:

    LdrpMapAndSnapDependency(LDRP_LOAD_CONTEXT* LoadContext)
    {
        LDR_DATA_TABLE_ENTRY* Entry = LoadContext->CurEntry; 
        if (LoadContext->pvIAT)
        {
            Entry->DdagNode->State = LdrModulesSnapping; 
            if (LoadContext->PrevEntry)
            { 
                LdrpQueueWork(LoadContext); 
            } 
            else
            { 
                status = LdrpSnapModule(LoadContext); 
            } 
        }
        else 
        {
            Entry->DdagNode->State = LdrModulesSnapped; 
        }
    }
    

    在这里有个判断if (LoadContext->PrevEntry),在这个判断中,如果是第一次调用LdrpMapAndSnapDependency那么LoadContext->PrevEntry==NULL成立,直接调用LdrpSnapModule加载,如果是递归调用LdrpMapAndSnapDependency加载导入表,那么将加载信息放入队列中,放入队列的代码如下:

    struct _PEB *__fastcall LdrpQueueWork(PVOID a1)
    {
      struct _PEB *result; // rax
      _LIST_ENTRY *v2; // rbx
      _LIST_ENTRY *v3; // rcx
      _LIST_ENTRY *v4; // rax
      __int64 v5; // rdx
      __int64 v6; // r8
    
      result = (struct _PEB *)*((_QWORD *)a1 + 5);
      v2 = (_LIST_ENTRY *)a1;
      if ( *(_DWORD *)&result->InheritedAddressSpace < 0 )
        return result;
      RtlEnterCriticalSection(&LdrpWorkQueueLock);
      v3 = LdrpWorkQueue.Blink;
      v4 = v2 + 4;
      if ( LdrpWorkQueue.Blink->Flink != &LdrpWorkQueue )
        __fastfail(3u);
      v2[4].Blink = LdrpWorkQueue.Blink;
      v4->Flink = &LdrpWorkQueue;
      v3->Flink = v4;
      LdrpWorkQueue.Blink = v2 + 4;
      result = (struct _PEB *)RtlLeaveCriticalSection(&LdrpWorkQueueLock);
      if ( !LdrpMapAndSnapWork )
        return result;
      result = NtCurrentPeb();
      if ( !result->Ldr->ShutdownInProgress )
        result = (struct _PEB *)TpPostWork((_PEB_LDR_DATA *)LdrpMapAndSnapWork, v5, v6);
      return result;
    }
    

    2.4 LdrpWorkCallback

    这个是加载的线程池函数,这个线程池函数的代码如下:

    void LdrpWorkCallback()
    {
      _LIST_ENTRY *FEntry; // rbx
      _LIST_ENTRY *FFEntry; // rax
    
      if ( !LdrpDetourExist )
      {
        RtlEnterCriticalSection(&LdrpWorkQueueLock);
        FEntry = LdrpWorkQueue.Flink;
        FFEntry = LdrpWorkQueue.Flink->Flink;
        if ( LdrpWorkQueue.Flink->Blink != &LdrpWorkQueue || FFEntry->Blink != LdrpWorkQueue.Flink )
          __fastfail(3u);
        LdrpWorkQueue.Flink = LdrpWorkQueue.Flink->Flink;
        FFEntry->Blink = &LdrpWorkQueue;
        if ( &LdrpWorkQueue != FEntry )
        {
          ++LdrpWorkInProgress;
          LdrpUpdateStatistics();
        }
        RtlLeaveCriticalSection(&LdrpWorkQueueLock);
        if ( &LdrpWorkQueue != FEntry )
          LdrpProcessWork((LDRP_LOAD_CONTEXT *)&FEntry[-4], 0);
      }
    }
    

    这个代码其实比较明了,就是从LdrpWorkQueue队列中取出一个节点,然后调用LdrpProcessWork加载,当然还会在中间更新统计信息。

    2.5 LdrpProcessWork

    这个函数的伪代码如下:

    __int64 __fastcall LdrpProcessWork(LDRP_LOAD_CONTEXT *LoadContext, BOOLEAN LoadOwner)
    {
      __int64 *v3; // rbx
      __int64 result; // rax
      int v5; // edi
      int v6; // eax
      __int64 v7; // rax
      int CurWorkInProgress; // eax
      char bSetEvent; // bl
      const char *v10; // [rsp+20h] [rbp-38h]
    
      v3 = (__int64 *)LoadContext;
      result = (__int64)LoadContext->ParentEntry;
      if ( *(_DWORD *)result < 0 )
        goto LABEL_21;
      if ( LODWORD(LoadContext->WorkQueueListEntry.Flink[9].Blink[3].Blink) )
      {
        result = LdrpSnapModule((__int64)LoadContext);
        v5 = result;
      }
      else
      {
        if ( (_QWORD)LoadContext->pstatus & 0x100000 )
        {
          v5 = LdrpMapDllRetry(LoadContext);
        }
        else
        {
          if ( (_QWORD)LoadContext->pstatus & 0x200 )
            v6 = LdrpMapDllFullPath(LoadContext);
          else
            v6 = LdrpMapDllSearchPath(LoadContext);
          v5 = v6;
        }
        result = v5 + 2147483648;
        if ( (signed int)result < 0 || v5 == -1073741267 )
          goto LABEL_21;
        result = (unsigned int)LdrpDebugFlags;
        if ( LdrpDebugFlags & 3 )
        {
          v10 = "Unable to load DLL: \"%wZ\", Parent Module: \"%wZ\", Status: 0x%x\n";
          LdrpLogDbgPrint((__int64)"minkernel\\ntdll\\ldrmap.c", 97);
          result = (unsigned int)LdrpDebugFlags;
        }
        if ( result & 0x10 )
          __debugbreak();
        if ( v5 == -1073741515 )
        {
          LdrpLogError(3221225781i64, 25i64, 0i64, v3, v10);
          LdrpLogDeprecatedDllEtwEvent(v3);
          v7 = v3[6];
          LdrpLogLoadFailureEtwEvent((char)v3);
          result = v3[7];
          if ( *(_BYTE *)(result + 104) & 0x20 )
            result = LdrpReportError(v3, 0i64, 3221225781i64);
        }
      }
      if ( v5 < 0 )
      {
        result = v3[5];
        *(_DWORD *)result = v5;
      }
    LABEL_21:
      if ( LoadOwner )
        return result;
      RtlEnterCriticalSection(&LdrpWorkQueueLock);
      CurWorkInProgress = LdrpWorkInProgress-- - 1;
      if ( LdrpWorkQueue.Flink != &LdrpWorkQueue || (bSetEvent = 1, CurWorkInProgress != 1) )
        bSetEvent = 0;
      result = RtlLeaveCriticalSection(&LdrpWorkQueueLock);
      if ( bSetEvent )
        result = ZwSetEvent(LdrpWorkCompleteEvent, 0i64);
      return result;
    }
    

    其实就是调用LdrpSnapModule加载Dll,但是这里有个比较重要的设置就是ZwSetEvent(LdrpWorkCompleteEvent, 0i64);,从上面代码我们可以比较容易的发现,当队列中的Dll全部加载完成之后,就会设置Worker完成的事件。

    2.6 LdrpDrainWorkQueue

    除了Worker线程会加载处理队列之外,LoadOwner线程也会处理队列,比如LoadLibrary的发起线程也会处理这个队列,如下:

    struct _TEB *__fastcall LdrpDrainWorkQueue(DRAIN_TASK Task)
    {
      __int64 hEvent; // r14
      char v2; // si
      DRAIN_TASK v3; // edi
      BOOLEAN v4; // bp
      LIST_ENTRY *v5; // rbx
      _LIST_ENTRY *v6; // rax
      struct _TEB *result; // rax
      _LIST_ENTRY *v8; // rax
      _LIST_ENTRY *v9; // rax
    
      hEvent = LdrpWorkCompleteEvent;
      v2 = 0;
      v3 = Task;
      if ( Task == WaitLoadComplete )
        hEvent = LdrpLoadCompleteEvent;
      while ( 1 )
      {
        while ( 1 )
        {
          RtlEnterCriticalSection(&LdrpWorkQueueLock);
          v4 = LdrpDetourExist;
          if ( LdrpDetourExist && v3 != 1 )
          {
            if ( LdrpWorkInProgress == v3 )
            {
              LdrpWorkInProgress = 1;
              v2 = 1;
            }
            v5 = &LdrpWorkQueue;
          }
          else
          {
            v5 = LdrpWorkQueue.Flink;
            if ( LdrpWorkQueue.Flink->Blink != &LdrpWorkQueue
              || (v6 = LdrpWorkQueue.Flink->Flink, LdrpWorkQueue.Flink->Flink->Blink != LdrpWorkQueue.Flink) )
            {
              __fastfail(3u);
            }
            LdrpWorkQueue.Flink = LdrpWorkQueue.Flink->Flink;
            v6->Blink = &LdrpWorkQueue;
            if ( &LdrpWorkQueue == v5 )
            {
              if ( LdrpWorkInProgress == v3 )
              {
                LdrpWorkInProgress = 1;
                v2 = 1;
              }
            }
            else
            {
              if ( !v4 )
                ++LdrpWorkInProgress;
              LdrpUpdateStatistics();
            }
          }
          RtlLeaveCriticalSection(&LdrpWorkQueueLock);
          if ( v2 )
            break;
          if ( &LdrpWorkQueue == v5 )
            NtWaitForSingleObject(hEvent, 0i64, 0i64);
          else
            LdrpProcessWork((LDRP_LOAD_CONTEXT *)&v5[-4], v4);
        }
        if ( v3 == WaitLoadComplete || LdrpRetryQueue.Flink == &LdrpRetryQueue )
          break;
        RtlEnterCriticalSection(&LdrpWorkQueueLock);
        v8 = LdrpRetryQueue.Flink;
        LdrpRetryQueue.Flink->Blink = &LdrpWorkQueue;
        LdrpWorkQueue.Flink = v8;
        v9 = LdrpRetryQueue.Blink;
        LdrpRetryQueue.Blink->Flink = &LdrpWorkQueue;
        LdrpWorkQueue.Blink = v9;
        LdrpRetryQueue.Blink = &LdrpRetryQueue;
        LdrpRetryQueue.Flink = &LdrpRetryQueue;
        _mm_storeu_si128((__m128i *)&LdrpRetryingModuleIndex, (__m128i)0i64);
        RtlLeaveCriticalSection(&LdrpWorkQueueLock);
        v2 = 0;
      }
      result = NtCurrentTeb();
      result->SameTebFlags |= 0x1000u;
      return result;
    }
    

    3. 线程的退出

    上面我们以及分析了并行加载的所有技术细节点,但是要解决文章刚开始的问题还是有点疑惑的,这要我们先熟悉一下线程退出的流程,代码如下:

    void __fastcall __noreturn RtlExitUserThread(unsigned int a1)
    {
      unsigned int v1; // ebx
      struct _RTLP_FLS_CONTEXT *v2; // rcx
      int ThreadAmILastThread; // [rsp+48h] [rbp+10h]
    
      ThreadAmILastThread = 0;
      v1 = a1;
      if ( (signed int)ZwQueryInformationThread((HANDLE)0xFFFFFFFFFFFFFFFEi64, 12u, &ThreadAmILastThread, 4u, 0i64) < 0// ThreadAmILastThread
        || !ThreadAmILastThread )
      {
        LdrShutdownThread(v2);
        TpCheckTerminateWorker(0i64);
        NtTerminateThread(0i64, v1);
      }
      RtlExitUserProcess(v1);
      JUMPOUT(*(_QWORD *)&byte_18004CF45);
    }
    

    这里我们判断,如果不是最后一个线程,那么调用NtTerminateThread结束线程自己,但是在结束线程的时候有一个操作就是LdrShutdownThread,在这个函数中存在如下流程:

    __int64 __fastcall LdrShutdownThread(struct _RTLP_FLS_CONTEXT *a1)
    {
        //...
        // SkipThreadAttach  RanProcessInit 
        if ( (!(NtCurrentTeb()->SameTebFlags & 8) || NtCurrentTeb()->SameTebFlags & 0x20) && !(v1->SameTebFlags & 0x2000) )// LoaderWorker 
        {
            if ( NtCurrentTeb()->SameTebFlags & 0x1000 )// LoadOwner 
            {
                v4 = 1;
            }
            else
            {
                v4 = 0;
                LdrpDrainWorkQueue(0);
            }
        }
        //...
    }
    

    这个函数的几个标记流程我自己也没看清楚,只是这里会调用LdrpDrainWorkQueue等待加载线程全部加载完成,在这个函数中就会等待LdrpLoadCompleteEvent.

    4. 验证

    我们来验证一下并行加载的正确性,我们直接启动一个notepad.exe,并且查看这个dll的加载流程,这个流程如下:

    ModLoad: 00000000`69610000 00000000`6988e000   C:\WINDOWS\SysWOW64\CoreUIComponents.dll
    .  1  Id: 5320.20a8 Suspend: 1 Teb: 00000000`00764000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`69890000 00000000`6992b000   C:\WINDOWS\SysWOW64\CoreMessaging.dll
    .  2  Id: 5320.6e50 Suspend: 1 Teb: 00000000`00767000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`74c30000 00000000`74c59000   C:\WINDOWS\SysWOW64\ntmarta.dll
    .  3  Id: 5320.b84 Suspend: 1 Teb: 00000000`0076a000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`69530000 00000000`6960e000   C:\WINDOWS\SysWOW64\wintypes.dll
    .  2  Id: 5320.6e50 Suspend: 1 Teb: 00000000`00767000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`02710000 00000000`027ee000   C:\WINDOWS\SysWOW64\wintypes.dll
    .  3  Id: 5320.b84 Suspend: 1 Teb: 00000000`0076a000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`04c00000 00000000`04cde000   C:\WINDOWS\SysWOW64\wintypes.dll
    .  1  Id: 5320.20a8 Suspend: 1 Teb: 00000000`00764000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`10000000 00000000`10036000   C:\Windows\LVUAAgentInstBaseRoot\SysWOW64\OverlordSpear.dll
    .  0  Id: 5320.35d4 Suspend: 1 Teb: 00000000`00761000 Unfrozen
          Start: DllLoad!ILT+2945(_wWinMainCRTStartup) (00000000`00a31b86)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`02730000 00000000`0273e000   C:\WINDOWS\SysWOW64\RsPrnMsg.dll
    .  0  Id: 5320.35d4 Suspend: 1 Teb: 00000000`00761000 Unfrozen
          Start: DllLoad!ILT+2945(_wWinMainCRTStartup) (00000000`00a31b86)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`02730000 00000000`0273e000   C:\WINDOWS\SysWOW64\RsPrnMsg.dll
    .  0  Id: 5320.35d4 Suspend: 1 Teb: 00000000`00761000 Unfrozen
          Start: DllLoad!ILT+2945(_wWinMainCRTStartup) (00000000`00a31b86)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`02770000 00000000`027cd000   C:\WINDOWS\SysWOW64\rsprnsv.dll
    .  0  Id: 5320.35d4 Suspend: 1 Teb: 00000000`00761000 Unfrozen
          Start: DllLoad!ILT+2945(_wWinMainCRTStartup) (00000000`00a31b86)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`02770000 00000000`027cd000   C:\WINDOWS\SysWOW64\rsprnsv.dll
    .  0  Id: 5320.35d4 Suspend: 1 Teb: 00000000`00761000 Unfrozen
          Start: DllLoad!ILT+2945(_wWinMainCRTStartup) (00000000`00a31b86)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`6ce70000 00000000`6cf92000   C:\WINDOWS\SysWOW64\MFC42u.DLL
    .  2  Id: 5320.6e50 Suspend: 1 Teb: 00000000`00767000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    ModLoad: 00000000`6cfa0000 00000000`6d010000   C:\WINDOWS\SysWOW64\MSVCP60.dll
    .  3  Id: 5320.b84 Suspend: 1 Teb: 00000000`0076a000 Unfrozen
          Start: ntdll_77c60000!TppWorkerThread (00000000`77cb0d90)
          Priority: 0  Priority class: 32  Affinity: 3f
    

    5. 问题重现

    明白上述原理之后,我们很容易复现上面遇到的问题,

    #include "stdlib.h"
    
    #include "windows.h"
    
    HANDLE g_hThread = NULL;
    
    int _cdecl Wait()
    {
    	if (g_hThread != NULL)
    	{
    		WaitForSingleObject(g_hThread, INFINITE);
    		CloseHandle(g_hThread);
    		g_hThread = NULL;
    	}
    	return 0;
    }
    
    DWORD WINAPI ThreadStart(LPVOID lpThreadParameter)
    {
    	Sleep(1);
    	return 0;
    }
    
    DWORD WINAPI ThreadLoad(LPVOID lpThreadParameter)
    {
    	HMODULE hDll = LoadLibraryW(L".\\test.dll");
    	return 0;
    }
    
    int main(void)
    {
    	HANDLE hLoadThread = NULL;
    	_onexit(Wait);
    	g_hThread = CreateThread(NULL, 0, ThreadStart, NULL, 0, NULL);
    	hLoadThread = CreateThread(NULL, 0, ThreadLoad, NULL, 0, NULL);
    	if (hLoadThread != NULL)
    	{
    		CloseHandle(hLoadThread);
    		hLoadThread = NULL;
    	}
    	Sleep(10);
    	return 0;
    }
    

    这个代码很简单,主要有一个流程需要注意的是_onexit(Wait),注册一个退出函数,等待线程退出,这个代码从编码规范什么的都没有任何问题,但是一运行就会死锁,如下:

       0  Id: 8454.4238 Suspend: 1 Teb: 00000000`00334000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3d1026ee : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!NtWaitForSingleObject+0x14
    01 00000000`0040157c : 00000000`00401550 00000000`00000000 00000000`00000000 00000000`0000008c : KERNELBASE!WaitForSingleObjectEx+0x8e
    02 00007fff`3e2ca73b : 00000000`00000000 00000000`0000002b 00000000`00000008 00000000`004016b0 : Lock+0x157c
    03 00000000`004014a5 : 00000000`00000008 00000000`0000002b 00000000`00000000 00000000`00000000 : msvcrt!doexit+0xe7
    04 00000000`004014fb : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : Lock+0x14a5
    05 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : Lock+0x14fb
    06 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    07 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       1  Id: 8454.3cf0 Suspend: 1 Teb: 00000000`00336000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4a23d7 : 00000000`00000000 00000000`00000000 00000000`006f42a0 00000000`006f6340 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
    01 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
    02 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       2  Id: 8454.7cec Suspend: 1 Teb: 00000000`00338000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4c36f2 : 00000000`006f8320 00000000`006f5b00 00007fff`3f5fb3f0 00000000`00000000 : ntdll!NtWaitForSingleObject+0x14
    01 00007fff`3f4c560d : 00000000`00338000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrpDrainWorkQueue+0x15e
    02 00007fff`3f4dcf2e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrShutdownThread+0x9d
    03 00007fff`3ee0703d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlExitUserThread+0x3e
    04 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x1d
    05 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       3  Id: 8454.501c Suspend: 1 Teb: 00000000`0033a000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4d619d : 00730065`0053005c 006e006f`00690073 005c0031`005c0073 0064006e`00690057 : ntdll!NtWaitForAlertByThreadId+0x14
    01 00007fff`3f4d6052 : 00000000`00000000 00000000`00000000 00000000`00f8f578 00007fff`3e31f508 : ntdll!RtlpWaitOnAddressWithTimeout+0x81
    02 00007fff`3f4d5e6d : 00007fff`3e31f500 00000000`00001722 00000000`00000000 00000000`00f8f5b0 : ntdll!RtlpWaitOnAddress+0xae
    03 00007fff`3f4b15b4 : 00007fff`2cbb3230 00000000`00000000 00000000`fffffffa 00000000`00000000 : ntdll!RtlpWaitOnCriticalSection+0xfd
    04 00007fff`3f4b13e2 : 00000000`00000000 00000000`00000000 00007fff`2cbb3230 00000000`00000000 : ntdll!RtlpEnterCriticalSectionContended+0x1c4
    05 00007fff`2cbb0800 : 00007fff`2cbb3230 00007fff`2cbf9690 00000000`00000100 00007fff`2cbf9688 : ntdll!RtlEnterCriticalSection+0x42
    06 00007fff`2cbb0865 : 00000000`00000000 00000000`00b847b0 00000000`006fbcc0 00000000`006f0000 : WINSPOOL!onexit+0x38
    07 00007fff`3e2ca553 : 00000000`00000000 00007fff`2cbf9688 00000000`006fbcc0 00000000`00000000 : WINSPOOL!atexit+0x9
    08 00007fff`2cbb01d0 : 00000000`00000000 00000000`00000000 00007fff`2cbf9690 00000000`00000009 : msvcrt!initterm+0x43
    09 00007fff`2cbb0355 : 00000000`00000001 00000000`00f8fc28 00000000`00000001 00000000`00000000 : WINSPOOL!CRT_INIT+0x1c4
    0a 00007fff`3f4c7b3d : 00007fff`2cba0000 00000000`00000001 00000000`00000000 00000000`7ffe0385 : WINSPOOL!__DllMainCRTStartup+0xc1
    0b 00007fff`3f4f3523 : 00000000`006fcde0 00007fff`2cba0000 00007fff`00000001 00000000`006f94c0 : ntdll!LdrpCallInitRoutine+0x61
    0c 00007fff`3f4f32b6 : 00000000`006fcf30 00000000`006f9400 00000000`00f8fc01 00000000`00000001 : ntdll!LdrpInitializeNode+0x1d3
    0d 00007fff`3f4f333c : 00000000`00f8fc00 00000000`006f7270 00000000`00f8fc28 00000000`00701d30 : ntdll!LdrpInitializeGraphRecurse+0x42
    0e 00007fff`3f4d0d13 : 00000000`00000000 00000000`00000000 00000000`00f8fce0 00000000`00f8fc28 : ntdll!LdrpInitializeGraphRecurse+0xc8
    0f 00007fff`3f4c4ca6 : 00000000`00f8fc28 00000000`00f8fc30 00000000`00f8fc00 00000000`00f8fc30 : ntdll!LdrpPrepareModuleForExecution+0xbf
    10 00007fff`3f4c5500 : 00000000`00f8fc30 00000000`00f8fdd0 00000000`00f8fec0 00000000`00f8fdc0 : ntdll!LdrpLoadDllInternal+0x19a
    11 00007fff`3f4c4464 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : ntdll!LdrpLoadDll+0xa8
    12 00007fff`3d108982 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrLoadDll+0xe4
    13 00000000`004015e6 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!LoadLibraryExW+0x162
    14 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : Lock+0x15e6
    15 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    16 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       4  Id: 8454.371c Suspend: 1 Teb: 00000000`0033c000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4a23d7 : 00000000`00000000 00000000`00000000 00000000`006f42a0 00000000`006f9bc0 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
    01 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
    02 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       5  Id: 8454.8408 Suspend: 1 Teb: 00000000`0033e000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4a23d7 : 00000000`00000000 00000000`00000000 00000000`006f42a0 00000000`006fb320 : ntdll!NtWaitForWorkViaWorkerFactory+0x14
    01 00007fff`3ee07034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x2f7
    02 00007fff`3f4dcec1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
    03 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
    
       6  Id: 8454.2320 Suspend: 1 Teb: 00000000`00340000 Unfrozen
     # RetAddr           : Args to Child                                                           : Call Site
    00 00007fff`3f4c36f2 : 00000000`00340000 00000000`00000000 00007fff`3f5fb3f0 00000000`00333000 : ntdll!NtWaitForSingleObject+0x14
    01 00007fff`3f4c58e3 : 00000000`00000000 00000000`00000000 00000000`00333000 00000000`0000000f : ntdll!LdrpDrainWorkQueue+0x15e
    02 00007fff`3f504855 : 00000000`00000000 00000000`00000000 00000000`00000001 00000000`00000000 : ntdll!LdrpInitializeThread+0x8b
    03 00007fff`3f5047b3 : 00000000`00000000 00007fff`3f490000 00000000`00000000 00000000`00340000 : ntdll!_LdrpInitialize+0x89
    04 00007fff`3f50475e : 00000000`02b9fae0 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrpInitialize+0x3b
    05 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrInitializeThunk+0xe
    

    解释一下这个死锁的流程:

    1. 0号线程占用msvcrt!CrtLock_Exit, 并且等待线程退出。
    2. 6号线程调用LdrpDrainWorkQueue,等待并行线程中的Dll加载完成然后退出。
    3. 3号线程在加载Dll,此时也要加载msvcrt!CrtLock_Exit(大概猜测是DLL_PROCESS_ATTACH操作不能进程退出)。

    达到一个完美的死锁情况,并且上面的代码看起来没有一点问题。

    6. 总结

    Windows 10 为了提升进程的启动速度,引入了并行加载的方案,怎么样避免遇到上述问题呢?我觉得关键在于_onexit(Wait)这个函数,我们知道msvcrt!doexit会占用msvcrt!CrtLock_Exit锁,因此解决这个问题的办法就是在_onexit回调函数中不要死等,否则将有可能带来死锁问题的产生。

    展开全文
  • 收集的部分windows字体文件
  • Windows10安装dlib失败的几点原因 因为学习Python需要,需要安装dlib,踩了许多个坑,尝试了种种方法,那一片红色的报错依然刺眼,发现问题,寻找问题的根源,终于找到解决方法。 1.安装dlib前需要有依赖,cmake...
  • windows10常用64位文件,包括 dwmapi.lib iphlpapi.lib Ole32.lib User32.lib wlanapi.lib
  • 目录 什么是windows ? 手动删除不行吗? 如何正确的“删除”? ...title: windows 10 删除后自动恢复的解决方法 ...tags: windows10, , 删除, 恢复 什么是windows ? 打开我的...

    title: windows 10 删除库后自动恢复的解决方法
    date: 2019-06-09 15:42:58
    tags: windows10, 库, 删除, 恢复


    什么是windows 库?

    打开我的电脑,或者文件夹后,在左侧就会显示:

    库文件

    手动删除不行吗?

    当然不行,它就像牛皮癣一样,具体请看效果图:

    库-牛皮癣

    如何正确的“删除”?

    请看操作图,确切的说,它只是被隐藏了,不会在左侧库里显示,但是“库”还在,操作方法如下:

    隐藏

    转载于:https://www.cnblogs.com/gobyte/p/10993813.html

    展开全文
  • windows 10 系统LCM通信的编译-附件资源
  • windows10 CAD解析libredwg的调用

    千次阅读 2020-05-25 23:11:50
    书接上回,这里主要介绍libredwg的调用,具体安装教程参考:windows10 CAD解析libredwg安装记录。众所周知,我们已经生成静态libredwg.a。但是在windows中无法被vs调用,那用什么调用呢?这里我们采用QT来编写...

    书接上回,这里主要介绍libredwg库的调用,具体安装教程参考:windows10 CAD解析库libredwg安装记录。众所周知,我们已经生成静态库libredwg.a。但是在windows中无法被vs调用,那用什么调用呢?这里我们采用QT来编写c程序进行调用。QT是一个跨平台的语言,而我们编译的libredwg库是使用GUN编译的,所以只能在linux上进行调用,此时可以使用QT来解决这个跨平台问题。

    Windows10上QT的安装教程:win10下,安装Qt5.9.7(一)


    目录

    一、配置Cygwin编译器

    二、创建Cygwin编译器的c工程

    三、配置libredwg库


    一、配置Cygwin编译器

    我们知道,我们是使用Cygwin中的工具进行编译得到的libredwg库,如果采用其他库的话可能会造成程序编译执行失败。而QT中默认的编译器主要有:MSVCMinGW

     所以我们需要添加相应的Cygwin编辑器。具体流程如下:选择“工具->选项->Kits”中进行设置

    (1)点击“添加”,设置Cygwin编辑器

    (2)对编译器命名和配置c/c++的编译器路径,其他可以自己选择或默认设置

    (3)最终我们可以看到一个Cygwin的编译器

    二、创建Cygwin编译器的c工程

    (1)创建纯c的工程

    (2)设置工程名及路径(可以直接下一步)

    (3)选择编译的系统(默认就行)

    (4)选择Cygwin编译器,并下一步,剩下的默认就ok了

    (5)最终的结果如下

    三、配置libredwg库

    (1)将安装好的libredwg库的头文件和lib文件复制到当前工程目录下

    include头文件:C:\cygwin64\usr\local\include

    lib库文件:C:\cygwin64\usr\local\lib

    (2)配置QT静态库

    INCLUDEPATH += D:\你的工程路径\include
    LIBS += \
        D:\你的工程路径\lib\libredwg.a \
        D:\你的工程路径\lib\libredwg.dll.a

    这样,你就可以来调用libredwg库的源代码了。这里贴一下我的测试结果。

    源代码:

    #include <stdio.h>
    #include"dwg.h"
    #include"dwg_api.h"
    
    int load_dwg (char *filename, unsigned int opts)
    {
        BITCODE_BL i;
        int success;
        Dwg_Data dwg;
    
        memset (&dwg, 0, sizeof (Dwg_Data));
        dwg.opts = opts;
        success = dwg_read_file (filename, &dwg);
        printf("%d\n",success);
    
        printf("%d\n",dwg.num_objects);
    
        return 0;
    }
    
    int main()
    {
        char *filename="D:\\QtProjects\\readDWG4\\1.dwg";
        load_dwg(filename,1);
    
        printf("Hello World!\n");
        return 0;
    }
    

    测试结果:

    哈哈哈,是不是很激动,可以操作CAD中的数据了。。。下一篇我们将继续将如何提取CAD中的数据信息 ,敬请期待中吧

    展开全文
  • Windows10中提取的微软雅黑字体,Regular,版权归属微软和北大方正
  • windows10 CAD解析libredwg安装记录

    千次阅读 2019-10-18 10:22:54
    因为项目需要解析CAD,网上搜了下...在windows10上安装libredwg前需要安装cygwin工具,并需要安装相关的依赖。 1、安装cygwin 参考链接:Windows:安装cygwin教程 2、相关依赖 make autoconf automake lib...

    因为项目需要解析CAD,网上搜了下C++解析库中目前libredwg还在更新,且支持最新2018版本的CAD图纸,所以安装试试。

    一、安装环境配置

    在windows10上安装libredwg前需要安装cygwin工具,并需要安装相关的依赖库。

    1、安装cygwin

    参考链接:Windows:安装cygwin教程

    2、相关依赖库

    • make
    • autoconf
    • automake
    • libtool

    选择如下:

    二、libredwg库安装

    1、下载libredwg库

    下载链接:https://github.com/LibreDWG/libredwg.git

    打开Cygwin64 Terminal,记住一定是要在Cygwin64自带的终端上执行。输入下载命令:git clone https://github.com/LibreDWG/libredwg.git

    2、安装libredwg库

    • 进入下载源文件中,这个没话说,cd **就好了
    • 执行安装命令

    ./autogen.sh

     

    这一步没啥说的,一般不会出错。

    ./configure

    出现错误:configure: error: cannot run C compiled programs.

    解决方法:在命令行后加上  --host=x86_64就行,完整的命令为: ./configure --host=x86_64

    最终的结果如下:

    make check

    make install

    三、libredwg库的使用

    最终,我们会在Cygwin安装目录下看到我们安装好的库文件:

    include头文件:C:\cygwin64\usr\local\include

    lib库文件:C:\cygwin64\usr\local\lib

    配置文件:C:\cygwin64\usr\local\lib\pkgconfig

    库信息:C:\cygwin64\usr\local\share\info

     

    展开全文
  • 通过Windows运行,可以使用本地类型实现流。尽管它们用本地代码实现,但看起来类似于.NET类型。然而,它们是有区别的:对于流,Windows运行在名称空间Windows.Storage.Streams中实现自己的类型。其中包含...
  • windows10下python安装Shapely

    千次阅读 2019-10-25 13:26:34
    windows10下安装shapely,使用pip install shapely无法成功安装 在网上寻找了一天的解决方案,什么: conda update --force conda 都不管用,还破坏了自己原有的python环境,各种解决方法都不行。 最后找到...
  • WPF 控件——仿制Windows10的进度条 WPF 控件系列博文地址: WPF 控件——仿制Chrome的ColorPicker WPF 控件——仿制Windows10的进度条 WPF 控件——轮播控件 WPF 控件——带有惯性...
  • 主要介绍了音频处理 windows10下python三方librosa安装方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,439
精华内容 2,975
关键字:

windows10库