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