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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]3 B3 n* _3 P( p2 x/ h+ Z
/ T1 v/ x& h7 O& `9 R7 G0 N- Q$ q
WINDOWS 2K Dll 加载过程* v2 }4 I% o, f- ?! @
jefong by 2005/03/30+ X3 A' }" O( e3 b: o: J+ W
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。: Q. y6 L! s+ U0 }
在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”。% _0 g2 Z9 A3 r1 M8 Z+ t
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
+ M( s. P' p& ~4 k9 w! Q初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
8 v. o: l' m% w( X- a  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。$ `3 V$ ?( a  O7 V# v' x
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
+ a0 F4 k/ H( O& i  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
5 W: I, \' x( k- K//=============================================================================4 I% L3 ?7 J; t. U  C- R( }4 a
// Matt Pietrek, September 1999 Microsoft Systems Journal+ K) E0 r1 U8 C* {
// 中文注释部分为jefong翻译- k" [( V5 X% J
//( _3 d( E! v7 O
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
7 Z; V# z) H- N//* h# J! e7 F. B$ Q
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;1 v7 X+ H# M: L6 n& H
//=============================================================================
/ h, i4 J1 u7 V  `
# M/ u- R% A6 l  P& r* M+ \#include <ntexapi.h>    // For HardError defines near the end/ x* P# u( n! s) V% \

" G* o4 Z+ M1 V" s# U- v// Global symbols (name is accurate, and comes from NTDLL.DBG)& r. v4 O, n5 ?- W
//  _NtdllBaseTag% |; t3 [. _2 ~
//  _ShowSnaps
/ p" Q+ V2 |7 ^+ b( t; P& }//  _SaveSp
: [# t6 g) b+ {; ~8 x: ?! R//  _CurSp
0 w% q' v' b* U. C//  _LdrpInLdrInit
; ?/ Y: c* w3 W8 a$ X//  _LdrpFatalHardErrorCount% D2 s+ a. a  ]0 h5 B
//  _LdrpImageHasTls9 Z5 D4 ^0 }+ V) J

: Z! Y) ~, O2 ^7 p' c7 YNTSTATUS' Z9 h# }) P9 m, ]5 o+ M
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
6 {3 h  y2 R/ v2 n0 ]- X. R{
0 x* w* B6 z) y. K; x    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了4 @1 M7 P% H. J/ s* ?
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
5 R% Z) L) C, X9 l( \1 Z7 Y# k
/ J/ u0 U8 d2 k# r. S6 N; O* D    if ( nRoutinesToRun ), h/ g' u. v  S. b1 a' q$ k1 O
    {
- S7 s- j/ J; u9 i8 Q        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
( i- t: G: _( [) X1 e        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
3 J5 g0 ?" G. o# h+ w                                            _NtdllBaseTag + 0x60000,9 Y. z% _6 A* t$ V, X& I7 {) D* a
                                            nRoutinesToRun * 4 );
2 S: x7 E6 M/ g/ Y3 J& n3 s) _                           
  }1 x6 D# M) U9 J+ q7 ?, _        if ( 0 == pInitNodeArray )    // Make sure allocation worked, I- p3 j, c" D) k
            return STATUS_NO_MEMORY;# j6 T9 Z& Y" v. w. {. l
    }
4 J& W0 w+ N0 z0 k    else
# `1 O8 r  Q& K; O9 }        pInitNodeArray = 0;
) e# Q8 u, F" Q9 y& U; a& D  \$ M( {: P$ _
    //第二部分;! a/ A% l+ @8 T9 ^, V
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。( l/ h. L( X; D! s
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);' D5 L7 |* n1 N  R2 l3 l1 v
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
/ W  c1 G7 q* a        # ]; a" Z1 T4 A( ]
    if ( _ShowSnaps ). {* K' K9 Y0 c" B, r3 D! Z
    {1 m) S! H' ^+ t  d' `
        _DbgPrint( "LDR: Real INIT LIST\n" );
8 M: h  v" P$ Z5 A    }8 X4 y. M. ~5 e: k8 I2 ~  i

, Q7 Y* u' y' f; k) m) C    nModulesInitedSoFar = 0;& d. y/ |  M2 K( T- Y

" W/ [( n) G. v/ N# c1 a3 T    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块$ ?$ A1 |1 N0 Q: K6 ~/ y0 j9 x& \
    {
! _' v! o+ J2 R1 L6 u        * r9 ?( I. `# S$ T3 E: B# P: Q8 {" I
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
+ p2 h+ s" Y  M" S# P! f        {
, W4 w0 _7 E: q& ~# p7 e* F) v            ModuleLoaderInfo  pModuleLoaderInfo;
+ z( H- J/ x, M            
9 A' O) @: _2 a" A/ u5 @5 l            //6 _$ W  O. [" o1 d9 G/ {- \. X
            //一个ModuleLoaderInfo结构节点的大小为0X10字节
: A5 f& F2 t5 f- K            pModuleLoaderInfo = &NextNode - 0x10;; ?; _9 o# }0 X! b: b: s
            * S. x2 j* r& \0 s: G; c
            localVar3C = pModuleLoaderInfo;         0 P8 {7 j6 Z5 z5 W9 p! b
1 F0 F4 @8 U( E  G4 q, O
            //
: F- l! H6 y. c8 d8 N            // 如果模块已经被初始化,就忽略; f0 Q) W, e3 _0 }8 T' R
            // X_LOADER_SAW_MODULE = 0x40 已被初始化. v; i0 I4 m# B0 S2 L
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
  [: D3 G) r5 s! S3 }; E            {1 M8 M' c  y% o! C/ r3 y& i3 A# y0 C
                //
* e+ A8 K2 k: ~' G+ ]                // 模块没有被初始化,判断是否具有入口函数
5 G2 R# l( a! s/ A                //5 R' c1 Y: d* p7 o. o3 V
                if ( pModuleLoaderInfo->EntryPoint )
( e8 t* y  Y1 d7 o3 F; s                {
5 F6 K# g- I% l9 H( F                    //6 z9 k4 |7 i" F& B) ]5 Q
                    // 具有初始化函数,添加到模块列表中,等待进行初始化
: Y1 x) U9 i- O. C8 G( O9 G                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;1 A5 u! \3 P/ V- b: p1 y

2 a1 K5 C, z9 z                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址8 Z2 |6 x6 r5 `9 n8 {4 G! I
      // 例如:
9 i0 c  d5 j: z2 r1 q& U                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
8 K! g3 t$ }, H                    if ( _ShowSnaps )' M9 A0 b; G6 A' y* M; O/ S: L
                    {
5 D  Z& o& F" a, ]" V- x$ X' p# e/ w                        _DbgPrint(  "%wZ init routine %x\n",
, |1 X' S2 j: d* ?  J                                    &pModuleLoaderInfo->24,
4 S3 H; k/ U( @2 H7 @) x, c, a$ ^                                    pModuleLoaderInfo->EntryPoint );3 a3 ?+ q3 S2 m! I$ C# ]  ~
                    }
5 G4 A; ?5 K; ~. A5 O* H2 }4 {
4 D8 S9 u% {# C                    nModulesInitedSoFar++;
  F$ `- C) @, K9 k                }7 u+ d3 a$ A2 F/ W3 ?
            }! x( N$ F' \" M  l& y; }! m& y* i1 Y

2 V# w) s  e  F- S3 Z            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
) [1 k% x, C- g            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;" ^8 K% J; v" J5 l, N7 k+ b, s" B
3 P6 A( y  ?5 I+ L" |3 t- J
            // 处理下一个模块节点6 E9 N0 s6 o7 N( w
            pCurrNode = pCurrNode->pNext' S, y2 U* f7 S. u0 o
        }
$ @$ A) W: e/ d2 {5 c    }  h& ^3 I: A3 O- v
    else
4 X6 L+ F$ X& A5 m2 ]    {* s8 f# ]' A+ y3 C
        pModuleLoaderInfo = localVar3C;     // May not be initialized???0 a6 R, z% B  g& r
    }
3 c$ h9 _3 s9 \* S; Y, [, ?   
; I. v/ X' I: [. U: q* n    if ( 0 == pInitNodeArray )0 R( f; u4 z; ^. `& T9 e/ N: u
        return STATUS_SUCCESS;
+ ?6 V8 F! @% @- u
2 _* T" v6 C0 c5 F' ~    // ************************* MSJ Layout! *****************, D+ m7 w; y( I. R# Y
    // If you're going to split this code across pages, this is a great
) k* K4 e& w5 e    // spot to split the code.  Just be sure to remove this comment
3 d* X* X/ |- B    // ************************* MSJ Layout! *****************8 l  m5 Z+ [  ^9 m
    " m2 H/ G- w: x4 a$ p4 N# D. m
    /// H8 X% q& y: f0 o$ k1 X; P, }
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
* Z# d& g: A$ n    // 第三部分,调用初始化部分% s0 a2 u8 T& N: B" X7 X  I+ N
    try     // Wrap all this in a try block, in case the init routine faults( C/ A! ]/ V7 H+ o( X
    {* n  f# T& Y; {, ^7 M
        nModulesInitedSoFar = 0;  // Start at array element 0: k+ f6 ]; _; ~/ ^0 S: l
: l! F% \1 u0 B8 Z, B, ~
        //3 L! a& V" \! r: a, V
        // 遍历模块队列
3 o4 T0 k5 H: X4 [) `& u# D/ l        //
/ z5 t+ c' C5 l        while ( nModulesInitedSoFar < nRoutinesToRun )
5 F& v0 y+ F7 M4 H4 }0 T2 v3 n        {" E0 k+ U5 _# K) o! ^
            // 获得模块指针
6 n& Y) ^6 f* e6 @& ~2 b! I  V$ Q            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
; L3 I$ Z/ h( L" U- T( K
9 g! ~1 ~1 @) K# l0 f9 C8 Y            // This doesn't seem to do anything...
, N! V! Q: s; Q( V            localVar3C = pModuleLoaderInfo;5 P. ]" ?: w& T2 v2 d) I
            , j6 h! N/ l( n9 l) ?1 j3 R9 F
            nModulesInitedSoFar++;
+ d. L9 j9 e1 D7 K                ) B" ?# y6 o" e
            // 保存初始化程序入口指针4 M( j8 ]; B+ S! c8 a5 j# N
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
: f$ h; H4 }; t2 [, ^( I5 W' G            
% |/ c% f8 c( ^4 x            fBreakOnDllLoad = 0;    // Default is to not break on load! m0 l- I9 n7 Q* l

: Q3 m" r1 j" m8 z% v% t' f            // 调试用  f4 g; y' H5 A( G( L1 |1 t
            // If this process is a debuggee, check to see if the loader
0 _, [$ F0 [: ]            // should break into a debugger before calling the initialization.
( @0 v1 E3 Q# C( P6 O0 k* l            //0 s% A( o% f% L5 n2 U5 t
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()- _+ E. F. {; A0 ^& _5 M
            // returns. IsDebuggerPresent is an NT only API.5 g8 m8 ?2 x$ c0 e+ E
            //
4 l, x) v5 R1 y2 [0 ?; Z; E            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )4 W6 \8 h, ^7 B* I# f$ D5 W* K8 \
            {( C1 Y5 e. e2 N) l9 c1 J+ e6 V. G
                LONG retCode;! [, S% A4 |4 q" ^+ W; J

  U* ], E: d  C& x" Y3 b                //              
* [( c/ T9 q; s% Q% y                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\  `" T5 ~/ q: `) x1 ?1 j
                // Windows NT\CurrentVersion\Image File Execution Options"3 k' e. B) |& E. k: N
                // registry key.  If a a subkey entry with the name of7 E' W- E- L" O) }0 |! a
                // the executable exists, check for the BreakOnDllLoad value.
9 [& N% F8 D8 A# H                //
" Z) n" k$ F! u+ J& A                retCode =
4 X9 X. K: e" y; I6 _+ ^+ Z                    _LdrQueryImageFileExecutionOptions(
; f8 l5 }) m2 j8 d8 C                                pModuleLoaderInfo->pwszDllName,. @3 l( }' ]/ U
                                "BreakOnDllLoad",pInitNodeArray
5 c8 D- O  H! i3 M                                REG_DWORD,: g. i5 `9 z6 k2 P3 Z$ k& E' k
                                &fBreakOnDllLoad,; i8 n* Y) R+ \: n8 n; H2 p
                                sizeof(DWORD),( G/ S& T" N, `
                                0 );; d0 S9 L* e0 H4 M
7 s$ j! ?4 S! K3 h: `8 V% m
                // If reg value not found (usually the case), then don't
+ [+ g! ]! N+ y- q. G& Y                // break on this DLL init3 i$ R$ d* Q* h. D) }
                if ( retCode <= STATUS_SUCCESS ); j& r) e* m9 L$ I
                    fBreakOnDllLoad = 0;pInitNodeArray* a  w$ W: t4 n$ r
            }4 A. A8 ]" k4 {5 E/ g8 D; e- R
            7 l) C/ o! B' @' P8 F& r
            if ( fBreakOnDllLoad )
& z& |, \" i8 d: i  V            {           
* C7 p) e* p. g' S                if ( _ShowSnaps )# S' y9 A& w2 r. b4 _! s
                {
% ]5 X2 J- m% K3 ^1 V7 ^                    // Inform the debug output stream of the module name+ u9 ?, j% N$ l- D! a" A
                    // and the init routine address before actually breaking/ A4 t3 n0 u9 Q! k9 G( E
                    // into the debugger
( o4 Q  }. q4 R: g1 S8 ^
5 Y# _+ X# U& E                    _DbgPrint(  "LDR: %wZ loaded.",: w$ e) z0 j0 k. T' \3 t  `" N
                                &pModuleLoaderInfo->pModuleLoaderInfo );
1 a) t( z3 c. e+ w, U% P                    
/ W9 n/ C* f5 n7 O                    _DbgPrint(  "- About to call init routine at %lx\n",/ c$ E! x% Z6 T
                                pfnInitRoutine )8 o4 A3 n1 h7 ^! b. O" ~
                }2 K6 ]4 U- |8 F. k( L" p! \; q& j9 h
                # m  T( y+ }7 c
                // Break into the debugger                              1 r( s2 D% o4 Q7 h
                _DbgBreakPoint();   // An INT 3, followed by a RET
/ h! o+ X. u3 X; h            }' d5 s; M( z8 V. c
            else if ( _ShowSnaps && pfnInitRoutine )
! u$ F2 n: R; i9 J            {
, E! n9 u% G6 X; y8 Z                // Inform the debug output stream of the module name
: J, B2 z. v/ n# i  c$ @/ F& y5 q                // and the init routine address before calling it               
; H  c) w4 ~. @; Y                _DbgPrint(  "LDR: %wZ loaded.",
1 p2 G0 G. o) m" c' ^                            pModuleLoaderInfo->pModuleLoaderInfo );
: k' Z5 u# j( w% ^+ S" J2 |6 M( ^+ c
: `, b! }, ^, f                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
. Y( S( N: w! K4 X) \* ^            }4 {+ E1 v9 S! ~. _: w( B
                    ) x6 f4 y$ M6 ^: Y& @  u
            if ( pfnInitRoutine )
4 }( O! Y! E, B  f            {
$ T9 A* e$ e+ J: |" D5 E$ e                // 设置DLL_PROCESS_ATTACH标志
: W/ m5 C4 f, p- o% w: ~                //5 \" P; U( h* ]1 a% Y4 H" @8 u
                // (Shouldn't this come *after* the actual call?)
* y& U( z6 U0 ~9 U8 N) Q                //
: `9 d! X4 v. \8 p' D4 P                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
& [+ J! U2 c2 o! i. c                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;8 c+ H- q" }" Z# {! \

" a8 A# K) P0 }5 k1 A; x* W                //7 h5 e: ^% n( s5 K; X) {% d
                // If there's Thread Local Storage (TLS) for this module,
( b  t5 g: s: ~, ?8 |+ d! t% p                // call the TLS init functions.  *** NOTE *** This only
  `/ H3 C: t/ @3 a5 }5 W                // occurs during the first time this code is called (when
2 A8 v- C4 d/ q                // implicitly loaded DLLs are initialized).  Dynamically
" ~7 q5 B6 A" m                // loaded DLLs shouldn't use TLS declared vars, as per the
# ]: `/ m  g. A                // SDK documentation
8 j0 p9 T" b9 [9 V7 Q                // 如果模块需要分配TLS,调用TLS初始化函数
7 p0 |$ f+ x6 X7 {# a  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时$ `9 ]4 ^; j$ b8 O/ h
  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量" |5 O( k. k2 _* r9 K
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
" z* [' G! }0 h! [1 G. l; j! I5 @                {
' O2 x' K& q, F- X% ~6 |5 e/ k. L  d                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,* J, j( v, {8 @9 g
                                                DLL_PROCESS_ATTACH );
8 ~( c3 G- J, u5 c* [* X$ A% ^4 z                }
  X* c% T" u& [, V# }) b+ i5 B! o                / y& `. @1 X5 g) n3 {1 P& i1 }

+ A  X7 @5 }  A: A# P3 b. T                hModDLL = pModuleLoaderInfo->hModDLL
! ^5 x% L! _) D# b
! k0 F/ u- [; V* h2 H& k! g* ]                MOV     ESI,ESP // Save off the ESP register into ESI
- E7 A9 w+ n3 T* g9 ?  
  F2 |  `9 e/ ^# F, Q  // 设置入口函数指针               
2 [* G: R/ b% V- O( I. T                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
1 w! I) }2 L  X; Q8 q8 [7 h* z! o; W. \; ^7 J
                // In C++ code, the following ASM would look like:
2 G( _" d/ N' C% @& I0 l& U% v/ N. t                //
3 P' \1 K4 Y" K! J' q  G( S* X                // initRetValue =- N3 I9 n% H  G
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
( ~6 [  R+ D- ~2 }7 Q- C                //5 c+ b5 i' o1 i* a

! U$ z2 J$ E% Y1 {                PUSH    DWORD PTR [bImplicitLoad]' P+ C! h- ?6 k8 ~+ @6 E
                - D$ Q  [7 f; p: |' m0 u& a3 Y
                PUSH    DLL_PROCESS_ATTACH
. G- u: _  D$ I5 S3 [0 a# X& x1 W                : d5 Q8 ~, x: N* J8 N  J
                PUSH    DWORD PTR [hModDLL]2 z9 `& v( z- P2 j( x
                & |4 k7 h' B" }- t  c; B# D
                CALL    EDI     // 调用入口函数
2 D* U% d" z, z. M% k0 y               
' n: H0 m$ F0 p; G" r                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值4 b2 A# j9 R$ m; M
0 H: P* l0 f# L, P
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the
' {8 l8 U4 m* R) Q' P& K                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns! K" B% D7 s# Z1 Z2 @
$ s  g" e  W, f  t% B
                MOV     ESP,ESI     // Restore ESP to value before the call8 v$ H" B+ m, E

" R( N" J7 n1 k                //0 W7 x% s! e- Z: q+ J# P, K3 Y
                // 检查调用前后的ESP值是否一至& T- M7 i3 \# K
  //
+ g9 B: M. ?7 b                if ( _CurSP != _SavSP )
- Q: y4 D: H6 i, T                {% b& e! k0 M4 H( Z% F* n& q
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;- ^9 @# `! U" O
% J6 G  Y: ]7 \# I) u2 p
                    hardErrorRetCode = ! G) P+ [& G3 t* G0 X7 t. z
                        _NtRaiseHardError(
  H; [  L" w$ L/ j- E! h7 s% w& X                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
' h! o$ {+ n. y* ?9 C6 {- F                            1,  // Number of parameters
$ h% l0 C; _9 i* H8 ~: A- c* Z                            1,  // UnicodeStringParametersMask,; @% m. z) s8 i6 P& M
                            &hardErrorParam,
3 P- i# l: k( q, s: e6 ]9 s                            OptionYesNo,    // Let user decide8 M6 n) Q7 c7 {6 O: m( q8 v
                            &hardErrorResponse );9 O$ L" j( H& X5 K
                                            9 L' b4 n8 b7 N% `+ E
                    if ( _LdrpInLdrInit )
% |) x4 h( Y+ W$ |% N! `                        _LdrpFatalHardErrorCount++;
; q' z, N4 w5 B# ~
0 P; O8 r: G0 Q" o                    if (    (hardErrorRetCode >= STATUS_SUCCESS)& q$ y7 s# |" e3 `+ ?
                        &&  (ResponseYes == hardErrorResponse) )
2 z" y) X4 t  A                    {
0 W* G3 h& Y* w" ^                        return STATUS_DLL_INIT_FAILED;% k$ v2 q) A: V% P6 z9 ?8 r$ ~( f
                    }
! I. R; p* k$ t                }! G3 f8 m( c+ Z1 i, r
, x, d3 x, Y* U0 z3 u, O
                //
0 }/ _3 L) D! W- ?# c3 M; N- K4 |                // 入口函数返回0,错误" e; B) D4 W' [% I! y( f
                //
0 P* y+ Q/ a8 C8 T. Q5 m                if ( 0 == initRetValue )( U# H, \  y7 {3 X
                {
/ o9 J2 q+ y7 z% E                    DWORD hardErrorParam2;' ^, I7 i8 {* B6 s- D; Z: S2 B
                    DWORD hardErrorResponse2;
; B1 ], k: c  u" a5 m$ B$ v                                       
- z4 ?6 F0 |9 h4 d) l2 h+ n% O                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;( s1 E: U: _' w( B# ?( E" t
                    
( S8 [5 r0 K; ^. a% L9 [) L                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,( e. @# i- j8 X* a* o% n
                                        1,  // Number of parameters
# E; R$ j  r; D, K- h                                        1,  // UnicodeStringParametersMask
. Z9 Y* _  d/ D                                        &hardErrorParam2,: k3 O, Y6 O1 F. x0 L3 M
                                        OptionOk,   // OK is only response
! K  w5 H2 a6 C) U' M* ^! D. c                                        &hardErrorResponse2 );; \8 D( N! U0 _$ u4 Z
                                                            
# Y) u0 t9 a' {; ?7 t' U                    if ( _LdrpInLdrInit )$ n$ Y$ T5 h& s8 O/ T6 I
                        _LdrpFatalHardErrorCount++;0 m" p  N# u% L- u. d. k% `; m
% b5 b) r  v) V0 I/ d6 h
                    return STATUS_DLL_INIT_FAILED;1 B5 U& u6 C, j
                }) [7 \. F; K! z; Y. r" e" j. Q
            }$ r7 c2 T3 @5 h$ k1 p4 ~5 T- s
        }5 j6 I- C% _) X' H

) k" z- G  t1 W3 C& @        //
6 _7 E: |# [! M  p        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
" a3 q" O; y7 ?; q. Z& R5 g1 L6 x        //      
. K2 z: c/ L/ P5 H- Q        if ( _LdrpImageHasTls && bImplicitLoad )0 G" `' P* `2 }. k* ]* u
        {
# x$ E: d2 u$ O7 g  D( q/ y; B1 L            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
$ V2 \7 _+ L5 B1 i0 y4 |# E1 `                                        DLL_PROCESS_ATTACH );
1 Q8 ]! z( i2 a1 K        }8 `! ]5 `5 i2 r) N, d/ p/ N: }
    }
; {! f. j. {. E1 @( B3 e    __finally; e5 o2 d4 u! Q
    {
* C$ b9 J' E5 |" E        //
& n/ ~" G) d$ B        // 第四部分;
" z0 H, e4 B: E% m        // 清除分配的内存) D7 [5 U  ?9 @  Y. t9 }
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );# a; V% w, {0 q- Y) m' O
    }
' k) q0 @: Y/ j  }  w7 {* q
1 ^# v+ w3 U4 ^3 z    return STATUS_SUCCESS;. @/ G# z0 ~7 ^1 b" o# }
}   
4 ?9 e: @; h( R
) S# Y- S; y/ u: m! M这个函数分为四个主要部分:
( J. `2 [- j: ~% H! G一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
0 I' i& z9 Z3 p3 L! B9 K5 v/ X二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
. n$ T  z; [$ f& @: ~) A三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
% @! G- R7 U$ B$ S0 Z0 _0 j另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
1 W! c5 U0 q' u在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。
$ ~- s: C$ h' R4 x9 Q四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-7-19 13:27 , Processed in 0.074263 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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