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
2
3
4
5
6
#include <stdio.h>

int main() {
printf("Hello, MIPS!\n");
return 0;
}

对于MIPS来说,有大端和小端两种格式,mips是大端,mipsel是小端,且都支持32位和64位的指令集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#32位小端序:
mipsel-linux-gnu-gcc -g test.c -o test_mipsel_32
#使用小端 MIPS 架构的编译器生成 32 位小端格式的可执行文件。

#32位大端序:
mips-linux-gnu-gcc -g test.c -o test_mips_32
#使用大端 MIPS 架构的编译器生成 32 位大端格式的可执行文件。

#64位小端序:
mips64el-linux-gnuabi64-gcc -g test.c -o test_mipsel_64
#使用小端 MIPS 64 位架构的编译器生成小端 64 位的可执行文件。

#64位大端序:
mips64-linux-gnuabi64-gcc -g test.c -o test_mips_64
#使用大端 MIPS 64 位架构的编译器生成大端 64 位的可执行文件。


现在我们测试一下其中一个

image-20250304180828333

然后我们就能模拟运行这个mips框架的elf文件了

1
2
qemu-mips64el-static -L /usr/mips64el-linux-gnuabi64 ./test_mipsel_64

image-20250304183115538

这里的-L /usr/mips-linux-gnu/指向MIPS库的位置,根据具体环境可能需要调整。这里是默认的位置,当然如果你的并不可以,那可以选择使用如下的语句来寻找

1
2
dpkg -L libc6-mipsel-cross

image-20250304183320297

MIPS指令集

1. MIPS 指令集结构

MIPS 指令集是精简指令集(RISC)架构的典型代表,所有指令长度为 32 位。指令分为三种类型:R 型I 型J 型,每种类型有不同的操作码格式。

  • R 型指令(寄存器-寄存器操作):适用于寄存器之间的运算,通常包括算术和逻辑操作。Op段为0,使用funct字段区分指令

  • I 型指令(立即数操作):适用于需要立即数或地址的操作。使用Op字段区分load/store指令

  • J 型指令(跳转操作):用于跳转指令。使用Op字段区分指令

    image-20250304203733299

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-CacheD-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
2
3
4
li $v0, 1        # 设置 syscall 码为 1(print_int)
li $a0, 123 # 要打印的整数
syscall # 执行系统调用

以上代码会打印 123

MIPS syscall 机制允许程序通过 syscall 指令调用操作系统提供的服务,适用于 MIPS 汇编语言模拟器(如 MARS、SPIM)。