|
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
* i/ M+ c/ d l V, S& Z& H+ X
+ ?- I& A4 r* E( a4 p. aWINDOWS 2K Dll 加载过程
. i- W2 P- g ^5 Rjefong by 2005/03/30' Y4 t8 G6 O+ u& h
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。: o: Y, V4 {: I5 A p! e
在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”。" ]. @2 l/ I2 @, G
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
; Q7 e; j. }+ ~5 b9 [初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
# B- P0 f0 H9 |5 n m 当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。; ?3 W% \6 D7 E* c7 w+ H
在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
~# E% q6 ]' O 接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
( e$ X$ S9 c# ]! ~1 Y" Y//=============================================================================& G& R' V/ L8 c7 J. N
// Matt Pietrek, September 1999 Microsoft Systems Journal; [ I% ~5 j# O# x( z+ S i$ ]( U
// 中文注释部分为jefong翻译
6 _) I$ T2 E6 C4 ]//1 p8 w) {4 r, b# p/ K1 R
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
: T6 S `, L: k! D3 Y# X2 w// V3 Y' R$ U2 p$ m3 Y" I
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;3 [9 B& i, C. `! K4 r& X( |2 H9 {
//=============================================================================
% T- }# ? j [' N+ L0 s# P- K
#include <ntexapi.h> // For HardError defines near the end
+ g# c0 b( Y3 q6 z ^, y1 @; K+ m8 [6 e4 ]/ c* ^8 J
// Global symbols (name is accurate, and comes from NTDLL.DBG)6 r* ~2 ?5 j% B( C, \2 g1 @0 ?
// _NtdllBaseTag
3 R+ `" ?- W. d7 Z, r// _ShowSnaps
) ?6 w% Z. ^* I7 l- ?5 F# z% ~// _SaveSp4 h6 D: K2 [5 K' m( G, N
// _CurSp
6 k, w7 a7 D- q) h* ~+ g" G$ `// _LdrpInLdrInit
& [: `6 A, i9 A: H// _LdrpFatalHardErrorCount
8 t1 Y6 j( v8 J* ^// _LdrpImageHasTls
7 ]5 D( L8 h* } V: M* N! O$ W: A- W% y$ g2 R
NTSTATUS2 c" s* r4 z3 J6 h+ i& I. @ H
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
) [- c7 _$ }- q/ t3 F( `{
/ p/ M( [: n1 \3 e // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
$ l; @7 m. W6 E9 m8 c# u' d unsigned nRoutinesToRun = _LdrpClearLoadInProgress();6 e. `7 i: w% H( d6 c
, G9 U G: h* {- v if ( nRoutinesToRun )3 X( Z4 o2 ^$ P9 y/ U
{3 }+ C& U) B+ W0 _6 M1 o, N
// 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
" h8 b9 l P' x8 I8 X- x pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
8 m* y; c. }% v _NtdllBaseTag + 0x60000,. I! Q* l9 T ~. s! c A0 W
nRoutinesToRun * 4 );
1 p" r$ Q- H3 b3 \7 Q z+ n1 {) a$ O
! r1 z; z- t# m if ( 0 == pInitNodeArray ) // Make sure allocation worked
* P6 ^- v9 J% r- k- d5 l( f return STATUS_NO_MEMORY;
4 e$ n6 H8 v) x) E8 s }
9 K/ B* ]5 [+ f4 d8 X& f1 P else
+ F3 h; W# O* S3 J. N. Z7 R pInitNodeArray = 0;
) I7 c) h5 L: a: R; |; C1 ]8 d' S9 t+ b
1 `2 q3 {5 h9 l! z a //第二部分;
9 B; e1 D2 Y" G //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。# }% I# o& D' W6 }
pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);3 B; a" n6 q0 K- |! S
ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
' V( T* u+ t+ z
! T& G- _! w# l* l" E$ l if ( _ShowSnaps )0 a$ l1 T2 h9 K; E1 d0 `
{9 m% q/ o' @* e% L& l5 n
_DbgPrint( "LDR: Real INIT LIST\n" );
) Z, t! g" w5 G" |! ?+ o }
" _+ w. @3 l# a8 K; |) a1 x0 v, E* S8 I) t, P6 w; W
nModulesInitedSoFar = 0;1 R: C* Q+ ~2 T: P
9 A3 {6 O! T$ C
if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块; t. r% e' k! ?9 r' [! m
{/ m5 v2 V+ r& o# Z
6 l& d1 h% A; l0 h" F0 } while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块# Z+ u/ W% k6 m" i- _; a
{
0 P4 o3 J, ~% s% H" X, }2 ?3 P ModuleLoaderInfo pModuleLoaderInfo;9 I5 f/ J% T8 X4 Y8 K- s5 b1 C$ B; B
3 s+ s3 w; U$ F: Y$ u$ @
//8 w+ T5 [3 D( z; z
//一个ModuleLoaderInfo结构节点的大小为0X10字节. M% ?5 o+ d4 S- \. q
pModuleLoaderInfo = &NextNode - 0x10;
. Y" ]: Y3 r: V* O # ]7 Y; h' B* N$ T2 t+ i/ p
localVar3C = pModuleLoaderInfo;
# }+ U6 F! u% B2 y$ c5 u; g. W; Y8 J0 P! |. |
//
1 h* s; d4 j2 i( J& I: d // 如果模块已经被初始化,就忽略
4 ~1 n( {: F M; { v // X_LOADER_SAW_MODULE = 0x40 已被初始化
' c1 X+ s1 t$ f- e4 T( o if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
4 A y/ I* s1 V2 T* `2 m1 E {4 H* I5 V, y& u5 E, z6 ^
//
9 \" O; j3 y5 P3 S1 U // 模块没有被初始化,判断是否具有入口函数" q' g3 U: U5 k# A& W, m; {
//% Z7 i; l; y: T) {- j
if ( pModuleLoaderInfo->EntryPoint ) X" ^2 q- o3 x# L" `. B
{: a# p S g2 i+ e1 j
//! }% W( [% g+ b; y
// 具有初始化函数,添加到模块列表中,等待进行初始化
2 k) O7 m* m$ P2 k% n4 A+ ?4 s pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
# d- o# g5 H/ Q# {5 N
) T# [* q8 V8 u# e$ t+ C // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
( n5 Q5 T% r& {* q1 _ // 例如:
& @4 b! r* o. a$ ? // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
2 h9 N5 h$ n2 p3 ~7 a if ( _ShowSnaps )
" l! @& G- e( [4 l" ?7 z {
& o9 k5 E# ?! R' L- L2 @# b$ K& p _DbgPrint( "%wZ init routine %x\n",2 M+ U! V" A) |) u
&pModuleLoaderInfo->24,
. K$ F/ F. Q( N1 W1 j/ W pModuleLoaderInfo->EntryPoint );+ `6 C1 c& ?, z3 ~4 I
}! T8 D$ A' `) x' b2 P
( B; T# t2 }( ^; _+ z R- W" ?1 U nModulesInitedSoFar++;
( g9 l0 g: A5 W }5 r( O/ t3 o& |% \$ o) H
}
6 l! H$ O* v0 l% K- c7 G6 U# M* v& z( b
// 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。& g' {! m3 C- P) M* K0 q# B
pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
' s/ }- ?/ c' _3 q% u$ P+ ^) X6 y3 A3 k5 A6 C) w
// 处理下一个模块节点
+ Z0 l* D7 ]; i& C! o B pCurrNode = pCurrNode->pNext$ \' k4 r6 W) R* b
}
( ]+ W2 g; t4 Z; J& n }( g/ n% U8 Q$ F# \0 V6 n4 v
else
3 r; m8 P5 n! w; q {
" K3 S" G6 u3 u. I3 G& | pModuleLoaderInfo = localVar3C; // May not be initialized???" b% x- \1 u* n9 l4 a' H4 q
}
% Q* c; u$ n+ R x4 D0 o" m " w! d4 z$ ?# A# N9 X" C2 U1 Q
if ( 0 == pInitNodeArray ). D2 I/ C, U) C# |
return STATUS_SUCCESS;
9 v4 A0 Y. m% l/ P
6 O! s" a3 B% D! k5 ` // ************************* MSJ Layout! *****************4 C* ~ B* E2 u" {
// If you're going to split this code across pages, this is a great$ ^0 D- I" L9 L1 q' X' S7 `1 k
// spot to split the code. Just be sure to remove this comment* f& u& {1 i2 T
// ************************* MSJ Layout! *****************& ?* M; D+ h1 I+ B- f2 m
. D! W W7 \- ^ q8 {3 [
//
# [& \) @$ s% r // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
- ^$ K+ e/ S1 r7 I8 S- N, I9 w/ ~ // 第三部分,调用初始化部分
8 }0 m4 U2 U. E1 p; B try // Wrap all this in a try block, in case the init routine faults
6 u* W9 r' Z* ~- M {; a- u, ^' M/ O) b7 J% t$ ~
nModulesInitedSoFar = 0; // Start at array element 0
5 p# q- ?5 ?; w8 C' W0 D0 }; }8 ?& v: Q& r. v
//
9 w# o) ~8 }% ^5 j4 v // 遍历模块队列9 b5 E% ] B% m0 m
//" V& o$ l, `# e$ B1 o5 G
while ( nModulesInitedSoFar < nRoutinesToRun )
$ {1 k4 p8 `: E* C2 C0 I4 b6 ~, V {: E$ z f% v0 W, K U) }
// 获得模块指针
1 e- P0 H2 L8 z" {- B) _* s pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
$ V# a$ v7 J4 y; E1 _ T/ h+ U
+ p5 { \+ C/ `/ v+ } // This doesn't seem to do anything...+ c6 r2 @4 i' Z$ Q
localVar3C = pModuleLoaderInfo;
4 ^2 x& o t+ |) F* p
: d. A$ P* S: @$ H' _ nModulesInitedSoFar++;; R: b0 l0 e; Q8 ~9 w1 K4 r b
. e7 Y1 K7 T( K# K: l // 保存初始化程序入口指针
[7 B9 e+ r( a' I$ ?" T: ^3 u. t pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
* Z7 e7 s, G+ J" D% P; J4 A: s* i $ B5 w' q; X* `7 U9 _ P
fBreakOnDllLoad = 0; // Default is to not break on load! \% S' |# e& U0 V8 _9 e% o5 d* o1 H6 U
/ [8 v% D4 G R( W- Q
// 调试用
! j8 ~/ w* C* v$ N+ _3 K0 m // If this process is a debuggee, check to see if the loader5 _6 p5 t' |0 F( p7 }
// should break into a debugger before calling the initialization.6 i A/ B; ?$ N3 b3 X7 T
//
- |) f2 `6 v# L; u. ^# y* \ // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()
% G3 w& [' x1 }6 G% [3 |. \8 M1 ~ // returns. IsDebuggerPresent is an NT only API.
; K6 v: h: H) M$ ]* l n //2 B4 ^! N) I; j& ]1 u
if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )3 B) j: O, Z/ G! u+ F8 j1 f
{
$ ?* g4 X1 p' f% B LONG retCode;; V; G$ Y2 d* y$ T2 z$ Q. M, t
6 K4 Y$ [$ V) g3 e' m
//
1 c1 Y J1 H9 O1 X# W T. |( o/ k& d // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\/ E. |5 {( a, d+ r! D
// Windows NT\CurrentVersion\Image File Execution Options"
1 P* h, _) k2 b e // registry key. If a a subkey entry with the name of
0 X% `% X, s, V0 f // the executable exists, check for the BreakOnDllLoad value. ?5 L |0 q! c4 u
//
v% g4 w2 p% W' O* B: w retCode = 5 O t/ G. ? M+ s
_LdrQueryImageFileExecutionOptions(
7 }1 f& {: T5 L pModuleLoaderInfo->pwszDllName,
( c) a3 W0 Y1 D" q "BreakOnDllLoad",pInitNodeArray
7 O. {8 W( ^ j: z+ F. j' [0 b; O REG_DWORD,
' M3 b* C2 _1 J+ ~, P* N &fBreakOnDllLoad,
' \3 I1 S' k' \ sizeof(DWORD),
# @# `9 q, L& ^ 0 ); p! P. u/ G+ ?9 `& ^% f
' q- Q7 F4 }" ~% r; P // If reg value not found (usually the case), then don't( a2 h( G# e% w( [
// break on this DLL init( Y2 J. @, E( y
if ( retCode <= STATUS_SUCCESS )
5 \ b8 P2 s/ W! s g7 L fBreakOnDllLoad = 0;pInitNodeArray
$ U8 K2 F! b0 O" a2 j" H3 } }! U7 H/ {9 E' ]0 u$ Y. D$ y- D1 g2 |4 S1 W
4 Y7 a" o3 H& x/ \/ o- U. h if ( fBreakOnDllLoad ): l' x" K7 z* U- q6 C& K4 A; h
{
1 d0 o+ S8 h8 Y, c. `4 Y' l if ( _ShowSnaps )
/ [) S, S" H. _* U+ N0 a/ M4 v6 q( v {
* t2 F! m) |; E // Inform the debug output stream of the module name
- n1 `% q6 N( t# p; O: F( |5 n1 b9 T // and the init routine address before actually breaking6 F7 A% G* S. y) @) w% [
// into the debugger
0 U0 T. M( r8 K7 {- B8 L
& `( D6 t' p) B% g/ E _DbgPrint( "LDR: %wZ loaded.",
" |0 ~. d5 p$ }; X &pModuleLoaderInfo->pModuleLoaderInfo );* g$ r' m1 `2 _7 a0 l$ w1 J
% y8 n0 @5 U4 A9 S) g0 Z7 N
_DbgPrint( "- About to call init routine at %lx\n",
! K& a8 V& f7 \' a3 e* g9 G pfnInitRoutine )& K& C) P# l! j) M3 C2 R7 t
}) B& V+ a4 k0 n& O2 F v
2 g% k6 S5 \- k: S0 `2 S8 |) { // Break into the debugger
0 e4 _0 u4 a' J& R5 X _DbgBreakPoint(); // An INT 3, followed by a RET+ X- h" W$ P0 j" i; b: p. ^
}3 l) J( `3 q& v" ]& N) v5 M
else if ( _ShowSnaps && pfnInitRoutine )! W: v! {, k% m0 S- i8 o
{
5 @$ b$ Y5 R. S7 | // Inform the debug output stream of the module name/ E; U% B5 {4 N, T: d* l6 Y
// and the init routine address before calling it . x7 |9 w. S$ X7 E* ?* L
_DbgPrint( "LDR: %wZ loaded.",
8 q/ ^* g k3 I' E0 K" t pModuleLoaderInfo->pModuleLoaderInfo );
) b- [, k' i" B: A+ S W) [* T( ]) i, x1 N% c9 h" r
_DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);% u! Z0 M/ V8 Z8 A$ [
}
- J; J3 ?1 j% W! u6 ]0 h A5 G# y
) X- q* \# G* v; P if ( pfnInitRoutine )
1 H0 u# D+ m/ {. h0 ~# w {/ n! D) ~) f4 p
// 设置DLL_PROCESS_ATTACH标志! T+ s. g, g; ]' P' K# P/ H2 g
//
# Y# e* Z% ]( ]; ?* V* F7 W // (Shouldn't this come *after* the actual call?)# q/ C3 z& @+ h# ~* _
//4 s* O1 e* Z: }
// X_LOADER_CALLED_PROCESS_ATTACH = 0x8 - i# x( E8 ~+ ~1 {: S( b" S
pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
- d' U1 h" T5 ?' r8 a
$ x$ r- F6 D; ]2 Y# w. } //4 z! N7 l# V" ~1 m* `5 [
// If there's Thread Local Storage (TLS) for this module,4 U7 @' K7 B: L! R: P
// call the TLS init functions. *** NOTE *** This only
& x* v" z5 e# {1 i" ~ N- Y: @5 [" W // occurs during the first time this code is called (when) V2 g- {4 L, D8 D" q. u
// implicitly loaded DLLs are initialized). Dynamically
, o$ N! W2 z: `) _ t/ ^" k+ D* p, ? // loaded DLLs shouldn't use TLS declared vars, as per the; e7 J& T# b8 |) f: ^/ }; r. X
// SDK documentation
" h2 a6 _# G8 k& F3 _$ w // 如果模块需要分配TLS,调用TLS初始化函数
/ Y* A$ }6 T. M# I5 o // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
2 O2 K8 Y5 `& C# s+ g // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量8 r' H; G+ s0 S9 g' `/ I
if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
4 ?4 |1 d/ K4 v; h9 S! j {. U/ U* Y0 Q" P) i V
_LdrpCallTlsInitializers( pModuleLoaderInfo->hModDLL,* `$ X/ v* w) K
DLL_PROCESS_ATTACH );( h5 ~( B# o* O/ S+ H5 r* H8 b; u
}3 N3 r5 J8 s$ {+ W/ Z
o6 G$ A6 [4 m% Q
$ w( B- [# X6 D: D hModDLL = pModuleLoaderInfo->hModDLL+ Q/ p0 d: H+ e3 n
$ }+ z$ t9 @ u/ g7 _# w
MOV ESI,ESP // Save off the ESP register into ESI$ x) ]0 l* B0 h
: L8 U" Y0 k9 q
// 设置入口函数指针 2 E& r1 q+ T2 d
MOV EDI,DWORD PTR [pfnInitRoutine]
( P# M" ^. p# C" M5 X5 L; T/ L7 ?* S# d! Q
// In C++ code, the following ASM would look like:
* y- p. Z5 Q1 n3 } //
0 d+ G! y. o, ^8 M* b/ u9 N) A7 } // initRetValue =3 r) T" n+ W M* I2 k2 w# H1 \
// pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
+ ^9 _) [- H! @6 | //3 v- D; }+ D& z
! f x2 ~9 ^4 O. y( ^/ `/ E
PUSH DWORD PTR [bImplicitLoad]
/ W$ g. V9 X7 `$ N& N+ c3 L. |
" ]3 m; l4 m1 j+ P# ]) Z: Y2 k PUSH DLL_PROCESS_ATTACH
2 c* r/ \' z8 D" W & O. y! V! W; H% m$ k" x
PUSH DWORD PTR [hModDLL]
9 ^* C3 k' \3 r+ [9 `
5 w- h/ X) L/ c7 _1 t# Z$ q" e CALL EDI // 调用入口函数
& |3 F5 D6 F6 Q3 M$ O+ ~ , c2 T7 v3 [. x: q5 ~8 ?
MOV BYTE PTR [initRetValue],AL // 保存入口函数返回值
: S1 r9 V. {' X% N& R7 }8 \7 ^1 x) t `
MOV DWORD PTR [_SaveSp],ESI // Save stack values after the
1 _7 i: _" h7 B9 } MOV DWORD PTR [_CurSp],ESP // entry point code returns6 v9 l/ r; ^4 f3 B: A4 i/ G
. y: R, P3 W: O# _+ p( G
MOV ESP,ESI // Restore ESP to value before the call; j3 @9 |1 N% S6 Y2 C( F7 n5 m
5 p; F5 Z& w8 j" q! ~; A! i; O' e // E1 _& C1 {+ T7 C
// 检查调用前后的ESP值是否一至
7 O2 K% R9 Z! z! g // 7 j- N9 V+ F) D% w- Y" m+ O
if ( _CurSP != _SavSP )
& n6 {0 `5 o% {0 q {% k1 V3 }$ R: f: w* Q; d
hardErrorParam = pModuleLoaderInfo->FullDllPath;9 U g( P5 h! D# p6 N
* @$ C- T; E! I/ g1 D3 F. K: u! q
hardErrorRetCode =
; Y' p7 z0 |- k9 p _NtRaiseHardError(, g+ }9 `! l5 d* y
STATUS_BAD_DLL_ENTRYPOINT | 0x10000000, D+ H- D/ B5 f
1, // Number of parameters) `9 t: I! A* P6 M+ U( H$ l
1, // UnicodeStringParametersMask,; j4 Z5 {( s( E" j+ `. I
&hardErrorParam,
+ U1 Q# m9 u* z OptionYesNo, // Let user decide4 F5 X2 c6 n7 j! @* q5 I
&hardErrorResponse );$ I4 `% C/ p- a
: d# T0 Y2 j. w7 V
if ( _LdrpInLdrInit )3 |" P/ L' c' ~/ v2 `5 S
_LdrpFatalHardErrorCount++;' A1 D+ V: m; Y3 K
+ R* B' P7 L1 D9 _% E3 B( X if ( (hardErrorRetCode >= STATUS_SUCCESS)7 F( P& y1 F1 ]2 M# w
&& (ResponseYes == hardErrorResponse) )/ z0 c2 ^) O! u; N" ?, o5 q, n
{
# \3 m. o6 M0 u f# d/ _6 t return STATUS_DLL_INIT_FAILED;" ~* k% g0 _" ~0 ?* k6 @" ^
}6 M2 ?* Y( f, J
}
9 x: O% f7 m+ {2 _& U/ G3 {2 W0 [; Y2 _# s6 s2 v
//
" f1 L- ~1 j D# L // 入口函数返回0,错误
( e5 X" {5 w- Z4 E1 S //4 x" b6 y/ h1 M
if ( 0 == initRetValue )7 @$ i& X" ~% B$ K
{
) Z6 q; ? S4 h* y) c. Q DWORD hardErrorParam2;0 H' |1 j) |- p) Q9 I
DWORD hardErrorResponse2;
2 J! W* Y7 u& H- f7 U G4 g( p - ]" Z( a$ v/ d* T5 a7 t
hardErrorParam2 = pModuleLoaderInfo->FullDllPath;2 n. X9 E7 Q* `0 c( `& h* S
, u3 V& t1 }" ]/ Z& U
_NtRaiseHardError( STATUS_DLL_INIT_FAILED,! b6 R' P- ?' i- E, }0 O" `
1, // Number of parameters" r' @6 h6 B; c" P* G3 u3 B
1, // UnicodeStringParametersMask3 ?3 ^; e! `8 K0 b6 Q" r8 i. X6 A
&hardErrorParam2,
4 N% F5 ?2 r# E4 ~ OptionOk, // OK is only response `& [4 G! t2 e. I$ r9 |' G$ _% K
&hardErrorResponse2 );! Q3 v" M4 A6 c; H4 L
. p. |. n3 O. h
if ( _LdrpInLdrInit )$ R9 @7 [: ` W* R R, Y5 V' g
_LdrpFatalHardErrorCount++;. I u# I/ l' h; S1 Q4 h
. i: y4 ~- b6 W$ |. U& I
return STATUS_DLL_INIT_FAILED;
7 z l9 ~/ I. H$ Y& N& o2 z% S& ]% ^ }
! p8 j! [; U J1 W2 l }
7 ^; _0 @" n1 D/ j( S) { }
9 S6 D* n" M/ u$ G* {6 N
. @: j* z) u& B1 R4 O& I) p //
; K4 y& `% A1 O4 T8 o. H1 h6 k // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时' Z0 r$ \, G9 |( `) a' m6 g
//
. T% {9 Q. P% K% m. h' { if ( _LdrpImageHasTls && bImplicitLoad )( `& X! e6 c, R) T& |: p
{3 |) z5 W( m6 m' g
_LdrpCallTlsInitializers( pCurrentPeb->ProcessImageBase,
7 u5 M3 B7 c) o& O7 J. ` DLL_PROCESS_ATTACH );& G8 @: a- u- z5 T' o2 K, @) x
}8 o' n& z* Q& Z' C, _0 h
}2 i% Q+ m3 |7 J5 Z" p+ Z$ B# I
__finally
8 O8 O l9 c: i& ~* a% A' h6 K {5 N) m; x/ a1 @
//2 w- B- ?/ q$ u% Y6 q2 a
// 第四部分;
/ v; r# u0 p* U* ~9 p // 清除分配的内存
. H! V7 H" ?" [5 q6 z _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );$ W# r9 @5 }7 W/ k% N2 H
}
" \, ~' N; }6 B K5 S
: \$ l+ e5 N- K( E0 c( L( Z: ^ Q p return STATUS_SUCCESS;
5 H l9 I: t# R+ r# {! w# V} , K3 e G2 n2 Q7 b# G4 Q. i
& h% x6 b1 d! _- l5 @这个函数分为四个主要部分: T* T0 ]% [/ `+ s( l, Q$ J( D
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。0 O) W6 T- r* a7 {. i4 {
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。# I) H# X+ ]0 j7 s
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
8 T" ~3 M% Y% x8 k7 k( k( s, v另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
8 J: t7 `2 p. r. }5 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。
2 C" W( y6 v+ l ~0 n! I5 o四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。 |
|