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