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