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