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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
8 L$ W' X" x' R2 U; K6 p
4 \+ U! b4 w5 K+ Y2 J5 QWINDOWS 2K Dll 加载过程
1 h6 T7 Q% j5 n/ b4 [jefong by 2005/03/30
0 O2 p) S# ?& m: ?这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
  R4 W7 O4 ?! ^) U# \( b3 Q在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”。
' a* u; t% h  I3 E+ e2 v  E* j你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。) u: Q4 F1 i6 `, K  v5 D0 Y$ N, l
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
% ?! w/ T' v$ x  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
3 w( G( L) o+ b: }5 T$ g$ F" e+ l  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。7 r6 y) ~# b0 l
  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):1 o+ B6 `( B: _6 D
//=============================================================================3 A* Z/ a' ^' z" o* g
// Matt Pietrek, September 1999 Microsoft Systems Journal, O2 X, t% A! t% c7 ]
// 中文注释部分为jefong翻译
# ]& [, `9 O/ _% l//' S7 i* h- n0 m$ X% {5 z
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)2 ~& A0 C  g5 k- e& m( E) N3 `9 A
//
9 K' _( C& l1 W// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
& t# n9 t* I; N//=============================================================================
1 r8 |0 |5 h  ]' S4 X2 v0 j( X1 q. `% f, o
#include <ntexapi.h>    // For HardError defines near the end
6 C# }0 c+ Q- t* }
- X, g* @; P: X7 j8 P$ I8 c" v3 \// Global symbols (name is accurate, and comes from NTDLL.DBG)! U2 h" {7 L  q% o
//  _NtdllBaseTag: m% g; E* m$ [! W+ x4 g
//  _ShowSnaps
- V3 T, f2 r3 s0 I; c/ }//  _SaveSp
  q' A; }& G3 n) p# e5 Z9 S//  _CurSp
. h) Z7 B# s  g7 F% `' h4 o+ A$ p//  _LdrpInLdrInit& B/ x6 H- m: Y* B6 W. ^. }5 ]! `
//  _LdrpFatalHardErrorCount4 _% G2 g# B# C7 n
//  _LdrpImageHasTls
+ x6 J+ _. E( W) {& M' O
3 r0 o) Y' J) i+ p( kNTSTATUS
" [, d4 f4 Y2 q2 x1 k/ rLdrpRunInitializeRoutines( DWORD bImplicitLoad )
* x6 ^: }; e2 L; \4 h* d; O4 W- D{- q% J# c* S* z( V* @8 n
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了4 L! V8 Y; w5 v
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();1 O0 K- T) I3 V
/ I4 k# Y6 Z! n
    if ( nRoutinesToRun )
; ]- Y6 I$ I' I; s: }    {6 x# q7 g7 Q+ P/ p
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。, |" U" C" j$ U
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),- [  J5 D* l0 J) b' x
                                            _NtdllBaseTag + 0x60000,% b: \4 S* |% f4 k; [' w9 J
                                            nRoutinesToRun * 4 );
- ]3 Q6 n3 A& e% V                           
$ H2 j' X! f- m  S) d/ X        if ( 0 == pInitNodeArray )    // Make sure allocation worked9 b: H: M9 Y( G* X+ T
            return STATUS_NO_MEMORY;0 o3 \! y2 k2 W4 b) Q$ X7 \# b
    }
6 L: ]$ p1 ?; `    else
! @( [# d. I( F! K6 |3 U- B* d& O# n        pInitNodeArray = 0;) z" @& [' @, y" B' M, j

* c' @  _( B! s9 j5 ?5 d    //第二部分;2 t& g% s1 G2 U( h: m
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。3 ~- l, P4 I" J: P
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);, T/ P  ~+ O3 e. p7 S
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;, X4 [+ y" O6 L7 J7 A6 b) x; v- `: M
        5 r: M5 b9 A2 S: E
    if ( _ShowSnaps )2 o2 T; J. i2 Q  V+ |  s1 c
    {: z2 K, x/ d* d2 }
        _DbgPrint( "LDR: Real INIT LIST\n" );
3 y8 y; w( U, A    }) V# o" F& `# D: f2 I4 ]( I" H( H

6 x# x) p. |  l. g- ?    nModulesInitedSoFar = 0;
! m4 b+ `: E& x+ X* p, \# X+ O& W0 Z8 _5 Q$ T5 M* `8 `( t6 s
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块* d6 T+ e; a/ X! ]# M6 N
    {8 K/ N. F2 g: G0 T% }5 K4 F  c( k
        - R' U( r* n; u- Y1 N: i
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块' P  X( G" m7 B2 N
        {6 L; M. }) R& t' S
            ModuleLoaderInfo  pModuleLoaderInfo;
$ a0 b9 m3 Z" V( U            
+ r  ~% n. n! S$ y. G            //
4 `' P7 T$ u2 h% O0 W! o0 i. z' X            //一个ModuleLoaderInfo结构节点的大小为0X10字节6 g+ m9 {$ e/ X2 I9 ]
            pModuleLoaderInfo = &NextNode - 0x10;3 J. y- P* D* L2 y) k8 W  M+ ?* z
            * `% u) _2 o  e6 A( j" p' H
            localVar3C = pModuleLoaderInfo;         
+ c4 o/ p! f" n1 G) `/ C! `4 ]$ E8 l- S/ n  b7 A
            //
% |( X+ B$ V$ c/ J2 c            // 如果模块已经被初始化,就忽略+ s5 i- A" I$ ?$ B/ n1 {
            // X_LOADER_SAW_MODULE = 0x40 已被初始化
6 j/ Y' w- R" o$ t5 T6 A) g! I/ u            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )8 F4 P( y8 a/ C) d+ m* r
            {8 U* l5 L, D3 u+ m. C/ @
                //, h9 s7 }3 r+ f' v# D$ C2 |
                // 模块没有被初始化,判断是否具有入口函数& V8 B& F5 o6 p6 a. f! Q6 n
                //
+ q6 W3 }0 D2 I/ x! G, q5 A                if ( pModuleLoaderInfo->EntryPoint )
" O; y  [8 d+ }- Z3 D% m                {
' ?9 ?) q+ _4 o- y$ j' u$ }% z2 s                    //8 ]0 G! i& D) v/ ~
                    // 具有初始化函数,添加到模块列表中,等待进行初始化8 t2 E* @  Q5 b! R1 b
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
# u/ B2 Z) o! Z' _5 D- v2 m
- V1 l$ [3 i. e) X- p/ B; w& C, t: W                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址9 t% n5 M: U) P( I3 i( {
      // 例如:
  x" m) J% Y* \6 A; v                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
6 r2 P) ~1 }/ F. ?, o! r                    if ( _ShowSnaps )
/ [- {" Z; O# {                    {
. p) {& d4 U1 i/ [                        _DbgPrint(  "%wZ init routine %x\n",$ b  O# w3 G4 f+ s
                                    &pModuleLoaderInfo->24,8 e$ R5 B6 z& _( u" p+ j. P
                                    pModuleLoaderInfo->EntryPoint );
6 D" S, R8 z. n5 i% l/ k7 S* D7 Y                    }+ K5 t- H8 D! u

8 [5 E) z  y5 W0 `( l' \                    nModulesInitedSoFar++;
2 ^1 g# M& D. K( Q: _                }
3 f- N+ n3 E+ y+ |            }" ~) X. p6 w5 l1 Q3 l/ j

6 H" y+ L5 L2 q$ ]) A8 m6 |5 {            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
- Z9 X7 E) V4 }. f+ @8 V            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
: A8 x0 P6 T8 H( R0 U! u1 c* z5 U
            // 处理下一个模块节点
% p' |' l7 E* W            pCurrNode = pCurrNode->pNext) U, x1 L3 q! `, f
        }+ R) n5 A; b5 y2 r9 X
    }
# y) v' l1 {% b    else
3 j' G7 F! U; Z4 h/ W- [    {
' p8 H- D% Q7 e* |! u$ d- U/ [        pModuleLoaderInfo = localVar3C;     // May not be initialized???
* w5 P0 @! u5 C9 ]9 F+ k    }* F5 C& O: g5 q  Z7 ?9 s$ B. i
   
4 a: O0 y  Z7 H9 i! Y    if ( 0 == pInitNodeArray )1 V/ R9 e) ^+ R; k0 g
        return STATUS_SUCCESS;" K/ D+ R# S9 Q0 |/ I

, h1 N' F$ |' W6 O    // ************************* MSJ Layout! *****************3 p+ G8 }/ P$ D1 M) D
    // If you're going to split this code across pages, this is a great$ x) H2 M  f: ~- |7 `0 ]# |
    // spot to split the code.  Just be sure to remove this comment2 `* C. E2 T* W2 b
    // ************************* MSJ Layout! *****************( L; ~) W( N9 A; v$ ]
   
; }/ P. \- _8 }9 N# o: S    //
: G4 j! D5 S4 r2 n) \    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH# z7 m2 @# R/ T( T" `5 b6 X
    // 第三部分,调用初始化部分
3 R, S- w/ z3 Q9 t: c8 i! V8 E9 K/ s    try     // Wrap all this in a try block, in case the init routine faults$ `! P+ I. k1 Y! h0 N* F# q
    {
& m0 B( F: o3 [* r9 G- s        nModulesInitedSoFar = 0;  // Start at array element 0- ?, `0 _4 n2 s+ i1 _" K3 e7 m0 n

( d- t. W9 j) Y  ?. t2 g        //
7 T) k+ P1 V% ?% Z9 ~+ |        // 遍历模块队列
3 ?; T5 [3 x2 B1 X        /// u, E( f, C# w) |
        while ( nModulesInitedSoFar < nRoutinesToRun ); K+ G. Z2 U+ s
        {- c7 f! {- {* B* |6 d+ q2 v
            // 获得模块指针1 y! N6 w4 z3 K, q6 D
            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
# ]# h% R* X4 [' O% h4 @9 {  N) B, U: `9 N5 k
            // This doesn't seem to do anything...7 k) p7 Q! P' J( F4 f8 M
            localVar3C = pModuleLoaderInfo;# z& g8 R! a; P$ A2 i
            2 S( V8 ]1 u1 ]% K# W- \5 L
            nModulesInitedSoFar++;
3 q# T+ T' n" X6 ~8 z- w$ u               
: h  y( m' h! J* F& h/ E            // 保存初始化程序入口指针) h' P9 P' h! h. N4 Z
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;7 {9 A# R" I* Z; Y/ d$ R
            ; m* V& X! ~3 d( `$ \4 Z/ T/ K
            fBreakOnDllLoad = 0;    // Default is to not break on load
8 Q5 r, B1 m4 H) ]3 T( c/ R) g5 Q3 t
            // 调试用, T( ~9 U. i0 C/ B4 F- W: W6 \' L
            // If this process is a debuggee, check to see if the loader8 U6 T- v4 V% c. w- @
            // should break into a debugger before calling the initialization.6 m* o2 a; H; n8 g5 ~  e
            //
, p, ?/ ]% U% m/ P% i2 A            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()% V' t# `3 v+ S: E3 Y( z
            // returns. IsDebuggerPresent is an NT only API.
* |7 |9 C2 Z+ D% b            //
3 v* \. s% t. t% U: i8 M  d% P! q            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )( N/ i+ x+ ^" t6 U
            {
3 E; F3 q( D1 _2 l# j) o  m                LONG retCode;
+ u4 i# `" f/ v& {6 t4 H% p$ w; \% M3 @( i& s9 X
                //              # r. S, |' }3 V9 R* P' }( _% J. X2 `
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
- F( Y! {' d' f8 n5 O* `                // Windows NT\CurrentVersion\Image File Execution Options"" b5 i+ @( c6 o  [* G
                // registry key.  If a a subkey entry with the name of
( L" H5 [% Y, D! {2 w                // the executable exists, check for the BreakOnDllLoad value., H/ z& N% x! ]' q) L( N2 j: x9 k1 ^5 ~
                //# N' v% v7 H" u' ?
                retCode =
1 {# A& e4 Z6 x+ Q( K1 q3 w                    _LdrQueryImageFileExecutionOptions(
9 o4 }# `0 I; ^- K* X                                pModuleLoaderInfo->pwszDllName,
8 {/ g$ ]8 _) f# R                                "BreakOnDllLoad",pInitNodeArray
7 f) I% |7 i- r7 e1 I                                REG_DWORD,8 o$ G  ~( A, _8 M% R* A
                                &fBreakOnDllLoad,
# g' Y9 K6 z; r# l                                sizeof(DWORD),
# Y& j: c; j- y+ T9 D# I: f& o                                0 );/ j4 B4 g5 v1 q; B5 k

7 g+ y( b# _5 v$ _0 {/ [* a5 T) v                // If reg value not found (usually the case), then don't
1 |0 D* b; e+ s                // break on this DLL init" x1 u! @7 x8 S% J
                if ( retCode <= STATUS_SUCCESS )
3 }  y2 m  E2 |; h2 c9 u                    fBreakOnDllLoad = 0;pInitNodeArray
, ~8 q- C  T( S) O            }# Q  E% N$ ^% j: \+ O3 ]0 Q
            # }/ @: W6 ?& O1 [. ?* U
            if ( fBreakOnDllLoad )1 e$ t: I4 n% f! b; P! G5 ^0 x
            {           - I/ v6 [0 J: _  U4 \+ Y
                if ( _ShowSnaps )
, o' u1 `; M) T2 X( z                {
) `- z7 x: p4 X9 k                    // Inform the debug output stream of the module name) M: j% Y: B! W- T+ |* _
                    // and the init routine address before actually breaking
. s( b3 x6 i3 Y2 Y  z; Q                    // into the debugger
) [1 r( _8 g& r/ L
& ~7 b( s8 g0 l$ g0 `0 M' i& t                    _DbgPrint(  "LDR: %wZ loaded.",1 ]5 d4 }/ U0 F! O* g4 g: p# J" B
                                &pModuleLoaderInfo->pModuleLoaderInfo );2 {8 V- J6 o8 d+ ^
                    
: Y& m7 Z6 F6 l" e' D6 N/ w6 P                    _DbgPrint(  "- About to call init routine at %lx\n",( Z) {$ z) }2 h
                                pfnInitRoutine )
3 S( z( ?9 l! D: l' [& Y4 a/ u                }
. ?) C* @, v2 F; L9 F% u- Q3 u                6 l( t7 @3 o" z% P6 F
                // Break into the debugger                              
# h0 G. i  |6 \                _DbgBreakPoint();   // An INT 3, followed by a RET
; t0 }! Q* B- B0 V" r. K            }
% R, j' w) s' J5 G/ h            else if ( _ShowSnaps && pfnInitRoutine )
' C0 Y  D2 U4 v2 {  ?            {
, _# A# r3 t- _                // Inform the debug output stream of the module name( P; {9 _8 `4 M0 J- j9 z0 o
                // and the init routine address before calling it               
" B! R: z0 _  g                _DbgPrint(  "LDR: %wZ loaded.",
; l7 s+ Y3 K' l7 O0 L& R: ^                            pModuleLoaderInfo->pModuleLoaderInfo );/ }6 @( M6 m$ X$ Q8 T0 ^% D' ?
+ e% ]! o/ E) J: P( w; E
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);& x% u3 v/ l1 C1 C% [4 S  H) }
            }
5 Q3 ^: }5 v; s$ H' v( v                    
6 {. C' \9 B. k0 _" U* o, J            if ( pfnInitRoutine )( s' q! k8 Y4 q8 d  p
            {/ e! j' n. B' a" g& M
                // 设置DLL_PROCESS_ATTACH标志/ G! n& K; s) P- o1 P
                //; q7 n8 z* m* g5 d7 i" [
                // (Shouldn't this come *after* the actual call?)
8 C; m, i$ W) O+ O# o* t2 v' B                //
% m+ r7 M/ c* k$ y  P                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             $ e- Z1 p, M) r. n( M& k
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;9 e- ?; }9 f# O2 A6 Z' i
  g$ r1 m+ G6 M1 ~  L
                //
5 b+ t' S2 ^, m7 i: N                // If there's Thread Local Storage (TLS) for this module,
, Q1 g% {2 D1 i# `; d0 Q                // call the TLS init functions.  *** NOTE *** This only6 L1 y; k: t6 D5 L
                // occurs during the first time this code is called (when
" O! D- v2 M# W6 O& a                // implicitly loaded DLLs are initialized).  Dynamically
; a4 U5 _$ I& X$ Y1 E  p9 w' @" d% ^; j                // loaded DLLs shouldn't use TLS declared vars, as per the# q0 Z8 P% g3 F8 ~
                // SDK documentation
% Y" P8 I% |$ X/ Z( f' R$ _$ Q                // 如果模块需要分配TLS,调用TLS初始化函数
' C$ c* B/ k4 V& M: Q+ Q$ p. B  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时; ^4 ~" u* ?2 s/ k4 R; F3 i
  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
6 m. W, v! {; g- ^6 g$ h5 V                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )8 @) T6 c: F6 K& q- \
                {
) i1 U9 B5 T) i0 ^. J/ \                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,* p$ k! K9 v- _, B) m4 |) T% P5 ?
                                                DLL_PROCESS_ATTACH );
, C$ E0 ?! W: W5 S" e- q                }
( m: r% W; a/ N& ^( |9 H- s               
) n! E' [* {) f& `0 d) i3 k5 |& M
3 U, t) R* A( u2 G- _# w                hModDLL = pModuleLoaderInfo->hModDLL& D' ^2 u$ ^2 l9 }! j2 K; {
3 R/ r2 R* P. j5 E% T" |6 ]
                MOV     ESI,ESP // Save off the ESP register into ESI
: ?/ c" _5 e( f( f3 h  ! W+ b" Q4 b2 ~7 O; Q
  // 设置入口函数指针               
5 v  c( D/ R' O: [; F6 e. {& u                MOV     EDI,DWORD PTR [pfnInitRoutine]                     4 |& e9 R5 P: g0 ^: z7 e- V
5 M0 E# v. P8 d# ^: ^
                // In C++ code, the following ASM would look like:
/ \8 W, K6 y1 e' |                //
( k' n6 ?; I4 P% j: j( H) o: g; N6 a                // initRetValue =. q/ W+ {: k6 {- m& \, L2 a
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
, a4 m, [( ~! O  C' x                //
2 J! K( @  m% w9 _
8 X" b! M( K/ [" ]8 ]# Q2 a$ a                PUSH    DWORD PTR [bImplicitLoad]$ L3 y1 L. @0 ~# V; u- v
                7 _& \: M- e$ Y* D! O6 G3 J. W# u+ x
                PUSH    DLL_PROCESS_ATTACH
2 t2 K/ v  ]& }                $ ~/ l# C! I, ^
                PUSH    DWORD PTR [hModDLL]: V5 f6 C0 u7 c. U0 W
                & i* \  j: s. F% ]8 A6 a
                CALL    EDI     // 调用入口函数) ^0 d$ A- x: `+ v) g4 H5 P
               
1 c+ G2 \/ `, V2 t2 x, a                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值
' ~) v3 e% L- P0 I' x- `, e* J' O. K* D* `) f% s4 g
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the* g4 q; _. {* n# d" g" q5 B) A
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
( G* |9 g, [1 J% C9 m  C
% J% Z. Q9 T4 J% R' ^                MOV     ESP,ESI     // Restore ESP to value before the call
8 }5 f. \" o  i: x- |/ l2 u& i% o2 a3 N
                //4 K; h+ ~/ I. O; Q. w0 {- ^
                // 检查调用前后的ESP值是否一至2 r9 \0 d) X1 K0 s& j4 F" ?; r
  //
. j. L8 d6 U/ d9 U: D                if ( _CurSP != _SavSP )
' `7 s+ X, T! {2 N7 w' z8 M                {( D4 K* u1 f+ q% E0 P: W% T
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
0 v% u7 T) _) C" x  v( E/ [; j
% C) P& I3 d9 t  Q- J& \0 t                    hardErrorRetCode = 2 l1 z0 p1 w% C/ n' _
                        _NtRaiseHardError(
1 I; i8 v8 I1 f9 t/ B/ E2 Q+ f1 |( v                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
! ^  p  R: @$ D6 w                            1,  // Number of parameters8 s' V4 R6 y' y0 b- E' M8 m0 l- ]
                            1,  // UnicodeStringParametersMask,+ e' i! U, {7 ~- N# L
                            &hardErrorParam,  ^( ]9 X$ P/ H+ G
                            OptionYesNo,    // Let user decide
8 E8 {$ k9 @7 m+ x) I2 ^$ J0 [+ o5 d& e                            &hardErrorResponse );
9 V" w1 S" G3 u! g, J8 ?                                            6 o9 [+ ?& _0 P' D! @
                    if ( _LdrpInLdrInit ): j0 x* |9 ~% q: X4 r4 |& x& M
                        _LdrpFatalHardErrorCount++;
1 m* m6 w. ~: o. |6 W
- y  L6 `5 g# {0 O, G& W5 B                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
  [% D+ w5 y" ~, U9 k4 O                        &&  (ResponseYes == hardErrorResponse) ). Y4 [6 q7 Y4 _  E- O3 D" f
                    {( h: K! u! X2 G/ J/ G% W
                        return STATUS_DLL_INIT_FAILED;' t7 W* X6 E$ n' W5 }
                    }
" Y& h1 F( b! g/ t1 s6 X+ k                }3 \! c2 s( k% h2 n

. Y; X) y3 u8 D$ u( x' Q6 a* S                //
: L& Q2 Z. F5 a! A( H$ e. o                // 入口函数返回0,错误
. K. B8 f2 H7 n* u! Q# }1 c1 t+ c                //) S, m1 J$ @& u+ X  b3 H
                if ( 0 == initRetValue )
) L" i: B( [& l; V' Z# ~                {3 y; }: m, {0 P0 i
                    DWORD hardErrorParam2;! V8 ]0 g4 g" D% u, ^2 z
                    DWORD hardErrorResponse2;* q0 z) i! N1 |) X
                                        * r* D* f" E; \# h
                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
4 r: s4 k: T" U7 x* |4 n( s                    
* k" Q" H( Y! o5 \: h3 a. R; V                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,9 }1 U/ O7 ?; d. v. d
                                        1,  // Number of parameters
# |; P' s; i8 [# n8 L                                        1,  // UnicodeStringParametersMask
, W; n( M$ h! t8 `                                        &hardErrorParam2,! n3 ~! k: x) S! W
                                        OptionOk,   // OK is only response
* t. R) m- o( z* G                                        &hardErrorResponse2 );4 v3 h! U6 Z' O  g( G- q
                                                            
/ D- D0 j! S2 U9 a' ^" [                    if ( _LdrpInLdrInit )9 }6 X4 l& V) O1 R
                        _LdrpFatalHardErrorCount++;
. h" z! l" R( V8 t3 u. X+ f" v5 V' Y# M! h+ z  o, C7 V
                    return STATUS_DLL_INIT_FAILED;
( i! P, ^% e* A2 o! q                }1 Y3 O' S2 D4 y" g; m& r
            }% ?2 w* t3 N6 K5 W7 q
        }
' `  _; f% }$ L* P1 @+ T* g, ~4 `$ a: Q$ M8 E$ c! j
        //: c& m4 I2 h) h
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时" I) T7 t$ w- I. U/ |: G
        //      
3 V: I* b$ b: F, h2 l  U        if ( _LdrpImageHasTls && bImplicitLoad )
# z& N3 P9 E0 l        {) v8 ]2 u4 W# l7 Z
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
: k$ f6 N" ?* g+ s1 P" a5 ]+ W                                        DLL_PROCESS_ATTACH );
9 t" }, D0 \$ U+ x6 i; ~        }. }/ s& L" [1 z; Q3 ^3 y
    }4 i6 ?6 l: U; G3 H' I) B' D
    __finally+ B# y, x4 s& v
    {2 s) X7 ]9 {5 P) v
        //
7 x% C3 u1 Y7 ]. V! f0 D        // 第四部分;9 U. N6 O; q* @0 M) m& j6 Q! B( s
        // 清除分配的内存, x# |; L) y7 c, y5 J- b
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );5 C2 [: L7 x9 G+ p4 f
    }* T* U3 ^9 n% t7 ~% a
' n7 {* D1 B: k( L6 n% I7 X# }
    return STATUS_SUCCESS;: t- \6 T& R' x9 t" f
}   4 b4 G, j- n$ x

+ `( T9 Q& ~7 J6 i/ {5 k这个函数分为四个主要部分:
, d" @( Y0 l" G" c" y一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
1 W5 B# x/ s2 Q+ h' c# \; _# Z二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。2 I* U# ^% b* X, g* j, w. i
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。* |% T: j5 b% _8 r) h1 X
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
1 G: _% L+ F! b, u1 ^' I在最后的伪代码部分使用汇编语言来进行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& ]) ?* }! P四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-5-13 21:38 , Processed in 0.073765 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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