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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]: C4 J' [( }$ Y( _2 `& E9 G
# o- ?* B# }9 U5 ]8 b
WINDOWS 2K Dll 加载过程+ C8 q" t5 A* \& N9 Z
jefong by 2005/03/30
* b* W8 `, T! Y) l* c9 U- e$ m这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。' v& ~, Q; t  U
在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”。1 _6 r. w- @/ ~* s* `
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
6 @$ t+ F, k; e; k2 p" W0 b# N初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。3 v/ y6 f8 v% l
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
8 L7 f$ u; ^7 {/ \! e  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
0 f; e% C9 `- u  j: r  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):$ P, G6 _" J- ]4 K
//=============================================================================
/ D: @3 F$ J7 K1 ]// Matt Pietrek, September 1999 Microsoft Systems Journal/ `2 \9 _' r  X* G2 Y# k
// 中文注释部分为jefong翻译
* J! H4 w6 ^5 N/ u, g) s//  W. w* Q- }! \; S! A. Z
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
/ B( j5 g7 g3 g3 c7 C//) |( `6 n% S1 l( j% B5 q
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;4 l2 a2 Z2 A8 s
//=============================================================================! F  E0 E# b  ^+ l" \7 _$ @; x: N* J4 S
' ^2 f2 s2 F6 f" P1 H  B2 a* [
#include <ntexapi.h>    // For HardError defines near the end
% U; ]  l/ K  O3 ^2 I0 c: w$ U- Y& T' j& e2 [# F
// Global symbols (name is accurate, and comes from NTDLL.DBG)
# g& m7 g" p. C8 b  w; @//  _NtdllBaseTag- h1 l* ], O( Q8 H) l  b+ x8 Z
//  _ShowSnaps
0 R9 `. [6 c. D# y5 j//  _SaveSp
9 O# l- t  U/ G//  _CurSp
4 `- j* G( d! A4 z. l5 z% w" n( Y//  _LdrpInLdrInit! v& ^/ E3 s0 d- }0 v- \2 N
//  _LdrpFatalHardErrorCount
8 {5 y4 e  M* }. A//  _LdrpImageHasTls
' @1 Y% A0 L5 v4 h/ r# }/ A( u4 J. k$ {& q2 p8 f
NTSTATUS
9 }* ?1 ?: {- c6 b, k/ h: cLdrpRunInitializeRoutines( DWORD bImplicitLoad )
* ~- v1 e- y; w; }/ M, s; C- A{
7 U' ^: o% k" R6 e    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了% A, a! {# I7 |5 j6 ?
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
; o5 \# b7 V0 Y: O- c/ K
, ?: J) o7 G: {    if ( nRoutinesToRun ). c) I8 i5 |! W# y. L. `
    {3 ]: I& \( _7 ~  f# x& g0 P$ h2 }
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
( }' X6 q2 m) P1 i7 C, G! c* ?        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
9 m% E) B5 B+ M) p% c                                            _NtdllBaseTag + 0x60000,* R" S) I2 W; {2 f# ]  W
                                            nRoutinesToRun * 4 );
4 q- Y7 e3 X6 K3 u! y                            9 ^+ \' `& |5 I) J. \
        if ( 0 == pInitNodeArray )    // Make sure allocation worked) ?+ K6 B+ D- Y6 L, l
            return STATUS_NO_MEMORY;
, x* ]2 ^# e2 W8 Q6 I" v5 z" {    }
" W4 X; ~# o* U6 @4 e    else
$ n, F+ j6 q, f; b/ D3 r        pInitNodeArray = 0;
! q5 q. {* M6 O1 }) p
& q* B4 g3 `$ H. i    //第二部分;5 i% J; [8 h) z. Q+ S$ T" N+ u
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
& v3 x- S* w/ @: I    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);5 v& P* L9 F  P/ f! k
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
9 B8 [9 X6 n% O, V& ~5 q        
+ V9 T2 `' \, A. ^! \    if ( _ShowSnaps )
3 {% N! T0 g9 D' y' h5 p    {
( K7 b4 l, r7 H1 t        _DbgPrint( "LDR: Real INIT LIST\n" );
4 O7 Z5 M+ Q+ b' a1 w* l. G3 G! B3 Q! d    }* ]/ g( P" Z7 ?+ {

$ q1 m# K- E7 A3 w9 p2 E    nModulesInitedSoFar = 0;1 M$ S. @' M8 Z- G6 z/ F$ M+ _0 S

0 Y+ I: E% t1 V2 {    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块% J% z& S9 ]$ A* D: H3 Q
    {
& S0 e# r3 F" s8 `5 G        
& X' {9 l; E3 m4 H2 B* P        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块- |* f9 u. S3 z: T/ {" J% I
        {
* c( l! F% w) q( m            ModuleLoaderInfo  pModuleLoaderInfo;9 Z- j& C- T/ ?" Q
            
+ ?. E! \) z: H1 W0 e, O/ e            //3 g9 r/ n% T+ L' r
            //一个ModuleLoaderInfo结构节点的大小为0X10字节
& S9 s1 F2 E5 J            pModuleLoaderInfo = &NextNode - 0x10;7 v$ K: h$ {2 J7 N2 O% [
            
: ?7 i' M" N( M3 \4 P7 ]            localVar3C = pModuleLoaderInfo;         
* e# t! ~: A2 `( E" e1 }  ^  G) }# k  j4 c
            //) \2 k. V5 t1 \9 g: w) H; ]) A
            // 如果模块已经被初始化,就忽略
9 C" o5 G' q9 Q: E            // X_LOADER_SAW_MODULE = 0x40 已被初始化
: V; {/ B: T3 _( l/ \            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )- V  j* [  ]  Z: r3 t
            {
$ Y  Y; d% u4 ~5 ?, i                //8 p( ]: D9 Z) ~
                // 模块没有被初始化,判断是否具有入口函数
9 A# l& z& q; e' E8 X+ e( m& f                //, Q8 t4 l* X9 Y$ Q
                if ( pModuleLoaderInfo->EntryPoint ), {) u" C7 k& E& H" A
                {
7 N0 }7 ~. \" i                    //- a3 x1 n. w8 U4 M
                    // 具有初始化函数,添加到模块列表中,等待进行初始化
7 A  |" }, _* G                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;3 u5 V" H" F& h# I- D+ Z2 Y8 d

2 o% M, I8 p1 ~* c                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
; I, k  X$ X  O      // 例如:
* [% m/ j6 Z; a) `% \                    // C:\WINNT\system32\KERNEL32.dll init routine 77f010002 x7 Z. S/ v9 {( _% n- m  v
                    if ( _ShowSnaps )
5 t; o* o/ U& f9 R% o: ]+ P6 Q                    {. w  Q" b7 l- ]) f; k0 w) Y
                        _DbgPrint(  "%wZ init routine %x\n",
$ W" Y. r" V( \# |                                    &pModuleLoaderInfo->24,
2 A* [' C- d# C) e# P4 z                                    pModuleLoaderInfo->EntryPoint );0 O. o$ K% u6 x0 K0 g
                    }
: N; i/ q, i7 V* \3 p. S2 F. f
                    nModulesInitedSoFar++;
8 d& Y+ p8 }% p6 E  Z8 f# P+ V                }' b; K0 w# k% b* K6 T) f# b/ y
            }# ~: Z% P" P# y( ?' q- }" {$ _% C

$ }% B# m0 R! k            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。- f/ }+ l( d! c" o$ ^% [  U: E( n
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;5 G0 p5 C: H' E% D4 J0 k  y# R4 @
- H; O1 {" u5 J& z
            // 处理下一个模块节点, R1 F8 W8 d) |3 }$ s2 z3 o. f+ `
            pCurrNode = pCurrNode->pNext1 U% W) c% X# o8 w% X4 x
        }8 ^4 O4 m% W9 m4 S1 R5 d! p
    }
6 o" J1 v6 M" ?& Z( M" g    else$ t3 {; z4 e8 T
    {1 Y  A/ v. |* \, w3 J9 G
        pModuleLoaderInfo = localVar3C;     // May not be initialized???9 l/ l) V- e. h. q/ h1 U/ Y7 V
    }, m4 R3 C' y) B1 k% t! I7 e
    0 Z8 q5 y: s, H# A
    if ( 0 == pInitNodeArray )( K" i9 ?0 j/ y- o$ |
        return STATUS_SUCCESS;. y( F( L( g3 `+ G0 U
) |& W2 H( X! J3 u! {' G1 U
    // ************************* MSJ Layout! *****************
( @& T# D) b0 ?$ v    // If you're going to split this code across pages, this is a great& v- }; a+ N, r. D
    // spot to split the code.  Just be sure to remove this comment, S, d; B' Q# r2 m
    // ************************* MSJ Layout! *****************
* C' R* v$ Q  i7 C6 ]   
1 E% h0 W1 Z# I    //: g3 }/ L8 a. b& M8 P
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
. d9 N8 D/ [* D* M* M/ a: `    // 第三部分,调用初始化部分! w# u3 f, e% ^% ^
    try     // Wrap all this in a try block, in case the init routine faults
" d3 W% V" S3 m    {
) U1 }: j$ G, u6 I! k8 [6 A        nModulesInitedSoFar = 0;  // Start at array element 0+ Z' ~+ \4 T6 f7 H1 [4 m# H1 C/ F1 M
- O' R3 J5 b1 U8 l; j9 l
        //& z# P( l; t2 [* ]
        // 遍历模块队列; M& a) x& G6 R" J
        //9 J& J3 ~7 p! {4 d; W: B; Y
        while ( nModulesInitedSoFar < nRoutinesToRun )
$ E1 z, P" p; K        {4 t9 C& X) G& g6 k9 _6 t. L
            // 获得模块指针, J( c% P& ]0 j; @! M
            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];% n% c$ k) n2 Z
6 J  f% E# V: l
            // This doesn't seem to do anything...
9 A& M* T& i6 R; B# l/ ]            localVar3C = pModuleLoaderInfo;
0 y$ R' c( `8 M            
/ R! l: m8 ?  f3 m- M6 d. }" k            nModulesInitedSoFar++;( F, i; ~* b) s4 Q
               
7 ^5 M6 t& A9 C* ?: g9 v6 J            // 保存初始化程序入口指针
; {; P2 E9 t7 Q5 _. [7 P            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;2 s  I7 E$ j! [2 m7 k
            ) N& g' `  ^7 ~- S& j3 W& [" D
            fBreakOnDllLoad = 0;    // Default is to not break on load/ v8 b1 o" t0 C& }
4 Z6 X" C+ Z4 m# a( d
            // 调试用
$ a5 }0 p6 d8 U            // If this process is a debuggee, check to see if the loader+ M- B, @" T2 R/ b' M  b6 g
            // should break into a debugger before calling the initialization.& P. L9 Y. J4 L2 \5 ?. F
            //
3 ]7 ^/ c) q$ G            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()
! h' t7 G4 ^3 E! ?, a            // returns. IsDebuggerPresent is an NT only API.. E8 s7 B- i, I$ E8 Z+ |
            //
  W6 l) W* l5 m; Y+ a6 S& f) C            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
+ v& u' d6 C7 M' B, \% u3 b            {
! Q) H5 B1 t& j                LONG retCode;
6 S% H  ~" m! ^, R. \
1 B5 e) h# C: I2 s3 Q& p                //              
6 A6 b# x9 ~- f$ n; ~. s8 ~- S$ T* t                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\. a; L# M  y2 T8 I
                // Windows NT\CurrentVersion\Image File Execution Options"
( ^* X# {1 q% ^; m0 o3 R* X. l1 K: }+ H                // registry key.  If a a subkey entry with the name of$ P& k; u4 s7 [# e( L
                // the executable exists, check for the BreakOnDllLoad value.
  o7 E6 r. ?  X0 F0 F                //" e- e" g9 D# i4 }) c
                retCode =
5 I- t- r+ d. Y$ T                    _LdrQueryImageFileExecutionOptions(
* b- \7 T. `  h1 i" A2 R                                pModuleLoaderInfo->pwszDllName,2 h9 @) R0 _, m- k
                                "BreakOnDllLoad",pInitNodeArray
) \1 ~; r6 s! D: Z; i                                REG_DWORD,: O6 g: r! j% O( }1 s( S8 _
                                &fBreakOnDllLoad,
+ g! ^# K' C) U" F( k- O# Z5 w/ q! K                                sizeof(DWORD),# h; \% s; {$ e1 d
                                0 );6 e& J0 i( m% l2 o. y

5 t1 f5 h9 i" Q  o* P1 [/ z                // If reg value not found (usually the case), then don't
2 B& A  H7 z# U                // break on this DLL init" E2 B" C( z' u6 U% [$ M' A/ \1 w
                if ( retCode <= STATUS_SUCCESS )
. [+ G2 K3 g; e* {; v' ?                    fBreakOnDllLoad = 0;pInitNodeArray, ~5 Z4 m$ x. f1 T, k
            }: I  B) A' r; S8 @) ?
            9 F9 B2 e' K7 |& ]) Y9 q1 Q. Y
            if ( fBreakOnDllLoad )
9 p8 b6 m) t, L  F0 R5 @$ z; X! g& e            {           ( X- I# P8 A5 C1 U6 J
                if ( _ShowSnaps )
8 {& z9 P2 @5 h                {
* T! K3 L4 \' Q" c                    // Inform the debug output stream of the module name4 W4 ^7 S# a9 y3 s4 I2 ]
                    // and the init routine address before actually breaking4 O- @5 x0 J% `' C2 p
                    // into the debugger6 g% i$ t: }  [6 a  H7 d4 k

+ X6 P6 c4 O. G                    _DbgPrint(  "LDR: %wZ loaded.",9 R+ {& Z: B: F$ w1 I
                                &pModuleLoaderInfo->pModuleLoaderInfo );
; j9 [; v4 m; }0 I) i) p$ [! O0 V                    
5 q4 X& M: j& C  Z2 T                    _DbgPrint(  "- About to call init routine at %lx\n",
: a% b( M9 h! n# v8 M4 _& }                                pfnInitRoutine )
, O% r& t3 U' w                }$ O: c& Z' _4 v- ^$ d& Z
               
8 i) c4 l* M! P' D. A                // Break into the debugger                              $ J" A' }7 q5 |+ v# z' w
                _DbgBreakPoint();   // An INT 3, followed by a RET- ]# a1 r' [" t: t- y. N
            }
' [$ V! Y- R0 n; u- N7 l. e            else if ( _ShowSnaps && pfnInitRoutine )
5 I4 X, g7 `$ L4 y2 l            {1 e  o6 D2 x, y8 S+ X7 ^
                // Inform the debug output stream of the module name, G. F' W4 j  N. l1 _, u* ?
                // and the init routine address before calling it               : k; H/ x6 s) {
                _DbgPrint(  "LDR: %wZ loaded.",/ G# v8 p7 s& W9 M
                            pModuleLoaderInfo->pModuleLoaderInfo );
6 F1 g/ N$ g: ]+ e8 l5 o- V5 ^+ A1 l- }4 L
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);6 ?& V7 i- t7 ~- z  i; r+ {
            }
( _8 B8 z0 t# O" t                    
. U! q1 A1 k/ A, k: t# T8 W9 g1 J            if ( pfnInitRoutine )" A8 U, c+ V) ~# R6 O
            {
; L* Q  I! E8 m1 y" _9 B9 h                // 设置DLL_PROCESS_ATTACH标志" q# @  ]/ z! P; C+ i9 L
                //) G' G4 G* `) g! l% \
                // (Shouldn't this come *after* the actual call?)
0 a: Y- U4 \- n                //0 s( K! D1 ?# G% Y% m5 \% ?% g
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             % f8 i8 [8 o( h, h( S3 p( c
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;/ H, Q8 h9 X! u  x: V

' w* e/ a* M% z* @                //+ j) V* t* E" w- o" |
                // If there's Thread Local Storage (TLS) for this module,/ ~3 S; M. f  k
                // call the TLS init functions.  *** NOTE *** This only
% [( h: C8 F. b. Y+ c  d  H* U6 J                // occurs during the first time this code is called (when3 V  m- H$ Z/ n" \* E" Z& ^+ ~
                // implicitly loaded DLLs are initialized).  Dynamically% n1 b% z4 @8 n: K  ]/ v
                // loaded DLLs shouldn't use TLS declared vars, as per the1 O/ x+ Q0 i* r% q
                // SDK documentation
" e" x7 \: x& U4 ~                // 如果模块需要分配TLS,调用TLS初始化函数
8 d/ L% L# y" z4 I! e( h# U7 Y  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
8 \; j+ {$ a+ }3 }1 a- P  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
0 m# w4 L  u5 f( ], W; a$ W3 R' H0 T                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
5 t1 V# k. R+ H  f, `; p7 u                {$ H3 p, }2 D2 `
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
" M+ r& P$ L/ e5 i6 A. D; y1 t                                                DLL_PROCESS_ATTACH );2 g" r' Q6 H/ o: I2 {
                }
2 e2 q2 W- N3 S" i9 L2 `* D% A' q                # M) Q9 i" m$ A
" [, P, A2 Q7 N
                hModDLL = pModuleLoaderInfo->hModDLL
; c) }: o$ m. P. U5 y5 D2 E4 x; X* {" f
                MOV     ESI,ESP // Save off the ESP register into ESI3 w% }2 I& \) ^' e' b
  & g6 k* i0 R" W/ v  A$ h
  // 设置入口函数指针                , q% ]" ?/ c4 z$ O; P
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     9 p" a# S! V$ c6 h3 s

4 ^8 r) Q1 W& o6 n( y                // In C++ code, the following ASM would look like:
4 ~- ]0 ]4 p0 `. n) E                //) [' R- G4 g; }2 K# S. q! W
                // initRetValue =
8 o  p! H& y+ u- A, j                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
, j1 t- o) z+ ]/ _% x                //
  i- H4 E# E% |* j( l# e$ K; O% ~, C7 O# {* N: w% F! k& l8 N
                PUSH    DWORD PTR [bImplicitLoad]# F; s, M; _- x9 x2 a
                - p! B; z+ Q0 ~1 N1 X
                PUSH    DLL_PROCESS_ATTACH( V$ b4 `. m, J! L/ i
                ( ?; j5 U% L* J: p9 `. A% O0 |% W: D
                PUSH    DWORD PTR [hModDLL]# G8 |4 ]( h* \+ V8 f9 O
               
9 d1 o/ ^4 O% N4 P' @                CALL    EDI     // 调用入口函数1 [* b& z1 J7 X
                % W2 n' f  O! H# h
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值7 `% a& q, i3 V- A
. y* J4 u2 c1 k, }& H! r
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the
& Y/ w% `# `. ^: }* O! K3 B                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns! B% ~+ L; m" P0 T5 _, L' ?
9 _8 J* L6 {' S- ?+ {
                MOV     ESP,ESI     // Restore ESP to value before the call7 ~8 a$ V; [& B* ^% s

$ y* A  @, x' h4 |. o1 m                //
7 v9 A4 e: N$ g/ u                // 检查调用前后的ESP值是否一至
6 b7 l% }& H/ i  //
! H- O! [4 x, `4 t5 \& T                if ( _CurSP != _SavSP )
% j) e& U4 M: M* J  L5 Y                {( I8 E) b. u6 ]5 O3 ]& C' p  I2 K
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
1 o+ z' v0 @9 R# Y' [- P5 J
2 i6 B- @& J" m/ Z3 ^                    hardErrorRetCode = 7 j1 @8 d9 }% w  d5 e: A/ ~
                        _NtRaiseHardError(
$ p" Y: D* d* X/ u4 y3 x                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
7 D3 Q! I# U$ S5 Y( p8 X  n4 ]7 f                            1,  // Number of parameters& w4 P# @) H- I1 H# n$ ^/ ~
                            1,  // UnicodeStringParametersMask,) t6 C  x5 `+ ], f2 a9 o
                            &hardErrorParam,* q3 K5 Y1 r2 U4 j
                            OptionYesNo,    // Let user decide- s* Z9 C8 b, S9 l( y. `- _1 V
                            &hardErrorResponse );- G) q! z, o* d2 ?
                                            
0 y7 b: [( a& T1 M3 c$ N/ C                    if ( _LdrpInLdrInit )1 _4 p( r8 ?9 P5 y' p
                        _LdrpFatalHardErrorCount++;
7 z6 D# {' P6 r
$ f( @: I: d8 W" w+ m2 b                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
' Q! |1 T6 l7 T; j( ^3 g% ~                        &&  (ResponseYes == hardErrorResponse) )
0 t9 A# x8 B9 g" F8 C                    {
0 o7 u" B1 E+ y, z% ?                        return STATUS_DLL_INIT_FAILED;
8 O6 V# R" i+ T                    }
. Z+ L% f# Z# z; ]. ]  g                }
4 h' ~! q' w! Y. _6 Q! h6 @8 \! `" `) W; g8 S2 w
                //
' p" f- {# a8 p$ d4 Q1 N. q                // 入口函数返回0,错误
" r0 j3 `& I2 [$ b                //1 V! l1 g) w, H( ~1 t; q# d
                if ( 0 == initRetValue ), h6 v0 B- D  p
                {1 @- G0 c2 u6 G5 R) l* P  z
                    DWORD hardErrorParam2;" ^4 c3 ]* g$ ^2 a$ y) J
                    DWORD hardErrorResponse2;
9 l+ \& _6 B/ Y' W: h                                       
0 a2 I& `4 b" A8 ]( G$ Q" c                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;  ?' u, }& f1 P* y* G; q
                    + a; [7 Y- W. G  w2 F( e0 e
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,! V0 p; E2 B/ Z( ^" W! Z
                                        1,  // Number of parameters
$ s. Z3 p$ O1 G                                        1,  // UnicodeStringParametersMask
1 F% G. k8 V- ]3 c2 U6 E: J$ H1 Q                                        &hardErrorParam2,6 T" B" H  ?# Q; T1 x: T) O( e
                                        OptionOk,   // OK is only response
+ s% s7 T6 v1 j( g' Y                                        &hardErrorResponse2 );( X/ w; R# Q/ Y
                                                            
8 d( B# f+ g$ z8 k, D+ ^                    if ( _LdrpInLdrInit )
5 \+ c+ I# d* n0 _                        _LdrpFatalHardErrorCount++;. Q* Z" U' b: D) Z2 _
% t  ]( ~4 F. w. k8 S8 Q2 I4 H
                    return STATUS_DLL_INIT_FAILED;
: D: E; B; f7 [  p                }
+ m( [: Q* Q+ C+ ^! f  G% I* {4 m; o            }% ~. k7 ~% m- r# R
        }
. C4 T3 S. a; s/ J4 y) N
/ I9 _0 F! q/ s& u        //" j: |. `0 i' r6 n+ ~/ K4 g' v, w
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时0 @( ^! {. N7 z# F& G. V# T' @
        //      - Q9 q% H1 N0 ~# S- m8 y1 h1 A* Y
        if ( _LdrpImageHasTls && bImplicitLoad )
1 d# T$ [% c# j; {8 @+ U! p        {( Q* f/ R  \; o7 d' d
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,7 O" m8 ?& }  K' j" P( t
                                        DLL_PROCESS_ATTACH );/ C% m4 x% @) x' b
        }2 K/ P. v$ a4 z+ o8 J$ {$ M
    }
' S. f% B- y4 w1 Z    __finally, Q8 @3 P9 R+ e) [
    {
" o5 R* n2 n4 r0 J8 ]        //
1 E+ r, W; F6 C% g        // 第四部分;0 \4 ?* @8 U; ^1 x
        // 清除分配的内存
7 f+ C6 W, k/ T8 G; \1 t; Q) m        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );% v/ V2 a  _5 E( B
    }
  w7 {. C1 c  Q6 _( K3 W1 o  W# o" j" Y' m% Y) F  ^4 P
    return STATUS_SUCCESS;2 ]/ ?, F0 [% ~& \4 ]* r: e
}   
: T6 n9 t) m5 ?$ J5 u1 b( `& Q+ H0 I
这个函数分为四个主要部分:& I0 v' R  R$ f( L
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
! A& M# N9 s; c5 s二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。
& E  y( V. f) m- Y, b7 o三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。. i- B% e3 p9 M- o/ e: a; `0 i& l
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
7 T! g) A) F1 W) S* ]8 Y2 t在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。9 E* }# D& W* {
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-7-20 08:47 , Processed in 0.085195 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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