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