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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]4 C# l- J5 n7 K5 W  p( A9 G8 i9 y
6 M# [1 [( e9 k& ^
WINDOWS 2K Dll 加载过程
2 v, e- U7 _2 F  M; E) z+ A* g7 Z9 S3 tjefong by 2005/03/30; T2 R! ]& }4 j
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。) M4 Z* x% l) ?
在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”。9 `* C. z3 ]# d8 w5 z) i
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。
* Z/ E5 M1 V# z8 Q! j初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。  a  d) \3 x+ [' J" g0 k
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。
8 u% Q6 z/ H# i  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。8 Q1 `9 v/ b, M. p  e& b7 s
  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
1 w0 I+ w; |* n; R//=============================================================================
4 [: C& y* H# [) w( h7 ^// Matt Pietrek, September 1999 Microsoft Systems Journal
/ O& d$ y  Y$ F' c* c3 _2 o// 中文注释部分为jefong翻译' @4 u2 V& `7 H
//
% i- |' w6 a. `- n// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
, N6 x! ]! G) F1 y. \' i5 }//
2 A: i% W, z; N0 l// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;: @5 N+ w  n6 a' a: [& d
//=============================================================================
1 O6 n7 L3 A6 L1 N; L/ E5 ]9 I7 k! _  f# h
#include <ntexapi.h>    // For HardError defines near the end6 C" m4 b  C8 }, O* V, R3 s5 z- Q
: c6 p4 H# U$ q# _. J2 y3 o. p
// Global symbols (name is accurate, and comes from NTDLL.DBG), G) l0 \% H) i7 n8 N8 k
//  _NtdllBaseTag
, s& F- E" L) E: X4 r//  _ShowSnaps2 L2 w4 ?/ N: W/ m4 c& x4 F
//  _SaveSp
( v% f6 i8 V; Z- o; N: k1 `! n//  _CurSp8 ?+ |. T7 l* J9 v5 b9 O' i2 J
//  _LdrpInLdrInit
- A' d9 S$ @5 x5 H# o//  _LdrpFatalHardErrorCount* H2 w0 {" x5 \. I
//  _LdrpImageHasTls
8 l2 |3 l7 Z; o
5 R; n# E- {( F# n% TNTSTATUS
6 F3 s7 M* q1 O* b8 ZLdrpRunInitializeRoutines( DWORD bImplicitLoad )4 i! G; m4 u' A/ {+ M% w) G
{
& h$ Y6 I' g& C$ a' j- k    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了; k/ E) T7 r, j/ |$ ?. f
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
" x7 C! {4 n4 S+ f! P2 b  X" [: g7 W8 M. C. m/ H
    if ( nRoutinesToRun )
2 E+ f; \0 |& K# n" Y5 j    {2 ~. e& S1 i# P& E: M0 \
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
! S3 o! O: f1 E- _5 C        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),6 G& K" e+ `6 j" b3 N8 Z
                                            _NtdllBaseTag + 0x60000," [, }: b  O* J, E9 X$ v9 I, @
                                            nRoutinesToRun * 4 );
' l. ^; W# w9 o+ X) W! W                            9 s9 k9 d% p( M& s4 C# A' q
        if ( 0 == pInitNodeArray )    // Make sure allocation worked
# T1 j% L+ q4 _7 _. {7 T7 H: S            return STATUS_NO_MEMORY;
: y* |8 Y) b9 X4 d( P. C. H) x, ~    }
' V- R7 A0 f4 Y    else
3 i# v2 a. \5 l9 m. L( s/ B& ?        pInitNodeArray = 0;
8 X0 J7 T$ }# ]3 ~& z  M' W
0 ?# x. f8 o0 `! J, u    //第二部分;
( |; f7 Z* ^, m' J    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。
: Q: n5 B5 P* ?" Q8 t    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);
" Z+ q! e: j5 v    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;( t6 W. Q7 ]/ X9 h9 n
        
* i6 v6 \8 G1 i. K' |- @, V5 R7 p. a    if ( _ShowSnaps )
+ O3 `) {* [6 u) N( p! ^6 N    {
* w3 f* \$ O1 Z- c7 j        _DbgPrint( "LDR: Real INIT LIST\n" );4 y  y* W) r$ l1 o9 N8 Z2 S5 p
    }
% Y7 d' H$ o. x/ d0 U# C# \/ B9 _: G4 m* `) e, @
    nModulesInitedSoFar = 0;9 G; U" p3 f4 o4 _

6 G9 U) w! Z) d! H: s: m    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块+ p4 u3 u% K, Z" f( G8 J
    {
9 F1 ]+ J: g8 ]! o- B8 c        
! Z/ N5 H2 v; A, x% r7 I- j        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块! _3 d0 T8 D0 y% X* F' U2 r
        {
; z. f0 v4 Z+ Q* E& M; C' v0 }            ModuleLoaderInfo  pModuleLoaderInfo;
* O, ^9 Z$ c, V4 m- f            
0 H) `8 ~! A1 D  Y2 Z& A2 @8 S* ?7 q            //! n  u. Y7 a6 a5 g. ^0 I# v: ~4 I/ k
            //一个ModuleLoaderInfo结构节点的大小为0X10字节
* w  B, p, l% T8 }/ E            pModuleLoaderInfo = &NextNode - 0x10;
8 N2 j, Y+ U) \# C; W- T            
" p! c% H9 z# L  u- X            localVar3C = pModuleLoaderInfo;         ' I' y% j& {$ i3 T

# |! m0 u7 }# O! p! X) ?# e            //
& R( h7 i+ t" I3 O1 A$ E            // 如果模块已经被初始化,就忽略
; c5 u1 K% A0 Y0 E2 j            // X_LOADER_SAW_MODULE = 0x40 已被初始化
+ J. C( G  I% M0 k. e            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
. f& t3 p5 C7 R* M# c( t            {
% U! [: w9 q0 H- ]' j1 g, s                //8 x' y2 [' C3 o' H
                // 模块没有被初始化,判断是否具有入口函数" U1 O' r( B' v0 l4 v( S, h4 Q8 G
                //3 m8 L( n9 ~0 w2 M5 N" Y
                if ( pModuleLoaderInfo->EntryPoint )
, u$ D  W* q; i" x                {
( P1 I! ^8 V$ T4 T! L                    //
0 }% K5 P: E  i% G$ V5 |- {                    // 具有初始化函数,添加到模块列表中,等待进行初始化
) |+ D' C5 x: m! l7 S4 U6 Z% O! {; V                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;9 C  V) [! `& k/ F

; ^, }! h6 I# T) Z! c! z                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址6 b0 A- x- h4 W. n- W
      // 例如:
& W( e: ~9 }: `# j) I* @4 W4 G                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000
' y- @9 f# f5 f% }. Z8 e                    if ( _ShowSnaps )
& K7 O6 G* s( q$ C! ~8 Y$ p                    {( B# Y' Z/ |5 m6 P7 l& q
                        _DbgPrint(  "%wZ init routine %x\n",
! @) k* I) u' c/ C4 o4 d/ B7 D0 t                                    &pModuleLoaderInfo->24,: D. B  `0 ]! {7 h) w
                                    pModuleLoaderInfo->EntryPoint );% a5 g: m* t% z' c# X
                    }
; V5 F! \' b0 O" s; ?; _' Z( c! ~6 f2 @5 g; ?
                    nModulesInitedSoFar++;" y; [% T& U5 p( I) w
                }
, I$ B* y: c# f) s# r; t            }( X& O' ~1 p0 ^

/ ]( E1 _! b6 V            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
" k' D5 T& ]* }2 }( c; f            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;- g1 Q) f7 p  Z

& i8 |* K5 c: s9 K$ U            // 处理下一个模块节点* y6 T0 c; c0 `9 B8 O
            pCurrNode = pCurrNode->pNext
5 ]+ z& |" ^" w        }
% N. w9 n8 Z$ l' Z/ Y# c! K    }! m3 A1 b7 r6 g6 x4 e& x) {6 e
    else
$ u7 r1 o8 V' n" A' @# {    {, t$ U( n# [0 f7 V  U0 U
        pModuleLoaderInfo = localVar3C;     // May not be initialized???4 v8 l9 T1 z3 Z  _. B8 t
    }. \: w% K- F7 `
    ; n" A, C* J$ m6 j
    if ( 0 == pInitNodeArray )
0 h0 u4 _0 u4 D5 e6 M: A4 w& S1 E        return STATUS_SUCCESS;
% [$ v5 G; |5 V. n+ V8 N, Q* ~9 }' U  V% o7 O
    // ************************* MSJ Layout! *****************, z& `5 U; |' |% t  N1 s
    // If you're going to split this code across pages, this is a great9 e3 q) P9 m  ]  p) X
    // spot to split the code.  Just be sure to remove this comment# F" v+ z4 E2 _2 e& o9 B
    // ************************* MSJ Layout! *****************
+ G8 {$ \$ K$ f) F9 R8 V    . T& |+ {5 u( e/ a) r
    //
6 Y  i! L7 ^3 [9 k    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH1 [8 _- h7 ?- F2 W
    // 第三部分,调用初始化部分7 Z! p1 H0 T2 n' C1 L& a2 l0 }
    try     // Wrap all this in a try block, in case the init routine faults: a# U) r2 G/ U
    {
# W$ S+ z8 e  Y' U+ }        nModulesInitedSoFar = 0;  // Start at array element 0! r. W) v% m( h  ~; P
$ }9 J4 H( m* @0 Y5 i6 D
        //
% |$ ?2 p* a4 S1 F2 s        // 遍历模块队列
  g! I! l& Q% m        //
  W8 v) T& C4 s6 F$ H7 [        while ( nModulesInitedSoFar < nRoutinesToRun )
& o- m! Q$ Z2 m# B; ]0 H        {; ?* V2 ?% J) i- w0 h, V& X
            // 获得模块指针. a) y  i, I+ ]9 j
            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];3 s; X/ r+ p$ X) T. L# ^

! q& d; p, f1 k  q            // This doesn't seem to do anything...
5 a7 I; S6 y! J% g  a            localVar3C = pModuleLoaderInfo;7 C; d  J8 a1 L6 l) Y4 B, u
            ( N  Y- Z/ z: d3 Y5 U" x  i
            nModulesInitedSoFar++;- ~4 ^- \- u+ C/ N* l+ n4 w/ S
               
  F7 G# C- \8 P, M6 }" ?            // 保存初始化程序入口指针' n$ |) s, J3 O+ ]! Z
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;2 P6 ~. x6 T$ n# A, g9 ?
            
; e1 q& ?$ ^. k            fBreakOnDllLoad = 0;    // Default is to not break on load
8 n  N1 A) G9 |5 u& d8 a5 u, p, L3 Y
            // 调试用
% n! h3 V% {5 N% R9 ~: u( N) }            // If this process is a debuggee, check to see if the loader# H) U' K5 E: \* S2 \$ f
            // should break into a debugger before calling the initialization.
( i4 R) f# ]4 S8 [, A            //
' `7 \" ^) o9 l4 G5 j$ H% K            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()2 |- R/ n: T) q$ C1 S) a
            // returns. IsDebuggerPresent is an NT only API.) D( \" }7 @. k- N
            //
) k$ y! i7 v: j# U+ W0 c; K. c            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )8 k  a1 A0 N5 t) Y0 D  Q# {, o
            {
  H7 l/ r7 K% U' c                LONG retCode;) B" Z) M4 H/ B6 u4 N5 p4 |
5 Y( A0 o8 x8 Q0 v4 U3 _
                //              $ Y0 b2 y/ z& p) k. L
                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\; g6 Y7 R8 ?- y5 ^
                // Windows NT\CurrentVersion\Image File Execution Options"1 R2 {& K4 ^  ~# M* U3 I, R0 q  S
                // registry key.  If a a subkey entry with the name of
4 p8 _' s9 g/ |0 R$ E; Z2 c                // the executable exists, check for the BreakOnDllLoad value., R) u  _; [/ f$ r: ^
                //0 R+ t7 w, O% c0 ?) f+ `3 m8 A
                retCode =
  N5 I0 n9 b% w                    _LdrQueryImageFileExecutionOptions(9 l. T% N4 E* I, Q7 m+ ^
                                pModuleLoaderInfo->pwszDllName,
; m. [- k2 A* E# h                                "BreakOnDllLoad",pInitNodeArray
$ i9 |  P, ]7 c0 g  c' ]                                REG_DWORD,
6 B( v8 e/ J! g6 V                                &fBreakOnDllLoad,
; o. o6 ?& G9 u- Q8 D9 h" {6 k1 y% w                                sizeof(DWORD),
) I7 T# ~( u: S- W' h                                0 );% q# U- L2 U" i
$ D' x; P2 f/ ?1 \. B1 B
                // If reg value not found (usually the case), then don't6 B( s1 P" f( s$ Y* L7 F8 ~( }
                // break on this DLL init' v4 r5 t- ]* [, L& `9 g
                if ( retCode <= STATUS_SUCCESS )
) X- n  x5 w: H. E4 k4 H7 ~, R% H                    fBreakOnDllLoad = 0;pInitNodeArray- h* Z& x! |# V9 W0 k( i
            }
6 ^% K4 ^& f/ |- {( w            
; q  i6 S4 Z! i) F' y, A. u            if ( fBreakOnDllLoad )" @4 c( d4 k' S7 c. N6 U' D! o" j. K' t
            {           
0 p5 U' l0 `8 x6 p. C" h6 G2 J                if ( _ShowSnaps )$ ~! m! I/ f7 F- r, R, [: ~
                {* e# q+ Q7 _& W
                    // Inform the debug output stream of the module name  p! ~8 y# w' x4 P6 l3 j( g2 Q! q
                    // and the init routine address before actually breaking
# F- \/ z- X, y, m0 M                    // into the debugger6 W/ k/ l$ i, b" v
% m  T7 O" D1 o7 X! ?( E% y$ W
                    _DbgPrint(  "LDR: %wZ loaded.",; T$ G4 N8 ^+ `# |0 m& K
                                &pModuleLoaderInfo->pModuleLoaderInfo );
1 c/ w: C- @: `1 H6 F- L! \                    ) T! |3 a5 c0 f7 [
                    _DbgPrint(  "- About to call init routine at %lx\n",
- P: S& f! Z: Z                                pfnInitRoutine )
. y3 Z/ j" Z( ]! T3 e                }
* X& l; n5 k# Z$ A4 Q8 R               
% i; _+ r1 o6 g4 p$ f                // Break into the debugger                              
* k! ]' q" ?2 [& Q                _DbgBreakPoint();   // An INT 3, followed by a RET0 S6 d0 o/ }. g# |+ k, r9 A
            }2 N, j9 z) c! T& Q" K) r+ o
            else if ( _ShowSnaps && pfnInitRoutine )/ }- C. _. J9 b5 @7 S! _5 x- Z) y
            {: L$ [  c( y2 I' T1 D0 Y
                // Inform the debug output stream of the module name8 `" p1 ]2 K+ L
                // and the init routine address before calling it               
6 O3 M, _, i/ R4 u% {                _DbgPrint(  "LDR: %wZ loaded.",
. S5 p4 i  d. U9 k6 o$ T2 J                            pModuleLoaderInfo->pModuleLoaderInfo );) r8 ^$ c( ~7 K) \0 @" Y

/ q/ ]0 V0 D. E6 o8 C                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);$ Q# {2 f' y3 c
            }3 @4 ]- w5 n0 X8 ?- P" l
                    ' x- r2 Q3 H7 A" w& y- q
            if ( pfnInitRoutine )
# {1 }/ b+ G, v" w) _( i            {- l1 f/ b: Q/ h5 p! |* _6 H$ B. ^
                // 设置DLL_PROCESS_ATTACH标志
" |/ I( f) \; g+ O7 r% h+ `* x                //
( X" D8 {' s6 Z+ ?1 }9 x                // (Shouldn't this come *after* the actual call?)
. N! [- e% T2 q7 B                //, e- Y0 W6 k- }8 \5 Z' ]6 X
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8             0 Z! i  O# u7 z) G
                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
; z7 [+ }0 S8 D; F
6 [3 C; P' _4 V% w# i0 F0 X) p                /// X- E. Y% v* a: O7 p0 N
                // If there's Thread Local Storage (TLS) for this module,7 V$ `4 R, m: ~
                // call the TLS init functions.  *** NOTE *** This only% F" ?& {4 ]3 u: P4 S" \
                // occurs during the first time this code is called (when
9 A. Q9 z# [6 |+ q                // implicitly loaded DLLs are initialized).  Dynamically
4 F3 P' a( \4 |                // loaded DLLs shouldn't use TLS declared vars, as per the
6 w7 U, B: J# k! E                // SDK documentation5 Z1 `' R5 |/ j2 b# R! ~, m% E
                // 如果模块需要分配TLS,调用TLS初始化函数& A8 y$ v6 ^7 r6 B
  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
0 [8 M2 j; k1 n5 c) f: K' m  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量( O5 O! v1 ~4 I( X% S+ c
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
/ o0 ~: H/ L, R                {
4 j- u- h& q$ E$ K9 q4 @4 V                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,# ?' A; T% `* s, Y) }- F9 _/ o3 a2 G! q0 t
                                                DLL_PROCESS_ATTACH );
- `# E+ N* _+ U! L4 C* D                }
, T% T" c1 J- l1 M               
4 f4 j. C* a- S6 Z* h3 {
; H- M1 e7 q; o5 L                hModDLL = pModuleLoaderInfo->hModDLL
& i' x+ F0 o4 m0 F, c" k( L
# t7 ^$ I9 b  b+ @, M                MOV     ESI,ESP // Save off the ESP register into ESI
  ?" w  l' D7 }( k  
$ w+ r1 [& i6 ~  w9 V; n  // 设置入口函数指针                0 Q, o' C4 |- ^5 \: c
                MOV     EDI,DWORD PTR [pfnInitRoutine]                     
0 D4 W; S: w9 d. {' r; [- m  t) S' M4 I: k7 D2 U5 A  Y
                // In C++ code, the following ASM would look like:8 T$ H; }3 ^: ^/ c& s. k, S
                //8 N2 C! f% [8 Z3 d% q
                // initRetValue =
6 h6 b, h( x+ ^0 a* q                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
4 {0 e* x6 _6 R# j0 x3 V' E                //
$ h  U+ l8 b: _1 ]$ d* d4 ~3 S6 y3 o! _
                PUSH    DWORD PTR [bImplicitLoad]' S6 t0 f( V2 l
                - W' Z. R  z1 z- Z; ?0 E- z* v8 i
                PUSH    DLL_PROCESS_ATTACH& K  {: k% X/ ^6 U5 C4 V. `  h
               
2 z! c$ Y. m0 _) a6 z, W  m                PUSH    DWORD PTR [hModDLL]
" d1 a" y) q  }  k               
4 ?; w) E- v# A5 b                CALL    EDI     // 调用入口函数
) o5 S/ Z4 r/ P* Z  q5 F                + `. b( b. t0 k8 {9 q
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值7 }! z- R" L. K3 \
  O7 t0 c: k  K9 D" ]- E
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the6 u2 h# q7 {+ a
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
& N  C2 x3 `' P) \2 R' N, Q: D2 {+ C; _
                MOV     ESP,ESI     // Restore ESP to value before the call5 n" I. }! k& d, @
- @" W8 A8 x5 @7 y0 y
                //
, _! X. Q8 \( s% J0 V- V  Y                // 检查调用前后的ESP值是否一至
/ x3 ?. ?! g1 v% h# M  // & U: \2 [4 t! x: `/ h/ a, Q% _
                if ( _CurSP != _SavSP )
. q* m2 y- I' K6 M, w6 @; r                {
5 d" v. h/ {3 Q                    hardErrorParam = pModuleLoaderInfo->FullDllPath;# A# {6 l- c* ?' m6 C, B6 U

" W: a9 w- f' `# T8 }0 x1 l9 Q/ A5 l                    hardErrorRetCode = ; a+ |* ~) t$ b0 |/ W
                        _NtRaiseHardError(5 N4 B6 i6 R" u) ], ]
                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
6 ?+ i5 W+ H" d; b* l                            1,  // Number of parameters
: Y2 G2 @$ L9 u6 o' f* @  x; _                            1,  // UnicodeStringParametersMask,
2 i* u9 i8 A: Z/ J: m6 A                            &hardErrorParam,
$ o  L- b# M7 G, P0 C' v$ c                            OptionYesNo,    // Let user decide
7 b1 o/ n) c9 T# f1 g: M                            &hardErrorResponse );' a: U8 F; k% o$ h2 k
                                            
7 @; C# x3 M6 S' {, x; t                    if ( _LdrpInLdrInit )! U3 t6 R. |+ x' a- I
                        _LdrpFatalHardErrorCount++;
( y# {5 Y+ n: S  a: F& Y% \0 ]$ j; |* }* c
                    if (    (hardErrorRetCode >= STATUS_SUCCESS)8 C. M: K* a8 j: c' y0 E, Z1 ?7 I
                        &&  (ResponseYes == hardErrorResponse) ): b. o9 c3 q1 o6 _  k- I
                    {0 X+ F7 L2 {/ {( D  ?/ H
                        return STATUS_DLL_INIT_FAILED;/ F3 u" e+ r7 K- T1 J9 j9 h5 W
                    }
8 T3 d! ]  F" |" i# T                }5 D% F5 G. I4 g& S  j
8 z1 M# E( e4 ]3 L' [+ a! k
                //
0 T8 X" o( @' f1 `, A                // 入口函数返回0,错误
4 q7 {0 P1 c# K3 _; b                //
4 N* h& I# Z9 f8 J4 H3 ]  V( z                if ( 0 == initRetValue )& H  Q' O- H6 b$ L
                {5 i1 N- g. x1 J6 E$ ~3 v
                    DWORD hardErrorParam2;
# }: Q) _3 o+ \, Z, |$ H, t                    DWORD hardErrorResponse2;( s5 n2 X+ ]' R  R2 S  a9 }
                                          F- Q7 ]  T/ R& E/ P: P+ A1 z
                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;* Y+ |8 F; W& a% s0 t( `! m2 @4 P3 [
                    
- S( S+ ]8 o7 w8 q4 _2 ~                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,& I# `6 m3 X  p- ]( S1 I. y% k& b
                                        1,  // Number of parameters- X) s! F6 j9 g4 `% r! M
                                        1,  // UnicodeStringParametersMask8 H0 _0 C, K0 m2 N$ V/ Z, {! h
                                        &hardErrorParam2,2 Q* f0 n( f% V$ c  G
                                        OptionOk,   // OK is only response
! A; o) H5 I& E. o% E8 U* t                                        &hardErrorResponse2 );/ G; B3 n& ^* R  Y
                                                            7 s, J$ U5 K) T
                    if ( _LdrpInLdrInit )9 U  [5 D7 j+ J8 ?/ y
                        _LdrpFatalHardErrorCount++;
$ [5 c0 X$ F) l/ p, q. e7 r1 E% P5 q4 y
                    return STATUS_DLL_INIT_FAILED;
- a( u' G. n4 d1 a                }2 _) h" c: a- N
            }; L- b6 k  h4 N1 P0 E8 N
        }' v) a1 O& O4 S5 u

* k" @, n) q* O9 a: w! w  `7 d        //
! H& p/ @+ d1 o: q* y, k. t# g        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时: A/ K8 H! n/ S0 ]1 F2 `
        //      
% e& I8 U9 W: G6 @5 j        if ( _LdrpImageHasTls && bImplicitLoad )& b9 N5 j+ }  Z# }3 C, x
        {
3 k; s3 Y3 ?% A$ e            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,! ^: H9 w3 ^: ~, C2 I
                                        DLL_PROCESS_ATTACH );
1 D; L2 ]* p$ r' U, X% I1 Q        }
0 n, ~6 \; m- ^    }
  v+ E. F' j) R! A2 O    __finally7 I' v8 F6 Q, ~) S: B, S0 h
    {
1 i) m/ w! a# g5 ]4 O. G        //
7 P) v# r/ \$ y' u5 p$ f+ x        // 第四部分;, R' W4 r- {0 W2 Q7 P+ P3 r
        // 清除分配的内存& W  Q  G4 g8 Y' ]* Q3 b4 u
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );2 n# C0 U6 q: m1 i, h
    }
5 s0 }: n  g& G! ^& |, Y% P6 m& }( Q) o3 v6 \+ G2 a7 P
    return STATUS_SUCCESS;
- V, N; T* q) f/ h' z8 h5 [}   
+ P0 u6 V* i2 K' \* e8 Y  h" W/ {  u8 Q2 |1 I
这个函数分为四个主要部分:
7 S% @6 d1 t4 [8 q, _* L一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。: P* p  i. S$ p6 e% R
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。# e6 b& o$ I0 Z6 G$ w* Y4 ]
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。9 ?0 U# h' W/ n9 p. ]9 N8 [
另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
. ?% E; m5 g- u+ w  L: `在最后的伪代码部分使用汇编语言来进行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 ~: _5 x/ V) Y$ Z
四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-9-22 09:59 , Processed in 0.062562 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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