参考:

http://blog.csdn.net/hw_henry2008/article/details/6568255

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()前的“当前形势”。

VOID STDCALL
__true_LdrInitializeThunk (ULONG Unknown1, ULONG Unknown2,
ULONG Unknown3, ULONG Unknown4)
{
. . . . . . DPRINT("LdrInitializeThunk()/n");
if (NtCurrentPeb()->Ldr == NULL || NtCurrentPeb()->Ldr->Initialized == FALSE)
{
Peb = (PPEB)(PEB_BASE); //进程环境块
DPRINT("Peb %x/n", Peb);
ImageBase = Peb->ImageBaseAddress; //EXE 映像在用户空间的起点
. . . .
/* Initialize NLS data * //语言本地化有关
RtlInitNlsTables (Peb->AnsiCodePageData, Peb->OemCodePageData,
Peb->UnicodeCaseTableData, &NlsTable);
RtlResetRtlTranslations (&NlsTable); NTHeaders = (PIMAGE_NT_HEADERS)(ImageBase + PEDosHeader->e_lfanew);
. . . . . .
 /* create process heap */
//创建一个堆、 及其第一个区块,其初始的大小来自映像头部的建议值,其中 SizeOfHeapReserve 是估
计的最大值,SizeOfHeapCommit是初始值,这是在编译/连接时确定的。
RtlInitializeHeapManager();
Peb->ProcessHeap = RtlCreateHeap(HEAP_GROWABLE, NULL,
NTHeaders->OptionalHeader.SizeOfHeapReserve,
NTHeaders->OptionalHeader.SizeOfHeapCommit,
NULL, NULL);
/* create loader information */
//PEB 中的 ProcessHeap 字段指向本进程用户空间可动态分配的内存区块“堆”
Peb->Ldr = (PPEB_LDR_DATA)RtlAllocateHeap (Peb->ProcessHeap,
0,
sizeof(PEB_LDR_DATA));
. . . . . .

 /*    PEB 中的另一个字段 Ldr是个 PEB_LDR_DATA 结构指针,所指向的数据结构用来为本进程维持三个“模块”
队列、即 InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList。
所谓“模块”就是 PE 格式的可执行映像,包括 EXE映像和 DLL 映像
两个模块队列的不同之处在于排列的次序,一个是按装入的先后,一个是按装入的位置(实际上目前ReactOS
的代码中并未使用这个队列)。
每当为本进程装入一个模块、即.exe 映像或 DLL 映像时,就要为其分配/创建一个LDR_MODULE 数据结构,
并将其挂入 InLoadOrderModuleList
。然后,完成对这个模块的动态连接以后,就把它挂入
InInitializationOrderModuleList 队列.LDR_MODULE 数据结构中有三个队列头,因而可以同时挂在三个队列
中。
Peb->Ldr->Length = sizeof(PEB_LDR_DATA);
Peb->Ldr->Initialized = FALSE;
Peb->Ldr->SsHandle = NULL;
InitializeListHead(&Peb->Ldr->InLoadOrderModuleList);
InitializeListHead(&Peb->Ldr->InMemoryOrderModuleList);
InitializeListHead(&Peb->Ldr->InInitializationOrderModuleList); . . . . . .
/* add entry for ntdll */
NtModule = (PLDR_MODULE)RtlAllocateHeap (Peb->ProcessHeap,
0,
sizeof(LDR_MODULE));
. . . . . .
InsertTailList(&Peb->Ldr->InLoadOrderModuleList,
&NtModule->InLoadOrderModuleList);
InsertTailList(&Peb->Ldr->InInitializationOrderModuleList,
&NtModule->InInitializationOrderModuleList);
. . . . . .
/* add entry for executable (becomes first list entry) */
ExeModule = (PLDR_MODULE)RtlAllocateHeap (Peb->ProcessHeap,
0,
sizeof(LDR_MODULE));
. . . . . .
InsertHeadList(&Peb->Ldr->InLoadOrderModuleList,
&ExeModule->InLoadOrderModuleList);
. . . . . .
 /*当 CPU从 LdrPEStartup()返回时,EXE 对象需要直接或间接引入的所有 DLL 均已映射到用户空间并已完成连
接,对 EXE 模块的“启动” 、即初始化也已完成

注意在调用 LdrPEStartup()时的参数 ImageBase 是目标 EXE 映像在用户空间的位置
EntryPoint = LdrPEStartup((PVOID)ImageBase, NULL, NULL, NULL);
. . . . . .
}

 /* attach the thread */
RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock);
//目的是调用各个 DLL 的初始化过程,以及对 TLS、即“线程本地存储(Thread Local Storage)”的初始化
//TLS:有时候又确实需要让每个线程都对于同一个全局变量有一份自己的拷贝,TLS就是为此而设的
LdrpAttachThread();
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
}



//注意在调用 LdrPEStartup()时的参数 ImageBase 是目标 EXE 映像在用户空间的位置
PEPFUNC LdrPEStartup (PVOID ImageBase, HANDLE SectionHandle,
PLDR_MODULE* Module, PWSTR FullDosName)
{
//PE 映像的 NtHeader 中有个指针,指向一个 OptionalHeader。说是“Optional”,实际上却是关键性的。在
//OptionalHeader中有个字段 ImageBase,是具体映像建议、或者说希望被装入的地址
. . . . . .
DosHeader = (PIMAGE_DOS_HEADER) ImageBase;
NTHeaders = (PIMAGE_NT_HEADERS) (ImageBase + DosHeader->e_lfanew); /*
* If the base address is different from the
* one the DLL is actually loaded, perform any
* relocation.
*/
if (ImageBase != (PVOID) NTHeaders->OptionalHeader.ImageBase)
{
DPRINT("LDR: Performing relocations/n");
//ImageBase 是目标 EXE 映像在用户空间的位置
Status = LdrPerformRelocations(NTHeaders, ImageBase);
. . . . . .
} if (Module != NULL)
{
*Module = LdrAddModuleEntry(ImageBase, NTHeaders, FullDosName);
(*Module)->SectionHandle = SectionHandle;
}
else
{
Module = &tmpModule;
Status = LdrFindEntryForAddress(ImageBase, Module);
. . . . . .
} . . . . . . /*
* If the DLL's imports symbols from other
* modules, fixup the imported calls entry points.
*/
//它所处理的就是当前模块所需DLL模块的装入(如果尚未装入的话)和连接。如前所述,这个函数递归地施行于所有的模块,直至最底层的“叶节点”ntdll.dll为止。
DPRINT("About to fixup imports/n");
Status = LdrFixupImports(NULL, *Module);
if (!NT_SUCCESS(Status))
{
DPRINT1("LdrFixupImports() failed for %wZ/n", &(*Module)->BaseDllName);
return NULL;
}
DPRINT("Fixup done/n"); . . . . . .
Status = LdrpInitializeTlsForProccess();
. . . . . . /*
* Compute the DLL's entry point's address.
*/
. . . . . .
if (NTHeaders->OptionalHeader.AddressOfEntryPoint != )
{
EntryPoint = (PEPFUNC) (ImageBase + NTHeaders->OptionalHeader.AddressOfEntryPoint);
}
DPRINT("LdrPEStartup() = %x/n",EntryPoint);
return EntryPoint;
}
//调用关系[__true_LdrInitializeThunk > LdrPEStartup() > LdrPerformRelocations()]

typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY; //每个 IMAGE_BASE_RELOCATION 数据结构代表着一个“重定位块” ,每个重定位块的(容器)大小是两个页面(8KB),而 SizeOfBlock 则说明具体重定位块的实际大小。这实际的大小中包括了这 IMAGE_BASE_RELOCATION数据结构本身。
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION,*PIMAGE_BASE_RELOCATION; static NTSTATUS
LdrPerformRelocations(PIMAGE_NT_HEADERS NTHeaders, PVOID ImageBase)
{
. . . . . .
//PE 映像的 OptionalHeader 中有个大小为 16 的数组 DataDirectory[],其元素都是“数据目录” 、即IMAGE_DATA_DIRECTORY 数据结构:其中之一(下标为 5)就是“重定位目录” ,这是一个IMAGE_BASE_RELOCATION结构数组。 RelocationDDir =&NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
. . . . . . ProtectSize = PAGE_SIZE;
//所谓重定位,就是计算出实际装入地址与建议装入地址间的位移 Delta,然后调整每个重定位块中的每一个重定位项、即指针,具体就是在指针上加 Delta
Delta = (ULONG_PTR)ImageBase - NTHeaders->OptionalHeader.ImageBase;
//IMAGE_BASE_RELOCATION结构数组
RelocationDir = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + RelocationDDir->VirtualAddress);
RelocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + RelocationDDir->VirtualAddress +
RelocationDDir->Size); while (RelocationDir < RelocationEnd && RelocationDir->SizeOfBlock > )
{
Count = (RelocationDir->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
Page = ImageBase + RelocationDir->VirtualAddress;
TypeOffset = (PUSHORT)(RelocationDir + ); /* Unprotect the page(s) we're about to relocate. */
ProtectPage = Page;
Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage,
&ProtectSize, PAGE_READWRITE, &OldProtect);
. . . . . . if (RelocationDir->VirtualAddress + PAGE_SIZE < NTHeaders->OptionalHeader.SizeOfImage)
{
ProtectPage2 = ProtectPage + PAGE_SIZE;
Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2,
&ProtectSize, PAGE_READWRITE, &OldProtect2);
. . . . . .
}
else
{
ProtectPage2 = NULL;
}
//具体的指针调整是由 LdrProcessRelocationBlock() 完成的,此前和此后的NtProtectVirtualMemory()只是为了先去除这些指针所在页面的写保护,而事后加以恢复。
RelocationDir = LdrProcessRelocationBlock(Page, Count, TypeOffset, Delta);
. . . . . . /* Restore old page protection. */
NtProtectVirtualMemory(NtCurrentProcess(),&ProtectPage,
&ProtectSize, OldProtect, &OldProtect); if (ProtectPage2 != NULL)
{
NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2,
&ProtectSize, OldProtect2, &OldProtect2);
}
} return STATUS_SUCCESS;
}

阅读 LdrInitializeThunk的更多相关文章

  1. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  2. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  3. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  4. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  5. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  6. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  7. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

  8. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  9. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

随机推荐

  1. erlang四种监控策略

    转自:http://jasionq.blog.163.com/blog/static/10970577920133883158424/ Supervisor Behaviour是一个用来实现一个sup ...

  2. Win7/Win8右键菜单管理工具(Easy Context Menu) v1.5 绿色版

    软件名称: Win7/Win8右键菜单管理工具(Easy Context Menu)软件语言: 简体中文授权方式: 免费软件运行环境: Win8 / Win7 / Vista / WinXP软件大小: ...

  3. ural 1203. Scientific Conference(动态规划)

    1203. Scientific Conference Time limit: 1.0 second Memory limit: 64 MB Functioning of a scientific c ...

  4. 基于QTcpSocket和QTcpServer的Tcp通讯以及QDataStream序列化数据

    最近要在QT下开发Tcp通讯,发送序列化数据以便于接收. 这里涉及到几个问题: 1.QTcpSocket.QTcpServer的通讯 2.QDataStream序列化数据 多的不说,直接上干货!!! ...

  5. Xbox360自制系统GOD包安装教程

    1.准备工作 U盘或移动硬盘一个,已下载好的GOD包,本教程用一个32G的U盘和游戏<猎天使魔女>为例. 右击U盘,属性,查看你的U盘是否为FAT32格式. 如果是FAT32格式,则可直接 ...

  6. PHP关于表单提交 后 post get分页

    PHP关于表单提交后分页函数的那点事--POST表单分页实现   phpfunctionclass加密inputjavascript     说到分页,其实你在Google一搜一大把.大部是通过GET ...

  7. Chapter 2 Open Book——16

    By Friday I was perfectly comfortable entering my Biology class, nolonger worried that Edward would ...

  8. mysql主从复制的配置总结

    首先确定安装配置的环境 centOS7,mysql5.6 在配置之前要保证的几个点 1.系统防火墙,如果只是测试,可以关闭防火墙,如果不是测试,请开发需要使用的端口号,如3306: 开放端口 fire ...

  9. 第三十节,正则表达式re模块

    正则表达式 正则表达式本身是一种小型的.高度专业化的编程语言,而在python中,通过内嵌集成re模块,程序员们可以直接调用来实现正则匹配.正则表达式模式被编译成一系列的字节码,然后由用C编写的匹配引 ...

  10. Q promise API简单翻译

    详细API:https://github.com/kriskowal/q/wiki/API-Reference Q提供了promise的一种实现方式,现在在node中用的已经比较多了.因为没有中文的a ...