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