MIPS汇编笔记
MIPS汇编语言基础
MIPS的系统结构及设计理念比较先进,其指令系统经过通用处理器指令体系MIPS I、MIPS II、MIPS III、MIPS IV、MIPS V,以及嵌入式指令体系MIPS16、MIPS32到MIPS64的发展。
MIPS32的架构是一种基于固定长度的定期编码指令集,并采用导入/存储(load/store)数据模型。经改进,这种架构可支持高级语言的优化执行。在路由器中,经常使用的一种MIPS架构就是MIPS32。
MIPS寄存器
RISC的一个显著特点就是大量使用寄存器。因为寄存器的存取可以在一个时钟周期内完成,同时简化了寻找方式,所以,MIPS32的指令中除了加载/存储指令以外,都使用寄存器或者立即数作为操作数,以便让编译器通过保持对寄存器内数据的频繁存取进一步优化代码的生成性能。
MIPS32寄存器分为两类:通用寄存器(GPR)和特殊寄存器。
通用寄存器(GPR)
在MIPS体系结构中有32个通用寄存器,寄存器标志由 $ 开头,在汇编程序中可以用编号$0~$31表示,也可以用寄存器的名字表示,如$sp、$t1、$at等,如图,堆栈是从内存的高地址方向向低地址方向增长的。
编号 | 寄存器名称 | 寄存器描述 |
---|---|---|
0 | $zero | 第0号寄存器,其值始终为0 |
1 | $at | 保留寄存器 |
2~3 | $v0~$v1 | values, 保存表达式或函数返回结果 |
4-7 | $a0~$a3 | aruments, 作为函数的前4个参数 |
8~15 | $t0~$t7 | temporaries,供汇编程序使用的临时寄存器 |
16~23 | $s0~$s7 | saved values,子函数使用时需要先保存原寄存器的值 |
24~25 | $t8~$t9 | temporaries, 供汇编程序的临时寄存器,补充$t0~$t7 |
26~27 | $k0~$k1 | 保留,中断处理函数使用 |
28 | $gp | global pointer,全局指针 |
29 | $sp | stack pointer, 堆栈指针,指向堆栈的栈顶 |
30 | $fp | frame pointer, 保存栈指针 |
31 | $ra | return address, 返回地址 |
- $0:即 $zero,该寄存器总是返回0,为0这个有用常数提供了一个简洁的编码形式。在MIPS处理器的通用寄存器中,没有任何帮助运算判断的标志寄存器,要实现相应的功能时,都是通过测试两个寄存器是否相等完成的。MIPS编译器常常会使用slt、beq、bne等指令和由寄存器 $0 获得0值产生比较所有的比较条件,如相等、不等、小于等于、大于、大于等于。还可以用add指令创建move伪指令,如”move $t0, $t1; $t0=$t1”实际为“add $t0,$0,$t1; $t0= $t1 + 0”。使用MIPS伪指令可以简化任务。
- $1 ($at) : 该寄存器为汇编保留,用做汇编器的暂时变量。
- $2~$3($v0~$v1): 用于存放子程序的返回值或非浮点结果。当这两个寄存器不够存放返回值时,编译器通过内存来完成。
- $4~$7($a0~$a3):用于将前4个参数传递给子程序,不够的用堆栈处理。$a0~$a3、$v0~$v1和$ra 一起完成子程序函数调用过程,分别用以传递参数、返回结果和存放返回地址。当需要使用更多的寄存器时就需要堆栈了。MIPS编译器总是为参数在堆栈中留有空间,以防有参数需要存储。
- $8~$15($t0~$t7): 一个子函数可以不用保存并随意使用这些寄存器。在进行表达式计算时,这些寄存器是非常好的临时变量。在使用时需要注意,当调用一个子函数时,这些寄存器的值有可能被子函数破坏。
- $16~$23($s0~$s7): 子函数必须保证当函数返回时这些寄存器的内容将恢复到函数调用以前的值,或者子函数里不使用这些寄存器或把它们保存在堆栈上并保存在函数退出时恢复。这种约定使这些寄存器非常适合作为寄存器变量,或者用于存放一些函数调用期间必须保存的原值。
- $24~$25($t8~$t9): 同$t0~$t7,作为$t0~$t7寄存器补充。
- $26~$27($k0~$k1): 通常被中断或异常处理程序使用,以保存一些系统参数。
- $28($gp): C语言中有两种存储类型,分别是自动型和静态型。自动变量是一个函数中的局部变量。静态变量在进入和退出一个函数时都是存在的。为了简化静态数据的访问,MIPS保留了一个寄存器作为全局指针gp在编译时,数据需要在以gp为基指针的64KB范围内。
- $29($sp): MIPS硬件并不直接支持堆栈,X86有单独的PUSH和POP指令,而MIPS没有单独的栈操作指令,所有对栈的操作都是统一的内存访问方式,单这并非不影响MIPS使用堆栈。在发生函数调用时,调用者把函数调用之后要用的寄存器压入堆栈,被调用者把返回地址寄存器$ra(并非任何时候都保存$ra)和保留寄存器压入堆栈。同时,调整堆栈指针,并在返回时从堆栈中恢复寄存器。
- $30($fp): 不同编译器可能对该寄存器使用方法不同。GNU MIPS C编译器使用了栈指针(Frame Pointer)。SGI的C编译器则没有使用栈指针,只是把这个寄存器当成保存寄存器使用($s8),这虽然节省了调用和返回开销,但增加了代码生成的复杂度性。
- $31 ($ra): 存放返回地址。MIPS没有call和retn指令,MIPS有一个jar(jump-and-link,跳转并链接)指令,在跳转到某个地址时可把下一条指令的地址放到$ra中,用于支持子程序。例如,调用程序把参数放到$a0~$a3中,“jar X”指令跳到X过程,被调用时需要保存的寄存器为$a0~$a3、$s0~$s7、$gp、$sp、$fp、$ra。
特殊寄存器
MIPS32架构中定义了3个特殊寄存器。分别为PC(程序计数器)、HI(乘除结果高位寄存器)和LO(乘除结果低位寄存器)。在进行乘法运算时,HI和LO保存乘法的运算结果,其中HI存储高32位,LO存储低32位;而在进行除法运算时,HI保存余数,LO存储商。
程序结构
- 本质其实就只是数据声明+普通文本+程序编码(文件后缀为.s,或者.asm也行)
- 数据声明在代码段之后
数据声明
- 数据段以 .data为开始标志
- 声明变量后,即在主存中分配空间。
代码(指令集)
- 代码段以 .text为开始标志
- 程序入口为main:标志
寻址
直接寻址:la $v0,var
var的值就是地址
寄存器存储地址:la $v0,($v1)
$v1存储的值是一个地址,将地址取出再寻址(类似指针)
偏移:la $v0,2($v1)
$v1中存储的地址取出再+2再寻址
MIPS指令格式
MIPS所有指令都是32位,指令格式简单且固定。
R格式
6 | 5 | 5 | 5 | 5 | 6 |
---|---|---|---|---|---|
op | rs | rt | rd | shamt | funct |
用处:
寄存器-寄存器ALU操作
读写专用寄存器
I格式
6 | 5 | 5 | 16 |
---|---|---|---|
op | rs | rt | 立即数操作 |
用处:
加载/存储字节,半字,字,双字
条件分支,跳转,跳转并链接寄存器
J格式
6 | 26 |
---|---|
op | 跳转地址 |
用处:
跳转,跳转并链接
陷阱和从异常中返回
各字段含义:
op:指令基本操作,称为操作码。
rs:第一个源操作数寄存器。
rt:第二个源操作数寄存器。
rd:存放操作结果的目的操作数。
shamt:位移量;
funct:函数,这个字段选择op操作的某个特定变体。在指令代码中目的操作数在操作数最后,但是在书写代码时还是习惯和x86一样放在最前面
例:
1 | add $t0,$s0,$s1 1 |
表示$t0=$s0+$s1
,即16号寄存器(s0)的内容和17号寄存器(s1)的内容相加,结果放到8号寄存器(t0)。
指令各字段的十进制表示为:
0 | 16 | 17 | 8 | 0 | 32 |
---|---|---|---|---|---|
op | rs($s0) | rt($s1) | rd($t0) | shamt | funct |
op=0和funct=32表示这是加法,
16=s0表示第一个源操作数(rs)在16号寄存器里,17=s0表示第一个源操作数(rs)在16号寄存器里,17=s1表示第二个源操作数(rt)在17号寄存器里,
8=$t0表示目的操作数(rd)在8号寄存器里。
MIPS 指令
指令 | 功能 | 应用实例 |
---|---|---|
LB | 从存储器中读取一个字节的数据到寄存器中 | LB R1, 0(R2) |
LH | 从存储器中读取半个字的数据到寄存器中 | LH R1, 0(R2) |
LW | 从存储器中读取一个字的数据到寄存器中 | LW R1, 0(R2) |
LD | 从存储器中读取双字的数据到寄存器中 | LD R1, 0(R2) |
L.S | 从存储器中读取单精度浮点数到寄存器中 | L.S R1, 0(R2) |
L.D | 从存储器中读取双精度浮点数到寄存器中 | L.D R1, 0(R2) |
LBU | 功能与LB指令相同,但读出的是不带符号的数据 | LBU R1, 0(R2) |
LHU | 功能与LH指令相同,但读出的是不带符号的数据 | LHU R1, 0(R2) |
LWU | 功能与LW指令相同,但读出的是不带符号的数据 | LWU R1, 0(R2) |
SB | 把一个字节的数据从寄存器存储到存储器中 | SB R1, 0(R2) |
SH | 把半个字节的数据从寄存器存储到存储器中 | SH R1,0(R2) |
SW | 把一个字的数据从寄存器存储到存储器中 | SW R1, 0(R2) |
SD | 把两个字节的数据从寄存器存储到存储器中 | SD R1, 0(R2) |
S.S | 把单精度浮点数从寄存器存储到存储器中 | S.S R1, 0(R2) |
S.D | 把双精度数据从存储器存储到存储器中 | S.D R1, 0(R2) |
DADD | 把两个定点寄存器的内容相加,也就是定点加 | DADD R1,R2,R3 |
DADDI | 把一个寄存器的内容加上一个立即数 | DADDI R1,R2,#3 |
DADDU | 不带符号的加 | DADDU R1,R2,R3 |
DADDIU | 把一个寄存器的内容加上一个无符号的立即数 | DADDIU R1,R2,#3 |
ADD.S | 把一个单精度浮点数加上一个双精度浮点数,结果是单精度浮点数 | ADD.S F0,F1,F2 |
ADD.D | 把一个双精度浮点数加上一个单精度浮点数,结果是双精度浮点数 | ADD.D F0,F1,F2 |
ADD.PS | 两个单精度浮点数相加,结果是单精度浮点数 | ADD.PS F0,F1,F2 |
DSUB | 两个寄存器的内容相减,也就是定点数的减 | DSUB R1,R2,R3 |
DSUBU | 不带符号的减 | DSUBU R1,R2,R3 |
SUB.S | 一个双精度浮点数减去一个单精度浮点数,结果为单精度 | SUB.S F1,F2,F3 |
SUB.D | 一个双精度浮点数减去一个单精度浮点数,结果为双精度浮点数 | SUB.D F1,F2,F3 |
SUB.PS | 两个单精度浮点数相减 | SUB.SP F1,F2,F3 |
DDIV | 两个定点寄存器的内容相除,也就是定点除 | DDIV R1,R2,R3 |
DDIVU | 不带符号的除法运算 | DDIVU R1,R2,R3 |
DIV.S | 一个双精度浮点数除以一个单精度浮点数,结果为单精度浮点数 | DIV.S F1,F2,F3 |
DIV.D | 一个双精度浮点数除以一个单精度浮点数,结果为双精度浮点数 | DIV.D F1,F2,F3 |
DIV.PS | 两个单精度浮点数相除,结果为单精度 | DIV.PS F1,F2,F3 |
DMUL | 两个定点寄存器的内容相乘,也就是定点乘 | DMUL R1,R2,R3 |
DMULU | 不带符号的乘法运算 | DMULU R1,R2,R3 |
MUL.S | 一个双精度浮点数乘以一个单精度浮点数,结果为单精度浮点数 | DMUL.S F1,F2,F3 |
MUL.D | 一个双精度浮点数乘以一个单精度浮点数,结果为双精度浮点数 | DMUL.D F1,F2,F3 |
MUL.PS | 两个单精度浮点数相乘,结果为单精度浮点数 | DMUL.PS F1,F2,F3 |
AND | 与运算,两个寄存器中的内容相与 | ANDR1,R2,R3 |
ANDI | 一个寄存器中的内容与一个立即数相与 | ANDIR1,R2,#3 |
OR | 或运算,两个寄存器中的内容相或 | ORR1,R2,R3 |
ORI | 一个寄存器中的内容与一个立即数相或 | ORIR1,R2,#3 |
XOR | 异或运算,两个寄存器中的内容相异或 | XORR1,R2,R3 |
XORI | 一个寄存器中的内容与一个立即数异或 | XORIR1,R2,#3 |
BEQZ | 条件转移指令,当寄存器中内容为0时转移发生 | BEQZ R1,0 |
BNEZ | 条件转移指令,当寄存器中内容不为0时转移发生 | BNEZ R1,0 |
BEQ | 条件转移指令,当两个寄存器内容相等时转移发生 | BEQ R1,R2 |
BNE | 条件转移指令,当两个寄存器中内容不等时转移发生 | BNE R1,R2 |
J | 直接跳转指令,跳转的地址在指令中 | J name |
JR | 使用寄存器的跳转指令,跳转地址在寄存器中 | JR R1 |
JAL | 直接跳转指令,并带有链接功能,指令的跳转地址在指令中,跳转发生时要把返回地址存放到R31这个寄存器中 | JAL R1 name |
JALR | 使用寄存器的跳转指令,并且带有链接功能,指令的跳转地址在寄存器中,跳转发生时指令的放回地址放在R31这个寄存器中 | JALR R1 |
MOV.S | 把一个单精度浮点数从一个浮点寄存器复制到另一个浮点寄存器 | MOV.S F0,F1 |
MOV.D | 把一个双精度浮点数从一个浮点寄存器复制到另一个浮点寄存器 | MOV.D F0,F1 |
MFC0 | 把一个数据从通用寄存器复制到特殊寄存器 | MFC0 R1,R2 |
MTC0 | 把一个数据从特殊寄存器复制到通用寄存器 | MTC0 R1,R2 |
MFC1 | 把一个数据从定点寄存器复制到浮点寄存器 | MFC1 R1,F1 |
MTC1 | 把一个数据从浮点寄存器复制到定点寄存器 | MTC1 R1,F1 |
LUI | 把一个16位的立即数填入到寄存器的高16位,低16位补零 | LUI R1,#42 |
DSLL | 双字逻辑左移 | DSLL R1,R2,#2 |
DSRL | 双字逻辑右移 | DSRL R1,R2,#2 |
DSRA | 双字算术右移 | DSRA R1,R2,#2 |
DSLLV | 可变的双字逻辑左移 | DSLLV R1,R2,#2 |
DSRLV | 可变的双字罗伊右移 | DSRLV R1,R2,#2 |
DSRAV | 可变的双字算术右移 | DSRAV R1,R2,#2 |
SLT | 如果R2的值小于R3,那么设置R1的值为1,否则设置R1的值为0 | SLT R1,R2,R3 |
SLTI | 如果寄存器R2的值小于立即数,那么设置R1的值为1,否则设置寄存器R1的值为0 | SLTI R1,R2,#23 |
SLTU | 功能与SLT一致,但是带符号的 | SLTU R1,R2,R3 |
SLTUI | 功能与SLT一致,但不带符号 | SLTUI R1,R2,R3 |
MOVN | 如果第三个寄存器的内容为负,那么复制一个寄存器的内容到另外一个寄存器 | MOVN R1,R2,R3 |
MOVZ | 如果第三个寄存器的内容为0,那么复制一个寄存器的内容到另外一个寄存器 | MOVZ R1,R2,R3 |
TRAP | 根据地址向量转入管态 | |
ERET | 从异常中返回到用户态 | |
MADD.S | 一个双精度浮点数与单精度浮点数相乘加,结果为单精度 | |
MADD.D | 一个双精度浮点数与单精度浮点数相乘加,结果为双精度 | |
MADD.PS | 两个单精度浮点数相乘加,结果为单精度 |