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