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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
) Z: v1 m+ ~( `; Z6 z
- L; B% A( k5 g" L! yWINDOWS 2K Dll 加载过程# d; _) c8 |2 Y- K" v- U. y" L
jefong by 2005/03/30/ N* N) B4 b- @( t; o4 H" v' Y7 S
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
  v9 o3 e- ]0 X$ |+ `! J在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”。' }4 ]0 ^) q/ Z% |! S- u, W
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
5 c: B, B. g4 G4 p) {* h初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
$ X  p2 @4 L* V) l# M: _2 T! A  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。0 b* O. M9 @; i6 \* D: n
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
( c* U% ?+ j# q' C( H5 g  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):) c+ l, L- g" d3 ~1 o+ Q
//=============================================================================
1 U& ?* \8 O" V// Matt Pietrek, September 1999 Microsoft Systems Journal+ \) u0 I4 d) k# ]4 _+ x3 S
// 中文注释部分为jefong翻译
! c& G% i3 L' S; N//) T( T6 B3 ^" ^  T5 |- }  q/ O+ C
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
0 @, B( M0 L+ j5 g) q//
! @2 z1 Y/ ]# R% G// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
' L! U, f4 e# [! Z" N3 T& b0 ^3 P+ L//=============================================================================
  I% m+ J* I0 y; ]8 i9 s6 N4 H7 T0 R6 O3 J& {& G
#include <ntexapi.h>    // For HardError defines near the end& P. z! E5 i  ~; {  y

/ |# p$ Y, G& @// Global symbols (name is accurate, and comes from NTDLL.DBG)
$ M6 g1 E4 f# f9 a; ]//  _NtdllBaseTag
* Y# M+ y" g9 p0 Y4 R! O//  _ShowSnaps# Z  t# K& [# A0 E7 Z
//  _SaveSp4 e4 @$ F; {! Q4 Y
//  _CurSp  s8 e! o0 B" l8 f- t( |
//  _LdrpInLdrInit& e( w' d9 _- z; [9 E' H9 j
//  _LdrpFatalHardErrorCount1 q) k  k9 _/ z) q; L+ ], o- W1 z
//  _LdrpImageHasTls: }+ v; `6 h" y. q2 ]6 q4 ^, E; P

; o& B( ]' f% eNTSTATUS  T& v! ?5 y: O% @- ], t1 T2 D
LdrpRunInitializeRoutines( DWORD bImplicitLoad )6 P0 P8 d7 v% s, i# f' Y
{
. i, z7 X9 r* [  d    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了  r/ Q" j9 U2 X2 v; N6 |( l
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();9 a+ \$ E6 H$ g5 e) F5 @# g

3 S# G# w: s# i' d0 U    if ( nRoutinesToRun )
  h1 |1 b) S8 ^; e3 v    {
/ _4 _/ W. p# y7 {+ `  s" o: N        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。5 H1 \& c8 q3 Q2 R# m
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
% J3 ~7 |0 [/ }                                            _NtdllBaseTag + 0x60000,
: [* b- T9 l9 C4 K, X                                            nRoutinesToRun * 4 );
) _" ?1 Q5 H  Y3 ?* D6 B3 E                            : ?2 n4 K: ~/ h" F- N
        if ( 0 == pInitNodeArray )    // Make sure allocation worked
0 p% h" _* D, f4 }/ r  U            return STATUS_NO_MEMORY;
9 g% }2 @) C; {3 G    }, m1 g: L. F$ E+ M. w
    else1 U/ A) n# d* H' {
        pInitNodeArray = 0;- U5 `: n1 {0 t

! ?, j: S/ z; E! \    //第二部分;4 h# v% ?) v9 S
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
5 j4 d) O2 s3 ?5 |    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);& r5 q% T8 N0 G3 g2 Z$ ^
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
1 ^8 a; a* Y! u& f: U        7 i) c6 s( N/ d) t/ M# y* C
    if ( _ShowSnaps )
. G4 w- n( ]! v2 S1 K- K$ ~    {2 Y& x8 Y) Z' }7 v) w0 Z% e( W. B
        _DbgPrint( "LDR: Real INIT LIST\n" );
7 Z8 S' n% P+ v2 H8 i, ~    }* d1 W! R# p. p5 |3 M7 q
" Q+ L) h# a3 x9 N0 ~: k  E+ j  g7 C
    nModulesInitedSoFar = 0;
  S7 M; M, o6 ^  V) z/ C1 l0 y$ D1 O! J! ]7 _: g0 B
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
7 K( N9 a- D: F! U  B  L    {
! r8 m5 |9 d: _8 Y        
2 D: k. l+ M5 q1 K  E! A        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
) E& Y4 F& S0 O3 P        {
  ]9 E1 m" X3 \  X5 p            ModuleLoaderInfo  pModuleLoaderInfo;
1 F" M. y3 z3 m) S" D            
) k0 `2 b( c9 l. P" K$ N            //1 \% o; ]" X" U
            //一个ModuleLoaderInfo结构节点的大小为0X10字节# R; X! j* g: V; o9 |
            pModuleLoaderInfo = &NextNode - 0x10;
: \4 {! \4 x: ~, ^3 B% }+ G            - V' U  g$ @9 x/ k
            localVar3C = pModuleLoaderInfo;         ( ^5 j7 d" }) x/ j' L! R5 ^
# u: W/ ]/ U) C0 k
            //
. U# C- m+ Y; P( @% \" r            // 如果模块已经被初始化,就忽略
5 h8 V; ^6 k: G8 K5 P- s( Y0 A6 O+ L# |            // X_LOADER_SAW_MODULE = 0x40 已被初始化
8 S# c% q5 H, @7 ^            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )& }- T8 n; @/ {. K
            {
+ s. x+ ]) b4 M' b% v; j                //4 @1 W6 e2 A! Q4 J; |% A
                // 模块没有被初始化,判断是否具有入口函数
- I1 v% `& f- g7 ]3 o                //
' V2 v3 C0 ~8 X+ D' t1 N                if ( pModuleLoaderInfo->EntryPoint )7 K7 [4 _* _* T: K
                {5 _8 C; w) }3 u- b& E2 g) [
                    //! |; R( U- g5 L) O: H) e7 y5 K8 f
                    // 具有初始化函数,添加到模块列表中,等待进行初始化
8 N# _9 \2 a: H& C2 B                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
% N$ r% J: J% _+ Y# u) R
& v% l" w2 G# h' t2 w$ c/ E, q* @0 F                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
# _" M4 P! j! J2 p      // 例如:
1 f+ i) N6 J# Y2 K                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000( W6 L- s3 l8 |, O
                    if ( _ShowSnaps )
' n& I- k3 F! R0 k; Q+ o! q2 R# d                    {
. u$ D( J# w. S7 s4 K/ i9 a- l6 V4 ?                        _DbgPrint(  "%wZ init routine %x\n",6 a. z, |# n9 n6 M1 _
                                    &pModuleLoaderInfo->24,
8 Y1 Q, v5 A3 e2 a                                    pModuleLoaderInfo->EntryPoint );
0 _' ^$ W; A7 D+ L$ C) z                    }
0 S. }7 j& F. k9 Z  J1 m  c& e- \: A
$ L7 v* w& {5 G3 ?) M# M4 P  b                    nModulesInitedSoFar++;
9 H  v" k# |, M                }
% k) }/ e4 J7 O9 J) a: E3 n$ q( l            }; d+ `+ n) U- [! s0 L# d1 e6 O6 ]

# U4 b0 _1 G1 K' y: ?, d; L            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。/ j: S3 w% @: R) D0 K
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
7 ?/ |; T- C4 }! _9 x
2 Y! c, ?4 [! m( a* l            // 处理下一个模块节点
7 P3 t: v" E5 B" v1 `) O            pCurrNode = pCurrNode->pNext
7 C- F9 d- }) M5 x9 s        }
7 H$ {; H$ `( q$ ?" b    }
3 Q% G. A) v. R# t) A    else
, F, x% w; A1 W2 w* G4 ]! L    {
3 p  V  C/ b; x5 R        pModuleLoaderInfo = localVar3C;     // May not be initialized???
0 ^3 u+ B7 U5 d- I    }
. p' w5 m' t9 D" \+ ^    9 \' F1 z4 n% P
    if ( 0 == pInitNodeArray )
' X8 g4 N! Z. W5 ]1 H- w5 _7 o        return STATUS_SUCCESS;3 ]3 C- l5 H8 n8 L; p) o2 T$ ?  s

1 ^5 {; ^: B% l) Y    // ************************* MSJ Layout! *****************- e) L* i/ Q) \1 Y4 C
    // If you're going to split this code across pages, this is a great3 F" H  J( v0 v5 q1 M2 ]1 C' y2 s
    // spot to split the code.  Just be sure to remove this comment+ ^2 F* c* U; N7 @; H2 f" K
    // ************************* MSJ Layout! *****************! H- _" G# ^3 Y/ S4 ^/ t% ^
   
& j, s6 v$ ^5 J% E) q    //" b" r% x7 |% w0 }& A3 B
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH! Z) C; w  c: E$ Y9 Z: W7 m+ {
    // 第三部分,调用初始化部分- Q8 }, ]! A( y8 b; h+ E$ N) U* N
    try     // Wrap all this in a try block, in case the init routine faults
2 E' S, T- n% \    {2 a( k- J/ `% M8 r8 X4 Z" Z" P
        nModulesInitedSoFar = 0;  // Start at array element 0
" ~. u2 b' L: G0 o( q3 _/ @  ?! J, }' Z# i% `8 {3 E
        //
( @5 [$ E- J* j8 Q        // 遍历模块队列5 y0 v( a1 j. p! f
        //
5 G  B' b* D8 g4 T/ `        while ( nModulesInitedSoFar < nRoutinesToRun )! Z8 I4 N6 T: k# b6 [$ y
        {
+ ?8 d; j6 o" C8 J, b& K            // 获得模块指针6 Q! W1 N5 u4 J: T1 T
            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
+ T& O5 t# i; f8 z4 [
0 ]9 ^* X- T0 D0 r. h* V            // This doesn't seem to do anything...5 S- g. Y* A# i4 ]* M3 v. ]' a# G9 Z% y- q
            localVar3C = pModuleLoaderInfo;! p  o- L7 k/ E7 \/ o
            
# J6 E+ v  |% O$ O            nModulesInitedSoFar++;
  I4 |/ P4 h1 @/ v+ e; o               
- `. S8 |7 L- l! C2 F" }( K  I3 l            // 保存初始化程序入口指针/ Q2 {. X* |3 T7 @/ R  H. p+ e4 z% d
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;7 l, o% C/ \" r4 H$ A' S
            
$ C+ L- X% U3 U            fBreakOnDllLoad = 0;    // Default is to not break on load
4 |; T; b/ k& |# q1 [1 X! a/ Y, q/ a0 P: f5 N) f! w" |
            // 调试用
' ?4 b1 y2 s& [$ S) n4 z; j            // If this process is a debuggee, check to see if the loader
8 e- Y( X4 m  j. }            // should break into a debugger before calling the initialization." X* p9 ~9 R  F& h" m3 U+ `, C1 X
            //8 g2 c% i: v$ _* |! O# T
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent(); y. {, }6 d& K; E
            // returns. IsDebuggerPresent is an NT only API.
3 f+ B6 n7 ]- y6 E( V( a7 s7 K            //+ f& b, U. r, v! u9 x) \1 z7 m
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
1 B: n  ^7 P* L+ Z            {7 ?# R5 L4 s' M0 e
                LONG retCode;
0 a4 N$ O; ~2 K$ `, C$ C9 j
/ c4 f8 \5 r: X                //              # D4 i8 O4 S5 H5 i
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\1 N! E8 k: ^' A7 r
                // Windows NT\CurrentVersion\Image File Execution Options"6 z  ?! a% O. \9 x, Z
                // registry key.  If a a subkey entry with the name of
$ A' v7 W; n3 e' v                // the executable exists, check for the BreakOnDllLoad value.
8 `) C( j# r! p, [) L% q5 S                //
4 Y3 U2 m! G% G/ n4 A( N                retCode =
: x4 B7 R- l2 S2 b/ a1 r8 _2 X+ y                    _LdrQueryImageFileExecutionOptions(, h8 D1 b2 ]8 X  F! N- d8 T
                                pModuleLoaderInfo->pwszDllName,
6 ]1 F, D% G& q& l  N7 J                                "BreakOnDllLoad",pInitNodeArray
7 U& J0 P' Z- H9 p) `                                REG_DWORD,
6 I- M) S$ g0 L. Q) Y$ I- J                                &fBreakOnDllLoad,
1 A% _7 R- I% K2 q/ T* E9 b* w                                sizeof(DWORD),3 e5 v; B- s' Z. m' m6 S
                                0 );. P- A+ g. j) b
: ^) p  t/ f" }; O3 e% C. g" m
                // If reg value not found (usually the case), then don't( o6 B3 {2 n+ d4 f+ u
                // break on this DLL init
& R* o7 a& O$ N2 U                if ( retCode <= STATUS_SUCCESS )# G6 A! U  Q* d* ~. v1 Y
                    fBreakOnDllLoad = 0;pInitNodeArray
$ C( R- q7 Q3 Q# ?" n3 S* g            }( b9 n6 Q/ K, G2 t2 T4 n
            
. j& v: o7 Y3 R, O* E# }            if ( fBreakOnDllLoad )
2 g" p+ g' Y. z( ~5 U! ^1 j" B            {           
- ~) v8 N8 x8 B: }                if ( _ShowSnaps )
% W# @& H% f) [& ~                {
4 G* K& n! ~7 Z4 q( F+ n. Y                    // Inform the debug output stream of the module name4 C5 F- ^9 M3 ~- d. D
                    // and the init routine address before actually breaking! c$ J; z' z* r, }2 n
                    // into the debugger
( z2 C6 ^2 Y$ c$ F( f7 v
/ q2 Y$ Z* X* Y                    _DbgPrint(  "LDR: %wZ loaded.",/ y0 g! E- T$ I! b
                                &pModuleLoaderInfo->pModuleLoaderInfo );$ Q0 z, u5 C4 e! I; g) Y
                    
. [3 @$ g% S! w9 F6 _0 F                    _DbgPrint(  "- About to call init routine at %lx\n",
- `, K* d+ Z% {2 s                                pfnInitRoutine )
0 Q( p5 {4 P. I( K. l: ?0 `                }6 V, u- x5 J* A; E7 [
               
2 C/ ?) r1 T7 }5 ?                // Break into the debugger                              1 h/ o3 ~9 T, Y( ^4 }1 ^
                _DbgBreakPoint();   // An INT 3, followed by a RET" `( }9 R6 U( J' z
            }
8 l+ A9 m( ]  _/ d* ~" M            else if ( _ShowSnaps && pfnInitRoutine )) H: j: U; b. S  ?8 x' h
            {
4 U6 S6 `5 o- c* G. |8 L                // Inform the debug output stream of the module name
, |! U) E) ~2 A0 M) ^                // and the init routine address before calling it               
6 R& h- Y: ?* s) q4 b                _DbgPrint(  "LDR: %wZ loaded.",
. j# x0 p$ ~4 k; R  U                            pModuleLoaderInfo->pModuleLoaderInfo );. V( n- B8 g1 @9 p9 T- e
( [8 E6 o6 h" R  j
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
  z! o: Q! j9 u7 H" l- L* }3 l            }% d. l0 j4 c# b: \( E& D4 T0 _. e8 {
                    
( G) l7 O( \, D' t& D; _            if ( pfnInitRoutine )
9 _) i: s# l8 C! ?" f            {
7 d3 G/ u6 u9 [4 C; ^! O                // 设置DLL_PROCESS_ATTACH标志
! w9 [: l. x3 m& y' ?6 D                //
. k" W7 R$ K7 J0 u                // (Shouldn't this come *after* the actual call?)
8 m8 [: H8 |, [/ ?$ `, v                //: |. b/ h; F7 V/ x' L" u
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             ( K% o& T7 R4 \5 s: H
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;% H! f' ], z# z1 Z. d5 F

. d" Q3 g& @+ e2 [2 _                //
! x& ~% a8 l$ u( B                // If there's Thread Local Storage (TLS) for this module,$ L/ s6 U/ t( F! @
                // call the TLS init functions.  *** NOTE *** This only
8 x% X  m. e% ]; P                // occurs during the first time this code is called (when
5 t; l! z- @; G3 D& x' s1 s% \                // implicitly loaded DLLs are initialized).  Dynamically
2 I& Z- k( [7 \                // loaded DLLs shouldn't use TLS declared vars, as per the8 S; o6 I( ^1 @+ G5 N
                // SDK documentation0 I- Q. Q) T- H  J% H
                // 如果模块需要分配TLS,调用TLS初始化函数
+ y6 B/ l. ~% N( X. R$ n( p  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时- D2 Y* y# C! o# z; @  j4 D0 z+ M
  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
  E$ K( `9 U2 M" y$ g4 Z                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
/ m$ ~" ]. c! ?  v8 o6 g                {! X* R3 {' ~" X! Q7 n
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,( f( e, j! f! ?5 m
                                                DLL_PROCESS_ATTACH );4 Y9 l" w- I$ G1 m& o
                }! ?* I( Z* ~2 b! p( Z* Q% W% u) Q
               
+ @6 C. |( }: C3 h0 I
9 `8 L( S" ?% t4 T& c; P                hModDLL = pModuleLoaderInfo->hModDLL% }8 J6 l6 B: z

/ {  _" {/ f" W3 _9 f. ^( F                MOV     ESI,ESP // Save off the ESP register into ESI' S3 M) ~9 a- f$ D
  ' L  E! x3 X$ V  T
  // 设置入口函数指针               
* N( I0 Z1 D: e$ q. I- j& D; c                MOV     EDI,DWORD PTR [pfnInitRoutine]                     0 W% Z! ?* S- e

! {6 b& z2 G9 _: S+ t                // In C++ code, the following ASM would look like:! V: t- Q) l, X- O5 h1 v
                //2 p- E% p! y5 C6 S
                // initRetValue =( U3 e) k# S7 }! N- e, o
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);/ N! }& D4 V+ d) ]6 ?' B& Q
                //
1 {4 Z( e8 J+ L) A2 h/ d8 `. a' ]9 L4 v$ C& _, @! x
                PUSH    DWORD PTR [bImplicitLoad]) {0 w) K  L( V* }
               
0 ~" z. n' }7 L6 E% K' t                PUSH    DLL_PROCESS_ATTACH; ~7 `: ?/ N" w( e
                0 C2 }( E% e+ z) U" u& o
                PUSH    DWORD PTR [hModDLL]
, k. u# U  a# P, n* N/ t               
: S! V! S* v2 i$ i3 C8 i                CALL    EDI     // 调用入口函数
5 ]0 s. s1 d, ]                : M, V4 P5 O8 m, z, D
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值
/ Q% h& i6 E2 \% |" ~* h- O) ~0 v- y
$ s0 x' I- d' o0 }$ \  b7 c                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the2 Q; x7 C5 [% W, d
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
3 i: r" a4 E1 g2 u/ y6 U6 y& |
# q1 k) `2 m- X                MOV     ESP,ESI     // Restore ESP to value before the call& ^# T4 b. @: e- D; p9 v
7 P3 J3 u  Y* Y# B2 d
                //
0 N# Q  J# x5 }0 ]$ Z% p4 e                // 检查调用前后的ESP值是否一至
- U$ J: d* b( B: R0 f! T& P; ]  //
5 I( a; d" @4 F- q+ R/ h! y                if ( _CurSP != _SavSP )
8 J2 {* L3 M1 k5 g- I                {* a$ u, x# v! J+ A* y
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
* n7 X1 X+ Q2 J+ c" j# H0 J4 Z- U+ c9 _! E
                    hardErrorRetCode =
+ ~5 {8 {+ H. I; W& `% {                        _NtRaiseHardError(
/ ^: c) M8 \& M5 W+ {                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
7 ^9 s, n, A+ E( `6 a$ J' x# h                            1,  // Number of parameters
9 _9 ^) S- c+ [                            1,  // UnicodeStringParametersMask,
: C( ^+ T. ]5 h                            &hardErrorParam,/ d: G& B2 j( @0 \6 m
                            OptionYesNo,    // Let user decide
, I3 _7 \3 [- V& K- L                            &hardErrorResponse );
$ C& E. }) m( |9 ]                                            8 g% y( R' Y2 m! T
                    if ( _LdrpInLdrInit )
0 B' N" t* b+ z2 J+ S+ V& v                        _LdrpFatalHardErrorCount++;
1 `& J9 {. u, ]8 s* p
- ?9 f; ]- a, W: k                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
3 B& ?5 O. O, M. X                        &&  (ResponseYes == hardErrorResponse) )
3 S# F2 q* E( [- `, S# }1 `                    {& M7 c, C  c  g! W  H! `) n: i2 Y
                        return STATUS_DLL_INIT_FAILED;+ N# I8 j$ T  _  L
                    }3 s" K5 w8 _9 b9 |" o  V
                }9 {/ ^: F0 G& i+ ^7 I4 g

* h. l. n( j% b7 z" a                //
7 R" u: S6 \8 f+ k4 f& W                // 入口函数返回0,错误
, H, I! c( j, r/ |: Z                //
) i! f3 h, M) W0 E                if ( 0 == initRetValue )( e# F  S; a- g. p1 s
                {
2 R. x1 M1 g3 B* E7 _                    DWORD hardErrorParam2;
5 U2 ^* _5 X1 B9 Z! G                    DWORD hardErrorResponse2;
7 y* ^" U! [9 h4 Q# ^                                       
# K2 l# ^$ w, u# |  X$ m+ b                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;. ^7 w, c$ s3 Y  p' l' l+ A) N6 M
                    
/ v. `% }2 ^: e& Z                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
8 V9 a/ J7 j0 H# D; {                                        1,  // Number of parameters
8 t- m" e) A" Y* B                                        1,  // UnicodeStringParametersMask
. Y1 f1 ~5 }7 L0 G                                        &hardErrorParam2," V! J' i' R" z. `9 N
                                        OptionOk,   // OK is only response1 {& Q0 }; w0 Y: w6 I
                                        &hardErrorResponse2 );; D7 P1 i! w, b
                                                            
8 s, L+ X" p" |( ^% p                    if ( _LdrpInLdrInit )+ B: x/ e" T# s0 I  m# q0 W
                        _LdrpFatalHardErrorCount++;
# f1 \- V. d8 I) V9 F* m  M
4 |, y+ T% W$ ]                    return STATUS_DLL_INIT_FAILED;' N! Y4 p% ~. K3 a+ E0 H: P! l& N
                }! I# b3 i  T4 |" \4 b
            }; g) u4 ~" U( W& H% M& y" d4 p
        }
, N; p  q, q9 k8 f
  c2 i2 p$ S8 `5 ~$ W+ @+ h$ z8 Z        //
- S! g; M2 _- Y' }; q8 S        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
% Z9 {6 e& n# |0 C$ }        //      
! B! P4 l& I1 _        if ( _LdrpImageHasTls && bImplicitLoad )
0 `0 F* j7 Q* y/ r# a$ x& Y        {
! ~% u) d# s  n0 p            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,( S; ?+ T2 w4 K# I+ }2 v- j
                                        DLL_PROCESS_ATTACH );
# C3 b! [: ~! y" R        }
& C8 u2 F1 h# H( G4 y: q7 t    }* g" U3 \# g. J7 V. [. u5 e
    __finally
% ]  o; a* P; Q9 a, o0 l; y2 A* U( B    {
) b- s' E7 g6 @- s# U' F5 r$ ?6 R        //
. ]3 J4 B: Q4 H2 E        // 第四部分;7 h% U2 `8 |# D0 I8 F- s
        // 清除分配的内存: H+ ~7 B' j# o9 ^+ T
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );; G' \+ n% L4 {6 T, [
    }
1 C- S) D8 |  b% @2 h. m( z2 y; i. k6 w$ `0 {! P
    return STATUS_SUCCESS;  {1 O& H9 y$ A- e& {! B) u
}   ! d$ A& o) C, z9 H

% j$ Y2 ]6 u% _1 i这个函数分为四个主要部分:
# p$ E( O: J1 U& h* F3 F. Z: U* x一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。- q7 Q; V$ t" l/ L- _1 e+ L+ `, N
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。( S/ J  F% u6 |& ^1 z# _
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
' A& F7 M$ w7 b3 f9 [5 d2 P0 K5 O, v另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
( @. H6 i( ]- \9 _# U* B在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。! }# |& y" [- _8 y$ q' x
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2024-5-9 22:35 , Processed in 0.026178 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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