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