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