mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
2441 字
7 分钟
asm01
2025-11-13
  1. 汇编指令是机器指令的助记符,同机器指令一一对应
  2. 每一种CPU都有自己的汇编指令集
  3. CPU可以直接使用的信息在存储器中存放
  4. 咋存储器中指令和数据没有任何区别,都是二进制信息
  5. 存储单元从零开始顺序编号
  6. 一个存储单元可以存储一个字节

内存地址空间#

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 8080A, B, C, H, L寄存器对,有限寻址
12位PDP-8AC (累加器)独特的字长,精简指令集
16位Intel 8086AX, BX, SP, CS分段内存模型 ,x86家族的起点
32位Intel 80386EAX, EBX, ESP, EIP平坦内存模型 ,保护模式,现代OS基石
64位AMD64RAX, R8, RSP, RIP16个通用寄存器 ,巨大地址空间,寄存器参数传递

核心演进规律:

  1. 向下兼容 : x86 系列最强大的特性。在 64位 模式下,你仍然可以运行 16位 和 32位的程序(需要操作系统支持)。
  2. 位宽扩展 : 从 8位 到 64位,处理能力和寻址空间呈指数级增长。
  3. 架构简化 : 从 16位 复杂的分段模型,逐步过渡到 32/64位 更简洁平坦的内存模型,使编程更容易。
  4. 性能提升 : 寄存器数量增加、位宽扩大,直接带来了更强大的数据处理能力和更高的效率。

物理地址#

所有内存单元构成的存储空间是一个一维的线性地址,每一个内存单元在这个空间中都有一个唯一的地址,我们将这个唯一的地址称为物理地址

按照16位CPU 8086CPU给出物理地址的方法为例

8086有20位地址总线但是内部只能一次性处理16位的地址,表现出的寻址能力只有64kb

所以8086 采用在内部使用两个地址合成的方式来形成一个20位的物理地址。

其他部件==>>-地址加法器-
16位/ 段地址-地址加法器-
==>>-地址加法器-
16位/偏移地址↓↓↓
物理地址 20位↓↓↓
输入输出控制电路

当8086 CPU需要读写内存的时候

  1. CPU相关的部件提供两个16位地址,一个称为段地址,另一个称为偏移地址
  2. 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件
  3. 地址加法器将两个16位地址合成一个20位的物理地址
  4. 地址加法器通过内部总线将20位物理地址是送上地址总线
  5. 20位物理地址被地址总线传送到存储器

地址加法器采用物理地址 = 段地址x16 + 偏移地址的方法用段地址和平移地址合成物理地址

段的概念#

内存中并没有分段,段的划分来自于CPU,由于8086CPU用段地址x16 + 偏移地址=物理地址的方式给出内存单元的物理地址,使得我们可以使用分段的方式来管理内存

所以在实际编写程序的时候,使用偏移地址来定位段中的内存单元

注意:一个段的地址一定为16的倍数,16位地址的寻址能力为64kb,所以一个段的长度最大为64kb

数据段#

对于 8086 我们可以根据需要,将一组内存单元定义为一个段。我们可以将一组长度为N(<= 64KB),地址连续,起始位置为16倍数的内存单元当作专门存储数据的内存空间,从而定义一个数据段

如何访问内存中的数据段?在具体操作的时候使用ds存放数据段的段地址在根据需要,使用相关指令访问数据段中的具体单元

  1. 字在内存中存储的时候,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中高位字节存放在高地址单元中
  2. 使用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS存储器中
  3. [address] 表示一个偏移地址为 address的内存单元
  4. 在内存和寄存器之间传送字形数据时吗高地址和高8位的寄存器,低地址单元和低8位寄存器相对应
  5. mov,add,sub 是具有两个操作对象的指令。jmp是具有一个操作对象的指令

CPU提供的栈机制#

现今的CPU都有栈的设计,栈是一个先进后出的线形表

栈顶的超界问题#

如果在持续使用push指令 由于 esp SS 等 只是指向栈顶的地址,不能保证是否超出栈顶的空间,所以会出现超界这一问题将栈空间外的数据覆盖,同理是持续使用pop出栈指令也会导致栈的下溢

所以在编写汇编程序的时候需要自己管理的栈空间

常用指令#

指令类型操作码例子(intel)实际效果
数据传输指令movmov rax rbxrax = rbx
取地址指令lealea rax [rbx]rax = &*rbx
算数运算指令addadd rax rbxrax = rax + rbx
subsub rax rbxrax = rax - rbx
逻辑运算指令andand rax rbxrax = rax & rbx
xorxor rax rbxrax = rax ^ rbx
函数调用指令callcall 1234h执行内存地址1234h处的函数
函数返回指令retret函数返回
比较cmpemp rax rbx比较rax 与rbx,结果返回EFLAG寄存器
无条件跳转jmpjmp 1234heip = 1234h
栈操作指令pushpush rax将rax 储存的值压栈
poppop rax将栈顶的值赋值给rax,rsp += 8

AT&T 语法特点#

  1. 操作数顺序源操作数在前,目的操作数在后
  2. 寄存器前缀%(如 %eax, %rbp
  3. 立即数前缀$(如 $5, $0x10
  4. 内存引用偏移量(%基址,%索引,比例)
  5. 指令后缀 :表示操作数大小(b=字节, w=字, l=长字, q=四字)
指令类型操作码例子(AT&T)实际效果解释说明
数据传输指令movbmovb %al,%blbl = al移动字节(8)
movwmovw %ax, %bxbx = ax移动字(16)
movlmovl %eax, %ebxebx = eax移动长字(32)
movqmovq %rax, %rbxrbx = rax移动四字(64)
pushqpushq %rax压栈
popqpopq %rax出栈
算数运算指令addladd $5, %eaxeax = eax + 5
sublsubl %ebx, %eaxeax = eax - ebx
inclincl %eaxeax++
decldecl %eaxeax—
andlandl $0xff, %eax0xff & eax
orlorl %0x10, %eax0x10| eax
xorlxorl %ebx, %eaxebx ^ eax按位异或
比较cmplcmpl %ebx, %eax比较ebx eax
控制转移指令jmpjmp .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 ; 系统调用约定 退出
syscall
as ./program.s -o ./program.o
ld ./program.o -o ./program
> ./program
> echo $?
42

Hello World#

.intel_syntax noprefix
.data
msg:
.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
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

asm01
https://yoyolp.github.io/posts/c_asm/asm01/
作者
超级玉米人
发布于
2025-11-13
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录