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

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

[复制链接]
发表于 2007-11-16 12:16:34 | 显示全部楼层 |阅读模式
来自:[url]http://www.whitecell.org/forums/viewthread.php?tid=34[/url]
" {) d! g7 v# F! Z6 C5 `
& D5 @" @3 E7 o8 |  [WINDOWS 2K Dll 加载过程  ~5 G- T) F0 _* g7 L3 y0 v1 E# m- J
jefong by 2005/03/30; F2 w1 l/ Q, a) Q
这片文章是我在阅读完MSJ September 1999 Under the Hood后的总结。
; P' B* r. X% d# C2 J- _4 V  b( P在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”。: \# G: O- e" n. W7 h' b- Q
你的函数正在执行一个初始化任务,例如设置TLS,创建同步对象或打开一个文件。那么你在函数中一定不要调用LoadLibrary函数,因为dll加载命令会创建一个依赖循环。这点会导致在系统执行dll的初始化代码前就已经调用了dll的函数。例如,你不能在入口函数中调用FreeLibrary函数,因为这样会使系统在已经结束了dll后还调用dll中的操作,引起严重错误。0 |( P& o; y( M5 m' a  w8 ~7 M
初始化任务时调用Win32函数也会引起错误,例如调用User,Shell和COM函数可能会引起存储无效的错误,因为dll中一些函数会调用LoadLibrary来加载别的系统组件。  k0 U3 d0 e7 D
  当你在你的DllMain函数中读一个注册表键值,这样做会被限制,因为在正常情况下ADVAPI32.DLL在你执行DllMain代码时还没被初始化,所以你调用的读注册表的函数会失败。5 j) k; S5 B$ `9 h. p7 U
  在文档中初始化部分使用LoadLibrary函数是严格限制的,但是存在特殊的情况,在WindowsNT中USER32.DLL是忽略上面的限制的。这样一来好像与上面所说的相背了,在USER32.DLL的初始化部分出现了调用LoadLibrary加载dll的部分,但是没有出现问题。这是因为AppInit_Dlls的原因,AppInit_Dlls可以为任一个进程调用一个dll列表。所以,如果你的USER32.dll调用出现问题,那一定是AppInit_Dlls没有工作。& c& Q9 e+ H' u1 y9 F6 [* U3 Q! a
  接下来,我们来看看dll的加载和初始化是怎样完成的。操作系统有一个加载器,加载一个模块通常有两个步骤:1.把exe或dll映象到内存中,这时,加载器会检查模块的导入地址表(IAT),看模块是否依赖于附加的dll。如果dll还没有被加载到进程中,那么加载器就把dll映象到内存。直到所有的未加载的模块都被映象到内存。2.初始化所有的dll。在windows NT中,系统调用exe和dll入口函数的程序会先调用LdrpRunInitializeRoutines函数,也就是说当你调用LoadLibrary时会调用LdrpRunInitializeRoutines,当调用LdrpRunInitializeRoutines时会首先检查已经映射到内存的dll是否已经被初始化。我们来看下面的代码(Matt的LdrpRunInitializeRoutines伪代码):
- \" A' z; A# [2 i: W6 H//=============================================================================
( o% E2 E! e9 C7 \; {+ I// Matt Pietrek, September 1999 Microsoft Systems Journal4 _3 M/ S( _3 U0 ~
// 中文注释部分为jefong翻译# T) }9 y' s- Y- c
//
, r" U$ J" I; @- a3 @// Pseudocode for LdrpRunInitializeRoutines in NTDLL.DLL (NT 4, SP3)
( ]4 g/ j2 g8 s$ Z: {//7 C3 F4 P; F/ k' `0 c  K! {
// 当LdrpRunInitializeRoutines 在一个进程中第一次被调用时(这个进程的隐式链接模块已经被初始化),bImplicitLoad 参数是非零。当使用LoadLibrary调用dll时,bImplicitLoad 参数是零;
5 V* \! o! ]* [  {( `//=============================================================================0 F3 F8 Q1 k: p& J; i

7 i8 s! ~# p9 S( _2 [#include <ntexapi.h>    // For HardError defines near the end
0 ]5 K3 d" g0 y. W$ ]0 |3 u/ T0 [+ Y4 W
// Global symbols (name is accurate, and comes from NTDLL.DBG)8 n5 L. {" N6 w
//  _NtdllBaseTag
% ^4 A( G9 ]2 [5 ?. A$ m//  _ShowSnaps
2 V/ O% m; ~) D' j//  _SaveSp
% w% E6 ~; A6 |3 t8 e: Z//  _CurSp4 v5 o1 _$ Z, I* c* P$ A2 K
//  _LdrpInLdrInit
5 J$ L! W: f% k5 P7 G& A& [$ R8 m//  _LdrpFatalHardErrorCount
+ c! e0 \$ M- o//  _LdrpImageHasTls8 b4 c  k5 F6 ]% ]" @+ `' t8 ^
, l2 m& w* n* n. k
NTSTATUS4 y9 |  E9 {' ]: ?- L% N8 ~9 z
LdrpRunInitializeRoutines( DWORD bImplicitLoad )
' S9 i" \+ A0 y" v4 m{
" Q  k# W- D' ^. ^5 p5 H    // 第一部分,得到可能需要初始化的模块的数目。一些模块可能已经被初始化过了4 ^  w! T0 O3 z; q! T# e5 g
    unsigned nRoutinesToRun = _LdrpClearLoadInProgress();" L1 m3 c3 X9 r  y. {

+ N1 L& g9 R+ j/ t6 g2 l/ h, R    if ( nRoutinesToRun )
5 P1 H: ^& R% E: d    {4 Z6 G8 r! x4 }/ ?, m
        // 如果有需要初始化的模块,为它们分配一个队列,用来装载各模块信息。
2 W; }, ^$ F% I3 y' U$ M        pInitNodeArray = _RtlAllocateHeap(GetProcessHeap(),
( O0 I, H$ X9 ]7 |                                            _NtdllBaseTag + 0x60000,
1 D# g! A% V; M0 J) a( r: C                                            nRoutinesToRun * 4 );; _9 [' u8 a) e
                            & W% _/ v- [; _5 M0 N
        if ( 0 == pInitNodeArray )    // Make sure allocation worked, ]6 J  v8 C/ A+ a
            return STATUS_NO_MEMORY;
: ]0 s% u, S7 Z! m1 ^+ Y+ D    }" \, B; q9 Y3 [( o
    else& w7 F6 l; k- A9 Z
        pInitNodeArray = 0;
: k/ p: s: a. o3 K7 R
. j' f9 g1 j5 \! q; |: i& t    //第二部分;
* o. i6 t5 K6 @( J9 M    //进程环境块(Peb),包含一个指向新加载模块的链接列表的指针。  u) ?; k9 \- I0 U) G
    pCurrNode = *(pCurrentPeb->ModuleLoaderInfoHead);# {4 C& J# K! X6 w% s
    ModuleLoaderInfoHead = pCurrentPeb->ModuleLoaderInfoHead;7 Y5 K7 ^( @* X
        
9 F( G4 J0 Z2 L$ t5 ]    if ( _ShowSnaps )
: B4 p$ d. ~/ u3 x2 Z    {: a' R# H! v8 v  _# L6 k) u6 Y
        _DbgPrint( "LDR: Real INIT LIST\n" );
/ B- y" K# A/ p    }- \- T* [# Z- E6 S' j# Q

% J# x; n8 p; ]3 Q& Q. `    nModulesInitedSoFar = 0;
# R4 @# C& L+ x
' ~! |) O+ T' |7 b    if ( pCurrNode != ModuleLoaderInfoHead ) //判断是否有新加载的模块2 F# \& \7 }/ y( p. f
    {/ ]5 g" _# S3 [3 k
        4 b7 A, t+ c* K* n  x
        while ( pCurrNode != ModuleLoaderInfoHead ) //遍历所有新加载的模块) D5 p7 C9 r' Q) {/ s4 D2 {1 Y( A
        {$ d6 p6 U, E7 f6 O0 v. a
            ModuleLoaderInfo  pModuleLoaderInfo;
" H; N; C& R$ }7 Y/ l            
9 z( T2 B  g4 Q; @  [# J            //# e  u2 Y, N+ M4 O
            //一个ModuleLoaderInfo结构节点的大小为0X10字节2 {: f* L& q# P! U5 N5 w6 I
            pModuleLoaderInfo = &NextNode - 0x10;
+ W9 H" w' D. m% |6 S+ l0 T. f            6 b) {6 X% A/ o, `! S  e
            localVar3C = pModuleLoaderInfo;         7 K$ G  v, k# Z* N* h: m8 t2 M- {
; C3 F! m8 Z5 d8 L3 f0 ^2 E; A6 _
            //# n" H- a3 X. v
            // 如果模块已经被初始化,就忽略
( t; e1 {9 z8 D! U( _3 @! Q1 r            // X_LOADER_SAW_MODULE = 0x40 已被初始化% C! k' a$ s" w9 v9 u
            if ( !(pModuleLoaderInfo->Flags35 & X_LOADER_SAW_MODULE) )
* I! b5 r3 O- V  w" j: J, P  _; D5 `3 x            {0 p, J) ^6 J9 h
                //
/ y* B, o# V0 ^6 B1 o1 w5 f# i  ]2 I                // 模块没有被初始化,判断是否具有入口函数
5 A" _3 h8 u5 s7 d& K4 Q  A6 b                //3 F8 I4 Y) G$ K) c# V4 e
                if ( pModuleLoaderInfo->EntryPoint )
' _: L( @. q3 ^2 g: y7 ^& B                {
9 n) k1 R2 w+ Z9 u                    //
7 ]. \. i+ J- D+ J3 J4 F% {                    // 具有初始化函数,添加到模块列表中,等待进行初始化; |+ |6 ^( B2 x  J$ I' f
                    pInitNodeArray[nModulesInitedSoFar] =pModuleLoaderInfo;+ H( j# d. F* f2 ?9 t- d

0 {0 x8 c8 w  B  ]0 |. }. Y9 }                    // 如果ShowSnaps为非零,那么打印出模块的路径和入口函数的地址5 e% D4 _: N/ |5 |4 f% `
      // 例如:
# J/ ?  j1 ?3 R& y3 c: _7 z5 J                    // C:\WINNT\system32\KERNEL32.dll init routine 77f01000! f# U6 S. \' i3 ^, c% a! c7 ~- B/ g+ ^
                    if ( _ShowSnaps )
, m5 m4 [9 Z4 F( D                    {
2 y, T- _6 w4 _! z                        _DbgPrint(  "%wZ init routine %x\n",! ^1 z; u) ]* @0 u9 I
                                    &pModuleLoaderInfo->24,
8 v0 P$ R1 `( d# N0 ^8 A                                    pModuleLoaderInfo->EntryPoint );6 q. o& {" ?$ v" J
                    }
, x' m6 M; \4 s8 ]+ |6 n* R1 B+ K0 c6 F
                    nModulesInitedSoFar++;2 ^4 \# U6 [3 j
                }
$ q  Y! m9 K8 E            }9 b3 r- ^# ^/ A7 [1 D2 }: Y
; L8 \/ w; _4 X$ B0 H7 V1 E& \6 |
            // 设置模块的X_LOADER_SAW_MODULE标志。说明这个模块还没有被初始化。
0 A& F3 B" a. Y, J5 |: ^+ B            pModuleLoaderInfo->Flags35 &= X_LOADER_SAW_MODULE;
7 C% Q) ?0 |4 W
0 w7 A9 V$ C/ U2 h* G7 M2 R7 m            // 处理下一个模块节点. }) q4 b& A3 \2 ~
            pCurrNode = pCurrNode->pNext
/ ^! U6 P: V4 _% }, Q+ }8 A        }# k) N! \! h( A/ r& x. }
    }" B9 X6 S/ F. A: P1 Q6 F; H
    else- H) K3 z. Q* q
    {5 H9 H9 D$ `) y1 j1 S$ A; [
        pModuleLoaderInfo = localVar3C;     // May not be initialized???
! o) _" C$ f8 p6 h) \    }. ?% S7 ~3 R+ x% J1 F3 j/ R) X* n
    ! s8 a: U% v) k# W
    if ( 0 == pInitNodeArray )
; X3 }. R. A5 D1 f; u7 k        return STATUS_SUCCESS;& O6 S; `! B+ }" R6 c
! e$ |# w+ j% U) x6 B
    // ************************* MSJ Layout! *****************
) P% V- d, S# q# L9 l    // If you're going to split this code across pages, this is a great, [. i5 [* k8 c; b
    // spot to split the code.  Just be sure to remove this comment2 I( V4 ~, C7 k/ J# ?
    // ************************* MSJ Layout! *****************# h' f/ T5 U1 q, f* W* C
   
  n% D7 u) y/ G. a: m  c    //
, j5 o% g/ c$ x0 c8 H8 {0 v" j& z" P    // pInitNodeArray指针包含一个模块指针队列,这些模块还没有 DLL_PROCESS_ATTACH4 {/ V+ {' J7 p& m6 }8 Q/ v
    // 第三部分,调用初始化部分( A8 d+ z2 [3 B
    try     // Wrap all this in a try block, in case the init routine faults; Q. Y7 l/ f( g4 L- u1 c7 J, A# b
    {+ A9 t4 ~2 I* {+ A
        nModulesInitedSoFar = 0;  // Start at array element 0
4 V6 z/ d8 ]' y! z1 V9 c# s, @0 A$ L) |1 Q* X
        //
( V4 o' Q- m, K+ C4 c, N        // 遍历模块队列, ?: n5 p( J, d- W! c# t. a
        //
' t3 a4 q' _# C& U/ o6 K2 \+ a; _        while ( nModulesInitedSoFar < nRoutinesToRun )
1 m+ S+ Q$ W2 E        {/ u1 v9 E# G( [  n1 f& k# [  `
            // 获得模块指针* Y( n! }  `6 y& @2 n( j
            pModuleLoaderInfo = pInitNodeArray[ nModulesInitedSoFar ];3 P5 d) \& C& x

6 q' m1 m/ K3 P' n4 [* Q) R            // This doesn't seem to do anything...
; [' b' H1 ?& @. f: k3 s* A            localVar3C = pModuleLoaderInfo;2 C; }) Z# z/ c" [) w
            , N) P" n" Q* |3 E
            nModulesInitedSoFar++;9 O- r; m8 c$ N' ~$ W
                9 a, v5 R' Y% ]& b; l
            // 保存初始化程序入口指针& y0 \' x- R8 K! d' m% S4 n1 k  F
            pfnInitRoutine = pModuleLoaderInfo->EntryPoint;
1 a; R3 w# [# w, n- Q( A' R  y. j' w            : K, s  H  L5 I* N8 A) U
            fBreakOnDllLoad = 0;    // Default is to not break on load
9 h; B) j( Z" X+ F' R) l( d2 D% {( v2 s' a- g, x. p
            // 调试用
& C& o" I& l% F! f& t            // If this process is a debuggee, check to see if the loader
8 \, c  g8 D0 r, E  ~! k8 l            // should break into a debugger before calling the initialization.6 u7 t7 m2 O9 S+ O5 U
            //, P; S, j6 H: O& v$ e8 M) B' d
            // DebuggerPresent (offset 2 in PEB) is what IsDebuggerPresent()0 j2 [+ f! {5 Z) e7 e; d
            // returns. IsDebuggerPresent is an NT only API.4 Z' f, x6 {7 i# L0 \6 B
            //
" R9 n+ y' E2 o3 w0 Q- f            if ( pCurrentPeb->DebuggerPresent || pCurrentPeb->1 )
5 m  v3 }" U/ ~( M. x3 Y( W; z            {
" j" p8 E) ~: l% I                LONG retCode;
( A6 j, ~# V" d8 \  t0 o# x1 r0 o+ Z$ p$ A8 P
                //              
" B1 T# I/ b  i                // Query the "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
. {- m6 D% |( z; J                // Windows NT\CurrentVersion\Image File Execution Options"
4 i7 O  |0 s, A& \                // registry key.  If a a subkey entry with the name of
; U& r9 {& t8 N) Y' W' M% y  C! k* c8 A% m                // the executable exists, check for the BreakOnDllLoad value.: G7 ^3 ?( B/ |& y% \
                //6 _6 |1 b2 G, c: ^) v2 s
                retCode = . j. h: ~6 d5 ]+ ^
                    _LdrQueryImageFileExecutionOptions(6 a9 i' f4 k. R" a
                                pModuleLoaderInfo->pwszDllName,
' M/ L, x% }% t                                "BreakOnDllLoad",pInitNodeArray
3 X, X# R7 A' v2 ]                                REG_DWORD,
5 f- l; b  M/ _% o                                &fBreakOnDllLoad,: ~% K/ }0 J/ C0 R0 Y( X
                                sizeof(DWORD),
8 w/ f% q2 G7 a' A                                0 );5 x* ?3 C# v) {+ v

: G+ m& b% P) m                // If reg value not found (usually the case), then don't
5 D' G7 G6 S7 v6 w+ g; [. ?                // break on this DLL init6 h( Z: ]" P) z5 K7 `! i
                if ( retCode <= STATUS_SUCCESS )
: F4 z  X% `9 k1 e                    fBreakOnDllLoad = 0;pInitNodeArray
/ p3 O" P: ?* b& ]6 |( p! b: g8 g' u            }
4 l  D& v4 m% c3 T4 J8 o            
" h8 v# i( E" L- K. z1 N6 C            if ( fBreakOnDllLoad )
' ?" V$ A; N+ r- m' f, h            {           0 n3 k8 X' R' z  H  R' M) M
                if ( _ShowSnaps )
9 \* R2 `1 {, H1 u                {
! V4 W0 n" s( m6 t- P1 q                    // Inform the debug output stream of the module name
* r3 q: x8 H" c% u7 h: c- f5 z                    // and the init routine address before actually breaking
# O2 x: }6 G* r" u9 G, k+ K7 a                    // into the debugger
2 \! _* s  N% f3 O* E3 X
( H( C$ v, }+ m+ |# a$ p3 ~' v# K                    _DbgPrint(  "LDR: %wZ loaded.",0 |! f; u$ n- Y% W( t# {8 Q4 S0 m
                                &pModuleLoaderInfo->pModuleLoaderInfo );, u2 @6 v* |& M& D5 M' i6 T& m
                    ( ]6 ~5 O. ^$ y# \- z: K" D; C2 }
                    _DbgPrint(  "- About to call init routine at %lx\n",3 y* ~: g  a/ P5 ?5 S
                                pfnInitRoutine )
5 c4 K: E! x4 R# c$ M& f3 [1 T5 C                }
! k9 e+ G& L! \' ]) G1 m- f4 H               
3 j/ t1 y1 B0 m" h6 u4 z                // Break into the debugger                              
% ]4 N' W& v% a! y                _DbgBreakPoint();   // An INT 3, followed by a RET
: s$ N  K  E( M' k) }  H            }
. P$ ]! C+ z" Z            else if ( _ShowSnaps && pfnInitRoutine )
, y6 q4 u) w, @9 s- \* L# e- |5 R; }            {
& o, T9 ?: }4 ]9 |                // Inform the debug output stream of the module name4 q: z" ~$ q% ?) |
                // and the init routine address before calling it               7 i" t5 K# E! n- A
                _DbgPrint(  "LDR: %wZ loaded.",+ w* I7 J- N7 j8 a( b. U. ]# ~5 L
                            pModuleLoaderInfo->pModuleLoaderInfo );
4 q& q4 ^9 G. y! E6 M1 d4 I1 l
+ A$ k9 S" H4 W9 q                _DbgPrint("- Calling init routine at %lx\n", pfnInitRoutine);
$ |8 T* \- b. A7 `" f* S            }2 m0 U7 e" a9 n0 M. S- F* W
                    
0 t3 N1 g0 A- l0 n            if ( pfnInitRoutine ). o' @, L8 C- e8 |- v$ t1 q! y+ }4 x
            {' t. P& D1 E7 r" g
                // 设置DLL_PROCESS_ATTACH标志
$ F8 N& M) e1 Q2 d) F                //
0 d* L/ ~$ }# `; h                // (Shouldn't this come *after* the actual call?)) \* T9 Q9 ]8 W$ V
                //4 U# k/ g: |8 [
                // X_LOADER_CALLED_PROCESS_ATTACH = 0x8            
' ]% G1 N/ ~6 r+ z, a9 m$ X  Q                pModuleLoaderInfo->Flags36 |= X_LOADER_CALLED_PROCESS_ATTACH;! m3 M; ~+ Y4 u
" F+ t! I1 s. |" @3 Y. e3 H
                /// h( j; A4 q& B+ }. r" a: i
                // If there's Thread Local Storage (TLS) for this module,
6 h4 B+ E9 t/ U" ~- ^( @                // call the TLS init functions.  *** NOTE *** This only/ n6 g' n. L' I- }
                // occurs during the first time this code is called (when
, V6 `7 A- i( V  R' l! G5 c                // implicitly loaded DLLs are initialized).  Dynamically  M) _* t7 j" G! y: C0 M
                // loaded DLLs shouldn't use TLS declared vars, as per the
) O" y/ f4 s  y" s+ _                // SDK documentation
, ?* _% p- p1 [% {: S/ a                // 如果模块需要分配TLS,调用TLS初始化函数
1 m$ C+ G  q4 k  // 注意只有在第一次调时(bImplicitLoad!=0)才会分配TLS,就是隐式dll加载时
  d8 f) m6 A0 T  // 当动态加载时(bImplicitLoad==0)就不需要声明TLS变量
3 ?* _3 {7 d( Z: U* y: F                if ( pModuleLoaderInfo->bHasTLS && bImplicitLoad )
' l" b# x) k' W+ |7 S+ t+ M  v                {3 ?0 J7 ^5 |2 W' ~  Z  \
                    _LdrpCallTlsInitializers(   pModuleLoaderInfo->hModDLL,
( J7 n- V% X1 }2 T                                                DLL_PROCESS_ATTACH );
% P' }1 h  A5 c# ]% O2 I" s: z                }
# @5 E" {4 ]% T% f# ?4 J1 Z5 ^& P               
& W5 I3 V- l/ X' ~4 K! H
2 [$ ^3 n8 a4 f+ @' D/ B; D                hModDLL = pModuleLoaderInfo->hModDLL" L/ ^( K' X7 R) g1 e

) V4 T7 n  q6 }8 K% G- }7 `7 Y                MOV     ESI,ESP // Save off the ESP register into ESI( i7 C: f: m* v+ t" y7 O1 K+ x
  ! g, v7 ~) h7 E6 _3 M! z
  // 设置入口函数指针               
8 a0 [6 [% I% d, l/ _9 C$ I                MOV     EDI,DWORD PTR [pfnInitRoutine]                     6 e+ t+ f/ r  H, \! p- U
% h- u" P7 U1 s" B+ S
                // In C++ code, the following ASM would look like:
% g! O& X' }. H# `7 t                //
# A- f$ n, _6 z# c  N* y5 |                // initRetValue =
/ t! |( @5 s% W                // pfnInitRoutine(hInstDLL,DLL_PROCESS_ATTACH,bImplicitLoad);- k2 K/ }. z3 |3 d% S0 u
                //
" g! |* r, Y) u' g/ Q2 y% h1 r) a6 ^" N7 m* d5 m
                PUSH    DWORD PTR [bImplicitLoad]
9 K. t0 v9 ?! k0 E6 v7 i                8 G3 Y( o6 o# N6 I
                PUSH    DLL_PROCESS_ATTACH
$ J9 H# L2 V' `3 n7 [. z               
5 L: a3 H& s# d+ i5 z. Q' l                PUSH    DWORD PTR [hModDLL]
. U. W$ z0 {/ ~1 v               
$ @3 v1 r7 B. a4 P' ^                CALL    EDI     // 调用入口函数
+ z; Y8 n- I0 I- a; D: o( u4 I( c) \                , t( z. a) @/ Z- o
                MOV     BYTE PTR [initRetValue],AL  // 保存入口函数返回值& U, Q/ N( ]* a5 H  X" P
. ?6 b6 f4 q  |1 B# h& v3 S
                MOV     DWORD PTR [_SaveSp],ESI // Save stack values after the* K) p( e, S1 K. c1 Z& v
                MOV     DWORD PTR [_CurSp],ESP  // entry point code returns
9 u' P5 `6 g$ Y; D6 |) }6 Q7 d  C3 @! v
                MOV     ESP,ESI     // Restore ESP to value before the call
9 j! y( O# F7 b3 \- Y' p- F. T9 W6 e/ o1 j0 j# g7 K. M
                //7 a& G& X& k5 D$ ^! ?
                // 检查调用前后的ESP值是否一至2 \& S4 j, p. `9 Q) p
  // 1 ?/ l  i- B2 m( X6 J
                if ( _CurSP != _SavSP ). |' U: ^2 ]/ L4 Y( Q
                {
- ^4 A' ]5 a5 U$ o& L) w  G                    hardErrorParam = pModuleLoaderInfo->FullDllPath;
9 c, }! R: z: ]6 Z7 n1 X# ?- y9 i3 S) i) y3 B7 H% F
                    hardErrorRetCode =
1 a/ \: B* Q6 V- b) U% ?: g5 Z                        _NtRaiseHardError(
% n2 P" e2 R) ~; I6 }4 @5 T                            STATUS_BAD_DLL_ENTRYPOINT | 0x10000000,
/ R) ~3 F5 E; D                            1,  // Number of parameters. g* A" v3 B1 r, H& z3 O
                            1,  // UnicodeStringParametersMask,
! d- V$ P% B" h/ @$ Q6 H5 H# U- u                            &hardErrorParam,
# E0 o: G% g0 W) Z, p6 B. T                            OptionYesNo,    // Let user decide0 K3 y; I3 M2 O# O. F
                            &hardErrorResponse );$ x& v: q1 j! q7 S3 V/ X1 J
                                            
, v2 o' d  ?% B- O5 u' r0 F! m                    if ( _LdrpInLdrInit ). e& X  x7 u. z4 w" `
                        _LdrpFatalHardErrorCount++;
- j- E% [3 d/ h5 u8 D% o1 y# X% l6 _' T
                    if (    (hardErrorRetCode >= STATUS_SUCCESS)
2 |) R' }2 S- s1 D/ g& D                        &&  (ResponseYes == hardErrorResponse) )% m- k/ \5 J5 r& {/ f% T7 r2 ]
                    {( C: S' [# W! R6 E
                        return STATUS_DLL_INIT_FAILED;& s! _' S  Y2 T0 x8 E! ~
                    }: c9 i# j2 }0 x4 U9 N5 t
                }. G: u& J+ ?# ]8 U
7 `! ]. J* h# ~. N
                //
; i) i: T" e0 S8 B  K                // 入口函数返回0,错误
6 s+ o. L8 C6 T                //7 l, I2 u% {' W  y3 b8 t
                if ( 0 == initRetValue )+ K% i# r+ p. }0 ?
                {
, E9 W! o6 R5 i                    DWORD hardErrorParam2;! C7 I4 J7 u+ X( j; M9 c  B* T
                    DWORD hardErrorResponse2;2 C% B. E; R4 b
                                       
8 n* |  p8 R% P% z/ m. ]& D                    hardErrorParam2 = pModuleLoaderInfo->FullDllPath;
' ?9 ], }# \) Q8 v. g- V                    ; o5 t4 Y9 R. h- R
                    _NtRaiseHardError(  STATUS_DLL_INIT_FAILED,0 ?0 a4 K/ ?& l" q! a: k" Y
                                        1,  // Number of parameters2 V" R3 I8 b. p1 P. |. ~; m5 v" e
                                        1,  // UnicodeStringParametersMask$ \) Q% N; B! B$ b  ]( P
                                        &hardErrorParam2,- N' k7 @' `. V& N
                                        OptionOk,   // OK is only response) M* V. K: H8 k2 f
                                        &hardErrorResponse2 );. p9 c8 F% k9 p% z3 O
                                                            
+ K2 Y/ q% d0 u) X                    if ( _LdrpInLdrInit )
7 V3 K/ l* U$ w( L1 m. x                        _LdrpFatalHardErrorCount++;
" A* ]% |* @- F; k" y
( e5 y' t0 q+ B8 T                    return STATUS_DLL_INIT_FAILED;& |* M  L" k* I
                }
+ w% F- w" }5 D7 R0 e# f            }- o' Z+ V! F- h$ P. S
        }
5 H$ @, n: c* j  i
5 r) e' W4 O* m" b9 z3 T5 B        //
4 w1 [: U* x: f. @+ H        // 如果EXE已经拥有了TLS,那么调用TLS初始化函数,也是在进程第一次初始化dll时- o7 O6 F$ ^: g8 }6 q
        //      * u  P! l- v, Z
        if ( _LdrpImageHasTls && bImplicitLoad ); Y$ y- w- |7 W$ o5 Y; h
        {
" p& p8 r9 ?9 W  b            _LdrpCallTlsInitializers(   pCurrentPeb->ProcessImageBase,
/ x) g( v1 ~' f  q9 [# |                                        DLL_PROCESS_ATTACH );
# d3 Z4 k8 M$ e" _! g) Q1 `- Z        }" u$ b0 t! A6 v6 b
    }7 F8 P  P8 v. Z, o! i8 d
    __finally
0 {- Y9 N+ \% t: W    {
7 O2 Q1 D' A$ h3 L+ }        //: U7 p# F; |7 P! V. t; I
        // 第四部分;5 G% S9 Z0 l- _) [4 w
        // 清除分配的内存
+ r# {8 g% s2 r) W! f/ S        _RtlFreeHeap( GetProcessHeap(), 0, pInitNodeArray );9 g: k0 Y8 Y" A4 ]
    }
8 O6 P. d6 w, P' _% C& B7 z3 H, o# |! K, U
    return STATUS_SUCCESS;
' p$ z) h' {/ T' F0 _; z" {2 h8 D6 r}   
, ], N* g) m: w; K, x7 w+ b' ]: ^/ F9 H  c! a) |/ l  k+ v" W
这个函数分为四个主要部分:& S% \$ T- O$ J( X# m4 X3 n, {% v
一:第一部分调用_LdrpClearLoadInProgress函数,这个NTDLL函数返回已经被映象到内存的dll的个数。例如,你的进程调用exm.dll,而exm.dll又调用exm1.dll和exm2.dll,那么_LdrpClearLoadInProgress会返回3。得到dll个数后,调用_RtlAllocateHeap,它会返回一个内存的队列指针。伪码中的队列指针为pInitNodeArray。队列中的每个节点指针都指向一个新加载的dll的结构信息。" X" e: R; v3 l. u
二:第二部分的代码通过进程内部的数据结构获得一个新加载dll的链接列表。并且检查dll是否有入口指针,如果有,就把模块信息指针加入pInitNodeArray中。伪码中的模块信息指针为pModuleLoaderInfo。但是有的dll是资源文件,并不具有入口函数。所以pInitNodeArray中节点比_LdrpClearLoadInProgress返回的数目要少。% ~/ v0 r' z6 ^2 P& j) x/ g% }& L& n! z
三:第三部分的代码枚举了pInitNodeArray中的对象,并且调用了入口函数。因为这部分的初始化代码有可能出现错误,所以使用了_try异常扑获功能。这就是为什么在DllMain中出现错误后不会使整个进程终止。
9 @& s% W% o% m7 C另外,在调用入口函数时还会对TLS进行初始化,当用 __declspec来声明TLS变量时,链接器包含的数据可以进行触发。在调用dll的入口函数时,LdrpRunInitializeRoutines函数会检查是否需要初始化一个TLS,如果需要,就调用_LdrpCallTlsInitializers。
4 F4 p; c5 |( 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。
. P8 n, K1 f6 ~" H# F四:第四部分代码是清理代码,象_RtlAllocateHeap 分配的pInitNodeArray的内存需要被释放。释放代码出现在_finally块中,调用了_RtlFreeHeap 。
您需要登录后才可以回帖 登录 | 加入计匠网

本版积分规则

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

GMT+8, 2026-4-24 22:37 , Processed in 0.040125 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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