我在之前的博客里并未提及过Tls的底层初始化,现在好好来谈谈。
首先我们要知道Tls的初始化是在入口之前。先从一开始的LdrInitializeThunk说起,在调用LdrPEStartup时,其在修复完导入表后,调用了一次LdrpInitializeTlsForProccess,我们看看他是怎样处理的。
static NTSTATUS
LdrpInitializeTlsForProccess(VOID)
{
PLIST_ENTRY ModuleListHead;
PLIST_ENTRY Entry;
PLDR_DATA_TABLE_ENTRY Module;
PIMAGE_TLS_DIRECTORY TlsDirectory;
PTLS_DATA TlsData;
ULONG Size;
DPRINT("LdrpInitializeTlsForProccess() called for %wZn", &ExeModule->BaseDllName);
if (LdrpTlsCount > 0)
{
LdrpTlsArray = RtlAllocateHeap(RtlGetProcessHeap(),
0,
LdrpTlsCount * sizeof(TLS_DATA));//分配了一个容纳所有Tls变量的空间,基于线程
if (LdrpTlsArray == NULL)
{
DPRINT1("Failed to allocate global tls data\n");
return STATUS_NO_MEMORY;
}
ModuleListHead = &NtCurrentPeb()->Ldr->InLoadOrderModuleList;
Entry = ModuleListHead->Flink;
while (Entry != ModuleListHead)//以装载顺序遍历
{
Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (Module->LoadCount == LDRP_PROCESS_CREATION_TIME &&
Module->TlsIndex != 0xFFFF)
{
TlsDirectory = (PIMAGE_TLS_DIRECTORY)//获得TLS目录表
RtlImageDirectoryEntryToData(Module->DllBase,
TRUE,
IMAGE_DIRECTORY_ENTRY_TLS,
&Size);
ASSERT(Module->TlsIndex < LdrpTlsCount);
TlsData = &LdrpTlsArray[Module->TlsIndex];//TlsData存放的是记录着该模块Tls信息的指针,其index在TlsIndex里
TlsData->StartAddressOfRawData = (PVOID)TlsDirectory->StartAddressOfRawData;//对该模块的信息进行填充
TlsData->TlsDataSize = TlsDirectory->EndAddressOfRawData - TlsDirectory->StartAddressOfRawData;
TlsData->TlsZeroSize = TlsDirectory->SizeOfZeroFill;
if (TlsDirectory->AddressOfCallBacks)//如果存在TLS回调函数
TlsData->TlsAddressOfCallBacks = (PIMAGE_TLS_CALLBACK *)TlsDirectory->AddressOfCallBacks;//放入到其中,TlsAddressOfCallBacks数组是函数指针数组
else
TlsData->TlsAddressOfCallBacks = NULL;
TlsData->Module = Module;
#if 0
DbgPrint("TLS directory for %wZn", &Module->BaseDllName);
DbgPrint("StartAddressOfRawData: %xn", TlsDirectory->StartAddressOfRawData);
DbgPrint("EndAddressOfRawData: %xn", TlsDirectory->EndAddressOfRawData);
DbgPrint("SizeOfRawData: %dn", TlsDirectory->EndAddressOfRawData - TlsDirectory->StartAddressOfRawData);
DbgPrint("AddressOfIndex: %xn", TlsDirectory->AddressOfIndex);
DbgPrint("AddressOfCallBacks: %xn", TlsDirectory->AddressOfCallBacks);
DbgPrint("SizeOfZeroFill: %dn", TlsDirectory->SizeOfZeroFill);
DbgPrint("Characteristics: %xn", TlsDirectory->Characteristics);
#endif
/*
* FIXME:
* Is this region allways writable ?
*/
*(PULONG)TlsDirectory->AddressOfIndex = Module->TlsIndex;
}
Entry = Entry->Flink;
}
}
DPRINT("LdrpInitializeTlsForProccess() donen");
return STATUS_SUCCESS;
}
/*
以加载模块的顺序,读取TLS目录,将信息填入到LdrpTlsArray[Module->TlsIndex]指定的索引结构中,其中Module中的索引值起到决定作用,之后内核就可以使用LdrpTlsArray和index来使用TLS
*/
以加载模块的顺序,读取TLS目录,将信息填入到LdrpTlsArray[Module->TlsIndex]指定的索引结构中,其中Module中的索引值起到决定作用,之后内核就可以使用LdrpTlsArray和index来使用TLS。这里就是为内核操作TLS提供了保障,TLS信息被收录到LdrpTlsArray中。
再来看下LdrpAttachThread(),这个是在LdrInitializeThunk中最后被使用的。
NTSTATUS
LdrpAttachThread (VOID)
{
PLIST_ENTRY ModuleListHead;
PLIST_ENTRY Entry;
PLDR_DATA_TABLE_ENTRY Module;
NTSTATUS Status;
DPRINT("LdrpAttachThread() called for %wZn",
&ExeModule->BaseDllName);
RtlEnterCriticalSection (NtCurrentPeb()->LoaderLock);//加锁
Status = LdrpInitializeTlsForThread();//下发到LdrpInitializeTlsForThread
if (NT_SUCCESS(Status))
{
ModuleListHead = &NtCurrentPeb()->Ldr->InInitializationOrderModuleList;
Entry = ModuleListHead->Flink;
while (Entry != ModuleListHead)//以初始化顺序遍历模块
{
Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InInitializationOrderModuleList);
if (Module->Flags & LDRP_PROCESS_ATTACH_CALLED &&
!(Module->Flags & LDRP_DONT_CALL_FOR_THREADS) &&
!(Module->Flags & LDRP_UNLOAD_IN_PROGRESS))
{//若DLL_PROCESS_ATTACH被调用了,现在轮到DLL_THREAD_ATTACH
TRACE_LDR("%wZ - Calling entry point at %x for thread attachingn",
&Module->BaseDllName, Module->EntryPoint);
LdrpCallDllEntry(Module, DLL_THREAD_ATTACH, NULL);
}
Entry = Entry->Flink;
}
Entry = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink;
Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
LdrpTlsCallback(Module, DLL_THREAD_ATTACH);
}
RtlLeaveCriticalSection (NtCurrentPeb()->LoaderLock);
DPRINT("LdrpAttachThread() donen");
return Status;
}
/*
LdrpInitializeTlsForThread
LdrpCallDllEntry
LdrpTlsCallback
*/
LdrpAttachThread->LdrpInitializeTlsForThread->LdrpCallDllEntry->LdrpTlsCallback
它将初始化工作交给LdrpInitializeTlsForThread,然后发出dwReason == DLL_THREAD_ATTACH,调用线程的TLS初始化,再来看下LdrpInitializeTlsForThread。
static NTSTATUS
LdrpInitializeTlsForThread(VOID)
{
PVOID* TlsPointers;
PTLS_DATA TlsInfo;
PVOID TlsData;
ULONG i;
PTEB Teb = NtCurrentTeb();
DPRINT("LdrpInitializeTlsForThread() called for %wZn", &ExeModule->BaseDllName);
Teb->StaticUnicodeString.Length = 0;
Teb->StaticUnicodeString.MaximumLength = sizeof(Teb->StaticUnicodeBuffer);
Teb->StaticUnicodeString.Buffer = Teb->StaticUnicodeBuffer;
if (LdrpTlsCount > 0)//若存在线程局部变量
{
TlsPointers = RtlAllocateHeap(RtlGetProcessHeap(),
0,
LdrpTlsCount * sizeof(PVOID) + LdrpTlsSize);
if (TlsPointers == NULL)
{
DPRINT1("failed to allocate thread tls datan");
return STATUS_NO_MEMORY;
}
TlsData = (PVOID)((ULONG_PTR)TlsPointers + LdrpTlsCount * sizeof(PVOID));//移到离边界还有LdrpTlsSize处
Teb->ThreadLocalStoragePointer = TlsPointers;
TlsInfo = LdrpTlsArray;//将之前的array赋值给TlsInfo array存放的是指针数组,指向的是每一个模块的目录信息
for (i = 0; i < LdrpTlsCount; i++, TlsInfo++)//将对应的每一个模块的TLS目录指定的StartAddressOfRawData开始处的大小为TlsDataSize的数据拷贝到ThreadLocalStoragePointer处
{
TRACE_LDR("Initialize tls data for %wZn", &TlsInfo->Module->BaseDllName);
TlsPointers[i] = TlsData;
if (TlsInfo->TlsDataSize)
{
memcpy(TlsData, TlsInfo->StartAddressOfRawData, TlsInfo->TlsDataSize);
TlsData = (PVOID)((ULONG_PTR)TlsData + TlsInfo->TlsDataSize);
}
if (TlsInfo->TlsZeroSize)
{
memset(TlsData, 0, TlsInfo->TlsZeroSize);
TlsData = (PVOID)((ULONG_PTR)TlsData + TlsInfo->TlsZeroSize);
}
}
}
DPRINT("LdrpInitializeTlsForThread() donen");
return STATUS_SUCCESS;
}
真正装载ThreadLocalStoragePointer的是由LdrpInitializeTlsForThread来执行的,可以认为LdrpInitializeTlsForProccess是对LdrpInitializeTlsForThread的初始化,它只是对所有模块TLS目录表的提取。而LdrpInitializeTlsForThread则是对LdrpTlsArray的内容的提取。所以之后只需要NtCurrentTeb()->ThreadLocalStoragePointer来完成Tls变量的存取工作就可以了。
紧接着看下LdrpInitializeTlsForThread初始化ThreadLocalStoragePointer之后LdrpCallDllEntry又干了啥吧
static BOOLEAN LdrpCallDllEntry(PLDR_DATA_TABLE_ENTRY Module, DWORD dwReason, PVOID lpReserved)
{
if (!(Module->Flags & LDRP_IMAGE_DLL) ||//如果不是DLL映像,则退出
Module->EntryPoint == 0)
{
return TRUE;
}
LdrpTlsCallback(Module, dwReason);//线程回调函数
return ((PDLLMAIN_FUNC)Module->EntryPoint)(Module->DllBase, dwReason, lpReserved);//对DLL映像发送DLL_THREAD_ATTACH消息
}
这里是专门针对DLL的,用来发送给DLL运行当DLL被线程附加的时候所进行的必要初始化,不过再这之前会调用TLS的回调函数。并且在LdrpAttachThread也调用了LdrpTlsCallback函数,有必要揭晓最后的秘密了。
static __inline VOID LdrpTlsCallback(PLDR_DATA_TABLE_ENTRY Module, ULONG dwReason)
{
PIMAGE_TLS_CALLBACK *TlsCallback;
if (Module->TlsIndex != 0xFFFF && Module->LoadCount == LDRP_PROCESS_CREATION_TIME)//若存在TLS变量
{
TlsCallback = LdrpTlsArray[Module->TlsIndex].TlsAddressOfCallBacks;//获取该模块相应的回调函数的指针数组
if (TlsCallback)//如果该回调有效
{
while (*TlsCallback)//依次调用所有的回调函数
{
TRACE_LDR("%wZ - Calling tls callback at %xn",
&Module->BaseDllName, *TlsCallback);
(*TlsCallback)(Module->DllBase, dwReason, NULL);//调用该回调函数
TlsCallback++;
}
}
}
}
这里就很简单了,首先获取TlsCallback的函数指针数组,然后依次调用它们。到此,TLS就分析完了,相比于之前,这次可以是相对简单,只要耐心分析源码,有很大收获