LdrInitializeThunk 解析
copy from http://hi.baidu.com/5iprog/item/00ab5e092ecfb1cb75cd3c44
LdrInitializeThunk()
Windows 的 DLL 装入(除 ntdll.dll 外)和连接是通过 ntdll.dll 中的一个函数LdrInitializeThunk()实现的.
在进入这个函数之前,目标 EXE 映像已经被映射到当前进程的用户空间,系统 DLL ntdll.dll 的映像也已经被映射, 但是并没有在 EXE 映像与 ntdll.dll 映像之间建立连接(实际上EXE 映像未必就直接调用 ntdll.dll 中的函数)。
LdrInitializeThunk()是 ntdll.dll 中不经连接就可进入的函数,实质上就是 ntdll.dll 的入口。除 ntdll.dll 以外,别的 DLL 都还没有被装入(映射)。此外,当前进程(除内核中的“进程控制块”EPROCESS 等数据结构外)在用户空间已经有了一个“进程环境块”PEB,以及该进程的第一个“线程环境块”TEB。这就是进入__true_LdrInitializeThunk()前的“当前形势”。
1 VOID STDCALL 2 __true_LdrInitializeThunk (ULONG Unknown1, ULONG Unknown2, 3 ULONG Unknown3, ULONG Unknown4) 4 { 5 . . . . . . 6 7 DPRINT("LdrInitializeThunk()/n"); 8 if (NtCurrentPeb()->Ldr == NULL || NtCurrentPeb()->Ldr->Initialized == FALSE) 9 { 10 Peb = (PPEB)(PEB_BASE); //进程环境块 11 DPRINT("Peb %x/n", Peb); 12 ImageBase = Peb->ImageBaseAddress; //EXE 映像在用户空间的起点 13 . . . . 14 /* Initialize NLS data * //语言本地化有关 15 RtlInitNlsTables (Peb->AnsiCodePageData, Peb->OemCodePageData, 16 Peb->UnicodeCaseTableData, &NlsTable); 17 RtlResetRtlTranslations (&NlsTable); 18 19 NTHeaders = (PIMAGE_NT_HEADERS)(ImageBase + PEDosHeader->e_lfanew); 20 . . . . . . 21 /* create process heap */ 22 //创建一个堆、 及其第一个区块,其初始的大小来自映像头部的建议值,其中 SizeOfHeapReserve 是估 23 计的最大值,SizeOfHeapCommit是初始值,这是在编译/连接时确定的。 24 RtlInitializeHeapManager(); 25 Peb->ProcessHeap = RtlCreateHeap(HEAP_GROWABLE, NULL, 26 NTHeaders->OptionalHeader.SizeOfHeapReserve, 27 NTHeaders->OptionalHeader.SizeOfHeapCommit, 28 NULL, NULL); 29 /* create loader information */ 30 //PEB 中的 ProcessHeap 字段指向本进程用户空间可动态分配的内存区块“堆” 31 Peb->Ldr = (PPEB_LDR_DATA)RtlAllocateHeap (Peb->ProcessHeap, 32 0, 33 sizeof(PEB_LDR_DATA)); 34 . . . . . . 35 /* PEB 中的另一个字段 Ldr是个 PEB_LDR_DATA 结构指针,所指向的数据结构用来为本进程维持三个“模块” 36 队列、即 InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList。 37 所谓“模块”就是 PE 格式的可执行映像,包括 EXE映像和 DLL 映像. 38 两个模块队列的不同之处在于排列的次序,一个是按装入的先后,一个是按装入的位置(实际上目前ReactOS 39 的代码中并未使用这个队列)。 40 每当为本进程装入一个模块、即.exe 映像或 DLL 映像时,就要为其分配/创建一个LDR_MODULE 数据结构, 41 并将其挂入 InLoadOrderModuleList。然后,完成对这个模块的动态连接以后,就把它挂入 42 InInitializationOrderModuleList 队列.LDR_MODULE 数据结构中有三个队列头,因而可以同时挂在三个队列 43 中。 44 Peb->Ldr->Length = sizeof(PEB_LDR_DATA); 45 Peb->Ldr->Initialized = FALSE; 46 Peb->Ldr->SsHandle = NULL; 47 InitializeListHead(&Peb->Ldr->InLoadOrderModuleList); 48 InitializeListHead(&Peb->Ldr->InMemoryOrderModuleList); 49 InitializeListHead(&Peb->Ldr->InInitializationOrderModuleList); 50 51 . . . . . . 52 /* add entry for ntdll */ 53 NtModule = (PLDR_MODULE)RtlAllocateHeap (Peb->ProcessHeap, 54 0, 55 sizeof(LDR_MODULE)); 56 . . . . . . 57 InsertTailList(&Peb->Ldr->InLoadOrderModuleList, 58 &NtModule->InLoadOrderModuleList); 59 InsertTailList(&Peb->Ldr->InInitializationOrderModuleList, 60 &NtModule->InInitializationOrderModuleList); 61 . . . . . . 62 /* add entry for executable (becomes first list entry) */ 63 ExeModule = (PLDR_MODULE)RtlAllocateHeap (Peb->ProcessHeap, 64 0, 65 sizeof(LDR_MODULE)); 66 . . . . . . 67 InsertHeadList(&Peb->Ldr->InLoadOrderModuleList, 68 &ExeModule->InLoadOrderModuleList); 69 . . . . . . 70 /*当 CPU从 LdrPEStartup()返回时,EXE 对象需要直接或间接引入的所有 DLL 均已映射到用户空间并已完成连 71 接,对 EXE 模块的“启动” 、即初始化也已完成。 72 注意在调用 LdrPEStartup()时的参数 ImageBase 是目标 EXE 映像在用户空间的位置 73 EntryPoint = LdrPEStartup((PVOID)ImageBase, NULL, NULL, NULL); 74 . . . . . . 75 } 76 /* attach the thread */ 77 RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock); 78 //目的是调用各个 DLL 的初始化过程,以及对 TLS、即“线程本地存储(Thread Local Storage)”的初始化 79 //TLS:有时候又确实需要让每个线程都对于同一个全局变量有一份自己的拷贝,TLS就是为此而设的 80 LdrpAttachThread(); 81 RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock); 82 } 83 84 85 //注意在调用 LdrPEStartup()时的参数 ImageBase 是目标 EXE 映像在用户空间的位置 86 PEPFUNC LdrPEStartup (PVOID ImageBase, HANDLE SectionHandle, 87 PLDR_MODULE* Module, PWSTR FullDosName) 88 { 89 //PE 映像的 NtHeader 中有个指针,指向一个 OptionalHeader。说是“Optional”,实际上却是关键性的。在 90 //OptionalHeader中有个字段 ImageBase,是具体映像建议、或者说希望被装入的地址 91 . . . . . . 92 DosHeader = (PIMAGE_DOS_HEADER) ImageBase; 93 NTHeaders = (PIMAGE_NT_HEADERS) (ImageBase + DosHeader->e_lfanew); 94 95 /* 96 * If the base address is different from the 97 * one the DLL is actually loaded, perform any 98 * relocation. 99 */ 100 if (ImageBase != (PVOID) NTHeaders->OptionalHeader.ImageBase) 101 { 102 DPRINT("LDR: Performing relocations/n"); 103 //ImageBase 是目标 EXE 映像在用户空间的位置 104 Status = LdrPerformRelocations(NTHeaders, ImageBase); 105 . . . . . . 106 } 107 108 if (Module != NULL) 109 { 110 *Module = LdrAddModuleEntry(ImageBase, NTHeaders, FullDosName); 111 (*Module)->SectionHandle = SectionHandle; 112 } 113 else 114 { 115 Module = &tmpModule; 116 Status = LdrFindEntryForAddress(ImageBase, Module); 117 . . . . . . 118 } 119 120 . . . . . . 121 122 /* 123 * If the DLL's imports symbols from other 124 * modules, fixup the imported calls entry points. 125 */ 126 //它所处理的就是当前模块所需DLL模块的装入(如果尚未装入的话)和连接。如前所述,这个函数递归地施行于所有的模块,直至最底层的“叶节点”ntdll.dll为止。 127 DPRINT("About to fixup imports/n"); 128 Status = LdrFixupImports(NULL, *Module); 129 if (!NT_SUCCESS(Status)) 130 { 131 DPRINT1("LdrFixupImports() failed for %wZ/n", &(*Module)->BaseDllName); 132 return NULL; 133 } 134 DPRINT("Fixup done/n"); 135 136 . . . . . . 137 Status = LdrpInitializeTlsForProccess(); 138 . . . . . . 139 140 /* 141 * Compute the DLL's entry point's address. 142 */ 143 . . . . . . 144 if (NTHeaders->OptionalHeader.AddressOfEntryPoint != 0) 145 { 146 EntryPoint = (PEPFUNC) (ImageBase + NTHeaders->OptionalHeader.AddressOfEntryPoint); 147 } 148 DPRINT("LdrPEStartup() = %x/n",EntryPoint); 149 return EntryPoint; 150 } 151 //调用关系[__true_LdrInitializeThunk > LdrPEStartup() > LdrPerformRelocations()] 152 153 typedef struct _IMAGE_DATA_DIRECTORY 154 { 155 DWORD VirtualAddress; 156 DWORD Size; 157 } IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY; 158 159 //每个 IMAGE_BASE_RELOCATION 数据结构代表着一个“重定位块” ,每个重定位块的(容器)大小是两个页面(8KB),而 SizeOfBlock 则说明具体重定位块的实际大小。这实际的大小中包括了这 IMAGE_BASE_RELOCATION数据结构本身。 160 typedef struct _IMAGE_BASE_RELOCATION 161 { 162 DWORD VirtualAddress; 163 DWORD SizeOfBlock; 164 } IMAGE_BASE_RELOCATION,*PIMAGE_BASE_RELOCATION; 165 166 167 static NTSTATUS 168 LdrPerformRelocations(PIMAGE_NT_HEADERS NTHeaders, PVOID ImageBase) 169 { 170 . . . . . . 171 //PE 映像的 OptionalHeader 中有个大小为 16 的数组 DataDirectory[],其元素都是“数据目录” 、即IMAGE_DATA_DIRECTORY 数据结构:其中之一(下标为 5)就是“重定位目录” ,这是一个IMAGE_BASE_RELOCATION结构数组。 172 173 RelocationDDir =&NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 174 . . . . . . 175 176 ProtectSize = PAGE_SIZE; 177 //所谓重定位,就是计算出实际装入地址与建议装入地址间的位移 Delta,然后调整每个重定位块中的每一个重定位项、即指针,具体就是在指针上加 Delta 178 Delta = (ULONG_PTR)ImageBase - NTHeaders->OptionalHeader.ImageBase; 179 //IMAGE_BASE_RELOCATION结构数组 180 RelocationDir = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + RelocationDDir->VirtualAddress); 181 RelocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + RelocationDDir->VirtualAddress + 182 RelocationDDir->Size); 183 184 while (RelocationDir < RelocationEnd && RelocationDir->SizeOfBlock > 0) 185 { 186 Count = (RelocationDir->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT); 187 Page = ImageBase + RelocationDir->VirtualAddress; 188 TypeOffset = (PUSHORT)(RelocationDir + 1); 189 190 /* Unprotect the page(s) we're about to relocate. */ 191 ProtectPage = Page; 192 Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage, 193 &ProtectSize, PAGE_READWRITE, &OldProtect); 194 . . . . . . 195 196 if (RelocationDir->VirtualAddress + PAGE_SIZE < NTHeaders->OptionalHeader.SizeOfImage) 197 { 198 ProtectPage2 = ProtectPage + PAGE_SIZE; 199 Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2, 200 &ProtectSize, PAGE_READWRITE, &OldProtect2); 201 . . . . . . 202 } 203 else 204 { 205 ProtectPage2 = NULL; 206 } 207 //具体的指针调整是由 LdrProcessRelocationBlock() 完成的,此前和此后的NtProtectVirtualMemory()只是为了先去除这些指针所在页面的写保护,而事后加以恢复。 208 RelocationDir = LdrProcessRelocationBlock(Page, Count, TypeOffset, Delta); 209 . . . . . . 210 211 /* Restore old page protection. */ 212 NtProtectVirtualMemory(NtCurrentProcess(),&ProtectPage, 213 &ProtectSize, OldProtect, &OldProtect); 214 215 if (ProtectPage2 != NULL) 216 { 217 NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2, 218 &ProtectSize, OldProtect2, &OldProtect2); 219 } 220 } 221 222 return STATUS_SUCCESS; 223 }