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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]8 r1 R, ^% Q9 L8 q/ y

  [9 ^: Q3 Z+ A) l  I$ N5 JWINDOWS 2K Dll 加载过程! j8 s: R  O# B( K
jefong by 2005/03/302 k6 A* A  y+ x$ m8 [
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
9 v$ m4 L& H6 ^9 {. U6 I在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”。+ H' V. ~; U2 F, `0 y; M
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
7 R; f4 W, e, _( n* J9 b+ ~初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
9 O% y+ ]$ f( w3 L2 {" {  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。2 c/ J; L4 M5 {& E5 R- T1 [# u
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
$ T( A+ w3 k  T4 j8 D7 r  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
0 |: U7 P. h. {5 n1 }( t" g, N: w//=============================================================================
7 R& m/ x7 K: L& S0 F( B9 U// Matt Pietrek, September 1999 Microsoft Systems Journal
$ @/ N* _# L) u# J; T4 q// 中文注释部分为jefong翻译
( H4 Z4 c% ]+ Q  w9 O( N' r6 O. u//
: X) Q  `/ N) e2 M// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)5 T4 P3 L1 C& y! N% _* K. S
//' Z6 s% {3 B; P' b  a8 k9 ^1 y6 e; p
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;3 n! e3 F6 I8 h2 q! }) c/ I3 E' b
//=============================================================================
& H$ u$ m$ p0 L* B! {5 n+ ^  J+ b' Z
#include <ntexapi.h>    // For HardError defines near the end
) s7 {3 O* Y* P+ E& o" b$ q( f9 i' @
" t+ w1 i0 c. `" i7 u# `: ?// Global symbols (name is accurate, and comes from NTDLL.DBG)
. L6 ~, s2 X1 Y" C5 R//  _NtdllBaseTag
8 v, }) X$ u0 D( |, X2 {//  _ShowSnaps
, G6 P& E, |! `* p8 h! F//  _SaveSp
. l4 X% T' y5 |7 b  T: B4 X//  _CurSp
- `7 W/ r: i+ W5 d" C8 G# V" \//  _LdrpInLdrInit' I) o- G; u' G0 [* Y
//  _LdrpFatalHardErrorCount+ P! b+ U. w6 E: m/ ^" n+ M
//  _LdrpImageHasTls
+ C  K. f* \, `' P1 H: m# U6 C- j: y( C: g- F
NTSTATUS
3 c9 Q9 X3 M& v$ sLdrpRunInitializeRoutines( DWORD bImplicitLoad )
( ], k" T% h& Y5 N7 p{
, A  p* a; M/ f& `" ]* N1 g    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
9 F4 J5 f: n; H7 R    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
' d" m. @' \! O' D5 d1 y' }8 n2 Z, J, f) ^7 U) p9 @
    if ( nRoutinesToRun )
" t& T' |: S* Q4 \3 |+ C( Q4 N    {; \& X/ x2 z  S8 e0 I4 x
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。3 F( W5 h2 `" N% ^
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),0 t6 J3 \! x: K+ q+ r
                                            _NtdllBaseTag + 0x60000,
; z3 @% ]# S4 L7 z6 s                                            nRoutinesToRun * 4 );/ @# e% X6 A1 V% P" h1 v
                            ) b+ {4 U5 a- a& Q* N2 u. B
        if ( 0 == pInitNodeArray )    // Make sure allocation worked8 ^* Z5 y  @" O) c- x$ w1 w
            return STATUS_NO_MEMORY;' N0 p9 _7 ?/ v9 Y4 v
    }
/ k: q% Z; E# H    else$ X5 q8 p. F5 X) E
        pInitNodeArray = 0;
5 g% O+ ~0 A$ s' g3 a2 v7 m$ `% f5 G  E; r  Y9 p8 J% D% q) x
    //第二部分;. ]" Y1 z) S7 d' d" R0 T! \- ], y1 H# |+ Y
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
' m( o0 q. W) x  a! y/ u3 G7 Z    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);% E# s( a, A8 o6 Q
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
7 p2 t% q% y$ [& Q        2 _3 }( [8 T( q# \* S# c+ E# j
    if ( _ShowSnaps )
5 w! r7 d: K2 ]. S0 C    {3 V% G) P& p! y! B1 o2 [8 g
        _DbgPrint( "LDR: Real INIT LIST\n" );: ]* a0 e- `% D4 M. k, Y* x
    }
3 l5 C8 Q; H/ I% }& k1 j" S# L* w
6 u! X* L: ]  I3 Z# K' ]+ q    nModulesInitedSoFar = 0;/ Z4 |% u  P8 i" L. i0 V
% n/ I9 q5 o$ V6 v0 p  @
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
$ z/ u6 j# x% M. g4 q0 M  E    {
+ `7 Q9 q: o/ b$ s" r1 i        # l3 F2 T: L1 i( k& F* H
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块5 Q7 U9 N5 `: J+ f3 S+ |) U& C
        {! I% Q( _; j9 l5 d
            ModuleLoaderInfo  pModuleLoaderInfo;7 J9 I7 a2 T$ L3 s
            
3 ~+ l7 k3 l) A+ l, ~& C; ?            //
* i$ ~* f4 m- R1 F% Z$ S! J! B' t            //一个ModuleLoaderInfo结构节点的大小为0X10字节
) h$ f3 C) n- ^5 D- B' j/ q            pModuleLoaderInfo = &NextNode - 0x10;9 e  I2 W3 {& |( e
            
! z3 [. Q$ Z1 J9 H            localVar3C = pModuleLoaderInfo;         - F8 x8 w. ]  d! J! w0 v& T
: |  n" O; ~# @5 x8 b
            //, s  S. \9 ?/ J3 j, A* g- z) I
            // 如果模块已经被初始化,就忽略* N# M/ Z% \& K) B, u# B
            // X_LOADER_SAW_MODULE = 0x40 已被初始化
2 `1 Y8 [, A! e# B$ a: X            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )& o. i- z- e2 i! ]
            {9 E& c( n8 D4 I  b0 f
                //
2 @0 {3 [4 i+ \                // 模块没有被初始化,判断是否具有入口函数, |" t9 e& J5 p, g
                //: ]& _  g" m) z2 N- I
                if ( pModuleLoaderInfo->EntryPoint )
+ z  L$ L& I2 m/ b                {# B. h6 X, v$ f) W
                    //2 c; q5 p) M' i% T; ], |2 N
                    // 具有初始化函数,添加到模块列表中,等待进行初始化  g, e% o2 x3 `/ w, \4 v  X
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
( Q, l5 `& X  R* e2 r$ Y; o  C, ^6 y# ]) ?! I
                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址4 @) J, V8 {: M0 |/ g
      // 例如:8 U7 N: n& ^. A
                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
( }2 f5 S; }4 \# H1 G; `: X0 A                    if ( _ShowSnaps )
7 z2 {& X, s* `                    {3 t# I0 V0 j" I9 Y
                        _DbgPrint(  "%wZ init routine %x\n",+ R/ |5 e3 N; I' M8 x
                                    &pModuleLoaderInfo->24,
$ ^; M7 ^8 B3 ^) x$ E: A: a- k                                    pModuleLoaderInfo->EntryPoint );
7 R; \" O. ~+ }9 E% T) D% F9 H                    }+ @- G$ X. {' k  [$ @
, b$ C/ c1 J6 v; K5 t- q
                    nModulesInitedSoFar++;3 l' _% A) A( S' D7 r. k- ^: |
                }
) _. U7 }$ c! e( [9 ?! Q# R2 D            }5 ^2 G" J! {* a4 @* U. `1 u9 w

3 h; p, w* K% \" y9 R$ ]( U            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。' p% {9 z, Z( E* v
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
: N; w) _2 l: z' l0 S1 ~
, u" n# B1 I8 K; K/ ~7 Y            // 处理下一个模块节点
; ]2 Z/ @( u7 _, f5 M' \            pCurrNode = pCurrNode->pNext' H. }8 _! [; M" b: z/ W- [( ?
        }
( w6 t1 f8 B* e' z$ F% i  \    }
7 ?9 u9 N& z, I4 W" h, E    else+ d# s8 M# z- g) C6 G7 u( q& s3 ^/ a
    {
% r/ [1 m3 T. I. H        pModuleLoaderInfo = localVar3C;     // May not be initialized???+ b* y; W- t1 S, D
    }
! V- n* d9 t% W" B% |. t0 ?# T" A3 H    5 z) Y- O9 e$ ?: F) U- k
    if ( 0 == pInitNodeArray )
& @( g* Y  D$ \        return STATUS_SUCCESS;. x& c( T' C* j

! [, G; o5 C$ Q' n+ U    // ************************* MSJ Layout! *****************6 Y4 N; Z7 J' x+ m' H% Z
    // If you're going to split this code across pages, this is a great
! q6 |+ s. P! G, [: p& Y* H2 `    // spot to split the code.  Just be sure to remove this comment
3 |/ M& U8 l8 w% i2 L    // ************************* MSJ Layout! *****************, B5 W2 {  C( F; a7 Q7 z
   
7 `4 {* ^) Q$ L' D1 s) N* ?3 P6 ^    //: S8 U: Q7 x) c: d2 e, Z
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
, m" C6 N' {8 r3 |- x. G) @$ J    // 第三部分,调用初始化部分& q9 h  m+ n8 e! v5 I
    try     // Wrap all this in a try block, in case the init routine faults
' A+ I" q$ K0 O0 d1 M, c# e    {
1 Q: {, `5 J+ R) s9 \' C3 u$ E        nModulesInitedSoFar = 0;  // Start at array element 0
$ ^# \6 {& k( ?3 f; ]  f& R% H8 y0 n
4 O0 ?- _$ |& C        //' C5 b# j9 |5 C
        // 遍历模块队列
8 z4 }+ Z' u2 h7 d: S0 [) L        //
! l, O4 g' D: e: c        while ( nModulesInitedSoFar < nRoutinesToRun )
: Z" a& R- B0 M! L+ U2 Z3 o+ R        {& A# a" _6 }) T" ]9 M* V+ G: l$ Q
            // 获得模块指针
3 n4 z( [/ b" s6 C            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
$ b+ h8 A0 K, t$ W3 K$ j
: g. u. A- c3 ^! ^! R) K5 T1 _0 P            // This doesn't seem to do anything...' s4 B9 {" Y" G8 y
            localVar3C = pModuleLoaderInfo;
& P9 Q' h1 n4 X/ V% V            " N9 b( f2 i6 L
            nModulesInitedSoFar++;! d) D/ H4 S& u7 u- i* a/ N: a
                ( Z, ^8 g# ]6 a5 K' h# n1 f
            // 保存初始化程序入口指针! M8 T7 m( j$ g# p9 [
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
! h# s3 ?. }6 {4 {( f0 |6 B+ o            
/ C; Q  r: d* u            fBreakOnDllLoad = 0;    // Default is to not break on load' a& K- K- h: ]+ H) v

4 W$ M0 I6 n9 o6 t            // 调试用
( {  y$ p+ z0 E7 _            // If this process is a debuggee, check to see if the loader2 F" R/ @; z8 w% g
            // should break into a debugger before calling the initialization.
& O& v) d! ^6 }            //
0 ~/ R- a7 B9 A& C7 \: ]) }& V            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()4 F% c; E7 p2 z4 i. t4 x
            // returns. IsDebuggerPresent is an NT only API.
4 S6 O% h; I+ ~, C            //( a% y* F- E- q  K9 R, `1 m
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
  l* ^5 f/ y6 x9 U% `            {
$ A; Y1 H7 _7 V+ ]( X4 ~, W8 B                LONG retCode;
, b2 Z2 s6 m# W  r
" m( b6 [: C: n2 E0 H+ B' Y                //              2 q1 v7 s/ q1 h/ A$ c* o( p& {
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
* O4 U& l4 X7 M' r: u5 K) Q                // Windows NT\CurrentVersion\Image File Execution Options") [7 O% ?5 c0 s2 U! k+ h# z
                // registry key.  If a a subkey entry with the name of
# i7 i* {0 s# t                // the executable exists, check for the BreakOnDllLoad value.% k, a; l1 _. |
                //$ h, A/ Q0 [4 J! r, {
                retCode =
9 H) Z3 ], `6 ?3 i                    _LdrQueryImageFileExecutionOptions(
) L& }$ f% w: W9 X                                pModuleLoaderInfo->pwszDllName,
" d+ i; J$ _3 g+ h( b/ k                                "BreakOnDllLoad",pInitNodeArray
; s/ t# S* d: C4 U8 |& S                                REG_DWORD,
: S4 [0 {5 ]7 j/ @( `                                &fBreakOnDllLoad,0 ~  a! `: l/ B+ x
                                sizeof(DWORD)," n0 n7 W0 H* ?7 }3 v
                                0 );
; q# d, b* V4 v* d$ `
8 X+ m, |* P# }, i                // If reg value not found (usually the case), then don't& Q9 Y* c9 `5 @- l# U8 c- a% ^
                // break on this DLL init
' N9 p* w! ]# }3 W+ S                if ( retCode <= STATUS_SUCCESS )
& q+ c$ k9 E- j$ E2 B& t                    fBreakOnDllLoad = 0;pInitNodeArray& G- A# a6 ]" X* ?3 [6 b
            }
. f9 ~: [' B$ e/ ~' Q8 @" {            / F% l' p( u) R$ g
            if ( fBreakOnDllLoad )
- W1 u( w( t# \  n3 a  M            {           . G* N( [# D. A7 c! ~
                if ( _ShowSnaps )
0 w7 A6 }8 K! A: v' C8 W% Y                {
4 ^  l% f" @5 Z  h8 d, D4 g! f                    // Inform the debug output stream of the module name6 @" O. [: F8 ]6 v# h
                    // and the init routine address before actually breaking) G. @0 |; L! S9 Z
                    // into the debugger
3 M7 ~0 \( d6 ?
: U" g  m; A( U; @! E1 v: S. |                    _DbgPrint(  "LDR: %wZ loaded.",
' j: @6 z+ V) A, e: r& T                                &pModuleLoaderInfo->pModuleLoaderInfo );( ]8 u; B6 x" W0 `; @6 o
                    . c: o- t. l3 J, j) R
                    _DbgPrint(  "- About to call init routine at %lx\n",
* Y+ M: q0 I, h; h% j                                pfnInitRoutine ): M& v) n. ]5 t* g& I2 }& v$ s
                }
1 ~3 e! t4 x; Q& J  A$ G& e               
8 s4 @+ |8 Y2 W+ e                // Break into the debugger                              & `7 Q3 o  x  c4 D
                _DbgBreakPoint();   // An INT 3, followed by a RET, z7 ^/ j9 o/ Z: m8 N5 i0 Z8 Z
            }
6 O$ O: {  Y( n) |4 G( C            else if ( _ShowSnaps && pfnInitRoutine )
# F- s5 X3 ~' r3 N. L' i            {" p/ R; Z8 ]0 q# |& x
                // Inform the debug output stream of the module name: i/ _! o/ ]$ j
                // and the init routine address before calling it               ' D" n6 [8 m: g3 Q3 n
                _DbgPrint(  "LDR: %wZ loaded.",
! _% V9 q* r# z: m/ A" Q3 s                            pModuleLoaderInfo->pModuleLoaderInfo );
. y- q& S" M9 y- d" T- ]1 G' k8 k1 ?* V4 M9 H4 _0 Y6 ^
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
4 x# ^5 U: X, G) v, h" C/ {            }$ ]% U3 s) a, D( X  L. D4 i% ?
                    
# R5 j8 T' Z/ Q7 ^2 V" V& r0 U            if ( pfnInitRoutine )
3 V: y; Y( _* @9 w+ _            {
; U( O& F/ N: J7 }; {1 Z                // 设置DLL_PROCESS_ATTACH标志
7 y1 W3 M3 }1 K8 O0 N0 X7 T6 n                //
% H' E: o% O9 ^" X8 l$ B                // (Shouldn't this come *after* the actual call?)
/ g1 w' R/ C: U8 G3 U) ~                //1 R& I! {; J0 T4 @$ L2 F5 p- N. }
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
8 }/ N5 }# a4 `                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;6 w& M1 Y' @; c" a6 Y, T& N9 q
, W, i! m- j" |: ?
                //
$ A/ _4 s7 o+ }                // If there's Thread Local Storage (TLS) for this module,
  x# {$ P% U1 R0 X  I3 Z( w                // call the TLS init functions.  *** NOTE *** This only/ Q6 |" i% h, v# s2 I3 Z
                // occurs during the first time this code is called (when2 c6 w4 V8 @' B3 Z% L. J8 M5 P
                // implicitly loaded DLLs are initialized).  Dynamically
% {& c+ z, K7 f: s% ?) C                // loaded DLLs shouldn't use TLS declared vars, as per the/ n' z6 ]+ @$ t; g6 k
                // SDK documentation
7 A0 X# |% x3 _# m                // 如果模块需要分配TLS,调用TLS初始化函数
4 _5 F& x' T5 c3 S" |+ Y! Y2 u4 E  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
+ t% r" W. t" J2 V- B3 v( x2 J' Z  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量' U2 j4 l% @2 s3 q2 b" _
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
1 h# g) u/ w, q6 w& n( n+ a4 X                {
* L7 H* n! P9 q3 l- G, J                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
6 N' l+ R, m" W: X                                                DLL_PROCESS_ATTACH );
5 V4 s6 {- p2 h* b                }  I+ G6 q' x$ u/ n2 T
               
9 W- z% A% H9 S5 X6 X9 f2 P- H& T! _$ {4 f  k# D8 y: n) i9 K5 F# A
                hModDLL = pModuleLoaderInfo->hModDLL
' [0 u0 T; m; I, M( t4 n
5 `! L( T7 T( P1 ?: e+ G% K( k                MOV     ESI,ESP // Save off the ESP register into ESI5 C( M) l8 Z" e9 I
  ( u% b5 L% a8 O# J) x" m
  // 设置入口函数指针                / y5 n: Q6 s4 O8 Z
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     / h$ u0 Y- r7 \2 `/ h* y/ r

. }' g4 x5 q: \- O" U                // In C++ code, the following ASM would look like:
" l: k1 V" S' f! Q& p0 p% f1 Z                //
! \& P- s, A# j. J% C, l* e                // initRetValue =
. u0 ]) X' w! d                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);6 I* ^$ y5 F9 m' a9 N
                //1 u$ ?" |5 x* Z1 d' ], K
* D+ T1 U) _: h# V: S0 S4 @
                PUSH    DWORD PTR [bImplicitLoad]
0 _: G$ L/ Q9 f0 {  o$ Q                  s% l/ Q# x' d, M0 L. j2 y$ }
                PUSH    DLL_PROCESS_ATTACH
! \) O' L& |; Y, f                % [7 v/ f/ R/ W9 d  P7 y
                PUSH    DWORD PTR [hModDLL]
7 ?! p. O% S  n3 F1 n               
2 X6 d. X! |* J6 T                CALL    EDI     // 调用入口函数
& u3 }- W. `$ f! ~* g% {: C! B                # D: Y" [7 b9 R9 M  S
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值8 R0 A! h% V* ?/ a

# h* l  ~9 U2 N                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the: h% c5 N, O& M3 D2 u
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns* W. q& @9 I6 l# }- E- a. Y
. ]3 ~5 h) R6 V; ^2 k  M* T8 {
                MOV     ESP,ESI     // Restore ESP to value before the call0 u$ {* q7 B8 z% G1 V" h
  O5 w( u- H- a3 F3 e
                //
8 L: j/ G: P7 [$ ^+ T* G                // 检查调用前后的ESP值是否一至! x$ h; F7 m  K2 m) H# ]
  // 2 b6 A/ U0 i# Z* h( h
                if ( _CurSP != _SavSP )
8 K5 D# J3 G$ \7 n; e) ?                {
5 K: T% d1 G1 @: i! N+ ^% c                    hardErrorParam = pModuleLoaderInfo->FullDllPath;6 H' q: O3 E: @7 j( L

* {* ]/ W3 E8 G                    hardErrorRetCode =
) c. I$ a  T. N1 t                        _NtRaiseHardError(/ H7 u3 i  U) @  G) n
                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,% O2 s" n1 o4 X2 F1 L
                            1,  // Number of parameters3 _9 B. |3 O# E, ?+ a+ _
                            1,  // UnicodeStringParametersMask,2 i, e# d/ y! F( t  l
                            &hardErrorParam," K& h) A4 T$ G
                            OptionYesNo,    // Let user decide
1 y+ P4 P/ U# W$ o# f( f2 G                            &hardErrorResponse );
" e% o2 N: n2 ?/ x1 u( D                                              T( e$ I& h$ W' G* q0 J" p
                    if ( _LdrpInLdrInit )" U4 c  P* v( B
                        _LdrpFatalHardErrorCount++;9 g( B" ^, r1 z0 q0 u- I

% d+ }9 ~  d- I. T                    if (    (hardErrorRetCode >= STATUS_SUCCESS)/ Z* r1 F' j3 X5 p$ E
                        &&  (ResponseYes == hardErrorResponse) )$ M, T: ^. Q( }+ W# F
                    {
; @7 D* c" H8 w- `) ?& Z9 q                        return STATUS_DLL_INIT_FAILED;" F  u3 ?) A( |* Y7 T
                    }
) e3 p/ a$ I/ d7 O1 `9 Y  F$ R  R                }
# F  T3 c: m. G6 N  k5 g: A* A* H& [: U% A$ r7 i9 u( W
                //( F: [. Y3 {5 r; F, E  L4 n
                // 入口函数返回0,错误! U6 e$ y4 P. q/ Q2 k  D) z7 T) b
                //& w' k2 F6 i1 H5 Y/ e
                if ( 0 == initRetValue )7 A( p3 F' f8 C! q5 b5 s
                {
# D1 _% m. `1 m                    DWORD hardErrorParam2;
7 O8 l' C+ p$ y* [                    DWORD hardErrorResponse2;
: R, w9 O- b1 q. C" s( t  h2 c9 G                                       
  d5 q& q; F- v' H/ \+ s                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
9 j5 G$ s! X) U: P2 ?9 S$ l                    $ h7 l  p* m$ d( v
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
; K+ O  H1 l. g7 ?. P                                        1,  // Number of parameters. Q# y- @( m- Z$ C# T4 F3 S
                                        1,  // UnicodeStringParametersMask3 P' E3 s2 }4 B0 B( l
                                        &hardErrorParam2,) K& ]8 t/ `- K6 _2 u& _
                                        OptionOk,   // OK is only response
5 D/ B0 G3 k" S5 W$ {                                        &hardErrorResponse2 );/ \2 t( b$ M# }/ Y7 Z) W# Y3 e
                                                            
) b! L/ ^  ^( a5 D, W                    if ( _LdrpInLdrInit )
  s$ m( f( r. U6 v6 l                        _LdrpFatalHardErrorCount++;
% x% }: h5 F6 p* x; j0 l" j* x9 E. k+ C/ C; G! W
                    return STATUS_DLL_INIT_FAILED;8 U  s  P. D( Z# c4 _2 N* G6 `
                }0 E0 t7 f' h5 ?! k& v
            }
6 a) f; k8 C' g$ H! ?& t        }6 J! U5 _3 c, D  s4 k( p+ l2 i
5 _5 m# r, P" }2 i1 \
        //9 E1 z$ H9 T: E
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时3 j# [+ k8 _% l  Y& K
        //      0 G! T( _) {+ P  c/ r+ T
        if ( _LdrpImageHasTls && bImplicitLoad )
6 U! S2 t: E/ }. u3 }' {        {
" y( p9 f+ Q% ?3 u, Q            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
( d/ G, y% W9 N/ A( N! i                                        DLL_PROCESS_ATTACH );) ]7 u( t: V3 p. a+ u
        }- p; T7 V/ }" }% A
    }: \+ ?) c% e- n9 @1 [" e' h; j
    __finally
" l. P; F7 w) B- ~# k" m; Y/ i    {/ j. i) q# K, }! o
        //
8 }: @& U/ i7 i) g. v        // 第四部分;+ H& M( @+ `+ E" B/ }! n- O( h( D
        // 清除分配的内存
% Z9 _8 r$ V8 V: ~5 h        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );0 V# z0 w9 b: O' o: p8 Q
    }
: F. m7 S/ L% {8 v
% w, ^! D4 V, ]    return STATUS_SUCCESS;
# ]. |# `1 }* P* }}   
4 l0 B  r# I/ d$ u. M2 P! Q; Q) r2 d
这个函数分为四个主要部分:
" U+ c0 ]* ?$ z一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
  m; B) G  X; D' d3 Q二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
2 t+ N. ^/ }- E, W! S, w三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。1 T8 h) }3 b: T& V9 s5 x
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。0 P0 P& {* _* \+ y. p8 T
在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。6 s9 T' a2 @# q6 I+ `6 u2 d0 N& v
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-11-1 19:36 , Processed in 0.181812 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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