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