一文入门64位x86汇编
本文我试图用学习一个普通编程语言的思路讲述x86_64汇编。
本文所有汇编代码均在linux系统写成,并且使用了很多linux系统调用。
需要C语言基础。
持续更新中。
目录
- 〇、汇编语言的选择
- (一)如何获得汇编器
- (二)如何编写汇编代码
- 一、Hello World
- (一)简单分析代码
- (二)运行代码
- 二、指令格式与汇编器语法
- (一)指令格式
- intel格式
- att格式
- 关于汇编指令
- (二)汇编器语法
- nasm
- gas
- 三、x86_64架构寄存器
- (一)通用寄存器
- (二)专用寄存器
- (三)其它寄存器
- mmx寄存器
- xmm寄存器
- 四、函数调用和变量使用
- (一)栈帧与调用约定
〇、汇编语言的选择
现在x86_64平台上有很多种不同的汇编语言,这些汇编语言一般只能由特定的汇编器汇编。
现在的x86_64汇编器主要有nasm
、gas
、微软MASM
等,汇编代码的编码格式也有些不同,分别是以nasm
为代表的intel汇编
格式和以gas
为代表的ATT汇编
格式。
本文使用nasm
和gas
两种汇编语言。
(一)如何获得汇编器
- windows平台
nasm
下载nasm官网安装程序并安装即可。gas
它是GCC的一部分,安装mingw-w64,并适当配置环境变量即可,运行它的命令是as
。
- linux平台
nasm
使用软件包管理器即可安装。gas
是GCC的一部分,只要有GCC环境就一定有as
命令。
(二)如何编写汇编代码
使用任意一款支持汇编语法高亮的代码编辑器即可。如vscode
、vim
、emacs
等。
一、Hello World
首先我们编写一个Hello World程序hello.s
。
global _startsection .text_start:endbr64push rbpmov rbp, rsp;系统调用sys_write(stdout, hellomsg, strlen)mov rdi, 1 ;第一个参数,文件描述符,stdout是1mov rsi, hellomsg ;第二个参数,字符串地址mov rdx, msglen ;第三个参数,字符串长度mov rax, 1 ;系统调用号syscallleave;系统调用sys_exit(0)xor rdi, rdi ;参数,返回值0mov rax, 60 ;系统调用号syscallnopsection .datahellomsg: db "Hello World!", 0xa
msglen: equ $-hellomsg
(一)简单分析代码
_start:
汇编语言以_start
为入口点。
endbr64
在x86_64平台中,endbr64
指令须在远跳转后立即出现。
;系统调用sys_write(stdout, hellomsg, strlen)mov rdi, 1 ;第一个参数,文件描述符,stdout是1mov rsi, hellomsg ;第二个参数,字符串地址mov rdx, msglen ;第三个参数,字符串长度mov rax, 1 ;系统调用号syscall
此段程序通过调用linux系统调用sys_write
向stdout
文件写入字符串。
;系统调用sys_exit(0)xor rdi, rdi ;参数,返回值0mov rax, 60 ;系统调用号syscall
此段程序通过调用linux系统调用sys_exit
退出进程。
hellomsg: db "Hello World!", 0xa
msglen: equ $-hellomsg
此处定义了一个字符串"Hello World!\n"
,nasm不支持转义字符,因此用\n
的16进制码0xa
代替。
section .text
section .data
这两个语句就是定义程序中的.text
段和.data
段的,一般程序分为.text
(程序代码).data
(全局变量).rodata
(全局常量).bss
(符号区)等段,这些段会在后文仔细讲解。
(二)运行代码
打开终端输入如下命令:
nasm -felf64 -o hello.o hello.s
ld -o hello hello.o
运行链接好的程序hello
可看到如下输出:
Hello World!
如图所示:
你的第一个x86_64汇编运行成功了!
二、指令格式与汇编器语法
(一)指令格式
x86汇编大体分为两个格式:intel格式
和att格式
。intel格式语法简洁,大多数x86汇编器都使用这种格式;att格式语法复杂,表达精确,适合编译器由高级语言编译输出,gas
和llvm
使用这种格式,因此GNU C语言和clang C语言的内嵌汇编也使用这种格式。
汇编的指令格式非常简单,由指令助记码和0个、1个、2个或3个操作数(基本没有3个以上的)组成,几个操作数之间一般用,
隔开,助记码与操作数之间用空格或缩进隔开。
操作数有源操作数
和目的操作数
,操作数分为3类
- 立即数
- 寄存器(后面的章节会讲到)
- 内存
intel格式
此处讲解的intel格式以nasm
汇编为准。
- 对于助记码,intel格式即intel白皮书上列出的指令表。
- 立即数与寄存器都是直接写出即可。
- 目的操作数在前,源操作数在后,对于3操作数指令第一个是目的操作数,其后都是源。
- 两种操作数的含义将在后文解释
- 内存操作数是由
[]
包围的常数、寄存器、寄存器与常数相加或相乘的数(具体能用什么寄存器、哪几个常数详见后文),有时需要在[]
前加上限定字符串限定长度:byte
一字节word
两字节(一个字)dword
四字节(两个字)qword
八字节(四字)
有些intel格式汇编需后加ptr
。
att格式
- 对于助记码需在intel助记码后加长度限定后缀:
b
字节w
字l
双字q
四字
如intel格式的汇编语句:
mov rax, dword [rel data]
写成att格式为:
movl data(%rip), %rax
上例同时可以展示att格式相比intel格式的区别,
- att格式源操作数在前,目的操作数在后,对于3操作数指令,最后一个是目的操作数。
- 上述汇编指令会将源操作数即内存地址
data(%rip)
开始的四字节的值传送给寄存器%rax
- 上述汇编指令会将源操作数即内存地址
- 寄存器前缀
%
- 内存寻址使用
()
- 另外,立即数前缀为
$
关于汇编指令
后面的文章中,我会讲到很多常用汇编指令,但是仅此一文根本无法讲解全部汇编指令,因此我将intel白皮书
挂到了github上供大家查阅。
(二)汇编器语法
这里先列出一些常用的汇编器器语法。
nasm
- 填充数据
此语法定义的数据用,
隔开db
填充字节;也可以定义字符串但是不支持转义字符。例如上节所示代码倒数第二行。dw
填充字。dd
填充双字。dq
填充四字。
section
定义段。global
声明一个全局标记,使其能够被链接程序ld
识别。extern
声明一个外来标记,ld
时被替换为其他.o
文件中标记定义的地址。
gas
gas
语法较为复杂,将在后文逐步渗透。
三、x86_64架构寄存器
- 此节中若无特殊说明均为64位寄存器。
(一)通用寄存器
rax
累加寄存器,通常用于传递函数返回值;rcx
计数寄存器,通常用于循环计数和函数传参;rbx
基址寄存器,现在的x86架构中可以随意使用;rdx
数据寄存器,可以随意使用,也用于函数传参;rsi
源变址寄存器,用于串指令和函数传参;rdi
目的变址寄存器,用于串指令和函数传参;rbp
栈底指针;rsp
栈顶指针;r8
r9
用于函数传参;r10
~r15
随意使用;
(二)专用寄存器
cs
ds
ss
es
fs
gs
等段寄存器在64位平台几乎没什么用;ip
指令指针寄存器,指向下一条指令的地址;flags
标志位寄存器:ZF
零标志;CF
进位/借位标志;SF
符号标志,正为0负为1;OF
溢出标志,加减乘运算时结果超出能表示的范围时为1;DF
方向标志,用于串指令,向高地址方向为0,向低地址方向为1;
(三)其它寄存器
mmx寄存器
mm0
~mm7
,用于mmx指令集。
xmm寄存器
xmm0
~xmm7
,128位寄存器;
ymm0
`ymm15`,256位寄存器,`ymm0`ymm7
的低128位是xmm的映射;
zmm0
`zmm31`,512位寄存器,`zmm0`zmm15
的低256位是ymm的映射。
- 接下来的章节我们将用独特的视角来逐步了解x86_64汇编。
- 最开始列举的汇编代码中难免有没讲解过的,对于这些代码,复制粘贴即可。
四、函数调用和变量使用
由于这两个都在栈上进行,因此放在同一节中讲解。
首先我们使用C语言写一个简单的递归斐波那契数列:
#include <stdio.h>
int fib(int n)
{if (n == 1 || n == 2)return 1;elsereturn fib(n - 1) + fib(n - 2);
}int main()
{int n;scanf("%d", &n);printf("%d\n", fib(n));
}
接下来的工作是,如何将这个代码翻译成汇编。
首先我们解决fib()
函数。
(一)栈帧与调用约定
在x86_64架构中,栈位于内存中,需要rsp
rbp
两个寄存器进行栈的管理;
栈从高地址开始,向低地址延伸;
rbp
与rsp
指向的地址之间的一段内存,称为栈帧
,一般一个函数只能拥有一个栈帧;
因为一个函数由其它函数调用执行,而这个函数不能使用调用者的栈帧,因此函数被调用后的第一件事就是创建栈帧。
汇编中,一个函数声明就是函数名:
。
fib:endbr64push rbpmov rbp, rsp
进入函数的第一个指令是endbr64
,这个指令是为了跳转时的安全。
push rbp
:
push
指令的作用是将寄存器
、内存值
、立即数
压入栈中;实际执行的效果是将操作数的值复制到rsp
指向的地址处,并将rsp
的值减去操作数的长度(指字节数)。此指令将rbp的值复制到rsp
指向的地址,意为保存上一个栈帧的栈底,使函数返回时能够恢复上一栈帧;rsp
自减8(rbp
为64位寄存器)。
mov rbp, rsp
:
mov
指令就是移动数据;此指令将rsp
的值复制给rbp
。这时,rsp
与rbp
的值相等,代表此时已经退出了调用者的栈帧,可以创建自己的栈帧了。
这时其它寄存器中可能还保存着上一个函数使用的值,因此创建栈帧的第一步是将此函数需要使用的寄存器值push到栈上:
push rbxpush rcx
这两个汇编指令分别将rbx
rcx
的值复制到栈顶,并将栈顶指针-8。
接下来的工作是给栈上变量创建空间,此函数不需要栈上变量,此内容留到主函数中讲解。
然后是语句if (n == 1 || n == 2) return 1;
,首先分析此语句的执行过程:
- 首先判断
n==1
,若成立,则返回1; - 若不成立,判断
n==2
,若成立,返回1; - 若不成立,继续执行。
首先,第1
步和第2
步,都会返回1,所以首先写一下返回1的代码:
F1:
mov rax, 1
ret
第一行的F1
是标号
,标记着紧跟着它的指令的地址,可以通过jmp
跳转指令或者条件跳转指令使CPU执行对应地址处的代码。
在调用约定中,rax规定为返回值寄存器;因此用mov
指令使rax=1,并使用ret
从fib函数中返回。
接下来实现if (n==1||n==2)
条件判断:
cmp rdi, 1
je F1
cmp rdi, 2
je F1
cmp
指令是比较指令,比较两个操作数的大小,比较结果会放到rflags
寄存器中;紧跟其后的je
是条件跳转指令,它会读取rflags
中的某些位来确定最近执行的cmp
指令的结果是否是相等
,如果是相等
,就会跳转到操作数地址处执行,否则跳过此指令。
rdi
是储存函数第一个参数的寄存器,调用约定中,函数的参数使用寄存器传递,按顺序分别是rdi
,rsi
,rdx
,rcx
,r8
,r9
,更多的参数可以用栈传递,不过本人更喜欢先用完其余的r10
~r15
寄存器。
这四个指令分别先判断参数与1和2是否相等,若相等直接跳转至F1
处返回1。
最终语句if (n == 1 || n == 2) return 1;
的汇编代码是这样:
cmp rdi, 1
je F1
cmp rdi, 2
je F1
jmp F0
F1:
mov rax, 1
ret
一文入门64位x86汇编相关推荐
- c语言与64位windows不兼容_微软发布可模拟 64 位 x86 程序的 ARM 版 Windows 10
微软今天宣布推出可以在 ARM 架构 PC 上模拟 64 位 x86 程序的新版 Windows 10.这意味着,拥有 ARM PC 的用户,比如 Surface Pro X 可以安装 64 位 x8 ...
- 64位x86的函数调用栈布局
作者:gfree.wind@gmail.com 博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net 在看本文之前,如果不了解x86的32位 ...
- 不要再被误导了,64位X86 CPU是没有64位寻址能力的!
本文转载于:http://itbbs.pconline.com.cn/9769891.html 最近这几天在CPU超频版发了个悬赏贴,特意看看有多少人认为CPU的位宽意味着寻址能力,结果发现也有相当一 ...
- 浅淡Windows7 32位与64位/x86与x64的区别
看到有很多会员问到底是选Windows7 x86,还是选x64.这里简单的谈一下这这两种系统的区别. 简单的说x86代表32位操作系统 x64代表64位操作系统. 简单的判断电脑是否支持64位操作系 ...
- 【Android 逆向】IDA 工具使用 ( IDA 32 位 / 64 位 版本 | 汇编代码视图 IDA View-A | 字符串窗口 Strings window )
文章目录 一.IDA 32 位 / 64 位 版本 二.汇编代码视图 IDA View-A 三.字符串窗口 Strings window 一.IDA 32 位 / 64 位 版本 IDA 安装完毕后 ...
- 【汇编语言】结合C语言,使用VS 2017调试模式下的反汇编工具学习32位x86汇编指令
0 前言 简要说明x86系列指令集的整体概况与变化. 我给到你补充学习内容:使用VS学习汇编语言的教程 1 8086CPU到现代CPU的变化 做一些了解即可,不是绝对的,取决于设计工艺以及用途,不同计 ...
- [分享]Win7 32位与64位/x86与x64的区别
看到有很多会员问到底是选Windows7 x86,还是选x64.这里简单的谈一下这这两种 系统 的区别. 简单的说x86代表32位操作系统 x64代表64位操作系统. 如果你的 CPU 是双核以上, ...
- win10 64位搭建汇编环境debug
目录 一,下载 二,安装 三,配置 四,链接 一,下载 1,DOSBox0.74 2,MASM 二,安装 1,文件夹DOSBox0.74里面,解压.安装.注意不要安装在C盘. 2, 创建文件夹 MAS ...
- TensorFlow Serving安装笔记(仅限64位x86)
官方文档: https://tensorflow.google.cn/serving/setup 几个要点 翻墙下载公钥 https://storage.googleapis.com/tensorfl ...
最新文章
- 干货!用 Python 快速构建神经网络
- JavaScript继承的多种方式和优缺点
- Django Bakend--后台管理插件开发-01
- Android 双击返回键退出程序 实现
- 马斯克遭“天劫”:40颗星链卫星葬身地磁风暴,数千万美元打了水漂
- 重磅下载!业界首本强化学习应用宝典,阿里核心算法团队联袂打造
- 基因表达聚类分析之初探SOM
- vue自动提交表单_(尚012)Vue表单数据的自动手集(表单数据提交,需要收集表单数据)...
- sqlmap简单用法
- oracle导入步骤,Oracle导入dmp文件步骤
- 【SQL】连接 —— 内连接、外连接、左连接、右连接、交叉连接
- DPDK之PMD原理
- 泰坦尼克数据集kaggle Titanic下载
- 水星无线路由启动dhcp服务器,水星无线路由器桥接设置桥接(图文详解) | 192.168.1.1登陆页面...
- 14年macmini装双硬盘_苹果2014款Mac mini更换固态硬盘图文教程
- 中e管家投资理财收益最大化技巧
- 锤子使用手册 android,锤子爱好者的新手使用指南
- coreseek 词库 导入搜狗词库
- win7下搭载ubuntu双系统,独立引导
- mysql怎么对月份进行统计_MySQL如何按月份统计数据详解(转)
热门文章
- Jmeter响应断言以及JSON断言
- Win10任务栏搜索框无法搜索,显示白色页面
- ci定位 lac_(LAC) 小区识别(CI).PPT
- 谈谈OpenCV中的四边形
- 软件测试之移动端自动化测试
- 新浪微博API学习使用笔记(2)
- acer switch 10 linux,【AcerSwitch10E评测】拆开来用的电脑 Acer Switch 10E评测_Acer Switch 10E_笔记本评测-中关村在线...
- Android Studio上非常棒的插件
- 大数据——有一堆袜子,如何用最快速高效的算法来给袜子配对?
- 2019年伯克利大学 CS294-112《深度强化学习》第2讲:监督学习和模仿学习(笔记)