C语言代码的x86-64汇编指令分析过程记录

先通过Xcode创建一个terminal APP,语言选择C。代码如下:

#include <stdio.h>int main(int argc, const char * argv[]) {int a[7]={1,2,3,4,5,6,7};int *ptr =(int*)(&a+1);printf("%d\n",*(ptr));return 0;
}

在return 0处打上断点,并且选择Xcode菜单Debug|Debug Workflow|Always Show Disassembly,然后run。这时候断点会调到汇编代码里。得到汇编代码如下:

Terminal`main:0x100003ec0 <+0>:   pushq  %rbp0x100003ec1 <+1>:   movq   %rsp, %rbp0x100003ec4 <+4>:   subq   $0x50, %rsp0x100003ec8 <+8>:   movq   0x131(%rip), %rax         ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard0x100003ecf <+15>:  movq   (%rax), %rax0x100003ed2 <+18>:  movq   %rax, -0x8(%rbp)0x100003ed6 <+22>:  movl   $0x0, -0x34(%rbp)0x100003edd <+29>:  movl   %edi, -0x38(%rbp)0x100003ee0 <+32>:  movq   %rsi, -0x40(%rbp)0x100003ee4 <+36>:  movq   0xa5(%rip), %rax0x100003eeb <+43>:  movq   %rax, -0x30(%rbp)0x100003eef <+47>:  movq   0xa2(%rip), %rax0x100003ef6 <+54>:  movq   %rax, -0x28(%rbp)0x100003efa <+58>:  movq   0x9f(%rip), %rax0x100003f01 <+65>:  movq   %rax, -0x20(%rbp)0x100003f05 <+69>:  movl   0x9d(%rip), %eax0x100003f0b <+75>:  movl   %eax, -0x18(%rbp)0x100003f0e <+78>:  leaq   -0x30(%rbp), %rax0x100003f12 <+82>:  addq   $0x1c, %rax0x100003f16 <+86>:  movq   %rax, -0x48(%rbp)0x100003f1a <+90>:  movq   -0x48(%rbp), %rax0x100003f1e <+94>:  movl   (%rax), %esi0x100003f20 <+96>:  leaq   0x85(%rip), %rdi          ; "%d\n"0x100003f27 <+103>: movb   $0x0, %al0x100003f29 <+105>: callq  0x100003f5a               ; symbol stub for: printf0x100003f2e <+110>: movq   0xcb(%rip), %rax          ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard0x100003f35 <+117>: movq   (%rax), %rax0x100003f38 <+120>: movq   -0x8(%rbp), %rcx0x100003f3c <+124>: cmpq   %rcx, %rax0x100003f3f <+127>: jne    0x100003f4d               ; <+141> at main.c
->  0x100003f45 <+133>: xorl   %eax, %eax0x100003f47 <+135>: addq   $0x50, %rsp0x100003f4b <+139>: popq   %rbp0x100003f4c <+140>: retq   0x100003f4d <+141>: callq  0x100003f54               ; symbol stub for: __stack_chk_fail0x100003f52 <+146>: ud2    

首先介绍下面会用到的几个寄存器:

rip :程序计数寄存器
rsp : 栈指针寄存器,指向栈顶
rbp : 栈基址寄存器,指向栈底
edi : 函数参数
rsi/esi : 函数参数
eax : 累加器或函数返回值用

1、 pushq %rbp
将rbp的地址压栈,rsp继续指向栈顶

2、 movq %rsp, %rbp
将栈顶rsp的值赋值给栈底rbp,

3、 subq $0x50, %rsp
栈顶往下移5*16个字节,可以理解成给后面预留的80字节的空间。X64中栈开辟的大小都是0x10的倍数。

4、movq   0x131(%rip), %rax         ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard

0x131(%rip)的意思是下一条指令的地址(0x100003ecf)加上0x131得到目标地址(0x100004000),然后取得其中的8字节值,设置给rax寄存器。

选中Debug workflow|View Memory,在Address里输入‘0x100004000’然后回车,我们可以看到此处的内容为0x00007ff84ef2f8a0(注意大小端问题):

 5、movq   (%rax), %rax

从rax寄存器存放的地址处取得值,并传给rax寄存器。类似前面的操作,我们发现此处的值为0x55d1d55afee700d6:

 6、movq   %rax, -0x8(%rbp)

现在把rax中的值,也就是上面这个图上所示的8个字节,存入rbp-0x8的位置。我们先打印下rbp和rsp的值,然后跳到rsp处查看内存:

 

红框处即是我们存放值的地方。右边8字节则是rbp指向的位置。

7、movl   $0x0, -0x34(%rbp)

将4字节0设置到rbp-0x34的位置,这里的目的是将下一条指令中-0x38(%rbp)的高字节清零。该位置为0x7ff7bfeff3ac:

 8、movl   %edi, -0x38(%rbp)

该命令保存edi寄存器的值到rbp-0x38位置,也就是上图的0x7ff7bfeff3a8,值是1。前面我们说edi是用来保存函数参数的,也就是int argc,在这个例子中argc的值为1,所以edi寄存器的值为1。

9、movq   %rsi, -0x40(%rbp)

该命令保存rsi寄存器的值到rbp-0x40位置,也就是上图的0x7ff7bfeff3a0,值是0x7ff7bfeff518。这里是参数argv的值0x7ff7bfeff708。由于argv是const char **,因此这也是个地址值,我们前往该地址查看其内容。

10、movq   0xa5(%rip), %rax

 获取位于0x100003eeb+0xa5=0x100003f90处的8字节内容,然后存入rax寄存器:

 11、movq   %rax, -0x30(%rbp)

 将rax寄存器的值存入rbp-0x30的位置:

12-17跟上面两步相同,就是存3,4,5,6,7的值,注意7是单独存的,因为movl表示4字节,而movq代表8字节,也就是2个int。

0x100003eef <+47>:  movq   0xa2(%rip), %rax0x100003ef6 <+54>:  movq   %rax, -0x28(%rbp)0x100003efa <+58>:  movq   0x9f(%rip), %rax0x100003f01 <+65>:  movq   %rax, -0x20(%rbp)0x100003f05 <+69>:  movl   0x9d(%rip), %eax0x100003f0b <+75>:  movl   %eax, -0x18(%rbp)

18、leaq   -0x30(%rbp), %rax

获取rbp-0x30=0x7ff7bfeff3b0,然后存入rax寄存器。

19、addq   $0x1c, %rax

将rax寄存器中的值0x7ff7bfeff3b0,加上0x1c后(0x7ff7bfeff3cc)存入rax寄存器。这里对应代码`int *ptr =(int*)(&a+1);`0x1c是28,也就是数组的大小28字节,说明指针的加法是在编译阶段将数值替换为具体的值,也就是该值乘以指针类型大小后的值。

20、movq   %rax, -0x48(%rbp)

接下去把rax的值0x7ff7bfeff3cc存入rbp-0x48的位置:

 21、movq   -0x48(%rbp), %rax

接着又把刚存入的这个值存入寄存器rax

22、movl   (%rax), %esi

把rax寄存器所存地址对应的值(1)存入寄存器esi,这是作为下面要调用的print方法的第二个参数。

 23、leaq   0x85(%rip), %rdi          ; "%d\n"

rip=0x100003f27,加上0x85后为0x100003fac,然后设置给rdi寄存器,也就是print调用的第一个参数。0x100003fac这个位置的值刚好是字符串"%d\n"。

 24、movb   $0x0, %al

将立即数0存储到al寄存器中。那么如何理解eax,ax,al(ah)之间的关系
专业点可以这样解释:eax是32位寄存器,ax是16位寄存器,al(ah)是八位寄存器。

对于变长参数的函数,要用%al指明用到的vector registers的个数 ,比如printf,这里我们没有用到可变参数,所以要给al寄存器设置0。参考:Why are the %al register and stack modified before calling printf x86 assembly from C "Hello World" program compiled by gcc

汇编函数调用的参数传递 

 25、callq  0x100003f5a               ; symbol stub for: printf

调用printf函数。call有一个作用:将call指令的下一条指令地址压栈:

 

26、movq   0xcb(%rip), %rax          ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard

27、movq   (%rax), %rax

26和27步骤跟4和5处类似,这里不再赘述。

28、movq   -0x8(%rbp), %rcx

将栈底8字节存入rcx寄存器

29、cmpq   %rcx, %rax

比较rcx寄存器和rax寄存器的值是否相等,并把结果写入状态寄存器

30、jne    0x100003f4d               ; <+141> at main.c

如果29的比较结果为不相等,就跳转0x100003f4d处继续执行,也就是35处。相等的话执行31处。这里主要是使用__stack_chk_guard_ptr来判断是否发生栈溢出,导致栈底开始8字节被篡改。可以参考關於__stack_chk_guard_ptr的理解

31、xorl   %eax, %eax

将eax寄存器清零,作为main函数的return值

32、addq   $0x50, %rsp

这句正好对应前面的subq $0x50, %rsp。通过给栈顶指针加上开辟栈的大小,回收栈顶指针开辟的空间。

33、popq   %rbp

这句指令表示出栈,同时将出栈的值放入寄存器rbp

34、retq

这句表示退出main函数,会恢复rip值。本例没有体现这一点,由main函数的调用者保存rip。

35、callq  0x100003f54               ; symbol stub for: __stack_chk_fail

调用__stack_chk_fail函数

36、ud2

UD2 指令的字节编码为 0F 0B,它是一个两字节的指令。在汇编语言中,可以使用 UD2 指令来实现一些特殊的功能,比如触发调试断点或者中断程序的执行。 UD2 指令常用于调试程序。具体可查看:ud2 汇编指令

最终的栈布局

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/80053.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R3LIVE项目实战(3) — 双目相机与激光雷达联合标定

目录 3.1 lidar_camera_calib简介 3.2 环境准备 3.3 编译 3.4 运行数据集 (1) 单场景标定 (2) 多场景标定 3.5 使用您自己的传感器设置 3.5.1 采集相机图片和雷达bag数据 3.5.2 使用多场景标定 3.5.3 相机内参获取 3.5.4 运行标定程序 3.5.5 验证结果 源码地址&am…

嵌入式基础知识-存储管理

上篇介绍了存储器的相关知识&#xff0c;偏重的是硬件结构&#xff0c;本篇介绍存储管理的相关知识&#xff0c;偏重的是软件管理。 1 存储管理概念 操作系统&#xff0c;包括嵌入式系统&#xff0c;通常利用存储管理单元MMU&#xff08;Memory Management Unit&#xff09;来…

填补5G物联一张网,美格智能快速推进RedCap商用落地

自5G R17版本标准冻结以来&#xff0c;RedCap一直引人注目。2023年更是5G RedCap突破性发展的一年&#xff0c;从首款5G RedCap调制解调器及射频系统——骁龙X35发布&#xff0c;到国内四大运营商发布RedCap技术白皮书&#xff0c;芯片厂商、模组厂商、运营商及终端企业都在积极…

Nginx(1)

目录 1.Nginx概述2.Nginx的特点3.Nginx主要功能1.反向代理2.负载均衡 1.Nginx概述 Nginx (engine x) 是一个自由的、开源的、高性能的HTTP服务器和反向代理服务器&#xff0c;也是一个IMAP、POP3、SMTP代理服务器。 Nginx是一个强大的web服务器软件&#xff0c;用于处理高并发…

网卡内部的 DMA

前言 MCU、SOC 内部通常带有 DMA 控制器&#xff0c;要想使用 DMA 通常需要如下操作 选择通道配置传输方向&#xff08;内存到外设、内存到内存、外设到内存&#xff09;设置源地址、目的地址&#xff08;内存地址、外设地址&#xff09;设置源地址、目的地址是否自增设置位宽…

04-5_Qt 5.9 C++开发指南_QComboBox和QPlainTextEdit

文章目录 1. 实例功能概述2. 源码2.1 可视化UI设计2.2 widget.h2.3 widget.cpp 1. 实例功能概述 QComboBox 是下拉列表框组件类&#xff0c;它提供一个下拉列表供用户选择&#xff0c;也可以直接当作一个QLineEdit 用作输入。OComboBox 除了显示可见下拉列表外&#xff0c;每个…

冠达管理投资前瞻:三星加码机器人领域 大信创建设提速

上星期五&#xff0c;沪指高开高走&#xff0c;盘中一度涨超1%打破3300点&#xff0c;但随后涨幅收窄&#xff1b;深成指、创业板指亦强势震动。截至收盘&#xff0c;沪指涨0.23%报3288.08点&#xff0c;深成指涨0.67%报11238.06点&#xff0c;创业板指涨0.95%报2263.37点&…

【HCIP】OSPF综合实验

题目&#xff1a; 配置&#xff1a; R1 //ip分配 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 172.16.0.1 27 [r1-GigabitEthernet0/0/0]q [r1]int lo [r1]int LoopBack 0 [r1-LoopBack0]ip add 172.16.1.1 24//配置缺省 [r1]ip route-static 0.0.0.0 0 172.16.0.3 //启动…

系统架构设计师-软件架构设计(7)

目录 大型网站系统架构演化 一、第一阶段&#xff1a;单体架构 到 第二阶段&#xff1a;垂直架构 二、第三阶段&#xff1a;使用缓存改善网站性能 1、缓存与数据库的数据一致性问题 2、缓存技术对比【MemCache与Redis】 3、Redis分布式存储方案 4、Redis集群切片的常见方式 …

日撸java_day60

文章目录 小结k近邻算法&#xff08;knn&#xff09;定义算法流程距离度量k值的选择总结 聚类定义k-means聚类步骤k-means算法小结 小结 k近邻算法&#xff08;knn&#xff09; 定义 如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别…

VLAN原理+配置

目录 一&#xff0c; 以太网二层交换机 二&#xff0c;三层架构&#xff1a; 三&#xff0c;VLAN配置思路 1.创建vlan 2.接口划入vlan 3.trunk干道 4.vlan间路由器 5.DHCP池塘配置 四&#xff0c;华为VLAN部分的接口模式讲解&#xff1a; 五&#xff0c;华为VLAN部分的…

《Zookeeper》从零开始学Zookeeper源码(三)之服务器的启动过程

目录 QuorumPeerMain QuorumPeerMain 在搭建本地的源码环境中&#xff0c;启动zookeeper服务端的入口为QuorumPeerMain&#xff0c;先看下它的类结构&#xff1a; 它本身只有一个属性quorumPeer&#xff0c;它代表了zookeeper集群中的一台机器&#xff0c;它会不断检测当前服…

docker镜像push到仓库

镜像可以很方便直接 push 到 docker 的公共仓库或阿里云仓库 一、Dockerpush指定仓库是什么&#xff1f; Dockerpush是Docker的一个命令&#xff0c;用于将本地的Docker镜像推送到Docker官方公共仓库或用户私人仓库。而指定仓库则是将这个Docker镜像推送到指定的仓库中。 通过D…

Visual Studio Code中对打开的脚本格式统一

什么是Language Server Protocol (LSP)? Language Server Protocol&#xff08;语言服务器协议&#xff0c;简称LSP&#xff09;是微软在2016年提出的一套统一的通讯协议方案。LSP定义了一套编辑器或者IDE与语言服务器&#xff08;Language Server&#xff09;之间使用的协议&…

Vue3 实现产品图片放大器

Vue3 实现类似淘宝、京东产品详情图片放大器功能 环境&#xff1a;vue3tsvite 1.创建picShow.vue组件 <script lang"ts" setup> import {ref, computed} from vue import {useMouseInElement} from vueuse/core/*获取父组件的传值*/ defineProps<{images:…

分布式应用:ELK企业级日志分析系统

目录 一、理论 1.ELK 2.ELK场景 3.完整日志系统基本特征 4.ELK 的工作原理 5.ELK集群准备 6.Elasticsearch部署&#xff08;在Node1、Node2节点上操作&#xff09; 7.Logstash 部署&#xff08;在 Apache 节点上操作&#xff09; 8.Kiabana 部署&#xff08;在 Node1 节点…

什么是全局代理,手机怎么设置全局代理

目录 什么是全局代理 全局代理的优缺点 优点 缺点 手机怎么设置全局代理 注意事项 总结 在计算机网络和信息安全中&#xff0c;全局代理是一种常用的技术手段&#xff0c;用于将网络流量通过代理服务器进行转发和处理。本文将介绍什么是全局代理&#xff0c;探讨全局代理…

使用kubeadm快速部署一个k8s集群

Kubernetes概述 使用kubeadm快速部署一个k8s集群 Kubernetes高可用集群二进制部署&#xff08;一&#xff09;主机准备和负载均衡器安装 Kubernetes高可用集群二进制部署&#xff08;二&#xff09;ETCD集群部署 Kubernetes高可用集群二进制部署&#xff08;三&#xff09;部署…

2020年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题 第1题 执行语句print(1010.0)的结果为&#xff1f; A&#xff1a;10 B&#xff1a;10.0 C&#xff1a;True D&#xff1a;False 正确的答案是 C&#xff1a;True。 解析&#xff1a;在Python中&#xff0c;比较运算符 “” 用于比较两个值是否相等。在这个特…

2023 电赛 E 题 激光笔识别有误--使用K210/Openmv/树莓派/Jetson nano实现激光笔在黑色区域的目标检测

1. 引言 1.1 激光笔在黑色区域目标检测的背景介绍 在许多应用领域&#xff0c;如机器人导航、智能家居和自动驾驶等&#xff0c;目标检测技术的需求日益增加。本博客将聚焦于使用K210芯片实现激光笔在黑色区域的目标检测。 激光笔在黑色区域目标检测是一个有趣且具有挑战性的…