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