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