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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
- _& x. `% p% x' G- w3 s
- l- t" u! F3 I% x4 GWINDOWS 2K Dll 加载过程
) o0 I! \( ~) B) J( m$ O1 k4 Tjefong by 2005/03/305 G  t* i0 ~3 n$ S
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
* c" ?# {/ \' X* N在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”。
/ E  K& N. ~4 o. ^) N9 q你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
+ O8 f6 \9 s8 ?+ d& q& N7 C, P- s初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。  U, Q/ r2 J0 b8 f7 b, G
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
  f- f" e. o7 s$ {  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。4 M) y. D' W, \4 }- i" r9 c" [1 g; r
  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
( q, k1 V7 ^7 s0 y3 c; Z//=============================================================================/ W0 S9 P+ D: }. l
// Matt Pietrek, September 1999 Microsoft Systems Journal
4 O' l2 q# b  K; @. n1 h1 w// 中文注释部分为jefong翻译4 Q1 [+ v5 M: l4 k, }$ b4 Y
//
3 G4 w# e; `! T+ Z5 R// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)% n4 U  W  d! E. \
//+ P; g8 l4 Q) m- j& I4 l
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
- v" ^! W& R6 ?# L//=============================================================================+ i+ K" T( l7 K$ B. \
; f6 H* {& t5 d2 r
#include <ntexapi.h>    // For HardError defines near the end
6 z1 |  c- E2 S+ C. K
5 O3 L1 v( ^5 U) c& Z" P// Global symbols (name is accurate, and comes from NTDLL.DBG): |" k) h: o, P6 z
//  _NtdllBaseTag, Q% u5 F% W+ O+ V# S
//  _ShowSnaps
" e. D6 }' h$ K( S" g; w- O//  _SaveSp
# G/ |' G  r3 r//  _CurSp
4 G" b, @2 m0 a0 x, @//  _LdrpInLdrInit
5 Y* ?' Y7 ]/ }+ d//  _LdrpFatalHardErrorCount- B9 \, Y" `9 L0 G3 ^7 q" H
//  _LdrpImageHasTls/ N( H6 `5 g( N- U5 r/ t& s! z

: W4 @1 q/ L/ ^) Z  v5 m$ INTSTATUS
: G" d; N: o6 M: m2 FLdrpRunInitializeRoutines( DWORD bImplicitLoad )
; t; s& c& Y( o8 Q8 v{& W6 e+ Z# _6 L) e& |0 G
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
7 D/ C. ~* K; F& T6 R4 M    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
9 P- C- i  c, L3 M+ w$ ]4 S$ }5 n$ a/ `3 c% s
    if ( nRoutinesToRun )" I( e$ U/ Q6 _2 h
    {
% {+ @% v- t5 D4 M        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
2 w5 S2 X9 _$ a# I' T+ i, k        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),. y. E8 `* Q  S- B
                                            _NtdllBaseTag + 0x60000,9 R+ e& ]5 ^4 O0 y& u3 I6 F
                                            nRoutinesToRun * 4 );! r" _6 v! |! L1 F
                            3 g& B4 F8 j% R& a6 i1 V7 R
        if ( 0 == pInitNodeArray )    // Make sure allocation worked
1 F# Z+ Z# J3 x( i* a: f+ j            return STATUS_NO_MEMORY;
7 \5 O2 ]6 s% Y1 p    }/ f# \: {6 X5 b2 O( Y
    else
+ r3 t$ v/ x7 {6 U& `/ ]        pInitNodeArray = 0;6 x! G3 r. I. P" |

+ h. n, f; b8 F, P! O7 z/ l    //第二部分;" v. n/ A0 Y8 x( f
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。* V" t* O* s5 `- m0 J( M
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);/ d6 D0 }$ R$ ]. K! m7 ~8 z& a. ^1 o* Q
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;' U- u, L: u  c2 ^
        
% F! e8 }$ D* i6 u    if ( _ShowSnaps ). t. L9 w% N6 I" a9 _9 i# f
    {: M  U7 ^  `% N5 Y
        _DbgPrint( "LDR: Real INIT LIST\n" );
: T6 B5 x, C9 I    }2 S& m  s/ }* C# j% z  E2 X
! u- G, n5 d7 k/ Q
    nModulesInitedSoFar = 0;+ E+ e7 x3 i% W/ Q
! y: w' A& _3 }% Y7 C1 Z
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
' G* A, ?6 M$ X- w    {/ x( s" e! @- E6 |0 ~0 H9 n
        
( V3 {4 a* `% _% h8 o" ?        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块/ u" ]( }7 p5 R* [
        {% J# r- _& V, T% z9 r
            ModuleLoaderInfo  pModuleLoaderInfo;
  q3 f9 V+ }  }+ _# o            
( n6 u6 Q  P4 H" F0 Z9 \3 o            //
5 L1 `: q2 W4 [            //一个ModuleLoaderInfo结构节点的大小为0X10字节
0 @: Z6 f  o, V$ s            pModuleLoaderInfo = &NextNode - 0x10;- ^" J3 q1 ~8 C/ V
            & j& N7 Y( O: T- b3 p5 Y
            localVar3C = pModuleLoaderInfo;         1 _6 s3 @1 P' S$ |

* @/ W$ j/ a. _! y" D$ V+ E9 @            //
3 ]2 k# }3 S/ p- Y            // 如果模块已经被初始化,就忽略/ o8 I. Y% |/ k2 ~3 a. l2 I
            // X_LOADER_SAW_MODULE = 0x40 已被初始化6 J8 I, m, S% n0 ?) x( H& e
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) ), m% O% D" c6 I' z6 t
            {' K" j1 x8 y" y* Y+ N8 E
                //
1 p  J3 z3 s3 a! L  B                // 模块没有被初始化,判断是否具有入口函数; X8 I" U+ s, @, T& t& v' ]
                //3 ]4 i/ I- n( ~; I" C
                if ( pModuleLoaderInfo->EntryPoint )
) T4 K0 ?8 a2 }$ K                {7 g% w+ W( i# H$ q# j8 ~
                    //  |  E+ s: f& _& r# I
                    // 具有初始化函数,添加到模块列表中,等待进行初始化9 _4 _! a! Z+ ], g$ |) F9 X; a
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
' M+ q% a% l7 e! q  h
" z' s0 J0 Q: {8 t+ V                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址2 W' H! z  G& m: q$ q; b- U
      // 例如:
8 o  p  n" A4 G; h/ }* C                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000. [& |. B6 F! ~% q+ q3 j
                    if ( _ShowSnaps )0 K: k/ v& ?8 v8 J! ?2 E7 K# n
                    {! A2 d; U5 u1 w
                        _DbgPrint(  "%wZ init routine %x\n",! @! v( u- X& Y* n7 {( n7 ^
                                    &pModuleLoaderInfo->24,- E' m6 M6 C$ n0 w0 f! v
                                    pModuleLoaderInfo->EntryPoint );
1 S' w3 r7 ]. n2 ]1 l9 i* O                    }
6 O( ]1 S* N3 N% u$ @! h" J
1 i$ o0 f6 k% j5 O$ u1 u# ~                    nModulesInitedSoFar++;+ x' I7 @3 Y0 }( W3 \5 n
                }! }+ p$ C, r4 V8 Q( W' S5 o: p
            }
0 v$ ^$ O; u4 Q+ j  X! x* k
- P* o, J4 q: T$ R# `& z0 ~            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。% r4 R; c6 m' N+ T
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;: P  A( ^; H4 b  p
# W1 v; z2 T1 o- ]
            // 处理下一个模块节点
; Q7 h# |6 A' f3 q            pCurrNode = pCurrNode->pNext
7 k1 Y: H( q+ E# @  j        }
* T) M3 u- R( G- X2 H! B; y    }
& a) n' ~) i$ Z" }/ ]% O. b/ _    else
! b2 V  y1 L+ J1 T    {
3 n% f# k8 v# E! s) S/ p; I% L8 E8 O        pModuleLoaderInfo = localVar3C;     // May not be initialized???
5 q9 M" \% Y8 T. W$ V    }
* P2 q5 a; q5 D& b6 n- I   
# P6 M. ^0 I, Y4 }1 Q    if ( 0 == pInitNodeArray )
1 h+ Q# N2 v  u- D        return STATUS_SUCCESS;- P8 {6 q6 l% u0 ?

7 I9 Y1 g$ R9 A0 T3 b    // ************************* MSJ Layout! *****************
& K$ w5 D8 ^1 |# w6 V8 U    // If you're going to split this code across pages, this is a great' o7 K# w* I  \; N$ Z
    // spot to split the code.  Just be sure to remove this comment5 `1 i3 G3 H. h4 u; R( d5 R! |
    // ************************* MSJ Layout! *****************
! b7 ]  z( K8 j% q) p- v8 N& D    & ?/ ~( y% C/ l: i- r0 H; W$ e
    //
3 L+ O6 ~% x0 d+ ^8 `    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH/ P9 H! x, u) N% U6 F" z- u0 ^
    // 第三部分,调用初始化部分- ?; p- {3 Y8 m4 J3 ^
    try     // Wrap all this in a try block, in case the init routine faults
1 F3 T8 l2 o0 O6 k( W" V    {
! f! u: D! {& T- ]$ p" a+ s        nModulesInitedSoFar = 0;  // Start at array element 0
- E. C5 `. I! }2 O8 `( j' a2 [
3 \6 Y) `1 [  n! E        //
3 B4 \2 E" J" v' r) j, U        // 遍历模块队列
0 z. m; D6 ~$ y* k9 l; p        //
9 i. s* g& ^1 @# Y5 j- m        while ( nModulesInitedSoFar < nRoutinesToRun )7 O3 b8 E( O8 h
        {8 F" F7 \/ h  {
            // 获得模块指针
5 n, Z8 d0 T7 I# \+ B5 w% J3 k            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];* T/ i  m7 ~  ~5 `
5 Q7 d) j+ J7 Q/ ~1 i- {& h
            // This doesn't seem to do anything...- ?! }9 r1 n# j. B# W: H
            localVar3C = pModuleLoaderInfo;
; U7 S1 v; o1 |            
2 ?( V' \8 U+ S7 _1 r            nModulesInitedSoFar++;
. I, c9 e6 E/ b! K+ X                0 D! y, f% z7 M
            // 保存初始化程序入口指针
5 G( O0 b5 x( N9 F, P  H7 {            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
) a! [9 _& Q- K) T+ T3 l            7 [3 l- k# q" T: y  T+ c. V! Z0 D. J8 G- N
            fBreakOnDllLoad = 0;    // Default is to not break on load+ ^! ^# A: R% F5 V+ m: O
. f% v1 \; X* ^" }6 c4 Y& Z
            // 调试用
9 |! U5 a- d3 Z            // If this process is a debuggee, check to see if the loader0 G# z( l. n7 [" _, r0 `
            // should break into a debugger before calling the initialization.9 o- {$ \' U: \
            //
* {# i# B' I4 o, O3 f. [            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()' y' j9 H' v1 J8 y
            // returns. IsDebuggerPresent is an NT only API.
# [$ ]% y% d0 s/ O2 M. ~            //
3 R/ D1 n# O7 I/ [& `9 d' K            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
! {3 R5 Y" O5 |* M  d/ N! j            {% A- {! t  Q! I& w# l8 r
                LONG retCode;  S4 q5 b1 u6 C% O1 ~
8 g$ [0 `: Y; _" Y+ N9 R
                //              ) n" g. Z  C' \# P4 |8 v8 G- k
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
5 }9 k; l+ ?6 G5 ]4 L                // Windows NT\CurrentVersion\Image File Execution Options") g; t2 Z  Y+ B+ M
                // registry key.  If a a subkey entry with the name of* _+ K0 N, J: R6 K7 g- ^% l
                // the executable exists, check for the BreakOnDllLoad value.: b6 f2 W8 K0 Q$ z  O4 _: t1 y
                //( J: L, o! ?+ }9 t& T7 R. \* D9 i* y. ~
                retCode = ! o3 k. b' ]; V+ {8 I. x  ^
                    _LdrQueryImageFileExecutionOptions(
- {2 i! `# [4 p- |; X) e                                pModuleLoaderInfo->pwszDllName,2 b6 [3 s% @% j3 ?
                                "BreakOnDllLoad",pInitNodeArray  X: x* w: P! x# [8 a6 t9 e% B3 o
                                REG_DWORD,
; z/ J/ A8 f' A                                &fBreakOnDllLoad,
6 z! k5 `* E1 f5 @2 |+ m                                sizeof(DWORD),# K6 t* o% Q0 l5 Z& l
                                0 );) w7 f) ~+ n7 m" y2 {1 f( G
9 \4 k0 o: c  f9 S% A, m
                // If reg value not found (usually the case), then don't  M1 m1 ^4 G* z! Z1 }" h; Y* A* @
                // break on this DLL init
5 r4 U0 J+ T1 S7 U$ z4 v/ _0 x* ~; C                if ( retCode <= STATUS_SUCCESS )
  T4 [" n2 w- S                    fBreakOnDllLoad = 0;pInitNodeArray% r, g2 u) G: V$ r3 V
            }
$ u8 E6 G5 l" G) r! k9 ?            & ^& c6 w, L0 h4 J$ V8 X
            if ( fBreakOnDllLoad )
3 a$ C: e  m# y/ x  a( b            {           
* h7 n3 f& Z4 k                if ( _ShowSnaps )
+ P3 E/ [4 R3 e; Y  u! L                {
: u) C& ?6 T8 Z+ L9 l- b. @  v                    // Inform the debug output stream of the module name
- M- Y+ s; o7 ?' P. u9 m+ Z                    // and the init routine address before actually breaking
! K7 e  I5 ?1 y/ i9 z! L9 B) r% ?                    // into the debugger+ @' ]2 X2 F! t. K, r6 l% v. `  H
# k, c# _( n& a' W8 `  K( j
                    _DbgPrint(  "LDR: %wZ loaded.",. c! U8 e$ r# C) Y
                                &pModuleLoaderInfo->pModuleLoaderInfo );
! K7 X. W. W% T5 r, x& M) r# ]                    1 Q2 ~3 i* u* j" [+ P: o; T
                    _DbgPrint(  "- About to call init routine at %lx\n",
- j; l. K. m, S. `) T                                pfnInitRoutine ): n: u. X9 Z6 c* U
                }
. w' q( O' f# h               
7 C1 D% R+ W1 J8 w( P; g# D                // Break into the debugger                                ^" l  @  c0 ?  a" w5 ^
                _DbgBreakPoint();   // An INT 3, followed by a RET$ n6 j! h" e3 u- U3 U) r% b* \# T
            }
! I$ [7 V. H2 a) Z, y3 m            else if ( _ShowSnaps && pfnInitRoutine )
4 \1 T  x4 T; w4 d+ i: ]            {
8 m. G3 @+ v7 |                // Inform the debug output stream of the module name
: y9 h% h8 T' u* u* D                // and the init routine address before calling it               
4 v: |  C; X7 B# s( V9 T# q( _3 a                _DbgPrint(  "LDR: %wZ loaded.",% p6 k# n1 S0 @
                            pModuleLoaderInfo->pModuleLoaderInfo );
/ t. `* ]" g2 _' x# w2 g6 J/ ~0 }& X* Q8 g; D% a/ g; j
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
3 |* M3 X2 G  k7 q3 ?5 G            }' W* t( c& R' g% P, n  I1 t1 V7 O
                    
% j' d2 {6 x& Q7 S1 W% b            if ( pfnInitRoutine )
. }  z, X$ v' V8 D# @8 T            {4 p  Y; q( n# s4 ?
                // 设置DLL_PROCESS_ATTACH标志
" }/ B! @7 G7 _% c* D                //" |5 q- R; l* B0 j$ |
                // (Shouldn't this come *after* the actual call?)
, Z5 F9 s7 c6 o* \                //$ b; ]- e: I0 K9 o/ R
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
1 Q+ o5 L6 h, i9 A7 u# ?. A                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;7 k9 @2 e$ C! j8 U6 N; @
8 K& i0 V' A% x& M  T
                //
; ?( p: J2 Z- b( z7 e) {8 E                // If there's Thread Local Storage (TLS) for this module,3 {9 Y' k+ }  }7 X
                // call the TLS init functions.  *** NOTE *** This only
# x8 r" e9 M; i$ L7 w3 ~  ]                // occurs during the first time this code is called (when) G* d# W( j- J. g, B
                // implicitly loaded DLLs are initialized).  Dynamically7 m( F& A: f3 `; B, E" V& N: j; q
                // loaded DLLs shouldn't use TLS declared vars, as per the5 Z& Q0 [( w" n  A
                // SDK documentation
- V6 u; Z3 A0 K  f* ~- r* B                // 如果模块需要分配TLS,调用TLS初始化函数
7 O# O) B2 p. {1 l4 _% a  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
6 _1 @' H0 W9 E, k) \5 x  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量6 ~/ m  Q3 P* Y# P; F4 p9 b+ b
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
; T" y% J# L* R% L( B. @$ R! {$ Y                {) H8 i: {$ t6 {6 T
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,+ l+ I& l( o$ ?8 O0 x
                                                DLL_PROCESS_ATTACH );
  w# h5 V6 z1 l  \9 H/ ^' J+ w( S                }* ?' o5 l8 f8 k3 B  _! f* }
               
: S( W; l$ x/ Z6 S) n; W
- x% l! w8 N6 \; ]+ _& p                hModDLL = pModuleLoaderInfo->hModDLL
6 g; ?* {9 g+ {! b1 C* E9 @" ~  `3 V! \, P
                MOV     ESI,ESP // Save off the ESP register into ESI+ b0 }2 F3 n) B. L. d9 Q( K6 r5 B% s% A
  # s" s* S/ o+ r
  // 设置入口函数指针                # ^+ R8 H5 }) Q" w) |: w
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
' c2 I$ x7 ^7 [" b) r$ v3 a. p2 J. _; R1 I
                // In C++ code, the following ASM would look like:
. Q) I" j3 l$ v1 r" W                //
" o9 _7 p$ G& f% z) p  E( c7 o                // initRetValue =# t6 ^7 A: ^& c
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
. U7 n" k. }$ t+ o& q- Q6 k                //8 \) B2 |5 v2 C+ R$ t) l+ A' i6 f

( G/ j3 j! Q: n/ @& ?+ Z7 M                PUSH    DWORD PTR [bImplicitLoad]8 a1 q2 y) z* ?
                # z1 b$ `' K6 ~" |# j- {$ R
                PUSH    DLL_PROCESS_ATTACH( {; j( x( I9 j, s
                8 {+ L. z8 a$ A1 f9 W
                PUSH    DWORD PTR [hModDLL]$ L5 g0 T% ?, D& F, x& Q
               
7 W6 K# v0 G; H9 ]1 s0 K# B                CALL    EDI     // 调用入口函数- \3 f- \9 ], E
               
. Q  y( n) x' t0 w6 t  q( ]* N9 c                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值
4 m  X* k5 E4 `8 {! Q! G( ?2 A! g' n2 B  V  B
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the
1 D3 h+ w. z% b$ k9 @- Y- z                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
& }, r0 {& Z  R/ H
9 Z6 Z* j' _! p$ H+ J' n                MOV     ESP,ESI     // Restore ESP to value before the call* v  {7 D: R3 E5 ]3 ^' L! b. L1 e1 Q+ c

# t# ?3 o4 S, V) V9 U# A                //
; c% l8 @; O: k" y) t, x7 O                // 检查调用前后的ESP值是否一至
& L8 b$ y( b5 `6 l: r4 p9 r  //   R4 E) n& ~. N. X! f9 f; s
                if ( _CurSP != _SavSP ), t) q2 {) q, H4 s+ Q
                {
. q) [/ T/ `& g. P                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
* e1 D  }5 @7 }2 j* d8 H
. q+ s7 h, x  t5 d, n. |* O                    hardErrorRetCode = 0 |5 x1 W8 }; k/ m2 k/ l& w0 G
                        _NtRaiseHardError(& L/ ?2 h, q+ i  {
                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,  V/ f' q( J" m  R" d3 C
                            1,  // Number of parameters
1 Y( K, L/ X2 n0 _$ O) e5 S9 @                            1,  // UnicodeStringParametersMask,, `2 D3 Q5 w$ w& `5 X  {. K; K! W
                            &hardErrorParam,
5 o  T8 [  e- W2 r                            OptionYesNo,    // Let user decide" O) J& A8 T  m9 l( {0 H- A
                            &hardErrorResponse );9 Z, u: G' w# ^5 h/ S7 o
                                            
3 A& j; l* b) W$ M8 Y7 r                    if ( _LdrpInLdrInit )
; g& n) E) m+ b                        _LdrpFatalHardErrorCount++;
+ e: R7 A4 z5 c( q  W( T
8 j5 z3 O  }9 F1 D1 S2 g+ X                    if (    (hardErrorRetCode >= STATUS_SUCCESS)( k% a# X6 p3 i. F
                        &&  (ResponseYes == hardErrorResponse) )9 s7 T5 J8 N; k
                    {
+ p! E6 f- n" H$ _/ {                        return STATUS_DLL_INIT_FAILED;& S% f5 w* C1 e8 e* |: t
                    }
8 y- j. @' ~6 o  E- y                }0 z; N) r4 @, A( w+ d9 G

- s' T1 c& I+ G1 S2 e3 D& @                //
4 w! K/ B. E3 g8 v0 L3 z9 ?2 E, W                // 入口函数返回0,错误
' q' n* f% }" M! W% T                //
) l, l/ A! k2 P) W- h1 e5 p! f                if ( 0 == initRetValue )
! V  |! \- v$ h2 e0 l                {1 _! [8 [; U- \
                    DWORD hardErrorParam2;
; H/ j8 p8 F% v3 g6 Z4 z8 P                    DWORD hardErrorResponse2;. w" j- n. S! Y# x5 r
                                       
- \6 x& l) }3 a' ?' f* s% n% F                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;4 J! o% }0 A4 h% F
                    . G; W2 R0 V4 w8 B8 I
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
/ V2 b' o& a& k. Y6 P& O1 E  w                                        1,  // Number of parameters
. h! U/ T! u# G" m9 p; q8 `                                        1,  // UnicodeStringParametersMask
6 i' Z/ A: Y" A, @$ R) }                                        &hardErrorParam2,+ F, U0 m7 ^( z) ?
                                        OptionOk,   // OK is only response  z! V0 V9 W) N1 s' H
                                        &hardErrorResponse2 );
* D2 a7 h" N; f                                                            ' ^4 N6 o6 p& Q
                    if ( _LdrpInLdrInit )
1 W) D; h9 U' o0 M' C" l                        _LdrpFatalHardErrorCount++;
$ t9 l& b+ |, N* ^
  ]" i1 r" ^' f* u$ y/ {# _                    return STATUS_DLL_INIT_FAILED;/ ^# [1 ~6 B' a+ p2 t
                }8 q9 }9 L, `  p( N! M# a
            }
; u& c3 K  _& q" E! L        }( O3 g. h0 S5 @
3 R' t: F; ^; {! z: T# H
        //: j* q4 y2 o# E/ U9 j5 K
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
0 X" H6 B) X6 B: l        //      % I" w) G0 }; K8 O' b4 _  J+ x
        if ( _LdrpImageHasTls && bImplicitLoad )
/ U  k& }" ?  ^( r/ ^' }- p        {: E6 d! R; ~" x3 W( @
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,1 C  V& m  L/ q" k1 Z  r2 O% H
                                        DLL_PROCESS_ATTACH );
  C6 M7 _4 `, z) O" D2 t# k        }
7 E# p- y, e& h+ R6 z    }5 E# G6 ?& C( }/ K4 i8 |
    __finally
7 T) G1 O- w2 d% y. K0 b    {
! M& }2 J6 `# q5 {2 [# g( A- p3 P        //2 g* O% S: i  |7 E( p
        // 第四部分;
1 E- l; f/ k3 ^: |6 k        // 清除分配的内存. v" ]7 j9 b2 |  c
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );5 L7 z# h5 T. f  `3 s8 a
    }
" y; @5 M$ k+ F. D' G: F' E6 n; K- c
( M5 [8 m; F! O0 t    return STATUS_SUCCESS;
; Q( @* s. e# _$ A}   ' M$ P9 J) y; K' S  C: i+ H
: T9 e- p! z8 s3 @7 {$ _- r: v7 \  \
这个函数分为四个主要部分:# u6 R3 x( o2 K0 U& Z: M0 \4 [! \
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。4 t" y9 F& N+ U  P$ c1 A7 G
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
: w4 r% I8 @. e5 R- G+ w% v三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。! @6 G+ @  |. Q/ |
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
& O( o% s5 w: O- @在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。
4 z% q, `! \* M  R/ v- D四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2025-6-17 07:07 , Processed in 0.063969 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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