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