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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]' B5 E1 o) L3 v. y' U; ^% i
( S! i4 E% A" {& M( P2 F
WINDOWS 2K Dll 加载过程- X0 u6 Q3 z  O0 J5 o
jefong by 2005/03/30
" z6 W. e* W. \& [这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。9 G: K( ]. `4 G, f- ^& q/ J, 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”。
: t6 h9 x! M6 _- ^! p# ^2 Q# _你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。. K( \) v' O; u8 {$ {
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。2 g3 J2 k2 `5 E* S1 l5 X2 F) q
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。" h9 q7 t0 H# @/ t0 z7 z
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
4 F* C& T/ O) \9 A9 Z  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):7 d& p: {9 x7 p9 ~$ V
//=============================================================================0 d# q$ Z, q( S  a" F4 F
// Matt Pietrek, September 1999 Microsoft Systems Journal
" n5 L  f9 a; d: I" ^& w! T; @6 R// 中文注释部分为jefong翻译
& M# _# i, p) s8 _9 J//# X5 F( h  Q: R, l, T
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3): Y. _! Q# }: G  {' y
//( P) h* _# o: D+ N' h: W
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;& _- o* J/ {; Y, C& W7 k
//=============================================================================5 ]2 @( v6 L/ I3 B, p1 t
; I0 o; [& L* `, K& I3 p/ E$ [! k% y
#include <ntexapi.h>    // For HardError defines near the end
" g  N/ S( z+ N1 g( A! Q/ U, F+ ^  H2 j. w. }7 H# B6 `+ z
// Global symbols (name is accurate, and comes from NTDLL.DBG)0 c5 i) x; i$ k
//  _NtdllBaseTag
1 X+ @* I7 N& @% P4 X! P$ N//  _ShowSnaps1 J" G+ ?  ~$ g) u" j
//  _SaveSp6 S% @1 Y) x+ ^4 R0 F# g
//  _CurSp( Y( U  s% @5 f* A& f
//  _LdrpInLdrInit
- }3 [$ e  V2 n! a! ?1 ?% R0 x//  _LdrpFatalHardErrorCount0 |/ A3 Y1 V' a* M6 T8 E
//  _LdrpImageHasTls
. k4 B8 q1 w7 u; ?7 B
% G! e5 j+ |1 z% b7 q: s8 ]NTSTATUS3 f1 N: g9 x# }0 O
LdrpRunInitializeRoutines( DWORD bImplicitLoad )# ?- J* H5 n2 q: P& K
{
9 z( ?# r' {" Y. c# p) E    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
3 E) Z) q6 U1 L    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();2 b$ E/ I7 x) l- r. o, \- w
9 a, F/ M5 g% y
    if ( nRoutinesToRun )* x! B) A6 P9 x
    {
( h, q& o; F( c' j6 J        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。' `, S6 h& }- e" B0 K! F
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),- a( N8 n5 `: E
                                            _NtdllBaseTag + 0x60000,; T$ [5 e1 s4 W/ ^5 C8 N
                                            nRoutinesToRun * 4 );( x8 K" _! w3 C4 i
                            6 J' h$ K- q, u/ j: r0 x* G
        if ( 0 == pInitNodeArray )    // Make sure allocation worked2 n8 w0 |5 |4 }8 S
            return STATUS_NO_MEMORY;
: e0 y/ n& V5 J( e1 r( e! G    }% R# y: L1 X; Z! E: k3 x& ]
    else
/ Q" n$ v5 ?' \, ]2 L& \3 A5 x0 f: j        pInitNodeArray = 0;
8 e0 Q* j6 e7 _) X3 C' _
( y: v: Q8 ^* N% y    //第二部分;. F) {# p# P( e. q) r
    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
: M( Y, J% X0 d- q8 V( y: w" Z    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);/ t* u! B- b" X+ Q. [5 n9 e* t- j
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;
0 i! H4 p, R4 H8 ]: [( u        
6 e$ y- W/ Y/ O, l/ i; `    if ( _ShowSnaps )
+ N5 H; M9 A3 T    {
" [- v1 e9 F: t" u7 O# U        _DbgPrint( "LDR: Real INIT LIST\n" );
" ?& r6 h3 \! q9 ~$ o& D    }
* {& S. W) p3 S. g, A. F# W- j4 s9 w1 s# ~& B+ D4 u
    nModulesInitedSoFar = 0;, e, L1 p4 l' |! W. o

( @0 _4 k. d: ]    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
0 Q* A( u! ^% S8 k7 g, G    {
9 H7 W0 U$ V0 s; |* T        4 n( b; E2 i/ p  K! m
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
6 q& m8 c/ i- T' e4 I! N2 _- e7 h8 s        {
9 h( X5 z  a1 p/ g            ModuleLoaderInfo  pModuleLoaderInfo;
. `# M" s8 [, q1 z$ {% z            
" s1 H3 r; H; F. F            //& B- Z& G4 X! E* X. e
            //一个ModuleLoaderInfo结构节点的大小为0X10字节
7 z+ i  R4 o0 G9 v$ P7 w: S& P            pModuleLoaderInfo = &NextNode - 0x10;/ s% e  J  _3 ?" @2 S4 k4 @
            0 z' G3 A5 X9 O& M6 d: W2 |, N
            localVar3C = pModuleLoaderInfo;         
  [6 R* z  ?: M: |/ o/ O
. r; Z! |# R( x            //0 L7 ~2 r( C  O; ~% i
            // 如果模块已经被初始化,就忽略
' v: s2 N; j' p            // X_LOADER_SAW_MODULE = 0x40 已被初始化7 v8 L  y( g" d2 t- n
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
  V0 W" _" E, N; o7 H- D: S            {% z6 C2 Z$ N' p& l0 V
                //
( C3 h& \2 p2 ^                // 模块没有被初始化,判断是否具有入口函数3 `( F2 H4 a6 O5 q
                //3 }' X+ ?  Z. R# a
                if ( pModuleLoaderInfo->EntryPoint )" Z( z. W" C. n5 i% G; ]
                {- s# ~' j- b8 p2 @3 a' p
                    //3 @3 @9 _+ R3 W+ Y! @! U5 Q6 U
                    // 具有初始化函数,添加到模块列表中,等待进行初始化& _" z: n& S5 u& }
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
0 D0 z! n( l# t* e8 H: p6 k7 g6 [+ d1 W$ H3 ~  u/ P! d
                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
. C# E0 [/ j; m# ]% q      // 例如:; a. o; n- J( \) H
                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
% k! G+ t& ]( t/ s! R                    if ( _ShowSnaps )
8 ]% c6 |7 T! ^& g  |6 {9 [                    {
$ L$ M% e* E  C( l! r                        _DbgPrint(  "%wZ init routine %x\n",. }  C% p: l7 O+ ]8 D
                                    &pModuleLoaderInfo->24,
1 V9 e( C  U, h2 J/ a8 D                                    pModuleLoaderInfo->EntryPoint );8 H) |$ G" Z9 ]1 ?6 Q' W
                    }
$ I; }) |) N$ ~2 \* @+ W
$ v, ?6 W& J6 ?  ^9 i* k, q                    nModulesInitedSoFar++;; ?7 Q- I' }' P' j
                }
6 H. f+ {0 P) L% Y            }) m2 x$ H! I2 N" w' [  u! R

3 T' Q$ B3 c4 ]            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。* o/ p! W, _( o; K! x+ g" h
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;( }/ A, }! P- h3 h: _/ r

) i2 w% M: {# g            // 处理下一个模块节点! x. h, H# I2 H/ {0 I* n
            pCurrNode = pCurrNode->pNext
! Z- A& R! X# F2 X1 _) b0 }        }2 g4 o) [* `) j0 x
    }
* X9 E1 K! l9 H1 |( l5 q$ W    else' K3 g; `! M1 @$ n# l
    {
& J' n! J' Y, A        pModuleLoaderInfo = localVar3C;     // May not be initialized???7 N: f: q9 w# o
    }
+ [+ N: X) Z& E# S; D4 u' C   
% u1 Q9 q5 c' G$ j$ y    if ( 0 == pInitNodeArray )
4 W& r* B# ?/ F# R* p        return STATUS_SUCCESS;
* v. r( B% y' K1 C; [6 L
; o9 O; H: i; J( R4 d- i1 a    // ************************* MSJ Layout! ****************** G4 m$ I# n- _5 s5 z- }
    // If you're going to split this code across pages, this is a great
0 s1 d' s7 ?) M    // spot to split the code.  Just be sure to remove this comment& Y0 W5 [* C3 S
    // ************************* MSJ Layout! *****************9 G( }0 @( e. h
    ; |! W; V2 S" S5 E
    //# C) o$ c( Y# ]0 c) M* n
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
( B8 B0 X! l" h* e6 S    // 第三部分,调用初始化部分" J% c1 X; u+ Z5 H
    try     // Wrap all this in a try block, in case the init routine faults
( O0 O2 D4 {2 a" w    {: k2 e" S+ h* z4 V  H5 d6 o* |
        nModulesInitedSoFar = 0;  // Start at array element 0& C( {( }3 o, R

, F! S2 p& B7 c! I6 x        //; a5 G( {/ w$ c& `( {
        // 遍历模块队列$ Q* y: q- C3 S" q5 v; J
        //
6 P  Y$ ?7 p( e# H1 \        while ( nModulesInitedSoFar < nRoutinesToRun )
: Z) x' ]( H/ N! h: C) |& c* `! m        {1 N$ U/ t  r& [% k) S
            // 获得模块指针
1 D3 y# P1 y- u7 J            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];- _5 \) a. j6 J7 U6 Y( s
! Z8 m/ y1 N+ W6 _
            // This doesn't seem to do anything...! R( \* [7 g( @' J% W& {; q
            localVar3C = pModuleLoaderInfo;
) B! @8 m$ B# i* L            
6 `. I8 e: D5 H- @8 d# X, D; ?            nModulesInitedSoFar++;
6 Z0 D9 W) b  N# b               
* F9 Q, [* n/ t/ \0 ~# x            // 保存初始化程序入口指针  E7 r4 j# e; ?. H7 V* a0 I
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
0 Y. N! o6 Q8 q" m7 L, }& U            * @1 \) e# ^; A, s9 P9 w6 {
            fBreakOnDllLoad = 0;    // Default is to not break on load5 D$ p+ |4 s/ {1 Y, `
" d5 i( k$ t8 |& F- p
            // 调试用
, W! |" ~/ P8 ~' u8 t$ R            // If this process is a debuggee, check to see if the loader; g! G/ W0 o% D& k% V' b
            // should break into a debugger before calling the initialization.6 M) [4 w4 T) r5 W0 i4 H; W4 b5 ?- v
            /// _8 o6 `' @" @
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()4 \6 n8 y3 A( b$ D% N0 L( J# n0 C
            // returns. IsDebuggerPresent is an NT only API.
" s  ?9 j/ c( ~9 T$ ^; [, k* n8 q: I            //
  H5 @) a  M+ Q! W# ~" u9 t  m            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
" L- I* e4 V% m- \            {5 @8 ^& K, T* i
                LONG retCode;, m9 T. P2 Y) X! U* f% P- N2 T3 q

7 `( q( {4 H% h$ I1 T                //              
, R. `# d$ W3 C4 I, ~" N3 n5 d                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
! N) Q, g) Y$ p  K) \5 Z                // Windows NT\CurrentVersion\Image File Execution Options"
7 S8 g* E, v2 }5 j1 c4 s                // registry key.  If a a subkey entry with the name of+ {6 ?$ `, z2 _( C2 V
                // the executable exists, check for the BreakOnDllLoad value.+ l2 N: D8 V+ d
                //
6 N6 U9 |3 G. u/ I                retCode = $ u1 N, L' _4 W- F' B7 x; `
                    _LdrQueryImageFileExecutionOptions(
9 ]: r3 z4 O2 u5 Z                                pModuleLoaderInfo->pwszDllName,2 v" \& B" r) ~+ M7 s
                                "BreakOnDllLoad",pInitNodeArray' b3 L# M3 X6 i& S" r
                                REG_DWORD,
( j, R6 H0 a0 X- c& J0 i                                &fBreakOnDllLoad,
. M3 j3 q6 }1 h9 I9 F6 C" m                                sizeof(DWORD),1 o. L3 V8 F/ f) c* {; O
                                0 );. ]0 P/ ^+ c& [; A5 W

* O% l7 L7 G' z                // If reg value not found (usually the case), then don't
$ j! \, d9 I+ L                // break on this DLL init
- c  r, O$ X* n4 }3 _, k                if ( retCode <= STATUS_SUCCESS )
: k! ]: i' d# f# s                    fBreakOnDllLoad = 0;pInitNodeArray
  l9 k# f' l7 c            }8 l0 T& M; D9 Q% ^+ s9 d
            ) [- c$ S8 K. B
            if ( fBreakOnDllLoad )/ ~; I8 ~. u, F7 @9 q  \$ ?
            {           
+ N# [5 U( S1 a9 t                if ( _ShowSnaps )3 F: A' S! [1 k2 z( ?% }3 w. T: x
                {
8 p4 L% B5 |$ ~9 J2 }% c% L                    // Inform the debug output stream of the module name. A- z0 K$ P5 V' a8 m3 ]
                    // and the init routine address before actually breaking% U0 S4 h2 U: a  {1 ?
                    // into the debugger; U, A1 f! d. j

, c, L9 T, t$ C- `: s                    _DbgPrint(  "LDR: %wZ loaded.",
( t/ q" S3 J: I7 K0 I% m                                &pModuleLoaderInfo->pModuleLoaderInfo );6 l( q) Z: ?# R/ P8 Z
                    
+ e! N6 s1 W2 n, C) E. z* }                    _DbgPrint(  "- About to call init routine at %lx\n",  h) }, I. _$ ]
                                pfnInitRoutine )
2 ?2 |$ D2 U8 z0 R* n- r, {                }0 E; w0 [0 h! m" P
               
7 d4 f; P# [/ Z* p" K                // Break into the debugger                              ! R1 U6 G+ s/ k9 {' K: p- \
                _DbgBreakPoint();   // An INT 3, followed by a RET
7 X+ y2 U  ]7 |# z            }
3 H+ f8 }; y' s, K: d0 Y' H" C$ F            else if ( _ShowSnaps && pfnInitRoutine )4 a4 I. U* @. t6 F+ w
            {" X8 Z! I" g# y, Z' x5 R2 A
                // Inform the debug output stream of the module name
9 ?& C% W1 S8 T! d8 h9 F* F                // and the init routine address before calling it               
( t$ {9 U1 l$ k& U& z                _DbgPrint(  "LDR: %wZ loaded.",, S7 m  o1 v9 L# h3 s* }- c+ `4 d
                            pModuleLoaderInfo->pModuleLoaderInfo );! Q4 K  A& i% ?  @% \! x
0 o( |$ v, D3 t8 N% W
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);0 b& t" `  |' U8 u9 |
            }
" A/ p1 V6 o4 l                    , q/ l% {2 D5 |) I3 s$ V
            if ( pfnInitRoutine )
4 i% N/ ?' l" K0 g4 A7 r0 b! J            {
/ l$ E" O, W7 y9 Q) D9 j6 @                // 设置DLL_PROCESS_ATTACH标志
( X5 K! P" u/ f# m% @/ m& q$ `# A$ C5 ^                //
7 G2 i% i/ _6 b; l2 c                // (Shouldn't this come *after* the actual call?)
* B. Y# f, ^, b                //. X4 k. Y) a  F
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
  U+ Y7 }9 ~- d5 U# |  u                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;6 }( ~, j8 s7 |% L9 j9 u: A( t

( D8 ?7 C. F$ k& b                //
4 j4 E( T: D' E% O" |. b  T                // If there's Thread Local Storage (TLS) for this module,; j& A7 v2 W4 L
                // call the TLS init functions.  *** NOTE *** This only- A! x  m+ _0 `3 W) ^
                // occurs during the first time this code is called (when
  z3 [" Q* R& D                // implicitly loaded DLLs are initialized).  Dynamically
; I/ S, A1 m5 B                // loaded DLLs shouldn't use TLS declared vars, as per the2 h' C8 ^, w! m; Y, V
                // SDK documentation  F$ y+ R! ?1 B
                // 如果模块需要分配TLS,调用TLS初始化函数
$ g* G! w0 s/ e: ?) r9 y. g: b  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
+ j! M$ A+ D) h  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量, @5 p, g) b: W
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )" K* k, Q. h3 K; t% ~2 G
                {
( n7 G; ?4 ]+ {1 A, n( _- t" I                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,3 R6 m+ n) q" a; S( \
                                                DLL_PROCESS_ATTACH );
9 H' ?* |6 l4 G9 N4 h                }
  W2 i4 C$ O1 W: W& R% b9 L- M" L6 e               
  x  Y- x# b3 I
& V* y, {; t, a' [2 F6 A                hModDLL = pModuleLoaderInfo->hModDLL
1 }# b1 E! Q: }- r8 H% ?2 s! ^
4 b. t8 v: q9 I                MOV     ESI,ESP // Save off the ESP register into ESI1 h' V6 q- S3 u$ R8 i3 ?
  
4 h* z; y. k" \' p  // 设置入口函数指针               
+ `$ f5 ~0 ^* V1 o# z" B, O+ K3 ]+ z( Z                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
! ]) s/ Z# o5 G* U  H" h( T" q* ]6 f; L5 H$ A0 A5 ?  q' c( z2 K6 ]. d) i. g
                // In C++ code, the following ASM would look like:
- F5 _1 V* L: {3 v                //
( S" {+ }7 B6 K                // initRetValue =! m! G8 T% v7 O! ]/ V! f8 u4 }
                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);5 d  u3 U6 R! x$ W
                //) v# Q/ ?0 k/ F% K, d: f
3 l; j, b1 k! }' n2 D% o
                PUSH    DWORD PTR [bImplicitLoad]
/ n2 s- c' ^4 L1 F* @  s               
' c9 D7 u: O( W" J) I$ w4 r                PUSH    DLL_PROCESS_ATTACH
: ~  o, j$ R  L$ e- w/ A2 o2 p               
" ]8 O4 s1 t6 k& `                PUSH    DWORD PTR [hModDLL]
* @' O' t5 I# ^  X                , Q/ h+ F0 Z: T* h
                CALL    EDI     // 调用入口函数
3 K( M! U: ^: Q+ s                9 C* s: S  m7 k
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值
' ]. R) R* H2 p( E5 z1 h' S
7 _9 f# |1 y) b. G* s& }3 q& s( p                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the( e0 O9 d- J4 _9 X& K
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns) Y$ W+ f* Z/ `! T/ S. K
9 o8 n& W  U. Z
                MOV     ESP,ESI     // Restore ESP to value before the call
* N4 j0 @9 c% n4 A; D$ r, G& l8 t3 e
                //
0 V: T1 y7 \/ H+ l7 W                // 检查调用前后的ESP值是否一至
' L7 y6 j, i( u( ~- \  // 2 S2 s* n1 q0 m
                if ( _CurSP != _SavSP )
+ N7 J$ @7 ~( t' U                {  U, d" g/ P! S  S4 ~: v
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;. D& s5 |  `8 `, w

; h6 J9 M: s% u/ j                    hardErrorRetCode =
* G# z' K1 h8 g5 m                        _NtRaiseHardError(
" j# W* y$ O; J# P2 G                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
# z& U6 P; U9 a4 C2 u                            1,  // Number of parameters+ C& R5 J  C0 O7 S% d
                            1,  // UnicodeStringParametersMask,
$ W  s. A% h7 I' z8 x6 N                            &hardErrorParam,
1 G1 O1 s/ g8 W6 W  N3 E                            OptionYesNo,    // Let user decide( u, A- \& k% {% T" j, R
                            &hardErrorResponse );5 a. E1 e4 J# i& o
                                            
, C1 ?+ b3 k; f1 ~; b9 w4 z* q                    if ( _LdrpInLdrInit )
5 A) e. q8 p- F$ S& M                        _LdrpFatalHardErrorCount++;
! X# h1 |) _$ Z# u% f* D( }2 q3 U, m; k! T0 h
                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
: |- P% T2 ]9 V$ q3 p                        &&  (ResponseYes == hardErrorResponse) ): h. E* h; L- ]
                    {
- i2 W' E7 y* l" [$ Q  A& S                        return STATUS_DLL_INIT_FAILED;6 m" g# Q+ T4 h
                    }
/ z1 d7 x4 g3 P# }3 Y( [                }/ O# {; j. c4 ~* u( G
& _2 h( S0 `7 W6 p, k8 k3 o4 N
                //) B1 M9 ^2 q3 A0 w3 k
                // 入口函数返回0,错误8 S7 W$ r) ~; n4 }
                //& t$ B6 `! n! W
                if ( 0 == initRetValue )
- ]( R% a. y4 E9 x  }4 Q! h( |' A                {
/ t" q( {4 N+ S# C1 l                    DWORD hardErrorParam2;
3 c1 N. R3 r0 Y; s                    DWORD hardErrorResponse2;/ y" y& p4 {6 `0 j! I- \: L0 L8 I# b
                                       
) ?/ B' y4 {, ]                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;2 W7 k0 a5 J0 r* M  e$ w1 C
                    1 i8 Y, O+ ]7 q& ~: @, V5 v
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
* |' K- T: T$ ]$ w                                        1,  // Number of parameters
/ d; n8 u  I; n2 N+ j5 L7 a3 e4 s                                        1,  // UnicodeStringParametersMask
# S' P: W" ~+ M4 \2 M: C* g/ K0 \                                        &hardErrorParam2,- o2 A2 G3 ~2 K8 X2 M$ F8 G. k
                                        OptionOk,   // OK is only response. r+ J- I- D' D* g& l: i& D  J- Y
                                        &hardErrorResponse2 );
! [9 {. o; B, @& ]* ]" |                                                            
% v: h6 I7 [' v% q4 e" F4 t                    if ( _LdrpInLdrInit )
2 }9 y0 {8 o' L, x7 g6 p                        _LdrpFatalHardErrorCount++;
" r. A- E2 P4 A' {" L& |0 }& b2 p
2 Z! Z% _  z5 p                    return STATUS_DLL_INIT_FAILED;
7 z2 t# C2 p9 a; t                }% C! z) x! m+ ?! B
            }
: N0 K6 G: M1 h4 z$ y6 q        }: p! Y4 [% ~7 d; g4 C

' P9 b1 c& l6 Q" g! J( f( F( {        //
* z& J% ]* K5 e1 s& Z2 X; f        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时& z1 |# t8 s7 c: B) [$ V* f0 H
        //      9 g! v' K9 T. R) H2 w
        if ( _LdrpImageHasTls && bImplicitLoad )
8 f$ ?6 m) w# H5 ^$ q        {
: U3 T( _8 I3 W* _; J. d            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
' p& p* Z. ^! o0 p' w0 U                                        DLL_PROCESS_ATTACH );
. z0 F- j- B6 W) \% [* Q5 R        }
! \4 ]! D* i; a3 k3 y/ l+ T    }; X, t, w* M, N
    __finally7 ~, S( E+ G" d, \" _( Z& O) d/ U0 T0 Y
    {
' q, i. @0 T# c  R2 ~( s: f        //
3 ~9 Q7 v/ f7 A, Q        // 第四部分;% N) `* h% T" G3 F: @
        // 清除分配的内存* o9 _. {' w% e- r! L
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
4 C: a$ ]8 y1 R% l& B5 F+ {+ `. _5 y    }* O7 @+ k+ K3 W& M' r8 v% M* x

* _$ z: z9 i% X2 ]: G    return STATUS_SUCCESS;* k, u4 p0 ?+ v) L/ f8 ^+ y" K
}   . R' y, o& m; I8 H8 d" S
8 s2 U/ j# H5 {" K
这个函数分为四个主要部分:
) {) u0 b: ^. i& C/ F/ `( `- o一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。* \6 x5 @" U) O5 A& O* p% p
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。" T: B, w$ J% q' H  \+ u+ D
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。+ r7 S6 n3 A9 z( }1 |
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。% L0 ~2 }& @+ ]3 r% C9 N) 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。+ [; y  y5 W% X% |$ k5 a) B
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2025-4-5 00:35 , Processed in 0.032931 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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