x86汇编入门
x86汇编入门
前言
我们知道,CPU需要指令来执行对应的操作。比如在x86架构下,add指令就是加法操作,inc指令就是加一操作。不同的cpu架构(比如arm架构、mips架构、8051架构)拥有不同的指令集,x86汇编是比较常见的,所以我们用它来进行一个汇编的入门。
寄存器
寄存器是cpu里的东西,用来存储临时数据的。由于内存读取相较于cpu运算速度来说太慢(大约在百倍左右),所以cpu一般自带一级缓存、二级缓存,但是自带缓存还是不够快,于是就有了寄存器。寄存器只依靠名字区分,读取速度最快,被喻为零级缓存。
在x86架构下,cpu寄存器有以下几种
通用寄存器
数据寄存器
| 寄存器 | 名称 | 作用 |
|---|---|---|
| EAX | 累加寄存器 | 在乘法和除法指令中被自动使用,一般也用于函数返回值 |
| EBX | 基地址寄存器 | 存放内存寻址时的基地址 |
| ECX | 计数寄存器 | 在循环计数中被自动使用,一般在字符串和循环操作中常用 |
| EDX | 数据寄存器 | 在整数除法中被自动用来存放余数 |
指针变址寄存器
| 寄存器 | 名称 | 作用 |
|---|---|---|
| ESP | 栈指针寄存器 | 用于指向当前栈帧的顶部 |
| EBP | 基指针寄存器 | 指向当前栈帧的底部,通常用于在函数中访问局部变量和参数 |
| ESI | 源索引寄存器 | 常用于字符串或其它数据序列操作中的源地址 |
| EDI | 目的索引寄存器 | 常用于字符串或其它数据序列操作的目标地址 |
以上寄存器是32位的,cpu在使用数据寄存器时有时候不必使用寄存器的全部位数,所以寄存器可以只使用部分位数,所以有以下相似名称的寄存器
16位寄存器,使用32位寄存器的低16位,有AX、BX、CX、DX、SI、DI、BP、SP
8位寄存器,使用16位寄存器的高8位或低8位,高8位有AH、BH、CH、DH,低8位有AL、BL、CL、DL
除此之外,x64结构cpu的通用寄存器为RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP,32位寄存器使用64位寄存器的低32位
标志寄存器EFLAGS
标志寄存器用来存储指令的执行结果,比如结果是否为零、是否为负数,可以为下一条指令提供判断依据,比如说条件跳转指令、循环指令就可以通过EFLAGS对应标志位是否置为1来进行对应的操作。
在x86架构cpu中,标志寄存器一共有32位,其中一些位数被保留不作使用,下面介绍一些常用的标志,它们大致分为两类:状态标志和控制标志。
状态标志,用来记录程序运行结果的状态信息
| 标志位 | 名称 | 作用 |
|---|---|---|
| CF | 进位标志 | 当运算结果最高有效位进位(加法)或借位(减法)时,置为1 |
| ZF | 零标志 | 运算结果为0,置为1 |
| SF | 符号标志 | 运算结果最高位为1则置为1,无符号数最高位为1也会置为1 |
| PF | 奇偶标志 | 运算结果二进制中‘1’的个数为偶数,置为1 |
| OF | 溢出标志 | 运算结果有溢出,置为1 |
| AF | 辅助进位标志 | 主要用于CPU内部使用,一般不用关心 |
控制标志,用于处理器执行指令的方式
| 标志位 | 名称 | 作用 |
|---|---|---|
| DF | 方向标志 | 用于控制串操作指令中的地址变化方向,置为1地址正向增加,否则反向减少 |
| IF | 中断允许标志 | 控制CPU是否可响应外部可屏蔽中断(比如I/O设备完成操作),置为1为允许 |
| TF | 陷阱标志 | 通常用于程序调试,置为1后,CPU会进入单步执行方式 |
指令指针寄存器EIP
一般只用来存放要执行的下一条指令的地址
段寄存器
段寄存器用来存放内存中程序对应段的基地址。程序在运行时,会将自身的二进制代码还有变量数据加载到内存中,同时开辟堆空间与栈空间。在x86架构中,段寄存器共有6个,它们分别指向对应内存区域的起始位置,它们为CS(代码段)、DS(数据段)、SS(堆栈段)、ES(附加段)、FS(附加段)、GS(附加段),指向的内存区域如下:
| 段寄存器 | 指向区域 | 用途 |
|---|---|---|
| CS | 代码段 | 代码段用来存放程序运行的代码 |
| DS | 数据段 | 数据段用来存放程序自身的变量、常量 |
| SS | 堆栈段 | 堆栈段用来确定栈的内存地址,而不是堆的 |
| ES、FS、GS | 附加段 | 附加段可以被用来访问额外的数据,例如字符串操作或其它需要额外数据段的操作 |
常用指令
寻址方式
在清楚的了解各种指令之前,我们先来了解一下CPU的寻址方式。
cpu可以直接从寄存器读取、从cpu内部缓存读取、通过内存总线从内存读取,所以我们可以看到它有以下寻址方式:
| 寻址方式 | 介绍 |
|---|---|
| 直接寻址 | 在指令中直接给出操作数的内存地址,比如访问数据段中的常量 |
| 间接寻址 | 指令中给出一个地址,该地址指向操作数所在的内存地址,可以认为是C语言中通过指针取变量的值 |
| 立即寻址 | 操作数直接在指令中 |
| 寄存器寻址 | 操作数在cpu寄存器中 |
| 寄存器间接寻址 | 寄存器中为指向操作数内存的地址,类似于间接寻址 |
| 寄存器相对寻址 | 基址寄存器的地址加上指令中的偏移量就是操作数地址 |
| 基址变址寻址 | 基址寄存器加变址寄存器中的偏移量就是操作数地址 |
| 堆栈寻址 | 直接通过堆栈指针寄存器来访问 |
堆栈操作指令及堆栈段
我们再来复习一下堆栈段,想要理解汇编,对于汇编在堆栈段的操作上是必不可少的,因为程序主要就在这块内存区域入栈、出栈。
堆栈段是一块先进后出的内存区域,可以理解为数据结构中的栈。既然是栈,就有入栈和出栈。在高级语言进行函数调用时,会将返回地址、函数参数入栈,然后在栈中开辟一段空间,形成栈帧,更新基指针寄存器与栈指针寄存器的值,分别让它们重新指向当前栈帧的底部与顶部。
入栈、出栈指令如下:
| 指令 | 作用 | 用法 |
|---|---|---|
| push | 将操作数入栈 | push 操作数 |
| pop | 将操作数出栈到对应地址 | pop 内存地址/寄存器 |
数据传递指令
内存与内存之间不能直接进行数据传递
| 指令 | 作用 | 用法 |
|---|---|---|
| mov | 数据传输,相当于赋值 | mov 目的操作数,源操作数 |
| xchg | 交换两个操作数的数据 | xchg 源操作数,目的操作数 |
| lea | 载入有效地址到寄存器 | lea 寄存器,地址 |
逻辑运算指令
| 指令 | 作用 | 用法 |
|---|---|---|
| and | 将两个数进行按位与操作的结果放在目的操作数 | and 目的操作数,源操作数 |
| or | 将两个数进行按位或操作的结果放在目的操作数 | or 目的操作数,源操作数 |
| not | 将操作数按位取反 | not 操作数 |
| xor | 将两个数按位异或后的结果放在目的操作数 | xor 目的操作数,源操作数 |
算术运算指令
| 指令 | 作用 | 用法 |
|---|---|---|
| add | 两操作数相加,结果存在目的操作数 | add 目的操作数,源操作数 |
| sub | 两操作数相减,结果存在目的操作数 | sub 目的操作数,源操作数 |
| inc | 加一指令 | inc 操作数 |
| dec | 减一指令 | dec 操作数 |
| mul | 将操作数与eax寄存器相乘 ,结果存在eax寄存器中 | mul 操作数 |
| div | 将eax寄存器除于操作数,商存在eax,余数存在edx | div 操作数 |
转移相关指令
| 指令 | 作用 | 用法 |
|---|---|---|
| jmp | 无条件转移到指定地方 | jmp 地址 |
| loop | 循环控制,若ecx寄存器不为0,跳转,否则执行下一条 | loop 地址 |
| call | 调用函数,会将下一条指令地址保存至堆栈 | call 函数地址 |
| ret | 函数返回,与call配合使用 | ret |
条件转移指令
条件转移指令有多条,称为jcc指令集。它们用法与jmp差不多,但是只根据EFLAG标志寄存器中的值来决定是否跳转,以下列出了几个常见条件转移指令
| 指令 | 作用 | 检查符号位 |
|---|---|---|
| jz | 若为0则跳转 | ZF=1 |
| jnz | 若不为0则跳转 | ZF=0 |
| js | 若为负/符号位为1则跳转 | SF=1 |
| jns | 若不为负则跳转 | SF=0 |
| je | 若相等则跳转 | ZF=1 |
| jne | 若不等则跳转 | ZF=0 |
通常配合使用的指令
| 指令 | 作用 | 用法 |
|---|---|---|
| test | 将两操作数进行逻辑与运算 | test 操作数1,操作数2 |
| cmp | 两操作数比较,目的操作数减去源操作数 | cmp 目的操作数,源操作数 |
结语
以上只是大致介绍了一下x86汇编,想要深入下去还是要多看看实际程序的汇编代码是怎样的,高级语言对应语句的汇编又是怎样实现的。看得多了,自然就熟悉了。

浙公网安备 33010602011771号