MIPS汇编基础

0x0 前言

最近准备学点路由器挖洞,但是感觉光看网上的知识还是不成体系,于是决定去看家用路由器0day漏洞挖掘来学习

0x1 MIPS介绍

MIPS的系统结构及其设计理念比较先进(也就是那个年代),其指令系统经过通用处理器指令体系MIPS I,MIPS II,MIPS III, MIPS IV, MIPS V,以及嵌入式指令体系MIPS16 ,MIPS32到MIPS64的发展,已经十分成熟

MIPS32架构是一种基于固定长度的定期编码指令集,并采用导入/存储(load/store)数据模型。经过改进,这种架构可以支持高级语言的优化执行。在路由器中,我们经常使用的一种MIPS架构就是MIPS32。

0x1.1 寄存器

RISC 的一个显著特点便大量使用寄存器。因为寄存器的存取可以在一个时钟周期内完成,同时简化了寻找方式,所以,MIPS32的指令中除了加载/存储的指令外,都使用寄存器或者立即数作为操作数,以便让编译器通过保持对寄存器内的数据的频繁存取进一步优化代码的生成性能。MIPS32的寄存器分为两类,分别是通用寄存器(GPR)和特殊寄存器。

  • 通用寄存器(GPR)

    在MIPS体系架构中有32个通用寄存器,在汇编程序中可以用编号$0~$32表示,也可以用寄存器的名字表示,如$sp,$tl,$ra等。

    下表描述32个通用寄存器的别名和用途
    [这里写图片描述](https://springbird3.oss-cn-chengdu.aliyuncs.com/lianxiang/20221022230518.png)

    下面给以详细说明:

    $0 不管写入什么值,读该寄存器永远返回零;

    $31 永远存在正常函数调用指令(JAL/JR)的返回地址;

    $v0, $v1 用来存放一个子程序(函数)的非浮点运算的结果或者返回值,如果这两个寄存器不够存放需要返回的值,则通过内存完成;

    $a0~a3 用来传递子函数调用时前4个非浮点参数,从左到右,超过4个参数使用任务栈传递;

    $t0~t9 函数执行过程中存放临时变量,当调用子函数时,这些寄存器中的值可以被随意更改,无需保存;

    $s0s8 子函数必须保证当函数返回时,这些寄存器的内容必须回复到函数调用之前的值,一般是在函数入口将s0s8压栈,函数返回时,退栈;

    $k0, $k1 异常或者中断处理程序使用,可以随意使用,ISR结束不需要恢复寄存器值;

    $gp 全局指针,指向运行时君顶的静态数据(static data)区域的一个位置,将gp指针作为基地址,前后32KB的数据存取,只需要一条指令即可实现;

    $sp 栈顶指针,MIPS是大端模式,栈的增长方向是从高地址向低地址增长;

  • 特殊寄存器

MIPS32架构中定义了3个特殊的寄存器,分别是PC(程序计数器),HI(乘除结果高位寄存器)和LO(乘除结果低位寄存器)。在进行乘法运算时,HI和LO保存乘法的运算结果,其中HI存储高32位,LO存储低32位;而在进行除法运算时,HI保存余数,LO存储商。

0x1.2 字节序

数据在存储器中是按照字节序存放的,处理器也是按照字节访问存储器中的指令或者数据。MIPS架构的数据是大端序,有区别与常见的X86架构的小端序

0x1.3 MIPS寻址方式

MIPS32架构的寻址模式有寄存器寻址,立即数寻址,寄存器相对寻址,和PC相对寻址4种,其中寄存器相对寻址,PC相对寻址的介绍如下:

  • 寄存器相对寻址:这种寻址模式主要被加载/存储指令使用,其对一个16位的立即数进行符号扩展,然后与指定通用寄存器的值相加,从而得到有效的地址
  • PC相对寻址:这种寻址模式主要被转移指令使用。在转移指令中有一个16位的立即数,将其左移2位并进行符号扩展,然后与程序计数寄存器PC的值相加,可得到有效地址

0x1.4 MIPS指令集

0x1.4.1 MIPS指令的特点

MIPS指令的特点如下

  • MIPS固定4字节指令长度
  • 内存中的数据访问(load/store)必须严格对齐(至少4字节对齐)
  • 跳转指令只有26位目标地址,加上2位对齐位,可以寻址28位的空间,即256M。
  • 条件分支指令只有16位跳转地址,加上2位对齐位,共18位寻址空间,即256kb。
  • MIPS默认不把子函数的返回地址(就是调用函数的受害指令地址)存放到栈中,而是放到$31($ra)寄存器中,这对那些叶子函数(在函数中不再调用其他函数的函数)有利。如果遇到嵌套函数会有其他机制处理
  • 流水线效应。MIPS采用了高度的流水线,其中一个最重要的效应就是分支延迟效应。在分支跳转语句后面的那条语句叫做分支延迟槽。实际上,在程序执行到分支语句时,当它刚把要跳转到的地址填充好(填充到代码计数器里),还没有完成本条指令时,分支语句后面的那个指令就已经执行了,其原因就是流水线效应–几条指令同时执行,只是处于不同的阶段

特别介绍一下流水线效应,示例如下

1
2
3
mov  $a0,$s2
jalr strchr
move $a0,$s0

在执行第二行跳转分支的时候,第三行的move指令已经执行完了,因此在上面的指令序列中,strchr函数的参数来自于第三行的$s0,而不是第一行的$s2。

从流水线效应可以看出,是否正确理解MIPS指令的这些特点会直接影响我们对MIPS程序逆向分析的结果,因此,我们需要熟练把握这些特点。下面我们就正式开始学习MIPS指令的格式以及常见的汇编指令

0x1.4.2

我们已经知道,所有的MIPS指令的长度相同,都是32位。为了让指令的格式刚好合适,设计者做了折中;将所有指令定长,但是不同的指令有不同的格式。在MIPS架构中,指令的最高位6位均为6位的Opcode码,剩下的24位可以将指令分为3种类型,分别是R型,I型和J型。

  • R型指令用连续3个5位二进制码表示三个寄存器的地址