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