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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]% X8 Z9 v# M( `2 Q

8 g4 R- P" }/ C2 RWINDOWS 2K Dll 加载过程
! ^* U; X) ?1 x4 |6 q) rjefong by 2005/03/30
2 E7 Z# b  p  J/ Q4 X这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
2 ]& R1 x0 a( ^3 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”。
8 y8 w& @0 z6 B: j# }你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。  _0 t) _8 ]2 L$ [- l9 D% |
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。  D6 w; g7 X. V2 R8 C  \, O6 P
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。1 ]5 M: S& c( a. ~$ S( e1 H& E
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。5 O2 I7 e) [# j  K
  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):! L! r4 x. q% g, r
//=============================================================================5 m% E8 S2 y0 {% p$ t+ C7 |# y$ v
// Matt Pietrek, September 1999 Microsoft Systems Journal
8 C! g, H1 ]6 E) M// 中文注释部分为jefong翻译
6 n( }1 K, Q* f2 i2 r7 a) {//( z2 j4 F2 e0 l( v3 Z9 P
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
. G- N* O3 L; o/ o//' C4 U! n4 V3 q, n& H6 T' X/ k
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;+ u0 t. n8 J) |9 H
//=============================================================================
( }% i9 V: q8 m( i' t' _) y3 B
: _' p6 @5 \8 f#include <ntexapi.h>    // For HardError defines near the end- W3 i/ Y0 g* l8 h$ t; r7 G6 A
+ ~% |- J6 N6 Q& P3 ~" c: v
// Global symbols (name is accurate, and comes from NTDLL.DBG)* |& q/ D2 y1 G3 }3 E& S7 y
//  _NtdllBaseTag
3 K  Q/ b/ f$ D% n9 H/ l' c//  _ShowSnaps$ v% [( x) |$ w5 u
//  _SaveSp) y/ |2 M& N: i; t. k; h) W# K
//  _CurSp
2 \; k4 O/ J, e) s//  _LdrpInLdrInit
9 h; |8 ?, u$ C//  _LdrpFatalHardErrorCount9 p- o$ E. x1 r+ q
//  _LdrpImageHasTls
9 ~  u5 b% z) d1 Q2 g& {3 k6 m2 |) M# q
NTSTATUS) x; S$ C" z+ g! S
LdrpRunInitializeRoutines( DWORD bImplicitLoad ); ~, F% W: r" d4 l" Y9 f
{9 u( V: i* `; P" ~. k
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
& K5 ]2 J  Z8 e$ x7 \5 o    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
4 z- F- H0 g3 t. S$ I" Y2 E, O0 ~3 b% k2 j/ q
    if ( nRoutinesToRun )' c3 [: A' D4 M( s
    {! _6 G) j6 Y, {  ?1 W6 E7 z! m+ G
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。* t6 R6 h( N+ u1 Y
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),& |% }+ ^0 y0 }/ d9 A$ c; c- s
                                            _NtdllBaseTag + 0x60000,3 e$ m3 H/ A' f/ c
                                            nRoutinesToRun * 4 );
2 W( v: ~# O/ n, j                           
1 P, g2 K# Q. R/ u6 z        if ( 0 == pInitNodeArray )    // Make sure allocation worked- a5 f# Y0 s: g
            return STATUS_NO_MEMORY;
$ L/ T, @  ]+ j9 e' \9 \! ~1 k    }
0 _! ]) e1 M$ q. s$ K    else
) j/ m" s; D0 a* ?9 j% z        pInitNodeArray = 0;7 |* |: a$ A3 B; c  h8 G. d2 o
5 `/ b8 u. D3 c$ Y
    //第二部分;
6 E: P! q8 D3 U/ k    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
. v+ v$ D- l- j2 `. y9 s    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);# M, I8 d6 |/ ]+ b; P) l
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
9 \$ O' ^3 c3 q+ B8 p! D$ I  o5 x        ! |& p& u: Y' f  E
    if ( _ShowSnaps ); @: l( ~: Y9 z5 y1 F) g
    {' M- p( E- j( F- T: s2 N( E
        _DbgPrint( "LDR: Real INIT LIST\n" );' S4 {0 i" J5 q* b
    }
3 S9 L" R" k, [/ O: {
9 S: J: [; _- m. ]/ p( l, W; Q    nModulesInitedSoFar = 0;9 K# q+ D+ A: ?6 _, D
# l* n+ Q' Y" I  w' x1 ?8 ^: @
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
/ Q+ L' M+ ~! I0 R) Y    {7 e0 w9 V% z! I% J
        * H$ x) C+ y' A4 b* U; ^
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
" l4 `1 \% n( D, w        {
! @( W& k/ `, Z- l3 y% {9 B5 v            ModuleLoaderInfo  pModuleLoaderInfo;
  }" a5 ^& ~0 r; y            
2 |% A: v' |/ \! P2 G0 D' A" s            //
0 p: n3 n9 W6 i  j            //一个ModuleLoaderInfo结构节点的大小为0X10字节$ K+ h3 U0 G/ }8 n
            pModuleLoaderInfo = &NextNode - 0x10;$ w2 C2 M" X, c9 d% r
            
; Y" B& X/ R( \/ @2 G# W! E            localVar3C = pModuleLoaderInfo;         ; b1 a: I% d' ]( ~

/ G  P4 S$ \2 n5 L/ I            //6 f7 A- @3 Z4 f2 G; |9 ]
            // 如果模块已经被初始化,就忽略
9 `1 \1 P$ I) X% n4 M! a6 x, n  Y            // X_LOADER_SAW_MODULE = 0x40 已被初始化& H4 K# {8 D: M& I4 Q  s
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
* k  H! h, F6 O2 G5 Y, T2 Z            {
$ Y' X; T0 R& Q' U& Z0 M/ {# P                //
, z2 r) f; ^- u                // 模块没有被初始化,判断是否具有入口函数' t" u/ U0 c" w% O. h+ r5 {
                //
4 F# e7 `# l0 u, k" ^# N8 p                if ( pModuleLoaderInfo->EntryPoint )  f2 j0 {% r# c) Q
                {+ b2 Y; _0 l0 O9 a- b
                    //
3 H0 d$ v' s0 U- h+ P" \                    // 具有初始化函数,添加到模块列表中,等待进行初始化1 M" ]' Q) x% U
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;8 s+ o& f) k9 t  N' r% y
) }9 k( k3 r" H5 Y
                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
' \! r% V" X! P! V, Q      // 例如:. j& R) {: G- }
                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000( G! L! \0 {$ C% O/ A
                    if ( _ShowSnaps )
, ?  q  {) ~/ b: _. ^5 z3 B                    {
. P) y2 g; q: k7 m! Q8 P, g                        _DbgPrint(  "%wZ init routine %x\n",$ K$ g, F: \* P/ M: r: D; i5 y
                                    &pModuleLoaderInfo->24,5 N; x. r9 w, r. v3 a6 T- q% |
                                    pModuleLoaderInfo->EntryPoint );# T9 l. j8 k) d9 S, j2 |
                    }+ `) @- \4 w  e2 f

7 ]6 i# z" c  X# S( E" s                    nModulesInitedSoFar++;
4 G! h* u. c, x: `# d! T5 \+ H, _                }
9 Y6 s9 i% ~; W1 O) T) k            }* Z5 y* X9 o1 @5 H, ~! |6 m$ z
# e& E6 m5 g( K9 l$ W4 T
            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
+ g& I3 o; C0 [+ n# @            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;/ T! ~6 s7 m8 |& N
. z4 M. v  _8 c. Q( C
            // 处理下一个模块节点
( B, u1 C3 [; i* n/ z            pCurrNode = pCurrNode->pNext$ F8 Z1 b% c) i# s$ _3 N8 W) C
        }
) M( [2 P1 @( t! O/ I& N    }* t3 ~+ }- ^; m3 s$ C: {
    else
2 y) C* }/ q- `1 [2 {    {
7 f' N3 ?* ?: e- T( g+ t        pModuleLoaderInfo = localVar3C;     // May not be initialized???
, O3 k. O3 G) p  \& }; }/ K" V    }% C3 m# `. u6 a
   
! p3 L) M% J) R* a3 ~: i    if ( 0 == pInitNodeArray )
2 B7 b/ l2 L4 r( [0 @2 R7 L        return STATUS_SUCCESS;
$ ^* l  {# q- ~
" {4 f- f& V  ^' }( P    // ************************* MSJ Layout! *****************
/ z' F" Q% {3 z# [- t    // If you're going to split this code across pages, this is a great. Z* s* U% d$ Q% \6 L
    // spot to split the code.  Just be sure to remove this comment% \& {: M9 Q# `; |6 X
    // ************************* MSJ Layout! *****************: K) Z6 B6 U. L. n# p
   
9 `/ g* O8 c- I& ^    //6 e3 J" x, A$ T+ y6 W8 n( W' h
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
& u0 I8 Y, j, t! r    // 第三部分,调用初始化部分6 a/ J3 `. v) w* ~$ ]/ c8 M
    try     // Wrap all this in a try block, in case the init routine faults6 y" v- B8 o1 z# }2 p( D: I. L$ P* s
    {2 T! k2 E/ o, E. d
        nModulesInitedSoFar = 0;  // Start at array element 0
; g5 X6 B' t6 L! u, }
! C% R3 c5 e2 A# U        //
: E+ t6 u9 H$ ?4 E5 @  p7 s0 f        // 遍历模块队列# k, d: `; O+ r
        //
2 d( T; a7 M! C% `, }        while ( nModulesInitedSoFar < nRoutinesToRun )
6 \: W- q" N/ w2 c; e; Z+ H$ }  J        {
: H2 J% n+ x; O+ m            // 获得模块指针
5 x2 i" l/ E$ C5 u1 W  ?            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];. m7 s) p. @! n% ?2 r

+ {& T8 K4 k4 O' q& b( u            // This doesn't seem to do anything...+ r" b: s6 k+ c
            localVar3C = pModuleLoaderInfo;
+ s, r" A( T4 e- N! F( l0 T8 Z            
  k) s) }' N: K5 }" C) e            nModulesInitedSoFar++;5 \  W! u4 A. R/ O; y3 w, U
               
7 h4 q+ f+ z2 g; S4 ^# n1 D  b            // 保存初始化程序入口指针8 ^) W# t! ?) r+ j9 p9 F# M
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;6 l+ t+ g8 W! H/ U& G" [& }% d
            / }& Z# N) ^, v8 P% z; G9 r1 V! z! a
            fBreakOnDllLoad = 0;    // Default is to not break on load( Z5 ]: C# m% y" W

" _# F; N* S6 J8 `! ?            // 调试用/ ?% Y* t7 D0 [0 k. e
            // If this process is a debuggee, check to see if the loader
. e; q0 A% V! i6 C            // should break into a debugger before calling the initialization./ K5 O! `$ W8 ^' I7 `  c
            //
3 ]. R+ C( W: w* l/ U( H! L0 i            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()% ^1 v, [/ \8 j
            // returns. IsDebuggerPresent is an NT only API.0 ~; r% ]0 G9 Q% x. v
            //2 ^& b8 {% X+ y$ d5 _
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )- D2 D$ g" t# |" Z
            {
& e1 C8 l! h' L0 F* s* L! C3 a' j' p                LONG retCode;5 |$ B! w! J/ y. Y3 B2 M* _9 ~  f7 t
# Z% d) j- M* T5 O& @
                //              
& Z, U5 L/ s5 M$ O                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\- l2 O0 m% D: V: t, _9 k* k7 ^
                // Windows NT\CurrentVersion\Image File Execution Options"0 b8 }! J- h3 i, `
                // registry key.  If a a subkey entry with the name of
9 u  p, K! U- f                // the executable exists, check for the BreakOnDllLoad value.$ O! X9 H$ |+ T  {% o8 z
                //
3 Q" v" t0 V# s) c2 M; k4 A: ?: z+ t                retCode = ( |" }+ ?1 F7 n. d  D; U9 d
                    _LdrQueryImageFileExecutionOptions(
/ X6 ~1 P- M- p. p                                pModuleLoaderInfo->pwszDllName,
+ E) i2 k2 L' _  U                                "BreakOnDllLoad",pInitNodeArray$ m4 w; q. e- F: }+ H
                                REG_DWORD,
4 Y6 t- _/ ^. O4 E# E                                &fBreakOnDllLoad,! s. T7 {) Z) \, J! C# k5 M
                                sizeof(DWORD),
# P! E" g: h2 U1 g3 l                                0 );
3 q6 Z8 k; `. ^+ g% Q! ?; f
( ?$ E: m1 g0 _                // If reg value not found (usually the case), then don't! s  f7 k. D) Z
                // break on this DLL init
! K& c0 Q$ Q2 l9 k- c( O                if ( retCode <= STATUS_SUCCESS )" \& z. \8 m$ S% b, j* L; K2 B5 T
                    fBreakOnDllLoad = 0;pInitNodeArray
4 H! Z6 ]$ j/ e$ T            }% M5 L8 F& g0 U! g
            - k. c% E! l& W2 N# Q/ X  X
            if ( fBreakOnDllLoad )
! f: }5 F9 _' [' `# b6 r) Y7 d            {           2 V$ S* ^! ~! Q; p& l/ g
                if ( _ShowSnaps )$ @' B4 f( R  @( t
                {0 l  C" k% A0 x7 \0 d
                    // Inform the debug output stream of the module name1 R0 j* ]* b1 K, `7 b1 H" N
                    // and the init routine address before actually breaking
1 j1 _" |. s4 d4 @1 h* l                    // into the debugger' r' b2 @! C0 o7 d# \

+ K. [( D! W4 f" |                    _DbgPrint(  "LDR: %wZ loaded.",% J) C3 z. R. P* W6 a
                                &pModuleLoaderInfo->pModuleLoaderInfo );7 T9 k/ Q' K5 o2 K& S
                    
! i+ c9 g" P) H& j                    _DbgPrint(  "- About to call init routine at %lx\n",
4 [* r/ p# s  A6 [! L                                pfnInitRoutine )
/ v9 n3 l. {; ^6 W) x                }0 \5 m6 Q8 \9 T% g6 j$ h
                $ C; o8 `  d8 A
                // Break into the debugger                              " |! G7 j& C: W& \  E9 R
                _DbgBreakPoint();   // An INT 3, followed by a RET# H. l9 p! X- h: S& X; ~$ ~
            }! ?" @5 c) l% l
            else if ( _ShowSnaps && pfnInitRoutine )9 N2 G, r9 X1 k
            {* S9 p/ h. w3 x7 f
                // Inform the debug output stream of the module name
3 `0 N" F/ @' t) z& t" ?                // and the init routine address before calling it               
: I' L% N- I; G. }! D                _DbgPrint(  "LDR: %wZ loaded.",2 Y0 E! F# p$ B# i3 i: {
                            pModuleLoaderInfo->pModuleLoaderInfo );& z: Y1 x( ~$ g
* p- X2 ?/ I1 {
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
/ o" J7 v0 h  m& u# j& R) y7 Z            }
* Y6 z; N& k9 J- e                    ! ]2 w8 P  m* k/ n  m$ c1 s" X
            if ( pfnInitRoutine )
  x' b; ?: k; K4 T. X            {
0 L. P9 w/ _  {% m) \4 }& Y2 g                // 设置DLL_PROCESS_ATTACH标志
0 k- [) p  B2 ^  _6 Z( K$ [3 }7 R                //
# W( C% W$ I& W  p8 [- B4 Z                // (Shouldn't this come *after* the actual call?)
2 W2 E0 g" ~4 m: Q                //
, n) S: Y% N- u* }8 `5 G                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             + w7 v0 x3 X  v# b; {; b; I
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;/ n3 y3 X  E9 O) n2 y

% k  g7 [" W( ~; z& N: P. ]                //' r; ]8 v' J) B
                // If there's Thread Local Storage (TLS) for this module,
1 n) _* f/ \; {$ x* e$ H- o& H, a' i                // call the TLS init functions.  *** NOTE *** This only
) G! c: Q# K7 R  Q                // occurs during the first time this code is called (when
9 {, ]  {; o2 ?; m  i  Q: |! p4 V( M                // implicitly loaded DLLs are initialized).  Dynamically. t) T% w% _4 y# @7 O7 m$ r/ ~
                // loaded DLLs shouldn't use TLS declared vars, as per the+ @- w* ~# c# r5 T7 F" O0 b, a5 {
                // SDK documentation
9 f, F  a4 M# ~# F# ?5 X                // 如果模块需要分配TLS,调用TLS初始化函数& Y2 {! D; N1 @/ ?' A
  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时6 ^7 h9 i, ?" M& X2 a  \) H
  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
" Z* `9 F3 O. z( u: ^; L: K                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )9 w, c* _9 Z0 c) w! I' ]3 ?) ?4 k
                {
2 h1 }1 t$ C$ n                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,& A) u4 s2 B1 u1 Y
                                                DLL_PROCESS_ATTACH );8 H4 T) ]+ g3 a5 W. {, k
                }, x. U. E7 p- {7 p3 \0 f  b
                1 x  E. O6 V5 W  G& Z

+ r5 E' U$ R, W. {                hModDLL = pModuleLoaderInfo->hModDLL
" V4 g1 |: }5 e4 m7 E8 h  M+ B4 E( f3 j- f
                MOV     ESI,ESP // Save off the ESP register into ESI
- D: z' p9 t* k" L, L  
" r; A5 ~" `/ _* d! S  // 设置入口函数指针               
8 w6 Z7 @+ r0 Q7 E5 `7 C                MOV     EDI,DWORD PTR [pfnInitRoutine]                     , P; `0 O5 h* u2 [) p$ ~8 z; I

- f( O. X' l# {8 d                // In C++ code, the following ASM would look like:
4 T) s2 ~$ C" `                //
& e! J$ L; z% L7 V4 g                // initRetValue =
3 e/ L4 G5 d( ?- |0 l                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
, A, ?( C" X' d% M* J, Y" Z( P+ a$ k% Q                //5 Y; o0 d0 {6 G; ]) @- G
3 k* u8 M% R" d
                PUSH    DWORD PTR [bImplicitLoad]
2 ?! q1 f& g$ g- v% s               
, y% G, `% u+ n6 n& e0 u" a! W+ Y                PUSH    DLL_PROCESS_ATTACH6 Q  x& q7 N% m
                : ]8 B: I& x6 W6 M, y
                PUSH    DWORD PTR [hModDLL]
; h( P9 J% z  H$ ^. V% @               
' c9 P( C; G" [6 X$ P: b+ t                CALL    EDI     // 调用入口函数
% G$ s6 K* y' Y' v+ {% Y- H                1 N6 `. W: x. d: T0 f* T
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值4 n4 ~# j  o1 c8 B2 n
/ d8 V8 g2 M- ]8 r% K1 M
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the. h7 O1 H; c  m/ h$ U: U
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
) I3 Y6 [* L" ^& h7 Z" d' |, U! d) c+ a
                MOV     ESP,ESI     // Restore ESP to value before the call
, `! E! ~2 X! l
; P5 Y( g( f- G% K/ _+ g( }                //( l- a6 Y' r6 }% m1 I
                // 检查调用前后的ESP值是否一至  a1 r$ a6 I& v2 s
  // ' S7 U7 s9 i. u5 d- K# D! }8 y
                if ( _CurSP != _SavSP )3 c4 G4 `7 D$ D# |/ c5 Q& R
                {( I  Z4 ]( M  ?- P8 ^( h
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;( I: w; C, n7 J9 I

' x: x$ l; E. ^  u                    hardErrorRetCode = ! U6 M5 R9 O& v5 f' `: F9 Z* V
                        _NtRaiseHardError(
' p) p, v1 [6 x& `% i" {                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,* o* n. O: C( ~9 c8 k8 W" o
                            1,  // Number of parameters/ z) r2 S$ j8 G. Y( j, e
                            1,  // UnicodeStringParametersMask,# u( s# _* Z8 g' b) B( Z
                            &hardErrorParam,. I1 A. n! S; i3 l
                            OptionYesNo,    // Let user decide
$ b/ I5 m* ^6 b) U& p* B& a                            &hardErrorResponse );
1 z& M2 H5 j7 o" b/ w" D# o  @                                            $ c8 ?3 s: O+ M" v
                    if ( _LdrpInLdrInit )
, \& e) ~8 R0 a                        _LdrpFatalHardErrorCount++;
' I0 e4 Z! z+ k" z" V& I7 o+ G: o
1 E& f! P) H) @5 s# S$ y5 |                    if (    (hardErrorRetCode >= STATUS_SUCCESS)& w8 o: ~/ A% w1 ], U/ Z
                        &&  (ResponseYes == hardErrorResponse) )4 k, s$ Z+ U- G
                    {9 {9 N6 H" b: u: F$ B
                        return STATUS_DLL_INIT_FAILED;
+ [# t$ k# x/ X/ g                    }8 G: ^' S# v% M' s4 G# t5 h
                }
/ y% U3 ~8 Q! c( ^2 A. G) N+ }) V9 F! h3 V3 P
                //- q# ^7 @( F+ O/ }* c' S1 |
                // 入口函数返回0,错误
5 j( J; j2 u+ d( P3 \                //2 `% ]& ]' Z5 E; h; i) ]2 j
                if ( 0 == initRetValue )
& K2 g& D  g# m7 y( H. a                {
" f/ F: h% h% ]: ]& m                    DWORD hardErrorParam2;
1 g7 L5 Z; A0 P8 i& ^; I( s                    DWORD hardErrorResponse2;% ?- c, p3 E5 n6 F! c* i( M8 C9 W
                                       
" A/ q  k3 u6 W9 c1 p                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;* p0 l8 s* ^) z/ g
                    
% _& ?2 B# t+ D                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
, a9 p0 a/ N" D                                        1,  // Number of parameters
, k/ r4 [7 R" ^3 g  i8 r                                        1,  // UnicodeStringParametersMask
1 r) ~- ]5 K' B$ i% Q* K. e9 [" X                                        &hardErrorParam2,/ V; T3 w( ?% _. d. ?
                                        OptionOk,   // OK is only response
- O1 ^5 }! @# ^5 I                                        &hardErrorResponse2 );* m; G. G$ f: [$ F1 \7 Q  N
                                                            7 `& b4 |/ l9 i0 C8 {3 F. V
                    if ( _LdrpInLdrInit )* ]6 |4 w: A; h
                        _LdrpFatalHardErrorCount++;
6 S/ W: K% E- p8 x7 _2 X6 m% d+ Q( o0 c+ B* i2 r3 q/ m' C. C
                    return STATUS_DLL_INIT_FAILED;
6 R  |9 J' z9 K/ [( D0 v. X                }
0 |4 g! M9 K0 X4 K! Y- E            }. W" b; E1 k/ A  b+ f1 u
        }" ^1 C; d4 U5 Y
1 @; n$ \% Q2 E: L/ h
        //% g: ^: `( l3 `8 i3 X" m
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
* J7 p9 A/ R/ `5 S% c- m4 v        //      
3 K- P( I% N1 X9 v, c4 U8 R        if ( _LdrpImageHasTls && bImplicitLoad )- F* I  z  Y* s
        {: k- w* `5 [) _6 y
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,# ]8 x- N: L* @3 _# {2 Y# I
                                        DLL_PROCESS_ATTACH );# |" r3 j6 v! K7 K+ n
        }
( `0 r- L/ e9 h  m( g    }" C2 w* I( G( D8 @
    __finally! X7 u# O; z" z' a! Q- q
    {
) m3 F2 B' Z8 t, i8 h2 T" U( N        //. K1 y4 U, E& p) S) n
        // 第四部分;, w4 P  p! }7 ^$ D8 E. W# ]% t
        // 清除分配的内存! v: s' `# X/ u) ~
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
+ |* {: s, l1 E  Y! r    }
6 L# \- @  {4 V. s) F. \6 b, a1 V3 K7 Q! b
    return STATUS_SUCCESS;
; a% X! q/ N/ j  ^}   8 N, Y$ {) R3 K) q) a5 o1 Q

; B" O6 Z4 O1 G. `% G& [+ V& l1 W这个函数分为四个主要部分:+ j4 }1 F1 |  G3 ]' @# u
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。! Y  S  J- g& s' b
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
, {) h: p& h/ K7 r/ v* L三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
  i/ J0 ~5 L0 ~4 g9 J另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
* Y! _6 x( O8 F7 q3 h3 G在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。- V  R! j! ?; }( S: n! y
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-3-5 12:48 , Processed in 0.071048 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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