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