本文我试图用学习一个普通编程语言的思路讲述x86_64汇编。

本文所有汇编代码均在linux系统写成,并且使用了很多linux系统调用。

需要C语言基础。

持续更新中。

目录

  • 〇、汇编语言的选择
    • (一)如何获得汇编器
    • (二)如何编写汇编代码
  • 一、Hello World
    • (一)简单分析代码
    • (二)运行代码
  • 二、指令格式与汇编器语法
    • (一)指令格式
      • intel格式
      • att格式
      • 关于汇编指令
    • (二)汇编器语法
      • nasm
      • gas
  • 三、x86_64架构寄存器
    • (一)通用寄存器
    • (二)专用寄存器
    • (三)其它寄存器
      • mmx寄存器
      • xmm寄存器
  • 四、函数调用和变量使用
    • (一)栈帧与调用约定

〇、汇编语言的选择

现在x86_64平台上有很多种不同的汇编语言,这些汇编语言一般只能由特定的汇编器汇编。
现在的x86_64汇编器主要有nasmgas、微软MASM等,汇编代码的编码格式也有些不同,分别是以nasm为代表的intel汇编格式和以gas为代表的ATT汇编格式。
本文使用nasmgas两种汇编语言。

(一)如何获得汇编器

  • windows平台

    • nasm 下载nasm官网安装程序并安装即可。
    • gas 它是GCC的一部分,安装mingw-w64,并适当配置环境变量即可,运行它的命令是as
  • linux平台
    • nasm 使用软件包管理器即可安装。
    • gas 是GCC的一部分,只要有GCC环境就一定有as命令。

(二)如何编写汇编代码

使用任意一款支持汇编语法高亮的代码编辑器即可。如vscodevimemacs等。

一、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_writestdout文件写入字符串。

    ;系统调用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格式语法复杂,表达精确,适合编译器由高级语言编译输出,gasllvm使用这种格式,因此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两个寄存器进行栈的管理;
栈从高地址开始,向低地址延伸;
rbprsp指向的地址之间的一段内存,称为栈帧,一般一个函数只能拥有一个栈帧;
因为一个函数由其它函数调用执行,而这个函数不能使用调用者的栈帧,因此函数被调用后的第一件事就是创建栈帧。
汇编中,一个函数声明就是函数名:

fib:endbr64push rbpmov rbp, rsp

进入函数的第一个指令是endbr64,这个指令是为了跳转时的安全。
push rbp
push指令的作用是将寄存器内存值立即数压入栈中;实际执行的效果是将操作数的值复制到rsp指向的地址处,并将rsp的值减去操作数的长度(指字节数)。此指令将rbp的值复制到rsp指向的地址,意为保存上一个栈帧的栈底,使函数返回时能够恢复上一栈帧;rsp自减8(rbp为64位寄存器)。
mov rbp, rsp
mov指令就是移动数据;此指令将rsp的值复制给rbp。这时,rsprbp的值相等,代表此时已经退出了调用者的栈帧,可以创建自己的栈帧了。
这时其它寄存器中可能还保存着上一个函数使用的值,因此创建栈帧的第一步是将此函数需要使用的寄存器值push到栈上:

 push rbxpush rcx

这两个汇编指令分别将rbx rcx的值复制到栈顶,并将栈顶指针-8。
接下来的工作是给栈上变量创建空间,此函数不需要栈上变量,此内容留到主函数中讲解。
然后是语句if (n == 1 || n == 2) return 1;,首先分析此语句的执行过程:

  1. 首先判断n==1,若成立,则返回1;
  2. 若不成立,判断n==2,若成立,返回1;
  3. 若不成立,继续执行。
    首先,第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汇编相关推荐

  1. c语言与64位windows不兼容_微软发布可模拟 64 位 x86 程序的 ARM 版 Windows 10

    微软今天宣布推出可以在 ARM 架构 PC 上模拟 64 位 x86 程序的新版 Windows 10.这意味着,拥有 ARM PC 的用户,比如 Surface Pro X 可以安装 64 位 x8 ...

  2. 64位x86的函数调用栈布局

    作者:gfree.wind@gmail.com 博客:blog.focus-linux.net    linuxfocus.blog.chinaunix.net 在看本文之前,如果不了解x86的32位 ...

  3. 不要再被误导了,64位X86 CPU是没有64位寻址能力的!

    本文转载于:http://itbbs.pconline.com.cn/9769891.html 最近这几天在CPU超频版发了个悬赏贴,特意看看有多少人认为CPU的位宽意味着寻址能力,结果发现也有相当一 ...

  4. 浅淡Windows7 32位与64位/x86与x64的区别

    看到有很多会员问到底是选Windows7 x86,还是选x64.这里简单的谈一下这这两种系统的区别. 简单的说x86代表32位操作系统  x64代表64位操作系统. 简单的判断电脑是否支持64位操作系 ...

  5. 【Android 逆向】IDA 工具使用 ( IDA 32 位 / 64 位 版本 | 汇编代码视图 IDA View-A | 字符串窗口 Strings window )

    文章目录 一.IDA 32 位 / 64 位 版本 二.汇编代码视图 IDA View-A 三.字符串窗口 Strings window 一.IDA 32 位 / 64 位 版本 IDA 安装完毕后 ...

  6. 【汇编语言】结合C语言,使用VS 2017调试模式下的反汇编工具学习32位x86汇编指令

    0 前言 简要说明x86系列指令集的整体概况与变化. 我给到你补充学习内容:使用VS学习汇编语言的教程 1 8086CPU到现代CPU的变化 做一些了解即可,不是绝对的,取决于设计工艺以及用途,不同计 ...

  7. [分享]Win7 32位与64位/x86与x64的区别

    看到有很多会员问到底是选Windows7 x86,还是选x64.这里简单的谈一下这这两种 系统 的区别. 简单的说x86代表32位操作系统  x64代表64位操作系统. 如果你的 CPU 是双核以上, ...

  8. win10 64位搭建汇编环境debug

    目录 一,下载 二,安装 三,配置 四,链接 一,下载 1,DOSBox0.74 2,MASM 二,安装 1,文件夹DOSBox0.74里面,解压.安装.注意不要安装在C盘. 2, 创建文件夹 MAS ...

  9. TensorFlow Serving安装笔记(仅限64位x86)

    官方文档: https://tensorflow.google.cn/serving/setup 几个要点 翻墙下载公钥 https://storage.googleapis.com/tensorfl ...

最新文章

  1. 干货!用 Python 快速构建神经网络
  2. JavaScript继承的多种方式和优缺点
  3. Django Bakend--后台管理插件开发-01
  4. Android 双击返回键退出程序 实现
  5. 马斯克遭“天劫”:40颗星链卫星葬身地磁风暴,数千万美元打了水漂
  6. 重磅下载!业界首本强化学习应用宝典,阿里核心算法团队联袂打造
  7. 基因表达聚类分析之初探SOM
  8. vue自动提交表单_(尚012)Vue表单数据的自动手集(表单数据提交,需要收集表单数据)...
  9. sqlmap简单用法
  10. oracle导入步骤,Oracle导入dmp文件步骤
  11. 【SQL】连接 —— 内连接、外连接、左连接、右连接、交叉连接
  12. DPDK之PMD原理
  13. 泰坦尼克数据集kaggle Titanic下载
  14. 水星无线路由启动dhcp服务器,水星无线路由器桥接设置桥接(图文详解) | 192.168.1.1登陆页面...
  15. 14年macmini装双硬盘_苹果2014款Mac mini更换固态硬盘图文教程
  16. 中e管家投资理财收益最大化技巧
  17. 锤子使用手册 android,锤子爱好者的新手使用指南
  18. coreseek 词库 导入搜狗词库
  19. win7下搭载ubuntu双系统,独立引导
  20. mysql怎么对月份进行统计_MySQL如何按月份统计数据详解(转)

热门文章

  1. Jmeter响应断言以及JSON断言
  2. Win10任务栏搜索框无法搜索,显示白色页面
  3. ci定位 lac_(LAC) 小区识别(CI).PPT
  4. 谈谈OpenCV中的四边形
  5. 软件测试之移动端自动化测试
  6. 新浪微博API学习使用笔记(2)
  7. acer switch 10 linux,【AcerSwitch10E评测】拆开来用的电脑 Acer Switch 10E评测_Acer Switch 10E_笔记本评测-中关村在线...
  8. Android Studio上非常棒的插件
  9. 大数据——有一堆袜子,如何用最快速高效的算法来给袜子配对?
  10. 2019年伯克利大学 CS294-112《深度强化学习》第2讲:监督学习和模仿学习(笔记)