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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
( L& p3 q: z* i5 O; K# f2 Q3 n# V
+ K$ D" U1 A8 i& ]0 BWINDOWS 2K Dll 加载过程
. Q/ P* O$ @: q$ Sjefong by 2005/03/30
7 o& E" v+ x5 N- q* e: Y8 M这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。1 K! t2 ]! A/ k& v+ `
在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”。  y1 i; N; ?+ Y, c1 U+ M! M- t
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。! [$ `6 U5 @4 v$ [6 e9 M! X+ O
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。0 S* U2 F# Q) {3 \1 a* y  f, l1 U& }
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。* F7 K% o! `9 W1 ]3 E/ e8 s! w
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。
* [, N* ~6 H' A0 X* W  \  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):' S6 r' m: W! b: B/ H
//=============================================================================9 R* s' ^6 |1 r# B& `6 z
// Matt Pietrek, September 1999 Microsoft Systems Journal
) N! C) p: @/ L/ A% m# q// 中文注释部分为jefong翻译
" t  P1 m. a( G: d: O* x//5 I& I) y0 F7 L: f9 V
// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)# o/ j1 R! ~8 b" T1 Y
//
8 k# I" @4 t; R( w* [0 H// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
  v1 l3 w! [: W6 {6 v//=============================================================================
4 q% M$ A; u6 z( F- ]
9 H& W" q/ t) I7 a#include <ntexapi.h>    // For HardError defines near the end
, P7 f5 y2 s2 V7 }- w, ?# P( _( p
5 c; B# G; x1 S: W! {, E1 }8 }// Global symbols (name is accurate, and comes from NTDLL.DBG)1 q0 P& A7 ]& h! H% \8 y
//  _NtdllBaseTag. S( p- r) ^; Y2 E# M% C
//  _ShowSnaps
" c& x8 v1 F4 @' ~  q//  _SaveSp" `" L/ Z1 F% o) A- `8 R
//  _CurSp8 _; j) c" x6 E( u% J
//  _LdrpInLdrInit
! t8 e/ c+ |' m3 Z& Q0 V//  _LdrpFatalHardErrorCount
& _$ m- ?) V- ?//  _LdrpImageHasTls( f! C" C" a( K' w
# J" w( L0 g) e# O: w6 M
NTSTATUS' U+ v. g( g$ |% B9 ]; N
LdrpRunInitializeRoutines( DWORD bImplicitLoad )% M: l# e0 @' Z& ?+ w( O. S& Y) v
{5 n6 `! C) ]. C8 x
    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了* g8 b8 I9 o) p, r
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();
- @( O7 Z9 V6 C6 O: }  ?. M5 a: Z4 I$ d5 L
    if ( nRoutinesToRun )
  c+ b8 V% X' h7 J- L    {+ S+ ]2 ~  @- V& a
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
8 m5 K2 }1 _& I* p* d; w: h* H2 N        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
9 k8 e$ T8 J. m3 f7 |1 T/ C                                            _NtdllBaseTag + 0x60000,  W. v+ u. s. O* Y- G
                                            nRoutinesToRun * 4 );: N- s- ^  g- U+ \4 B9 c# X
                           
" n; _' x3 p3 O0 O. F; N        if ( 0 == pInitNodeArray )    // Make sure allocation worked
1 A9 v1 z  j* o            return STATUS_NO_MEMORY;
. U5 D- `, g. v  m5 h7 O    }- }2 g. K/ d; `& d& J
    else
, K/ P# J  A* e# e. j        pInitNodeArray = 0;2 g' z# `- Z1 u$ [  v, b% C$ |

% u! C0 E- l; E" P: S% y$ m4 i    //第二部分;
. n8 }1 W7 [8 Z) ]8 e    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。5 T& I! A8 U$ f; g& ~2 e& g
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);
6 b2 L' u7 p2 \2 N4 Y    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;3 g- s! K, {4 |- v/ |  ?& X$ n
        3 M  H8 g# S  h
    if ( _ShowSnaps )
! ?8 J! \0 s. x1 u5 _/ t! b    {
7 _5 X# v6 W, p; E& E% g        _DbgPrint( "LDR: Real INIT LIST\n" );1 Y& w2 a- [8 `2 z1 C; x
    }+ z6 v0 I2 X' L& O" r

( }9 [- d- I- T& F+ |! k) S    nModulesInitedSoFar = 0;5 K0 D6 q& U7 y6 O& `

3 K7 E( @% X3 _' w* G+ `9 T/ r    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块" f: v! ?- d6 Y, S$ S3 \9 `
    {
, O" z$ o* H4 c+ [. w$ ]8 |6 r        
! u8 k1 J  n8 p$ d        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块
* r3 [# Y7 W9 s1 a5 r* G        {; Z1 X8 s% y8 Y& k! c
            ModuleLoaderInfo  pModuleLoaderInfo;
0 z! B& b# c% h' \# a4 `  b            
- U6 O# E: t$ u, m            //
0 f3 v2 ~6 m" Y3 r& `8 Y            //一个ModuleLoaderInfo结构节点的大小为0X10字节4 B. ?' W- ^" p" }" [
            pModuleLoaderInfo = &NextNode - 0x10;
4 @0 X, [. U/ Y+ G2 ~; S            6 C! c3 X: a3 n/ v' e; L
            localVar3C = pModuleLoaderInfo;         + V& |" M1 f: i$ U

0 P! [" K0 ~, |& W$ m8 x( T2 g            //
; _& s- a6 B- g4 v( g# ?& n0 H. M            // 如果模块已经被初始化,就忽略
0 G' @/ i/ ~! \% t7 p& `/ E/ ~% O8 l            // X_LOADER_SAW_MODULE = 0x40 已被初始化
( d' y) _' {- ?' V' @- j            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )& S4 v' G, c& ]+ S* D
            {8 L" U4 |/ ~) [) i6 O
                //- T7 R* ~" Q0 ^0 I) F1 O8 }7 R
                // 模块没有被初始化,判断是否具有入口函数
! w  A& X4 z! h) J                //+ x; S: R; a5 y  F' [! C+ R
                if ( pModuleLoaderInfo->EntryPoint )
1 Y7 F9 `: X# e3 I                {
3 z$ y5 O8 @3 ^- o2 r- u, F* b1 l                    //
1 n, ^4 U, A( q5 L                    // 具有初始化函数,添加到模块列表中,等待进行初始化
- o, f5 S  M$ K                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;
, o: d4 H/ D# c2 p4 S$ v7 S/ m3 Y! q: }! T# q! U  c) w! N7 e
                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址9 ~4 K  L' r7 |/ J* e( b
      // 例如:; j  |: X% V# K5 v# m8 f
                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000% A% i5 U0 z( `7 E
                    if ( _ShowSnaps )( m  \6 {/ a" c6 P  H
                    {5 Y& P& R+ [) U/ a+ k
                        _DbgPrint(  "%wZ init routine %x\n",
: E% {. X8 T# Z# w" o0 q+ m$ s                                    &pModuleLoaderInfo->24,
/ f  Y! T1 o$ S+ P0 \; `                                    pModuleLoaderInfo->EntryPoint );
$ s1 G. B$ W7 K/ a8 R1 b; K                    }' s! e1 M* f1 O3 h4 N6 h( ]

1 W4 n% ^, M5 R: I+ s                    nModulesInitedSoFar++;5 P0 K# F; E0 ^$ Z* b
                }8 _9 E4 C3 |1 m  f& M+ S4 P
            }; E& I' m$ E5 A

  v' \3 B6 p: p4 T  A: x            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。# x( N9 {) Q- r, j
            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;4 ~  @9 H6 E2 p
6 j4 y, R8 x" u  f0 F
            // 处理下一个模块节点
( ~" k& q& F2 h9 X! n            pCurrNode = pCurrNode->pNext9 o8 y$ z( E- H, ~) ?5 X5 p' u
        }6 P1 x' Y% K( A  }; S
    }. w, o' u7 B! V0 U: m, k
    else
7 U0 Q0 Y& ^7 b    {
: m* Q7 O: {4 \$ Z+ Y. l        pModuleLoaderInfo = localVar3C;     // May not be initialized???
- {( H9 a0 ^1 X: `+ \    }. n) n: T0 s; ?; r8 a+ A
   
$ I2 Z9 G9 G* r. T5 d    if ( 0 == pInitNodeArray )1 h% O( z! d0 ^  G+ k9 m
        return STATUS_SUCCESS;. E9 v- S6 H, g" h8 e8 o7 j! J
0 l; t; P; ?+ ]$ J
    // ************************* MSJ Layout! *****************, C/ d% O9 x$ S" e0 u+ _# h
    // If you're going to split this code across pages, this is a great
7 s8 T+ E4 E" m" x1 d7 N* T' z    // spot to split the code.  Just be sure to remove this comment
! k1 y2 V2 B8 ?& g6 R    // ************************* MSJ Layout! *****************1 m& z: Q0 _6 z/ U1 j: h
      [, M* Z$ c4 G$ s+ Q
    //& x2 ^4 w" L! p6 T
    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH$ ?: ^6 ?$ m7 p+ i  ~5 s
    // 第三部分,调用初始化部分& ]. N0 h, s: v# p; R
    try     // Wrap all this in a try block, in case the init routine faults3 q# z: N; u8 l0 ^6 S
    {
% z8 d  k* J0 d4 _6 s" x) o' G        nModulesInitedSoFar = 0;  // Start at array element 0
. _# S# t- Z8 o- u
  ?  m) Y$ c& ?" n6 K9 A1 M        //1 A7 Q: k$ \3 K8 q
        // 遍历模块队列: K0 }% M1 J/ l* D4 c8 U+ E
        //
1 ^/ W1 L; G; d( {+ ]        while ( nModulesInitedSoFar < nRoutinesToRun )+ r7 ]7 `$ H6 i
        {8 T8 Q% V4 ~! u% r  @
            // 获得模块指针
1 r" ^- q$ z2 o6 U            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];
/ f( X3 r- c* G* g3 w% I+ G+ J4 K/ K( K
. S1 R: f' x: w4 j- N& ]& f            // This doesn't seem to do anything...' Y$ }, l: f/ _
            localVar3C = pModuleLoaderInfo;: b  s2 b+ _8 u# O8 j; t
            7 v. w0 Z# f/ |* ?' F
            nModulesInitedSoFar++;7 i% c( _: X$ O3 P! @3 q9 o
               
1 G# J5 A0 z2 h, ~1 w5 {            // 保存初始化程序入口指针7 y& ^9 w# ^5 T) d2 Q7 W4 e% w! a
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;( C7 i0 S% C5 Q+ e
            # I& W8 \- G# }9 ?  d( u# [' G0 Y
            fBreakOnDllLoad = 0;    // Default is to not break on load
" y; M% ~% ?$ p, B: c
; I0 \* \2 q0 k* }            // 调试用
4 g* [$ l& {/ u7 b            // If this process is a debuggee, check to see if the loader$ N1 I3 O' w7 [
            // should break into a debugger before calling the initialization.
& h5 I# y; r7 k: C# I* N            //3 y2 d* ^  r5 L' ~- T3 F
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()
8 E+ I, Z3 x6 s( n, j/ m            // returns. IsDebuggerPresent is an NT only API.5 X7 @1 w( I; `
            /// W# x$ @3 T( h, @* V% g& |/ ~
            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
2 Q% p+ z) o3 j- T. |            {
9 l* S7 X+ ^1 M# [                LONG retCode;
' a9 n4 m' v& D* }1 y; e9 A. `2 u5 k
$ q' @% R2 E" S) O1 N6 M: ~% d" z                //              
/ w% @- O4 |9 H# S) @2 x8 S1 Q8 J/ F                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\. y" A. S( c% I: G
                // Windows NT\CurrentVersion\Image File Execution Options"
: b8 h/ n2 J8 T5 o                // registry key.  If a a subkey entry with the name of0 Z- U# U/ }6 K, {% {  Y, G# d
                // the executable exists, check for the BreakOnDllLoad value.' C  x6 V8 I/ _$ U
                //
* S* E# y0 ~: J5 p                retCode =
( X$ g7 `3 u' z- Q' h4 {! `( j7 y                    _LdrQueryImageFileExecutionOptions(. x) o5 g& J! b1 p8 Y5 m& K. g  @
                                pModuleLoaderInfo->pwszDllName,8 a: u9 ^% ^$ Q9 ]9 t& h
                                "BreakOnDllLoad",pInitNodeArray+ k! |+ C5 o+ X6 d" M8 t
                                REG_DWORD,
& _% I4 ]1 q6 `) i. o6 H                                &fBreakOnDllLoad,5 R8 L1 r4 P! z
                                sizeof(DWORD),
/ j' S. ~$ I+ N' w                                0 );) u& a4 A( V+ w# `4 b2 o
. b  W. T+ @+ {6 x, V, P) g
                // If reg value not found (usually the case), then don't9 p  n7 x+ M  _. x
                // break on this DLL init
: e( q3 Z1 Y% z9 }0 c; n' x                if ( retCode <= STATUS_SUCCESS )
$ O: A& |3 S2 Z7 Z5 Q                    fBreakOnDllLoad = 0;pInitNodeArray
  a# R! g- }; z, B, w            }& V. c3 v9 f" B+ o* U& r# E% l
            
. {7 m# k3 h# N' X* V% A0 j4 s4 W" ?            if ( fBreakOnDllLoad )
/ a/ O7 V% o3 T3 v            {           * L" W3 N) S+ I% {, m
                if ( _ShowSnaps )7 @/ d9 P- k! }5 l- m6 g+ C/ t
                {
8 n! q6 N; S9 u7 v5 H% i% u                    // Inform the debug output stream of the module name
- e$ c# y" e+ Y( S) P                    // and the init routine address before actually breaking
& K! ~. @; o# o% u; d* N5 o% A                    // into the debugger+ N& d' H$ L8 O) R) @1 O

3 S% Y, M  X9 E; N) k4 y2 p                    _DbgPrint(  "LDR: %wZ loaded.",
+ W' W7 h4 j! B5 x                                &pModuleLoaderInfo->pModuleLoaderInfo );
* r3 j. M8 o% I/ I6 p                    , e. r' L6 L3 }: f, J
                    _DbgPrint(  "- About to call init routine at %lx\n",5 N, O5 T, i  F+ R
                                pfnInitRoutine )
2 s) q! d. [1 w& u0 [; R$ b# y' n                }& m- y' E- e/ Z! B: Y7 Y
                  P0 X3 `8 B  X; o
                // Break into the debugger                              0 G- D1 L) U8 ~( `: K9 A- Y; T
                _DbgBreakPoint();   // An INT 3, followed by a RET7 m/ ~. E3 N( F# j8 `6 D4 o/ o6 w
            }) e9 ]$ @9 M$ ]- C2 @7 F! w( c
            else if ( _ShowSnaps && pfnInitRoutine )
$ i2 U" G5 u% J$ e, D            {
* z! o8 q) B2 @                // Inform the debug output stream of the module name
% f6 {/ Y( J; y2 V! V0 i8 _6 \                // and the init routine address before calling it               + V& Q; V+ E2 q9 ?! d  Z3 ^
                _DbgPrint(  "LDR: %wZ loaded.",: [% v: L, N0 W2 p+ B$ Y
                            pModuleLoaderInfo->pModuleLoaderInfo );
! J& n/ ?* U, [
- q) `; y6 b% a2 w                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
) x1 M+ S$ N; D2 C            }
' |& c1 z: x1 e1 n                    0 f3 P. k4 U* s, p- B( n
            if ( pfnInitRoutine )* O! y" f+ R; }- i/ @
            {  }3 j/ T- L! W. Z3 S
                // 设置DLL_PROCESS_ATTACH标志
% ^0 h& I' ]# w6 l                //
* L. {* b* a! J& x8 |; M  O+ w                // (Shouldn't this come *after* the actual call?)
' w" ]/ [- w& ^/ m- V2 J1 n& n                /// k; {2 ~- d7 {, F0 C
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
6 J* h  f' E6 a7 C- R5 o0 n, M' H                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;
. q/ c  d; u- O3 f& \/ |6 j/ @6 x, ^9 `2 Q3 S& H
                //
, i  F1 u' r8 i" H& {                // If there's Thread Local Storage (TLS) for this module,
5 g# x' s7 Z7 d                // call the TLS init functions.  *** NOTE *** This only
! V: U' B; `: A! x8 x& ?                // occurs during the first time this code is called (when
; j' o* V! w! ^                // implicitly loaded DLLs are initialized).  Dynamically
) Y- ^& |% T1 u4 O                // loaded DLLs shouldn't use TLS declared vars, as per the! l* G( B& O* q% x( ~' W4 x  P3 A
                // SDK documentation9 u  O" h) ]1 [0 U: S
                // 如果模块需要分配TLS,调用TLS初始化函数
/ [+ r$ Y" |, Z! w. U  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时# p/ E- Q! ^( i/ q: B
  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量. `4 x( ]: W: t& C# i- ^/ a/ k1 t
                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
& i' j, O  X" D* Y3 ~                {( c* f0 n7 f. d- W( g" D4 z
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
' C) O# \. z" U% X& K. F: ]                                                DLL_PROCESS_ATTACH );* W' w! m' W2 B4 p* r; M! p
                }2 \1 i2 f+ u, N2 D! `: z
               
& L5 s7 ~) @1 d) I
2 s# C: b% b# Y7 o' v+ V  k                hModDLL = pModuleLoaderInfo->hModDLL6 ]% z+ |' H7 G. z# c- b

9 W( r& R3 b: P9 r4 r  [                MOV     ESI,ESP // Save off the ESP register into ESI
* s3 o; V  ^  _+ R2 u$ C  P4 g  
* R7 y# q$ s9 b$ ]) w2 w  // 设置入口函数指针               
0 m+ W: C. y* P1 I                MOV     EDI,DWORD PTR [pfnInitRoutine]                     % Z2 a* K: S6 R+ {
- u6 E! {2 n/ r0 w/ p1 j# E7 }
                // In C++ code, the following ASM would look like:
/ Z" w8 e' o* Z4 ]& y                //
0 x2 J: n1 }4 h0 j                // initRetValue =
7 {  O9 z7 ]% t                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);
+ j  Y; I) V& L" e/ C9 }; }                //: y. b* @; L$ D4 V9 `  A3 Q+ o

. H7 q2 M; y1 F4 m# S2 K                PUSH    DWORD PTR [bImplicitLoad]
" g6 z  X7 J; J2 |/ a$ F               
3 y3 G! {6 k- O( w" Z6 D2 K) Y                PUSH    DLL_PROCESS_ATTACH
- ]/ p# T: |$ t3 O                ( a# d, }: k! b& \
                PUSH    DWORD PTR [hModDLL]2 V- S7 U' \* V  f
                / t) a. s$ G/ u  a; I
                CALL    EDI     // 调用入口函数
" J: r) ?$ E( `" I% x, Y5 W' c                ' h. b; l* J/ b& y
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值
. \" {9 O2 Z0 W% L
6 F$ g" a% w, g                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the* l3 I, H0 t: R. t* F6 h) h
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
- E7 A* h) k; j5 A; a, i6 f
( c3 O2 u! e( w' k0 _* a                MOV     ESP,ESI     // Restore ESP to value before the call( W3 o: p* D+ {+ [
0 N* v7 w' ~: \0 G+ N% j
                //
! T# {& x% [- K6 ]4 M                // 检查调用前后的ESP值是否一至1 I9 `( h+ `0 _( K& d* f
  // # F" `/ L4 Q4 Q
                if ( _CurSP != _SavSP )) Z7 q1 h" J' \5 i9 D9 n
                {, X: K( V" J6 ?2 C  I' \, K
                    hardErrorParam = pModuleLoaderInfo->FullDllPath;# q7 {( G3 P. g. A7 ~/ `2 N

% V, x. y& h; \" g- Y) K- d                    hardErrorRetCode =
+ ^# ], I: C# Q$ w  }                        _NtRaiseHardError(
" q% X( @! K$ s: M1 p+ n1 v5 X                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,1 Y, A0 f( J& Z1 }# m1 E" T/ K2 d' f
                            1,  // Number of parameters
: O/ K& U3 Y7 A) y3 S0 o                            1,  // UnicodeStringParametersMask,) N( b3 j6 Q! x) P
                            &hardErrorParam,
. ]0 l' z" B, r2 e% f                            OptionYesNo,    // Let user decide5 w8 W# G2 i3 ~9 t0 K/ @
                            &hardErrorResponse );/ e/ K' r# _( u. p
                                            
  l3 d9 a' t( C' T( r8 f  z                    if ( _LdrpInLdrInit )
3 O, P5 x# Q1 s- x. p                        _LdrpFatalHardErrorCount++;5 z8 W# Q2 \! p& r
* V) @$ n$ t+ a+ G/ M
                    if (    (hardErrorRetCode >= STATUS_SUCCESS)' A  v6 s# V' |8 m% s1 }
                        &&  (ResponseYes == hardErrorResponse) )" M) x) ?& n8 _
                    {/ g& Z5 |+ y8 ~+ f+ `& i9 @# M1 G
                        return STATUS_DLL_INIT_FAILED;
! Y0 E# v2 g# ?. [+ o6 x# y                    }
* p4 L( X1 J6 A$ G2 N                }
. D' b) x6 F3 u/ O. E8 s; H1 k" ?$ \" i0 t4 E
                //$ K! a# _1 C7 \4 H3 x# f
                // 入口函数返回0,错误
" {' Y, R! ^1 t8 j- d4 r                //
: G* j2 a! o1 G- ~- D" K/ K                if ( 0 == initRetValue )7 `6 ^& C# f- |  g. D/ h& M" L
                {
6 ?0 R8 v. g+ U: {! Z                    DWORD hardErrorParam2;
, f  S! I! T6 v+ i7 c; I                    DWORD hardErrorResponse2;5 C# y+ J" l- e: L1 l
                                        8 s. p8 {' s, Q; o; {
                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
& \/ Q) k1 B* K$ ~                    
! q% l" X, g6 c$ y& r                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,
0 n1 [  q$ ~( }# I7 ?* f                                        1,  // Number of parameters
& t! ?" Q6 O. j$ F5 I: {                                        1,  // UnicodeStringParametersMask6 G" X5 k/ n! _. Y( b* v
                                        &hardErrorParam2,
/ T) R- ?5 \) {                                        OptionOk,   // OK is only response
1 q9 q* H4 e4 n/ _+ A) u                                        &hardErrorResponse2 );$ G5 q! g1 t- X6 t4 W
                                                            , \( o; }9 L3 V% [
                    if ( _LdrpInLdrInit )
, s" h1 |4 S7 q1 o: R7 U) z                        _LdrpFatalHardErrorCount++;" s% _: E- S! W( a, D
) m; R; l( m8 ?' ?8 L% q
                    return STATUS_DLL_INIT_FAILED;6 Q! F( \, R0 v- n
                }- T! t* }$ |: g
            }
4 t, e& [- b: \" {' P& c4 j        }* X0 Q% d0 |( P* v! K

0 |: T' {3 D* F* o5 \        //9 h" F; r( q$ |1 w
        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时
: N3 d( I/ U2 G6 _! C4 Q        //      
( z; T. {. [( O+ y6 R% A& S4 @7 e0 z        if ( _LdrpImageHasTls && bImplicitLoad )$ p% h6 J5 {! _! W8 V
        {' Z: @3 e0 D; v; }
            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
+ q/ s& Y6 C1 f0 ^. n+ M6 e                                        DLL_PROCESS_ATTACH );9 ~) C. ?- q( Z* Q# B% X
        }
5 B; O  j: A6 H8 z% B: \) Y    }
1 N/ h3 G% f2 K! h7 R) Z- t+ n    __finally$ r" r9 J% B1 o& U! x
    {
; F. p- v2 d4 z4 i' o- w5 b        //
; D( G: }' `, A3 x5 \8 e7 t3 Y        // 第四部分;/ e, i& E+ X3 \
        // 清除分配的内存" H9 {& {2 Q- o/ m7 ~
        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );+ G% q, @0 C0 E% J2 H8 }; L
    }5 r" w7 x" Q& r9 A6 ~% n$ @1 Z* D& }, g

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

本版积分规则

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

GMT+8, 2026-6-13 20:45 , Processed in 0.079014 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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