|
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
7 l3 b1 ]9 p( y3 S9 t6 f1 i2 W+ T5 m3 w3 l
WINDOWS 2K Dll 加载过程; X& h* l. r4 A8 \5 V; A) b
jefong by 2005/03/30
1 M# G% ^8 I) u$ m& ^5 @' B这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
. q$ I( f1 T" ]5 F6 B; E在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”。
4 @5 T; Z' N. E9 {你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。3 t* J: k$ Y& J! M m: c% V
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
) M* m. M$ M' K$ S1 ] 当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。$ I9 i2 e+ X8 a3 I
在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
t. h9 c/ O- }9 N8 \9 p 接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):3 l- J6 K7 C" F5 v5 H, j$ w
//=============================================================================$ c1 n: @" [5 Y& L1 X! t. y
// Matt Pietrek, September 1999 Microsoft Systems Journal3 q( Q/ n0 f: f, H( [
// 中文注释部分为jefong翻译
6 m3 i4 I* @& Q# J7 J//1 j! k8 t, L9 j( M# h6 M
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3). d; w& J( A; O
//
+ Q. G8 |7 e6 {: Z0 [// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;0 m2 Q4 d! B& J P' s
//=============================================================================! ]4 U/ @" |4 \6 X' R
2 m1 E# }8 v$ ?, Z' R1 @! ~2 [2 y7 Q/ f* G#include <ntexapi.h> // For HardError defines near the end# d1 u5 ?9 L' ?& M' q$ a+ \: R3 ~
+ z* ~* |/ I. _% X// Global symbols (name is accurate, and comes from NTDLL.DBG)! A n$ ]* _( Y7 ^1 h
// _NtdllBaseTag
7 }( U% Y) d4 o6 u* D' a" n// _ShowSnaps
% J! ~, x w( S5 g// _SaveSp
+ p4 V8 ?& w' m// _CurSp& G8 o6 U; y5 S% Z
// _LdrpInLdrInit
7 ^; B; @" K `// _LdrpFatalHardErrorCount
1 G4 ~; K: N1 [7 t/ T// _LdrpImageHasTls
7 }; x' |5 d' h# i: g" M( h! y5 v1 ?
NTSTATUS5 p' t. Z) v4 k% Z
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
: ]" k# F& }9 V/ C" m) u( o{
. P# W1 P" g" C3 S. K // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了 C6 D8 F0 D6 q5 {. o1 K& s
unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
+ Y9 R- o' V. a' N. o) c* v2 l9 e& y) b* f( K, }: d
if ( nRoutinesToRun )
# i& M. d9 u. [ {7 U4 e! Y( Z8 d9 Q
// 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
7 X8 V0 x, X) _; C2 {8 l$ D* [ pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
}" J& T; j% ^( w6 q _NtdllBaseTag + 0x60000,( y V/ ~# t$ n
nRoutinesToRun * 4 );8 J0 V$ \) L2 F2 d. S4 G
9 z5 t; I% H. I9 s0 p if ( 0 == pInitNodeArray ) // Make sure allocation worked9 ?+ I/ ~9 H/ i G5 l& t* y# h4 N" U
return STATUS_NO_MEMORY;' D! ^* ?- A+ M W7 c2 l* k m0 Y
}
6 q: c, G* h9 p3 ] else# Y2 a8 S8 ]2 I
pInitNodeArray = 0;0 B# M) A2 P( t1 \/ F, G8 @1 o7 l: E
' h, @6 m, i0 h8 C0 ^" W
//第二部分;# [1 }4 U) n& n% J3 n. X- R
//进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。% @( M) {. M% H8 q
pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);/ l: \9 R/ u: _2 {. W, u
ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;+ u) v# d& c# ?
/ _% A2 H* [' x' T. d: e1 V
if ( _ShowSnaps )
* }+ A5 r- h* B0 n) y, }0 a4 r { Q. J+ {" W3 ]
_DbgPrint( "LDR: Real INIT LIST\n" );+ n0 Z) E9 S- {# ?7 K; h3 V& m7 `
}
' F2 q; l* N$ M! b, c m' r4 t$ B" r
nModulesInitedSoFar = 0;
/ V9 X" p( d: R) i% P+ e! \7 p* I9 P* V% Q: Q" } Q
if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块# d$ D: o* R5 z7 k
{% k" u; H9 y, V& f) | P
: a. y+ ?! l; W: z while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
2 ~# h6 p6 H. G {
u+ {4 h% p2 \" b+ H: D. i5 Q ModuleLoaderInfo pModuleLoaderInfo;
" m% }4 m, @: l
5 \% y, w: T7 w3 Q! a //6 ~3 W; \) h @ w6 N9 ]
//一个ModuleLoaderInfo结构节点的大小为0X10字节3 e/ x0 [ I; U+ G) T2 z! l
pModuleLoaderInfo = &NextNode - 0x10;+ A. T D: `* ~) K4 ]# f' f
% t2 s8 B# o; b, w. Y
localVar3C = pModuleLoaderInfo;
9 N7 \9 f" E$ s% [$ l2 t1 T/ X" n4 a ~! [
//
: B6 C. \" g+ a7 Z# J# }3 I // 如果模块已经被初始化,就忽略$ n" Q/ V* z; g$ ^2 K/ U4 E
// X_LOADER_SAW_MODULE = 0x40 已被初始化) N6 L# o, j) k6 r& x4 u
if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
1 K# z0 u) X- H, A {
9 {! G1 y( ?0 b! i3 @: @' D //
! E6 r5 {/ Q1 D" L, O/ m9 _ // 模块没有被初始化,判断是否具有入口函数( o: d7 b- \9 D9 z- d2 h2 u
//
/ u5 c; T. e# C0 V! u: w/ O" o" P9 j if ( pModuleLoaderInfo->EntryPoint )5 P) v5 Y1 B5 V* H0 j2 k6 C9 V
{
3 G" G1 {# m; Q2 ^. Q G5 w- G3 o. [; ~ //
( t) W' w1 T" J1 R, S3 R5 o1 C // 具有初始化函数,添加到模块列表中,等待进行初始化, m9 n9 |' k: s, o2 A. b- y
pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
: K( [: q Y U \& l$ N8 ]- }. O1 p0 V- y" ~5 ]0 G9 T" K
// 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址0 d7 y) U# h3 s
// 例如:
" o% m6 y! A/ o2 Y) Z7 A* a // C:\WINNT\system32\KERNEL32.dll init routine 77f01000: s) h5 R8 j" f2 c/ C/ C( k8 o
if ( _ShowSnaps )
; d* Z9 B4 I" N* v0 U$ T% i {
6 @1 L( P7 m9 ^6 S- ?' w v& p6 p+ m _DbgPrint( "%wZ init routine %x\n",, r2 a, F% ]* d. K
&pModuleLoaderInfo->24,
% ]0 @1 x- H0 Z- K# v9 k pModuleLoaderInfo->EntryPoint );3 ~ `* g* w0 [9 T0 R! `: {9 }4 T
}
; C5 n/ m& {. y0 b' l w( X) F" S; o4 _7 D2 \
nModulesInitedSoFar++;
. W" \2 j: K, N' A' g* ] }- c8 z# Y1 {: ]0 T$ w% |/ i
}2 D: J; L8 X. ^- m: ~5 C; D
: {8 \3 z7 r7 ^$ y
// 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
7 D: ?! z6 ?# R9 z. v2 j O1 {5 ] pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
) h# Y! O1 g2 j) y8 N0 L# `, K9 ?9 K( v |+ B; J- }
// 处理下一个模块节点
/ { A% k6 q" Y) Y pCurrNode = pCurrNode->pNext
% l& w: b" ~& S- C+ j2 o9 i4 A# K2 c }4 k) `* u$ Z) g4 t
}
: }) `3 @4 n6 h8 f3 g! h4 Z$ a* [ else, J* ?3 }: n( T w2 n
{" M5 j4 G/ o' [/ s+ n# M+ D% O" G
pModuleLoaderInfo = localVar3C; // May not be initialized???
! J$ b4 U% X5 I }4 h4 i) I5 Q( @% G* Y1 h: r
! Q& A/ Q1 L# @8 A3 b S U
if ( 0 == pInitNodeArray )
/ H& m9 T7 c6 e return STATUS_SUCCESS;. ]# w+ |; \) b3 p
) I2 A: B: h7 R+ a v3 W // ************************* MSJ Layout! *****************3 s7 I) w$ D8 ~% v" E2 ?
// If you're going to split this code across pages, this is a great
. d: w X: o0 x2 W8 U // spot to split the code. Just be sure to remove this comment
# T% l6 s9 \, w( x // ************************* MSJ Layout! *****************
9 @' ]7 D, |6 ]3 ^5 o0 J5 p! h / P8 x8 I1 L% g9 L$ k; S' @ S# t
//' h9 K3 j* J% _8 a* p' g
// pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
' p9 O' b9 W! i4 }/ D5 n // 第三部分,调用初始化部分
% u3 w5 N" G9 A+ ? try // Wrap all this in a try block, in case the init routine faults
9 Z& T h+ A3 A; e5 R2 E {
9 `# ~# {' p. o- X/ e nModulesInitedSoFar = 0; // Start at array element 0
- L8 M9 Y1 u w! q- z& `. G" z; ~9 S, v6 k Q* b5 X
//. K- N, A$ S5 O$ k
// 遍历模块队列
) c; G! _1 |2 m+ s% C) W // q9 I C" `5 w
while ( nModulesInitedSoFar < nRoutinesToRun )
: `1 _* p/ [6 J5 H. Q0 @ {& y, k. D7 P; _# K8 Z, q
// 获得模块指针7 ?7 g# b$ X# h7 v2 t- U6 d
pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];+ i( b( @9 \! l x" { D' D
D" }/ \3 x: M' p z* y
// This doesn't seem to do anything...1 \# Q) ?+ n- o! n/ c$ b; @/ S H2 L, e' x
localVar3C = pModuleLoaderInfo;! y3 l9 ?+ g7 Z+ `) o
4 W+ [/ ]/ V! n0 M6 w5 ^
nModulesInitedSoFar++;
% v# N- G+ L1 k
2 r+ z0 O/ W6 O7 P; m // 保存初始化程序入口指针
4 c" P. s9 c- B( g8 G7 j pfnInitRoutine = pModuleLoaderInfo->EntryPoint;- U& G% a4 L$ ^$ e* o
4 P8 [1 p; e8 c: E0 u Z l fBreakOnDllLoad = 0; // Default is to not break on load$ c N9 `/ @" I' F/ U# C% A- w
. b! u2 M8 C/ E& V // 调试用
+ w9 [% x& F# _0 h4 W! v k // If this process is a debuggee, check to see if the loader8 y! |. V7 s9 ?- h# b
// should break into a debugger before calling the initialization.
# Q# N& p3 ^; g7 L, K B //5 g+ ~4 ~7 l; ]3 P
// DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()8 |+ }6 c! H2 `& k& {
// returns. IsDebuggerPresent is an NT only API.
1 G6 {& F8 I2 p) |, H' E* a //+ [% a L/ g# ~2 E6 g
if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
3 i4 a# H: v ^( \2 d {
2 \2 Y" U3 o1 s( e LONG retCode;- o, |. \' G& ^( o+ I+ h
6 c/ {' H5 \1 I+ ~0 j
// % t: O- r G ~% [* q) _1 D
// Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\8 j* b" A2 ^* J" [9 r
// Windows NT\CurrentVersion\Image File Execution Options"
+ i/ y5 M1 z9 g5 I1 ~- [+ h // registry key. If a a subkey entry with the name of
4 G( x) C$ W5 _$ @) D // the executable exists, check for the BreakOnDllLoad value.
5 L; Z( u g' { //
$ J) W% j+ f1 o) B1 f/ o retCode = ; r6 V6 b& R$ k; C5 X& y: m/ f
_LdrQueryImageFileExecutionOptions(
8 `- g- Q3 m0 @- j9 n, T* Q pModuleLoaderInfo->pwszDllName,$ u* J6 D+ E3 @" r0 o% X! e
"BreakOnDllLoad",pInitNodeArray
+ J2 P! F: y2 K: g/ V5 C REG_DWORD,' z M; y0 T) h% X% S- E* W& k
&fBreakOnDllLoad,7 i: m! f! c8 q+ T, V5 U- Q8 ]7 m
sizeof(DWORD),
' d; E9 w) E( p& \8 P7 M 0 );& e2 r. B: N% }
) d M$ J( N6 D6 P" F
// If reg value not found (usually the case), then don't+ `; w4 G. {6 T8 i
// break on this DLL init% a5 w* |$ b0 ^1 e& s- i% w |! i
if ( retCode <= STATUS_SUCCESS )
& I. b! Z# T5 m- O8 c5 U fBreakOnDllLoad = 0;pInitNodeArray; m+ v& [6 o* s: l+ ?( [
}! m- I- t1 Y- H
I; _# n# j; P6 A7 V if ( fBreakOnDllLoad )9 d( b* t" v4 t. G" h% |7 u* b
{
3 _4 A z: j+ n8 e- p6 _1 o3 i+ ^6 V if ( _ShowSnaps )0 N8 z" R4 N4 U" o) }* n' n
{
# l) i2 r5 O* W3 M/ u5 ?0 z // Inform the debug output stream of the module name6 r& j( S6 }/ l6 ~- \
// and the init routine address before actually breaking
, Y7 }- i; o) n! r // into the debugger
U5 U" ]+ a( [- G2 | [# m/ a7 e" M3 }- u' I
_DbgPrint( "LDR: %wZ loaded.",/ c" H" Q! f0 H, g/ @
&pModuleLoaderInfo->pModuleLoaderInfo );8 S- [$ F$ ^$ ^
r9 j& }% ~6 x4 }$ H- ]2 e
_DbgPrint( "- About to call init routine at %lx\n",) y' N" u0 H+ I
pfnInitRoutine ): Q6 J: j- y0 v4 Q: ?
}3 {7 Y* T+ l- h+ n g$ a% R
0 h+ }) H' q* D0 x" R // Break into the debugger - A$ q! E8 C+ K
_DbgBreakPoint(); // An INT 3, followed by a RET
1 @4 [8 Y s2 W8 T3 S: O, c8 T }/ ~) r% P8 K! C
else if ( _ShowSnaps && pfnInitRoutine ): x& L$ j7 i( C- A, t
{
6 y; z( H) s7 b // Inform the debug output stream of the module name
. o# v ]0 l$ s: _. ^ I, ~ // and the init routine address before calling it e) F, G f' ]
_DbgPrint( "LDR: %wZ loaded.",
! w# `! `5 A0 O3 V: m: r2 R: ~ pModuleLoaderInfo->pModuleLoaderInfo );3 n2 }. `* @* [+ Q
/ v: e, V, X3 l- g, ~; i' z( O; G& Q5 r
_DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
+ O6 l1 O" `. o. L( a' `1 O' k }
; i6 j; n6 h, _5 W4 m
* Z3 s, Q9 S! q7 m! X1 }6 S if ( pfnInitRoutine )
- ?" ^" U) [! X {
3 I/ m& k. C4 v5 K! F // 设置DLL_PROCESS_ATTACH标志
+ m* W3 H$ X# Z k6 W" s; ?/ _ //9 f& B8 B% G- S' D
// (Shouldn't this come *after* the actual call?)
& ]1 g! s8 L) d/ m //
; g' M- v( n4 m8 w* e // X_LOADER_CALLED_PROCESS_ATTACH = 0x8 $ e6 v" b/ ^- f
pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
( |# G/ S1 k) T8 _0 Y5 H4 k4 W) e3 E9 S) B% f# q* I
//' K! l5 j" n0 S# i
// If there's Thread Local Storage (TLS) for this module,/ \8 k4 C; E) S) D* C% f
// call the TLS init functions. *** NOTE *** This only
; p: K& ]- n8 _. L) z+ E // occurs during the first time this code is called (when$ J+ ~, ~% H' y/ R" E! f- w) R" n
// implicitly loaded DLLs are initialized). Dynamically
$ _7 R R6 x3 S. }0 a. r$ B' A // loaded DLLs shouldn't use TLS declared vars, as per the
* O4 u, V6 k K, b // SDK documentation
1 S) N' z7 U3 i5 g; | // 如果模块需要分配TLS,调用TLS初始化函数# }. O8 n0 l- @, |# I6 y5 v& ]" |
// 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
- T9 m. `7 k! n3 D4 w // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
+ R$ p* H% @; K0 } if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )) c! ^/ g4 o% @) A( X
{/ |' |5 \& e! E1 z ?! S0 O. O
_LdrpCallTlsInitializers( pModuleLoaderInfo->hModDLL,6 M' H4 o" i1 x6 c$ L& Z1 s/ c
DLL_PROCESS_ATTACH );% _- s h; [ a" S7 ^
}$ `' O: ^/ a. q# S% ]1 U1 ~
1 Y1 |9 A: _* {5 W' ~3 S# [# e. K
4 U) S+ U& t3 d5 K! u. Z2 G$ H
hModDLL = pModuleLoaderInfo->hModDLL
" s! a/ g d4 U5 v [, W1 d2 t& p% ] `: U1 O4 {8 E- \. _
MOV ESI,ESP // Save off the ESP register into ESI) |' c8 r& x% M% X/ u @$ _3 G
) R* F+ h+ R) D. `# ^8 d) A
// 设置入口函数指针 ) s9 G" r* ]9 w: h
MOV EDI,DWORD PTR [pfnInitRoutine] ) A" z% k* x- \2 c. k5 q
8 t' [! E" Q \' A! ?0 s. ] // In C++ code, the following ASM would look like:
3 h/ T& U8 E7 ^1 t1 l3 d4 n //
. ^! k9 A, {) z: V+ Y$ k // initRetValue =* ?8 c$ \7 O8 h$ H( k
// pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
( ^. ^2 C& P8 o //
2 s& r: c& a% w" T6 ]: \: ^! t1 R# x3 P6 d3 n" k
PUSH DWORD PTR [bImplicitLoad]
3 W3 i: G& @" i5 R3 p% n k z, n) I; d) A8 V
PUSH DLL_PROCESS_ATTACH4 j: ^0 r# ~" x, e% q
2 X+ a9 }, |, C B' m
PUSH DWORD PTR [hModDLL]% J8 X! n* k6 f3 E! R- ^
) Z) |3 _0 p9 j. _$ O5 X' x CALL EDI // 调用入口函数
: h, Q7 T4 s2 @2 K
9 l) o; A: N6 n MOV BYTE PTR [initRetValue],AL // 保存入口函数返回值
6 t9 ]) X+ k; m7 F( C) H$ Q5 N9 J* M5 ]/ A7 q: {/ E. G
MOV DWORD PTR [_SaveSp],ESI // Save stack values after the
- I S, Q! T6 O/ q* ]: Q! t; x MOV DWORD PTR [_CurSp],ESP // entry point code returns3 L) ~2 ?' e. v' b3 t
* }/ l0 M2 x ], p
MOV ESP,ESI // Restore ESP to value before the call
, D- W; Z4 B) C: t( V9 ^7 q4 U9 u+ K5 \- q* d; E
//
9 g5 v' s, ^" t // 检查调用前后的ESP值是否一至. h0 l! W$ f1 U2 X
// 5 K7 _# H# v& _ e9 o5 a
if ( _CurSP != _SavSP )8 B" n! G7 }0 p$ J1 O* t
{' N0 ]9 f1 f9 Q. A! Y
hardErrorParam = pModuleLoaderInfo->FullDllPath;6 K2 u4 ?1 @5 D, v0 k
8 q7 V- P; U! e: e; ^1 g7 { hardErrorRetCode = $ [2 F. Y, U% Q% `
_NtRaiseHardError(' n5 l8 I' `. j4 t, z6 ~; i
STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,! J" |" J5 c6 ^1 H( L- S/ l
1, // Number of parameters& [) ~' \+ g% `* G" H
1, // UnicodeStringParametersMask,. Q. x8 Q3 Y) W; h( }- g+ t
&hardErrorParam,
6 c/ K7 {% O1 Y) v* m! }$ g [7 L8 x OptionYesNo, // Let user decide. b, w" ^* `. b3 S8 I8 v1 O
&hardErrorResponse );1 `% J) v2 b( z/ r$ V, X y1 X
N! K9 ^9 Q c5 { if ( _LdrpInLdrInit )
x6 |8 u. {! B# u _LdrpFatalHardErrorCount++;
6 t$ a7 A, ^ O$ Z) H7 J
/ I% K b! W$ o8 M9 @! H' v2 B( I# s if ( (hardErrorRetCode >= STATUS_SUCCESS). [ V7 F, @* v. K
&& (ResponseYes == hardErrorResponse) )
2 v- a1 H9 f! a' d" E1 p2 U5 i {0 r, o+ }9 w T. b4 r, s3 S
return STATUS_DLL_INIT_FAILED;+ I0 l& g7 {5 o+ Q
}
6 j1 k. \( g# A }: J0 p w& k. L \ b4 A
! J$ y3 Z% c. U4 z //
. S% `9 a1 {; m* e* l5 ^' }* _/ V // 入口函数返回0,错误
* c/ G6 F+ X3 D3 ?5 G$ f+ e5 K //
1 w/ j) V- q& @" S2 k9 v) I if ( 0 == initRetValue )
9 g V1 K( u1 Z* k; y7 H! E {
) u# T- W! Z# K5 H0 \; a9 R4 p DWORD hardErrorParam2;
# P* k3 T" {4 C DWORD hardErrorResponse2;
2 U: G: S& u& X. ? , n) A5 C. C& O% N ]+ o
hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
% x- B- W! @7 y {, D
7 x$ @6 w7 i1 z- j& }7 z _NtRaiseHardError( STATUS_DLL_INIT_FAILED,+ d( y3 i' A; Y+ H J; |/ D }
1, // Number of parameters' _/ e1 q0 T( g" q
1, // UnicodeStringParametersMask6 O2 ^" i h( \( C. j
&hardErrorParam2,1 f c+ {; {" U* Y& w8 A
OptionOk, // OK is only response+ Q: g* x" z. x
&hardErrorResponse2 );
. E$ w9 J5 U, J/ p; v% i. C 2 Q; h) @( p" {$ I3 V
if ( _LdrpInLdrInit )) v- W- I, u+ @ U) C7 n
_LdrpFatalHardErrorCount++;! M% r* B9 d! h, z' i2 T6 Y7 ]
U* Y+ X8 R* C0 Y& h5 k return STATUS_DLL_INIT_FAILED;8 T- G. j9 M% m/ X9 y5 r2 z
}5 G: p) ~9 z6 F6 w% [5 l: r
}4 [, N: T7 |8 e6 _# W* m2 g8 m
}/ @. Q4 i- S' J9 E) E
: d, Z8 \! R+ `8 _5 G
//
+ |- u4 z: v3 n4 [, I // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
1 I! T6 Q# S. h( i //
% t- f4 ]' j5 ]* \+ Q0 a if ( _LdrpImageHasTls && bImplicitLoad )9 P* x2 O2 w' r* v. V
{& A* _' X( Z3 ]2 L* ]- A' C
_LdrpCallTlsInitializers( pCurrentPeb->ProcessImageBase,
2 s" Z0 C, D0 i) q$ f& s; A DLL_PROCESS_ATTACH );* r4 J2 z9 y- A8 `1 s( [
}
3 v& M3 W6 p9 f% E2 Z }
% m, h H* j/ M$ y0 J7 e+ u8 t __finally
4 U: a7 c- I) s. f; F/ \, y1 c {
% S' A9 z0 M1 e% F //
; q* f, p! ^5 I9 J // 第四部分;8 O4 y# C6 M0 k# Y) e6 Q
// 清除分配的内存
}0 i4 o. j9 _3 u0 h _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
( F/ ?+ _( A% r, @ }
' C; T! j( ]' R
+ K7 I8 p& O# w0 f' J return STATUS_SUCCESS;
% d" @; [1 }' B2 p2 {6 D0 o6 e; y' P}
' Z* ?( ^0 Y x6 a/ b. n( k
( z- Q# l$ Q6 e/ s7 z这个函数分为四个主要部分:3 j1 z: B4 S/ l
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
% z8 M& l3 N" O( ^, w, m/ X二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。* C- L9 s) o; R5 [# R3 ^( N
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
3 E# h& J6 R- e) o另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
# B2 H& L, N- \, o. 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。
0 r- w; s( ^ U: V四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。 |
|