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