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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]5 z% E9 X5 @( h; I* w1 t: M

( Z$ ^3 u  u8 a% _# p+ C& e4 a& v3 cWINDOWS 2K Dll 加载过程/ c. ], B2 y) U4 ^5 y
jefong by 2005/03/307 t/ u; q7 D6 c8 L" D! ?3 T5 P
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。% Q* R) n: v7 m/ z. W
在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”。
' q' L' F2 B0 G. e7 N你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。, C$ W! [5 v% e# G; T
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。& x" L0 U0 X5 ^6 {0 b' R4 h
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
7 U& J. S. H7 J8 N" ^  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
9 V0 x; a; @, T; e1 Y  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):) j% s0 P! h! s$ b7 U
//=============================================================================
( k1 B3 \) C1 `// Matt Pietrek, September 1999 Microsoft Systems Journal7 Q3 ^$ |; \' J% U. a; ?
// 中文注释部分为jefong翻译
! g9 |8 o9 J0 Q- k! l//
( ]) l8 M; ^4 a3 [// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
" U4 U- A7 t9 h$ s' }" d//& h3 R( l8 a0 ^! i$ c. l# I8 k
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
: h, w7 S. T. c$ D1 W( U, _//=============================================================================
+ V- B+ w5 z- g5 I+ G9 u7 [5 g9 ]- F7 o8 f% f3 Y" a
#include <ntexapi.h>    // For HardError defines near the end' S9 f' J  R! Z3 q- A) {0 i
  C2 ]  n( Z1 p* ~* H: Z
// Global symbols (name is accurate, and comes from NTDLL.DBG)
! q6 j% Q( p( e5 c3 j5 \//  _NtdllBaseTag
* d' ^# B. |4 t% ?9 \$ C% k- }3 x' Q//  _ShowSnaps7 v3 C  R& M+ G) E  D* D. n6 C. b
//  _SaveSp9 T! [4 S; v8 V' F, e, y/ y3 }: c
//  _CurSp
5 g, {) |$ w0 u7 q% [//  _LdrpInLdrInit. [" [0 w; R4 L( Y; T2 X( h
//  _LdrpFatalHardErrorCount0 \) E, G, R' S1 Y
//  _LdrpImageHasTls9 ~1 L' ^" R) ?1 W
8 g4 g: `  g$ c) a- J" F) w
NTSTATUS
( c. v" Y1 J* ~8 R# ^/ {" y2 R! DLdrpRunInitializeRoutines( DWORD bImplicitLoad )  i! y1 _! K( j/ b
{
9 T# v# A) L5 Z) ~9 c5 s0 C: O    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了
5 a) m! I, ?" r8 G    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
  s; g) _  }; n& a- ?  W3 U2 {/ q' `
    if ( nRoutinesToRun ): h/ ]* c; ]# f2 A1 {8 g
    {
3 O! E7 u/ y0 G( _" g+ G        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。$ X* f+ U8 Y- E% K8 T% X
        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),+ g# p) l5 g$ j7 F7 N# B
                                            _NtdllBaseTag + 0x60000,0 D: p6 V3 V' Z8 T0 A4 _; {* p7 G
                                            nRoutinesToRun * 4 );
7 b% P6 J1 z+ k                            ; g3 v* C6 x: K* v3 F3 b
        if ( 0 == pInitNodeArray )    // Make sure allocation worked
3 k8 D7 u- e# A4 T4 S$ e8 x" ]            return STATUS_NO_MEMORY;
  ^" v; g& L; G" D1 p+ r' U/ b    }2 t' z/ ~, ?* N( y% o# l0 w. Z, q
    else
2 e0 Y3 g3 Q" I) _( m8 }% o2 l$ l        pInitNodeArray = 0;
5 _) o# n( k  T# A
. a, J6 Z1 T+ q% I; ^    //第二部分;
  N6 `* {( E1 e    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
0 C& p: T# O- Q5 z4 R    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);5 M/ j- q3 c- l: G2 q
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;( s. Y  t2 e2 r+ l
        
: q% f: u6 f8 J- s- K" Q    if ( _ShowSnaps )$ n+ i; p* d* ^' A  F3 F. A; A
    {, w. O3 k, R; A  p7 y
        _DbgPrint( "LDR: Real INIT LIST\n" );) k, Q  Q- F; y- U* ^: A
    }9 O5 E; N: r, c8 K) o

% O- w3 X" K; Z( |! q& ^    nModulesInitedSoFar = 0;
( ?4 ^4 U+ q4 f6 n+ h
9 l! L$ s4 O0 s7 B5 o% n    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块
2 H  e+ n) Z* T8 Q    {
9 p( u9 z9 T' y8 y5 m* {6 r        
5 h7 w+ ]4 |" c        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块/ L& V& y) o8 W. A+ C
        {
% H& E( Z4 h% h- I' U8 W            ModuleLoaderInfo  pModuleLoaderInfo;8 z& f- l( y4 W# p/ J
            ' G# I& f8 X/ F2 Q' }6 l' Y
            //
0 E4 b: Y; Y7 K* d8 W" @* I            //一个ModuleLoaderInfo结构节点的大小为0X10字节/ h( @4 v. @- X: X2 T. K
            pModuleLoaderInfo = &NextNode - 0x10;
4 J) z, j8 \0 I! U+ I            % Y% X9 d& V0 Z2 x
            localVar3C = pModuleLoaderInfo;         . Q: P& H. D. ^4 q1 r5 e% j! F- b

. X% w& U6 \9 j            //* ?, K  j& C# h. Q+ [% p, K
            // 如果模块已经被初始化,就忽略4 K/ d) w1 J0 _9 O9 G
            // X_LOADER_SAW_MODULE = 0x40 已被初始化
) z$ H/ i: `& B/ W) h            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )2 c4 w/ F" p# m% M/ I- }
            {) a. Y' |9 d' x4 J9 x4 P* O9 s. q
                //8 Y2 y0 v( t' P7 s& b& x- `
                // 模块没有被初始化,判断是否具有入口函数
( }% |& Q: L% W: f                //
4 _, _: y& H; Z4 _8 M+ t* i                if ( pModuleLoaderInfo->EntryPoint )" Q; @% e7 X' V+ B3 s' J
                {5 `9 {$ ?/ A3 K8 _. ^# ?4 x
                    //
; K4 p" W. k+ T                    // 具有初始化函数,添加到模块列表中,等待进行初始化$ `/ u3 }; _! B' V8 G
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
5 Q+ C( |7 t! q9 }
6 r: ~1 x" O% O+ V2 i                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址
/ q% I- h, [* b( ^( B- r0 Z      // 例如:
* n# ?) A4 H- C  ^  F5 z                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
: T7 v# }7 n# X  O$ J: ]& m                    if ( _ShowSnaps )7 M& H# ]: i6 [3 x' G4 U* E! a
                    {
1 S- d" q6 E4 \4 B! T                        _DbgPrint(  "%wZ init routine %x\n",. Y7 L0 ?" _  S* `
                                    &pModuleLoaderInfo->24,1 L. }8 X% I  D4 G! V: n2 q
                                    pModuleLoaderInfo->EntryPoint );( J5 y, h, z/ D4 ^. V  k
                    }+ r1 B& x$ d: k* f

" T$ n. }% C  c' h                    nModulesInitedSoFar++;1 s! {, y" W- N1 ^" E: @" I
                }
/ b5 n4 f" H+ W" ~# m) o            }  t) z" g" ?5 }
* S6 M" }* D0 ?! L/ _0 I1 M( O
            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
) v3 y- L0 I, A  B5 Z7 J1 W. Y            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
2 x6 L6 h# Z9 A  _8 s: q
$ b& h0 M. Z( W5 m5 o2 y; x            // 处理下一个模块节点
6 ^  U5 B! F: G2 I0 V7 R1 d" B, a            pCurrNode = pCurrNode->pNext
5 c; L+ T# ]% H+ r9 z  m        }5 S, c, J+ \2 ^& C/ q
    }
2 k* G3 k% P; S6 v& }    else
, e; R/ L2 T" N! g. i    {8 f. M8 j, a6 d7 R
        pModuleLoaderInfo = localVar3C;     // May not be initialized???
9 d5 n. {  ?/ n% Z$ F: t    }
, O0 E: M* @+ C1 X- z: I, Z* U! W   
2 b. Z- t* R& C1 B) u0 q    if ( 0 == pInitNodeArray )
& G2 F) T$ o4 @        return STATUS_SUCCESS;
7 A% g, n6 g# H1 s# f7 g2 _; i. D, o3 H% _: I9 {. J2 o$ ~1 _8 V
    // ************************* MSJ Layout! *****************& L+ M# x9 k! ]3 A
    // If you're going to split this code across pages, this is a great
5 r7 S/ I, w! ]5 O    // spot to split the code.  Just be sure to remove this comment' w' Z1 h9 o" c  I8 ~, U9 O
    // ************************* MSJ Layout! *****************4 z# z5 i& d8 ?9 I! N
   
* C( h* H+ g# l! [: A0 J* Q# E    //
/ q- V/ @  `% i# ]$ E4 @+ U7 Z    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH2 z4 c  k  ], q
    // 第三部分,调用初始化部分. j" c! B% z* U9 t( Z) P4 [
    try     // Wrap all this in a try block, in case the init routine faults$ }8 m+ K$ H6 ?2 u+ I$ V" k6 f& r
    {$ n5 g7 I: y/ F/ l$ o: C8 I
        nModulesInitedSoFar = 0;  // Start at array element 0/ K8 {# J" O2 n( U& J6 I

2 U1 F* R$ @  Q( r% j        //: l2 J3 f9 \  d5 I3 `+ I
        // 遍历模块队列0 |( c# [& y2 ~# E
        //* C' K) w, J( K6 F% p& q9 _
        while ( nModulesInitedSoFar < nRoutinesToRun )2 ]* n2 d( N* o: i/ p$ @4 J, _# \
        {
# f- e) d, b) A& r- \4 y1 f            // 获得模块指针
& j/ }* u8 e% i; Q3 I, v9 z8 H            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];3 C1 p& t5 T* |; z5 b# [

7 E+ [4 k. G+ c8 \; d            // This doesn't seem to do anything...
2 F- o7 E; J7 l, z" ^# F0 ^            localVar3C = pModuleLoaderInfo;
; J' M' g5 W- W6 B            ! p5 |; |% H( k7 b0 ^! X
            nModulesInitedSoFar++;
" s4 z: }, A6 P, R% k' l, R. G                8 }5 d& X- c+ n+ H& b: E
            // 保存初始化程序入口指针4 @8 }, }6 }) K' F; X. T0 {
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
6 ~: g: N0 u- |            ' F5 S8 S$ g' I% ~) Z1 e5 n; d
            fBreakOnDllLoad = 0;    // Default is to not break on load
# l3 ], {" j+ w: \( V6 m& g  @$ I" W
1 U* s& p. B  d/ c  c! U- c( Z            // 调试用; n% y) e) n# A  p  @1 S% @" X1 v
            // If this process is a debuggee, check to see if the loader
% _$ @8 f; C, X6 ~$ `8 W' y6 d            // should break into a debugger before calling the initialization.
& ~7 ^7 ~8 s+ s            //* z8 x  P# b! l  t, Z( a& o& Y1 B
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()
& ~; `* S% a4 R7 n" f, L. H            // returns. IsDebuggerPresent is an NT only API.( o) u6 A, g& D0 C
            //
/ x% l3 Q' m) c# c2 T            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )3 |" F- a+ O. q9 O3 b7 ?3 N
            {
8 R# y6 N2 Y4 Y% D                LONG retCode;
! P9 i$ S# _6 W4 f0 s' W
# z. f# D! g4 Y  i9 h' g                //              / }/ t7 Y( m( G+ L
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\" f* r/ M1 D7 b3 P% ~3 A
                // Windows NT\CurrentVersion\Image File Execution Options"  g$ f1 m* J- d8 z4 {
                // registry key.  If a a subkey entry with the name of  ?; A) o' c2 V) l- \8 L
                // the executable exists, check for the BreakOnDllLoad value.9 o' r0 H! B# D9 M( d: A
                //
) ?$ Q3 i0 ]/ T' n                retCode =
$ _3 c# C/ U; B# e                    _LdrQueryImageFileExecutionOptions(2 @3 k: S6 k. [" }/ _! [
                                pModuleLoaderInfo->pwszDllName,
3 K, e( a. H: z; J                                "BreakOnDllLoad",pInitNodeArray
! C4 h/ l* F4 a. M2 G1 ~, c                                REG_DWORD,
5 z( Z$ T% e7 y/ a                                &fBreakOnDllLoad,# z( R+ s$ s2 v8 a( L
                                sizeof(DWORD),& \/ f# C4 n: c: Q8 u) y2 N8 i6 I1 a
                                0 );6 N) X+ c) `$ B
# A! c2 r* ]) H$ c# ^. S6 n
                // If reg value not found (usually the case), then don't; J, _0 I+ ^1 }9 Z$ \# [$ }  N, f
                // break on this DLL init
% b8 U# z5 G8 _7 k5 E                if ( retCode <= STATUS_SUCCESS )
  @- |8 c8 K5 g& @1 n  P; v4 q                    fBreakOnDllLoad = 0;pInitNodeArray
0 K0 r8 I1 x: Z            }
8 w- q7 Q7 o. W9 H! k+ Q" Z            
, M( W4 D( Z: n+ R( i            if ( fBreakOnDllLoad )
7 Y" e9 \3 w/ ~; Q" T6 F            {           6 J- U- ?; u3 ^+ X! m7 i) }
                if ( _ShowSnaps )
5 B7 i' g6 w9 R6 k' z) I5 p                {
. I9 I9 c: Z& M2 c# g, d& Q% c2 r                    // Inform the debug output stream of the module name
7 X6 x4 V9 G2 ~* G7 n2 k2 V                    // and the init routine address before actually breaking
+ a( i: ]7 B1 @* }0 B6 G/ Q                    // into the debugger% r. }" R, r& V, r( c4 y! B
. R- R, b4 S3 r" ^) A* L+ a1 g
                    _DbgPrint(  "LDR: %wZ loaded.",
" p6 N3 k8 r7 J' i/ A                                &pModuleLoaderInfo->pModuleLoaderInfo );# |6 L' z$ g. M. o0 M9 j- n
                    
, {3 E+ j% n# @8 s2 W& k                    _DbgPrint(  "- About to call init routine at %lx\n",
2 k& c) N) j8 w) b* f                                pfnInitRoutine )
- ]' D. s7 }" o& I: o  \3 e1 o                }
( G/ Z# i, s6 }/ `               
$ ^, y$ w1 r) N$ f. Z                // Break into the debugger                              $ n8 M0 i+ U5 r5 T$ q* T( [
                _DbgBreakPoint();   // An INT 3, followed by a RET
, Z9 U8 [( U# O7 |9 d5 R            }
1 T; x# s! \" J- v6 o4 F            else if ( _ShowSnaps && pfnInitRoutine )
4 K5 P+ \; D* l" a$ ]            {3 }9 s9 t8 [& e1 A7 g
                // Inform the debug output stream of the module name
2 {) H2 n0 J) }3 N3 e7 ]) j  P2 C                // and the init routine address before calling it               
* L9 N$ ?% ]8 v4 R4 I+ v                _DbgPrint(  "LDR: %wZ loaded.",* q3 i  p9 |2 e! l
                            pModuleLoaderInfo->pModuleLoaderInfo );8 J! c3 V# w6 }" C" V+ @+ m3 J
+ f- B; y. j$ {9 h! W& B
                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);( r+ q. z7 ?9 a8 x: s$ z0 q
            }
. M2 T7 e8 U( m( q                    * \+ F6 E4 m! u# s6 W: L
            if ( pfnInitRoutine )
  t% f* s! x. s. t) k            {, M% r+ g) g* h; m
                // 设置DLL_PROCESS_ATTACH标志
3 g3 c& Y% ?$ k7 Z5 q( J4 x: m) R                //6 f  o; t8 d7 U/ |
                // (Shouldn't this come *after* the actual call?)7 p9 L3 n# m9 f# A2 K
                //
+ e* |/ x8 j$ a+ ?3 q+ F# N+ @4 N                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             # x" Z) J. h, j3 _  A
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;% [; h9 ?9 W8 ?+ d

& Z& }( @5 r; ^                //' R3 r) `5 m0 |7 D) F
                // If there's Thread Local Storage (TLS) for this module,3 @% k7 S6 h4 F" y
                // call the TLS init functions.  *** NOTE *** This only
; B* W" ], d, s8 j' R                // occurs during the first time this code is called (when
7 F) }; U6 g2 O9 g$ g7 f                // implicitly loaded DLLs are initialized).  Dynamically
2 r5 ~; d  U; Z; Z2 r! Q! P4 _                // loaded DLLs shouldn't use TLS declared vars, as per the
" c0 [( \7 p  `# U                // SDK documentation. ]1 N4 a) V- j9 Z+ Y" J7 h) m) ^
                // 如果模块需要分配TLS,调用TLS初始化函数5 M  [2 N% v0 }6 O% s7 G
  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
' M5 C' F; M+ w, v" j* e1 x  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
$ p6 k1 l2 l8 \                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
* g# P  x5 z/ f# i  S                {
- r7 Q0 ]$ z9 T, o/ h5 u6 Y  a$ F' A& Y                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,! ~; j1 V( y, n$ R
                                                DLL_PROCESS_ATTACH );5 B2 V$ q9 n+ g6 }. V- G! c  T
                }
, U' Y; J/ @9 b+ b- u3 s                % Z* W0 I3 A8 E1 `
. I" K1 {$ F! F
                hModDLL = pModuleLoaderInfo->hModDLL
4 D7 {" D5 M/ R; R
9 t. ?+ Z& [( N! p7 c                MOV     ESI,ESP // Save off the ESP register into ESI
0 B  v* t! s1 M$ y+ K3 E  
2 d6 w2 x+ O" o4 W  // 设置入口函数指针                ' r& x# h; S0 A% t
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     - b( I% K/ |7 W+ f9 h

' `* s! ~( }! y" V# h1 l                // In C++ code, the following ASM would look like:
! A" G7 g# ~! w- F: v                //
( h: i) \0 ^) d/ {  I8 s0 J                // initRetValue =
! I1 S' r. s" E  W                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
" i; B& @! P. }  w8 X" R, `3 v                //
  Y4 w' J! u7 y% S2 V. f2 Z
2 P' A5 }" D$ u4 S9 G                PUSH    DWORD PTR [bImplicitLoad]& c3 J0 ^" ~* q
                : ?; I/ J: O/ O9 [7 M
                PUSH    DLL_PROCESS_ATTACH8 z$ t' v2 I+ q. [7 h
                6 S4 T5 G' H* m. O# A! i
                PUSH    DWORD PTR [hModDLL]) V1 u' M9 P% K6 e4 M! E
                ; D2 |( ]2 k5 j% O
                CALL    EDI     // 调用入口函数
5 R8 f, M/ s, l! y6 e3 n                ( q. j  l- U( f5 Q! `1 f
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值! q2 k8 I8 D6 f, i( h; {$ Z* `
1 \; J( A7 ]! v6 R. W: k7 l
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the  H) q5 ?/ g5 E5 D# f" N. o
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns8 E. f6 v4 ]9 q/ |5 O# h, F7 W
# \5 t2 r! w2 D: u" t
                MOV     ESP,ESI     // Restore ESP to value before the call
3 W2 g3 x" o2 i- i! b
' [6 _; R0 O* G( e4 g& \                //
$ p% Q9 J# l8 G" p* `; u1 {                // 检查调用前后的ESP值是否一至( J+ g2 ~( q0 G4 n# c
  //
5 [) y6 P4 Z+ n                if ( _CurSP != _SavSP )0 Z2 _2 f  b; w6 v' v( _
                {4 A0 h1 i6 R; o$ z
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
$ d, y  V, e' \) s# Y2 F7 U6 H- K& g- s' b* Z) w
                    hardErrorRetCode = 6 F& P/ k# i8 [) S- n5 j2 X& J9 h  b! w
                        _NtRaiseHardError(( |7 P% F0 u7 b, S( Z5 m! e* ^" I$ Y* J
                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
4 W' g7 V, U: K4 @2 e                            1,  // Number of parameters8 \* v5 h7 h2 [
                            1,  // UnicodeStringParametersMask,
7 \( m% ]( m4 o( U4 S                            &hardErrorParam,+ t- ?9 l4 O0 N5 A2 G/ e) ^# o
                            OptionYesNo,    // Let user decide
+ n" P, N/ [) K: u" F* m5 m/ C                            &hardErrorResponse );
- W& c5 ?" k3 r" j( ]% i* F                                            & D  N# v! K' {* _- A$ t! [
                    if ( _LdrpInLdrInit )7 l  C0 q+ |2 o8 s& H+ }
                        _LdrpFatalHardErrorCount++;, J6 w% R9 Y/ Y( Z
4 e. G! |. N8 G9 d. I8 c
                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
% V; L- y3 S% q                        &&  (ResponseYes == hardErrorResponse) )
  I' ~' C8 V' V: ?" c4 e( x                    {0 y/ c6 t: O2 e' H0 F4 Y
                        return STATUS_DLL_INIT_FAILED;
- x( M) {7 V; K: i: C" [                    }
& t, T: d, Y" b! M7 T% L                }
# a- j/ p" ~& l& B. r7 }# j( Z9 f; N
                //7 e  c6 {1 g. e- d& H
                // 入口函数返回0,错误
9 v6 Q3 U7 }- `& Y; ~( Q/ s                //
9 y$ D( o1 H6 r3 n                if ( 0 == initRetValue )  T5 ?3 e( {; K" z8 Q. C$ O2 o
                {
, \0 g/ T  s+ A& J' Y: L& |                    DWORD hardErrorParam2;
: c; c5 d' T4 O) }3 W3 h                    DWORD hardErrorResponse2;
* n3 e  V" a9 d& O                                        0 C1 k/ k  W$ t5 c- ^5 @+ ^' d# G
                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
3 \, @: a7 ~4 V  s                    
% A) |$ P' P# f9 i' r2 |* X                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,! p! R0 u2 W" _+ N# R
                                        1,  // Number of parameters- f' }7 r7 F$ H0 [2 H# l
                                        1,  // UnicodeStringParametersMask# y% u! Z; L* Q) u0 p  K
                                        &hardErrorParam2,
* \* M' M: D5 n& L% c                                        OptionOk,   // OK is only response
& k6 p( h1 w4 w+ U& C                                        &hardErrorResponse2 );; B, I1 ^& u0 ^" c8 ?9 ?+ v# |" _
                                                            
& L* Y+ j2 ?  b/ B                    if ( _LdrpInLdrInit )* u3 O& J4 L# }5 [! c
                        _LdrpFatalHardErrorCount++;
1 R! R/ Q* I/ t# A
5 R. G  }+ C8 a) F) c                    return STATUS_DLL_INIT_FAILED;/ r; |6 w8 `; n& v
                }- U' Z) D' O' ~9 V- p
            }0 z( B9 K* D5 e
        }
6 s- }* l3 S4 O9 c; u4 `' D: {( Y+ M3 ^
        //
9 @' b2 L1 y+ A% B        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
! H* C9 ]% }0 c        //      
2 P" T& |" f: K! C# a6 u        if ( _LdrpImageHasTls && bImplicitLoad )) d: a8 N9 v! }" S. ]3 D) |+ V/ b
        {
. i; P. T* Q( V            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,  V$ ~) H' t% u( ]( j
                                        DLL_PROCESS_ATTACH );/ f- [  O4 Q9 E7 w
        }! D& }8 X+ h9 ?- K4 l5 c
    }
+ ^% \8 E/ ?0 E' [. h    __finally1 V: [+ ^# K: J6 S% C
    {) Q& x) F1 t/ L5 ?) f7 m
        //
8 b' k! J+ y- {- ]3 \        // 第四部分;& A% u  x( M. }4 U+ |+ s. b
        // 清除分配的内存$ I' K" _% ?4 M7 e$ ?9 C: Z/ F- d
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );
' r8 n1 O" A. I5 @4 B5 j    }
) `' ]9 c+ u0 ?
& S- w( V% c# D2 V$ _# |    return STATUS_SUCCESS;
- [7 P: ?5 l# y$ L}   
" N4 Y' t) ~  G6 f; ]4 A' b  |( |) i  O  E, ^# j0 N
这个函数分为四个主要部分:) r' @9 b( p) [1 s- h5 B% U) E
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。1 b1 S+ s8 `. T' b5 y
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。* M+ W( w9 i4 X( g1 X/ w* `5 }
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。. t% x. \9 ~$ L2 h$ u! N! m7 r
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。* m6 \8 i! B! V) ~
在最后的伪代码部分使用汇编语言来进行dll的入口函数调用。主要的命令时CALL EDI;EDI中就是入口函数的指针。当此命令返回后,dll的初始化工作就完成了。对于C++写的dll,DllMain已经执行完成了它的DLL_PROCESS_ATTACH代码。注意一下入口函数的第三个参数pvReserved,当exe或dll隐式调用dll时这个参数是非零,当使用LoadLibrary调用时是零。在入口函数调用以后,加载器会检查调用入口函数前和后的ESP的值,如果不同,dll的初始化函数就会报错。检查完ESP后,还会检查入口函数的返回值,如果是零,说明初始化的时候出现了什么问题。并且系统会报错并停止调用dll。在第三部分的最后,在初始化完成后,如果exe进程已经拥有了TLS,并且隐式调用的dll已经被初始化,那么会调用_LdrpCallTlsInitializers。
. y9 [8 Y1 b4 m. r+ Y& ]四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-1-18 21:00 , Processed in 0.055267 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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