找回密码
 加入计匠网
搜索
热搜: BIOS ACPI CPU Windows
查看: 14355|回复: 0

[转载]WINDOWS 2K Dll 加载过程

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]2 J9 m' X9 g, u' {+ w

# C! S9 a1 N: V& R6 R4 r/ J8 FWINDOWS 2K Dll 加载过程  A6 T; i7 O: B
jefong by 2005/03/30
% w( n/ ~& p3 v: i/ R5 u这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。! S- I/ ?( O+ p1 G1 _; p
在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”。
" O& Z& }0 {+ E; E0 R; {你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。1 |7 U1 ^3 L' e1 ~* x# Q  w) F8 _
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
0 }" @9 ^0 ~- r% H  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
4 B% o' `0 T0 v; h6 k' ~+ A% I/ q. C  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。* j9 O8 C( J1 Z1 T6 R
  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
) q/ B1 T: I1 D4 a! _; E//=============================================================================
  u8 K7 [* _  P: |9 `" i// Matt Pietrek, September 1999 Microsoft Systems Journal) O8 g5 W! C& E0 x& g0 h; Z4 {
// 中文注释部分为jefong翻译+ v5 N" C3 ?, K, @! T
//$ L* q- U; b/ J' w. r
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
: P5 f% W# N6 k, e: k  j//
4 X2 t) s7 X+ t, Y& g9 w/ R: X1 L// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;# x0 {6 d$ q6 Q1 q7 w: B
//=============================================================================) `- l0 F& N  T) J
! b1 P) O1 \1 Q8 y
#include <ntexapi.h>    // For HardError defines near the end  w0 z1 L! G5 @7 H
- [) Y4 u6 D: m! }* H( ~
// Global symbols (name is accurate, and comes from NTDLL.DBG)- e. O$ m# ]4 f. G; s4 i5 L
//  _NtdllBaseTag
1 v, ?/ t  i  k5 t//  _ShowSnaps1 o3 Z% F7 }* _8 t) P
//  _SaveSp
( Y4 D+ f! b5 }; X  u* R//  _CurSp
8 n+ o, U/ ^( _# R2 h//  _LdrpInLdrInit1 k% f( l1 S2 L! E. ?/ }/ p3 F
//  _LdrpFatalHardErrorCount
2 l! u  l2 z3 v! r  i2 i//  _LdrpImageHasTls) k% E) y4 X. c! ~/ r! M6 d
2 f5 j; U: J+ S
NTSTATUS
* ^4 @  `1 y6 U4 u+ _2 N3 Q2 O7 ALdrpRunInitializeRoutines( DWORD bImplicitLoad )
0 `+ o# V" s5 ^, f  A3 N{
8 X/ s7 L* I) j9 L) J    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
; b* Q7 y2 y) [- z% ^+ S    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
# ?+ y& {9 g1 ^/ \* H
" M: C* b$ g( M    if ( nRoutinesToRun )
4 _2 ^3 L* i! g4 g; D    {
7 v" ~- a& `4 T) o0 i" }1 N2 W9 @        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。5 Q' C/ e5 m1 T
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
9 T" Q  M' z# q                                            _NtdllBaseTag + 0x60000,
/ U$ ]+ U+ i- l) Y! R3 L                                            nRoutinesToRun * 4 );
: r+ T8 m7 P" {5 v5 C! }                            * _; S( T; }5 _- q) {$ l
        if ( 0 == pInitNodeArray )    // Make sure allocation worked
9 l1 f; r+ R3 l: c            return STATUS_NO_MEMORY;
  {$ @. M* [* n! x8 S! X) e    }
( ?) d, C5 f! }2 b    else" V  @, f& _( ^% p1 y3 F
        pInitNodeArray = 0;
8 n5 e: M( W) w9 U  ^+ ~6 v( J' p9 }! J+ T7 |0 {* R0 [
    //第二部分;' C$ R3 _' c" v% |6 B+ O4 M4 A
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。4 R6 ~+ K& k7 h4 g+ }
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);
- k$ \! X. }, a# u$ Z    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
. S0 u+ \% q3 b        
( O7 P" J; Y$ r2 |0 k6 f    if ( _ShowSnaps )
8 G' X3 X: o% R" }( Q4 s4 n) V& f    {# f+ {. u3 K2 {7 |$ z1 L) }
        _DbgPrint( "LDR: Real INIT LIST\n" );
5 c4 _* E8 x: H; ]! j) G    }
6 Y3 I: P$ ~" l) T2 Z
7 Y8 O0 J/ s4 P& k+ p* Q% q5 B* `4 {  Z    nModulesInitedSoFar = 0;
7 R6 c( j, t6 C% ^* ]; ^" Z5 E% I& {" o3 E, g! i
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块" E% ]+ f; m# r0 A) W
    {6 q( \5 |: L/ t- w3 K$ H: H3 V& A8 @3 g
        / z0 Q5 S$ C3 E& k, G* v% Z9 f
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
0 }  L: b! a* {+ x1 q        {
! u9 a0 ?8 H; y8 X/ ]            ModuleLoaderInfo  pModuleLoaderInfo;
, Y# m2 P7 Z# z% f. {            
3 [% ^9 S, n. X8 T$ [            //
2 U: _! o3 ?3 w  d' d            //一个ModuleLoaderInfo结构节点的大小为0X10字节0 a; G7 `* V' t! M# Z) r& H5 ?
            pModuleLoaderInfo = &NextNode - 0x10;- D" A4 w8 r3 Y3 f( l  T
            
0 h7 Q1 y; `0 {( ^            localVar3C = pModuleLoaderInfo;         
1 v& W/ o. K' @0 o$ M* q) t) \
% y. N( `: Y& Y( u' o! o            //0 o, b5 ?! G& c; @: L9 l
            // 如果模块已经被初始化,就忽略4 S, J, \) r! m$ q2 I+ `: v
            // X_LOADER_SAW_MODULE = 0x40 已被初始化
) W, l7 f. i3 a9 q$ {6 k            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )# B9 v5 |/ T# J" E8 c
            {) s+ u; R1 l6 x$ j7 S
                //5 ?: u  H& ~. T/ C* [6 X7 L
                // 模块没有被初始化,判断是否具有入口函数
1 p' r! B4 W, r7 c" C/ \7 r                //# o% |# l) L. s0 A9 C
                if ( pModuleLoaderInfo->EntryPoint )
% ?* I9 j: `; g4 y! m& P                {
; e$ c7 M, e& a$ X0 D4 X                    //
0 f6 ?8 B4 f# `3 X  l2 a. [                    // 具有初始化函数,添加到模块列表中,等待进行初始化
" z- Q( d" f% Y8 e6 ~4 b                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;% L8 ~" `! k$ E9 f

* a5 i9 h8 O' A" F$ l% u5 G* d                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
' d3 P$ k+ Q# \      // 例如:; U3 |0 V- l0 G) [8 Z- V2 U
                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000. ?9 e* Y$ ~7 \6 S
                    if ( _ShowSnaps )
% X# y# K. H( T9 j% I                    {7 M$ {; _2 c$ B
                        _DbgPrint(  "%wZ init routine %x\n",1 M5 {0 Z( k# L5 X1 D
                                    &pModuleLoaderInfo->24,
6 r7 r5 F: \" R& N  C; P                                    pModuleLoaderInfo->EntryPoint );
, r: O. R* p% @  S, _- ~' N) {! M                    }
' L) E/ @! l6 |* }- j
; I8 z: {8 o3 ~; s; T) q  f                    nModulesInitedSoFar++;
0 z0 [: M5 F6 E9 d                }1 r. X  r0 s+ _* r* X' o
            }
  z1 p" E; Y$ a4 C' \
  X1 \1 V- k7 |, A3 T! v; n            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。1 p. C% c* a% O0 G3 ^7 @& ?+ g$ p9 s- y2 w
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;2 w$ s5 R2 J2 G4 d& Y& \4 b& M

: B" b7 n; O6 c: }) ?  u            // 处理下一个模块节点- |4 v1 A7 ]) Q# q
            pCurrNode = pCurrNode->pNext- J$ o. \0 T. V& ?* ?  j. @
        }
5 J% E4 E7 q7 S/ o7 E/ G' J    }$ O/ w5 g6 ?! H4 e% ?# m
    else
6 Q$ E/ r5 j  O( a8 d6 t    {3 _: w7 j' w& w) ~! b
        pModuleLoaderInfo = localVar3C;     // May not be initialized???
+ k. I" n2 T$ ~( M) a    }1 t, {' u( f+ z: o1 T1 d
    - C" S& {. g* I+ \  f" b; f
    if ( 0 == pInitNodeArray )
, f8 Z8 B* R3 c  b% Z( E        return STATUS_SUCCESS;
/ ]9 y2 ~9 C& v" |. w5 z
. L# [7 g0 E+ }1 l, j) r2 A/ w    // ************************* MSJ Layout! *****************
) w' Z1 P. J6 v4 E: x9 l9 _" w; [( k    // If you're going to split this code across pages, this is a great
( z) c" |& e2 `    // spot to split the code.  Just be sure to remove this comment" W8 n& R$ c. P8 d- H$ b9 h" `
    // ************************* MSJ Layout! *****************2 B; w. B# W4 a8 h2 g# A% p
   
. a. q$ e0 v8 [" v    //# `# S" C- @/ ~- ?4 p
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH' G! _/ ^1 Y% ~3 @3 @
    // 第三部分,调用初始化部分8 T% ?/ _( d2 d
    try     // Wrap all this in a try block, in case the init routine faults6 X  r  A4 Q$ v" Z" I8 r' e
    {  O- w& l; I( l5 D+ }" p5 N3 p3 }6 M
        nModulesInitedSoFar = 0;  // Start at array element 0
! R1 G# Z+ v% H0 w. }
6 a. I- S" n" Z        //
9 ?5 d+ s0 B# q* }" k        // 遍历模块队列
& ~! V' n( z* i( S" z0 I$ A        //% \5 g, {5 G& z
        while ( nModulesInitedSoFar < nRoutinesToRun )0 G9 R* U. j/ v
        {
0 x, [) H* N5 V            // 获得模块指针
( h) W$ E. y9 n$ H4 q" L5 d            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
  Y2 W7 p, @2 U* f+ K3 M
4 B& h5 P6 l( t            // This doesn't seem to do anything...
1 R9 z2 Q' o& }2 S0 ^0 e            localVar3C = pModuleLoaderInfo;3 {4 u0 g( |+ ^; {$ w  v6 Y4 q
            
6 H8 c# }/ R1 }" m- u5 i; g            nModulesInitedSoFar++;
) F* y6 y5 q+ T0 S2 F. E               
/ r( T# O* T' H            // 保存初始化程序入口指针
7 G. @; h/ J+ E2 s# {+ H            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
8 g, r% b2 ~# ?& c; u            
, H. \) ]* I9 l# T7 f  F            fBreakOnDllLoad = 0;    // Default is to not break on load$ w- s% a: C: R

! V1 b1 `2 A* \! X( ^% i' l            // 调试用
. U- [" }- e) _- w            // If this process is a debuggee, check to see if the loader
# a* l; |( k3 V) s$ r' a+ @            // should break into a debugger before calling the initialization.
- C. u7 L/ C* y8 a            //
0 t6 g  R0 t/ c% z0 P. z            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()3 W( w* I% R7 _8 D5 F9 t
            // returns. IsDebuggerPresent is an NT only API.7 ^! u: D9 m2 r( q# w5 R& y
            //+ S$ N( G* I6 ]$ d$ x, h
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )/ q# L4 L) e7 E: F% Q, z3 D
            {: I7 l: T; H( Y! q+ d- K
                LONG retCode;
) \# B5 l3 v) u1 z9 I1 R
4 G, {1 j& J6 _5 Z% h1 t                //              
5 A  s& l) k" e4 {$ I                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
6 b8 ^$ e$ T5 k, _4 Z1 ]' J2 h                // Windows NT\CurrentVersion\Image File Execution Options"8 s( r8 I7 }7 K1 q" g4 s
                // registry key.  If a a subkey entry with the name of
8 C% j( [. b+ o' Y! X                // the executable exists, check for the BreakOnDllLoad value.
" D! N/ R* b  j4 l3 Y% l                //
: D+ E. @# t, }0 e" N. q( L& [                retCode = 4 }3 N9 G) i- c
                    _LdrQueryImageFileExecutionOptions(
( @6 Y& t$ U) v; e. j                                pModuleLoaderInfo->pwszDllName,  j" f' w+ ?( B) B/ `
                                "BreakOnDllLoad",pInitNodeArray( @# g$ C- A9 j1 h
                                REG_DWORD,
1 k. y3 D- e) M; t; ^4 V                                &fBreakOnDllLoad,
  V8 m/ D4 g9 l* K; S6 F8 h! N                                sizeof(DWORD),0 Q7 T/ Q( q$ a
                                0 );
4 B# m" ]8 a( X) V0 e7 _8 V% L/ S& V1 [
                // If reg value not found (usually the case), then don't% ]# H. y% n8 {6 V
                // break on this DLL init
% r. h/ {: n' F. Q; q                if ( retCode <= STATUS_SUCCESS )- E- D! |" ~, {1 K  |: ^( U
                    fBreakOnDllLoad = 0;pInitNodeArray
7 c" Z4 _3 H6 b* [6 {            }" l" j1 ^  Q& y3 G
            
+ }5 W3 n8 R! n5 T            if ( fBreakOnDllLoad )
/ H, d0 k1 j" c, @( {5 _/ u            {           
* _/ k' }# `9 ^3 }" X- y7 w                if ( _ShowSnaps )1 c3 U% C2 Q2 T, z; e$ [& Y
                {# ]: X! b' {0 T
                    // Inform the debug output stream of the module name: S0 k: `: \2 Q; g
                    // and the init routine address before actually breaking& J! P" o+ h* t" Q8 G
                    // into the debugger! z+ x; ]: E+ D( V( q, v

4 o- L: o" n  K7 O/ Y                    _DbgPrint(  "LDR: %wZ loaded.",8 C5 J. i/ @! f
                                &pModuleLoaderInfo->pModuleLoaderInfo );  g- U4 D4 r0 U" L$ m# \
                    " ^( k  s5 k1 y: e- M
                    _DbgPrint(  "- About to call init routine at %lx\n",8 y# y7 T& c$ F
                                pfnInitRoutine )
2 r! s8 H* |1 C( Z4 v6 f                }* J. Y, s, r; O$ k5 F3 s5 d+ V
               
: N* X, W& R& m, A1 e                // Break into the debugger                              3 D4 H  ]) C# }& p; j% l5 D
                _DbgBreakPoint();   // An INT 3, followed by a RET4 P8 J+ P( A4 `* w' Y( F
            }  a& D& u. W, P' N
            else if ( _ShowSnaps && pfnInitRoutine )* V. E6 N1 d2 K9 `* N. I
            {
+ `# q* D  U* s5 Y. i8 n                // Inform the debug output stream of the module name
! M. J% ]9 ?0 h6 q& ^* a                // and the init routine address before calling it               & Y4 S$ h7 |5 ~
                _DbgPrint(  "LDR: %wZ loaded.",4 f1 n7 X+ x  D2 Q1 |0 \
                            pModuleLoaderInfo->pModuleLoaderInfo );$ ~# F  E/ a8 c
4 I; K! i. o/ }  K  g
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
& ^0 S" u, {' C# E7 ~0 }# E! a1 S            }
  ~% o8 t, W. s) p# ~                    
) |$ U7 \7 `* G5 G+ K* l4 T            if ( pfnInitRoutine )- _6 y/ U4 t, `
            {2 j! S$ V# f" J/ I, f
                // 设置DLL_PROCESS_ATTACH标志
3 f9 R+ o, f2 C' p4 T+ \% _' W1 x( v) |                //1 f7 v9 Z# d( @0 [3 O& f) y! d
                // (Shouldn't this come *after* the actual call?)% k$ A. \- z6 T6 Y( _& X& a
                //* W' l2 `; B5 Q1 Q" F
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             2 K& V6 `+ N8 G9 \3 h$ ]
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;! }4 n6 e! J7 A0 A0 [6 h
3 ^. M- v% x4 e1 G2 y
                //3 W% A9 K/ K9 j
                // If there's Thread Local Storage (TLS) for this module,
/ U5 H/ w9 f+ e# Q9 p- B* e  B                // call the TLS init functions.  *** NOTE *** This only8 J9 N/ k9 s+ W2 m/ ]- `6 k9 g
                // occurs during the first time this code is called (when
6 U% ]# M, Q: j6 Q; `                // implicitly loaded DLLs are initialized).  Dynamically! V( \" L. ^6 T6 i! |
                // loaded DLLs shouldn't use TLS declared vars, as per the
4 f: L9 U8 d! F/ _! R* u                // SDK documentation3 u/ \8 k3 I4 |& ~( {" L
                // 如果模块需要分配TLS,调用TLS初始化函数
  E* G& ]+ f& b2 Q+ c! v+ M  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
$ x/ e. g: h: n) y* S  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量- I/ r$ @1 z' H
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )* l' z: B2 E* M4 x. \$ c# y8 h) y$ ^
                {- A6 F3 R7 H: }  h7 g, @
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
) }  O% i# k5 o) ~. R3 e1 A                                                DLL_PROCESS_ATTACH );  H, p: G) z* }8 U0 o1 m
                }
2 B0 j1 N8 H% k5 @3 y                % f: c+ I- X; j/ x0 K, ?

5 p# f. P) C' `8 ?                hModDLL = pModuleLoaderInfo->hModDLL
2 d9 }' e; S0 u% \! ]! V& W6 X
1 ^& U0 N& ~, p                MOV     ESI,ESP // Save off the ESP register into ESI3 i! z- U* K1 F# X% ~6 E8 `$ n
  & B; p3 W1 ~  v% }- y+ ?$ i
  // 设置入口函数指针                . O$ ]& T! N3 j
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
* f% t) S& W' z* F( e0 `" ~+ l" Z/ w" n# ]9 \/ e
                // In C++ code, the following ASM would look like:! n5 i& J  ?  P5 e
                //
+ v  n# ?) V" W. |                // initRetValue =
0 u: y+ F, u( M/ f                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);' I+ K0 |! ?# _( B2 n% a6 N+ E
                //
0 O* Q( ]0 ]# D  f% R4 j- I9 J1 Y
                PUSH    DWORD PTR [bImplicitLoad]
4 J+ z( W! t! S3 {! r1 \, a1 @1 e. N                ' L5 |2 }( M2 F& Z6 F% A
                PUSH    DLL_PROCESS_ATTACH2 I6 g; g0 D2 u& `7 G
               
4 d4 d/ s0 W0 ]  B  H% j* i                PUSH    DWORD PTR [hModDLL]
# H: ], c; F' O$ M                : z: n( Z( x6 b. Z0 ?
                CALL    EDI     // 调用入口函数7 {+ ?6 }: A: Z- K* Q. V4 m% D+ q( W
                ) @* o' T' l# @7 {
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值0 n: b  M8 N1 ~! h

8 m2 k( y. X' h4 c1 n8 g# m                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the8 J1 A* p3 v- f
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
8 B$ A& h6 l0 d) F
4 E3 {% ^' G7 z. J- }                MOV     ESP,ESI     // Restore ESP to value before the call
0 M+ k% _1 J) y1 M9 [. M$ L6 S6 S& \% w9 v$ e
                //
6 J( M# G) G8 G( t8 K; b                // 检查调用前后的ESP值是否一至
  p6 Z' r* L: E; w! L. T  //
, g) j. W. K4 m3 J" |4 U                if ( _CurSP != _SavSP )7 B  O5 P+ J9 n7 }' k
                {$ D& ?! f9 A# E& f$ [1 _/ C
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;, R9 K( i2 I+ U
! Q1 Z& [% X* H, m
                    hardErrorRetCode = + B9 W, Z6 R4 s
                        _NtRaiseHardError(
5 c- D! G" e0 h0 M/ {. v- y                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
& F$ Y* R% ?5 S9 y& ?                            1,  // Number of parameters
9 s6 ^6 b+ g. U                            1,  // UnicodeStringParametersMask,# |, m# L$ v' j7 Z/ [1 a
                            &hardErrorParam,% i7 s8 X) c3 D) L9 ]  u9 f- ~1 X
                            OptionYesNo,    // Let user decide1 c5 {3 q5 Y6 G( ]8 o+ v, u4 e
                            &hardErrorResponse );; A* g5 m6 l' j. ^
                                            " m5 E; S' I% A) M
                    if ( _LdrpInLdrInit )
9 x0 f9 o8 X4 q% C; Q6 g8 [9 t                        _LdrpFatalHardErrorCount++;- N/ o0 w8 |7 x6 D6 h3 Q& M: z

8 W0 b* O: W0 g. }8 N$ A! x6 e1 A/ R                    if (    (hardErrorRetCode >= STATUS_SUCCESS)/ k$ w  l2 _( ^- ^# e; q
                        &&  (ResponseYes == hardErrorResponse) )
4 p* w$ Y. D4 u$ S0 l/ R                    {- L* F2 k; I* o5 Y
                        return STATUS_DLL_INIT_FAILED;3 ~# f( O3 b0 s: N/ q
                    }6 M0 c8 d* s5 s6 }+ P7 q6 v  k" V- S
                }
/ l) q6 ]2 S4 J* z( [# I2 S- ?  v2 J/ C7 s$ g/ o" v; V
                //: j1 G2 B% |$ y* Y) M6 E1 n7 G
                // 入口函数返回0,错误8 ^' ~; g5 d4 Z5 ~
                //
, f- L( t  h, j' x                if ( 0 == initRetValue ): j( K& Y) b7 h& j
                {3 ~6 R9 ~" [6 {# B; o
                    DWORD hardErrorParam2;
, ]/ Q- Y. f- z: t6 w                    DWORD hardErrorResponse2;8 t" s9 E/ R1 d1 y4 A2 O2 S  k
                                       
: \, A2 d3 P& @" w                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;) N; l7 q5 F+ }1 N7 q
                    . U: d- ^4 s+ I& W/ d
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
) `! w7 r" T- _( n6 k                                        1,  // Number of parameters) f. @  ~% R( [5 i. d
                                        1,  // UnicodeStringParametersMask+ l& [" I; B7 B4 A
                                        &hardErrorParam2,4 k7 b; ~$ Z5 ?4 z- c- O8 O' O0 p
                                        OptionOk,   // OK is only response
* L& n* U  s9 j( M3 F                                        &hardErrorResponse2 );5 M9 w2 s+ R' g4 w
                                                            
/ F. Q, A" M/ Y- G4 x4 E                    if ( _LdrpInLdrInit )9 S+ U# f! R1 H
                        _LdrpFatalHardErrorCount++;- D- M) }7 {! {6 d: [7 u
1 J4 f/ k6 {% D) P/ A' S1 y1 z
                    return STATUS_DLL_INIT_FAILED;  B$ h" u6 m  |7 V) Z" V
                }: Y" j' H( G  ?/ f
            }
/ w$ m( @4 V# m        }
/ x6 g7 e1 b8 W& \0 g- w1 h: E" Y1 S  l% V5 c! Q+ t: q1 j, T
        //
, d. X5 n' L( k/ x8 Y: R% E        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时. r; Q% Y* }/ C
        //      7 r- n, @8 M/ C: u2 x' M' J
        if ( _LdrpImageHasTls && bImplicitLoad )
6 }6 E4 G! q2 d  d        {" z% ?$ \* N5 O
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
5 r. r& P- j2 W& }2 d; j! A* `) O                                        DLL_PROCESS_ATTACH );
/ e* T; s7 S  D; K        }
$ h2 Q; V8 K& u' t% t    }
9 f# @0 {# \. m/ u3 \    __finally; [* {( `; O8 c- R! B1 Y" I% b
    {
2 G: f' W/ Z! K" N. M* a( |        //
  \0 _" ?( `6 D- h2 U6 X        // 第四部分;
8 |- d$ t2 h2 A: d        // 清除分配的内存
! V8 V9 W4 m7 k. Y, c/ u, n$ C        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );# [3 v) \) l, r1 \
    }; }+ }. ~& E$ T* h

) c$ n2 J& o& J, E9 Y    return STATUS_SUCCESS;" x  a6 G  m) i0 V
}   + ^0 [& y4 U- I+ ~. F( d1 ~
: Y' H8 U3 Q5 a6 W
这个函数分为四个主要部分:. o! v9 \" }1 o0 E% h
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。: ^; _+ X3 t8 C6 h/ A5 b# t- C
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
3 R, G( d: V" ^5 r+ u; g三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
, a! u  d' p8 s6 ~5 K另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。7 T! h. B  }7 z) J2 X
在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。$ n6 d) ^4 r. M) x3 N1 d- a3 u
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

Archiver|手机版|小黑屋|计匠网

GMT+8, 2026-4-19 21:53 , Processed in 1.820791 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表