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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
7 l3 b1 ]9 p( y3 S9 t6 f1 i2 W+ T5 m3 w3 l
WINDOWS 2K Dll 加载过程; X& h* l. r4 A8 \5 V; A) b
jefong by 2005/03/30
1 M# G% ^8 I) u$ m& ^5 @' B这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
. q$ I( f1 T" ]5 F6 B; E在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 @5 T; Z' N. E9 {你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。3 t* J: k$ Y& J! M  m: c% V
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。
) M* m. M$ M' K$ S1 ]  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。$ I9 i2 e+ X8 a3 I
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
  t. h9 c/ O- }9 N8 \9 p  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):3 l- J6 K7 C" F5 v5 H, j$ w
//=============================================================================$ c1 n: @" [5 Y& L1 X! t. y
// Matt Pietrek, September 1999 Microsoft Systems Journal3 q( Q/ n0 f: f, H( [
// 中文注释部分为jefong翻译
6 m3 i4 I* @& Q# J7 J//1 j! k8 t, L9 j( M# h6 M
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3). d; w& J( A; O
//
+ Q. G8 |7 e6 {: Z0 [// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;0 m2 Q4 d! B& J  P' s
//=============================================================================! ]4 U/ @" |4 \6 X' R

2 m1 E# }8 v$ ?, Z' R1 @! ~2 [2 y7 Q/ f* G#include <ntexapi.h>    // For HardError defines near the end# d1 u5 ?9 L' ?& M' q$ a+ \: R3 ~

+ z* ~* |/ I. _% X// Global symbols (name is accurate, and comes from NTDLL.DBG)! A  n$ ]* _( Y7 ^1 h
//  _NtdllBaseTag
7 }( U% Y) d4 o6 u* D' a" n//  _ShowSnaps
% J! ~, x  w( S5 g//  _SaveSp
+ p4 V8 ?& w' m//  _CurSp& G8 o6 U; y5 S% Z
//  _LdrpInLdrInit
7 ^; B; @" K  `//  _LdrpFatalHardErrorCount
1 G4 ~; K: N1 [7 t/ T//  _LdrpImageHasTls
7 }; x' |5 d' h# i: g" M( h! y5 v1 ?
NTSTATUS5 p' t. Z) v4 k% Z
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
: ]" k# F& }9 V/ C" m) u( o{
. P# W1 P" g" C3 S. K    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了  C6 D8 F0 D6 q5 {. o1 K& s
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
+ Y9 R- o' V. a' N. o) c* v2 l9 e& y) b* f( K, }: d
    if ( nRoutinesToRun )
# i& M. d9 u. [    {7 U4 e! Y( Z8 d9 Q
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
7 X8 V0 x, X) _; C2 {8 l$ D* [        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
  }" J& T; j% ^( w6 q                                            _NtdllBaseTag + 0x60000,( y  V/ ~# t$ n
                                            nRoutinesToRun * 4 );8 J0 V$ \) L2 F2 d. S4 G
                           
9 z5 t; I% H. I9 s0 p        if ( 0 == pInitNodeArray )    // Make sure allocation worked9 ?+ I/ ~9 H/ i  G5 l& t* y# h4 N" U
            return STATUS_NO_MEMORY;' D! ^* ?- A+ M  W7 c2 l* k  m0 Y
    }
6 q: c, G* h9 p3 ]    else# Y2 a8 S8 ]2 I
        pInitNodeArray = 0;0 B# M) A2 P( t1 \/ F, G8 @1 o7 l: E
' h, @6 m, i0 h8 C0 ^" W
    //第二部分;# [1 }4 U) n& n% J3 n. X- R
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。% @( M) {. M% H8 q
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);/ l: \9 R/ u: _2 {. W, u
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;+ u) v# d& c# ?
        / _% A2 H* [' x' T. d: e1 V
    if ( _ShowSnaps )
* }+ A5 r- h* B0 n) y, }0 a4 r    {  Q. J+ {" W3 ]
        _DbgPrint( "LDR: Real INIT LIST\n" );+ n0 Z) E9 S- {# ?7 K; h3 V& m7 `
    }
' F2 q; l* N$ M! b, c  m' r4 t$ B" r
    nModulesInitedSoFar = 0;
/ V9 X" p( d: R) i% P+ e! \7 p* I9 P* V% Q: Q" }  Q
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块# d$ D: o* R5 z7 k
    {% k" u; H9 y, V& f) |  P
        
: a. y+ ?! l; W: z        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
2 ~# h6 p6 H. G        {
  u+ {4 h% p2 \" b+ H: D. i5 Q            ModuleLoaderInfo  pModuleLoaderInfo;
" m% }4 m, @: l            
5 \% y, w: T7 w3 Q! a            //6 ~3 W; \) h  @  w6 N9 ]
            //一个ModuleLoaderInfo结构节点的大小为0X10字节3 e/ x0 [  I; U+ G) T2 z! l
            pModuleLoaderInfo = &NextNode - 0x10;+ A. T  D: `* ~) K4 ]# f' f
            % t2 s8 B# o; b, w. Y
            localVar3C = pModuleLoaderInfo;         
9 N7 \9 f" E$ s% [$ l2 t1 T/ X" n4 a  ~! [
            //
: B6 C. \" g+ a7 Z# J# }3 I            // 如果模块已经被初始化,就忽略$ n" Q/ V* z; g$ ^2 K/ U4 E
            // X_LOADER_SAW_MODULE = 0x40 已被初始化) N6 L# o, j) k6 r& x4 u
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
1 K# z0 u) X- H, A            {
9 {! G1 y( ?0 b! i3 @: @' D                //
! E6 r5 {/ Q1 D" L, O/ m9 _                // 模块没有被初始化,判断是否具有入口函数( o: d7 b- \9 D9 z- d2 h2 u
                //
/ u5 c; T. e# C0 V! u: w/ O" o" P9 j                if ( pModuleLoaderInfo->EntryPoint )5 P) v5 Y1 B5 V* H0 j2 k6 C9 V
                {
3 G" G1 {# m; Q2 ^. Q  G5 w- G3 o. [; ~                    //
( t) W' w1 T" J1 R, S3 R5 o1 C                    // 具有初始化函数,添加到模块列表中,等待进行初始化, m9 n9 |' k: s, o2 A. b- y
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
: K( [: q  Y  U  \& l$ N8 ]- }. O1 p0 V- y" ~5 ]0 G9 T" K
                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址0 d7 y) U# h3 s
      // 例如:
" o% m6 y! A/ o2 Y) Z7 A* a                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000: s) h5 R8 j" f2 c/ C/ C( k8 o
                    if ( _ShowSnaps )
; d* Z9 B4 I" N* v0 U$ T% i                    {
6 @1 L( P7 m9 ^6 S- ?' w  v& p6 p+ m                        _DbgPrint(  "%wZ init routine %x\n",, r2 a, F% ]* d. K
                                    &pModuleLoaderInfo->24,
% ]0 @1 x- H0 Z- K# v9 k                                    pModuleLoaderInfo->EntryPoint );3 ~  `* g* w0 [9 T0 R! `: {9 }4 T
                    }
; C5 n/ m& {. y0 b' l  w( X) F" S; o4 _7 D2 \
                    nModulesInitedSoFar++;
. W" \2 j: K, N' A' g* ]                }- c8 z# Y1 {: ]0 T$ w% |/ i
            }2 D: J; L8 X. ^- m: ~5 C; D
: {8 \3 z7 r7 ^$ y
            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
7 D: ?! z6 ?# R9 z. v2 j  O1 {5 ]            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
) h# Y! O1 g2 j) y8 N0 L# `, K9 ?9 K( v  |+ B; J- }
            // 处理下一个模块节点
/ {  A% k6 q" Y) Y            pCurrNode = pCurrNode->pNext
% l& w: b" ~& S- C+ j2 o9 i4 A# K2 c        }4 k) `* u$ Z) g4 t
    }
: }) `3 @4 n6 h8 f3 g! h4 Z$ a* [    else, J* ?3 }: n( T  w2 n
    {" M5 j4 G/ o' [/ s+ n# M+ D% O" G
        pModuleLoaderInfo = localVar3C;     // May not be initialized???
! J$ b4 U% X5 I    }4 h4 i) I5 Q( @% G* Y1 h: r
    ! Q& A/ Q1 L# @8 A3 b  S  U
    if ( 0 == pInitNodeArray )
/ H& m9 T7 c6 e        return STATUS_SUCCESS;. ]# w+ |; \) b3 p

) I2 A: B: h7 R+ a  v3 W    // ************************* MSJ Layout! *****************3 s7 I) w$ D8 ~% v" E2 ?
    // If you're going to split this code across pages, this is a great
. d: w  X: o0 x2 W8 U    // spot to split the code.  Just be sure to remove this comment
# T% l6 s9 \, w( x    // ************************* MSJ Layout! *****************
9 @' ]7 D, |6 ]3 ^5 o0 J5 p! h    / P8 x8 I1 L% g9 L$ k; S' @  S# t
    //' h9 K3 j* J% _8 a* p' g
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
' p9 O' b9 W! i4 }/ D5 n    // 第三部分,调用初始化部分
% u3 w5 N" G9 A+ ?    try     // Wrap all this in a try block, in case the init routine faults
9 Z& T  h+ A3 A; e5 R2 E    {
9 `# ~# {' p. o- X/ e        nModulesInitedSoFar = 0;  // Start at array element 0
- L8 M9 Y1 u  w! q- z& `. G" z; ~9 S, v6 k  Q* b5 X
        //. K- N, A$ S5 O$ k
        // 遍历模块队列
) c; G! _1 |2 m+ s% C) W        //  q9 I  C" `5 w
        while ( nModulesInitedSoFar < nRoutinesToRun )
: `1 _* p/ [6 J5 H. Q0 @        {& y, k. D7 P; _# K8 Z, q
            // 获得模块指针7 ?7 g# b$ X# h7 v2 t- U6 d
            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];+ i( b( @9 \! l  x" {  D' D
  D" }/ \3 x: M' p  z* y
            // This doesn't seem to do anything...1 \# Q) ?+ n- o! n/ c$ b; @/ S  H2 L, e' x
            localVar3C = pModuleLoaderInfo;! y3 l9 ?+ g7 Z+ `) o
            4 W+ [/ ]/ V! n0 M6 w5 ^
            nModulesInitedSoFar++;
% v# N- G+ L1 k               
2 r+ z0 O/ W6 O7 P; m            // 保存初始化程序入口指针
4 c" P. s9 c- B( g8 G7 j            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;- U& G% a4 L$ ^$ e* o
            
4 P8 [1 p; e8 c: E0 u  Z  l            fBreakOnDllLoad = 0;    // Default is to not break on load$ c  N9 `/ @" I' F/ U# C% A- w

. b! u2 M8 C/ E& V            // 调试用
+ w9 [% x& F# _0 h4 W! v  k            // If this process is a debuggee, check to see if the loader8 y! |. V7 s9 ?- h# b
            // should break into a debugger before calling the initialization.
# Q# N& p3 ^; g7 L, K  B            //5 g+ ~4 ~7 l; ]3 P
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()8 |+ }6 c! H2 `& k& {
            // returns. IsDebuggerPresent is an NT only API.
1 G6 {& F8 I2 p) |, H' E* a            //+ [% a  L/ g# ~2 E6 g
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
3 i4 a# H: v  ^( \2 d            {
2 \2 Y" U3 o1 s( e                LONG retCode;- o, |. \' G& ^( o+ I+ h
6 c/ {' H5 \1 I+ ~0 j
                //              % t: O- r  G  ~% [* q) _1 D
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\8 j* b" A2 ^* J" [9 r
                // Windows NT\CurrentVersion\Image File Execution Options"
+ i/ y5 M1 z9 g5 I1 ~- [+ h                // registry key.  If a a subkey entry with the name of
4 G( x) C$ W5 _$ @) D                // the executable exists, check for the BreakOnDllLoad value.
5 L; Z( u  g' {                //
$ J) W% j+ f1 o) B1 f/ o                retCode = ; r6 V6 b& R$ k; C5 X& y: m/ f
                    _LdrQueryImageFileExecutionOptions(
8 `- g- Q3 m0 @- j9 n, T* Q                                pModuleLoaderInfo->pwszDllName,$ u* J6 D+ E3 @" r0 o% X! e
                                "BreakOnDllLoad",pInitNodeArray
+ J2 P! F: y2 K: g/ V5 C                                REG_DWORD,' z  M; y0 T) h% X% S- E* W& k
                                &fBreakOnDllLoad,7 i: m! f! c8 q+ T, V5 U- Q8 ]7 m
                                sizeof(DWORD),
' d; E9 w) E( p& \8 P7 M                                0 );& e2 r. B: N% }
) d  M$ J( N6 D6 P" F
                // If reg value not found (usually the case), then don't+ `; w4 G. {6 T8 i
                // break on this DLL init% a5 w* |$ b0 ^1 e& s- i% w  |! i
                if ( retCode <= STATUS_SUCCESS )
& I. b! Z# T5 m- O8 c5 U                    fBreakOnDllLoad = 0;pInitNodeArray; m+ v& [6 o* s: l+ ?( [
            }! m- I- t1 Y- H
            
  I; _# n# j; P6 A7 V            if ( fBreakOnDllLoad )9 d( b* t" v4 t. G" h% |7 u* b
            {           
3 _4 A  z: j+ n8 e- p6 _1 o3 i+ ^6 V                if ( _ShowSnaps )0 N8 z" R4 N4 U" o) }* n' n
                {
# l) i2 r5 O* W3 M/ u5 ?0 z                    // Inform the debug output stream of the module name6 r& j( S6 }/ l6 ~- \
                    // and the init routine address before actually breaking
, Y7 }- i; o) n! r                    // into the debugger
  U5 U" ]+ a( [- G2 |  [# m/ a7 e" M3 }- u' I
                    _DbgPrint(  "LDR: %wZ loaded.",/ c" H" Q! f0 H, g/ @
                                &pModuleLoaderInfo->pModuleLoaderInfo );8 S- [$ F$ ^$ ^
                      r9 j& }% ~6 x4 }$ H- ]2 e
                    _DbgPrint(  "- About to call init routine at %lx\n",) y' N" u0 H+ I
                                pfnInitRoutine ): Q6 J: j- y0 v4 Q: ?
                }3 {7 Y* T+ l- h+ n  g$ a% R
               
0 h+ }) H' q* D0 x" R                // Break into the debugger                              - A$ q! E8 C+ K
                _DbgBreakPoint();   // An INT 3, followed by a RET
1 @4 [8 Y  s2 W8 T3 S: O, c8 T            }/ ~) r% P8 K! C
            else if ( _ShowSnaps && pfnInitRoutine ): x& L$ j7 i( C- A, t
            {
6 y; z( H) s7 b                // Inform the debug output stream of the module name
. o# v  ]0 l$ s: _. ^  I, ~                // and the init routine address before calling it                 e) F, G  f' ]
                _DbgPrint(  "LDR: %wZ loaded.",
! w# `! `5 A0 O3 V: m: r2 R: ~                            pModuleLoaderInfo->pModuleLoaderInfo );3 n2 }. `* @* [+ Q
/ v: e, V, X3 l- g, ~; i' z( O; G& Q5 r
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
+ O6 l1 O" `. o. L( a' `1 O' k            }
; i6 j; n6 h, _5 W4 m                    
* Z3 s, Q9 S! q7 m! X1 }6 S            if ( pfnInitRoutine )
- ?" ^" U) [! X            {
3 I/ m& k. C4 v5 K! F                // 设置DLL_PROCESS_ATTACH标志
+ m* W3 H$ X# Z  k6 W" s; ?/ _                //9 f& B8 B% G- S' D
                // (Shouldn't this come *after* the actual call?)
& ]1 g! s8 L) d/ m                //
; g' M- v( n4 m8 w* e                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             $ e6 v" b/ ^- f
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
( |# G/ S1 k) T8 _0 Y5 H4 k4 W) e3 E9 S) B% f# q* I
                //' K! l5 j" n0 S# i
                // If there's Thread Local Storage (TLS) for this module,/ \8 k4 C; E) S) D* C% f
                // call the TLS init functions.  *** NOTE *** This only
; p: K& ]- n8 _. L) z+ E                // occurs during the first time this code is called (when$ J+ ~, ~% H' y/ R" E! f- w) R" n
                // implicitly loaded DLLs are initialized).  Dynamically
$ _7 R  R6 x3 S. }0 a. r$ B' A                // loaded DLLs shouldn't use TLS declared vars, as per the
* O4 u, V6 k  K, b                // SDK documentation
1 S) N' z7 U3 i5 g; |                // 如果模块需要分配TLS,调用TLS初始化函数# }. O8 n0 l- @, |# I6 y5 v& ]" |
  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
- T9 m. `7 k! n3 D4 w  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
+ R$ p* H% @; K0 }                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )) c! ^/ g4 o% @) A( X
                {/ |' |5 \& e! E1 z  ?! S0 O. O
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,6 M' H4 o" i1 x6 c$ L& Z1 s/ c
                                                DLL_PROCESS_ATTACH );% _- s  h; [  a" S7 ^
                }$ `' O: ^/ a. q# S% ]1 U1 ~
                1 Y1 |9 A: _* {5 W' ~3 S# [# e. K
4 U) S+ U& t3 d5 K! u. Z2 G$ H
                hModDLL = pModuleLoaderInfo->hModDLL
" s! a/ g  d4 U5 v  [, W1 d2 t& p% ]  `: U1 O4 {8 E- \. _
                MOV     ESI,ESP // Save off the ESP register into ESI) |' c8 r& x% M% X/ u  @$ _3 G
  ) R* F+ h+ R) D. `# ^8 d) A
  // 设置入口函数指针                ) s9 G" r* ]9 w: h
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     ) A" z% k* x- \2 c. k5 q

8 t' [! E" Q  \' A! ?0 s. ]                // In C++ code, the following ASM would look like:
3 h/ T& U8 E7 ^1 t1 l3 d4 n                //
. ^! k9 A, {) z: V+ Y$ k                // initRetValue =* ?8 c$ \7 O8 h$ H( k
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
( ^. ^2 C& P8 o                //
2 s& r: c& a% w" T6 ]: \: ^! t1 R# x3 P6 d3 n" k
                PUSH    DWORD PTR [bImplicitLoad]
3 W3 i: G& @" i5 R3 p% n                  k  z, n) I; d) A8 V
                PUSH    DLL_PROCESS_ATTACH4 j: ^0 r# ~" x, e% q
                2 X+ a9 }, |, C  B' m
                PUSH    DWORD PTR [hModDLL]% J8 X! n* k6 f3 E! R- ^
               
) Z) |3 _0 p9 j. _$ O5 X' x                CALL    EDI     // 调用入口函数
: h, Q7 T4 s2 @2 K               
9 l) o; A: N6 n                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值
6 t9 ]) X+ k; m7 F( C) H$ Q5 N9 J* M5 ]/ A7 q: {/ E. G
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the
- I  S, Q! T6 O/ q* ]: Q! t; x                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns3 L) ~2 ?' e. v' b3 t
* }/ l0 M2 x  ], p
                MOV     ESP,ESI     // Restore ESP to value before the call
, D- W; Z4 B) C: t( V9 ^7 q4 U9 u+ K5 \- q* d; E
                //
9 g5 v' s, ^" t                // 检查调用前后的ESP值是否一至. h0 l! W$ f1 U2 X
  // 5 K7 _# H# v& _  e9 o5 a
                if ( _CurSP != _SavSP )8 B" n! G7 }0 p$ J1 O* t
                {' N0 ]9 f1 f9 Q. A! Y
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;6 K2 u4 ?1 @5 D, v0 k

8 q7 V- P; U! e: e; ^1 g7 {                    hardErrorRetCode = $ [2 F. Y, U% Q% `
                        _NtRaiseHardError(' n5 l8 I' `. j4 t, z6 ~; i
                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,! J" |" J5 c6 ^1 H( L- S/ l
                            1,  // Number of parameters& [) ~' \+ g% `* G" H
                            1,  // UnicodeStringParametersMask,. Q. x8 Q3 Y) W; h( }- g+ t
                            &hardErrorParam,
6 c/ K7 {% O1 Y) v* m! }$ g  [7 L8 x                            OptionYesNo,    // Let user decide. b, w" ^* `. b3 S8 I8 v1 O
                            &hardErrorResponse );1 `% J) v2 b( z/ r$ V, X  y1 X
                                            
  N! K9 ^9 Q  c5 {                    if ( _LdrpInLdrInit )
  x6 |8 u. {! B# u                        _LdrpFatalHardErrorCount++;
6 t$ a7 A, ^  O$ Z) H7 J
/ I% K  b! W$ o8 M9 @! H' v2 B( I# s                    if (    (hardErrorRetCode >= STATUS_SUCCESS). [  V7 F, @* v. K
                        &&  (ResponseYes == hardErrorResponse) )
2 v- a1 H9 f! a' d" E1 p2 U5 i                    {0 r, o+ }9 w  T. b4 r, s3 S
                        return STATUS_DLL_INIT_FAILED;+ I0 l& g7 {5 o+ Q
                    }
6 j1 k. \( g# A                }: J0 p  w& k. L  \  b4 A

! J$ y3 Z% c. U4 z                //
. S% `9 a1 {; m* e* l5 ^' }* _/ V                // 入口函数返回0,错误
* c/ G6 F+ X3 D3 ?5 G$ f+ e5 K                //
1 w/ j) V- q& @" S2 k9 v) I                if ( 0 == initRetValue )
9 g  V1 K( u1 Z* k; y7 H! E                {
) u# T- W! Z# K5 H0 \; a9 R4 p                    DWORD hardErrorParam2;
# P* k3 T" {4 C                    DWORD hardErrorResponse2;
2 U: G: S& u& X. ?                                        , n) A5 C. C& O% N  ]+ o
                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
% x- B- W! @7 y  {, D                    
7 x$ @6 w7 i1 z- j& }7 z                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,+ d( y3 i' A; Y+ H  J; |/ D  }
                                        1,  // Number of parameters' _/ e1 q0 T( g" q
                                        1,  // UnicodeStringParametersMask6 O2 ^" i  h( \( C. j
                                        &hardErrorParam2,1 f  c+ {; {" U* Y& w8 A
                                        OptionOk,   // OK is only response+ Q: g* x" z. x
                                        &hardErrorResponse2 );
. E$ w9 J5 U, J/ p; v% i. C                                                            2 Q; h) @( p" {$ I3 V
                    if ( _LdrpInLdrInit )) v- W- I, u+ @  U) C7 n
                        _LdrpFatalHardErrorCount++;! M% r* B9 d! h, z' i2 T6 Y7 ]

  U* Y+ X8 R* C0 Y& h5 k                    return STATUS_DLL_INIT_FAILED;8 T- G. j9 M% m/ X9 y5 r2 z
                }5 G: p) ~9 z6 F6 w% [5 l: r
            }4 [, N: T7 |8 e6 _# W* m2 g8 m
        }/ @. Q4 i- S' J9 E) E
: d, Z8 \! R+ `8 _5 G
        //
+ |- u4 z: v3 n4 [, I        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
1 I! T6 Q# S. h( i        //      
% t- f4 ]' j5 ]* \+ Q0 a        if ( _LdrpImageHasTls && bImplicitLoad )9 P* x2 O2 w' r* v. V
        {& A* _' X( Z3 ]2 L* ]- A' C
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
2 s" Z0 C, D0 i) q$ f& s; A                                        DLL_PROCESS_ATTACH );* r4 J2 z9 y- A8 `1 s( [
        }
3 v& M3 W6 p9 f% E2 Z    }
% m, h  H* j/ M$ y0 J7 e+ u8 t    __finally
4 U: a7 c- I) s. f; F/ \, y1 c    {
% S' A9 z0 M1 e% F        //
; q* f, p! ^5 I9 J        // 第四部分;8 O4 y# C6 M0 k# Y) e6 Q
        // 清除分配的内存
  }0 i4 o. j9 _3 u0 h        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
( F/ ?+ _( A% r, @    }
' C; T! j( ]' R
+ K7 I8 p& O# w0 f' J    return STATUS_SUCCESS;
% d" @; [1 }' B2 p2 {6 D0 o6 e; y' P}   
' Z* ?( ^0 Y  x6 a/ b. n( k
( z- Q# l$ Q6 e/ s7 z这个函数分为四个主要部分:3 j1 z: B4 S/ l
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
% z8 M& l3 N" O( ^, w, m/ X二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。* C- L9 s) o; R5 [# R3 ^( N
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
3 E# h& J6 R- e) o另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
# B2 H& L, N- \, o. 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。
0 r- w; s( ^  U: V四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2024-12-23 22:23 , Processed in 0.057154 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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