十三、MIPS汇编指令-main函数

在这里插入图片描述
本次的分享,我们将以一段简单的C语言程序为例,使用mips-linux的gcc编译器对其进行编译后,使用ida对编译后的程序进行反汇编,并通过对照C语言代码的方式理解MIPS汇编指令。

首先我们来看这段C代码(test.c):

#include <stdio.h>
#include <string.h>

void B() {
        printf(".....\n");
}

void A(char *content) {
        char buf[32];
        strcpy(buf, content);
        printf("buf: %s\n", buf);
        B();
}

void main(int argc, char **argv) {
        A(argv[1]);
}

在代码中,main函数直接调用A函数,并将第一个命令行参数作为函数A的参数传递过去,在函数A中,使用strcpy函数将参数保存到局部变量buf中,然后是使用printf函数打印buf变量。最后函数A调用了函数B,在函数B中只是简单的调用了printf函数打印了一串字符串常量。

我们使用mips-linux-gnu-gcc命令将源码编译,得到名为test的可执行文件:

$ mips-linux-gnu-gcc -static test.c -o test

编译后,使用qemu-mips命令执行这个test程序:

$ cp /usr/bin/qemu-mips-static .
$ ./qemu-mips-static  ./test  AAAAAAAA
buf: AAAAAAAA
.....

可以看到,我们编译的程序是可以正常运行的。

然后我们使用IDA保持一路默认打开这个test程序:
在这里插入图片描述
在IDA窗口左侧的Function window窗口中,我们可以看到,IDA识别出test中包含了main函数、A函数、B函数。

双击这个main函数,就可以开始进入汇编世界了:
在这里插入图片描述
在main函数的起始处,有4个变量定义,分别是var_8、var_4、arg_0、arg_4。为了更便于理解,我在文稿中绘制了当前main函数的栈帧图示:
在这里插入图片描述
main函数实际上是由__libc_start_main函数调用起来的,所以在main函数被执行后,main函数首先要为自己分配函数栈空间,对应的汇编指令就是0x00400970地址处的addiu s p , − 0 x 20 指 令 。 a d d 是 加 法 , i 代 表 立 即 数 , u 代 表 无 符 号 数 。 而 sp, -0x20指令。add是加法,i代表立即数,u代表无符号数。而 sp,0x20addiusp就是栈顶指针,通过将栈顶指针向下移动0x20个字节,这0x20个字节就是main函数的函数栈空间,对应图中的绿色部分。
在这里插入图片描述为了保证在main函数返回后,程序流程继续回到__lib_start_main函数中继续执行,需要将__lib_start_main函数中调用main函数完成后的下一条指令的地址保存到栈空间,也就是将RA寄存器的值存储到main函数的函数栈中,对应的汇编指令为:sw r a , 0 x 20 + v a r 4 ( ra, 0x20+var_4( ra,0x20+var4(sp),sw指令是将ra寄存器的值存储到$sp+0x20-4地址处,这个地址就是变量var_4所在的内存。

类似的,汇编代码中也将fp寄存器做了同样的处理,关于这个fp寄存器,为了不与栈顶指针sp混淆,我们目前可以不必关心。
在这里插入图片描述
紧接着在汇编代码的0x00400980地址处,分别使用sw指令将a0、a1寄存器的值存储到了__lib_start_main函数的参数调用空间中,也就是上面图片中的arg_0、arg_4。a0和a1分别代表了main函数的两个参数,也就是argc和argv。其中arg_4代表了前面使用命令行执行test程序时传递的命令函参数,是一个字符串数组。
在这里插入图片描述
紧接着,使用lw指令将arg_4参数取出,并复制给v0寄存器。并在v0寄存器指向的main函数命令行参数数组中取出argv[1],也就是“AAAAAAAA”,然后将这个“AAAAAAAA”再次复制给v0寄存器,并最终赋值给a0寄存器:
在这里插入图片描述
上一篇的分享中,我们提到过,在MIPS架构程序做函数调用时,会将前4个参数分别使用a0~a3寄存器进行保存,所以,代码分析到这里,我们已经能够发现,为了调用函数A,汇编代码为此准备好了第一个参数,也就是a0寄存器,它对应了字符串“AAAAAAAA”。
然后在main函数中,使用了jal指令将程序流程跳转到A函数执行:
在这里插入图片描述
这里我们应该知道的一个关键点是,使用jal指令进行函数跳转时,会隐式的将调用函数A完成后的下一条指令的地址保存到RA寄存器中。所以RA寄存器在这里被覆盖了,所以,在main函数的末尾处应该从main函数的中空间中将RA寄存器的值进行恢复。

我们假设函数A已经执行完成,在函数A的尾部会通过RA寄存器,将程序的执行流程跳转回main函数,也就是main函数中的jal指令后面的地址处:
在这里插入图片描述
然后,main函数的结尾处几行汇编代码会将 s p 、 sp、 spra、$fp寄存器从main函数的栈空间中进行值恢复,然后使用jr指令,将程序的执行流程返回到__libc_start_main函数中执行。注意,这里使用的jr指令并不会改变RA寄存器的值,jal指令才会改变RA寄存器的值。

我们通过今天分享,通过将我们自己编写的C代码编译为mips-linux架构的程序,并使用IDA对其进行逆向分析,初探了MIPS汇编代码。对于繁杂的汇编代码,看似要记住的指令有很多,但它们不过是一些英文单词的缩写组合而成的。比如add代表加法、i代表Immediat,就是立即数、u代表unsigned,就是无符号、lw中的l代表load,就是加载的意思,w代表word,就是4个字节的意思、jal指令中的j代表jump,就是跳转的意思、a代表address,就是跳转到某个地址的意思、l代表long,就是跳转的地址距离jal这条指令很远的意思(这个l代表long纯属乱猜,只是为了方便自己记忆)。

希望本节的分析能够为你打开进入MIPS汇编世界的大门。下次分享中,我们会继续通过分析main函数调用的A函数的反汇编代码,以此来增强我们对汇编语言的理解。

最后希望本次的分享能够为你带来帮助,谢谢大家。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后知晚觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值