|
|
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]: C4 J' [( }$ Y( _2 `& E9 G
# o- ?* B# }9 U5 ]8 b
WINDOWS 2K Dll 加载过程+ C8 q" t5 A* \& N9 Z
jefong by 2005/03/30
* b* W8 `, T! Y) l* c9 U- e$ m这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。' v& ~, Q; t U
在windows中exe可执行程序运行时都会调用一些DLL,例如KERNEL32.DLL和USER32.DLL等系统的dll。但是dll是怎么被加载的呢?通常,大家都知道在编写dll时会有一个DLLMain的入口函数,但是实际上这个函数并不是调用dll时最先的工作。首先dll需要被加载,然后要进行初始化分配,再之后才进入DLLMain。还有可能你的一个dll中还会调用另一各dll。那么dll到底是怎样加载和初始化的呢,我们来参考一下Platform SDK中的“Dynamic-Link Library Entry-Point Function”。1 _6 r. w- @/ ~* s* `
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
6 @$ t+ F, k; e; k2 p" W0 b# N初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。3 v/ y6 f8 v% l
当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
8 L7 f$ u; ^7 {/ \! e 在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
0 f; e% C9 `- u j: r 接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):$ P, G6 _" J- ]4 K
//=============================================================================
/ D: @3 F$ J7 K1 ]// Matt Pietrek, September 1999 Microsoft Systems Journal/ `2 \9 _' r X* G2 Y# k
// 中文注释部分为jefong翻译
* J! H4 w6 ^5 N/ u, g) s// W. w* Q- }! \; S! A. Z
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
/ B( j5 g7 g3 g3 c7 C//) |( `6 n% S1 l( j% B5 q
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;4 l2 a2 Z2 A8 s
//=============================================================================! F E0 E# b ^+ l" \7 _$ @; x: N* J4 S
' ^2 f2 s2 F6 f" P1 H B2 a* [
#include <ntexapi.h> // For HardError defines near the end
% U; ] l/ K O3 ^2 I0 c: w$ U- Y& T' j& e2 [# F
// Global symbols (name is accurate, and comes from NTDLL.DBG)
# g& m7 g" p. C8 b w; @// _NtdllBaseTag- h1 l* ], O( Q8 H) l b+ x8 Z
// _ShowSnaps
0 R9 `. [6 c. D# y5 j// _SaveSp
9 O# l- t U/ G// _CurSp
4 `- j* G( d! A4 z. l5 z% w" n( Y// _LdrpInLdrInit! v& ^/ E3 s0 d- }0 v- \2 N
// _LdrpFatalHardErrorCount
8 {5 y4 e M* }. A// _LdrpImageHasTls
' @1 Y% A0 L5 v4 h/ r# }/ A( u4 J. k$ {& q2 p8 f
NTSTATUS
9 }* ?1 ?: {- c6 b, k/ h: cLdrpRunInitializeRoutines( DWORD bImplicitLoad )
* ~- v1 e- y; w; }/ M, s; C- A{
7 U' ^: o% k" R6 e // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了% A, a! {# I7 |5 j6 ?
unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
; o5 \# b7 V0 Y: O- c/ K
, ?: J) o7 G: { if ( nRoutinesToRun ). c) I8 i5 |! W# y. L. `
{3 ]: I& \( _7 ~ f# x& g0 P$ h2 }
// 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
( }' X6 q2 m) P1 i7 C, G! c* ? pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
9 m% E) B5 B+ M) p% c _NtdllBaseTag + 0x60000,* R" S) I2 W; {2 f# ] W
nRoutinesToRun * 4 );
4 q- Y7 e3 X6 K3 u! y 9 ^+ \' `& |5 I) J. \
if ( 0 == pInitNodeArray ) // Make sure allocation worked) ?+ K6 B+ D- Y6 L, l
return STATUS_NO_MEMORY;
, x* ]2 ^# e2 W8 Q6 I" v5 z" { }
" W4 X; ~# o* U6 @4 e else
$ n, F+ j6 q, f; b/ D3 r pInitNodeArray = 0;
! q5 q. {* M6 O1 }) p
& q* B4 g3 `$ H. i //第二部分;5 i% J; [8 h) z. Q+ S$ T" N+ u
//进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
& v3 x- S* w/ @: I pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);5 v& P* L9 F P/ f! k
ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
9 B8 [9 X6 n% O, V& ~5 q
+ V9 T2 `' \, A. ^! \ if ( _ShowSnaps )
3 {% N! T0 g9 D' y' h5 p {
( K7 b4 l, r7 H1 t _DbgPrint( "LDR: Real INIT LIST\n" );
4 O7 Z5 M+ Q+ b' a1 w* l. G3 G! B3 Q! d }* ]/ g( P" Z7 ?+ {
$ q1 m# K- E7 A3 w9 p2 E nModulesInitedSoFar = 0;1 M$ S. @' M8 Z- G6 z/ F$ M+ _0 S
0 Y+ I: E% t1 V2 { if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块% J% z& S9 ]$ A* D: H3 Q
{
& S0 e# r3 F" s8 `5 G
& X' {9 l; E3 m4 H2 B* P while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块- |* f9 u. S3 z: T/ {" J% I
{
* c( l! F% w) q( m ModuleLoaderInfo pModuleLoaderInfo;9 Z- j& C- T/ ?" Q
+ ?. E! \) z: H1 W0 e, O/ e //3 g9 r/ n% T+ L' r
//一个ModuleLoaderInfo结构节点的大小为0X10字节
& S9 s1 F2 E5 J pModuleLoaderInfo = &NextNode - 0x10;7 v$ K: h$ {2 J7 N2 O% [
: ?7 i' M" N( M3 \4 P7 ] localVar3C = pModuleLoaderInfo;
* e# t! ~: A2 `( E" e1 } ^ G) }# k j4 c
//) \2 k. V5 t1 \9 g: w) H; ]) A
// 如果模块已经被初始化,就忽略
9 C" o5 G' q9 Q: E // X_LOADER_SAW_MODULE = 0x40 已被初始化
: V; {/ B: T3 _( l/ \ if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )- V j* [ ] Z: r3 t
{
$ Y Y; d% u4 ~5 ?, i //8 p( ]: D9 Z) ~
// 模块没有被初始化,判断是否具有入口函数
9 A# l& z& q; e' E8 X+ e( m& f //, Q8 t4 l* X9 Y$ Q
if ( pModuleLoaderInfo->EntryPoint ), {) u" C7 k& E& H" A
{
7 N0 }7 ~. \" i //- a3 x1 n. w8 U4 M
// 具有初始化函数,添加到模块列表中,等待进行初始化
7 A |" }, _* G pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;3 u5 V" H" F& h# I- D+ Z2 Y8 d
2 o% M, I8 p1 ~* c // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
; I, k X$ X O // 例如:
* [% m/ j6 Z; a) `% \ // C:\WINNT\system32\KERNEL32.dll init routine 77f010002 x7 Z. S/ v9 {( _% n- m v
if ( _ShowSnaps )
5 t; o* o/ U& f9 R% o: ]+ P6 Q {. w Q" b7 l- ]) f; k0 w) Y
_DbgPrint( "%wZ init routine %x\n",
$ W" Y. r" V( \# | &pModuleLoaderInfo->24,
2 A* [' C- d# C) e# P4 z pModuleLoaderInfo->EntryPoint );0 O. o$ K% u6 x0 K0 g
}
: N; i/ q, i7 V* \3 p. S2 F. f
nModulesInitedSoFar++;
8 d& Y+ p8 }% p6 E Z8 f# P+ V }' b; K0 w# k% b* K6 T) f# b/ y
}# ~: Z% P" P# y( ?' q- }" {$ _% C
$ }% B# m0 R! k // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。- f/ }+ l( d! c" o$ ^% [ U: E( n
pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;5 G0 p5 C: H' E% D4 J0 k y# R4 @
- H; O1 {" u5 J& z
// 处理下一个模块节点, R1 F8 W8 d) |3 }$ s2 z3 o. f+ `
pCurrNode = pCurrNode->pNext1 U% W) c% X# o8 w% X4 x
}8 ^4 O4 m% W9 m4 S1 R5 d! p
}
6 o" J1 v6 M" ?& Z( M" g else$ t3 {; z4 e8 T
{1 Y A/ v. |* \, w3 J9 G
pModuleLoaderInfo = localVar3C; // May not be initialized???9 l/ l) V- e. h. q/ h1 U/ Y7 V
}, m4 R3 C' y) B1 k% t! I7 e
0 Z8 q5 y: s, H# A
if ( 0 == pInitNodeArray )( K" i9 ?0 j/ y- o$ |
return STATUS_SUCCESS;. y( F( L( g3 `+ G0 U
) |& W2 H( X! J3 u! {' G1 U
// ************************* MSJ Layout! *****************
( @& T# D) b0 ?$ v // If you're going to split this code across pages, this is a great& v- }; a+ N, r. D
// spot to split the code. Just be sure to remove this comment, S, d; B' Q# r2 m
// ************************* MSJ Layout! *****************
* C' R* v$ Q i7 C6 ]
1 E% h0 W1 Z# I //: g3 }/ L8 a. b& M8 P
// pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
. d9 N8 D/ [* D* M* M/ a: ` // 第三部分,调用初始化部分! w# u3 f, e% ^% ^
try // Wrap all this in a try block, in case the init routine faults
" d3 W% V" S3 m {
) U1 }: j$ G, u6 I! k8 [6 A nModulesInitedSoFar = 0; // Start at array element 0+ Z' ~+ \4 T6 f7 H1 [4 m# H1 C/ F1 M
- O' R3 J5 b1 U8 l; j9 l
//& z# P( l; t2 [* ]
// 遍历模块队列; M& a) x& G6 R" J
//9 J& J3 ~7 p! {4 d; W: B; Y
while ( nModulesInitedSoFar < nRoutinesToRun )
$ E1 z, P" p; K {4 t9 C& X) G& g6 k9 _6 t. L
// 获得模块指针, J( c% P& ]0 j; @! M
pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];% n% c$ k) n2 Z
6 J f% E# V: l
// This doesn't seem to do anything...
9 A& M* T& i6 R; B# l/ ] localVar3C = pModuleLoaderInfo;
0 y$ R' c( `8 M
/ R! l: m8 ? f3 m- M6 d. }" k nModulesInitedSoFar++;( F, i; ~* b) s4 Q
7 ^5 M6 t& A9 C* ?: g9 v6 J // 保存初始化程序入口指针
; {; P2 E9 t7 Q5 _. [7 P pfnInitRoutine = pModuleLoaderInfo->EntryPoint;2 s I7 E$ j! [2 m7 k
) N& g' ` ^7 ~- S& j3 W& [" D
fBreakOnDllLoad = 0; // Default is to not break on load/ v8 b1 o" t0 C& }
4 Z6 X" C+ Z4 m# a( d
// 调试用
$ a5 }0 p6 d8 U // If this process is a debuggee, check to see if the loader+ M- B, @" T2 R/ b' M b6 g
// should break into a debugger before calling the initialization.& P. L9 Y. J4 L2 \5 ?. F
//
3 ]7 ^/ c) q$ G // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()
! h' t7 G4 ^3 E! ?, a // returns. IsDebuggerPresent is an NT only API.. E8 s7 B- i, I$ E8 Z+ |
//
W6 l) W* l5 m; Y+ a6 S& f) C if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
+ v& u' d6 C7 M' B, \% u3 b {
! Q) H5 B1 t& j LONG retCode;
6 S% H ~" m! ^, R. \
1 B5 e) h# C: I2 s3 Q& p //
6 A6 b# x9 ~- f$ n; ~. s8 ~- S$ T* t // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\. a; L# M y2 T8 I
// Windows NT\CurrentVersion\Image File Execution Options"
( ^* X# {1 q% ^; m0 o3 R* X. l1 K: }+ H // registry key. If a a subkey entry with the name of$ P& k; u4 s7 [# e( L
// the executable exists, check for the BreakOnDllLoad value.
o7 E6 r. ? X0 F0 F //" e- e" g9 D# i4 }) c
retCode =
5 I- t- r+ d. Y$ T _LdrQueryImageFileExecutionOptions(
* b- \7 T. ` h1 i" A2 R pModuleLoaderInfo->pwszDllName,2 h9 @) R0 _, m- k
"BreakOnDllLoad",pInitNodeArray
) \1 ~; r6 s! D: Z; i REG_DWORD,: O6 g: r! j% O( }1 s( S8 _
&fBreakOnDllLoad,
+ g! ^# K' C) U" F( k- O# Z5 w/ q! K sizeof(DWORD),# h; \% s; {$ e1 d
0 );6 e& J0 i( m% l2 o. y
5 t1 f5 h9 i" Q o* P1 [/ z // If reg value not found (usually the case), then don't
2 B& A H7 z# U // break on this DLL init" E2 B" C( z' u6 U% [$ M' A/ \1 w
if ( retCode <= STATUS_SUCCESS )
. [+ G2 K3 g; e* {; v' ? fBreakOnDllLoad = 0;pInitNodeArray, ~5 Z4 m$ x. f1 T, k
}: I B) A' r; S8 @) ?
9 F9 B2 e' K7 |& ]) Y9 q1 Q. Y
if ( fBreakOnDllLoad )
9 p8 b6 m) t, L F0 R5 @$ z; X! g& e { ( X- I# P8 A5 C1 U6 J
if ( _ShowSnaps )
8 {& z9 P2 @5 h {
* T! K3 L4 \' Q" c // Inform the debug output stream of the module name4 W4 ^7 S# a9 y3 s4 I2 ]
// and the init routine address before actually breaking4 O- @5 x0 J% `' C2 p
// into the debugger6 g% i$ t: } [6 a H7 d4 k
+ X6 P6 c4 O. G _DbgPrint( "LDR: %wZ loaded.",9 R+ {& Z: B: F$ w1 I
&pModuleLoaderInfo->pModuleLoaderInfo );
; j9 [; v4 m; }0 I) i) p$ [! O0 V
5 q4 X& M: j& C Z2 T _DbgPrint( "- About to call init routine at %lx\n",
: a% b( M9 h! n# v8 M4 _& } pfnInitRoutine )
, O% r& t3 U' w }$ O: c& Z' _4 v- ^$ d& Z
8 i) c4 l* M! P' D. A // Break into the debugger $ J" A' }7 q5 |+ v# z' w
_DbgBreakPoint(); // An INT 3, followed by a RET- ]# a1 r' [" t: t- y. N
}
' [$ V! Y- R0 n; u- N7 l. e else if ( _ShowSnaps && pfnInitRoutine )
5 I4 X, g7 `$ L4 y2 l {1 e o6 D2 x, y8 S+ X7 ^
// Inform the debug output stream of the module name, G. F' W4 j N. l1 _, u* ?
// and the init routine address before calling it : k; H/ x6 s) {
_DbgPrint( "LDR: %wZ loaded.",/ G# v8 p7 s& W9 M
pModuleLoaderInfo->pModuleLoaderInfo );
6 F1 g/ N$ g: ]+ e8 l5 o- V5 ^+ A1 l- }4 L
_DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);6 ?& V7 i- t7 ~- z i; r+ {
}
( _8 B8 z0 t# O" t
. U! q1 A1 k/ A, k: t# T8 W9 g1 J if ( pfnInitRoutine )" A8 U, c+ V) ~# R6 O
{
; L* Q I! E8 m1 y" _9 B9 h // 设置DLL_PROCESS_ATTACH标志" q# @ ]/ z! P; C+ i9 L
//) G' G4 G* `) g! l% \
// (Shouldn't this come *after* the actual call?)
0 a: Y- U4 \- n //0 s( K! D1 ?# G% Y% m5 \% ?% g
// X_LOADER_CALLED_PROCESS_ATTACH = 0x8 % f8 i8 [8 o( h, h( S3 p( c
pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;/ H, Q8 h9 X! u x: V
' w* e/ a* M% z* @ //+ j) V* t* E" w- o" |
// If there's Thread Local Storage (TLS) for this module,/ ~3 S; M. f k
// call the TLS init functions. *** NOTE *** This only
% [( h: C8 F. b. Y+ c d H* U6 J // occurs during the first time this code is called (when3 V m- H$ Z/ n" \* E" Z& ^+ ~
// implicitly loaded DLLs are initialized). Dynamically% n1 b% z4 @8 n: K ]/ v
// loaded DLLs shouldn't use TLS declared vars, as per the1 O/ x+ Q0 i* r% q
// SDK documentation
" e" x7 \: x& U4 ~ // 如果模块需要分配TLS,调用TLS初始化函数
8 d/ L% L# y" z4 I! e( h# U7 Y // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
8 \; j+ {$ a+ }3 }1 a- P // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
0 m# w4 L u5 f( ], W; a$ W3 R' H0 T if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
5 t1 V# k. R+ H f, `; p7 u {$ H3 p, }2 D2 `
_LdrpCallTlsInitializers( pModuleLoaderInfo->hModDLL,
" M+ r& P$ L/ e5 i6 A. D; y1 t DLL_PROCESS_ATTACH );2 g" r' Q6 H/ o: I2 {
}
2 e2 q2 W- N3 S" i9 L2 `* D% A' q # M) Q9 i" m$ A
" [, P, A2 Q7 N
hModDLL = pModuleLoaderInfo->hModDLL
; c) }: o$ m. P. U5 y5 D2 E4 x; X* {" f
MOV ESI,ESP // Save off the ESP register into ESI3 w% }2 I& \) ^' e' b
& g6 k* i0 R" W/ v A$ h
// 设置入口函数指针 , q% ]" ?/ c4 z$ O; P
MOV EDI,DWORD PTR [pfnInitRoutine] 9 p" a# S! V$ c6 h3 s
4 ^8 r) Q1 W& o6 n( y // In C++ code, the following ASM would look like:
4 ~- ]0 ]4 p0 `. n) E //) [' R- G4 g; }2 K# S. q! W
// initRetValue =
8 o p! H& y+ u- A, j // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
, j1 t- o) z+ ]/ _% x //
i- H4 E# E% |* j( l# e$ K; O% ~, C7 O# {* N: w% F! k& l8 N
PUSH DWORD PTR [bImplicitLoad]# F; s, M; _- x9 x2 a
- p! B; z+ Q0 ~1 N1 X
PUSH DLL_PROCESS_ATTACH( V$ b4 `. m, J! L/ i
( ?; j5 U% L* J: p9 `. A% O0 |% W: D
PUSH DWORD PTR [hModDLL]# G8 |4 ]( h* \+ V8 f9 O
9 d1 o/ ^4 O% N4 P' @ CALL EDI // 调用入口函数1 [* b& z1 J7 X
% W2 n' f O! H# h
MOV BYTE PTR [initRetValue],AL // 保存入口函数返回值7 `% a& q, i3 V- A
. y* J4 u2 c1 k, }& H! r
MOV DWORD PTR [_SaveSp],ESI // Save stack values after the
& Y/ w% `# `. ^: }* O! K3 B MOV DWORD PTR [_CurSp],ESP // entry point code returns! B% ~+ L; m" P0 T5 _, L' ?
9 _8 J* L6 {' S- ?+ {
MOV ESP,ESI // Restore ESP to value before the call7 ~8 a$ V; [& B* ^% s
$ y* A @, x' h4 |. o1 m //
7 v9 A4 e: N$ g/ u // 检查调用前后的ESP值是否一至
6 b7 l% }& H/ i //
! H- O! [4 x, `4 t5 \& T if ( _CurSP != _SavSP )
% j) e& U4 M: M* J L5 Y {( I8 E) b. u6 ]5 O3 ]& C' p I2 K
hardErrorParam = pModuleLoaderInfo->FullDllPath;
1 o+ z' v0 @9 R# Y' [- P5 J
2 i6 B- @& J" m/ Z3 ^ hardErrorRetCode = 7 j1 @8 d9 }% w d5 e: A/ ~
_NtRaiseHardError(
$ p" Y: D* d* X/ u4 y3 x STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
7 D3 Q! I# U$ S5 Y( p8 X n4 ]7 f 1, // Number of parameters& w4 P# @) H- I1 H# n$ ^/ ~
1, // UnicodeStringParametersMask,) t6 C x5 `+ ], f2 a9 o
&hardErrorParam,* q3 K5 Y1 r2 U4 j
OptionYesNo, // Let user decide- s* Z9 C8 b, S9 l( y. `- _1 V
&hardErrorResponse );- G) q! z, o* d2 ?
0 y7 b: [( a& T1 M3 c$ N/ C if ( _LdrpInLdrInit )1 _4 p( r8 ?9 P5 y' p
_LdrpFatalHardErrorCount++;
7 z6 D# {' P6 r
$ f( @: I: d8 W" w+ m2 b if ( (hardErrorRetCode >= STATUS_SUCCESS)
' Q! |1 T6 l7 T; j( ^3 g% ~ && (ResponseYes == hardErrorResponse) )
0 t9 A# x8 B9 g" F8 C {
0 o7 u" B1 E+ y, z% ? return STATUS_DLL_INIT_FAILED;
8 O6 V# R" i+ T }
. Z+ L% f# Z# z; ]. ] g }
4 h' ~! q' w! Y. _6 Q! h6 @8 \! `" `) W; g8 S2 w
//
' p" f- {# a8 p$ d4 Q1 N. q // 入口函数返回0,错误
" r0 j3 `& I2 [$ b //1 V! l1 g) w, H( ~1 t; q# d
if ( 0 == initRetValue ), h6 v0 B- D p
{1 @- G0 c2 u6 G5 R) l* P z
DWORD hardErrorParam2;" ^4 c3 ]* g$ ^2 a$ y) J
DWORD hardErrorResponse2;
9 l+ \& _6 B/ Y' W: h
0 a2 I& `4 b" A8 ]( G$ Q" c hardErrorParam2 = pModuleLoaderInfo->FullDllPath; ?' u, }& f1 P* y* G; q
+ a; [7 Y- W. G w2 F( e0 e
_NtRaiseHardError( STATUS_DLL_INIT_FAILED,! V0 p; E2 B/ Z( ^" W! Z
1, // Number of parameters
$ s. Z3 p$ O1 G 1, // UnicodeStringParametersMask
1 F% G. k8 V- ]3 c2 U6 E: J$ H1 Q &hardErrorParam2,6 T" B" H ?# Q; T1 x: T) O( e
OptionOk, // OK is only response
+ s% s7 T6 v1 j( g' Y &hardErrorResponse2 );( X/ w; R# Q/ Y
8 d( B# f+ g$ z8 k, D+ ^ if ( _LdrpInLdrInit )
5 \+ c+ I# d* n0 _ _LdrpFatalHardErrorCount++;. Q* Z" U' b: D) Z2 _
% t ]( ~4 F. w. k8 S8 Q2 I4 H
return STATUS_DLL_INIT_FAILED;
: D: E; B; f7 [ p }
+ m( [: Q* Q+ C+ ^! f G% I* {4 m; o }% ~. k7 ~% m- r# R
}
. C4 T3 S. a; s/ J4 y) N
/ I9 _0 F! q/ s& u //" j: |. `0 i' r6 n+ ~/ K4 g' v, w
// 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时0 @( ^! {. N7 z# F& G. V# T' @
// - Q9 q% H1 N0 ~# S- m8 y1 h1 A* Y
if ( _LdrpImageHasTls && bImplicitLoad )
1 d# T$ [% c# j; {8 @+ U! p {( Q* f/ R \; o7 d' d
_LdrpCallTlsInitializers( pCurrentPeb->ProcessImageBase,7 O" m8 ?& } K' j" P( t
DLL_PROCESS_ATTACH );/ C% m4 x% @) x' b
}2 K/ P. v$ a4 z+ o8 J$ {$ M
}
' S. f% B- y4 w1 Z __finally, Q8 @3 P9 R+ e) [
{
" o5 R* n2 n4 r0 J8 ] //
1 E+ r, W; F6 C% g // 第四部分;0 \4 ?* @8 U; ^1 x
// 清除分配的内存
7 f+ C6 W, k/ T8 G; \1 t; Q) m _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );% v/ V2 a _5 E( B
}
w7 {. C1 c Q6 _( K3 W1 o W# o" j" Y' m% Y) F ^4 P
return STATUS_SUCCESS;2 ]/ ?, F0 [% ~& \4 ]* r: e
}
: T6 n9 t) m5 ?$ J5 u1 b( `& Q+ H0 I
这个函数分为四个主要部分:& I0 v' R R$ f( L
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
! A& M# N9 s; c5 s二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
& E y( V. f) m- Y, b7 o三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。. i- B% e3 p9 M- o/ e: a; `0 i& l
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
7 T! g) A) F1 W) S* ]8 Y2 t在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。9 E* }# D& W* {
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。 |
|