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