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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
' u7 m( ]8 A' z: {3 p7 D  P) P- D2 J7 w, _. n. _4 i
WINDOWS 2K Dll 加载过程2 \" @2 ]: a1 s
jefong by 2005/03/30: @& A7 b% I% w" u( X  z$ K/ g
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。- x1 [% J# N  N! }3 }; ~( n$ 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”。) j' |- L& L  t0 s9 u9 v
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
$ ?* K7 w( ~0 l' C初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
* M& W; _1 u' g% ~2 i+ S  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。& M( ~0 [: h6 r' Q: l
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
. c, b# n' b9 L6 R. g% k  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):- f! O2 H. g: j2 L- E6 K- ]" I8 y
//=============================================================================
/ ~. I" {# a$ v' O* t! m* V" I: _$ G// Matt Pietrek, September 1999 Microsoft Systems Journal1 ?8 y5 ~0 |5 l( M* {- |7 F8 i0 R
// 中文注释部分为jefong翻译6 x' |, s- h% k5 {0 R9 h+ C
//
. f  g2 X+ f7 \% B( q1 t3 q+ k// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3). `. S+ P" {" c# _
//. @# ?7 f5 ]/ y/ _; `* K' T. }
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
% n+ {7 n9 @- J6 i7 p8 G//=============================================================================
( x0 C; O4 c+ h: P. B4 D6 I/ y. p) N
#include <ntexapi.h>    // For HardError defines near the end
9 K" z9 I+ E( H. J
' G% J" G% @  P4 M1 x- t// Global symbols (name is accurate, and comes from NTDLL.DBG)
% D" }' L7 t2 h: ~0 f! ?//  _NtdllBaseTag- Q  p# ~, R5 K; x9 v
//  _ShowSnaps
9 |% K. G& L* P1 Y: w  a( S. O//  _SaveSp
- n8 y4 Z" M# l//  _CurSp
+ K  O: }8 \7 f+ N//  _LdrpInLdrInit
7 g0 Y  F' ]. v4 F- u//  _LdrpFatalHardErrorCount
9 U' Q- [3 e# R" C  K8 X//  _LdrpImageHasTls. ]! I) k$ O8 U# L3 J6 q1 U  o

7 u9 e: y' W% Z/ m- w& \* l) SNTSTATUS5 b" K4 E1 |5 }' U$ S' f9 x
LdrpRunInitializeRoutines( DWORD bImplicitLoad )) b! p) ~* I" o! m$ k9 k
{. w% Z% q  Y- ]+ u
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了1 I2 g7 B# D$ y, Y( ]- A$ |% w
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
  M# r. f% M! x
: X. Z& Y5 u" Z1 Q/ F    if ( nRoutinesToRun )0 M, F2 M- B3 K" S+ n
    {3 i+ N. B" X$ A0 ?4 {5 a! a! W/ Q
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
. p9 G1 [3 U7 v/ }2 q        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
( q$ `& b/ S  l, g                                            _NtdllBaseTag + 0x60000,
/ ]2 N) A! U6 ]7 X                                            nRoutinesToRun * 4 );9 t( r* Y# W4 x7 G/ u/ }* {
                           
" L1 {; l/ G2 r( `, i        if ( 0 == pInitNodeArray )    // Make sure allocation worked$ u6 F* j9 v3 K. F3 c
            return STATUS_NO_MEMORY;2 p5 Y2 W7 p0 R' D3 s# B0 [$ ?
    }& h. X8 H8 Y! M; R1 k
    else
  b: Z* r3 a$ U" {6 }        pInitNodeArray = 0;* }* M2 z) s/ m. {: N$ U8 q
/ y( _  Q! w) Y! J
    //第二部分;
3 v7 B' T4 y3 _. T# E: p7 r    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
+ w' e, i7 ~2 S. @8 c4 p    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);) v% }3 O' U" Y5 D0 d
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
  ?4 w7 Q0 ]& L, }7 O# ^1 E% v" }        
/ p6 G# j, Z7 }7 G    if ( _ShowSnaps )5 G7 E4 v# d2 M; S
    {8 p5 g4 T8 I3 w/ L& D
        _DbgPrint( "LDR: Real INIT LIST\n" );
- `! x, I& p, h/ i) s5 K2 b3 I0 F    }
2 ^3 X; x2 e* E8 q+ O' K9 n! N' z3 x2 q0 M9 S2 s& u) Z0 L* j
    nModulesInitedSoFar = 0;
/ n# T# x5 z/ j& s: _9 H. T4 r# }0 y
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
, `$ `* U1 q1 m$ Z1 E- h    {2 l- O! r4 s6 Y4 A
        
) C! J% }( E7 g5 d' E        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块6 Z+ ^) J. N. y2 L5 K
        {
. b# c3 ]0 [; I6 u' K            ModuleLoaderInfo  pModuleLoaderInfo;
6 |, X" K# y- l) u            
' E6 c+ j7 j, K. b+ ]' F$ a* W            //
3 x2 U: p( c) B            //一个ModuleLoaderInfo结构节点的大小为0X10字节
7 D$ ]  v  [' y# n  b" n            pModuleLoaderInfo = &NextNode - 0x10;0 E% H0 b' D( i+ ^- ^3 w
            1 w7 D# i7 }0 s) D$ V
            localVar3C = pModuleLoaderInfo;         ' E- o% {* }# E; N

8 c; w$ p- Z; S4 W8 I. b1 ^# I            //
4 _0 U/ ]- T8 n* [            // 如果模块已经被初始化,就忽略& G+ w% o2 F/ d' d
            // X_LOADER_SAW_MODULE = 0x40 已被初始化
  b0 ~% z5 w# N4 ^$ H0 R/ b            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )- `3 l! V. q+ ~- v
            {$ V9 S* C' [+ o* y# @: f! s7 Y
                //- B& v" R+ d% t& p3 f6 N
                // 模块没有被初始化,判断是否具有入口函数
1 E6 F8 D# C6 g8 y0 m                //
' I  f0 \" g$ h1 q- `1 H                if ( pModuleLoaderInfo->EntryPoint )
4 W: D* l- o0 t' k% n5 @: l! n                {
) N! k  v/ `. t9 M0 k# P! o$ l3 _                    //
% C* x1 p' t7 ~6 i5 Q3 Q                    // 具有初始化函数,添加到模块列表中,等待进行初始化5 N3 A& I2 J; v9 k9 O; m& f8 h7 v
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
  @- d/ D! ^/ N5 b( i, s
5 t) F( a" g( }9 L                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址3 S; D$ Y* J: c
      // 例如:
7 p  x; b0 L. ~1 `                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
+ h$ I9 L. A1 T& P6 o* D  {. ]- l                    if ( _ShowSnaps )
/ n. c% k( o# E+ u9 k  g                    {# s: o8 y( f0 W4 x, Z  q4 D
                        _DbgPrint(  "%wZ init routine %x\n",
! t* J: F0 O, v: S4 l* {2 Z; g                                    &pModuleLoaderInfo->24,1 w0 u; U, A5 L4 u/ _% g8 E
                                    pModuleLoaderInfo->EntryPoint );+ e  q" |8 J* _
                    }1 H  A0 V! a$ r5 {- {

4 _) `+ B4 Z& A8 W. E2 Z                    nModulesInitedSoFar++;& S; T4 `7 ^  T- O# F, `) [
                }
+ e8 C' t  U; ^4 x1 m' Z! p6 I            }/ w+ j% I" j- T/ a
/ U+ c# m8 s# f. {+ f! E% A0 \: A
            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
9 c( x- w7 m+ X: c, ]& n4 ?            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
# x/ n. f; S. G9 |; W( u
& ~; K% A4 }3 G1 p) @            // 处理下一个模块节点
* z+ ~( N* d( p- o" u7 f" m            pCurrNode = pCurrNode->pNext" v& J+ y3 u7 j! n) }
        }' Q, q& c0 H4 V) e" e# \
    }
9 H" E" E! s2 ~; A9 C    else
5 Q0 v3 V7 h" V8 u. |4 x: B- Q    {& l1 S. n( U, K
        pModuleLoaderInfo = localVar3C;     // May not be initialized???
3 h  m9 M# f) `3 j    }( [! o2 l! D$ s, q* n
    1 }7 X# T' K" N0 v9 c6 P
    if ( 0 == pInitNodeArray )2 z* D: Q8 [- i5 x  J' p! l1 |. E
        return STATUS_SUCCESS;
3 _) {5 k+ K# P; V9 N. A1 Z% d6 z' a" X3 ?( R2 p& |
    // ************************* MSJ Layout! *****************! f6 d. r5 s) [/ \
    // If you're going to split this code across pages, this is a great3 x% l" m2 a- ?) C9 X
    // spot to split the code.  Just be sure to remove this comment
, }% y  Y" d5 g    // ************************* MSJ Layout! *****************
0 Q# \3 K8 b. f* ^   
5 u: O$ X( N, X# {, [8 L; |4 E3 O    //
' c2 [) a5 k$ b# P$ c6 T& z0 i    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH2 o# B0 k( G- q/ a/ g
    // 第三部分,调用初始化部分; b1 }/ h4 b( U( H. Y
    try     // Wrap all this in a try block, in case the init routine faults
- i4 o2 M. j7 T8 z    {
9 O, E+ Y7 v# X) k        nModulesInitedSoFar = 0;  // Start at array element 0
6 p7 Y; x9 ]# F# g$ ^+ s, |1 `. p- H) j
        //
% @- ?8 Z, S2 M/ N- K; j        // 遍历模块队列
( C! w9 |1 A% K# {$ g( C1 w, \  ~        //- z, R( M$ m& o
        while ( nModulesInitedSoFar < nRoutinesToRun )
  _' A9 M3 s7 J) x. m9 Y        {& I4 l+ }" Y. ~- h' z6 H: m8 D! D7 Y
            // 获得模块指针
5 h5 _4 S2 A$ S' i8 h  s. v5 m            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];/ r2 X, d, U$ D, A0 j8 c3 ]; j5 J
1 x8 A4 y  d( H4 m$ U
            // This doesn't seem to do anything...# [3 G$ W' r8 L$ G
            localVar3C = pModuleLoaderInfo;
8 i5 {; x6 N' \1 L) D  V& R            ) v) @/ o- _. Z& a/ D/ A) Y
            nModulesInitedSoFar++;
$ C& D& u+ c# B5 u8 t* o; c- R               
. M& p5 _3 {- H            // 保存初始化程序入口指针0 u' L2 D4 k7 d1 t
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
7 t$ m1 w0 x& |1 c            $ i7 K: p1 X- }; I* T# l2 v8 M
            fBreakOnDllLoad = 0;    // Default is to not break on load
  X1 t$ Q8 [& U, U6 e, G) J  g; B% |9 a4 W" b5 ^% }) H  C/ f
            // 调试用
, \4 ?: c+ ]3 D) @            // If this process is a debuggee, check to see if the loader
: E/ L9 u# `6 B            // should break into a debugger before calling the initialization.& ~, p9 C5 x; d+ t# O
            //9 I/ C! e1 e, e5 s/ O- w
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()3 I# |: A0 X1 y3 B, j6 P
            // returns. IsDebuggerPresent is an NT only API.
4 ]/ s' c4 P: h" X4 c            //, O; f1 \+ [) Z; {1 c& m
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
) [0 f7 c) I+ l" i0 y* }7 y9 {            {( F4 W" {; r3 G' ]
                LONG retCode;3 Y0 b" R* J/ h3 j: K/ f' `
/ W7 R, K: J2 g4 a  a& d- p" T* e
                //              5 E2 R# _( N4 M$ C9 k0 e! h: c
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
+ h# C. `- c7 v/ Y7 X6 [- p                // Windows NT\CurrentVersion\Image File Execution Options". K2 r- _, U8 M- z5 s" P5 @
                // registry key.  If a a subkey entry with the name of7 c3 Y6 s& U# z; r
                // the executable exists, check for the BreakOnDllLoad value.% c9 r" U% r( q" A% D, `! w( {, T
                //
+ u% {/ d: [; c' |                retCode =
! j1 |% O! e' [# b' K. M2 v& j1 o; e                    _LdrQueryImageFileExecutionOptions(
; c4 W1 l8 U* w8 a5 y. u                                pModuleLoaderInfo->pwszDllName,
" L7 `; c9 H/ Q                                "BreakOnDllLoad",pInitNodeArray
  L" G. Q/ f/ V; L1 O                                REG_DWORD,, `: n6 d3 X1 A( ]
                                &fBreakOnDllLoad,
( W1 k) A# p6 T) P0 t: g                                sizeof(DWORD),! z+ V+ W1 ?' s/ j
                                0 );3 k+ Y4 B4 z7 S3 Z, X
. J3 F0 ^) d. G. A$ l0 ^; L
                // If reg value not found (usually the case), then don't
# d4 d1 u4 j0 t                // break on this DLL init
* f9 J6 A- d) T% W6 v% y                if ( retCode <= STATUS_SUCCESS )- f# X3 W/ q* S8 I$ _0 o0 k
                    fBreakOnDllLoad = 0;pInitNodeArray
; V! t! W/ Y& B% M2 J# s            }, O0 F: @5 G  d& Q) F
            
( J* U, g: g, s" W, t            if ( fBreakOnDllLoad )
  v1 [  O" W0 M. N; d6 o            {           2 B+ b7 n# g  p0 o
                if ( _ShowSnaps )
: ^* W9 n% ?- i" R9 `5 ?                {7 d3 i8 w+ j# H6 a% U) Y
                    // Inform the debug output stream of the module name$ t' I1 s1 ^4 c3 d1 ]5 z; g9 h
                    // and the init routine address before actually breaking6 R/ s* G6 Z' Q' \! Y' c
                    // into the debugger# w# c) a+ H' M. L( |5 Q- ?+ Z
7 o3 K" B9 m$ j) e
                    _DbgPrint(  "LDR: %wZ loaded.",
8 Z1 N1 B# e. R. @+ F% M7 y5 w2 B                                &pModuleLoaderInfo->pModuleLoaderInfo );) x0 S: V3 n3 d6 X5 e( p
                    
' M2 t# D* D% ]; V                    _DbgPrint(  "- About to call init routine at %lx\n",! i0 p4 T4 ?# e2 I2 x5 s
                                pfnInitRoutine )5 \# w, E; D2 m1 t
                }
) V" K, C" W) `3 _1 Y1 O# u/ H                : w& @7 c* j0 r4 K; ]
                // Break into the debugger                                L. K/ z  p4 C' e
                _DbgBreakPoint();   // An INT 3, followed by a RET
0 a6 B9 \0 l; W1 O" n. K  L) W  D            }
7 {) ~1 ?6 t4 C" D/ @            else if ( _ShowSnaps && pfnInitRoutine )+ I$ n: Q+ L6 H, g. Q. l1 P+ E
            {
5 ]  i  o" u  s) S0 _+ @2 f                // Inform the debug output stream of the module name
8 c- a$ S8 p% ~6 Y4 E- U" c                // and the init routine address before calling it               . h1 Q0 k0 b" Z* h! _1 m
                _DbgPrint(  "LDR: %wZ loaded.",
! Z8 |9 o' ^1 ^4 g) I% B. k                            pModuleLoaderInfo->pModuleLoaderInfo );
3 B" C6 V7 l8 H8 f" z4 V, b  h6 E6 H& ?5 m! O% b
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
' g) {# J7 |2 Z  l  p            }6 C9 a/ O; R1 \2 A4 {9 ^
                    2 k/ W9 i5 ^: H, H
            if ( pfnInitRoutine )* p7 f1 o9 x6 N/ b$ z
            {
; ]$ L5 u6 r" L8 X+ v" }/ r                // 设置DLL_PROCESS_ATTACH标志- ^$ n( O& }% x% _2 u3 b+ B
                //( ?! A# L1 ^' Z  o. ?
                // (Shouldn't this come *after* the actual call?). [7 o2 z0 _1 n. [; m
                //
7 M8 r3 C. R6 ^" w" g# H$ p                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             6 ~  ~( U2 o! L; e- s  E# S
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
# W& }- h% b7 v2 T$ e' ^/ x: V$ `% }1 `, F  |1 o
                //
, v% x% r+ S6 }( Q5 G: ]8 w3 c                // If there's Thread Local Storage (TLS) for this module,& @% D2 l! E: a+ W* S* f% W
                // call the TLS init functions.  *** NOTE *** This only1 D) t8 n% _0 \" v2 z
                // occurs during the first time this code is called (when
5 |6 k7 T7 z: S$ P6 m                // implicitly loaded DLLs are initialized).  Dynamically" o/ _& b2 b- n* Q+ I$ n' [
                // loaded DLLs shouldn't use TLS declared vars, as per the
  K, h/ y3 i9 B8 b, @$ s                // SDK documentation& B4 ^# T' b- s  t- E" p
                // 如果模块需要分配TLS,调用TLS初始化函数$ w6 F2 l( a# V2 S, u* r. }5 ^2 l
  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时9 B( W6 i! M5 I7 h; O( Z
  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量+ u% Y0 r; ]6 `5 t. F: k
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
% Q  p7 t: i. O4 ?: K7 J  U/ j. I( K                {/ J$ \( v! q2 k7 k2 U5 T7 \
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
1 K( \" H' w: O0 @                                                DLL_PROCESS_ATTACH );. G3 m6 U( X( U6 r+ a; q
                }
' L2 t: }/ N* t3 X) y2 }               
' }1 }9 N0 U- N, ^
+ z- p8 |; v" I% {8 {+ U                hModDLL = pModuleLoaderInfo->hModDLL
. N- I* F- k) \) T/ r1 D% w7 D5 g
- \+ E9 g9 c& V; I                MOV     ESI,ESP // Save off the ESP register into ESI0 _0 F# z2 `( h
  
) m; J, m* D" }% w  // 设置入口函数指针                4 ~+ |" z5 \( n6 I& F8 }* [7 [3 ^% j! C
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
- a1 x. r- U9 Q  O* I( x) P& E* |. ]2 F5 s$ t2 Q  A: m$ T
                // In C++ code, the following ASM would look like:# _# m# A3 M) A. X" d
                //
9 ?9 R0 p9 p& e                // initRetValue =+ o& @4 P/ `4 Q5 U( U2 N
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
! a  Z, d# s. U$ O' T0 \                //
0 n+ K/ d+ F* C3 W) X9 z7 V1 P6 Q3 Q
& z: h- v2 _" H                PUSH    DWORD PTR [bImplicitLoad]
" I- O1 E' E+ Z9 N0 E% V3 ?               
! s7 R; t* V8 b6 y/ ]! z                PUSH    DLL_PROCESS_ATTACH
4 L6 K0 S9 q* m. c; ]                1 L( H0 D4 V  L4 U0 @" g" F+ h9 h
                PUSH    DWORD PTR [hModDLL]
! [9 N9 M, g2 B! {                  ?3 }2 z+ s$ Y
                CALL    EDI     // 调用入口函数
2 V8 o& ~# o. e7 L7 H$ G                * o) {4 J* g! `+ ~7 s
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值: X! s1 v4 Q4 h% @0 I, l, B

# H) j- |0 U0 i% e' Z( c                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the+ _6 B6 l' o% M& B0 @; T. ]
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns# n, }+ i. o. L- y, G

8 O! m7 @0 u5 `9 h6 `                MOV     ESP,ESI     // Restore ESP to value before the call
3 _; t& Q( q- ~7 `0 x& `) F" E; H& j; ~( s1 _
                //
- R/ d7 C: M" r3 W6 X0 D5 T9 T  H                // 检查调用前后的ESP值是否一至
% E' f) M  w& H7 ]* H/ B  //
+ A1 ~; ^; b. P3 D                if ( _CurSP != _SavSP )
; T, I- Q0 A  u5 N* \                {4 n$ P2 t9 V0 E' R5 m( |. |" g. @
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
" j$ m6 W- F9 I" P* B; ]  a% l! B6 u$ w% \* a  V
                    hardErrorRetCode = 3 e" H) w# t% n
                        _NtRaiseHardError(
9 l! ~, O( i- g8 o7 \; V- c6 v                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
- Q  ?7 Q, r# R7 Y" I4 q/ p# p                            1,  // Number of parameters1 D6 F+ s) f6 V+ g# T
                            1,  // UnicodeStringParametersMask,
$ F/ h8 W, ]+ g4 b( v# z                            &hardErrorParam,
) H8 W5 w2 z( E                            OptionYesNo,    // Let user decide4 H4 ~: ~. E) u, W7 n2 E  }7 R
                            &hardErrorResponse );0 a! }4 [0 o* h6 t7 x% N
                                            : f( q9 X" v5 h/ h6 @5 [/ G3 K5 Y
                    if ( _LdrpInLdrInit )+ @( G( l, S# K+ E) S) H5 u
                        _LdrpFatalHardErrorCount++;* k9 m. i' K, L( ~2 d' K! i2 t

8 p% B, E- r4 a( K3 u  E0 A9 N# [                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
& y# H" h& |* ?, _6 a                        &&  (ResponseYes == hardErrorResponse) )
% d( }3 P9 k) a) J6 R                    {1 j- a7 Y' Y/ W: S+ m
                        return STATUS_DLL_INIT_FAILED;
: O9 U6 P' _, T. ?# z- I                    }& E! q3 O/ Q  R# ]  V
                }
) G' q+ \' _3 o7 _$ W! c! g; n- Y$ o- T6 t3 k
                //9 P$ b$ ~0 S, }$ Z
                // 入口函数返回0,错误3 `7 l- n" H7 S
                //
0 ~1 O. Z$ {' J5 y# P2 q3 B: Z9 M# Z                if ( 0 == initRetValue )
$ [& k1 [3 g% Y                {
, J  c4 }2 H* S2 }( V5 H  F+ c5 P                    DWORD hardErrorParam2;3 ]6 @5 T9 {1 A
                    DWORD hardErrorResponse2;
) O( V' U. e! z/ \' B1 D5 Y                                       
0 d& o7 W4 Y9 H                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
$ R/ n& }: o% \3 H7 o6 w- r: ]                    
2 B- C) o, K9 w/ m- y5 \% B                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
6 q1 ]9 [* l8 k                                        1,  // Number of parameters
2 T: X' G! H; U; D                                        1,  // UnicodeStringParametersMask
) q3 [" c2 u& b: w4 a                                        &hardErrorParam2,
2 |8 `/ u, d  {5 B0 h4 P3 v4 v  }$ }                                        OptionOk,   // OK is only response+ h- C9 m8 |9 _6 f" W- ]
                                        &hardErrorResponse2 );" {2 T8 P9 ^& v( M9 o
                                                            3 I$ B- ^- ^  F3 k& Z
                    if ( _LdrpInLdrInit )
0 x$ r5 l- X7 w+ q3 @                        _LdrpFatalHardErrorCount++;
+ V1 q5 P7 j" a
1 P: R* F8 s* ]2 F: B; K                    return STATUS_DLL_INIT_FAILED;1 o, v9 k  Q2 ]% t
                }1 R. F6 V; Y. y6 _2 Z) F) \( X
            }
' y: b2 b; H" E3 h8 b! H# n: k        }
7 p- W* m, A9 R+ v( A
! f: i$ ]6 ~) B0 ^        //
3 _1 e. x& ?* a; s4 D        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时1 R' b: d9 T' Z1 E1 U
        //      7 Y( Y! X( }6 C% n& `6 \
        if ( _LdrpImageHasTls && bImplicitLoad )
6 A! U9 `& x0 ?& i! e        {: m4 E7 Z7 E4 a% t* s/ k
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,# N, t& N- B2 ~/ f/ U! V! x
                                        DLL_PROCESS_ATTACH );" R& {: L! ^% T) g/ E0 e
        }& ^6 g0 B: V, n1 F
    }2 i# m5 P- _; _
    __finally. J8 S9 r- {* S$ b0 d
    {
1 i0 P, `9 _2 M  I2 H1 n: o$ x# h        //. i$ j, \% n; j. |
        // 第四部分;
% [; i8 w% ^$ o+ p! W        // 清除分配的内存9 T5 A# a- b& A
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
5 r0 E, Z2 A( H+ w0 ]    }
" N" E* I5 `) n$ x/ ~
" m6 ~1 `1 `( c+ K    return STATUS_SUCCESS;
2 ^$ x& n% q! t( f: h}   
( N0 p+ m) Y4 M# T3 M! X: q. c0 w$ n* o# y' u, u/ j
这个函数分为四个主要部分:
7 [( v1 I  e; s# p. C7 g% Z一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
9 e* i4 ?* s* a二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。$ p- _' M$ g8 N# u& g
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
" ~. e. n. k! O+ a. z$ I另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
! u& M( a$ |7 I' 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。
3 W5 V7 q, i: p' f1 C9 h! b四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2025-5-22 03:34 , Processed in 0.176738 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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