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