- 汇编指令是机器指令的助记符,同机器指令一一对应
- 每一种CPU都有自己的汇编指令集
- CPU可以直接使用的信息在存储器中存放
- 咋存储器中指令和数据没有任何区别,都是二进制信息
- 存储单元从零开始顺序编号
- 一个存储单元可以存储一个字节
内存地址空间
| CPU | 内存地址空间 | |
|---|---|---|
| 主存储器地址空间 | RAM 主储存器 RAM 主存储器(内存条) | |
| 显存地址空间 | RAM(显存)【显卡】 | |
| 显卡BIOS ROM 地址空间 | ROM(装有显卡BIOS)【显卡】 | |
| 网卡BIOS ROM 地址空间 | ROM(装有网卡的BIOS)【网卡】 | |
| 系统BIOS ROM 地址空间 | ROM(装有系统BIOS) |
所有物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间,CPU在这段地址空间中读写数据,实际上就是在相应的的物理存储器中读写数据
注意:内存地址大小收CPU地址地址总线宽度的限制,碧血知道这个系统中的内存地址空间分配情况
寄存器
一个典型的CPU是由运算器,控制器,寄存器等器件构成,这些器件靠内部总线组成。
在CPU中:
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线链接各种期间,在他们之间进行数据的传送。
对于汇编来说CPU主要的部件是寄存器,程序通过改变寄存器上的内容来实现对于CPU的控制
以下是目前常用的寄存器(64)
| 寄存器类别 | 寄存器名字 | 只要内容 |
|---|---|---|
| 通用寄存器 | RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP, R8-R15 | 数据计算、内存地址指针、计数器等。 |
| 指令指针 | RIP(x86 esp eip ebp) | 指向下一条要执行的指令。 |
| 标志寄存器 | RFLAGS | 存储上一条指令执行后的状态(如是否为零、是否进位等)。 |
| 段寄存器 | CS, DS, SS, ES, FS, GS | 定义内存段的属性和权限(由OS管理)。 |
寄存器位宽演进关系表
| 位宽 | 代表架构 | 核心寄存器举例 | 关键特性 |
|---|---|---|---|
| 8位 | Intel 8080 | A, B, C, H, L | 寄存器对,有限寻址 |
| 12位 | PDP-8 | AC (累加器) | 独特的字长,精简指令集 |
| 16位 | Intel 8086 | AX, BX, SP, CS | 分段内存模型 ,x86家族的起点 |
| 32位 | Intel 80386 | EAX, EBX, ESP, EIP | 平坦内存模型 ,保护模式,现代OS基石 |
| 64位 | AMD64 | RAX, R8, RSP, RIP | 16个通用寄存器 ,巨大地址空间,寄存器参数传递 |
核心演进规律:
- 向下兼容 : x86 系列最强大的特性。在 64位 模式下,你仍然可以运行 16位 和 32位的程序(需要操作系统支持)。
- 位宽扩展 : 从 8位 到 64位,处理能力和寻址空间呈指数级增长。
- 架构简化 : 从 16位 复杂的分段模型,逐步过渡到 32/64位 更简洁平坦的内存模型,使编程更容易。
- 性能提升 : 寄存器数量增加、位宽扩大,直接带来了更强大的数据处理能力和更高的效率。
物理地址
所有内存单元构成的存储空间是一个一维的线性地址,每一个内存单元在这个空间中都有一个唯一的地址,我们将这个唯一的地址称为物理地址
按照16位CPU 8086CPU给出物理地址的方法为例
8086有20位地址总线但是内部只能一次性处理16位的地址,表现出的寻址能力只有64kb
所以8086 采用在内部使用两个地址合成的方式来形成一个20位的物理地址。
| 其他部件 | ==>> | -地址加法器- |
|---|---|---|
| 16位/ 段地址 | -地址加法器- | |
| ==>> | -地址加法器- | |
| 16位/偏移地址 | ↓↓↓ | |
| 物理地址 20位 | ↓↓↓ | |
| 输入输出控制电路 |
当8086 CPU需要读写内存的时候
- CPU相关的部件提供两个16位地址,一个称为段地址,另一个称为偏移地址
- 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件
- 地址加法器将两个16位地址合成一个20位的物理地址
- 地址加法器通过内部总线将20位物理地址是送上地址总线
- 20位物理地址被地址总线传送到存储器
地址加法器采用物理地址 = 段地址x16 + 偏移地址的方法用段地址和平移地址合成物理地址
段的概念
内存中并没有分段,段的划分来自于CPU,由于8086CPU用段地址x16 + 偏移地址=物理地址的方式给出内存单元的物理地址,使得我们可以使用分段的方式来管理内存
所以在实际编写程序的时候,使用偏移地址来定位段中的内存单元
注意:一个段的地址一定为16的倍数,16位地址的寻址能力为64kb,所以一个段的长度最大为64kb
数据段
对于 8086 我们可以根据需要,将一组内存单元定义为一个段。我们可以将一组长度为N(<= 64KB),地址连续,起始位置为16倍数的内存单元当作专门存储数据的内存空间,从而定义一个数据段
如何访问内存中的数据段?在具体操作的时候使用ds存放数据段的段地址在根据需要,使用相关指令访问数据段中的具体单元
- 字在内存中存储的时候,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中高位字节存放在高地址单元中
- 使用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS存储器中
- [address] 表示一个偏移地址为 address的内存单元
- 在内存和寄存器之间传送字形数据时吗高地址和高8位的寄存器,低地址单元和低8位寄存器相对应
- mov,add,sub 是具有两个操作对象的指令。jmp是具有一个操作对象的指令
CPU提供的栈机制
现今的CPU都有栈的设计,栈是一个先进后出的线形表
栈顶的超界问题
如果在持续使用push指令 由于 esp SS
所以在编写汇编程序的时候需要自己管理的栈空间
常用指令
| 指令类型 | 操作码 | 例子(intel) | 实际效果 |
|---|---|---|---|
| 数据传输指令 | mov | mov rax rbx | rax = rbx |
| 取地址指令 | lea | lea rax [rbx] | rax = &*rbx |
| 算数运算指令 | add | add rax rbx | rax = rax + rbx |
| sub | sub rax rbx | rax = rax - rbx | |
| 逻辑运算指令 | and | and rax rbx | rax = rax & rbx |
| xor | xor rax rbx | rax = rax ^ rbx | |
| 函数调用指令 | call | call 1234h | 执行内存地址1234h处的函数 |
| 函数返回指令 | ret | ret | 函数返回 |
| 比较 | cmp | emp rax rbx | 比较rax 与rbx,结果返回EFLAG寄存器 |
| 无条件跳转 | jmp | jmp 1234h | eip = 1234h |
| 栈操作指令 | push | push rax | 将rax 储存的值压栈 |
| pop | pop rax | 将栈顶的值赋值给rax,rsp += 8 |
AT&T 语法特点
- 操作数顺序 :
源操作数在前,目的操作数在后 - 寄存器前缀 :
%(如%eax,%rbp) - 立即数前缀 :
$(如$5,$0x10) - 内存引用 :
偏移量(%基址,%索引,比例) - 指令后缀 :表示操作数大小(
b=字节,w=字,l=长字,q=四字)
| 指令类型 | 操作码 | 例子(AT&T) | 实际效果 | 解释说明 |
|---|---|---|---|---|
| 数据传输指令 | movb | movb %al,%bl | bl = al | 移动字节(8) |
| movw | movw %ax, %bx | bx = ax | 移动字(16) | |
| movl | movl %eax, %ebx | ebx = eax | 移动长字(32) | |
| movq | movq %rax, %rbx | rbx = rax | 移动四字(64) | |
| pushq | pushq %rax | 压栈 | ||
| popq | popq %rax | 出栈 | ||
| 算数运算指令 | addl | add $5, %eax | eax = eax + 5 | |
| subl | subl %ebx, %eax | eax = eax - ebx | ||
| incl | incl %eax | eax++ | ||
| decl | decl %eax | eax— | ||
| andl | andl $0xff, %eax | 0xff & eax | 与 | |
| orl | orl %0x10, %eax | 0x10| eax | 或 | |
| xorl | xorl %ebx, %eax | ebx ^ eax | 按位异或 | |
| 比较 | cmpl | cmpl %ebx, %eax | 比较ebx eax | |
| 控制转移指令 | jmp | jmp .L1 | 无条件跳转 |
Linux 系统中栈的地址由高地址向低地址跳转
call & jmp 的区别
- call 只是函数的调用,会保存返回地址和参数
- jmp只是简单跳转,类似if-else语句
将C语言编译汇编
有如下文件
#include <stdio.h>int main(){
return 0;}./clang hello_world.c -S -masm=intel -o hello_world.s获得如下汇编代码
.def @feat.00; .scl 3; .type 0; .endef .globl @feat.00.set @feat.00, 1 .intel_syntax noprefix .file "hello_world.c" .def _main; # 定义符号 _main .scl 2; # 存储类:函数 (2=函数) .type 32; # 类型:函数 (32=函数类型) .endef # 定义结束 .text # 切换到代码段 .globl _main # -- Begin function main 声明 _main 为全局符号 .p2align 4 # 16字节对齐 (2^4=16)_main: # @main# %bb.0: push ebp mov ebp, esp sub esp, 8 call ___main mov dword ptr [ebp - 4], 0 lea eax, [L_.str] mov dword ptr [esp], eax call ___mingw_printf xor eax, eax add esp, 8 pop ebp ret # -- End function .section .rdata,"dr"L_.str: # @.str .asciz "Hello World!\n"
.addrsig .addrsig_sym ___mingw_printf使用汇编写一个简单的程序
环境: WSL-Ubuntu, gcc
.intel_syntax noprefix.global _start_start: mov rdi, 42 ; 退出状态码 mov rax, 60 ; 系统调用约定 退出 syscallas ./program.s -o ./program.old ./program.o -o ./program
> ./program> echo $?42Hello World
.intel_syntax noprefix.datamsg: .ascii "Hello_World!\n" len = . - msg
.text .global _start
_start: # 写入 mov rax, 1 # 系统调用 - write mov rdi, 1 lea rsi, [msg] mov rdx, len syscall
# exit mov rax, 60 mov rdi, 0 syscall如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时










