CPU汇编入门
CPU的核心部件
程序计数器(Program Counter,PC)
- 程序计数器属于一个特殊的寄存器,始终指向主存中的某条命令语句。
- CPU通过读取PC指向的那条命令来执行。
- 顺序执行语句时,PC每次加上一个单位(一个命令所占的内存大小),自动指向下一条要执行的语句。
指令寄存器(Instruction Register,IR)
- 用来存放从主存中读取的某条命令。
算术逻辑单元(ALU)
- CPU用于计算的部件。
汇编指令
- 定义:汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。汇编语言又被称为第二代计算机语言。
- 不同的CPU有内置不同的汇编指令集。(如:arm,x86…)
- 最简单的汇编程序的组成:CPU从主存读取程序到寄存器(命令,变量),CPU进行计算,CPU将结果放回主存。
SEAL指令
load R1,(address)
:读取到寄存器,从地址读取值存入寄存器R1mov R1,C
:赋值,把值C赋值给寄存器R1add R1,R2,C
:加法运算,把R2+c
的结果赋值给寄存器R1sub R1,R2,C
:减法运算,把R2-c
的结果赋值给寄存器R1mul R1,R2,C
:乘法运算,把R2*c
的结果赋值给寄存器R1div R1,R2,C
:除法运算,把R2//c
的结果赋值给寄存器R1shiftl R1,R2,C
:左移运算,把R2<<c
的结果赋值给寄存器R1shiftr R1,R2,C
:右移运算,把R2>>c
的结果赋值给寄存器R1and R1,R2,R3
:与运算,把R2&R3
的结果赋值给寄存器R1or R1,R2,R3
:或运算,把R2|R3
的结果赋值给寄存器R1xor R1,R2,R3
:异或运算,把R2^R3
的结果赋值给寄存器R1store (address),R2
:保存到主存,把寄存器R2的值保存到主存的某个地址处slt R1,R2,R3
:(set if less than) 判断值大小R2是否小于R3,结果存到寄存器R1- 若
R2<R3
,R1=1
;否则R1=0
- 若
sle R1,R2,R3
:(set if less than or euqal to) 判断值大小R2是否小等于R3,结果存到寄存器R1- 若
R2<=R3
,R1=1
;否则R1=0
- 若
beqz R1,Label
:(branch equal zero) 判断0跳转,如果R1值为0,则跳转到标签Label处。bneqz R1,Label
:(branch not equal zero) 判断0跳转,如果R1值不为0,则跳转到标签Label处。goto Label
:直接跳转至标签Label处。_pr R1
:打印出R1的值。_data first_address,[a0,a1,a2,…,an]
:一次存储多个数,按顺序存储。第一个操作数为要存储一组数据的首地址,第二个操作数为所要存储的一组数,依次将该组数存储在以首地址开始递增的内存中,一般首地址都是比较小的地址。
简单的SEAL程序
a=a+1
- 简单三步走:读取,计算,保存
1 | # 假设a储存在主存地址300处 |
if选择语句的实现
- python
1 | if R1<R2: |
- SEAL
1 | slt R3,R1,R2 |
循环结构的实现
while循环
- python
1 | while R1<R2: |
- SEAL
1 | Lwhile: |
for循环
- c++
1 | for(int i=0;i<10;i++) { |
- SEAL
1 | mov R1,0 # 循环计数变量 |
位运算的小技巧
判断一个数是否为偶数
- 将一个数
a
与1进行与运算,若a为奇数则返回1,否则返回0。
判断两个数是否相等
- 将两个数
a
,b
进行异或运算,若相等则返回0,否则返回非0数。
左/右移实现乘/除法
- 将一个数a左移n位得到的结果为:
- 将一个数a右移n位得到的结果为:
实现位取反
- 对一位二进制数
a
取反可以由a^1
得到 - 对N位二进制数
a
取反可以由a^111...111(N个1)
得到
if语句的扩展
if的大于条件实现
- python
1 | if R1>R2: |
- SEAL
1 | # 由sle条件取反得到 |
if的等于条件实现
- python
1 | if R1==R2: |
- SEAL
1 | xor R3,R1,R2 |
简单的程序实例1
- if的多分支实现:python
1 | a = 10 |
- SEAL
1 | mov R0,10 #a |
简单的程序实例2
- 数组求和:python
1 | l = [2,1,4,1,5,6,1,7,2,1,3,4] |
- SEAL
1 | _data 1,[2,1,4,1,5,6,1,7,2,1,3,4] |
函数的调用逻辑
Stack–栈
- 栈是一种先进后出(FILO)的数据结构,有两个基本操作,入栈(在栈顶添加元素),弹栈/出栈(弹出栈顶元素)。
- 栈是函数调用的储存形式。
栈帧(Frame)
- 当调用一个函数时,会在栈顶开辟一空间用于存储函数的信息(局部变量,返回地址),这一块区域叫做栈帧。函数调用结束时即从栈顶弹出。
建栈步骤
- 当调用一个函数时要建立相应的栈帧,按以下步骤进行建立:
- 若有参数,参数先入栈(按参数接受的顺序的反序入栈)
- 将PC中的地址入栈(作为调用函数的返回地址)
- 将FP中的地址入栈(将旧的FP地址入栈,便于函数调用结束时恢复)
- 将函数的局部变量入栈
栈指针
SP(Stack Pointer)
- 栈顶指针SP,始终指向一个栈帧的顶部,CPU将栈帧的顶部地址保存在寄存器SP中
FP/BP (Frame Pointer/Base Pointer)
- FP指针始终指向一个栈帧的底部,CPU将栈帧的底部地址保存在寄存器FP中
相关的SEAL指令
call Lable
:调用函数,巨指令,执行两件事,将PC值push入栈,然后执行goto Lable
。push Value
:将值Value放入栈顶。- 其内部的操作步骤为:
sub sp,sp,1
(将sp往栈顶方向推移一个单位,开辟一个单位的空间用于存储数据),store 0(sp),R1
(将寄存器R1的值放入sp指向的内存)。
- 其内部的操作步骤为:
pop R1
:将栈顶的数值取出赋给寄存器。- 其内部的操作步骤为:
load R1,0(sp)
(将sp指向地址的值读取到寄存器R1),add sp,sp,1
(将sp往栈底方向推移一个单位)。
- 其内部的操作步骤为:
ret
:函数返回,即pop pc
,接受返回地址
函数调用的建栈模板
- 程序开头处,初始化栈
1 | mov R15,100 # 将栈底指针fp的初始地址设置为100 |
- 函数调用前,传递参数
1 | # 保存参数原始值 |
- 函数调用开始时,建立栈帧
1 |
|
- 函数调用结束时恢复栈帧
1 | # 弹出临时变量,恢复变量的初始状态(按push的反序弹出) |
函数调用的程序实例–递归调用
- python
1 | # 找到x的因数 |
- SEAL
1 | # 初始化栈 |