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