gpt4 book ai didi

c++ - 为什么 Win32 控制台应用程序启动时会出现三个意外的工作线程?

转载 作者:行者123 更新时间:2023-12-01 21:38:36 26 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





Why does Windows 10 start extra threads in my program?

(3 个回答)


4年前关闭。




这是情况的截图!

Here is the screenshot!

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

我不知道这三个工作线程是从哪里来的。
我想知道这三个线程的作用。

提前致谢!

最佳答案

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()(内部为新加载的 DLL 调用 LdrpMapAndSnapDependency() - 所以这个过程是递归的)加载其直接(第一级)依赖 DLL。最后,LdrpMapAndSnapDependency() 需要调用 NTSTATUS LdrpSnapModule(LDRP_LOAD_CONTEXT* LoadContext) 将导入绑定(bind)到已加载的 DLL。 LdrpSnapModule() 在顶级 DLL 加载过程中为许多 DLL 执行,并且此过程对于每个 DLL 都是独立的 - 因此这是并行化的好地方。 LdrpSnapModule() 在大多数情况下不会加载新的 DLL,而只会将导入绑定(bind)到已加载的导出。但是,如果导入被解析为转发导出(这种情况很少发生) - 将加载新的转发 DLL。

一些当前的实现细节:

  • 首先,让我们看看 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() 或设置此例程的 Hook )。
  • 在新的进程初始化阶段,调用了 LdrpInitializeExecutionOptions()(来自 LdrpInitializeProcess())。此例程检查 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<app name> 的几个值(如果 <app name> 子项存在 - 通常不存在),包括 MaxLoaderThreads ( REG_DWORD ) - 如果 MaxLoaderThreads 存在 - 其值覆盖 079104
  • RTL_USER_PROCESS_PARAMETERS.LoaderThreads 被调用。此例程必须创建 2 个全局事件: LdrpCreateLoaderEvents() ,用于同步。
    NTSTATUS LdrpCreateLoaderEvents()
    {
    NTSTATUS status = ZwCreateEvent(&LdrpWorkCompleteEvent, EVENT_ALL_ACCESS, 0, SynchronizationEvent, TRUE);

    if (0 <= status)
    {
    status = ZwCreateEvent(&LdrpLoadCompleteEvent, EVENT_ALL_ACCESS, 0, SynchronizationEvent, TRUE);
    }
    return status;
    }
  • HANDLE LdrpWorkCompleteEvent, LdrpLoadCompleteEvent; 调用 LdrpInitializeProcess() 。这个名字不言自明。它不返回值但初始化全局变量 void LdrpDetectDetour() 。这个例程首先检查一些加载器关键例程是否被钩住——目前这些是 5 个例程:
  • NtOpenFile
  • NtCreateSection
  • NtQueryAttributesFile
  • NtOpenSection
  • NtMapViewOfSection

  • 如果是 - BOOLEAN LdrpDetourExist如果未上钩 - 查询 LdrpDetourExist = TRUE; - 完整代码:
    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;
    }
    }
    }
    }
  • ThreadDynamicCodePolicyInfo 调用 LdrpInitializeProcess() - 作为 NTSTATUS LdrpEnableParallelLoading (ULONG LoaderThreads) :
    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;
    }
    创建了一个特殊的加载器线程池 - LdrpEnableParallelLoading(ProcessParameters->LoaderThreads) ,最大线程数为 LdrpThreadPool。空闲超时设置为 30 秒(之后线程退出)并分配 LoaderThreads - 1 ,然后在 PTP_WORK LdrpMapAndSnapWork 中使用。
  • 并行加载器使用的全局变量:
    HANDLE LdrpWorkCompleteEvent, LdrpLoadCompleteEvent;
    CRITICAL_SECTION LdrpWorkQueueLock;
    LIST_ENTRY LdrpWorkQueue = { &LdrpWorkQueue, &LdrpWorkQueue };


    ULONG LdrpWorkInProgress;
    BOOLEAN LdrpDetourExist;
    PTP_POOL LdrpThreadPool;

    PTP_WORK LdrpMapAndSnapWork;

    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;
    };
    不幸的是 void LdrpQueueWork(LDRP_LOAD_CONTEXT* LoadContext) 不包含在已发布的 LDRP_LOAD_CONTEXT 文件中,因此我的定义仅包含部分名称。
    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++
    }
    .pdb - 现在存在 2 个新标志:
    USHORT LoadOwner : 01; // 0x1000;
    USHORT LoaderWorker : 01; // 0x2000;
    最后 2 位是备用的 ( TEB.CrossTebFlags )
  • USHORT SpareSameTebBits : 02; // 0xc000 包含以下代码:
    LDR_DATA_TABLE_ENTRY* Entry = LoadContext->CurEntry;
    if (LoadContext->pvIAT)
    {
    Entry->DdagNode->State = LdrModulesSnapping;
    if (LoadContext->PrevEntry)// if recursive call
    {
    LdrpQueueWork(LoadContext); // !!!
    }
    else
    {
    status = LdrpSnapModule(LoadContext);
    }
    }
    else
    {
    Entry->DdagNode->State = LdrModulesSnapped;
    }
    所以,如果LdrpMapAndSnapDependency(LDRP_LOAD_CONTEXT* LoadContext)(说我们加载LoadContext->PrevEntry在第一次调用user32.dllLdrpMapAndSnapDependency()将始终为0时(LoadContext->PrevEntryCurEntry),但是当我们递归调用user32.dll对于依赖IT LdrpMapAndSnapDependency() - gdi32.dll将成为PrevEntryuser32.dllCurEntry ),我们不直接调用 gdi32.dll 而是 LdrpSnapModule(LoadContext);
  • LdrpQueueWork(LoadContext); 只是:
    void LdrpQueueWork(LDRP_LOAD_CONTEXT* LoadContext)
    {
    if (0 <= ctx->pstatus)
    {
    EnterCriticalSection(&LdrpWorkQueueLock);

    InsertHeadList(&LdrpWorkQueue, &LoadContext->WorkQueueListEntry);

    LeaveCriticalSection(&LdrpWorkQueueLock);

    if (LdrpMapAndSnapWork && !RtlGetCurrentPeb()->Ldr->ShutdownInProgress)
    {
    SubmitThreadpoolWork(LdrpMapAndSnapWork);//TpPostWork
    }
    }
    }
    我们将 LdrpQueueWork() 插入到 LoadContext 并且如果“并行加载器”已启动( LdrpWorkQueue )而不是 LdrpMapAndSnapWork != 0 - 我们将工作提交到加载器池。但是即使池没有初始化(比如因为存在 Detours)——也不会出现错误——我们在 ShutdownInProgress 中处理这个任务。
  • 在工作线程回调中,执行以下操作:
    void LdrpWorkCallback()
    {
    if (LdrpDetourExist) return;

    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);
    }
    }
    我们简单地从 LdrpDrainWorkQueue() 弹出一个条目,将其转换为 LdrpWorkQueue ( LDRP_LOAD_CONTEXT* ) 并调用 CONTAINING_RECORD(Entry, LDRP_LOAD_CONTEXT, WorkQueueListEntry)
  • void LdrpProcessWork(LDRP_LOAD_CONTEXT* LoadContext, BOOLEAN LoadOwner)通常调用 void LdrpProcessWork(LDRP_LOAD_CONTEXT* ctx, BOOLEAN LoadOwner),最后执行下一个代码:
    if (!LoadOwner)
    {
    EnterCriticalSection(&LdrpWorkQueueLock);
    BOOLEAN bSetEvent = --LdrpWorkInProgress == 1 && IsListEmpty(&LdrpWorkQueue);
    LeaveCriticalSection(&LdrpWorkQueueLock);
    if (bSetEvent) ZwSetEvent(LdrpWorkCompleteEvent, 0);
    }
    所以,如果我们不是 LdrpSnapModule(LoadContext)(在工作线程中),我们递减 LoadOwner ,如果 LdrpWorkInProgress 是空的,那么信号 LdrpWorkQueue ( LdrpWorkCompleteEvent 可以等待它)。
  • 最后,从 LoadOwner(主线程)调用 LdrpDrainWorkQueue() 以“耗尽”工作队列。它可以弹出并直接执行由 LoadOwner 推送到 LdrpWorkQueue 的任务,但没有被工作线程弹出或因为并行加载器被禁用(在这种情况下 LdrpQueueWork() 也推送 LdrpQueueWork() 但没有真正将工作发布到工作线程),最后等待(如果需要)在 LDRP_LOAD_CONTEXTLdrpWorkCompleteEvent 事件上。
    enum DRAIN_TASK {
    WaitLoadComplete, WaitWorkComplete
    };

    void LdrpDrainWorkQueue(DRAIN_TASK task)
    {
    BOOLEAN LoadOwner = FALSE;

    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);
    }
    }
    }

  • void LdrpDropLastInProgressCount()
    {
    NtCurrentTeb()->LoadOwner = 0;
    EnterCriticalSection(&LdrpWorkQueueLock);
    LdrpWorkInProgress = 0;
    LeaveCriticalSection(&LdrpWorkQueueLock);
    ZwSetEvent(LdrpLoadCompleteEvent);
    }
  • 关于c++ - 为什么 Win32 控制台应用程序启动时会出现三个意外的工作线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42789199/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com