当前位置: 首页 > news >正文

x86系列CPU寄存器和汇编指令总结

文章目录

    • 概要
    • 一、寄存器
        • 1.1、8086寄存器
        • 1.2、通用寄存器
        • 1.3、扩展寄存器
    • 二、指令集
    • 三、x86指令集常见指令使用说明
    • 四、汇编
        • 4.1、汇编语法
        • 4.2、nsam汇编
    • 五、参考

概要

在对学习Go的过程中,涉及到了汇编,因此对X86系列CPU的背景、寄存器、汇编指令做了一番了解,仅以本文作为总结,便于后续查看。

x86架构始于Intel在1978年推出的8086 CPU(典中典),它是从Intel 8008处理器中发展而来的,而8008则是发展自Intel 4004的。8086在三年后为IBM PC所选用,之后x86便成为了电脑的标配(当然,现在有些电脑采用ARM架构的CPU,比如mac m1),成为了非常成功的CPU架构。

除了Intel公司,其他公司也有制造x86架构的CPU,比如IBM、IDT以及Transmeta,但Intel以外最成功的制造商要数AMD了,其早先产品Athlon系列处理器的市场份额仅次于Intel的Pentium。

值得注意的是8086是16位处理器,直到1985年32位的80386(IA-32)才被开发,接着推出了一系列针对32位架构细微改进的CPU型号,直到2003年AMD对于这个架构进行了64位的扩展,并命名为AMD64,后来英特尔也推出了与之兼容的处理器,并命名为Intel 64。两者一般被统称为x86-64或x64,开创了x86的64位时代。

一、寄存器

正如概要中所说,x86系列发展从16、32到64位,寄存器也随之发展。

1.1、8086寄存器

8086结构图
8086中寄存器总共为 14 个,且均为 16 位。

  • 通用寄存器
寄存器含义
AX、BX、CX、DX
也称为数据寄存器
AX (Accumulator):累加寄存器,也称之为累加器;
BX (Base):基地址寄存器;
CX (Count):计数器寄存器;
DX (Data):数据寄存器;
SP、BP
也称为指针寄存器
SP (Stack Pointer):堆栈指针寄存器,存放栈顶偏移地址,与SS配合使用,即栈顶地址=SS给出的栈段首地址+SP中偏移地址;
BP (Base Pointer):基指针寄存器,一般存放函数栈帧起始指针;
SI、DI
也称为变址寄存器
SI (Source Index):源变址寄存器;
DI (Destination Index):目的变址寄存器;

ps:AX可以通过AH操作其高8位,AL操作其低8位(这样可以当做两个8位寄存器使用了),BX、CX、DX同理。

  • 控制寄存器
寄存器含义
IPIP (Instruction Pointer):指令指针寄存器;存放下一条指令的偏移地址,搭配CS使用,即下一条指令的地址=CS给出代码段基址<<4+IP中偏移地址
FLAG标志寄存器

ps:偏移地址也称偏移量,就是计算机里的内存分段后,在段内某一地址相对于段首地址(段地址)的偏移量。

  • 段寄存器
寄存器含义
CSCS (Code Segment):代码段寄存器;
DSDS (Data Segment):数据段寄存器;
SSSS (Stack Segment):堆栈段寄存器;
ESES (Extra Segment):附加段寄存器;

8086寄存器示意图

DS 寄存器和 ES 寄存器都属于段寄存器,其实它们和 CS 寄存器以及 SS 寄存器用起来区别不大,既然是段寄存器的话,自然它们存放的就是某个段地址了 。我们已经知道,如果 CPU 要访问一个内存单元时,我们必须要提供一个指向这个内存单元的物理地址给 CPU ,而我们也知道在 8086 CPU 中,物理地址是由段地址左移 4 位,然后加上偏移地址形成的,所以,我们也就只需要提供段地址和偏移地址即 OK 。8086 CPU 呢,提供了一个 DS 寄存器,并且通常都是通过这个 DS 段寄存器来存放要访问的数据的段地址 。DS(Data Segment):很显然,DS 中存放的是数据段的段地址 。但是这里不得不再点一下,那就是我们对段的支持是在 CPU 上体现的,而不是在内存中实现了段,所以事实上我们使用的段其实是一个逻辑概念,即是我们自己定义的,再说白了,我定义一个段,我说它是数据段那它就是数据段,我说它是代码段那么它就是代码段,它们其实都是一块连续的内存而已,我们只要能定位不同段即可,至于为什么要区分为数据段和代码段,很明显,是用来给我们编程提供方便的,即我们在自己的思想上或者说是编码习惯上规定,数据放数据段中,代码放代码段中 。而我们在使用数据段的时候,为了方便或者说是代码的编写方便起见,我们一般把数据段的段地址放在 DS 寄存器中,当然,如果你硬要觉得 DS 不顺眼,那你可以换个 ES 也是一样的,至于 ES(Extra Segment) 段寄存器的话,自然,是一个附加段寄存器,如果再说得过分点,就当它是个扩展吧,当你发现,你几个段寄存器不够用的时候,你可以考虑使用 ES 段寄存器,在使用方式上,则和其他的段寄存器没什么区别 。

1.2、通用寄存器

通用寄存器在汇编指令中是最多用到了,所以这里多说一嘴。

最初的8086有8个16位通用寄存器从%ax~%sp,等开发出32位时,这8个寄存器也被扩展成32位寄存器,标号命名以%e(Extense)开头从%eax ~ %esp,后来又了64位的,这8个寄存器也被扩展成64位寄存器,标号命名以%r(Register)开头从%rax ~ %rsp,另外还增加了8个寄存器,它们的标号命名规则为%r8 ~%r15。

x64通用寄存器:

64位寄存器操作低32位操作低16位操作低8位
raxeaxaxal
rax与rsp之间的寄存器同理
rspespspspl
r8r8dr8wr8b
r8与r15之间的同理
r15r15dr15wr15b

另外rax、rbx、rcx 和 rdx 的高 8 位仍可通过 ah、bh、ch、dh访问。

1.3、扩展寄存器

Intel为了增强CPU对多媒体信息的处理能力,提高CPU处理3D图形、视频和音频信息的能力,在1996年推出了多媒体扩展指令集MMX(Multi-Media Extension),为了支持该指令集,增加了[MM0,MM7]8个64位的寄存器。

由于MMX指令集只局限于整数的运算,反响并不好。在1999年Intel推出SSE指令集(Streaming SIMD Extension),支持整数和浮点数运算,从此多了[XMM0,XMM15]共16个128位的寄存器。后续又接连推出了SSE2到SSE4.2作为补充的SSE家族,还有AVX、AVX-512、AMX等指令指令集家族,相应的也增加了[YMM0,YMM15]16个256位的寄存器和[ZMM0,ZMM31]32个512位的寄存器

ps:当然,x86系列寄存器肯定不止这些,本文只介绍了相对常用的,有兴趣的可到Intel官网了解传送门

二、指令集

指令集,就是CPU中用来计算和控制计算机系统的一套指令的集合,而每一种新型的CPU在设计时就规定了一系列与其他硬件电路相配合的指令系统。而指令集的先进与否,也关系到CPU的性能发挥,它也是CPU性能体现的一个重要标志。

通俗的理解,指令集就是CPU能认识的语言,指令集运行于一定的微架构之上,不同的微架构可以支持相同的指令集,比如Intel和AMD的CPU的微架构是不同的,但是同样支持X86指令集,这很容易理解,指令集只是一套指令集合,一套指令规范,具体的实现,仍然依赖于CPU的翻译和执行。

指令集一般分为RISC(精简指令集 Reduced Instruction Set Computer)和CISC(复杂指令集Complex Instruction Set Computer)。Intel X86的第一个CPU定义了第一套指令集,后来一些公司发现很多指令并不常用,所以决定设计一套简洁高效的指令集,称之为RICS指令集(ARM指令集),从而将原来的Intel X86指令集定义为CISC指令集。两者的使用场合不一样,对于复杂的系统,CISC更合适,否则,RICS更合适,且低功耗。

  1. X86指令集
    X86指令集是Intel为其第一块16位CPU(i8086)专门开发的,IBM1981年推出的世界第一台PC机中的CPU—i8088(i8086简化版)使用的也是X86指令,同时电脑中为提高浮点数据处理能力而增加的X87芯片系列数学协处理器则另外使用X87指令,以后就将X86指令集和X87指令集统称为X86指令集。常见的如mov、add、sub、pop、push等.

  2. MMX指令集
    1996年Intel的MMX(AMD认为这是矩阵数学扩展Matrix Math Extensions的缩写,但大多数时候都被当成Multi-Media Extension,而Intel从来没有官方宣布过词源)技术出现。尽管这项新的科技得到广泛宣传,但它的精髓是非常简单的:MMX定义了八个64位SIMD寄存器,与Intel Pentium处理器的FPU堆栈有相重叠。不幸的是,这些指令无法非常简单地对应到由原来C编译器所产生的脚本中。MMX也只局限于整数的运算。这项技术的缺点导致MMX在它早期的存在有轻微的影响。现今,MMX通常是用在某些2D影片应用程序中。

  3. SSE指令集
    由于MMX指令并没有带来3D游戏性能的显著提升,所以,1999年Inter公司在Pentium III CPU产品中推出了数据流单指令序列扩展指令(SSE),兼容MMX指令。SSE为Streaming SIMD Extensions的缩写,如同其名称所表示的,是一种SSE指令包括了四个主要的部份:单精确度浮点数运算指令、整数运算指令(此为MMX之延伸,并和MMX使用同样的寄存器)、Cache控制指令、和状态控制指令。
    在Pentium 4 CPU中,Inter公司开发了新指令集SSE2。SSE2指令一共144条,包括浮点SIMD指令、整形SIMD指令、SIMD浮点和整形数据之间转换、数据在MMX寄存器中转换等几大部分。其中重要的改进包括引入新的数据格式,如:128位SIMD整数运算和64位双精度浮点运算等。相对于SSE2,SSE3又新增加了13条新指令,此前它们被统称为pni(prescott new instructions)。13条指令中,一条用于视频解码,两条用于线程同步,其余用于复杂的数学运算、浮点到整数转换和SIMD浮点运算。SSE4增加了50条新的增加性能的指令,这些指令有助于编译、媒体、字符/文本处理和程序指向加速。

这里说下 SIMD(Single Instruction Multiple Data),单指令多数据,其不是指令集,而是一种思想,MMX、SSE、AVX等指令集就是这种思想的具体实现。它的精髓就是要通过一条指令,实现对寄存器上多组数据并行操作。比如arr:=[]int64{1,2,3,4},计算下标1与下标3之和,下标2与下标4之和,如果使用x86指令集,就要执行add rax,rbx两次,而SSE指令直接addpd xmm0,xmm1就搞定了。

ps:AVX、AVX-512、AMX等指令集,就不一一介绍了。传送门

三、x86指令集常见指令使用说明

x86指令大全传送门。

本章节最好结合实战文章来看1和2。

汇编语法有很多,主流如Intel 语法(源操作数在后,目标操作数在前)、AT&T 语法(源操作数在前,目标操作数在后)、本节解释指令示例用Intel 语法,x86-64环境

  • mov
    mov指令将源操作数(可以是寄存器的内容、内存中的内容或立即数)复制到目的操作数(寄存器中或内存上)。mov不能用于直接从内存复制到内存。

语法如下:
mov <reg>,<reg>
mov <reg>,<mem>
mov <mem>,<reg>
mov <reg>,<const>
mov <mem>,<const>
示例如下:
mov rax, rbx; 将rbx的值拷贝到rax
mov rax, 0x5; 将立即数5加载到rax寄存器
mov qword ptr [rsp+0x18], 0x1;将立即数1(qword ptr表示占8字节)加载到地址rsp+0x18到rsp+0x20之间的内存区域
mov qword ptr [rsp+0x18], rax; 将rax寄存器的内容(qword ptr表示大小为8字节)加载到地址rsp+0x18到rsp+0x20之间的内存区域

  • lea - Load effective address
    lea是一个载入有效地址的指令,将源操作数表示的地址载入到目的操作数(寄存器)中。

语法如下:
lea <reg>,<mem>
示例如下:
lea rbp, ptr [rsp-0x10]; 将地址rsp-0x10到rsp-0x8之间内存区域存储的8字节地址(指针)加载到rbp寄存器

  • push
    push指令扩栈后将操作数压入栈顶。通过第一章寄存器可知,一般栈顶由rsp寄存器确定。栈一般从高地址向低地址扩张。

语法如下:
push <reg>
push <mem>
push <const>
示例如下:
push rbp; 将rbp寄存器的内容压入栈,x86-64下等价于sub rsp 0x8; mov rsp, rbp,也就是说push指令改变栈大小,减8字节

  • pop
    pop指令与push指令相反,将栈顶内容弹出,并缩栈。

语法如下:
pop <reg>
pop <mem>
示例如下:
pop rbp; 将栈顶内容写入rbp寄存器,x86-64下等价于 mov rbp,rsp; add rsp 0x8 也就是说pop指令改变栈大小,增8字节

  • call
    call指令跳转到标签处,其主要工作是扩栈(字节数为指针大小)后将call下一条指令地址写入栈顶(下一条指令由rip寄存器会自动装载的),并跳转到标签。

语法如下:
call <label>
示例如下:
call $main.sub; 调用sub函数,等价于push rip,jmp <label>; 也就是说call指令也会改变栈大小,x86-64下减8字节

  • ret
    ret指令指令实现子程序的返回机制, 其主要工作是将栈顶内容写入rip寄存器,并缩栈,后续cpu继续执行rip的指令。这样就和call实现的函数的调用与返回。

ret; 等价于pop rip;也就是说ret指令也会改变栈大小,x86-64下增加8字节

  • cmp
    cmp指令比较源操作数与目的操作数大小,比较结果设置到FLAG寄存器中。
  • jxx条件跳转,根据FLAG寄存器特定标志位来决定是否跳转

je或jz 如果相等(等于零)跳转;
jne或jnz 如果不相等(等于零)跳转;
js 如果为负值跳转;
jns 如果不为负值跳转;
jg 如果大于跳转;
jge 如果大于等于跳转;
jl 如果小于跳转;
jle 如果小于等于跳转;

运算指令

add rax,rbx;加法,rbx+rax,并将结果保存到rax中。
sub rax,rbx;减法,rbx-rax并将结果保存到rax中。
inc rax; 自增,rax=rax+1;
dex rax;自减,rax=rax-1
imul rax,rbx;乘法
idiv rax,rbx;除法
and、or、xor、not、neg 都是位运算,依次是与、或、异或、非、求补

四、汇编

提到汇编,不得不说汇编器和汇编语法。

4.1、汇编语法
汇编器默认语法典型应用场景‌
NASMIntelWindows/Linux 跨平台
GNU Assembler(GAS、AS)AT&TLinux 内核及 GCC 工具链
MASMIntelWindows 平台开发
YASMIntel跨平台优化
go tool asm‌plan9go编译过渡阶段

汇编语法也各有特色,简单说下其核心差异:

差异\语法IntelAT&Tplan9
操作数顺序‌目标在前,源在后源在前,目标在后源在前,目标在后
寄存器前缀无前缀前缀 %,如%eax无前缀
立即数前缀无前缀前缀 $,如$0x5前缀 $,如$0x5
指令后缀‌无,但通过byte ptr(1字节)、word ptr(2字节)、
dword ptr(4字节)、qword ptr(8字节)修饰符表示多少字节,
如mov qword ptr [rsp+0x10], 0x2
b(1字节)、w(2字节)、l(4字节)、q(8字节)
如movq $0x2 %rax
同AT&T,如SUBQ $48, SP
寻址方式[]作为修饰符,如[rsp+0x8],定位栈偏移 0x8 处的内存地址()作为修饰符,如8(%ebp),定位栈偏移 0x8 处的内存地址同AT&T
寄存器抽象不支持不支持支持,也叫伪寄存器,如SB、SP、FP、PC,用于简化内存寻址
函数声明无专用指令,通过标签和栈操作实现无专用指令,通过标签和栈操作实现TEXT func(SB), flags, $size
4.2、nsam汇编

汇编语言程序由三种类型的语句组成:

  • 可执行指令:直接告诉处理器要执行的操作,如mov、add等;
  • 汇编器指令:指导汇编器如何处理源代码及其生成的输出,如section、global _start、db、dw、resb等;
  • 宏:在源代码级别进行文本替换。
  • 汇编程序一般分成3个区域:
  • data section: 用于声明初始化的数据或者常量,运行时不会更改;
  • bss section: 用于声明全局变量;
  • text section: 用于保存实际的代码。这个部分必须以声明global _start开始,它告诉内核程序从哪里开始执行。
    这里着重说下section汇编指令。

section 指令用于定义或切换段;
section .my_data progbits alloc write ; 定义可读写的初始化数据段
section .my_code progbits alloc exec ; 定义可执行的代码段
section .my_rodata progbits alloc ; 定义只读数据段(不可写)

section .my_data progbits alloc writemy_var: dd 0x12345678  ; 定义可写数据section .text
global _start
_start:mov eax, [my_var]      ; 正常读取mov dword [my_var], 0  ; 允许写入

我们一般给程序内存分为代码段、数据段、堆、栈等、其中代码段和数据段的内容在汇编中就由section 指令定义:

section .data 存放已初始化全局变量
section .bss 存放未初始化全局变量
section .text 存放可执行代码,特别注意。这个部分必须以声明global _start开始,它告诉内核程序从哪里开始执行。
section .rodata 存放只读数据(如字符串,常量等)
这几种段无需设置读写权限,nsam汇编器会自动追加上,比如还会在.bss后追加nobits alloc write,表示未初始化且可写,因为一般程序会这么分,约定俗成了。

一个输出hello world的汇编程序:

#hello.asm 文件
section .datahello db 'Hello, World!',0xA  ; 定义字符串并以换行符结束len equ $ - hello             ; 计算字符串长度section .textglobal _start                 ; 指定入口点_start:; 写字符串到标准输出mov eax, 4                    ; 系统调用号 (sys_write)mov ebx, 1                    ; 文件描述符 (stdout)mov ecx, hello                ; 要写入的缓冲区地址mov edx, len                  ; 缓冲区长度int 0x80                      ; 触发中断; 退出程序mov eax, 1                    ; 系统调用号 (sys_exit)xor ebx, ebx                  ; 返回状态码 0int 0x80                      ; 触发中断

x86-64下yum安装下nasm汇编器

nasm -f elf64 hello.asm -o hello.o
ld hello.o -o hello
./hello  # Hello, World!

五、参考

1]:维基百科Intel_8086
2]:维基百科x86发展
3]:x86汇编指令
4]:Intel官网SSE
5]:x64 体系结构
6]:nasm官网

相关文章:

  • Kettle学习
  • VSCode远程登录云服务器并设置免密登录全攻略
  • LeetCode --- 446 周赛
  • 2.4.5goweb项目上传到csdn的git仓库
  • Eigen的主要类及其功能
  • 《数据库系统工程师》-B站-视频截图整理-2021-23
  • JavaScript原生实现简单虚拟列表(列表不定高)
  • 【数据结构刷题】顺序表与ArrayList
  • 2025.04.26-美团春招笔试题-第一题
  • 使用 LangGraph 和 Elasticsearch 构建强大的 RAG 工作流
  • JDBC数据库操作中如何保证最后关闭了所占用的资源——try用法的细节控制
  • 【信息融合】卡尔曼滤波EKF毫米波雷达和红外数据信息融合
  • 参考平面的宽度-信号与电源完整性分析
  • uniapp开发03-轮播图组件swiper的简单使用案例
  • Webpack模块打包工具
  • [计算机科学#2]:从继电器到晶体管的电子计算机发展史(庞然大物的进化)
  • 计算机视觉与深度学习 | LSTM原理及与卡尔曼滤波的融合
  • 分享基于时间moment的时间快捷工具
  • Springboot基础篇
  • 物联网安全解决方案介绍:有效利用现有的下一代防火墙
  • 从“网点适老化”到“社区嵌入式”,“金融为老上海模式”如何探索?
  • 文旅部:推动离境退税购物便利化有利于更多国内优质商品走出去
  • 读科学发展的壮丽史诗,也读普通人的传奇
  • 苏迪曼杯即将在厦门打响,国羽向创纪录的14冠进军
  • 泰山景区管委会:未经审核同意不得擅自举办竞速类登山活动
  • 政治局会议深度|提出“设立新型政策性金融工具”有何深意?