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