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