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