汇编中的la_MIPS汇编与指令
⼀、MIPS寄存器
MIPS的系统结构及设计理念⽐较先进,其指令系统经过通⽤处理器指令体系MIPS I、MIPS II、MIPS III、MIPS IV、MIPS V,以及嵌⼊式指令体系MIPS16、MIPS32到MIPS64的发展。
MIPS32的架构是⼀种基于固定长度的定期编码指令集,并采⽤导⼊/存储(load/store)数据模型。经改进,这种架构可⽀持⾼级语⾔的优化执⾏。在路由器中,经常使⽤的⼀种MIPS架构就是MIPS32。
RISC的⼀个显著特点就是⼤量使⽤寄存器。因为寄存器的存取可以在⼀个时钟周期内完成,同时简化了寻⽅式,所以,MIPS32的指令中除了加载/存储指令以外,都使⽤寄存器或者⽴即数作为操作数,以便让编译器通过保持对寄存器内数据的频繁存取进⼀步优化代码的⽣成性能。
MIPS32寄存器分为两类:通⽤寄存器(GPR)和特殊寄存器。
1、通⽤寄存器(GPR)
在MIPS体系结构中有32个通⽤寄存器,在汇编程序中可以⽤编号$0~$31表⽰,也可以⽤寄存器的名字表⽰,如$sp、$t1、$ta等,如图,堆栈是从内存的⾼地址⽅向向低地址⽅向增长的。编号寄存器名称寄存器描述
0zero第0号寄存器,其值始终为0
1$at保留寄存器
2~3$v0~v1values, 保存表达式或函数返回结果
4-7$a0~a3aruments, 作为函数的前4个参数
8~15$t0~$t7temporaries,供汇编程序使⽤的临时寄存器
16~23$s0~$s7saved values,⼦函数使⽤时需要先保存原寄存器的值
24~25$t8~t9temporaries, 供汇编程序的临时寄存器,补充$t0~t7
26~27$k0~$k1保留,中断处理函数使⽤
28$gpglobal pointer,全局指针
29$spstack pointer, 堆栈指针,指向堆栈的栈顶
30$fpframe pointer, 保存栈指针
31$rareturn 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有⼀个jar(jump-and-link,跳转并链接)指令,在跳转到某个地址时可把下⼀条指令的地址放到$ra中,⽤于⽀持⼦程序。例如,调⽤程序把参数放到$a0~$a3中,“jar X"指令跳到X过程,被调⽤时需要保存的寄存器为$a0~$a3、$s0~$s7、$gp、$sp、$fp、$ra。
2、特殊寄存器
MIPS32架构中定义了3个特殊寄存器。分别为PC(程序计数器)、HI(乘除结果⾼位寄存器)和LO(乘除结果低位寄存器)。在进⾏乘法运算时,HI和LO保存乘法的运算结果,其中HI存储⾼32位,LO存储低32位;⽽在进⾏除法运算时,HI保存余数,LO存储商。
⼆、MIPS指令集初步
1、MIPS寻址⽅式
MIPS架构的寻址模式有寄存器寻址、⽴即数寻址、寄存器相对寻址和PC相对寻址4种,其中寄存器相对寻址、PC相对寻址介绍如下:
1.1、寄存器相对寻址
这种寻址模式主要被加载/存储指令使⽤,其对⼀个16位的⽴即数进⾏符号扩展,然后与指定通⽤寄存器的值相加,从⽽得到有效地址。
通⽤寄存器GRP  +  16位⽴即数做符号扩展      =      有效地址
1.2、PC相对寻址
这种寻址模式主要被转移指令使⽤。在转移指令中有⼀个16位的⽴即数,将其左移2位并进⾏符号扩展,然后与程序计数器PC的值相加,可得到有效地址。
程序计数器PC    +      16位⽴即数左移2位并做符号扩展    =    有效地址
2、MIPS指令集
2.1、MIPS指令特点MIPS固定4字节指令长度。
内存中的数据访问(load/store)必须严格对齐(⾄少4字节对齐)。
跳转指令只有26位⽬标地址,加上2位对齐位,可寻址28位寻址空间,即256MB。
条件分⽀指令只有16位跳转地址,加上2位对齐位,共18位寻址空间,即256KB。
MIPS默认不把⼦函数的返回地址(就是调⽤函数的受害指令地址)存放到栈中,⽽是存放到$31($ra)寄存器中,这对那些叶⼦函数(在函数中不再调⽤其他函数的函数)有利。如果遇到嵌套函数,有其他机制
处理。
流⽔线效应。MIPS采⽤了⾼度的流⽔线,其中⼀个最重要的效应就是分⽀延迟效应。在分⽀跳转语句后⾯的那条语句叫做分⽀延迟槽。实际上,在程序执⾏到分⽀语句时,当它刚把要跳转到的地址填充好(填充到代码计数器⾥),还没有完成本条指令时,分⽀语句后⾯的那个指令就已经执⾏了,其原因就是流⽔线效应 ---- ⼏条指令同时执⾏,只是处于不同的阶段。
流⽔线效应:
mov $a0, $s2
jalr strrchr
move $a0, $s0
在执⾏第2⾏跳转分⽀时,第3⾏的move指令已经执⾏完了。因此,在上⾯指令序列中,strrchr函数的参数来⾃第3⾏的$s0,⽽不是第1⾏的$s2。
从流⽔线效应中可以看出,是否正确理解MIPS指令的这些特点会直接影响我们对MIPS程序逆向分析的结果,因此,我们需要熟悉把握这些特点。
2.2、指令格式
所有MIPS指令的长度相同,都是32位。为了让指令的格式刚好合适,设计者做了折中:将所有指令定长,但是不同的指令有不同的格式。在MIPS架构中,指令的最⾼6位均为Opcode码,剩下的26位可以将指令分为3种类型,分别为R型、I型和J型。R型指令⽤连续3个5位⼆进制码表⽰3个寄存器的地址,然后⽤1个5位⼆进制码表⽰移位的位数(如果未使⽤移位操作,则全为0),最后是6位的Function码(它与Opcode码共同决定R型指令的具体操作⽅式)。
I型指令则⽤连续2个5位⼆进制码表⽰2个寄存器的地址,然后是由1个16位⼆进制码表⽰1个⽴即数⼆进制码。
J型指令⽤26位⼆进制码表⽰跳转⽬标的指令地址(实际的指令地址应为32位,其中最低2位为“00”,最⾼4位由PC当前地址决定)。类型格式
ROpcode(6)Rs(5)Rt(5)Rd(5)Shamt(5)Funct(6)
IOpcode(6)Rs(5)Rt(5)Immediate(16)
JOpcode(6)Address(26)Opcode: 指令基本操作,称为操作码。
Rs: 第⼀个源操作数寄存器。
Rt: 第⼆个源操作数寄存器。
Rd: 存放操作结果的⽬的操作数。
Shamt: 位移量。
Funct: 函数,这个字段选择Opcode操作某个特定变体。
3、汇编常⽤的指令
注意:$Rd表⽰⽬的寄存器, $Rs表⽰源寄存器,$Rt表⽰作为中间缓存的寄存器,"imm"表⽰⽴即数,“MEM[]“表⽰RAM中的⼀段内存,“offset"表⽰偏移量。
3.1、LOAD/STORE指令
LOAD/STORE指令有14条,分别是lb、lbu、lh、lhu、ll、lw、lwl、lwr、sb、sc、sh、sw、swl和swr。
以"l"开头的都是加载指令,以"s"开头的都是存储指令,这些指令⽤于从存储器中读取数据,或者将数据保存在存储器中。
3.1.1、LA(Load Address) 指令⽤于将⼀个地址或标签存⼊⼀个寄存器。语法实例备注
la $Rd, Labella $t0, val_1复制val_1表⽰的地址到$t0寄存器中,其中val_1是⼀个Label
3.1.2、LI(Load Immediate)指令⽤于将⼀个⽴即数存⼊⼀个通⽤寄存器。语法实例备注
lw $Rt, offset($Rs)lw $s0, 0($sp)"$s0 = MEM[$sp+0]",相当于取堆栈地址偏移0内存word长度的值到$s0中
3.1.3、LW(Load Word) 指令⽤于从⼀个指定的地址加载⼀个word类型的值到⼀个寄存器中。语法实例备注
lw $Rt, offset($Rs)lw $s0, 0($sp)"$s0=MEM[$sp+0];",相当于取堆栈地址偏移0内存word长度的值到$s0中
3.1.4、SW(Store Word)⽤于将源寄存器中的值存⼊指定的地址。语法实例备注
sw $Rt, offset($Rs)sw $a0,0($sp)"MEM[$sp+0]=$a0;",相当于将$a0寄存器中⼀个word⼤⼩的值存⼊堆栈,且$sp⾃动堆栈
3.1.5、MOVE指令⽤于寄存器之间值的传递。语法实例备注
move $Rt, $Rsmove $t5, $t1$t5=$t1;
3.2、算术运算指令
MIPS汇编指令的算术运算特点如下:算术运算指令的所有操作数都是寄存器,不能直接使⽤RAM地址或间接寻址。
操作数⼤⼩都为word(4 Byte)。
算术运算指令有21条,分别为add、addi、sub、subu、clo、clz、slt、slti、sltiu、sltu、mul、mult、madd、maddu、msub、msubu、div和divu,实现了加、减、⽐较、乘、乘累加、除等运算。指令格式与实例注释
add $t0,$t1,$t2"$t0=$t1+$t2;",带符号数相加
sub $t0,$t1,$t2"$t0=$t2 - $t2;", 带符号数相减
addi $t0,$t1,5$t0 = $t1 + 5;
addu $t0,$t1,$t2"$t0 = $t1 + $t2;",⽆符号数相加
subu $t0, $t1, $t2"$t0 = $t2 - $t2;",⽆符号数相减
汇编指令有多少个mult $t3, $t4"$t3 * $t4", 把64 Bits的积存储到"Lo,Hi"中,即"(Hi,Lo)=$t3 * $t4;"
div $t5, $t6"$LO=$t5/$t6", $LO为商的整数部分;"$HI=$t5 mod $t6", $HI为余数
mfhi $t0$t0 = $HI
mflo $t1$t1 = $LO
以上是MIPS指令基础。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。