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