在PE映像的装入和启动过程中,DLL的装入和连接是一个重要的环节。读者在上一篇漫谈中看到,Windows的DLL装入(除ntdll.dll外)和连接是通过ntdll.dll中的一个函数LdrInitializeThunk()实现的。在Wine中,这个环节也是通过一个同名的函数实现的,只不过这个函数不在ntdll.dll中,而是wine-kthread里面的一个函数。在ReactOS中则同样也是LdrInitializeThunk(),同样也在ntdll.dll中。DLL的装入和连接当然离不开PE格式,但是本文的目的不在于完整、系统地介绍PE格式,而在于从LdrInitializeThunk()这个函数入手讲述DLL动态连接的过程,这个过程涉及PE格式中的什么成分,就讲解什么成分。这样,整个过程下来,PE格式中关键性的部件也就差不多都见到了。
先对LdrInitializeThunk()这个函数名作些解释。“Ldr”显然是“Loader”的缩写。而“Thunk”意为“翻译”、“转换”、或者某种起着“桥梁”作用的东西。这个词在一般的字典中是查不到的,但却是个常见于微软的资料、文档中的术语。这个术语起源于编译技术,表示一小片旨在获取某个地址的代码,最初用于函数调用时“形参”和“实参”的结合。后来这个术语有了不少新的特殊含义和使用,但是DLL的动态连接与函数调用时的“形实结合”确实有着本质的相似。其实Unix/Linux也常常用到类似的技术,只是很少使用这个术语。例如,在Linux内核(i386版本)中,current是作为指针使用的,但实际上却是个宏操作:
CODE:
#define current get_current()
而get_current()是一个函数:
CODE:
static inline struct task_struct * get_current(void)
{
struct task_struct *current;
__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
return current;
}
这显然就是一个Thunk。还有,ELF映像(与.so模块)的连接所用的“过程连接表”PLT和“全局位移表”GOT本质上也是Thunk。所以,Thunk就是“动态连接”的同义词。
下面我们就来看LdrInitializeThunk()的代码。由于Windows不公开它的代码,而Wine需要处理PE和ELF两种格式的动态连接库,相比之下略为复杂一些,所以我们先看ReactOS代码中的这个函数,其代码在reactos/ib/ntdll\ldr/entry.S中:
CODE:
.globl
:
#if defined(_M_IX86)
nop /* breakin overwrites this with "int 3" */
jmp
#elif defined(_M_ALPHA)
. . . . . . .
由于是汇编代码,所以在函数名前面加上了‘_’,而后缀@16则表示调用参数一共是16字节、即4个参数(不过下面的代码并未用到这4个参数)。其实这个函数的本身就是个Thunk,因为它起的只是桥梁、跳板的作用,真正起作用的是__true_LdrInitializeThunk()。
在进入这个函数之前,目标EXE映像已经被映射到当前进程的用户空间,系统DLL ntdll.dll的映像也已经被映射,但是并没有在EXE映像与ntdll.dll映像之间建立连接(实际上EXE映像未必就直接调用ntdll.dll中的函数)。LdrInitializeThunk()是ntdll.dll中不经连接就可进入的函数,实质上就是ntdll.dll的入口。除ntdll.dll以外,别的DLL都还没有被装入(映射)。此外,当前进程(除内核中的“进程控制块”EPROCESS等数据结构外)在用户空间已经有了一个“进程环境块”PEB,以及该进程的第一个“线程环境块”TEB。这就是进入__true_LdrInitializeThunk()前的“当前形势”。
CODE:
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;
. . . . . .
/* Initialize NLS data */
RtlInitNlsTables (Peb->AnsiCodePageData, Peb->OemCodePageData,
Peb->UnicodeCaseTableData, &NlsTable);
RtlResetRtlTranslations (&NlsTable);
NTHeaders = (PIMAGE_NT_HEADERS)(ImageBase + PEDosHeader->e_lfanew);
. . . . . .
/* create process heap */
RtlInitializeHeapManager();
Peb->ProcessHeap = RtlCreateHeap(HEAP_GROWABLE, NULL,
NTHeaders->OptionalHeader.SizeOfHeapReserve,
NTHeaders->OptionalHeader.SizeOfHeapCommit,
NULL, NULL);
. . . . . .
/* create loader information */
Peb->Ldr = (PPEB_LDR_DATA)RtlAllocateHeap (Peb->ProcessHeap,
0,
sizeof(PEB_LDR_DATA));
. . . . . .
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,
阅读(1199) | 评论(0) | 转发(0) |