liu*_*liu 7 c++ winapi multithreading
这是情况的截图!

我用VS2010创建了一个Visual C++ Win32控制台应用程序.当我启动应用程序时,我发现有四个线程:一个'主线程'和三个工作线程(我没有编写任何代码).
我不知道这三个工作线程来自哪里.
我想知道这三个线程的作用.
提前致谢!
RbM*_*bMm 25
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.LdrpSnapModule在顶级DLL加载过程中为许多DLL执行,并且此过程对于每个DLL都是独立的 - 因此这是并行化的好地方.LdrpSnapModule在大多数情况下,不会加载新的dll,只会将导入绑定到已加载的导出.但是如果导入被解析为转发导出(很少发生) - 将加载新的转发DLL.
一些当前的实施细节:
首先让我们来看看struct _RTL_USER_PROCESS_PARAMETERS新领域 - ULONG LoaderThreads.this LoaderThreads(如果设置为非零)在新进程中启用或禁用"Parallel loader".当我们通过ZwCreateUserProcess创建一个新进程时- 第9个参数是
PRTL_USER_PROCESS_PARAMETERS ProcessParameters.但如果我们使用CreateProcess[Internal]W- 我们不能PRTL_USER_PROCESS_PARAMETERS直接传递- 只STARTUPINFO.RTL_USER_PROCESS_PARAMETERS部分初始化STARTUPINFO,但我们不控制ULONG LoaderThreads,它将始终为零(如果我们不调用ZwCreateUserProcess或设置此例程的钩子)
在新进程初始化阶段LdrpInitializeExecutionOptions被调用(from LdrpInitializeProcess).此例程检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<app name>多个值(如果子键存在 - 通常不存在),包括MaxLoaderThreads(REG_DWORD) - 如果MaxLoaderThreads存在 - 其值将覆盖RTL_USER_PROCESS_PARAMETERS.LoaderThreads.
LdrpCreateLoaderEvents()叫做.此例程必须创建2个全局事件:HANDLE LdrpWorkCompleteEvent, LdrpLoadCompleteEvent;用于同步.
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;
Run Code Online (Sandbox Code Playgroud)
}
LdrpInitializeProcess电话void LdrpDetectDetour().这个名字不言而喻.它不返回值但初始化全局变量BOOLEAN LdrpDetourExist.这个例程首先检查一些加载器关键例程是否被挂钩 - 目前这些是5个例程
如果是 - LdrpDetourExist = TRUE;如果没有上钩 - ThreadDynamicCodePolicyInfo查询 - 完整代码:
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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
LdrpInitializeProcess电话NTSTATUS LdrpEnableParallelLoading (ULONG LoaderThreads)- 如LdrpEnableParallelLoading(ProcessParameters->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;
Run Code Online (Sandbox Code Playgroud)
}
创建一个特殊的加载器线程池 - LdrpThreadPool具有LoaderThreads - 1最大线程.空闲超时设置为30秒(之后线程退出)并分配PTP_WORK LdrpMapAndSnapWork,然后使用void LdrpQueueWork(LDRP_LOAD_CONTEXT* LoadContext)
并行加载器使用的全局变量:
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;
};
Run Code Online (Sandbox Code Playgroud)
遗憾的LDRP_LOAD_CONTEXT是,它不包含在已发布的pdb文件中,因此我的定义仅包含部分名称
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++
}
Run Code Online (Sandbox Code Playgroud)
在TEB.CrossTebFlags中 - 现在存在2个新标志:
USHORT LoadOwner : 01; // 0x1000;
USHORT LoaderWorker : 01; // 0x2000;
Run Code Online (Sandbox Code Playgroud)
最后2位是备用(USHORT SpareSameTebBits : 02; // 0xc000)
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; }
所以如果LoadContext->PrevEntry(假设我们加载user32.dll.在第一次调用时LdrpMapAndSnapDependency LoadContext->PrevEntry将始终为0(当CurEntry指向时user32.dll),但是当我们递归调用LdrpMapAndSnapDependency它依赖时gdi32.dll- PrevEntry将为for user32.dll和CurEntryfor gdi32.dll)我们不直接调用LdrpSnapModule(LoadContext);但是LdrpQueueWork(LoadContext);
LdrpQueueWork 很简单:
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
}
}
Run Code Online (Sandbox Code Playgroud)
}
我们插入LoadContext到LdrpWorkQueue,如果"并行加载器"开始(LdrpMapAndSnapWork != 0),而不是ShutdownInProgress-我们提交的工作装载机池.但即使池未初始化(比如存在弯路) - 也不会出错 - 我们会处理此任务LdrpDrainWorkQueue
在工作线程回调中执行:
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);
}
Run Code Online (Sandbox Code Playgroud)
}
我们只需弹出条目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)
在一般调用LdrpSnapModule(LoadContext)和最后的下一个代码:
if(!LoadOwner){EnterCriticalSection(&LdrpWorkQueueLock); BOOLEAN bSetEvent = --LdrpWorkInProgress == 1 && IsListEmpty(&LdrpWorkQueue); LeaveCriticalSection(&LdrpWorkQueueLock); if(bSetEvent)ZwSetEvent(LdrpWorkCompleteEvent,0); }
所以,如果我们没有LoadOwner(在工作线程中)我们减少LdrpWorkInProgress,如果LdrpWorkQueue是空信号LdrpWorkCompleteEvent(LoadOwner可以等待它)
最后LdrpDrainWorkQueue从LoadOwner(主线程)调用"drain"WorkQueue.它可以能够流行和推到直接执行任务LdrpWorkQueue的LdrpQueueWork,但不是由工作线程POP操作,或因为并行加载器被禁用(在这种情况下LdrpQueueWork也推LDRP_LOAD_CONTEXT,但没有真正发布工作的工作线程),最后等待(如果需要)上LdrpWorkCompleteEvent或LdrpLoadCompleteEvent事件.
枚举DRAIN_TASK {WaitLoadComplete,WaitWorkComplete};
void LdrpDrainWorkQueue(DRAIN_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);
}
}
Run Code Online (Sandbox Code Playgroud)
}
void LdrpDropLastInProgressCount()
{
NtCurrentTeb()->LoadOwner = 0;
EnterCriticalSection(&LdrpWorkQueueLock);
LdrpWorkInProgress = 0;
LeaveCriticalSection(&LdrpWorkQueueLock);
ZwSetEvent(LdrpLoadCompleteEvent);
}
Run Code Online (Sandbox Code Playgroud)