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