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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
6 G" ^$ b* x4 `$ z! r% l8 l/ F4 l1 b9 c+ r$ M1 ]8 I' r) }* L! {
WINDOWS 2K Dll 加载过程- k$ z, k1 E# \% h( K
jefong by 2005/03/30
5 n' c7 |5 B% r* s5 W5 S这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。- E9 |+ x9 t$ `- n# 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”。
3 T5 T6 H( |& K) L: s2 F你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
. x2 p# y9 S" d8 g8 i初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。- F9 x: i& k& u$ f: o1 T
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
8 ~% R3 Y8 S0 I4 N9 n- y  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
6 j) V& O; V6 x  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
7 u% L: V: Q5 R7 G//=============================================================================& B- k+ D6 u0 m6 L* a+ I. k& L
// Matt Pietrek, September 1999 Microsoft Systems Journal/ j& F( b) s6 g, C7 r+ z
// 中文注释部分为jefong翻译, b+ b$ w* U7 x9 ?* t7 }6 ^- O
//
6 ~  a6 K# S3 T! G- y  h// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3); Y4 U9 O4 R$ a' z+ ^  }* r
//
6 g! ]4 c5 r# Y# G+ I// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
3 p3 V) L- b9 N1 h& K//=============================================================================
; W, B. x( c1 a  Z7 _' {$ d+ Y8 [6 f
& v- X; g, m: d; d8 @#include <ntexapi.h>    // For HardError defines near the end9 Z  q$ |( F4 h0 X: U( D
3 f0 d% O( l* S3 P- ?
// Global symbols (name is accurate, and comes from NTDLL.DBG)
% y  y% s, I! I//  _NtdllBaseTag
  R$ d1 m' G9 ]' x" ?" n5 N' l//  _ShowSnaps
# `6 ]& ?7 j+ _: |4 ]* i) q//  _SaveSp' J6 o, [' S5 P6 t
//  _CurSp9 t/ `( |/ s* `
//  _LdrpInLdrInit/ A9 z1 x; {2 _3 F5 s" M
//  _LdrpFatalHardErrorCount. K$ g* `$ I0 B/ C7 r! C
//  _LdrpImageHasTls
1 B' E" y% d2 T- o  `! X/ [0 F' H5 s+ \% h: ~5 h
NTSTATUS* _7 t5 O% E/ p+ M% y3 f
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
& @7 ~; _. G1 {/ F{# y5 J: y# p  r) H8 U
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了6 I# J1 s5 M5 r& R9 _: M
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();& K: {- Q" G# h3 w- O/ O; |2 M* V

' ^  v, a: W6 v# {) C: j! Q! k    if ( nRoutinesToRun )
9 Z( c* a  \& F+ V! c    {
! U) M5 u) Q$ b/ A9 l" h        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
1 Z/ M  J3 d5 V- O8 _0 M- v        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
2 t, j. ]4 |  }1 {: R% Y                                            _NtdllBaseTag + 0x60000,
% Z% I" k4 Y% K) X                                            nRoutinesToRun * 4 );
3 t$ m! c3 |& H                            1 }1 i2 P3 a2 [0 o
        if ( 0 == pInitNodeArray )    // Make sure allocation worked5 J. S' d# o9 v& \- z
            return STATUS_NO_MEMORY;
6 f; A5 V9 q& e5 v% s4 ^    }
. [& s3 Y6 k2 D- y# l8 ~7 h    else( f' {- {: k/ J* ?8 m% Q( R  x# G
        pInitNodeArray = 0;
. |$ v( B: M, d2 f" x" W: A2 o' z; r
" I: \+ s6 O) E! V2 B" P    //第二部分;
' O& Y4 R7 b. f; a    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。4 Q4 f- v3 Z8 y; p9 c
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);
5 c& z. \( c& k8 H$ C    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;2 ?0 P+ y5 m* x: N/ X3 p/ Q
        
7 h. p8 g$ w( {4 d4 [    if ( _ShowSnaps )
6 {" V2 `2 R# r! h" _9 g3 P    {
. l; a2 J" g/ f8 x, k        _DbgPrint( "LDR: Real INIT LIST\n" );) i# ]. e, I1 X# }( N- U
    }
9 l5 b1 w" [8 Y7 L
  U1 ?3 L- N! b6 U! ^* v8 W    nModulesInitedSoFar = 0;
, f1 p* m) A; C0 M9 a+ ^; H2 l2 \. x* J( `# ~2 j% b3 \3 ?
    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
6 u3 n7 M7 |1 S3 t- Q6 I7 C    {& ^& F% f6 v4 P! R+ p
        
/ Y& }3 w' r, x# u+ [6 [        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
0 D* u! G& V1 S7 t1 P  O7 q) M* j' z        {
9 W' s' O7 _( p7 S. R/ T: R3 J            ModuleLoaderInfo  pModuleLoaderInfo;+ g  C/ y0 u3 n, t
            ' m5 [1 q/ M: x2 t5 r
            //
  c0 H  B! ?1 c. G! y" |            //一个ModuleLoaderInfo结构节点的大小为0X10字节: z% l5 K1 A: G; F. t
            pModuleLoaderInfo = &NextNode - 0x10;% e$ n, a; N- |3 o! h, l7 V
            
% K* L. o8 c2 \$ D8 s            localVar3C = pModuleLoaderInfo;         
$ V9 V" R. {; H5 o6 t9 G+ o3 Z! y4 ]* H& u
            //. w5 R% R9 M, g: t% l3 W
            // 如果模块已经被初始化,就忽略, @1 I( }  \/ X
            // X_LOADER_SAW_MODULE = 0x40 已被初始化/ w  z' g8 w$ G. L
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
" G6 K" M: d' u) x) N, A2 f) ~            {* r/ O/ Y2 r7 s5 I8 c
                //: F3 v/ _8 f* R2 f1 R
                // 模块没有被初始化,判断是否具有入口函数3 s+ l1 c! H/ G" V* K) \
                //) l8 g1 t! h3 n( U) m6 ~! j+ L2 |
                if ( pModuleLoaderInfo->EntryPoint )
- r1 X" u( V1 N; P. A0 C* a0 l- C2 i                {
" J5 a+ W( f* U4 e                    //4 l/ c: W2 v. Y' B* g4 d+ m
                    // 具有初始化函数,添加到模块列表中,等待进行初始化
4 Y, q* B$ T0 S9 m% R                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;/ ^" t' G7 e9 Q. M: u! G

9 ^+ ^7 g) Z2 }                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址9 M" y* Z3 d. {# j* T. N
      // 例如:& p( E, w' ?. a/ T. T" w
                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000* i/ a/ g5 p: h
                    if ( _ShowSnaps )
" a6 Y2 h, a3 m' E1 Z                    {
, o/ U. p; }7 {5 ]3 j! l                        _DbgPrint(  "%wZ init routine %x\n",
5 y7 s1 p: g' ?1 T                                    &pModuleLoaderInfo->24,
6 s; Q- i+ ~1 u: y                                    pModuleLoaderInfo->EntryPoint );3 d. I6 h4 |7 s: y. [8 u. ?
                    }$ ]$ k2 i# v! h) ~! C" k7 S# _

# R9 N( {6 _# J. A) ~                    nModulesInitedSoFar++;
+ I( _: j9 G" N* h7 K$ ^! ?                }
" T7 u8 n7 _# ?+ r0 B6 C! E$ ]7 F6 }            }
1 Q; S: c) C3 A+ w  X
  q7 A& y% P9 ]0 k5 X4 z5 B            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。8 s: M9 `0 U. l* O; Q: I% x
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
* S' j8 F* r% y" P1 d5 o# s8 n% ~( i3 F2 m0 C' I4 ?" I( |" |
            // 处理下一个模块节点; Z, H& S" c6 f0 \. L. @9 p7 ]/ @
            pCurrNode = pCurrNode->pNext7 M6 D! n4 D( v; ~6 I
        }) s0 w7 ~5 ~; `- t" G
    }; k' x. i- f3 f. {$ ^2 A& K
    else1 C0 Z$ k0 f6 B
    {
, _$ }/ c! p( \+ N        pModuleLoaderInfo = localVar3C;     // May not be initialized???. O& y0 O  g0 d
    }+ P" b, R4 \+ B! B2 ^( o
    0 B- g: |" ^# e: A
    if ( 0 == pInitNodeArray )1 `; `) C3 c+ x6 [2 ?! J/ P
        return STATUS_SUCCESS;6 |& [& t5 U$ p" K5 r5 y

) o/ `3 \2 l. n( b* C    // ************************* MSJ Layout! ****************** r! P5 t% W$ K8 E& q; b/ R
    // If you're going to split this code across pages, this is a great( R  ^! p6 _2 T/ L  u0 M! g: E0 I
    // spot to split the code.  Just be sure to remove this comment# ^- Q) w6 \; p5 W8 j0 I+ l
    // ************************* MSJ Layout! *****************
) C- C/ _, p/ H' e1 ^' |   
! d! w" v+ G! N. ?    //
4 r% V/ ]" C+ I    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH
2 b2 g3 `) z$ p# \. z% l: \    // 第三部分,调用初始化部分1 {  J4 G  D# V
    try     // Wrap all this in a try block, in case the init routine faults
+ @0 O: k; b( R+ L    {
/ S7 z" k: W+ V6 j5 p/ B% _2 o, f9 I        nModulesInitedSoFar = 0;  // Start at array element 0" x% [6 }8 _9 B0 b
: ]$ Q8 X# g7 Y2 A) ]" t- Y8 {
        //
& }/ t3 ?4 E6 z        // 遍历模块队列
' E! ]$ B2 S  v! \9 ]        //1 ^/ Y' V* R( T# u- X: C' Z
        while ( nModulesInitedSoFar < nRoutinesToRun )
) z- z4 Z3 k) O" n9 U; z, g        {) s) v9 O7 a) \$ E, |
            // 获得模块指针
/ z" S$ E4 R9 S4 A; a1 N- y) V# B            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];& U3 {2 m+ d% R* `$ N6 y, G

1 h' b4 L, P' a, Z2 d6 a) W            // This doesn't seem to do anything...# U0 ?6 m% @1 m3 ?$ p9 _
            localVar3C = pModuleLoaderInfo;2 e, O; k2 d3 L
            / h& P0 H6 L( P0 A: A) I
            nModulesInitedSoFar++;) g- s0 d; G4 Z4 D& M
                * R. f$ J/ `% B& r2 Q
            // 保存初始化程序入口指针9 ?& `6 I) Q  y  q
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;* C. J% m6 k! G- ~
            ) E3 _/ y% w: Z3 s" H
            fBreakOnDllLoad = 0;    // Default is to not break on load9 i) A# }5 r- w8 N; T
( p, t1 o; t; U7 ~: z/ t
            // 调试用( l+ r1 ~# ]+ j" R$ E% t) _
            // If this process is a debuggee, check to see if the loader
' p- j8 X7 i3 I            // should break into a debugger before calling the initialization.; ~3 Y1 [/ D) _( [; a# ?+ o
            //
1 b2 d2 E1 [  b% ^  C            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()
& Y  b5 R' o# J4 t            // returns. IsDebuggerPresent is an NT only API.
* s# D$ \4 A- Z6 f! Y            //* P6 ?% T: o7 b! T* i6 R
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )) ^) @4 q9 y! ]) B. {
            {
0 P  o+ h( ?4 W3 J                LONG retCode;
9 Q$ \* B! U/ d' f/ w" u7 y4 v& C! z: @8 B+ q( `/ C
                //              5 c7 j" Y0 v. z: N
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
7 ~9 m0 ?) P7 V  Z3 Q8 }                // Windows NT\CurrentVersion\Image File Execution Options"( h* w( F. h9 }6 p+ \: @3 J
                // registry key.  If a a subkey entry with the name of& w* T' `4 Z  p' C& Y
                // the executable exists, check for the BreakOnDllLoad value.' Y4 v% ?8 S. w7 G  C1 ~# ~
                //! y% e5 H8 o7 d' B/ q- x. m
                retCode =
9 o; x) N8 G: L2 f                    _LdrQueryImageFileExecutionOptions(
  }) }3 \# F: \3 N                                pModuleLoaderInfo->pwszDllName,
. P, h. q) N% v8 X& B& ~                                "BreakOnDllLoad",pInitNodeArray
* a( Q$ y8 T/ \+ z# g                                REG_DWORD,
2 @3 @  U, W) W. H                                &fBreakOnDllLoad,% x! ^0 B- q- C# o; O
                                sizeof(DWORD),
9 }! X4 ^* Q+ l                                0 );
& t* R) r" V& Z8 |% K; M3 Y; W& T7 ]% E* G& ^- V4 k) |) C
                // If reg value not found (usually the case), then don't- C$ s$ Y) s" T& l" ], w% P# V
                // break on this DLL init
' A( i  |" h! \4 d0 {6 A                if ( retCode <= STATUS_SUCCESS )
* D$ t4 U4 {0 n                    fBreakOnDllLoad = 0;pInitNodeArray
8 w' P6 \* \2 w3 v. p) Q            }
1 t: O( Z# `7 X1 ~            
% r$ a7 |* h  H1 q$ i1 \* F4 W+ k# Y) ?            if ( fBreakOnDllLoad )
7 C1 h. X  e6 j- l            {           + @* @( z- H1 W4 G7 B
                if ( _ShowSnaps )
4 B  W0 @; S1 K" n                {
! u; W9 l3 ?7 H* a) e6 e/ f" a                    // Inform the debug output stream of the module name
/ {3 f. t8 Q$ j* W) B                    // and the init routine address before actually breaking8 e9 U9 N9 E' T9 y' q
                    // into the debugger
" M/ r- n8 @% `6 \, Q: D& B+ d3 D' y9 P% D* D# N; c
                    _DbgPrint(  "LDR: %wZ loaded.",3 L. S6 }% c% S& C- u8 X, M$ A
                                &pModuleLoaderInfo->pModuleLoaderInfo );
, ^( }% O# C- x/ p! r' n7 C9 c                    
2 F% p0 \2 n* p* i                    _DbgPrint(  "- About to call init routine at %lx\n",+ l7 O" ^4 d: B' r
                                pfnInitRoutine )
3 H$ Q; N7 U4 v# |: ~% R. H% g                }
6 X% X" j8 F9 b: ~, p                6 N6 g+ Y( {6 @0 l9 F4 [
                // Break into the debugger                              + C8 K3 F9 z2 }, `9 h
                _DbgBreakPoint();   // An INT 3, followed by a RET9 E6 K8 u( t' {% t/ ]5 |& ~
            }/ |! i/ A9 Y+ I8 J: d# H  L
            else if ( _ShowSnaps && pfnInitRoutine )
! p5 l9 @. Y$ [( f. ~2 ^            {$ }. R$ G( t" S* p
                // Inform the debug output stream of the module name
9 A" s' Z2 d- Q                // and the init routine address before calling it               $ w! }/ s/ q3 q: ^0 W' o+ x( H& N
                _DbgPrint(  "LDR: %wZ loaded.",/ z5 }/ R$ V) N
                            pModuleLoaderInfo->pModuleLoaderInfo );
$ E7 P% s/ D: E6 l  s" @- ~& S) i7 ^8 W8 N4 K* f% y
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);# D" d# I- X) J! g* d6 O
            }
+ m6 e1 o# x2 l$ P" h                    
! K6 i  N4 E7 F9 b0 A! h            if ( pfnInitRoutine )% B( f. T1 S+ N; C0 H" Q
            {( n: K' z* p2 q& x3 S& H
                // 设置DLL_PROCESS_ATTACH标志
/ f, u/ A/ h- D  a& z! H  }+ H6 b                //
( i4 R. ]/ U2 R5 R/ }: z  n( N5 e                // (Shouldn't this come *after* the actual call?)* q; V  Q# w& v& z
                //
, |3 g8 C* _# k4 B                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             % R. s! s  {" x4 I
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
* s, Q0 ~8 [4 U* n9 k) }
% n! f( r2 t' l3 B                //
& Q" m( C5 Y1 y% U; R% z                // If there's Thread Local Storage (TLS) for this module,
$ r4 m2 m# U4 ^1 D6 A  C8 G" x  @                // call the TLS init functions.  *** NOTE *** This only
1 {; W! i6 x9 i                // occurs during the first time this code is called (when
" L  ?2 y# w- ~& s( D                // implicitly loaded DLLs are initialized).  Dynamically& E6 }! K6 ^. Q
                // loaded DLLs shouldn't use TLS declared vars, as per the
5 l1 J( }$ g0 Q# w4 W" n                // SDK documentation
2 |: K1 K- ~: k# ~5 K/ c3 S5 ?                // 如果模块需要分配TLS,调用TLS初始化函数
. i4 a' Y' P/ [7 @& F  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
2 h: a6 i6 N- T* {4 I8 u# [/ O6 j$ x  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
" G' t  `( U6 N, U+ H* J4 I                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )2 e( k, t( _5 {  \7 `: A
                {3 [( J4 A, }( R; x7 `" X; U8 L
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
" x/ o8 x( O8 H5 d$ v( i- o                                                DLL_PROCESS_ATTACH );4 \+ @" n) R0 B$ ^
                }
4 T, \: E/ t! s% D  G               
4 T6 b0 O  O  P. T4 _9 H
5 V0 i3 X; f9 L  i$ u                hModDLL = pModuleLoaderInfo->hModDLL# R6 b& ^$ L2 i% L& `% U& y1 U
% Z3 K" R# w& {; A, L+ m' l5 S+ h+ l
                MOV     ESI,ESP // Save off the ESP register into ESI
: l% L3 i& d- y$ G) d0 H% P) l  ! }6 s' w* K1 O% Q& T
  // 设置入口函数指针               
% l1 B0 B* |8 M* W% f                MOV     EDI,DWORD PTR [pfnInitRoutine]                     0 X2 T2 J" K- u  q; U: }

9 l' [; F/ i- K# [1 z. ]                // In C++ code, the following ASM would look like:
/ M5 U, T# y- W. s0 x. w                //
9 T; j  P! J, i7 J7 Y' d9 E+ Z+ ]; C                // initRetValue =
+ m9 Y, @% E! N* ]' j( B# z                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);* q; I9 l' {' G: [" i* p% t% D* d
                //+ Q& H9 C2 g4 r! h- [4 k
  O: C+ e0 p* M/ l# {; k8 I
                PUSH    DWORD PTR [bImplicitLoad]
! b, J! e5 j$ [4 a3 h5 w$ g                - p1 ^: E% d- W% b
                PUSH    DLL_PROCESS_ATTACH7 w+ f) Y+ x  d9 J, @' l# P/ j2 P
               
1 v& S- G- Y4 Z                PUSH    DWORD PTR [hModDLL]0 q! m: t5 V$ S) I) H
               
0 U. r1 ^$ H* |; A" C4 v                CALL    EDI     // 调用入口函数; m% U* E5 p  U% P+ {  q2 Q
                # C/ W3 W2 b; ?, a- c0 c5 o
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值" |' B9 Q4 g0 c- k; e% [

7 d, \- H7 G# n, r                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the* I) B; s2 H' F# v9 o3 q+ I* G
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
) L% M& |  S. c& A# q+ B# h; B& I: T1 N
                MOV     ESP,ESI     // Restore ESP to value before the call
( @4 `( c" F% w2 ~  I7 }- [  l  w) n
, p; \; }. [; r- Q- K                //) T) |3 X) P2 m2 a
                // 检查调用前后的ESP值是否一至
, d% |* I, p5 q$ Z" h  //
# D5 F! V! a4 z* V2 V% Q- G, t                if ( _CurSP != _SavSP )( u) o: l) I/ \
                {8 C8 f0 ~8 W# c" k# o
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
$ ~- u4 I# K7 a2 _6 h  L/ G: V* o# w- B! Q$ i* ?( Y% F
                    hardErrorRetCode = ! U9 v9 o; k/ N5 I; f
                        _NtRaiseHardError(
  \+ G9 z: c8 L$ V, T# Y7 a                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
. b) o# O) x3 \                            1,  // Number of parameters- ?. R4 Z( z+ Y1 y" R, M% `
                            1,  // UnicodeStringParametersMask,. B2 N* h2 ]' w
                            &hardErrorParam,9 {2 Z  C0 y8 f
                            OptionYesNo,    // Let user decide
( t; a5 D' s5 d* }0 [1 k4 E! }                            &hardErrorResponse );8 W0 @! f  n7 ^
                                            ' n' y' ^. b$ O$ C
                    if ( _LdrpInLdrInit )
$ v! i& O- {; Q* |( E- P" e                        _LdrpFatalHardErrorCount++;' [$ @0 U& E. w  {
+ }$ o8 S, j5 l% l
                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
; Z; q4 A8 b5 z                        &&  (ResponseYes == hardErrorResponse) )! P- d4 |- W1 H" g# M
                    {
2 J. g# h$ Q% e1 |' N                        return STATUS_DLL_INIT_FAILED;
3 W: K" u2 T6 e                    }- U( L0 ~) }* e6 y) b7 O
                }
" i/ }0 U- o  _9 g2 {4 v) Z/ J9 V( N& O( _# Q6 J
                //( P: w+ t4 p. F* Y* Y/ J
                // 入口函数返回0,错误7 U. r. v( M% r- r
                /// p% N$ h8 S& e- _
                if ( 0 == initRetValue )) G1 S% [, ]7 i/ e' U0 b
                {
  b8 `/ j$ C3 }* E: w                    DWORD hardErrorParam2;
9 \/ M# G% O1 y/ \6 K                    DWORD hardErrorResponse2;
' u. x" A( x# [) T+ F                                        6 H1 ?" \, S8 j4 }" N
                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;8 P7 s1 \& X! D3 o
                    0 {" o# p7 }0 i% |  B$ V
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
3 H8 y% A7 l& \- N5 z                                        1,  // Number of parameters
0 O) i7 n2 M! B                                        1,  // UnicodeStringParametersMask
% E9 z7 V) [4 [9 d  F6 z1 B                                        &hardErrorParam2,
! n4 w" D5 _& u( }: q) |  n. r                                        OptionOk,   // OK is only response
2 B( K& V, G, X2 t3 F+ e' C                                        &hardErrorResponse2 );
. g7 O- `( _; e1 |$ M) W& |* ?; W                                                            
% k: G  `6 ~' s' [7 \7 R3 V                    if ( _LdrpInLdrInit )! l0 a; w$ R( o9 x
                        _LdrpFatalHardErrorCount++;* c. d9 u* X0 m+ c3 s( g" f
+ c/ w% z6 y6 h3 B8 G
                    return STATUS_DLL_INIT_FAILED;
: j/ L% n4 j6 t! j7 h0 F1 H                }& Z. g0 y+ w' j
            }
0 |% H- Z6 Q' M, n5 Q9 w        }
- X2 j# T3 q8 t" F) e
. C3 b% |8 |+ q( O& K5 u# m        //0 ]+ U! }  M. Y& ?- }, [
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时0 q/ w3 L$ l7 n
        //      0 I/ p2 D% F2 I, j" V* b
        if ( _LdrpImageHasTls && bImplicitLoad )
2 E" T  O  F( o( a! h6 O        {
6 c: T& ]! ?6 m            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase," J" ?) B( n( X
                                        DLL_PROCESS_ATTACH );* L* }% S* n4 {! C3 Q3 R7 |9 y
        }
6 y$ w! D5 A. q8 I" N% ^3 f. V' B    }9 ?6 ]9 c, [. S; H6 ^
    __finally- s. O, q& V! U- A7 Q# k
    {
0 e2 `. k% ^4 Z/ T& W+ {        //9 _0 U3 [2 ?( G: h. u
        // 第四部分;
8 D- }. A% B; F0 h        // 清除分配的内存. u/ O5 h; R) q9 J
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );1 W/ A8 F. E1 ?; m2 k7 c. v  }
    }& d  c6 U* T: a- t5 \) a) x9 s& @

! c, [( b. Q# y; \/ q    return STATUS_SUCCESS;
6 p4 q4 d* h9 f9 A0 f}   
% k5 @$ }- ?' U: b! @
% n" D; m% o+ B8 |这个函数分为四个主要部分:
- [8 b$ g  D1 X" _; K一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。
9 G1 k- b  C& E9 B5 J4 v二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。( M( b. w! A3 p( z* x* e
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
" d+ A. R& x/ V另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。5 k9 g! L! f: Z6 ?2 ~* N9 q# U# Z
在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。1 Q# o* W9 C% w4 {! @& h# M% u3 ]
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-4-19 23:49 , Processed in 0.140696 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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