MIPS基础入门
MIPS基础入门
基础介绍
首先我们来介绍一下什么是mips,MIPS架构是一种采取 精简指令集(RISC)的处理器架构,1981年出现,由MIPS科技公司开发并授权,它是基于一种固定长度的定期编码指令集,并采用 导入/存储(Load/Store)数据模型。经改进,这种架构可支持高级语言的优化执行。其算术和逻辑运算采用三个操作数的形式,允许编译器优化复杂的表达式。
如今基于该架构的芯片广泛被使用在许多电子产品、网络设备、个人娱乐装置与商业装置上。最早的MIPS架构是32位,最新的版本已经变成64位。
环境模拟
我们的虚拟机基本都是ubuntu,也就是基于8086架构,所以这里我们学习mips需要理由qemu来模拟这个架构,而QEMU代表快速模拟器,是虚拟化领域的一个重要工具,能够在单个硬件平台上同时运行多个操作系统。以其能够仿真广泛的客户系统和架构而闻名,QEMU是创建和管理虚拟环境的多功能解决方案。它作为一个类型1虚拟机运行,直接与物理硬件接口,这与其他虚拟化技术有显著的不同。通过整合像Intel VT和AMD-V这样的硬件虚拟化技术,QEMU优化了虚拟机的性能,为开发人员和IT专业人员提供了一个强大的平台,用于模拟各种计算环境,无需为每个系统提供专用硬件。
下面是一个自动化脚本,运行可以得到完整配置的qemu和mips,现在我们来编译一个demo,看看怎么个事儿。
1 |
|
对于MIPS来说,有大端和小端两种格式,mips是大端,mipsel是小端,且都支持32位和64位的指令集。
1 |
|
现在我们测试一下其中一个
然后我们就能模拟运行这个mips框架的elf文件了
1 |
|
这里的-L /usr/mips-linux-gnu/
指向MIPS库的位置,根据具体环境可能需要调整。这里是默认的位置,当然如果你的并不可以,那可以选择使用如下的语句来寻找
1 |
|
MIPS指令集
1. MIPS 指令集结构
MIPS 指令集是精简指令集(RISC)架构的典型代表,所有指令长度为 32 位。指令分为三种类型:R 型、I 型 和 J 型,每种类型有不同的操作码格式。
R 型指令(寄存器-寄存器操作):适用于寄存器之间的运算,通常包括算术和逻辑操作。Op段为0,使用funct字段区分指令
I 型指令(立即数操作):适用于需要立即数或地址的操作。使用Op字段区分load/store指令
J 型指令(跳转操作):用于跳转指令。使用Op字段区分指令
2. 指令分类
MIPS 指令集主要分为以下几类:
以下是 MIPS 指令的详细执行表,包括操作数的来源和结果存储位置:
(1)算术运算指令
指令 | 功能 | 格式 | 执行描述 |
---|---|---|---|
add |
有符号整数加法 | add rd, rs, rt |
rd = rs + rt |
addi |
有符号整数加法(立即数) | addi rt, rs, imm |
rt = rs + imm |
sub |
有符号整数减法 | sub rd, rs, rt |
rd = rs - rt |
mult |
有符号乘法 | mult rs, rt |
HI, LO = rs × rt (结果存储在 HI/LO 寄存器) |
div |
有符号除法 | div rs, rt |
LO = rs / rt, HI = rs % rt (商存 LO ,余数存 HI ) |
(2)逻辑运算指令
指令 | 功能 | 格式 | 执行描述 |
---|---|---|---|
and |
按位与 | and rd, rs, rt |
rd = rs & rt |
andi |
按位与(立即数) | andi rt, rs, imm |
rt = rs & imm |
or |
按位或 | or rd, rs, rt |
rd = rs |
ori |
按位或(立即数) | ori rt, rs, imm |
rt = rs |
xor |
按位异或 | xor rd, rs, rt |
rd = rs ^ rt |
nor |
按位取反或 | nor rd, rs, rt |
rd = ~(rs |
(3)移位指令
指令 | 功能 | 格式 | 执行描述 |
---|---|---|---|
sll |
左移 | sll rd, rt, shamt |
rd = rt << shamt |
srl |
逻辑右移 | srl rd, rt, shamt |
rd = rt >> shamt (高位补 0) |
sra |
算术右移 | sra rd, rt, shamt |
rd = rt >> shamt (高位补符号位) |
(4)数据传输指令
指令 | 功能 | 格式 | 执行描述 |
---|---|---|---|
lw |
加载字 | lw rt, offset(rs) |
rt = *(rs + offset) (从 rs + offset 地址加载 4 字节到 rt ) |
sw |
存储字 | sw rt, offset(rs) |
*(rs + offset) = rt (将 rt 存到 rs + offset 地址) |
lb |
加载字节 | lb rt, offset(rs) |
rt = *(rs + offset) (加载 1 字节,符号扩展到 32 位) |
sb |
存储字节 | sb rt, offset(rs) |
*(rs + offset) = rt (仅存储 rt 的低 8 位到 rs + offset ) |
(5)条件分支指令
指令 | 功能 | 格式 | 执行描述 |
---|---|---|---|
beq |
等于则跳转 | beq rs, rt, label |
if (rs == rt) goto label |
bne |
不等则跳转 | bne rs, rt, label |
if (rs != rt) goto label |
bgtz |
大于零则跳转 | bgtz rs, label |
if (rs > 0) goto label |
blez |
小于等于零则跳转 | blez rs, label |
if (rs <= 0) goto label |
(6)跳转指令
指令 | 功能 | 格式 | 执行描述 |
---|---|---|---|
j |
无条件跳转 | j label |
goto label |
jal |
跳转并链接 | jal label |
ra = PC + 4; goto label (保存返回地址) |
jr |
寄存器跳转 | jr rs |
goto rs (跳转到 rs 指向的地址) |
3. 特殊指令
syscall
:用于系统调用,通常用于程序和操作系统之间的交互。nop
:空操作指令,执行时不产生任何效果,通常用于指令延迟槽。
MIPS 寄存器
常用寄存器
名称 | 编号 | 作用 | 说明 |
---|---|---|---|
$zero |
$0 |
常数 0 | 值恒为 0 ,无法更改 |
$at |
$1 |
汇编器临时寄存器 | 由汇编器内部使用,不建议手动操作 |
$v0-$v1 |
$2-$3 |
函数返回值 | 32 位返回值存于 $v0 ,64 位返回值存于 $v0, $v1 |
$a0-$a3 |
$4-$7 |
函数参数 | 依次存放第 1-4 个参数,更多参数需使用栈 |
$t0-$t7 |
$8-$15 |
临时寄存器 | 不需要保持值,函数调用后可能被覆盖 |
$s0-$s7 |
$16-$23 |
保存寄存器 | 需要保持值,函数调用时需保存和恢复 |
$t8-$t9 |
$24-$25 |
额外临时寄存器 | 用法同 $t0-$t7 |
$k0-$k1 |
$26-$27 |
内核保留寄存器 | 仅操作系统使用,用户程序不应修改 |
$gp |
$28 |
全局指针 | 指向全局数据区 |
$sp |
$29 |
栈指针 | 指向栈顶,函数调用时维护 |
$fp |
$30 |
帧指针 | 用于存储栈帧地址(可选) |
$ra |
$31 |
返回地址 | jal 指令跳转时存储返回地址 |
特殊寄存器
名称 | 作用 |
---|---|
HI |
存储乘法的高 32 位结果或除法的余数 |
LO |
存储乘法的低 32 位结果或除法的商 |
PC |
程序计数器,存储当前执行指令的地址 |
MIPS特性
1. 指令集架构
固定指令长度:所有 MIPS 指令都是 4 字节(32 位),符合 RISC(精简指令集计算机)设计理念。
流水线优化:MIPS 采用 多级流水线,提高指令执行效率,但也带来了分支延迟效应和载入延迟效应。
分支延迟槽(Branch Delay Slot)
:
- 由于流水线特性,分支跳转指令后的一条指令仍会执行,称为分支延迟槽。
- 典型情况下,这条指令是
nop
(无操作),但可以替换为对程序无害的有用指令。
2. 内存与栈管理
栈增长方向:MIPS 架构的栈(Stack)从高地址向低地址增长。
缓存(Cache)机制
:
- 指令缓存(I-Cache) 和 数据缓存(D-Cache) 独立存储,指令和数据不会混用。
- 执行代码必须先经过缓存刷新,否则数据缓存中的 shellcode 可能不会被执行,通常执行
sleep(1)
触发缓存刷新。
3. 函数调用约定
- 叶子函数(Leaf Function):函数内部没有调用其他函数,仅使用
$t0-$t9
作为临时寄存器。 - 非叶子函数(Non-Leaf Function):函数内部调用了其他函数,需要保存
$ra
(返回地址寄存器) 以及可能被覆盖的$s0-$s7
寄存器。
4. MIPS 代码执行安全性
指令与数据分离
:
- 由于指令和数据分别存储在
I-Cache
和D-Cache
,在代码注入攻击中,如果D-Cache
没有同步刷新到I-Cache
,则shellcode
可能不会执行。 - 解决方案:使用
sleep(1)
或flush cache
触发缓存同步。
- 由于指令和数据分别存储在
5. MIPS 指令特点
RISC 设计:指令种类少,每条指令执行时间固定。
加载/存储架构(Load/Store Architecture)
:
- 只有
lw
(加载字) 和sw
(存储字) 指令可以访问内存,其他指令只能操作寄存器。
- 只有
延迟槽优化:跳转指令的下一条指令仍会被执行,需手动填充
nop
或有用的指令。
MIPS 体系结构核心特性概述
特性 | 说明 |
---|---|
指令长度 | 所有指令都是 4 字节(32 位) |
流水线效应 | 分支延迟槽,跳转指令后的一条指令仍会执行 |
栈方向 | 从高地址向低地址增长 |
缓存结构 | 独立的 I-Cache 和 D-Cache,代码执行前需刷新缓存 |
调用约定 | 叶子函数不保存 $ra ,非叶子函数需保存 $ra 和 $s0-$s7 |
加载存储架构 | 只有 lw / sw 访问内存,其他指令操作寄存器 |
安全性 | shellcode 可能因缓存未同步导致执行失败,需要 flush |
MIPS系统调用码
MIPS 系统调用(syscall)使用 $v0
寄存器指定调用码,不同的调用码执行不同的系统功能。以下是常见的 MIPS 系统调用码表:
调用码($v0) | 函数名字 | 功能 | 调用条件 |
---|---|---|---|
1 | print_int |
打印整数 | $a0 = 要打印的整数 |
2 | print_float |
打印浮点数(单精度) | $f12 = 要打印的浮点数 |
3 | print_double |
打印双精度浮点数 | $f12 = 要打印的双精度浮点数 |
4 | print_string |
打印字符串 | $a0 = 字符串地址 |
5 | read_int |
读取整数 | 读取的整数存入 $v0 |
6 | read_float |
读取浮点数(单精度) | 读取的浮点数存入 $f0 |
7 | read_double |
读取双精度浮点数 | 读取的双精度浮点数存入 $f0 |
8 | read_string |
读取字符串 | $a0 = 缓冲区地址, $a1 = 最大长度 |
9 | sbrk |
分配内存(返回地址) | $a0 = 需要分配的字节数,返回分配的内存地址于 $v0 |
10 | exit |
退出程序 | 无 |
11 | print_char |
打印字符 | $a0 = 要打印的字符 |
12 | read_char |
读取字符 | 读取的字符存入 $v0 |
13 | open |
打开文件 | $a0 = 文件名地址, $a1 = 访问模式, $a2 = 权限(可选),返回文件描述符 |
14 | read |
读取文件 | $a0 = 文件描述符, $a1 = 读取缓冲区, $a2 = 读取字节数,返回读取的字节数 |
15 | write |
写入文件 | $a0 = 文件描述符, $a1 = 写入缓冲区, $a2 = 写入字节数,返回写入的字节数 |
16 | close |
关闭文件 | $a0 = 文件描述符 |
17 | exit2 |
退出程序(带返回值) | $a0 = 退出码 |
示例:
1 |
|
以上代码会打印 123
。
MIPS syscall 机制允许程序通过 syscall
指令调用操作系统提供的服务,适用于 MIPS 汇编语言模拟器(如 MARS、SPIM)。