为什么在Win32控制台应用程序启动时有三个意外的工作线程?

时间:2017-03-14 14:49:28

标签: c++ multithreading winapi

以下是情况的截图!

Here is the screenshot!

我使用VS2010创建了一个Visual C ++ Win32控制台应用程序。当我启动应用程序时,我发现有四个线程:一个'主线程'和三个工作线程(我没有写任何代码)。

我不知道这三个工作线程来自哪里 我想知道这三个主题的作用。

提前致谢!

1 个答案:

答案 0 :(得分:24)

Windows 10实现了一种加载DLL的新方法 - 几个工作线程并行执行(LdrpWorkCallback)。所有Windows 10进程现在都有几个这样的线程。

在win10之前,系统(ntdll.dll)总是在单线程中加载DLL,但从win10开始,这种行为发生了变化。现在"并行装载机"存在于ntdll中。现在,加载任务(NTSTATUS LdrpSnapModule(LDRP_LOAD_CONTEXT* LoadContext))可以在工作线程中执行。几乎每个DLL都有导入(依赖DLL),所以当加载DLL时 - 它的依赖DLL也被加载,这个过程是递归的(依赖DLL有自己的依赖)。

函数void LdrpMapAndSnapDependency(LDRP_LOAD_CONTEXT* LoadContext)遍历当前加载的DLL导入表并通过调用LdrpLoadDependentModule(内部调用LdrpMapAndSnapDependency为新加载的DLL加载其直接(第1级)依赖DLL) - 所以这个过程是递归的)。最后LdrpMapAndSnapDependency需要调用NTSTATUS LdrpSnapModule(LDRP_LOAD_CONTEXT* LoadContext)来将导入绑定到已经加载的DLL。对于顶级DLL加载过程中的许多DLL执行LdrpSnapModule,并且此过程对于每个DLL都是独立的 - 因此这是并行化的好地方。在大多数情况下,LdrpSnapModule不会加载新的dll,而只会绑定导入以从已加载的导出导出。但如果导入被解析为转发导出(很少发生) - 将加载新的转发DLL。

一些当前的实施细节:

  1. 首先让我们看一下struct _RTL_USER_PROCESS_PARAMETERS新字段 - ULONG LoaderThreads。这个LoaderThreads(如果设置为非零)启用或禁用"并行加载程序"在新的过程中。当我们通过ZwCreateUserProcess创建一个新流程时,第9个参数就是 PRTL_USER_PROCESS_PARAMETERS ProcessParameters 的。但如果我们使用CreateProcess[Internal]W - 我们无法直接传递PRTL_USER_PROCESS_PARAMETERS - 仅STARTUPINFORTL_USER_PROCESS_PARAMETERS已从STARTUPINFO部分初始化,但我们无法控制ULONG LoaderThreads,并且它始终为零(如果我们不调用ZwCreateUserProcess或设置此例程的挂钩)

  2. 在新流程初始化阶段LdrpInitializeExecutionOptions被调用(来自LdrpInitializeProcess)。此例程检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<app name>有几个值(如果子键存在 - 通常它不存在),包括MaxLoaderThreads REG_DWORD ) - 如果{{ 1}}存在 - 其值覆盖MaxLoaderThreads

  3. RTL_USER_PROCESS_PARAMETERS.LoaderThreads被调用。此例程必须创建2个全局事件:LdrpCreateLoaderEvents(),用于同步。

    NTSTATUS LdrpCreateLoaderEvents() {     NTSTATUS status = ZwCreateEvent(&amp; LdrpWorkCompleteEvent,EVENT_ALL_ACCESS,0,SynchronizationEvent,TRUE);

    HANDLE LdrpWorkCompleteEvent, LdrpLoadCompleteEvent;

    }

  4. if (0 <= status) { status = ZwCreateEvent(&LdrpLoadCompleteEvent, EVENT_ALL_ACCESS, 0, SynchronizationEvent, TRUE); } return status; 来电LdrpInitializeProcess。这个名字不言而喻。它不返回值,但初始化全局变量void LdrpDetectDetour()。该例程首先检查是否有一些加载器关键例程被挂钩 - 目前这些例程是5个例程

    • &#34; NtOpenFile&#34;,
    • &#34; NtCreateSection&#34;,
    • &#34; NtQueryAttributesFile&#34;,
    • &#34; NtOpenSection&#34;,
    • &#34; NtMapViewOfSection&#34;
  5. 如果是 - BOOLEAN LdrpDetourExist如果没有挂钩 - LdrpDetourExist = TRUE;查询 - 完整代码:

    ThreadDynamicCodePolicyInfo
    1. void LdrpDetectDetour() { if (LdrpDetourExist) return ; static PVOID LdrpCriticalLoaderFunctions[] = { NtOpenFile, NtCreateSection, ZwQueryAttributesFile, ZwOpenSection, ZwMapViewOfSection, }; static M128A LdrpThunkSignature[5] = { //*** }; ULONG n = RTL_NUMBER_OF(LdrpCriticalLoaderFunctions); M128A* ppv = (M128A*)LdrpCriticalLoaderFunctions; M128A* pps = LdrpThunkSignature; do { if (ppv->Low != pps->Low || ppv->High != pps->High) { if (LdrpDebugFlags & 5) { DbgPrint("!!! Detour detected, disable parallel loading\n"); LdrpDetourExist = TRUE; return; } } } while (pps++, ppv++, --n); BOOL DynamicCodePolicy; if (0 <= ZwQueryInformationThread(NtCurrentThread(), ThreadDynamicCodePolicyInfo, &DynamicCodePolicy, sizeof(DynamicCodePolicy), 0)) { if (LdrpDetourExist = (DynamicCodePolicy == 1)) { if (LdrpMapAndSnapWork) { WaitForThreadpoolWorkCallbacks(LdrpMapAndSnapWork, TRUE);//TpWaitForWork TpReleaseWork(LdrpMapAndSnapWork);//CloseThreadpoolWork LdrpMapAndSnapWork = 0; TpReleasePool(LdrpThreadPool);//CloseThreadpool LdrpThreadPool = 0; } } } } 调用LdrpInitializeProcess - NTSTATUS LdrpEnableParallelLoading (ULONG LoaderThreads)

      NTSTATUS LdrpEnableParallelLoading(ULONG LoaderThreads) {     LdrpDetectDetour();

      LdrpEnableParallelLoading(ProcessParameters->LoaderThreads)

      }

    2. 创建一个特殊的加载器线程池 - 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; ,最多LdrpThreadPool个线程。空闲超时设置为30秒(之后线程退出)并分配LoaderThreads - 1,然后在PTP_WORK LdrpMapAndSnapWork中使用

      1. 并行加载程序使用的全局变量:

        HANDLE LdrpWorkCompleteEvent,LdrpLoadCompleteEvent; CRITICAL_SECTION LdrpWorkQueueLock; LIST_ENTRY LdrpWorkQueue = {&amp; LdrpWorkQueue,&amp; LdrpWorkQueue};

        ULONG LdrpWorkInProgress; BOOLEAN LdrpDetourExist; PTP_POOL LdrpThreadPool;

      2. PTP_WORK LdrpMapAndSnapWork;

        void LdrpQueueWork(LDRP_LOAD_CONTEXT* LoadContext)

        遗憾的是enum DRAIN_TASK { WaitLoadComplete, WaitWorkComplete }; struct LDRP_LOAD_CONTEXT { UNICODE_STRING BaseDllName; PVOID somestruct; ULONG Flags;//some unknown flags NTSTATUS* pstatus; //final status of load _LDR_DATA_TABLE_ENTRY* ParentEntry; // of 'parent' loading dll _LDR_DATA_TABLE_ENTRY* Entry; // this == Entry->LoadContext LIST_ENTRY WorkQueueListEntry; _LDR_DATA_TABLE_ENTRY* ReplacedEntry; _LDR_DATA_TABLE_ENTRY** pvImports;// in same ordef as in IMAGE_IMPORT_DESCRIPTOR piid ULONG ImportDllCount;// count of pvImports LONG TaskCount; PVOID pvIAT; ULONG SizeOfIAT; ULONG CurrentDll; // 0 <= CurrentDll < ImportDllCount PIMAGE_IMPORT_DESCRIPTOR piid; ULONG OriginalIATProtect; PVOID GuardCFCheckFunctionPointer; PVOID* pGuardCFCheckFunctionPointer; }; 未包含在已发布的pdb文件中,因此我的定义仅包含部分名称

        LDRP_LOAD_CONTEXT
        TEB.CrossTebFlags中的

        - 现在存在2个新标志:

        struct {
            ULONG MaxWorkInProgress;//4 - values from explorer.exe at some moment
            ULONG InLoaderWorker;//7a (this mean LdrpSnapModule called from worker thread)
            ULONG InLoadOwner;//87 (LdrpSnapModule called direct, in same thread as `LdrpMapAndSnapDependency`)
        } LdrpStatistics;
        
        // for statistics
        void LdrpUpdateStatistics()
        {
          LdrpStatistics.MaxWorkInProgress = max(LdrpStatistics.MaxWorkInProgress, LdrpWorkInProgress);
          NtCurrentTeb()->LoaderWorker ? LdrpStatistics.InLoaderWorker++ : LdrpStatistics.InLoadOwner++
        }
        

        最后2位是备用的(USHORT LoadOwner : 01; // 0x1000; USHORT LoaderWorker : 01; // 0x2000;

        1. USHORT SpareSameTebBits : 02; // 0xc000包含以下代码:

          LDR_DATA_TABLE_ENTRY * Entry = LoadContext-&gt; CurEntry; if(LoadContext-&gt; pvIAT) {     Entry-&gt; DdagNode-&gt; State = LdrModulesSnapping;     if(LoadContext-&gt; PrevEntry)//如果是递归调用     {         LdrpQueueWork(LoadContext); // !!!     }     其他     {         status = LdrpSnapModule(LoadContext);     } } 其他 {     Entry-&gt; DdagNode-&gt; State = LdrModulesSnapped; }

        2. 所以如果LdrpMapAndSnapDependency(LDRP_LOAD_CONTEXT* LoadContext)(我们加载LoadContext->PrevEntry。在第一次通话中user32.dll LdrpMapAndSnapDependency将始终为0(当CurEntry指向LoadContext->PrevEntry时),但当我们递归调用user32.dll时,依赖LdrpMapAndSnapDependency - gdi32.dll将用于PrevEntryuser32.dll用于CurEntry)我们不会直接调用{{ 1}}但gdi32.dll

          1. LdrpSnapModule(LoadContext);只是:

            void LdrpQueueWork(LDRP_LOAD_CONTEXT * LoadContext) {     if(0&lt; = ctx-&gt; pstatus)     {         EnterCriticalSection的(安培; LdrpWorkQueueLock);

            LdrpQueueWork(LoadContext);

            }

          2. 我们将LdrpQueueWork插入 InsertHeadList(&LdrpWorkQueue, &LoadContext->WorkQueueListEntry); LeaveCriticalSection(&LdrpWorkQueueLock); if (LdrpMapAndSnapWork && !RtlGetCurrentPeb()->Ldr->ShutdownInProgress) { SubmitThreadpoolWork(LdrpMapAndSnapWork);//TpPostWork } } ,如果&#34;并行加载器&#34;已启动(LoadContext)而非LdrpWorkQueue - 我们将工作提交到装载程序池。但即使池没有初始化(比如存在弯路) - 也不会出错 - 我们在LdrpMapAndSnapWork != 0

            中处理此任务
              执行工作线程回调中的
            1. void LdrpWorkCallback() {     if(LdrpDetourExist)返回;

              ShutdownInProgress

              }

            2. 我们只需从LdrpDrainWorkQueue弹出条目,将其转换为EnterCriticalSection(&LdrpWorkQueueLock); PLIST_ENTRY Entry = RemoveEntryList(&LdrpWorkQueue); if (Entry != &LdrpWorkQueue) { ++LdrpWorkInProgress; LdrpUpdateStatistics() } LeaveCriticalSection(&LdrpWorkQueueLock); if (Entry != &LdrpWorkQueue) { LdrpProcessWork(CONTAINING_RECORD(Entry, LDRP_LOAD_CONTEXT, WorkQueueListEntry), FALSE); } LdrpWorkQueue)并致电LDRP_LOAD_CONTEXT*

              1. CONTAINING_RECORD(Entry, LDRP_LOAD_CONTEXT, WorkQueueListEntry) 通常调用void LdrpProcessWork(LDRP_LOAD_CONTEXT* LoadContext, BOOLEAN LoadOwner),最后调用下一个代码:

                if(!LoadOwner) {     EnterCriticalSection的(安培; LdrpWorkQueueLock);     BOOLEAN bSetEvent = --LdrpWorkInProgress == 1&amp;&amp; IsListEmpty(安培; LdrpWorkQueue);     LeaveCriticalSection(安培; LdrpWorkQueueLock);     if(bSetEvent)ZwSetEvent(LdrpWorkCompleteEvent,0); }

              2. 所以,如果我们不void LdrpProcessWork(LDRP_LOAD_CONTEXT* ctx, BOOLEAN LoadOwner)(在工作线程中),我们会减少LdrpSnapModule(LoadContext),如果LoadOwner为空信号LdrpWorkInProgressLdrpWorkQueue可以等待它)

                1. 最后LdrpWorkCompleteEventLoadOwner(主要线程)调用&#34; drain&#34;工作队列。它可以通过LdrpDrainWorkQueue弹出并直接执行被LoadOwner推送到LdrpWorkQueue的任务,但是没有被工作线程驱动,或者因为并行加载器被禁用(在这种情况下LdrpQueueWork也推LdrpQueueWork但不是真正将工作发布到工作线程)并最终在LDRP_LOAD_CONTEXTLdrpWorkCompleteEvent事件上等待(如果需要)。

                  枚举DRAIN_TASK {     WaitLoadComplete,WaitWorkComplete };

                  void LdrpDrainWorkQueue(DRAIN_TASK任务) {     BOOLEAN LoadOwner = FALSE;

                  LdrpLoadCompleteEvent

                  }

                2. 12。

                  HANDLE hEvent = task ? LdrpWorkCompleteEvent : LdrpLoadCompleteEvent;
                  
                  for(;;)
                  {
                      PLIST_ENTRY Entry;
                  
                      EnterCriticalSection(&LdrpWorkQueueLock);
                  
                      if (LdrpDetourExist && task == WaitLoadComplete)
                      {
                          if (!LdrpWorkInProgress)
                          {
                              LdrpWorkInProgress = 1;
                              LoadOwner = TRUE;
                          }
                          Entry = &LdrpWorkQueue;
                      }
                      else
                      {
                          Entry = RemoveHeadList(&LdrpWorkQueue);
                  
                          if (Entry == &LdrpWorkQueue)
                          {
                              if (!LdrpWorkInProgress)
                              {
                                  LdrpWorkInProgress = 1;
                                  LoadOwner = TRUE;
                              }
                          }
                          else
                          {
                              if (!LdrpDetourExist)
                              {
                                  ++LdrpWorkInProgress;
                              }
                              LdrpUpdateStatistics();
                          }
                      }
                      LeaveCriticalSection(&LdrpWorkQueueLock);
                  
                      if (LoadOwner)
                      {
                          NtCurrentTeb()->LoadOwner = 1;
                          return;
                      }
                  
                      if (Entry != &LdrpWorkQueue)
                      {
                          LdrpProcessWork(CONTAINING_RECORD(Entry, LDRP_LOAD_CONTEXT, WorkQueueListEntry), FALSE);
                      }
                      else
                      {
                          ZwWaitForSingleObject(hEvent, 0, 0);
                      }
                  }