飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 1755|回复: 2

[C/C++] 类中未调用的成员方法会被编译进EXE吗?

[复制链接]

该用户从未签到

发表于 2010-1-16 17:38:43 | 显示全部楼层 |阅读模式

编译器的解决方案
应该会先将类编译为 obj 文件,然后将这些obj进行逐个拼接来生成最终的EXE
拼接过程会将类中所有成员方法全部COPY到EXE进行封装吗
还是在最终的拼接中只提取需要的成员方法 根据其偏移地址来填补调用函数的地址或其他

//////////////////////////////////////////////////////////////////////////
// 类方法声明
//////////////////////////////////////////////////////////////////////////
class Test  
{
public:
        Test();
        virtual ~Test();
public:
        int m_a;
        int m_b;
public:
        int Add();
        void Set(int a,int b);
};

//////////////////////////////////////////////////////////////////////////
// 类方法实现
//////////////////////////////////////////////////////////////////////////
int Test::Add()
{
        return m_a + m_b;
}       

void Test::Set(int a,int b)
{
        m_a = a;
        m_b = b;
}
//////////////////////////////////////////////////////////////////////////
// main函数调用
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
        Test tt;
        tt.Set(10,20);
        printf("%d %d",tt.m_a,tt.m_b);
        return 0;
}
//////////////////////////////////////////////////////////////////////////
// main函数结束
//////////////////////////////////////////////////////////////////////////

在Debug编译模式中可见:

@ILT+0(??1Test@@UAE@XZ):
00401005   jmp         Test::~Test (004011c0)
@ILT+5(?Add@Test@@QAEHXZ):
0040100A   jmp         Test::Add (00401200)          // 在Debug模式下确实是有Add这个未使用的方法的
@ILT+10(??_GTest@@UAEPAXI@Z):  
0040100F   jmp         Test::`scalar deleting destructor' (00401150)
@ILT+15(??_GTest@@UAEPAXI@Z):
00401014   jmp         Test::`scalar deleting destructor' (00401150)
@ILT+20(?Set@Test@@QAEXHH@Z):
00401019   jmp         Test::Set (00401240)
@ILT+25(_main):
0040101E   jmp         main (00401050)
@ILT+30(??0Test@@QAE@XZ):
00401023   jmp         Test::Test (00401110)

注意哦 先调用一下该方法 编译后EXE中一定会有该代码 想要测试的话 需要先Clear之后再进行搜索哦 就会发现VC已经不搭理他了


那 Release 版会是什么情况呢? 如果说Debug版是为了我们调试才全盘封装的,Release版是否会将这些无用代码阉割呢 ?
那有如何来得出最终结论呢?得出这个结论又有什么作用呢?

这里提供两种思路:
一种是在未调用方法中 输入一个特殊字符串,我们根据生成的文件中是否有该字符串来判定;
另一种是先调用下该函数(如Add),调用后用OD等工具动态跟踪出该函数编译后的源码,将其特征码提取

004010C0  /$  8B41 08       MOV EAX,DWORD PTR DS:[ECX+8]             ;  m_b
004010C3  |.  8B51 04       MOV EDX,DWORD PTR DS:[ECX+4]             ;  m_a
004010C6  |.  03C2          ADD EAX,EDX                              ;  m_a + m_b
004010C8  \.  C3            RETN                                     ;  return eax

特征码为:8B 41 08 8B 51 04 03 C2 C3

然后用工具进行内存搜索 若有便可得出真的编译进去了 ~
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2010-1-16 18:02:38 | 显示全部楼层
//////////////////////////////////////////////////////////////////////////
// main 函数样本
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
        Test tt;
        tt.Set(10,20);
        tt.Add();
        printf("%d %d",tt.m_a,tt.m_b);
        return 0;
}
//////////////////////////////////////////////////////////////////////////
// main 函数结束
//////////////////////////////////////////////////////////////////////////

00401000  /$  6A FF         PUSH -1
00401002  |.  68 A8684000   PUSH test.004068A8                     ;  SE handler installation
00401007  |.  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]               ;  开始处理异常
0040100D  |.  50            PUSH EAX
0040100E  |.  64:8925 00000>MOV DWORD PTR FS:[0],ESP
00401015  |.  83EC 0C       SUB ESP,0C                             ;  把ESP向上移三格
00401018  |.  8D4C24 00     LEA ECX,DWORD PTR SS:[ESP]
0040101C  |.  E8 5F000000   CALL test.00401080                     ;  构造
00401021  |.  6A 14         PUSH 14
00401023  |.  6A 0A         PUSH 0A
00401025  |.  8D4C24 08     LEA ECX,DWORD PTR SS:[ESP+8]           ;  以为压两个参数 这里 ESP+8 为this指针
00401029  |.  C74424 1C 000>MOV DWORD PTR SS:[ESP+1C],0            ;  给 0 做什么
00401031  |.  E8 9A000000   CALL test.004010D0                     ;  tt.Set()
00401036  |.  8D4C24 00     LEA ECX,DWORD PTR SS:[ESP]             ;  这里ECX位置是虚表
0040103A  |.  E8 81000000   CALL test.004010C0                     ;  tt.Add
0040103F  |.  8B4424 08     MOV EAX,DWORD PTR SS:[ESP+8]
00401043  |.  8B4C24 04     MOV ECX,DWORD PTR SS:[ESP+4]
00401047  |.  50            PUSH EAX
00401048  |.  51            PUSH ECX
00401049  |.  68 40804000   PUSH test.00408040                     ;  ASCII "%d %d"
0040104E  |.  E8 9D000000   CALL test.004010F0
00401053  |.  83C4 0C       ADD ESP,0C                             ;  自动平衡堆栈
00401056  |.  8D4C24 00     LEA ECX,DWORD PTR SS:[ESP]
0040105A  |.  C74424 14 FFF>MOV DWORD PTR SS:[ESP+14],-1           ;  ESP+14 给 -1 做什么
00401062  |.  E8 49000000   CALL test.004010B0
00401067  |.  8B4C24 0C     MOV ECX,DWORD PTR SS:[ESP+C]           ;  ??
0040106B  |.  33C0          XOR EAX,EAX
0040106D  |.  64:890D 00000>MOV DWORD PTR FS:[0],ECX               ;  ??
00401074  |.  83C4 18       ADD ESP,18
00401077  \.  C3            RETN


--------------------------------------------------

构造的时候 往虚表中传入了一个地址
构造函数:
00401080  /$  8BC1          MOV EAX,ECX
00401082  |.  C700 C0704000 MOV DWORD PTR DS:[EAX],test.004070C0  // 其中0X004070C0 中存放 0X0401090
00401088  \.  C3            RETN

构造和析构都给[ECX]一个奇怪的函数指针:

00401090   .  56            PUSH ESI
00401091   .  8BF1          MOV ESI,ECX
00401093   .  E8 18000000   CALL test.004010B0
00401098   .  F64424 08 01  TEST BYTE PTR SS:[ESP+8],1
0040109D   .  74 09         JE SHORT test.004010A8
0040109F   .  56            PUSH ESI
004010A0   .  E8 51040000   CALL test.004014F6
004010A5   .  83C4 04       ADD ESP,4
004010A8   >  8BC6          MOV EAX,ESI
004010AA   .  5E            POP ESI
004010AB   .  C2 0400       RETN 4
004010AE      90            NOP
004010AF      90            NOP
004010B0  /$  C701 C0704000 MOV DWORD PTR DS:[ECX],test.004070C0
004010B6  \.  C3            RETN

析构函数:
004010B0  /$  C701 C0704000 MOV DWORD PTR DS:[ECX],test.004070C0
004010B6  \.  C3            RETN


这里一些代码还是没看明白 尤其是构造和析构时 给该对象虚表中一个特殊的函数指针
还有就是 main入口和结束时对异常链的处理 不太懂为什么是这些代码

/////////////////////////////////////////////////////////
//  看一下入口的这点东西
/////////////////////////////////////////////////////////

00401000  /$  6A FF         PUSH -1
00401002  |.  68 A8684000   PUSH test.004068A8                     ;  SE handler installation
00401007  |.  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]               ;  开始处理异常
0040100D  |.  50            PUSH EAX
0040100E  |.  64:8925 00000>MOV DWORD PTR FS:[0],ESP
00401015  |.  83EC 0C       SUB ESP,0C                             ;  把ESP向上移三格
00401018  |.  8D4C24 00     LEA ECX,DWORD PTR SS:[ESP]             ;  ESP 已经到 -18 了
0040101C  |.  E8 5F000000   CALL test.00401080                     ;  构造

到构造时 ESP 已经退到 -18了

$-18     > 0000D9D2
$-14     >/0012FFC0
$-10     >|00403C44  RETURN to test.00403C44 from kernel32.SetUnhandledExceptionFilter
$-C      >|0012FFB0  Pointer to next SEH record
$-8      >|004068A8  SE handler
$-4      >|FFFFFFFF
$ ==>    >|004015B5  RETURN to test.<ModuleEntryPoint>+0B4 from test.00401000

--------------------------------------
Set() 函数
--------------------------------------

00401021  |.  6A 14         PUSH 14
00401023  |.  6A 0A         PUSH 0A
00401025  |.  8D4C24 08     LEA ECX,DWORD PTR SS:[ESP+8]           ;  因为压两个参数 这里 ESP+8 为this指针
00401029  |.  C74424 1C 000>MOV DWORD PTR SS:[ESP+1C],0            ;  给 0 做什么 // 这里的是是压入 main 的第一个参数
00401031  |.  E8 9A000000   CALL test.004010D0                     ;  tt.Set()

004010D0  /$  8B4424 04     MOV EAX,DWORD PTR SS:[ESP+4]
004010D4  |.  8B5424 08     MOV EDX,DWORD PTR SS:[ESP+8]
004010D8  |.  8941 04       MOV DWORD PTR DS:[ECX+4],EAX
004010DB  |.  8951 08       MOV DWORD PTR DS:[ECX+8],EDX
004010DE  \.  C2 0800       RETN 8

$-C      > 00401036  RETURN to test.00401036 from test.004010D0
$-8      > 0000000A
$-4      > 00000014
$ ==>    > 004070C0  test.004070C0
$+4      > 0000000A  ; m_a
$+8      > 00000014  ; m_b

看一下析构之后的代码

00401060  |.  8B4C24 10     MOV ECX,DWORD PTR SS:[ESP+10]            ;  NEXT SEH record
00401064  |.  33C0          XOR EAX,EAX
00401066  |.  64:890D 00000>MOV DWORD PTR FS:[0],ECX                 ;  出函数时还原异常链
0040106D  |.  83C4 1C       ADD ESP,1C                               ;  ??
00401070  \.  C3            RETN
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2010-1-16 18:25:11 | 显示全部楼层
哈哈 原来这个东西就是函数内 所有变量的总空间大小 ~~

SUB ESP,0C

Debug 版要测定局部变量空间 需-44H

11:   int main(int argc, char* argv[])
12:   {
00401050   push        ebp
00401051   mov         ebp,esp
00401053   push        0FFh
00401055   push        offset __ehhandler$_main (00413419)
0040105A   mov         eax,fs:[00000000]
00401060   push        eax
00401061   mov         dword ptr fs:[0],esp
00401068   sub         esp,50h            // here 真是的 大小应该为 50-44=0c
0040106B   push        ebx
0040106C   push        esi
0040106D   push        edi
0040106E   lea         edi,[ebp-60h]
00401071   mov         ecx,14h
00401076   mov         eax,0CCCCCCCCh
0040107B   rep stos    dword ptr [edi]


// ps : 在Debug模式下 为调用的函数也是不会被编译的 :loveliness:
PYG19周年生日快乐!
您需要登录后才可以回帖 登录 | 加入我们

本版积分规则

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