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