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