通过汇编代码分析C++的动态联编机制
2007-08-12 10:19
423 查看
C++中的一大法宝就是虚函数,简单来说就是加virtual关键字定义的函数。
其特性就是支持动态联编。现在C++开发的大型软件中几乎已经离不开虚函数的
使用,一个典型的例子就是虚函数是MFC的基石之一。
这里有两个概念需要先解释:
静态联编:通俗点来讲就是程序编译时确定调用目标的地址。
动态联编:程序运行阶段确定调用目标的地址。
在C++中通常的函数调用都是静态联编,但如果定义函数时加了virtual关键
字,并且在调用函数时是通过指针或引用调用,那么此时就是采用动态联编。
一个简单例子:
// test.cpp
#include<iostream.h>
class ClassA
{
public:
int num1;
ClassA(){ num1=0xffff; };
virtual void test1(void){};
virtual void test2(void){};
};
ClassA objA,* pobjA;
int main(void)
{
pobjA=&objA;
objA.test1();
objA.test2();
pobjA->test1();
pobjA->test2();
return 0;
}
使用VC编译:
开一个命令行直接在命令行调用cl来编译: (如果你安装vc时没有选择注册环境
变量,那么先在命令行运行VC目录下bin/VCVARS32.BAT )
cl test.cpp /Fa
产生test.asm中间汇编代码
接下来就看看asm里有什么玄虚,分析起来有点长,要有耐心 !
我们来看看:
数据定义:
_BSS SEGMENT
?objA@@3VClassA@@A DQ 01H DUP (?) ;objA 64位
?pobjA@@3PAVClassA@@A DD 01H DUP (?) ;pobjA 一个地址32位
_BSS ENDS
看到objA为64位,里边存放了哪些内容呢? 接着看看构造函数:
_this$ = -4
??0ClassA@@QAE@XZ PROC NEAR ; ClassA::ClassA() 定义了一个变量 _this ?!
; File test.cpp
; Line 6
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx ; ecx 赋值给 _this ?? 不明白??
mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
; 前面的部分都是编译器加的东东,我们的赋值在这里
mov ecx, DWORD PTR _this$[ebp]
mov DWORD PTR [ecx+4], 65535 ;0xffff num1=0xffff;
; 看来 _this+4就是num1的地址
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
??0ClassA@@QAE@XZ ENDP
那个_this和mov DWORD PTR _this$[ebp], ecx 让人比较郁闷了吧,不急看看何
处调用的构造函数:
_$E9 PROC NEAR
; File test.cpp
; Line 10
push ebp
mov ebp, esp
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A
call ??0ClassA@@QAE@XZ ;call ClassA::ClassA()
pop ebp
ret 0
_$E9 ENDP
看,ecx指向objA的地址,通过赋值,那个_this就是objA的开始地址,其实CLASS中
的非静态方法编译器编译时都会自动添加一个this变量,并且在函数开始处把ecx
赋值给他,指向调用该方法的对象的地址 。
那么构造函数里的这两行又是干什么呢?
mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
我们已经知道_this保存的为对象地址: &objA。 那么 eax = &objA
接着就相当于 ( * eax ) = OFFSET FLAT:??_7ClassA@@6B@
来看看 ??_7ClassA@@6B@ 是哪个道上混的:
CONST SEGMENT
??_7ClassA@@6B@
DD FLAT:?test1@ClassA@@UAEXXZ ; ClassA::`vftable'
DD FLAT:?test2@ClassA@@UAEXXZ
CONST ENDS
看来这里存放的就是test1(),test2()函数的入口地址 ! 那么这个赋值:
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
就是在对象的起始地址填入这么一个地址列表的地址。
好了,至此我们已经看到了objA的构造了:
| 低地址 |
+--------+ ---> objA的起始地址 &objA
|pvftable|
+--------+-------------------------+
| num1 | num1变量的空间 |
+--------+ ---> objA的结束地址 +--->+--------------+ 地址表 vftable
| 高地址 | |test1()的地址 |
+--------------+
|test2()的地址 |
+--------------+
来看看main函数:
_main PROC NEAR
; Line 13
push ebp
mov ebp, esp
; Line 14
mov DWORD PTR ?pobjA@@3PAVClassA@@A,
OFFSET FLAT:?objA@@3VClassA@@A ; pobjA = &objA
; Line 15
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A ; ecx = this指针
; 指向调用者的地址
call ?test1@ClassA@@UAEXXZ ; objA.test1()
; objA.test1()直接调用,已经确定了地址
; Line 16
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A
call ?test2@ClassA@@UAEXXZ ; objA.test2()
; Line 17
mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
mov edx, DWORD PTR [eax] ; edx = vftable
mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
call DWORD PTR [edx] ;
; call vftable[0] 即 pobjA->test1() 看地址是动态查找的 ; )
; Line 18
mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
call DWORD PTR [edx+4] ; pobjA->test2()
; call vftable[1] 而vftable[1]里存放的是test2()的入口地址
; Line 19
xor eax, eax
; Line 20
pop ebp
ret 0
_main ENDP
好了,相信到这里你已经对动态联编有了深刻印象。
其特性就是支持动态联编。现在C++开发的大型软件中几乎已经离不开虚函数的
使用,一个典型的例子就是虚函数是MFC的基石之一。
这里有两个概念需要先解释:
静态联编:通俗点来讲就是程序编译时确定调用目标的地址。
动态联编:程序运行阶段确定调用目标的地址。
在C++中通常的函数调用都是静态联编,但如果定义函数时加了virtual关键
字,并且在调用函数时是通过指针或引用调用,那么此时就是采用动态联编。
一个简单例子:
// test.cpp
#include<iostream.h>
class ClassA
{
public:
int num1;
ClassA(){ num1=0xffff; };
virtual void test1(void){};
virtual void test2(void){};
};
ClassA objA,* pobjA;
int main(void)
{
pobjA=&objA;
objA.test1();
objA.test2();
pobjA->test1();
pobjA->test2();
return 0;
}
使用VC编译:
开一个命令行直接在命令行调用cl来编译: (如果你安装vc时没有选择注册环境
变量,那么先在命令行运行VC目录下bin/VCVARS32.BAT )
cl test.cpp /Fa
产生test.asm中间汇编代码
接下来就看看asm里有什么玄虚,分析起来有点长,要有耐心 !
我们来看看:
数据定义:
_BSS SEGMENT
?objA@@3VClassA@@A DQ 01H DUP (?) ;objA 64位
?pobjA@@3PAVClassA@@A DD 01H DUP (?) ;pobjA 一个地址32位
_BSS ENDS
看到objA为64位,里边存放了哪些内容呢? 接着看看构造函数:
_this$ = -4
??0ClassA@@QAE@XZ PROC NEAR ; ClassA::ClassA() 定义了一个变量 _this ?!
; File test.cpp
; Line 6
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx ; ecx 赋值给 _this ?? 不明白??
mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
; 前面的部分都是编译器加的东东,我们的赋值在这里
mov ecx, DWORD PTR _this$[ebp]
mov DWORD PTR [ecx+4], 65535 ;0xffff num1=0xffff;
; 看来 _this+4就是num1的地址
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
??0ClassA@@QAE@XZ ENDP
那个_this和mov DWORD PTR _this$[ebp], ecx 让人比较郁闷了吧,不急看看何
处调用的构造函数:
_$E9 PROC NEAR
; File test.cpp
; Line 10
push ebp
mov ebp, esp
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A
call ??0ClassA@@QAE@XZ ;call ClassA::ClassA()
pop ebp
ret 0
_$E9 ENDP
看,ecx指向objA的地址,通过赋值,那个_this就是objA的开始地址,其实CLASS中
的非静态方法编译器编译时都会自动添加一个this变量,并且在函数开始处把ecx
赋值给他,指向调用该方法的对象的地址 。
那么构造函数里的这两行又是干什么呢?
mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
我们已经知道_this保存的为对象地址: &objA。 那么 eax = &objA
接着就相当于 ( * eax ) = OFFSET FLAT:??_7ClassA@@6B@
来看看 ??_7ClassA@@6B@ 是哪个道上混的:
CONST SEGMENT
??_7ClassA@@6B@
DD FLAT:?test1@ClassA@@UAEXXZ ; ClassA::`vftable'
DD FLAT:?test2@ClassA@@UAEXXZ
CONST ENDS
看来这里存放的就是test1(),test2()函数的入口地址 ! 那么这个赋值:
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
就是在对象的起始地址填入这么一个地址列表的地址。
好了,至此我们已经看到了objA的构造了:
| 低地址 |
+--------+ ---> objA的起始地址 &objA
|pvftable|
+--------+-------------------------+
| num1 | num1变量的空间 |
+--------+ ---> objA的结束地址 +--->+--------------+ 地址表 vftable
| 高地址 | |test1()的地址 |
+--------------+
|test2()的地址 |
+--------------+
来看看main函数:
_main PROC NEAR
; Line 13
push ebp
mov ebp, esp
; Line 14
mov DWORD PTR ?pobjA@@3PAVClassA@@A,
OFFSET FLAT:?objA@@3VClassA@@A ; pobjA = &objA
; Line 15
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A ; ecx = this指针
; 指向调用者的地址
call ?test1@ClassA@@UAEXXZ ; objA.test1()
; objA.test1()直接调用,已经确定了地址
; Line 16
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A
call ?test2@ClassA@@UAEXXZ ; objA.test2()
; Line 17
mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
mov edx, DWORD PTR [eax] ; edx = vftable
mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
call DWORD PTR [edx] ;
; call vftable[0] 即 pobjA->test1() 看地址是动态查找的 ; )
; Line 18
mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
call DWORD PTR [edx+4] ; pobjA->test2()
; call vftable[1] 而vftable[1]里存放的是test2()的入口地址
; Line 19
xor eax, eax
; Line 20
pop ebp
ret 0
_main ENDP
好了,相信到这里你已经对动态联编有了深刻印象。
相关文章推荐
- C++中通过指针,引用方式做返回值的汇编代码分析
- C++链表AT&T代码,通过Ubuntu实现生成(Linux内核分析笔记)
- VC实例分析:VC++通过汇编获取代码运行时间
- 1、通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
- 通过汇编一个简单的C程序,分析汇编代码理解计算机工作原理
- 通过分析汇编代码了解计算机是如何工作
- (编程语言中)后置++(即i++),分别通过汇编和字节码分析其在vs2012 C++和java中区别
- 通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的--20135334赵阳林
- 通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
- 通过分析汇编代码理解计算机如何工作
- 基于visual c++之windows核心编程代码分析(53)在C++中嵌入汇编实现DLL注入源代码
- 通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
- C++内嵌汇编(一):反汇编分析C++代码
- Linux内核分析第一周-通过分析汇编代码理解计算机是如何工作的
- 第一周:通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
- 通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
- 基于visual c++之windows核心编程代码分析(53)在C++中嵌入汇编实现DLL注入源代码
- 【分析】C++中通过溢出覆盖虚函数指针列表执行代码
- 通过汇编代码分析单任务与多任务计算机的工作方式
- 通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的