用汇编分析C++程序,x86汇编

简介:   说到用汇编的眼光看C++语言,那么怎么阅读汇编代码就成了我们需要解决的一个问题。其实,实话说,汇编其实不难。只是我们需要明白这样几个问题:  (1)汇编是什么语言?  (2)汇编中的主要内容有哪些?  (3)汇编语言是怎么和实际C/C++语言代码一一对应的?

  说到用汇编的眼光看C++语言,那么怎么阅读汇编代码就成了我们需要解决的一个问题。其实,实话说,汇编其实不难。只是我们需要明白这样几个问题:

  (1)汇编是什么语言?

  (2)汇编中的主要内容有哪些?

  (3)汇编语言是怎么和实际C/C++语言代码一一对应的?

  其实汇编语言是CPU指令码的一种标记符号。不同的CPU具有不同的指令集,普通PC上的CPU一般来自AMD或者是INTEL,使用的也就是我们今天所要说的X86指令集。其他类似的CPU还有POWERPC,主要来自电信企业的交换机、路由器使用;ARM类型,主要是智能终端或者仪器仪表类的设备使用;SUN SPARC类型,主要来供SUN服务器使用。因为CPU指令集和二进制码几乎是一一对应的,所以汇编语言不但可以帮助我们快速了解机器的硬件,也方便我们了解程序是怎么在设备上面运行的。

  汇编语言的内容有很多,但是真正和我们C/C++语言相关的内容其实并不多。大体上你需要了解的只有寄存器、段地址、堆栈、寄存器之间的基本操作、地址访问这些就足够了。

  我们从一个范例开始。一般来说,一条语句都需要拆分成若干条汇编语句。比如说下面这一段话:

  int m=10;

  int n=20;

  int p=m + n;

  我们这里假设m、n、p都是处在一个函数之中,所以事实上三个变量都是临时变量,在进入到函数之前,ebp和esp之间都要腾出空间为这些临时变量做准备。那么这三句话应该是这样解释的。

  43: int m=10;

  004012E8 mov dword ptr [ebp-4],0Ah

  44: int n=20;

  004012EF mov dword ptr [ebp-8],14h

  45: int p=m + n;

  004012F6 mov eax,dword ptr [ebp-4]

  004012F9 add eax,dword ptr [ebp-8]

  004012FC mov dword ptr [ebp-0Ch],eax

  我们可以通过上面的代码直观地看到汇编语句和C语言之间的对应关系。第一句,m赋值为10,内存正是ebp向下的内存;第二句和第一句类似;第三句稍微有点复杂,我们可以分析一下。首先我们看到,CPU从堆栈中把m的数据找了出来,也就是[ebp-4]地址处的数据,接着CPU用同样的方法把n的数据也找了出来,直接加到寄存器eax上,最后一步比较简单,就是把eax的数据保存在[ebp-0c]处的地址上。只要是函数内部的临时变量,就会看到这样的形式。临时变量就是依靠ebp的偏移地址获取的。 大家有没有想过,如果p是全局变量呢?

  45: int m=10;

  004012E8 mov dword ptr [ebp-4],0Ah

  46: int n=20;

  004012EF mov dword ptr [ebp-8],14h

  47: p=m + n;

  004012F6 mov eax,dword ptr [ebp-4]

  004012F9 add eax,dword ptr [ebp-8]

  004012FC mov [p (0042b0b4)],eax

  看到上面的代码,我们发现m、n的赋值方向没有发生什么样的变化。变化的是,最后寄存器eax的数值被赋值给了一个绝对地址0x42b0b4。这说明了一个问题,在古玩程序加载到内存后,全局变量是有独立地址空间,并不会随着堆栈的浮动发生变化。 前面我们说过,在函数内部的所有变量都会保存在ebp到esp之间的堆栈空间上,那么代码是怎么做的呢?我们可以看这样一段汇编代码?

  41: void process()

  42: {

  004012D0 push ebp

  004012D1 mov ebp,esp

  004012D3 sub esp,4Ch

  004012D6 push ebx

  004012D7 push esi

  004012D8 push edi

  004012D9 lea edi,[ebp-4Ch]

  004012DC mov ecx,13h

  004012E1 mov eax,0CCCCCCCCh

  004012E6 rep stos dword ptr [edi]

  43: int m=10;

  004012E8 mov dword ptr [ebp-4],0Ah

  44: int n=20;

  004012EF mov dword ptr [ebp-8],14h

  45: int p=m + n;

  004012F6 mov eax,dword ptr [ebp-4]

  004012F9 add eax,dword ptr [ebp-8]

  004012FC mov dword ptr [ebp-0Ch],eax

  46: }

  我们把刚才一段函数的完整代码打印出来。我们发现,事实上在临时变量m运算之前,函数做了很多预备操作,其主要目的有两个:(1)为临时变量准备空间;(2)对函数运算中使用到的寄存器进行保存处理,这是因为寄存器是所有函数共有的资源,如果不记录好原来的数据,那么在函数返回后,寄存器就会忘记原来的数值,不能在原来的状态下继续正确地运算了。从地址0x4012D0到地址0x4012E6之间共有10句话,其实意思并不困难。第一句,ebp压栈;第二句esp复制给ebp;第三句esp自减4C大小,这个大小一般是按照函数内部定义了多少临时变量决定的;第四句,ebx压栈;第五句,esi压栈;第六句,edi压栈;第七句到第十句,把[ebp-4C]处向上的0x4C个字节全部设置成CC,edi为起始地址,ecx为循环次数0x13次,dword表示每次设置4个字节。 那么函数在返回前做了一些什么呢?

  46: }

  004012FF pop edi

  00401300 pop esi

  00401301 pop ebx

  00401302 mov esp,ebp

  00401304 pop ebp

  00401305 ret

  其实函数返回的时候,做的内容特别简单。第一句edi出栈;第二句esi出栈;第三句ebx出栈,和前面寄存器进栈的顺序是完全相反的。最后三句特别关键,我们看到ebp复制给esp,ebp出栈,函数返回,这样一切都恢复到函数调用之前的状态了。 那么函数调用的时候,入参是怎么处理的呢?

  53: process(20);

  0040EFA4 push 14h

  0040EFA6 call @ILT+40(process) (0040102d)

  0040EFAB add esp,4

  上面一段代码就是process函数含有一个参数时候的情形。函数调用后esp+4,堆栈恢复。堆栈+4,主要是因为参数的空间就是4个字节。所以用一幅图说明一下,函数调用的时候堆栈空间应该是这样的: | 函数参数 |

  | 返回地址 |

  | 临时变量 | <------------------------ ebp

  | 压栈寄存器 |

  | 栈顶 | <

  -------------------------esp

  (1) 全局运算cpu寄存器很多,一般有eax,ebx,ecx,edx等等。我们通常说的ax,bx,cx,dx指的只是他们的低位部分。

  (2)段寄存器寄存了程序的代码段,数据段和堆栈段。代码段保存了全部的程序代码,数据段保存了全据变量的代码,而堆栈则是全部堆栈空间。

  (3)目前 vc编译器支持嵌入式汇编,大家有兴趣的话可以在函数内部试试身手。下面的代码只是一个范例:

  void process(int* q)

  {

  _asm {

  push eax

  push ebx

  push ecx

  mov eax, 0x10

  mov ebx, 0x15

  add eax, ebx

  mov ecx, q

  mov [ecx], eax

  pop ecx

  pop ebx

  pop eax

  }

  }

目录
相关文章
|
2月前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
51 2
|
3月前
|
C++
C++ 根据程序运行的时间和cpu频率来计算在另外的cpu上运行所花的时间
C++ 根据程序运行的时间和cpu频率来计算在另外的cpu上运行所花的时间
44 0
|
8天前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【11月更文挑战第6天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
|
1月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
101 21
|
1月前
|
存储 算法 搜索推荐
对二叉堆的简单分析,c和c++的简单实现
这篇文章提供了对二叉堆数据结构的简单分析,并展示了如何在C和C++中实现最小堆,包括初始化、插入元素、删除最小元素和打印堆的函数,以及一个示例程序来演示这些操作。
34 19
|
1月前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
【10月更文挑战第8天】在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。
|
2月前
|
C++
【C++基础】程序流程结构详解
这篇文章详细介绍了C++中程序流程的三种基本结构:顺序结构、选择结构和循环结构,包括if语句、三目运算符、switch语句、while循环、do…while循环、for循环以及跳转语句break、continue和goto的使用和示例。
45 2
|
2月前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
简介 在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。 1. Perf 基础 1.1 Perf 简介 perf是Linux下的一款性能分析工具,能够进行函数级与指令级的热点查找。利用perf剖析程序性能时,需要指定当前测试的性能时间。性能事件是指在处理器或操作系统中发生的,可能影响到程序性能的硬件事件或软件事件 1.2 Perf的安装 ubuntu 18.04: sudo apt install linux-tools-common linux-tools-4.15.0-106-gen
加快软盘操作速度的汇编程序源代码
加快软盘操作速度的汇编程序源代码
|
3月前
|
程序员 编译器 数据处理
汇编高手秘籍:解锁性能优化新境界,用汇编语言让你的程序飞起来!
【8月更文挑战第31天】汇编语言作为编程基石,其高效性能备受推崇。尽管现代软件开发更偏爱高级语言,但在性能要求极高的场景下,汇编优化仍不可或缺。本文通过示例代码介绍四种优化技巧:循环展开、寄存器分配、指令重排及SIMD指令使用,显著提升执行效率。同时强调分析性能瓶颈、测试优化效果及保持代码可读性的重要性,助力开发者在关键代码路径上实现性能突破。
75 0