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