以下是情况的截图!
我使用VS2010创建了一个Visual C ++ Win32控制台应用程序。当我启动应用程序时,我发现有四个线程:一个'主线程'和三个工作线程(我没有写任何代码)。
我不知道这三个工作线程来自哪里 我想知道这三个主题的作用。
提前致谢!
答案 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。
一些当前的实施细节:
首先让我们看一下struct _RTL_USER_PROCESS_PARAMETERS
新字段 - ULONG LoaderThreads
。这个LoaderThreads
(如果设置为非零)启用或禁用"并行加载程序"在新的过程中。当我们通过ZwCreateUserProcess创建一个新流程时,第9个参数就是
PRTL_USER_PROCESS_PARAMETERS ProcessParameters
的。但如果我们使用CreateProcess[Internal]W
- 我们无法直接传递PRTL_USER_PROCESS_PARAMETERS
- 仅STARTUPINFO
。 RTL_USER_PROCESS_PARAMETERS
已从STARTUPINFO
部分初始化,但我们无法控制ULONG LoaderThreads
,并且它始终为零(如果我们不调用ZwCreateUserProcess
或设置此例程的挂钩)
在新流程初始化阶段LdrpInitializeExecutionOptions
被调用(来自LdrpInitializeProcess
)。此例程检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<app name>
有几个值(如果子键存在 - 通常它不存在),包括MaxLoaderThreads
( REG_DWORD
) - 如果{{ 1}}存在 - 其值覆盖MaxLoaderThreads
。
RTL_USER_PROCESS_PARAMETERS.LoaderThreads
被调用。此例程必须创建2个全局事件:LdrpCreateLoaderEvents()
,用于同步。
NTSTATUS LdrpCreateLoaderEvents() { NTSTATUS status = ZwCreateEvent(&amp; LdrpWorkCompleteEvent,EVENT_ALL_ACCESS,0,SynchronizationEvent,TRUE);
HANDLE LdrpWorkCompleteEvent, LdrpLoadCompleteEvent;
}
if (0 <= status)
{
status = ZwCreateEvent(&LdrpLoadCompleteEvent, EVENT_ALL_ACCESS, 0, SynchronizationEvent, TRUE);
}
return status;
来电LdrpInitializeProcess
。这个名字不言而喻。它不返回值,但初始化全局变量void LdrpDetectDetour()
。该例程首先检查是否有一些加载器关键例程被挂钩 - 目前这些例程是5个例程
如果是 - BOOLEAN LdrpDetourExist
如果没有挂钩 - 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;
}
}
}
}
调用LdrpInitializeProcess
- NTSTATUS LdrpEnableParallelLoading (ULONG LoaderThreads)
NTSTATUS LdrpEnableParallelLoading(ULONG LoaderThreads) { LdrpDetectDetour();
LdrpEnableParallelLoading(ProcessParameters->LoaderThreads)
}
创建一个特殊的加载器线程池 - 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
中使用
并行加载程序使用的全局变量:
HANDLE LdrpWorkCompleteEvent,LdrpLoadCompleteEvent; CRITICAL_SECTION LdrpWorkQueueLock; LIST_ENTRY LdrpWorkQueue = {&amp; LdrpWorkQueue,&amp; LdrpWorkQueue};
ULONG LdrpWorkInProgress; BOOLEAN LdrpDetourExist; PTP_POOL LdrpThreadPool;
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;
)
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; }
所以如果LdrpMapAndSnapDependency(LDRP_LOAD_CONTEXT* LoadContext)
(我们加载LoadContext->PrevEntry
。在第一次通话中user32.dll
LdrpMapAndSnapDependency
将始终为0(当CurEntry指向LoadContext->PrevEntry
时),但当我们递归调用user32.dll
时,依赖LdrpMapAndSnapDependency
- gdi32.dll
将用于PrevEntry
而user32.dll
用于CurEntry
)我们不会直接调用{{ 1}}但gdi32.dll
LdrpSnapModule(LoadContext);
只是:
void LdrpQueueWork(LDRP_LOAD_CONTEXT * LoadContext) { if(0&lt; = ctx-&gt; pstatus) { EnterCriticalSection的(安培; LdrpWorkQueueLock);
LdrpQueueWork(LoadContext);
}
我们将LdrpQueueWork
插入 InsertHeadList(&LdrpWorkQueue, &LoadContext->WorkQueueListEntry);
LeaveCriticalSection(&LdrpWorkQueueLock);
if (LdrpMapAndSnapWork && !RtlGetCurrentPeb()->Ldr->ShutdownInProgress)
{
SubmitThreadpoolWork(LdrpMapAndSnapWork);//TpPostWork
}
}
,如果&#34;并行加载器&#34;已启动(LoadContext
)而非LdrpWorkQueue
- 我们将工作提交到装载程序池。但即使池没有初始化(比如存在弯路) - 也不会出错 - 我们在LdrpMapAndSnapWork != 0
:
void LdrpWorkCallback() { if(LdrpDetourExist)返回;
ShutdownInProgress
}
我们只需从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*
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); }
所以,如果我们不void LdrpProcessWork(LDRP_LOAD_CONTEXT* ctx, BOOLEAN LoadOwner)
(在工作线程中),我们会减少LdrpSnapModule(LoadContext)
,如果LoadOwner
为空信号LdrpWorkInProgress
(LdrpWorkQueue
可以等待它)
最后LdrpWorkCompleteEvent
从LoadOwner
(主要线程)调用&#34; drain&#34;工作队列。它可以通过LdrpDrainWorkQueue
弹出并直接执行被LoadOwner
推送到LdrpWorkQueue
的任务,但是没有被工作线程驱动,或者因为并行加载器被禁用(在这种情况下LdrpQueueWork
也推LdrpQueueWork
但不是真正将工作发布到工作线程)并最终在LDRP_LOAD_CONTEXT
或LdrpWorkCompleteEvent
事件上等待(如果需要)。
枚举DRAIN_TASK { WaitLoadComplete,WaitWorkComplete };
void LdrpDrainWorkQueue(DRAIN_TASK任务) { BOOLEAN LoadOwner = FALSE;
LdrpLoadCompleteEvent
}
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);
}
}