反汇编分析C++的返回值

2024-01-23 08:38
文章标签 分析 c++ 返回值 反汇编

本文主要是介绍反汇编分析C++的返回值,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这几天一直在给同学讲数据结构和算法的题,讲题的过程中突然想到这函数的返回值究竟是存放在哪的,为什么可以通过函数的调用来直接给变量赋值。仔细想想这么多年居然没有深究过这个问题,太不应该,于是今天花了些时间来好好研究了一下这个问题。也跟了一些thread,某些前辈的观点是“不应该动不动就提到寄存器,栈,堆这些较底层的概念。完全可以这样理解:返回值保存在一段临时区域,到下一条语句时,返回值被销毁,所以,如果要继续使用返回值,必须将返回值赋予其他变量。”这确实不假,但是我觉得这位前辈应该是在透彻理解了内在的机理之后才这么说的,我觉得作为学习过程还是应该深究一下。

另有一位前辈说“不大于32位的(返回值)在eax中,64位的在edx + eax中。再大就传指针了。”

那么就拿反汇编代码开刀,验证一下。

(一)  基础知识

在看反汇编代码之前,应该有几点要明确一下:

1.每个进程对应一个Call Stack(调用栈),而CallStack 又由许多Stack Frame(栈帧)组成,对应每个未完成的函数调用,由系统分配。(VC调试中默认是会显示调用栈窗口的,越往栈顶的就是越最近调用的函数,黑色显示的就是还未返回的函数调用)

2.eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的32位通用寄存器的名称,他们各自的作用如下:

EAX 是累加器(accumulator), 它是很多加法乘法指令的缺省寄存器。

EBX 是基地址(base)寄存器,在内存寻址时存放基地址。

ECX 是计数器(counter),是重复(REP)前缀指令和LOOP指令的内定计数器。

EDX 则一般被用来放整数除法产生的余数。

ESI/EDI分别是源/目标索引寄存器(source/destinationindex),在很多字符串操作指令中,DS:ESI指向源串,而ES:EDI指向目标串。

EBP是基址指针(BASE POINTER),按照习惯,通常用于界定StackFrame(栈帧)。

ESP 是堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,每压入一个数据,ESP就减少4字节。

3.windows平台下的Call Stack,从上(高地址)往下(低地址)生长。

(二)代码分析

首先我实验的平台是32位版vs2010,程序为Debug版本。

先试试32位整型的int,C++的实验代码如下:

#include "stdafx.h"
int minus(int a ,int b)
{
int j=2;
return a-b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int i=1;
int c=minus(3,1);
return 0;
}

在return 0处设置断点,或者直接用system(“pause”)也行。

调试运行,可以看到反汇编代码如下:

int minus(int a ,int b)
{
/*step8 再次将EPB压栈,并将ESP的值放入EPB中,此时ESP指向位置5,EPB也指向位置5,这个位置也正是minus()栈帧的底部,同时又是Main()栈帧的顶部*/
01091380 push        ebp 
01091381 mov         ebp,esp 
/*step9 作用同step2。此时EPB位置不变,ESP指向位置6*/
01091383 sub         esp,0CCh 
/*step10 作用同step3。此时EPB位置不变,ESP指向位置7*/
01091389 push        ebx 
0109138A push        esi 
0109138B push        edi 
/*step11 作用同step4。ESP和EPB位置不变*/
0109138C lea         edi,[ebp-0CCh] 
01091392 mov         ecx,33h 
01091397 mov         eax,0CCCCCCCCh 
0109139C rep stos    dword ptres:[edi] 
int j=2;
/*step12 作用同step5。ESP和EPB位置不变*/
0109139E mov         dword ptr [j],2 
return a-b;
/*step13 将参数a的值放入EAX。然后将参数b的值也传入,在EAX中进行减法,结果存放在EXA中。ESP和EPB位置不变*/
010913A5 mov         eax,dword ptr [a] 
010913A8 sub         eax,dword ptr [b] 
}
/*step14 返回过程。将三个寄存器的值弹出,恢复到调用前的状态。并将EBP的值赋给ESP,此时EBP与ESP都指向位置5,也就是释放了从位置6到位置5的预留空间。最后弹出EBP的值,使其恢复到调用minus()前的状态,ret语句进行返回,返回到先前压入栈中的返回地址所指向的地址处。此时EPB指向位置0,ESP指向位置3*/
010913AB pop         edi 
010913AC pop         esi 
010913AD pop         ebx 
010913AE mov         esp,ebp 
010913B0 pop         ebp 
010913B1 ret
int _tmain(int argc, _TCHAR* argv[])
{
/*step1 运行时库的启动函数mainCRTStartup()调用Main(),正式进入主函数,将EPB压栈,再将ESP的值放入EPB中,此时ESP指向位置0,EPB也指向位置0,这个位置也正是的Main()栈帧的底部,同时又是mainCRTStartup()栈帧的顶部*/
010913C0 push        ebp 
010913C1 mov         ebp,esp 
/*step2 将ESP下移0CCh个字节,也就是位置0到位置1之间的空间,这段空间将预留用来存储局部变量,此时ESP指向位置1,EPB指向位置0*/
010913C3 sub         esp,0D8h 
/*step3 将三个寄存器的值压栈,此时ESP指向位置2,EPB指向位置0*/
010913C9 push        ebx 
010913CA push        esi 
010913CB push        edi 
/*step4 首先将位置1的地址放入目标索引寄存器EDI中,stos dwordptr es:[edi]的意思是从累加器EAX传送4个字节(双字),到由EDI作为指针的目的串中,同时修改EDI以指向串中的下一个单元,前面加上rep代表重复执行,执行次数就是ECX寄存器中的值,而36h*4正好等于0D8h,所以以下4句的作用就是将刚刚预留的位置0到位置1之间的空间全部赋值为0CCCCCCCCh ,这其实就是int 3断点,以防止意外访问。ESP和EPB位置不变*/
010913CC lea         edi,[ebp-0D8h] 
010913D2 mov         ecx,36h 
010913D7 mov         eax,0CCCCCCCCh 
010913DC rep stos    dword ptres:[edi] 
int i=1;
/*step5 将1传到局部变量i所在地址中,也就是刚刚预留的那块空间中,i应当是EBP-偏移量的形式。ESP和EPB位置不变*/
010913DE mov         dword ptr [i],1 
int c=minus(3,1);
/*step6 在调用minus()前,按从右到左的顺序将实参压栈。EPB位置不变,ESP指向位置3*/
010913E5 push        1 
010913E7 push        3 
/*step7 调用minus(),并将下一条语句的地址010913EE压栈,作为返回地址。EPB位置不变,ESP指向位置4*/
010913E9 call        minus (10911C7h) 
/*step15 从minus返回。将ESP+8,代表释放前面压入栈中的两个市场(2个4字节),并将EAX中的值(也就是返回值)赋给局部变量c,这里也就证明了当返回值是32位int类型的时候,返回值确实是暂存在EAX中的。EPB指向位置0,ESP指向位置2*/
010913EE add         esp,8 
010913F1 mov         dword ptr [c],eax 
return 0;
/*step16 准备返回0给main函数。先对EAX自身做异或运算,EAX变成0,此时返回值才是真正的不存在了。然后将三个寄存器的值弹出,恢复到mainCRTStartup()调用main()前的状态,接着释放位置1到位置0之间的预留空间。EPB和ESP都指向位置0。再接着通过比较EBP与ESP的值,检查ESP是否正常。不相等则调用ILT+305指向的异常处理函数,使ESP等于EBP。正常的话就直接弹出EBP,结束main()函数,返回mainCRTStartup()函数中继续剩下的操作。*/
010913F4 xor         eax,eax 
}
010913F6 pop         edi 
010913F7 pop         esi 
010913F8 pop         ebx 
010913F9 add         esp,0D8h 
010913FF cmp         ebp,esp 
01091401 call       @ILT+305(__RTC_CheckEsp) (1091136h) 
01091406 mov         esp,ebp 
01091408 pop         ebp 
01091409 ret  

示意图如图:

再试试64位整型的long long,C++的实验代码如下(VC6编译器可能不支持long long):

#include "stdafx.h"
long long minus(long long a , long long b)
{
int j=2;
return a-b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int i=1;
long long c=minus(3,1);
return 0;
}

反汇编代码如下:

long long minus(long long a ,long long b)
{
01351380 push        ebp 
01351381 mov         ebp,esp 
01351383 sub         esp,0CCh 
01351389 push        ebx 
0135138A push        esi 
0135138B push        edi 
0135138C lea         edi,[ebp-0CCh] 
01351392 mov         ecx,33h 
01351397 mov         eax,0CCCCCCCCh 
0135139C rep stos    dword ptres:[edi] 
int j=2;
0135139E mov         dword ptr [j],2 
return a-b;
<SPAN style="COLOR: #ff0000">013513A5 mov         eax,dword ptr [a] 
013513A8 sub         eax,dword ptr [b] 
013513AB mov         edx,dword ptr[ebp+0Ch] 
013513AE sbb         edx,dword ptr[ebp+14h] </SPAN>
}
013513B1 pop         edi 
013513B2 pop         esi 
013513B3 pop         ebx 
013513B4 mov         esp,ebp 
013513B6 pop         ebp 
013513B7 ret 
int _tmain(int argc, _TCHAR* argv[])
{
013513C0 push        ebp 
013513C1 mov         ebp,esp 
013513C3 sub         esp,0DCh 
013513C9 push        ebx 
013513CA push        esi 
013513CB push        edi 
013513CC lea         edi,[ebp-0DCh] 
013513D2 mov         ecx,37h 
013513D7 mov         eax,0CCCCCCCCh 
013513DC rep stos    dword ptres:[edi] 
int i=1;
013513DE mov         dword ptr [i],1 
long long c=minus(3,1);
013513E5 push        0 
013513E7 push        1 
013513E9 push        0 
013513EB push        3 
013513ED call        minus (13511C2h) 
013513F2 add         esp,10h 
<SPAN style="COLOR: #ff0000">013513F5 mov         dword ptr [c],eax 
013513F8 mov        dword ptr [ebp-14h],edx </SPAN>
return 0;
013513FB xor         eax,eax 
}
013513FD pop         edi 
013513FE pop         esi 
013513FF pop         ebx 
01351400 add         esp,0DCh 
01351406 cmp         ebp,esp 
01351408 call       @ILT+305(__RTC_CheckEsp) (1351136h) 
0135140D mov         esp,ebp 
0135140F pop         ebp 
01351410 ret  

可以发现在进行减法运算和传递返回值时确实发生了变化,说明返回值为64位整型时,会使用EAX和EDX来分别传递返回值的低32位和高32位。

浮点数的情况就不重复贴代码了,直接看反汇编代码:

float c=minus(3,1);
010E13F5 push        ecx 
//压入实参1到浮点寄存器st(0),注意这个操作不会影响ESP的值
010E13F6 fld1 
//将1送入到ESP指向的空间中,也就是ECX中?并且1从st(0)出栈
010E13F8 fstp        dword ptr [esp] 
010E13FB push        ecx 
//另一个实参3
010E13FC fld         dword ptr[__real@40400000 (10E573Ch)] 
010E1402 fstp        dword ptr [esp] 
010E1405 call        minus (10E117Ch) 
010E140A add         esp,8 
//返回值赋给c,然后出栈
010E140D fstp        dword ptr [c]  

可以看出浮点类型的返回值是暂存在浮点寄存器st(0)中的。

 

这篇关于反汇编分析C++的返回值的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/635835

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57