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 }

 

posted @ 2012-10-03 19:51  BinSys  阅读(5849)  评论(0编辑  收藏  举报