C++反汇编 利用反汇编分析常见C/C++语句的底层实现(硬核)

2023-11-19

本节我们利用反汇编技术来对我们最常见的C语言语句进行解析,C++反汇编技术可以让你更好的理解C++/C语言的底层含义,相信我,读完本节,一定会让你感到醍醐灌顶,瞬间通透C++/C语言的底层含义

我们假设你已经基本了解了x86汇编的基本指令:

mov ax,06h: 基本赋值指令 ax=0006h
add ax,cx: 相加指令:ax=ax+cx
sub ax,cx: 相减指令:ax=ax-cx
call 地址: 跳转指令:跳转到这个地址位置,常用于跳转到函数
ret : 跳转指令:和call指令配合使用,相当于return,从函数返回
jmp 地址: jmp:无条件跳转指令,跳转到此地址
jne | je : 有条件跳转指令,jne表示如果nor equal则跳转,je表示如果equal则跳转
lea ax,[地址]:地址赋值指令,把这个地址赋值给ax
等等等等…

基本汇编单位:

  • byte ptr : 表示一个字节
  • word ptr:一个字,表示两个字节
  • dword ptr:两个字,表示四个字节
  • qword ptr:四个字:表示八个字节

赋值操作

int a = 10;
int b = 20;

就是这么简单的两条赋值语言,你知道他在汇编里是什么样的吗?

 mov         dword ptr [a],0Ah  
 mov         dword ptr [b],14h  

我们把十六进制的 0Ah(H:10)送到 a所表示的内存地址空间。
我们把十六进制的14h(H:20)送到b表示的内存地址空间
dword ptr表示他们是四个字节,正好可以表示出:我们的int类型的sizeof(int)为4,即4个字节。
在这里插入图片描述

if条件判断

	if (a == b)
	{
		printf("a==b");
	}
	else
	{
		printf("a!=b");
	}

它的汇编指令是怎样的呢?

 mov         eax,dword ptr [b]  	//b的内存空间里的内容送往eax寄存器
 cmp         dword ptr [a],eax  	// cmp和a内存空间里的内容比较
 jne         __$EncStackInitStart+52h (07FF7C6895CB1h)  //如果不相等,则跳转,否则,继续往下执行
 
 lea         rcx,[string "a==b" (07FF7C689AE38h)]  		//将表示字符串的地址赋值给rcx,配合下面的跳转到printf的函数打印此字符串
 call        printf (07FF7C6891195h)  
 
07FF7C6895CB1h:  jmp  __$EncStackInitStart+5Eh (07FF7C6895CBDh)  	//cmp相等则直接跳转到此处
 lea         rcx,[string "a!=b" (07FF7C689AE40h)]  
 call        printf (07FF7C6891195h) 

我们来分析一下:

  1. 两条call的指令相同,可以推断出call的作用(在此处)为跳转到printf 函数所在的位置,然后在此函数内部,一定还有一个ret返回主程序。
  2. jne的作用:在cmp比较后,如果相等则不会触发jne,则跳过此jne,继续往下执行;如果cmp比较后不相等,则触发jne,有条件跳转到下方,即到了else的位置,接着打印不相等的信息。
  3. 注意cmp的比较:在此处我们只是简单的比较他们,如果相等或者不相等分别干什么,但是请注意,cmp指令的执行过程不会这么简单,它也包含复杂的比较条件,在此我们不再赘述。

指针和引用的实质

一个很重要的问题: 引用就是指针,为什么?

int c = 5;
int d = 15;
int* p1 = &c;
int& p2 = d;

看一下汇编代码:

 mov         dword ptr [c],5  
 mov         dword ptr [d],0Fh  
 //指针的操作
 lea         rax,[c]  				//将c的地址值给rax
 mov         qword ptr [p1],rax  	//将rax(c的地址值)给p1所在的内存空间
 //引用的操作
 lea         rax,[d]  
 mov         qword ptr [p2],rax  
  1. 我们把5这个值送到c的内存空间,把0F值送到d的内存空间,即完成了对c和d的赋值操作。
  2. lea指令:把c的地址值赋值给rax寄存器,注意:就是c的地址,不是其里面的内容。
  3. p1指针:注意指针的单位是qword ptr 表示八个字节(32位 msvc编译器),我们把rax(存储c的地址)给到p1所表示内存空间,即完成了 int* p1=&c
  4. p2引用:我们把d的地址给到了rax,再把rax的值(存储d的地址)给到了p2所在的内存空间,即完成了int &p2=d。

注意: 指针和引用的内存空间里存储的是c和d的地址,我们再通过解引用,从他们的内存空间中获取c和d的地址,这就是我们所知道的指针赋值和解引用操作。

大家有没有注意到一个问题: 指针和引用的汇编指令是一样的:

lea  寄存器,[地址值]
mov  qword ptr [指针变量地址],寄存器

他们的汇编指令一样,所以说他们的实质是一样的: 引用的本质就是指针。
同时,如果我们看到这两条指令,则99%的概率是引用或者指针在赋值。

跳转函数

void fun()
{
	int a = 10;
	int c = 20;
	a += c;
}
int main()
{
	fun();
	return 0;
}
call        fun (07FF63C6A13FCh) 	//函数跳转
..............
.....
 //int a=10;
 mov         dword ptr [a],0Ah  
 //int b=20;
 mov         dword ptr [c],14h  
 //a=a+c;
 mov         eax,dword ptr [c]  
 mov         ecx,dword ptr [a]  
 add         ecx,eax  
 mov         eax,ecx  
 mov         dword ptr [a],eax   
 ....	
 ret  	//返回主程序

我们进入函数时,实际上就是call 一个地址,这个地址指向的就是我们的函数地址,然后进入函数,我们完成对int类型的a和c的赋值,接着我们在完成 a+=c的操作:
分解:

  1. a+c: 将a的内存空间的内容和c的内容分别放到两个寄存器中,在由寄存器完成 add操作,则ecx就表示了a+c的值,此时和放在ecx寄存器中。
  2. a=a+c:把ecx寄存器传给eax寄存器,再由eax送到a所指向的内存空间,完成了a的赋值。
  3. 为什么不能把 [a]和[c]直接相加呢?为什么还要借助好几个寄存器中转?
    原因:mov汇编指令不支持两个内存地址的相互操作,必须借助一个寄存器完成中转,也就是说mov的左右两边一定要有一个寄存器。

在最后,我们会有 一个ret的指令,此指令 完成return的返回操作,即返回主程序。

两个数字的交换操作

这是一个我们非常熟悉的两数字交换的指针操作:

void swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

//传递形参地址
 lea         rdx,[b]  		//取b的地址给rdx寄存器
 lea         rcx,[a]  		//取a的地址给rcx寄存器
 call        swap (07FF721F1132Fh)  	//进入函数
.....
 ...
 //int temp=*a;
 mov         rax,qword ptr [a]  
 mov         eax,dword ptr [rax]  
 mov         dword ptr [temp],eax  
 //*a=*b;
 mov         rax,qword ptr [a]  
 mov         rcx,qword ptr [b]  
 mov         ecx,dword ptr [rcx]  
 mov         dword ptr [rax],ecx  
 //*b=temp;
 mov         rax,qword ptr [b]  
 mov         ecx,dword ptr [temp]  
 mov         dword ptr [rax],ecx  
...
 ret  

解析:

  • 进入函数时,如果我们有形参,并且是指针类型,我们要完成对形参的取地址的行为,即用lea指令获得地址,分别存储在两个寄存器中。

  • int temp=*a 的操作过程:

    • 把 a的内存单元所存储的内容放到rax寄存器中,注意,这时是qword ptr即是指针的解引用操作,解引用后的值放入到eax寄存器中存储,eax把这个值放到temp所在的内存空间中。
  • *a=*b的操作过程:

    • 分别对两个指针变量解引用,分别存储在两个寄存器中,我们要让a的值等于b的值,所以我们要把b的值转换为整形,再存储到ecx寄存器中,把ecx所存储的值送入到rax所在的存储空间,注意这里是把原来的a的值给覆盖了。
  • *b=temp的过程:

    • 对指针解引用,即把b的值放入到rax寄存器中,把temp所在的内存空间的内容送到ecx寄存器中,ecx的值最后再送到b的内存空间。

数组的赋值及 -858993460数字的由来

int ar[5]={0};

是如何进行的呢?

 lea         rax,[ar]  
 mov         rdi,rax  
 
 xor         eax,eax  
 mov         ecx,14h  
 rep stos    byte ptr [rdi]  

rep stos指令的作用:重复执行上述指令,以ecx的值为执行次数,把eax的值送到之后的目标地址处。

把ar的地址送到rax寄存器中,注意:ar表示的是数组首地址,rax表示的是数组的首地址,再把rax送到rdi所造的内存单元,即rdi所在的内存单元里存储者数组的首地址,通过这个地址,我们可以根据偏移来获得整个数组的地址。

eax表示我们要送往目标的值我们一共要重复执行ecx次:即20次

注意:我们每次移动一个 byte ptr:即一个字节,但是我们的数组每个元素都是int,所以每个元素会执行4次,一共执行20次正好和ecx总的重复次数相对应
我们通过看内存可以得知:
在这里插入图片描述
数组的初始内存全部都是CCCC…, 即我们的eax默认也是存放的eax,我们对eax执行XOR操作(异或操作),

异或操作: 两数相同则为0,两数相异则为1 .

我们对eax和eax执行异或操作,每次执行一个字节,执行过程如下:

二进制数中,开始1表示负数,0表示正数
eax: CCCC CCCC

原码:1100 1100 1100 1100 1100 1100 1100 1100
反码:1011 0011 0011 0011 0011 0011 0011 0011 (对第二个数往后的数取反,第一个数是符号位)
补码:1011 0011 0011 0011 0011 0011 0011 0100 (反码在最后加1,得到补码)

第一个数字表示符号位: 负数
计算除第一位以外的补码转换为十进制:
在这里插入图片描述
第二个数字开始,转换为十进制,(再加上最开始的符号位)我们会发现一个挺熟悉的值: - 858993460

这是个啥??
如果我们打印一个未经初始化的数组的值:

int ar[5] ;
	for (int i = 0; i < 5; i++)
	{
		cout << ar[i]<<"  ";
	}

在这里插入图片描述
它的值就是 -858993460 这个值。

接着我们回到这段汇编上来:

  • 一共执行20次,注意:·我们每次处理一个字节!!

      1. 第一次:eax= CCCC CCCC 异或操作后: eax : CCCC CC00 ,数组首元素的值:- 858,993,664
      1. 第二次:eax=CCCC CC00 异或操作后: eax:CCCC 0000 , 数组首元素的值: -859045888
      1. 第三次:eax=CCCC 0000 异或操作后: eax:CC00 0000 , 数组首元素的值:-872415232
      1. 第四次:eax=CC00 0000 异或操作后: eax:0000 0000 , 数组首元素的值: 0

这样我们就完成了对数组首元素的赋值,接着我们循环执行ecx次,即20次,再完成对剩下4个 赋值为0

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

本节内容比较硬核,解释了底层C语言的语句执行情况,在此后,我也会写很多有意义的C++反汇编的代码,帮助大家理解C/C++的底层含义。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++反汇编 利用反汇编分析常见C/C++语句的底层实现(硬核) 的相关文章

  • Accept() 是线程安全的吗?

    我目前正在用 C 语言为我正在做的课程编写一个简单的网络服务器 我们的一项要求是实现一个线程池来使用 pthread 处理连接 我知道我将如何粗略地执行此操作 在主线程中调用accept并将文件描述符传递给freee线程 但是我的朋友建议了
  • 如何从RichTextBox中获取显示的文本?

    如何获得显示的RichTextBox 中的文本 我的意思是 如果 RichTextBox 滚动到末尾 我只想接收那些对我来说可见的行 P S 获得第一个显示的字符串就足够了 您想使用 RichTextBox GetCharIndexFrom
  • 键盘加速器在 UWP 应用中停止工作

    我正在尝试将键盘加速器添加到 UWP 应用程序中的 CommandBar 菜单项 当应用程序启动时 这工作正常 但在我第一次打开溢出菜单后 加速器停止工作 这似乎不会发生在主要命令 菜单之外 上 只有溢出菜单内的辅助命令才会发生 此外 单击
  • 找到的程序集的清单定义与程序集引用不匹配

    我试图在 C Windows 窗体应用程序 Visual Studio 2005 中运行一些单元测试 但出现以下错误 System IO FileLoadException 无法加载文件或程序集 实用程序 版本 1 2 0 200 文化 中
  • 从 future 中检索值时的 SIGABRT

    我在使用 C 11 future 时遇到问题 当我打电话时wait or get 关于返回的未来std async 程序接收从mutex标头 可能是什么问题呢 如何修复它 我在 Linux 上使用 g 4 6 将以下代码粘贴到 ideone
  • 导出类时编译器错误

    我正在使用 Visual Studio 2013 但遇到了一个奇怪的问题 当我导出一个类时 它会抛出 尝试引用已删除的函数 错误 但是 当该类未导出时 它的行为会正确 让我举个例子 class Foo note the export cla
  • Qt 计算和比较密码哈希

    目前正在 Qt 中为测验程序构建面向 Web 的身份验证服务 据我了解 在数据库中存储用户密码时 必须对其进行隐藏 以防落入坏人之手 流行的方法似乎是添加的过程Salt https en wikipedia org wiki Salt cr
  • 矩阵向量变换

    我正在编写一个代码来制作软件蒙皮器 骨骼 皮肤动画 并且我正处于 优化 阶段 蒙皮器工作得很好 并且在 Core 上 1 09 毫秒内对 4900 个三角形网格与 22 个骨骼进行蒙皮Duo 2 Ghz 笔记本 我需要知道的是 1 有人可以
  • 是否有像 gccxml 这样的用于生成包装器的 C 标头解析器工具?

    我需要为一种新的编程语言编写一些 C 标头包装器 并且想要类似 gccxml 的东西 但不完全依赖 gcc 以及它在 Windows 系统上带来的问题 只需要读C而不是C 只要有完整的文档记录 任何格式的输出都可以 Linux Solari
  • 操纵 setter 以避免 null

    通常我们有 public string code get set 如果最终有人将代码设置为 null 我需要避免空引用异常 我尝试这个想法 有什么帮助吗 public string code get set if code null cod
  • 您可以在一个 Windows Azure 实例上部署多个 Web 应用程序吗?

    是否可以在一个 windows azure 小型计算实例中运行一堆 Web 应用程序 我正在考虑使用 Azure 作为放置一堆处于开发和非生产状态的项目 Web 应用程序 的地方 有些实际上已经被封存了 但我想在某个地方有一个活跃的实例 我
  • 从事务范围调用 WCF 服务方法

    我有这样的代码 using TransactionScope scope TransactionScopeFactory CreateTransactionScope some methodes calls for which scope
  • 将非算术类型作为参数传递给 cmath 函数是否有效?

    给定以下用户定义类型S具有转换功能double struct S operator double return 1 0 以及以下调用cmath http en cppreference com w cpp header cmath使用类型的
  • .NET JIT 编译的代码缓存在哪里?

    NET 程序首先被编译为 MSIL 代码 当它被执行时 JIT编译器会将其编译为本机机器代码 我想知道 这些JIT编译的机器代码存储在哪里 它只存储在进程的地址空间中吗 但由于程序的第二次启动比第一次快得多 我认为即使在执行完成后 该本机代
  • 使用 foreach 循环和 XmlNodeList C# 将新节点附加到节点列表

    目前我处理的是这样的XML类型 XML FILE http 20drive google com open id 0By5BxgNi9eGcRldxcEZNU0FDTzQ 参考XML文件 我想检查一个节点 如果找不到该节点 我必须将该节点附
  • 相当于 C# 中 Java 的“ByteBuffer.putType()”

    我正在尝试通过从 Java 移植代码来格式化 C 中的字节数组 在 Java 中 使用方法 buf putInt value buf putShort buf putDouble 等等 但我不知道如何将其移植到 C 我尝试过 MemoryS
  • #pragma pack(16) 和 #pragma pack(8) 的效果总是相同吗?

    我正在尝试使用来对齐数据成员 pragma pack n http msdn microsoft com en us library 2e70t5y1 28v vs 100 29 aspx 以下面为例 include
  • 如何设置 CMake 与 clang 交叉编译 Windows 上的 ARM 嵌入式系统?

    我正在尝试生成 Ninja makefile 以使用 Clang 为 ARM Cortex A5 CPU 交叉编译 C 项目 我为 CMake 创建了一个工具链文件 但似乎存在错误或缺少一些我无法找到的东西 当使用下面的工具链文件调用 CM
  • c# 模拟 IFormFile CopyToAsync() 方法

    我正在对一个异步函数进行单元测试 该函数将 IFormFile 列表转换为我自己的任意数据库文件类列表 将文件数据转换为字节数组的方法是 internal async Task
  • FindAsync 很慢,但是延迟加载很快

    在我的代码中 我曾经使用加载相关实体await FindAsync 希望我能更好地遵守 C 异步指南 var activeTemplate await exec DbContext FormTemplates FindAsync exec

随机推荐

  • 服务器选哪个系统,服务器选择哪个操作系统

    服务器选择哪个操作系统 内容精选 换一换 裸金属服务器在详情页面显示的云硬盘设备名称与操作系统内部的设备名称不一致 为防止设备名称变化对业务造成影响 建议通过UUID的方式使用云硬盘 当携带云硬盘创建裸金属服务器完成后 裸金属服务器详情界面
  • DenyHosts安装与部署

    DenyHosts是Python语言写的一个程序软件 运行于Linux上预防SSH暴力破解的 它会分析sshd的日志文件 var log secure 当发现重复的攻击时就会记录IP到 etc hosts deny文件 从而达到自动屏IP的
  • Http协议详解

    引入 超文本传输协议 HTTP HyperText Transfer Protocol 是互联网上应用最为广泛的一种网络协议 所有的WWW文件都必须遵守这个标准 设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法 1960年美
  • 日赚4.12亿,腾讯最新员工薪酬公布:均薪破100万!!!

    近日 腾讯发布2023年第二季度财报 有一项数据冲上热搜 引起热议 据计算 腾讯人均年薪破100万 网友直呼 酸了酸了 这是认真的吗 跟随播妞一起来看看吧 腾讯员工平均年薪达100万 从大厂财报看互联网行业回暖之势 近日 腾讯发布截至6月3
  • [Python]保姆级win11环境安装Python

    1 下载安装包 https www python org downloads 选择自己的系统对应的安装包 我的是Windows系统 我就直接选择它了 选择64位安装包 根据自己系统对应的安装包 2 开始安装 去下载路径下 双击源文件 开始安
  • LeetCode第321场周赛题解

    这周周赛没有什么过多难的 也是可以自己写完的 芜湖 第一道题 6245 找出中枢整数 给你一个正整数 n 找出满足下述条件的 中枢整数 x 1 和 x 之间的所有元素之和等于 x 和 n 之间所有元素之和 返回中枢整数 x 如果不存在中枢整
  • Android之RecyclerView多布局

    做一个项目的主页面的时候 想要它呈现出来的效果 不单一 更丰富那就要使用多布局来展现出来 那么就要思考一个问题 他呈现的是多个布局 怎么才能展现出来不同的布局 逻辑很简单 通过设置几个flag 来表示这些布局当前显示的是哪个布局 接下来 和
  • 使用python对光谱数据进行lorentz峰值拟合(bounds限定拟合参数范围)

    1 lorentz峰值拟合 发光光谱是一种用于表征二维半导体材料光学性质的重要技术 它可以反映出材料中的载流子密度 缺陷态 激子束缚能等信息 由于二维半导体材料的厚度极其薄 其发光信号往往很弱 且受到基底 环境和测量设备等因素的干扰 因此需
  • MySQL怎么实现行转列SQL

    问题 关于Mysql 的分级输出问题 情景 学校里面记录成绩 每个人的选课不一样 而且以后会添加课程 所以不需要把所有课程当作列 数据表里面数据如下图 使用姓名 课程作为联合主键 有些需求可能不需要联合主键 本文以MySQL为基础 其他数据
  • 在JSP中弹出信息框

    下面我以登录界面的代码为例子 在LoginServlet中 判断验证码是否正确 忽略大小写 if attribute equalsIgnoreCase user getCheckCode User login new UserDao log
  • python元组

    第026讲 元组 小甲鱼python第26讲 课堂笔记 rhyme 1 2 3 4 5 上山打老虎 rhyme 1 2 3 4 5 上山打老虎 rhyme 1 2 3 4 5 上山打老虎 rhyme 1 2 3 4 5 上山打老虎 rhym
  • 今天一次性给你讲清楚:File、Blob、FileReader、ArrayBuffer、Base64

    Blob Blob 全称为 binary large object 即二进制大对象 blob对象本质上是js中的一个对象 里面可以储存大量的二进制编码格式的数据 Blob 对象一个不可修改 从Blob中读取内容的唯一方法是使用 FileRe
  • 如何搭建Python开发环境

    目录 一 要求及注意 可选 二 安装Anaconda 三 设置环境变量 四 Pycharm的安装及配置及conda虚拟环境的创建 一 要求及注意 1 要求 操作为 Windows 10 及以上 推荐 64 位 2 注意 系统登录名 非显示名
  • 交通部809协议服务器代码,部标平台检测(三).交通部部标809协议测试和运行测试

    本身交通部在制定jt t 809协议文档时 过度设计 采用双链路的复杂的通信架构 文档中文字抽象 而且歧义是很多的 开发者很容易疑惑 产生各种不确定和疑惑 又没有人答疑 全靠摸索 在加上交通部部表809的测试相对比较困难 因为你在开发的时候
  • Python字符串替换方法replace

    字符串替换方法replace str1 replace old str new str count 字符串的替换 将str1中的 old str 替换成new str old str 将要被替换的字符串 new str 新的字符串 替换成的
  • 认识shell

    Shell俗称 壳 他提供了用户和内核进行交互操作的一种接口 它接收用户输入的命令并把它送入到内核中去执行 Shell实际上是一个命令解释器 它通过解释用户输入的命令并把它传输到系统内核中去执行 Shell有自己的编程语言用于对命令的编辑
  • 贪心算法解汽车加油问题——算法解题报告

    一辆汽车加满油后可行驶n公里 旅途中有若干个加油站 设计一个有效算法 指出应在哪些加油站停靠加油 使沿途加油次数最少 对于给定的n n lt 5000 和k k lt 1000 个加油站位置 编程计算最少加油次数 并证明算法能产生一个最优解
  • 多线程(六):多线程案例

    多线程最最经典案例就是上一章的单例设计模式 当然除了单例设计模式 还有其他的案例 本章就 一一 来介绍 阻塞队列 这里是第一次提到阻塞队列这个东西 简单介绍一下 什么是阻塞队列 阻塞队列 BlockingQueue 是一个支持两个附加操作的
  • 连续和离散傅立叶变换总结及推导

    连续时间复指数信号 e j w 0 t e jw 0t ejw0 t 是否为周期信号 x t x t T x t x t T x t x t T 现假设 x t e j w 0 t x t e jw 0t x t ejw0 t e j w
  • C++反汇编 利用反汇编分析常见C/C++语句的底层实现(硬核)

    文章目录 赋值操作 if条件判断 指针和引用的实质 跳转函数 两个数字的交换操作 数组的赋值及 858993460数字的由来 总结 本节我们利用反汇编技术来对我们最常见的C语言语句进行解析 C 反汇编技术可以让你更好的理解C C语言的底层含