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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
8 W% E1 Z1 D( C* n# K
6 V) L% A: ^1 M$ B: Q3 c6 u" eWINDOWS 2K Dll 加载过程% E% a+ }, A2 f6 \" ?, `  j
jefong by 2005/03/30
" r: L" A4 \* V6 E" e这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
" w5 x& C6 ~. K3 g+ c+ y* R在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”。
0 G5 _- W$ q0 m你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。; V5 T! B* m8 Y& n# C
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。6 {4 G  Z3 l: f" T
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
, u( @5 E9 E  l  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
# k% q2 [- c7 [. n& M) E0 ~6 J  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):2 t# @8 I5 @) {, g( Z" R
//=============================================================================2 H. ^( p" l- H- U+ M1 l7 W
// Matt Pietrek, September 1999 Microsoft Systems Journal
) m( ^2 _- e  K* x0 j// 中文注释部分为jefong翻译
9 T* m% L1 M; t//
0 L# T  K% l5 A, r( R// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
# O1 k% q3 `- W% H# A1 o2 d' S//  F+ T8 e" |4 Q- T$ C# u7 M- x
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;4 x: t4 Y. o4 k% |6 h& h
//=============================================================================( L) m/ \  ~7 y9 l# {- _

! U; x* k7 [# R#include <ntexapi.h>    // For HardError defines near the end: {: C0 y% A! m7 N
+ b/ h2 @7 B- d$ v/ g3 Z4 X, p) p6 Z
// Global symbols (name is accurate, and comes from NTDLL.DBG)
6 k8 a3 D5 l, N( _9 |//  _NtdllBaseTag5 G9 q6 r$ Z2 @3 I7 [
//  _ShowSnaps
7 x, [4 d, N6 z2 v: f$ u//  _SaveSp
  |0 w) M. b6 ^5 ~+ k  I" m0 T//  _CurSp) C, f  M! ~  S( d' x
//  _LdrpInLdrInit3 i3 L+ u( m, G. e- E/ d" y
//  _LdrpFatalHardErrorCount
, Q7 g- C4 j( m2 e% |+ d: Q//  _LdrpImageHasTls
! Y) p! h' X" s. k3 f  m
, P/ Z) L. D5 e7 jNTSTATUS
8 T% s+ ]$ W& NLdrpRunInitializeRoutines( DWORD bImplicitLoad ); [4 F( h. P/ a7 |
{+ `9 U! h2 z1 @/ S. c7 g5 x( W4 N
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了, a) C" a  l, D
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();0 d1 \) U3 F# b" Y5 z

# C) D5 T; J4 C$ m  D  R    if ( nRoutinesToRun )5 @  i$ I. z1 V* T. f4 S( X
    {
$ x' L: J, V6 _( P' B: p        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。9 T# H# [, M8 U# d/ I9 q
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),! m0 H! n- Z  b) ^
                                            _NtdllBaseTag + 0x60000,
! Q* h' N: ?4 v* |8 x5 K! g0 @                                            nRoutinesToRun * 4 );  q0 [1 Q' x( W
                           
% n( R5 ~2 k: r; E        if ( 0 == pInitNodeArray )    // Make sure allocation worked( ^( D) `. {6 |+ ]$ E2 [# O
            return STATUS_NO_MEMORY;5 L$ W: O$ ?3 J3 \+ t
    }
: v) p! X  r( X$ p1 @    else- E1 j/ G3 ^) m( g$ A1 V; |! ]. g2 p
        pInitNodeArray = 0;! o5 v, Z$ s/ P9 n

: `3 `* e, q6 Y: f* K9 m    //第二部分;
4 k5 z* I( Z4 K7 i& a    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
) O- Q5 ~; q% i4 A6 B    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);" q* r" v7 n8 F% o& x  T- N
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
5 e4 w' M1 B( m5 g        . L$ y( D! ?* P1 `" g1 ^; {/ F- T- r0 I
    if ( _ShowSnaps )
) Y# k0 b' O$ l+ `! ^    {
1 a7 B$ i8 W0 F- j8 y        _DbgPrint( "LDR: Real INIT LIST\n" );) Q' G5 n$ _& B" V% ]
    }' [, V0 ]. O+ w" `, A" s
' Z5 \( ]4 s' Y7 a! _
    nModulesInitedSoFar = 0;2 |: q: h6 i, P; m: p) I) W

0 J: h8 O% V2 _( i. I1 l7 k9 Y    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
2 Q. |% K+ V& Z+ ]! ~    {
) E9 ?; F* M' Y# M        
% Y9 X1 }. v. m0 Y. @        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块+ E' V8 g) R4 s+ m5 i
        {! [( ~0 Q# |8 ]2 c0 x
            ModuleLoaderInfo  pModuleLoaderInfo;
% Q( x% P! b/ ~# G" p2 w; {3 ]            " l* q* ~3 G/ ^0 p% G
            //
0 D2 k- s6 Y' C* ^& q/ y            //一个ModuleLoaderInfo结构节点的大小为0X10字节
0 t7 Y" T# n: |& H! ~3 P            pModuleLoaderInfo = &NextNode - 0x10;
0 l8 L/ P2 t9 e            7 v7 P, \6 Y+ v) [
            localVar3C = pModuleLoaderInfo;         
" A5 H3 J' Q, P: B! u: K* z3 Z- O4 ^: m  a: |
            //5 n! ~/ i" f: m. F6 R& ], n
            // 如果模块已经被初始化,就忽略) v  e0 e, T+ D8 p) P
            // X_LOADER_SAW_MODULE = 0x40 已被初始化$ t- U. u: f( g) M5 h
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )2 g/ V1 Z* Q5 ~+ Y, q3 r$ V4 V
            {- Q7 e, Q3 B9 M: x
                //
; g8 j* ]# _  J' J# D( c                // 模块没有被初始化,判断是否具有入口函数/ Y4 }# i. m3 V" W7 x# B
                //$ M1 J/ j7 W/ R7 E2 d* u
                if ( pModuleLoaderInfo->EntryPoint )
8 q+ R. v) [: g; V2 d                {5 ]; s0 D: {* I3 ?; U! e
                    //
5 d/ J$ C& Q# u& r' K8 P$ W                    // 具有初始化函数,添加到模块列表中,等待进行初始化9 B, V5 X  m) F
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
$ G; M( N/ \( c9 p5 R6 D
& k$ u1 K, Y8 C' [' F& q; f9 h$ J                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址& p  [/ B8 |3 D; ?4 Q
      // 例如:
0 M9 F/ k& G/ Z8 \, V$ w/ t; S                    // C:\WINNT\system32\KERNEL32.dll init routine 77f010000 t1 ], F" ?2 G$ }
                    if ( _ShowSnaps )) v2 `2 n, j+ X5 B: w
                    {
; W+ Z0 Y, v( Y+ d0 e3 i                        _DbgPrint(  "%wZ init routine %x\n",+ }6 F$ b" Z# V, }" m
                                    &pModuleLoaderInfo->24,: H7 u: U" O: q/ _' ]' f' S. T
                                    pModuleLoaderInfo->EntryPoint );! W  G* z/ D# S' z9 }! ?4 N. W9 [8 l
                    }
9 M/ Q( h3 f# B' K# [% ?5 M. n) M
                    nModulesInitedSoFar++;$ F3 i) g* S; Y! S# V8 \$ ?
                }& G7 {7 S  \# h7 C
            }. f, z; G; M, v! Q- c
# {* k% Z# P8 L
            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。/ e( o% ~3 Y' |: \+ ?  v6 I: j& a) T
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;! A0 _8 K0 D/ K/ f% N* i
- }/ e# h8 t& V& e
            // 处理下一个模块节点
# ~% C+ W8 f( K7 m+ r% ]            pCurrNode = pCurrNode->pNext. W6 U. P$ c8 g' L/ t: i2 f) j0 p- b
        }
5 r! C8 z& I0 ~1 V- a2 ^5 ?    }; M( D& E0 v& S! [! L4 P" j" h
    else
4 v9 M2 x4 S, Q0 u" ^    {2 C6 x9 Z8 ]' e
        pModuleLoaderInfo = localVar3C;     // May not be initialized???
6 R! u, j; J& J' j" @7 M" I, q    }
  T8 l7 e. z7 f    ( N5 }* L3 ?) [- T5 P9 L
    if ( 0 == pInitNodeArray )
8 w! h6 D, k9 S6 m% S/ y/ h        return STATUS_SUCCESS;
: T/ _, _' L0 j" u  V: l& s" t7 ~2 q
    // ************************* MSJ Layout! *****************
7 o- s; O  G1 B+ A* ^    // If you're going to split this code across pages, this is a great
% V2 b# Q# L% E2 x( J; F    // spot to split the code.  Just be sure to remove this comment
* G+ [9 B; V2 a( l    // ************************* MSJ Layout! *****************$ t. t4 i1 K; `9 Q0 m: {
    3 L7 c! _/ x5 U' b5 @
    //2 i. J- m" [7 t
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH1 X$ `0 X1 [5 C# x& j1 C2 @0 p
    // 第三部分,调用初始化部分
/ Z) x; e0 H3 m/ p4 h    try     // Wrap all this in a try block, in case the init routine faults: k5 v) a0 {0 D
    {, b1 d5 t+ Q: W' }8 U
        nModulesInitedSoFar = 0;  // Start at array element 0, g3 W4 Y) H1 Q) ^9 T
) p5 X* v! _# K" j4 E% _
        //# C, T3 C+ f' M3 P5 c6 d6 f5 a
        // 遍历模块队列  k9 @8 S- |  _9 v6 z
        //
$ Y% L5 m, T* c2 K: u        while ( nModulesInitedSoFar < nRoutinesToRun )
- h" e9 B7 f$ ?( ], e        {
" }9 n/ Y1 `  z% U; F            // 获得模块指针
# J( W$ h+ [$ s8 f4 b4 T            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];0 W4 }6 M% c/ G. ^% }# Q- i
" e) ~+ z% Q5 W3 Q4 R
            // This doesn't seem to do anything...
6 D+ H* H1 n" B' d            localVar3C = pModuleLoaderInfo;
; u. j; |9 v. F) d& f            
7 _" b( G6 ~9 F8 _8 e/ B: a            nModulesInitedSoFar++;4 h8 l" y6 g$ v% k' @5 Z
                ( T' N7 t( i. K2 ]5 w
            // 保存初始化程序入口指针* \: J- U3 X8 k& k7 K
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;. k& B$ B; \$ k3 e$ F0 ?  `
            
; G* G2 t6 `/ U2 n0 e            fBreakOnDllLoad = 0;    // Default is to not break on load$ p: E; L: ]! C( g3 Q, q
* N$ y7 C$ V5 r8 d2 A
            // 调试用
1 s3 u# ~3 x) Z0 D+ M( q            // If this process is a debuggee, check to see if the loader
6 W7 d$ l  U1 y% j. Z& o            // should break into a debugger before calling the initialization.
( u, G' f; j; B8 `6 d/ R            //
& v! ?* f- ]7 I) t            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()1 m4 ]9 Z" B! F
            // returns. IsDebuggerPresent is an NT only API.
" E- M# \' k4 O7 P            //' p( j! t& q+ l# M8 n. S' J
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
) ]) [& u# A4 }( {! p  n- h2 A            {
& P# S, a9 _* n  T; o( @% n                LONG retCode;
5 q; k" [7 E6 O5 M  f, |% Q4 R5 |8 E
                //              
* t" ^! `. K6 J; H) D* T. R: q8 m: C                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
9 r& y* M2 |" ~! F                // Windows NT\CurrentVersion\Image File Execution Options"
- y# y6 g& t' K# o                // registry key.  If a a subkey entry with the name of
6 e/ Z0 f- w. [4 F                // the executable exists, check for the BreakOnDllLoad value.3 G! k# x! e* _' Q# f. ?! c$ i* C
                //
- O- E8 p( w2 Y, R                retCode =
& y3 g3 Z& `5 E                    _LdrQueryImageFileExecutionOptions() ?3 U! [2 e+ ^7 E+ P) L
                                pModuleLoaderInfo->pwszDllName,  O! ~4 [" a$ S3 N# \5 j8 q
                                "BreakOnDllLoad",pInitNodeArray+ i8 w0 r' e" @
                                REG_DWORD,# D; V; A4 z" _/ A8 D
                                &fBreakOnDllLoad,
6 d" I7 E$ T. l                                sizeof(DWORD),
* T- d/ X% d  ^% X$ u$ [' O                                0 );* d! ]7 U/ w9 d/ o0 s( r

1 B6 g: h! d' L, {: |                // If reg value not found (usually the case), then don't0 H, ?' r9 o. S
                // break on this DLL init7 ^+ j$ e  ]) M* D: L2 w  u
                if ( retCode <= STATUS_SUCCESS )
: \. ~; j0 |9 O  c  m                    fBreakOnDllLoad = 0;pInitNodeArray! q: B* U, e8 F- g
            }( c; E6 e: z2 M, k0 p" H' n
            
  E2 @; h7 \; D4 Y            if ( fBreakOnDllLoad )9 O  x# C+ t4 j
            {           / P& P- f9 M* q( P) `
                if ( _ShowSnaps ). H3 p5 k# g! t  [% L, h( v, z
                {
  `7 R  o; c* F8 q7 v                    // Inform the debug output stream of the module name- V0 `& q" V7 A% I7 o3 K
                    // and the init routine address before actually breaking$ y0 n. }4 E5 i  A% |6 N
                    // into the debugger+ A% S! Y8 B7 h! p9 X- f
3 e! j; {+ U& k9 f1 e7 n2 t$ \* C
                    _DbgPrint(  "LDR: %wZ loaded.",
4 r6 x) j$ t( i; U( x4 O                                &pModuleLoaderInfo->pModuleLoaderInfo );+ Y! z" p' C+ W0 o! V" I9 n
                    0 L# i* u1 L2 [, l
                    _DbgPrint(  "- About to call init routine at %lx\n",* @8 z: u/ \9 p9 o5 w9 A5 c
                                pfnInitRoutine )
1 v& O, ?7 y! }/ q' ^2 ^; C                }# {0 u( y6 c- A  {6 W
                / U& [$ u4 D4 `. D
                // Break into the debugger                              
# N" a; K4 ~7 D/ _                _DbgBreakPoint();   // An INT 3, followed by a RET6 }' ?7 S+ o$ E- S
            }
1 I8 p6 c8 s# B  ^8 A8 q  h0 v            else if ( _ShowSnaps && pfnInitRoutine )
0 Y# w, N: E% z! ^/ r            {7 I' N* j2 d2 _# F9 n* l  H
                // Inform the debug output stream of the module name* c9 Z, W" a% j, Z
                // and the init routine address before calling it               # @& L4 d9 _/ ~" @) `& O+ W
                _DbgPrint(  "LDR: %wZ loaded.",. `8 j- j3 y! x9 l- I& e5 s% x
                            pModuleLoaderInfo->pModuleLoaderInfo );
3 R' K+ v) Y5 {8 I0 n$ m" T) v2 o9 ?, A; [5 H! p) J
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);( _1 ]! S! P0 r
            }+ `% a# R" |& {1 I
                    # P  H+ F* e% R% F2 @9 ?  G& d1 m
            if ( pfnInitRoutine )3 S8 m" z' T9 i7 v
            {2 Y* b5 b8 B' H, L: C% X4 q
                // 设置DLL_PROCESS_ATTACH标志
$ O7 ~4 _1 A$ u) f0 Q* A                //4 |" ?6 P. z6 N" i
                // (Shouldn't this come *after* the actual call?)0 P7 ?5 G4 G3 ?4 ~% w* ?; K; F' Y
                //0 N2 {) Z% Z; E9 }
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
) {) F  Y4 |, s& A1 ?: @2 z                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;1 v7 g; S" _! O4 N+ }& D
; ]9 F' b2 B4 \; U
                //
  v& P. b  g- |8 k) Q; T0 h: [/ H                // If there's Thread Local Storage (TLS) for this module,( U4 i) A* w6 V- T4 k& N8 T
                // call the TLS init functions.  *** NOTE *** This only1 j/ Y: R) L0 D5 M- E
                // occurs during the first time this code is called (when0 ^) a: S" ?& x, @$ n  X; o5 D+ o- Y
                // implicitly loaded DLLs are initialized).  Dynamically( [& @; |! J# W0 m& x
                // loaded DLLs shouldn't use TLS declared vars, as per the  C7 A& ]/ L1 a  }# z) J$ M& v
                // SDK documentation9 S, _! n! Q& V% d, {1 w! K6 V
                // 如果模块需要分配TLS,调用TLS初始化函数
2 y7 H# _. F# F. A3 [  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
- D+ E  l* y! ?; \( p  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量$ e% |( s; h2 c( t5 Y4 V* _
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )" {- E1 ?( h0 k) P
                {2 r4 y' P; e8 h1 ]/ f2 C- d+ ~
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
) K6 _9 S( S; r% b' y# J( r, f                                                DLL_PROCESS_ATTACH );
) [9 X, U; R# Q$ b2 x                }5 e# I! b1 p# D+ e, [
                2 }' l6 ]5 w+ v5 n' t
! ~. b: B8 N' W1 T
                hModDLL = pModuleLoaderInfo->hModDLL4 Z8 ?- x# p9 Q

, P0 A% L3 T' v$ y+ h                MOV     ESI,ESP // Save off the ESP register into ESI
0 y: e3 K- H6 a, a" E, W/ @% ?  8 ~* K3 |; O- a9 d  Y+ K; }5 q
  // 设置入口函数指针               
7 ~1 k5 r; |& l) K& n$ v1 z9 q                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
$ e' Q& K6 r9 S8 @0 B8 e( [8 G* ?  P% E% l1 l% U' N3 f; |( Z
                // In C++ code, the following ASM would look like:# X. H) Q# T& P& ?" ]9 q+ [% m! p
                //5 J' Q" f/ D1 @) r' K) d; D
                // initRetValue =
' ^, I, i) C) w/ N+ p, k* e                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);3 |( l  Y. z, a. K0 i/ B7 E
                //' n  ]- n/ Z! a- [( z

3 X7 ?2 M: D) X2 G                PUSH    DWORD PTR [bImplicitLoad]
! ?; ]- I: f1 X4 U/ [$ J               
; P' N& _% V3 C: f                PUSH    DLL_PROCESS_ATTACH, r% Y# f) p3 ~
                + D% y' i; e( g* f5 N0 t" h
                PUSH    DWORD PTR [hModDLL]: f9 s; ~$ K% a5 g' u- `  N* t
               
* U9 q/ R2 Q" i2 l7 ?7 b                CALL    EDI     // 调用入口函数' w+ C& ]# r, u' v& q: W
                - G% z' G) Y& S5 C0 d5 T. r& q
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值. @( n1 g" F5 S4 u# r
4 b& {" V- E: S9 ], H, v, U) {$ y
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the& R, H! M& j' o! i# F3 ~& l0 Q
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
, k: D! h! _/ n# g" a. E$ l& T: r/ L; Z# N
                MOV     ESP,ESI     // Restore ESP to value before the call! W9 ]/ R+ h9 z" P& Q0 ^; n. p

. }1 S0 X% E# W+ m                //
+ Z# c) }# G- f! G+ P                // 检查调用前后的ESP值是否一至8 S8 M- e1 g( C! X, C0 \
  //
! ?( k- H7 n4 m+ ~6 F) y. R                if ( _CurSP != _SavSP )2 T0 J. f8 l' R. {: O
                {
6 E5 W' V7 _, A, v8 _8 |6 W2 Z                    hardErrorParam = pModuleLoaderInfo->FullDllPath;6 c6 \3 j, a$ y" B8 ]* `

( c. g; E) E2 ~                    hardErrorRetCode =
/ p, k' G' F7 b8 `+ [6 X                        _NtRaiseHardError() t0 R1 I8 {( m3 x  L$ Z
                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,- ~4 y' t1 {9 R, K: H+ A$ G
                            1,  // Number of parameters6 b$ n. U/ n# Z
                            1,  // UnicodeStringParametersMask,
. L- \# M) c" v! z                            &hardErrorParam,, w, g" ?5 ^: Z
                            OptionYesNo,    // Let user decide
( ?* }$ j' q. l3 x                            &hardErrorResponse );: y) Y4 ]2 M; }9 I% a
                                            
/ F- z7 b1 m# D- v                    if ( _LdrpInLdrInit )
9 z0 `, f) k0 Y" @. }' Y% ?8 q0 q                        _LdrpFatalHardErrorCount++;
( {) `5 z1 k  c  F
, ~' e+ J) b/ A$ Q+ v1 N+ q                    if (    (hardErrorRetCode >= STATUS_SUCCESS)8 }+ a4 L' i% \* C" y
                        &&  (ResponseYes == hardErrorResponse) )6 s1 ?) d# d  U8 S! G
                    {" |! @2 C  [5 ?: J4 ?. R
                        return STATUS_DLL_INIT_FAILED;. y# ?  d4 j* Z9 W
                    }
$ K; I  q( x9 f; E( L                }5 p" C! s. q2 b! x) x* m
& J5 [$ ~; u0 O
                //9 |5 t2 u8 y6 {$ G$ v
                // 入口函数返回0,错误. X4 I: K" F4 Q1 z" I2 m/ Y- n" a
                //& M! X; c6 p, _- B& Z' `
                if ( 0 == initRetValue )
, E9 U0 D" T0 N  s. g# H                {
# G7 {0 P: f# n- U( {                    DWORD hardErrorParam2;
7 e0 M6 `' X8 q1 ?: e: U6 Z                    DWORD hardErrorResponse2;
) E4 E/ g- s* [+ W  P+ L* V                                       
( O9 ?- B. x4 p0 v" c% ?                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;3 N  y8 y6 }) X1 `+ W
                    * V) c: q- K- `0 O' R9 a
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
" ~1 q6 O$ W! }) x" d: t6 [- l& W                                        1,  // Number of parameters( @8 |0 @0 H9 T1 z9 f
                                        1,  // UnicodeStringParametersMask
4 K; S7 J5 h& b                                        &hardErrorParam2,
  X0 E  R( t# u                                        OptionOk,   // OK is only response1 i( Z* C5 j$ e$ W, e
                                        &hardErrorResponse2 );
; e* {0 n/ p$ m/ W0 W                                                            
0 g  C5 |  ~1 w7 v% j                    if ( _LdrpInLdrInit )
4 ]# @+ ]) }5 h5 x                        _LdrpFatalHardErrorCount++;- I* h7 g5 ]" `! o( Y5 t# a

+ g& c0 M, r3 \" C8 [! }8 b                    return STATUS_DLL_INIT_FAILED;( C( @8 `6 Z4 V6 X, e
                }. H+ p% g3 K: Q; e  K  s# V
            }: g2 C* n. Y0 K3 H& l: w" J
        }
" M( d0 U- h9 K7 Y( {0 {: L) o. i9 @2 U4 ?  H" f) k, @4 p5 Q9 e
        //. t. c! R0 V/ V- K7 A" q
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
- c# ]1 `/ Z( A) f        //      2 h: |6 C2 h+ m( w
        if ( _LdrpImageHasTls && bImplicitLoad )
3 |; @8 p* H$ w/ l* v  S. o        {
% g& A) `) d7 v% T& Z            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,4 A" h* H; c1 |. ]
                                        DLL_PROCESS_ATTACH );0 f7 H) `2 m2 q( E( k4 T
        }
4 z7 ]+ y! o/ |) M# m    }( D* T/ q, U0 n1 g6 |. C
    __finally& J% t9 c  g! d
    {
+ a8 a  q+ ?# a' A/ @        //
5 D3 F' Q( K4 R7 E8 c        // 第四部分;
9 N$ Z: r! t4 Y* N        // 清除分配的内存% ?4 b2 U. k; [# t" Q+ T
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );# ]+ s5 n6 l* Y; d1 n1 i/ x' |
    }
4 d+ F% T' i; t! A& Q6 B' R
: \- `  v* e' {* `( `    return STATUS_SUCCESS;
+ |  k/ }3 u7 Z- n0 o}   - d; s4 B. x3 J6 [, H

8 l. x. z% r# t0 d  C" e3 t7 ~这个函数分为四个主要部分:
% z! v) {# b/ m+ a3 h一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。) I. i" j3 E+ h( ?' i
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。- H, X2 j5 a" j  P3 l( |' Z
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
, L. w* s# l, _& V" e, q7 f另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
  O9 J7 a7 a0 i2 T) u9 G+ L$ s在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。$ q/ u- H5 ]/ [
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-8-11 16:03 , Processed in 0.096400 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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