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