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