|
|
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
8 L$ W' X" x' R2 U; K6 p
4 \+ U! b4 w5 K+ Y2 J5 QWINDOWS 2K Dll 加载过程
1 h6 T7 Q% j5 n/ b4 [jefong by 2005/03/30
0 O2 p) S# ?& m: ?这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
R4 W7 O4 ?! ^) U# \( b3 Q在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”。
' a* u; t% h I3 E+ e2 v E* j你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。) u: Q4 F1 i6 `, K v5 D0 Y$ N, l
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
% ?! w/ T' v$ x 当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
3 w( G( L) o+ b: }5 T$ g$ F" e+ l 在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。7 r6 y) ~# b0 l
接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):1 o+ B6 `( B: _6 D
//=============================================================================3 A* Z/ a' ^' z" o* g
// Matt Pietrek, September 1999 Microsoft Systems Journal, O2 X, t% A! t% c7 ]
// 中文注释部分为jefong翻译
# ]& [, `9 O/ _% l//' S7 i* h- n0 m$ X% {5 z
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)2 ~& A0 C g5 k- e& m( E) N3 `9 A
//
9 K' _( C& l1 W// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
& t# n9 t* I; N//=============================================================================
1 r8 |0 |5 h ]' S4 X2 v0 j( X1 q. `% f, o
#include <ntexapi.h> // For HardError defines near the end
6 C# }0 c+ Q- t* }
- X, g* @; P: X7 j8 P$ I8 c" v3 \// Global symbols (name is accurate, and comes from NTDLL.DBG)! U2 h" {7 L q% o
// _NtdllBaseTag: m% g; E* m$ [! W+ x4 g
// _ShowSnaps
- V3 T, f2 r3 s0 I; c/ }// _SaveSp
q' A; }& G3 n) p# e5 Z9 S// _CurSp
. h) Z7 B# s g7 F% `' h4 o+ A$ p// _LdrpInLdrInit& B/ x6 H- m: Y* B6 W. ^. }5 ]! `
// _LdrpFatalHardErrorCount4 _% G2 g# B# C7 n
// _LdrpImageHasTls
+ x6 J+ _. E( W) {& M' O
3 r0 o) Y' J) i+ p( kNTSTATUS
" [, d4 f4 Y2 q2 x1 k/ rLdrpRunInitializeRoutines( DWORD bImplicitLoad )
* x6 ^: }; e2 L; \4 h* d; O4 W- D{- q% J# c* S* z( V* @8 n
// 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了4 L! V8 Y; w5 v
unsigned nRoutinesToRun = _LdrpClearLoadInProgress();1 O0 K- T) I3 V
/ I4 k# Y6 Z! n
if ( nRoutinesToRun )
; ]- Y6 I$ I' I; s: } {6 x# q7 g7 Q+ P/ p
// 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。, |" U" C" j$ U
pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),- [ J5 D* l0 J) b' x
_NtdllBaseTag + 0x60000,% b: \4 S* |% f4 k; [' w9 J
nRoutinesToRun * 4 );
- ]3 Q6 n3 A& e% V
$ H2 j' X! f- m S) d/ X if ( 0 == pInitNodeArray ) // Make sure allocation worked9 b: H: M9 Y( G* X+ T
return STATUS_NO_MEMORY;0 o3 \! y2 k2 W4 b) Q$ X7 \# b
}
6 L: ]$ p1 ?; ` else
! @( [# d. I( F! K6 |3 U- B* d& O# n pInitNodeArray = 0;) z" @& [' @, y" B' M, j
* c' @ _( B! s9 j5 ?5 d //第二部分;2 t& g% s1 G2 U( h: m
//进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。3 ~- l, P4 I" J: P
pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);, T/ P ~+ O3 e. p7 S
ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;, X4 [+ y" O6 L7 J7 A6 b) x; v- `: M
5 r: M5 b9 A2 S: E
if ( _ShowSnaps )2 o2 T; J. i2 Q V+ | s1 c
{: z2 K, x/ d* d2 }
_DbgPrint( "LDR: Real INIT LIST\n" );
3 y8 y; w( U, A }) V# o" F& `# D: f2 I4 ]( I" H( H
6 x# x) p. | l. g- ? nModulesInitedSoFar = 0;
! m4 b+ `: E& x+ X* p, \# X+ O& W0 Z8 _5 Q$ T5 M* `8 `( t6 s
if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块* d6 T+ e; a/ X! ]# M6 N
{8 K/ N. F2 g: G0 T% }5 K4 F c( k
- R' U( r* n; u- Y1 N: i
while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块' P X( G" m7 B2 N
{6 L; M. }) R& t' S
ModuleLoaderInfo pModuleLoaderInfo;
$ a0 b9 m3 Z" V( U
+ r ~% n. n! S$ y. G //
4 `' P7 T$ u2 h% O0 W! o0 i. z' X //一个ModuleLoaderInfo结构节点的大小为0X10字节6 g+ m9 {$ e/ X2 I9 ]
pModuleLoaderInfo = &NextNode - 0x10;3 J. y- P* D* L2 y) k8 W M+ ?* z
* `% u) _2 o e6 A( j" p' H
localVar3C = pModuleLoaderInfo;
+ c4 o/ p! f" n1 G) `/ C! `4 ]$ E8 l- S/ n b7 A
//
% |( X+ B$ V$ c/ J2 c // 如果模块已经被初始化,就忽略+ s5 i- A" I$ ?$ B/ n1 {
// X_LOADER_SAW_MODULE = 0x40 已被初始化
6 j/ Y' w- R" o$ t5 T6 A) g! I/ u if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )8 F4 P( y8 a/ C) d+ m* r
{8 U* l5 L, D3 u+ m. C/ @
//, h9 s7 }3 r+ f' v# D$ C2 |
// 模块没有被初始化,判断是否具有入口函数& V8 B& F5 o6 p6 a. f! Q6 n
//
+ q6 W3 }0 D2 I/ x! G, q5 A if ( pModuleLoaderInfo->EntryPoint )
" O; y [8 d+ }- Z3 D% m {
' ?9 ?) q+ _4 o- y$ j' u$ }% z2 s //8 ]0 G! i& D) v/ ~
// 具有初始化函数,添加到模块列表中,等待进行初始化8 t2 E* @ Q5 b! R1 b
pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
# u/ B2 Z) o! Z' _5 D- v2 m
- V1 l$ [3 i. e) X- p/ B; w& C, t: W // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址9 t% n5 M: U) P( I3 i( {
// 例如:
x" m) J% Y* \6 A; v // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
6 r2 P) ~1 }/ F. ?, o! r if ( _ShowSnaps )
/ [- {" Z; O# { {
. p) {& d4 U1 i/ [ _DbgPrint( "%wZ init routine %x\n",$ b O# w3 G4 f+ s
&pModuleLoaderInfo->24,8 e$ R5 B6 z& _( u" p+ j. P
pModuleLoaderInfo->EntryPoint );
6 D" S, R8 z. n5 i% l/ k7 S* D7 Y }+ K5 t- H8 D! u
8 [5 E) z y5 W0 `( l' \ nModulesInitedSoFar++;
2 ^1 g# M& D. K( Q: _ }
3 f- N+ n3 E+ y+ | }" ~) X. p6 w5 l1 Q3 l/ j
6 H" y+ L5 L2 q$ ]) A8 m6 |5 { // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
- Z9 X7 E) V4 }. f+ @8 V pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
: A8 x0 P6 T8 H( R0 U! u1 c* z5 U
// 处理下一个模块节点
% p' |' l7 E* W pCurrNode = pCurrNode->pNext) U, x1 L3 q! `, f
}+ R) n5 A; b5 y2 r9 X
}
# y) v' l1 {% b else
3 j' G7 F! U; Z4 h/ W- [ {
' p8 H- D% Q7 e* |! u$ d- U/ [ pModuleLoaderInfo = localVar3C; // May not be initialized???
* w5 P0 @! u5 C9 ]9 F+ k }* F5 C& O: g5 q Z7 ?9 s$ B. i
4 a: O0 y Z7 H9 i! Y if ( 0 == pInitNodeArray )1 V/ R9 e) ^+ R; k0 g
return STATUS_SUCCESS;" K/ D+ R# S9 Q0 |/ I
, h1 N' F$ |' W6 O // ************************* MSJ Layout! *****************3 p+ G8 }/ P$ D1 M) D
// If you're going to split this code across pages, this is a great$ x) H2 M f: ~- |7 `0 ]# |
// spot to split the code. Just be sure to remove this comment2 `* C. E2 T* W2 b
// ************************* MSJ Layout! *****************( L; ~) W( N9 A; v$ ]
; }/ P. \- _8 }9 N# o: S //
: G4 j! D5 S4 r2 n) \ // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH# z7 m2 @# R/ T( T" `5 b6 X
// 第三部分,调用初始化部分
3 R, S- w/ z3 Q9 t: c8 i! V8 E9 K/ s try // Wrap all this in a try block, in case the init routine faults$ `! P+ I. k1 Y! h0 N* F# q
{
& m0 B( F: o3 [* r9 G- s nModulesInitedSoFar = 0; // Start at array element 0- ?, `0 _4 n2 s+ i1 _" K3 e7 m0 n
( d- t. W9 j) Y ?. t2 g //
7 T) k+ P1 V% ?% Z9 ~+ | // 遍历模块队列
3 ?; T5 [3 x2 B1 X /// u, E( f, C# w) |
while ( nModulesInitedSoFar < nRoutinesToRun ); K+ G. Z2 U+ s
{- c7 f! {- {* B* |6 d+ q2 v
// 获得模块指针1 y! N6 w4 z3 K, q6 D
pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
# ]# h% R* X4 [' O% h4 @9 { N) B, U: `9 N5 k
// This doesn't seem to do anything...7 k) p7 Q! P' J( F4 f8 M
localVar3C = pModuleLoaderInfo;# z& g8 R! a; P$ A2 i
2 S( V8 ]1 u1 ]% K# W- \5 L
nModulesInitedSoFar++;
3 q# T+ T' n" X6 ~8 z- w$ u
: h y( m' h! J* F& h/ E // 保存初始化程序入口指针) h' P9 P' h! h. N4 Z
pfnInitRoutine = pModuleLoaderInfo->EntryPoint;7 {9 A# R" I* Z; Y/ d$ R
; m* V& X! ~3 d( `$ \4 Z/ T/ K
fBreakOnDllLoad = 0; // Default is to not break on load
8 Q5 r, B1 m4 H) ]3 T( c/ R) g5 Q3 t
// 调试用, T( ~9 U. i0 C/ B4 F- W: W6 \' L
// If this process is a debuggee, check to see if the loader8 U6 T- v4 V% c. w- @
// should break into a debugger before calling the initialization.6 m* o2 a; H; n8 g5 ~ e
//
, p, ?/ ]% U% m/ P% i2 A // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()% V' t# `3 v+ S: E3 Y( z
// returns. IsDebuggerPresent is an NT only API.
* |7 |9 C2 Z+ D% b //
3 v* \. s% t. t% U: i8 M d% P! q if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )( N/ i+ x+ ^" t6 U
{
3 E; F3 q( D1 _2 l# j) o m LONG retCode;
+ u4 i# `" f/ v& {6 t4 H% p$ w; \% M3 @( i& s9 X
// # r. S, |' }3 V9 R* P' }( _% J. X2 `
// Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
- F( Y! {' d' f8 n5 O* ` // Windows NT\CurrentVersion\Image File Execution Options"" b5 i+ @( c6 o [* G
// registry key. If a a subkey entry with the name of
( L" H5 [% Y, D! {2 w // the executable exists, check for the BreakOnDllLoad value., H/ z& N% x! ]' q) L( N2 j: x9 k1 ^5 ~
//# N' v% v7 H" u' ?
retCode =
1 {# A& e4 Z6 x+ Q( K1 q3 w _LdrQueryImageFileExecutionOptions(
9 o4 }# `0 I; ^- K* X pModuleLoaderInfo->pwszDllName,
8 {/ g$ ]8 _) f# R "BreakOnDllLoad",pInitNodeArray
7 f) I% |7 i- r7 e1 I REG_DWORD,8 o$ G ~( A, _8 M% R* A
&fBreakOnDllLoad,
# g' Y9 K6 z; r# l sizeof(DWORD),
# Y& j: c; j- y+ T9 D# I: f& o 0 );/ j4 B4 g5 v1 q; B5 k
7 g+ y( b# _5 v$ _0 {/ [* a5 T) v // If reg value not found (usually the case), then don't
1 |0 D* b; e+ s // break on this DLL init" x1 u! @7 x8 S% J
if ( retCode <= STATUS_SUCCESS )
3 } y2 m E2 |; h2 c9 u fBreakOnDllLoad = 0;pInitNodeArray
, ~8 q- C T( S) O }# Q E% N$ ^% j: \+ O3 ]0 Q
# }/ @: W6 ?& O1 [. ?* U
if ( fBreakOnDllLoad )1 e$ t: I4 n% f! b; P! G5 ^0 x
{ - I/ v6 [0 J: _ U4 \+ Y
if ( _ShowSnaps )
, o' u1 `; M) T2 X( z {
) `- z7 x: p4 X9 k // Inform the debug output stream of the module name) M: j% Y: B! W- T+ |* _
// and the init routine address before actually breaking
. s( b3 x6 i3 Y2 Y z; Q // into the debugger
) [1 r( _8 g& r/ L
& ~7 b( s8 g0 l$ g0 `0 M' i& t _DbgPrint( "LDR: %wZ loaded.",1 ]5 d4 }/ U0 F! O* g4 g: p# J" B
&pModuleLoaderInfo->pModuleLoaderInfo );2 {8 V- J6 o8 d+ ^
: Y& m7 Z6 F6 l" e' D6 N/ w6 P _DbgPrint( "- About to call init routine at %lx\n",( Z) {$ z) }2 h
pfnInitRoutine )
3 S( z( ?9 l! D: l' [& Y4 a/ u }
. ?) C* @, v2 F; L9 F% u- Q3 u 6 l( t7 @3 o" z% P6 F
// Break into the debugger
# h0 G. i |6 \ _DbgBreakPoint(); // An INT 3, followed by a RET
; t0 }! Q* B- B0 V" r. K }
% R, j' w) s' J5 G/ h else if ( _ShowSnaps && pfnInitRoutine )
' C0 Y D2 U4 v2 { ? {
, _# A# r3 t- _ // Inform the debug output stream of the module name( P; {9 _8 `4 M0 J- j9 z0 o
// and the init routine address before calling it
" B! R: z0 _ g _DbgPrint( "LDR: %wZ loaded.",
; l7 s+ Y3 K' l7 O0 L& R: ^ pModuleLoaderInfo->pModuleLoaderInfo );/ }6 @( M6 m$ X$ Q8 T0 ^% D' ?
+ e% ]! o/ E) J: P( w; E
_DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);& x% u3 v/ l1 C1 C% [4 S H) }
}
5 Q3 ^: }5 v; s$ H' v( v
6 {. C' \9 B. k0 _" U* o, J if ( pfnInitRoutine )( s' q! k8 Y4 q8 d p
{/ e! j' n. B' a" g& M
// 设置DLL_PROCESS_ATTACH标志/ G! n& K; s) P- o1 P
//; q7 n8 z* m* g5 d7 i" [
// (Shouldn't this come *after* the actual call?)
8 C; m, i$ W) O+ O# o* t2 v' B //
% m+ r7 M/ c* k$ y P // X_LOADER_CALLED_PROCESS_ATTACH = 0x8 $ e- Z1 p, M) r. n( M& k
pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;9 e- ?; }9 f# O2 A6 Z' i
g$ r1 m+ G6 M1 ~ L
//
5 b+ t' S2 ^, m7 i: N // If there's Thread Local Storage (TLS) for this module,
, Q1 g% {2 D1 i# `; d0 Q // call the TLS init functions. *** NOTE *** This only6 L1 y; k: t6 D5 L
// occurs during the first time this code is called (when
" O! D- v2 M# W6 O& a // implicitly loaded DLLs are initialized). Dynamically
; a4 U5 _$ I& X$ Y1 E p9 w' @" d% ^; j // loaded DLLs shouldn't use TLS declared vars, as per the# q0 Z8 P% g3 F8 ~
// SDK documentation
% Y" P8 I% |$ X/ Z( f' R$ _$ Q // 如果模块需要分配TLS,调用TLS初始化函数
' C$ c* B/ k4 V& M: Q+ Q$ p. B // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时; ^4 ~" u* ?2 s/ k4 R; F3 i
// 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
6 m. W, v! {; g- ^6 g$ h5 V if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )8 @) T6 c: F6 K& q- \
{
) i1 U9 B5 T) i0 ^. J/ \ _LdrpCallTlsInitializers( pModuleLoaderInfo->hModDLL,* p$ k! K9 v- _, B) m4 |) T% P5 ?
DLL_PROCESS_ATTACH );
, C$ E0 ?! W: W5 S" e- q }
( m: r% W; a/ N& ^( |9 H- s
) n! E' [* {) f& `0 d) i3 k5 |& M
3 U, t) R* A( u2 G- _# w hModDLL = pModuleLoaderInfo->hModDLL& D' ^2 u$ ^2 l9 }! j2 K; {
3 R/ r2 R* P. j5 E% T" |6 ]
MOV ESI,ESP // Save off the ESP register into ESI
: ?/ c" _5 e( f( f3 h ! W+ b" Q4 b2 ~7 O; Q
// 设置入口函数指针
5 v c( D/ R' O: [; F6 e. {& u MOV EDI,DWORD PTR [pfnInitRoutine] 4 |& e9 R5 P: g0 ^: z7 e- V
5 M0 E# v. P8 d# ^: ^
// In C++ code, the following ASM would look like:
/ \8 W, K6 y1 e' | //
( k' n6 ?; I4 P% j: j( H) o: g; N6 a // initRetValue =. q/ W+ {: k6 {- m& \, L2 a
// pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
, a4 m, [( ~! O C' x //
2 J! K( @ m% w9 _
8 X" b! M( K/ [" ]8 ]# Q2 a$ a PUSH DWORD PTR [bImplicitLoad]$ L3 y1 L. @0 ~# V; u- v
7 _& \: M- e$ Y* D! O6 G3 J. W# u+ x
PUSH DLL_PROCESS_ATTACH
2 t2 K/ v ]& } $ ~/ l# C! I, ^
PUSH DWORD PTR [hModDLL]: V5 f6 C0 u7 c. U0 W
& i* \ j: s. F% ]8 A6 a
CALL EDI // 调用入口函数) ^0 d$ A- x: `+ v) g4 H5 P
1 c+ G2 \/ `, V2 t2 x, a MOV BYTE PTR [initRetValue],AL // 保存入口函数返回值
' ~) v3 e% L- P0 I' x- `, e* J' O. K* D* `) f% s4 g
MOV DWORD PTR [_SaveSp],ESI // Save stack values after the* g4 q; _. {* n# d" g" q5 B) A
MOV DWORD PTR [_CurSp],ESP // entry point code returns
( G* |9 g, [1 J% C9 m C
% J% Z. Q9 T4 J% R' ^ MOV ESP,ESI // Restore ESP to value before the call
8 }5 f. \" o i: x- |/ l2 u& i% o2 a3 N
//4 K; h+ ~/ I. O; Q. w0 {- ^
// 检查调用前后的ESP值是否一至2 r9 \0 d) X1 K0 s& j4 F" ?; r
//
. j. L8 d6 U/ d9 U: D if ( _CurSP != _SavSP )
' `7 s+ X, T! {2 N7 w' z8 M {( D4 K* u1 f+ q% E0 P: W% T
hardErrorParam = pModuleLoaderInfo->FullDllPath;
0 v% u7 T) _) C" x v( E/ [; j
% C) P& I3 d9 t Q- J& \0 t hardErrorRetCode = 2 l1 z0 p1 w% C/ n' _
_NtRaiseHardError(
1 I; i8 v8 I1 f9 t/ B/ E2 Q+ f1 |( v STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
! ^ p R: @$ D6 w 1, // Number of parameters8 s' V4 R6 y' y0 b- E' M8 m0 l- ]
1, // UnicodeStringParametersMask,+ e' i! U, {7 ~- N# L
&hardErrorParam, ^( ]9 X$ P/ H+ G
OptionYesNo, // Let user decide
8 E8 {$ k9 @7 m+ x) I2 ^$ J0 [+ o5 d& e &hardErrorResponse );
9 V" w1 S" G3 u! g, J8 ? 6 o9 [+ ?& _0 P' D! @
if ( _LdrpInLdrInit ): j0 x* |9 ~% q: X4 r4 |& x& M
_LdrpFatalHardErrorCount++;
1 m* m6 w. ~: o. |6 W
- y L6 `5 g# {0 O, G& W5 B if ( (hardErrorRetCode >= STATUS_SUCCESS)
[% D+ w5 y" ~, U9 k4 O && (ResponseYes == hardErrorResponse) ). Y4 [6 q7 Y4 _ E- O3 D" f
{( h: K! u! X2 G/ J/ G% W
return STATUS_DLL_INIT_FAILED;' t7 W* X6 E$ n' W5 }
}
" Y& h1 F( b! g/ t1 s6 X+ k }3 \! c2 s( k% h2 n
. Y; X) y3 u8 D$ u( x' Q6 a* S //
: L& Q2 Z. F5 a! A( H$ e. o // 入口函数返回0,错误
. K. B8 f2 H7 n* u! Q# }1 c1 t+ c //) S, m1 J$ @& u+ X b3 H
if ( 0 == initRetValue )
) L" i: B( [& l; V' Z# ~ {3 y; }: m, {0 P0 i
DWORD hardErrorParam2;! V8 ]0 g4 g" D% u, ^2 z
DWORD hardErrorResponse2;* q0 z) i! N1 |) X
* r* D* f" E; \# h
hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
4 r: s4 k: T" U7 x* |4 n( s
* k" Q" H( Y! o5 \: h3 a. R; V _NtRaiseHardError( STATUS_DLL_INIT_FAILED,9 }1 U/ O7 ?; d. v. d
1, // Number of parameters
# |; P' s; i8 [# n8 L 1, // UnicodeStringParametersMask
, W; n( M$ h! t8 ` &hardErrorParam2,! n3 ~! k: x) S! W
OptionOk, // OK is only response
* t. R) m- o( z* G &hardErrorResponse2 );4 v3 h! U6 Z' O g( G- q
/ D- D0 j! S2 U9 a' ^" [ if ( _LdrpInLdrInit )9 }6 X4 l& V) O1 R
_LdrpFatalHardErrorCount++;
. h" z! l" R( V8 t3 u. X+ f" v5 V' Y# M! h+ z o, C7 V
return STATUS_DLL_INIT_FAILED;
( i! P, ^% e* A2 o! q }1 Y3 O' S2 D4 y" g; m& r
}% ?2 w* t3 N6 K5 W7 q
}
' ` _; f% }$ L* P1 @+ T* g, ~4 `$ a: Q$ M8 E$ c! j
//: c& m4 I2 h) h
// 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时" I) T7 t$ w- I. U/ |: G
//
3 V: I* b$ b: F, h2 l U if ( _LdrpImageHasTls && bImplicitLoad )
# z& N3 P9 E0 l {) v8 ]2 u4 W# l7 Z
_LdrpCallTlsInitializers( pCurrentPeb->ProcessImageBase,
: k$ f6 N" ?* g+ s1 P" a5 ]+ W DLL_PROCESS_ATTACH );
9 t" }, D0 \$ U+ x6 i; ~ }. }/ s& L" [1 z; Q3 ^3 y
}4 i6 ?6 l: U; G3 H' I) B' D
__finally+ B# y, x4 s& v
{2 s) X7 ]9 {5 P) v
//
7 x% C3 u1 Y7 ]. V! f0 D // 第四部分;9 U. N6 O; q* @0 M) m& j6 Q! B( s
// 清除分配的内存, x# |; L) y7 c, y5 J- b
_RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );5 C2 [: L7 x9 G+ p4 f
}* T* U3 ^9 n% t7 ~% a
' n7 {* D1 B: k( L6 n% I7 X# }
return STATUS_SUCCESS;: t- \6 T& R' x9 t" f
} 4 b4 G, j- n$ x
+ `( T9 Q& ~7 J6 i/ {5 k这个函数分为四个主要部分:
, d" @( Y0 l" G" c" y一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
1 W5 B# x/ s2 Q+ h' c# \; _# Z二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。2 I* U# ^% b* X, g* j, w. i
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。* |% T: j5 b% _8 r) h1 X
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
1 G: _% L+ F! b, u1 ^' I在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。
. q& ]) ?* }! P四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。 |
|