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