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