山东大学 2020级计算机系统原理——拆解二进制炸弹

写在前面

第一次拿到这个实验还是有点慌!之前没见过,不过还是慢慢做过来了。

这是个需要耐心的过程,请一定静下心来哦!

环境及配置

环境:Ubuntu 20.04 + GDB 调试工具
可参考配置:GDB调试工具配置(须先自行下载 Ubuntu 哈)

参考资料

MIPS 常用指令:MIPS 常用指令
MIPS寄存器说明:MIPS寄存器说明
供参考的 C 语言代码:Brief C Programs of the Bombs
队友 scy 巨巨的博客(占个坑):太奆了

phase_1

先看主函数:

...
//先看主函数
0x00400900<main>:0x00400ba4 <+676>:	lw	gp,16(s8)0x00400ba8 <+680>:	jal	0x401fec <read_line>0x00400bac <+684>:	nop0x00400bb0 <+688>:	lw	gp,16(s8)0x00400bb4 <+692>:	sw	v0,32(s8)0x00400bb8 <+696>:	lw	a0,32(s8)0x00400bbc <+700>:	jal	0x400d6c <phase_1>
...

首先是主函数的部分,这一部分主要是为了给 phase_1 传递我们所输入的字符串。看下面的例子,首先我在 phase_1 时输入 “Helloworld!”“Hello\ world!”Hello world!,共 12 个字符,此时使用 GDB 进行调试:
在这里插入图片描述
先在 0x00400bb8 处设置一个断点,查看寄存器 $v0 的值:
在这里插入图片描述
可以看到,从 v0v0v0 中向后读取 12 个字符就是我们所输入的 "Helloworld!""Hello\ world!""Hello world!"
因此,这部分的思路就是:
1. 先调用 read_line 函数去读取我们输入的字符串,并将它存入专门保存函数返回值的寄存器 v0v0v0 中;
2. 接着将 v0v0v0 中的值存到内存 (s8+32)(s8+32)(s8+32) 中去;从该内存中读取字符串保存到寄存器 a0a0a0 中(为了在 phase_1 中使用);
3. 最后跳转到 phase_1 中进行炸弹 1 的拆解。

...0x00400ba4 <+676>:	lw	gp,16(s8)0x00400ba8 <+680>:	jal	0x401fec <read_line>	//调用函数读取字符串0x00400bac <+684>:	nop0x00400bb0 <+688>:	lw	gp,16(s8)0x00400bb4 <+692>:	sw	v0,32(s8)	//m[$s8 + 32] = v0,将 $v0 中的值(即输入的字符串)保存到内存中0x00400bb8 <+696>:	lw	a0,32(s8)	//a0 = m[$s8 + 32],在从内存中读出这个字符串到 $a0 中,为了在 phase_1 中使用0x00400bbc <+700>:	jal	0x400d6c <phase_1>	//跳转至 phase_1 中
...

知道输入的参数的传递了之后,接着看 phase_1:

   0x00400d6c <+0>:		addiu	sp,sp,-320x00400d70 <+4>:		sw	ra,28(sp)0x00400d74 <+8>:		sw	s8,24(sp)0x00400d78 <+12>:	move	s8,sp0x00400d7c <+16>:	sw	a0,32(s8)0x00400d80 <+20>:	lw	a0,32(s8)0x00400d84 <+24>:	lui	v0,0x400x00400d88 <+28>:	addiu	a1,v0,100920x00400d8c <+32>:	jal	0x401cf8 <strings_not_equal>0x00400d90 <+36>:	nop0x00400d94 <+40>:	beqz	v0,0x400da4 <phase_1+56>0x00400d98 <+44>:	nop0x00400d9c <+48>:	jal	0x4021f0 <explode_bomb>0x00400da0 <+52>:	nop0x00400da4 <+56>:	move	sp,s80x00400da8 <+60>:	lw	ra,28(sp)0x00400dac <+64>:	lw	s8,24(sp)0x00400db0 <+68>:	addiu	sp,sp,320x00400db4 <+72>:	jr	ra0x00400db8 <+76>:	nop

首先,前四行:
在这里插入图片描述
这是为了在栈中开辟空间,即为 phase_1 开辟足够的空间,以便函数运行;
接下来,可以看见一个 string_not_equal 函数,用于对字符串进行比较。那么比较的是哪两个字符串呢?我们可以使用 GDB 调试来查看相关寄存器的值:

在函数 string_not_equal 调用之前,涉及到了 a0a0a0a1a1a1 寄存器的处理,我们先看 a0a0a0
在这里插入图片描述
再往后面输出 12 个字符:
在这里插入图片描述
刚好是我们输入的字符串 "Helloworld!""Hello\ world!""Hello world!",那么可以确定 a0a0a0 中存的就是输入的字符串;

接着在 0x00400d8c 设置断点之后,看 a1a1a1
在这里插入图片描述
发现,我们输出 17 个字符之后,第 17 个字符就是 ‘\000’ 了,看前面 16 个字符,刚好组成:"Let′sbeginnow!""Let's\ begin\ now!""Lets begin now!",就是内部存储的要比较的字符串啦~

结合 string_not_equal 可知,phase_1 比较的就是我们输入的字符串和 "Let′sbeginnow!""Let's\ begin\ now!""Lets begin now!" 是否相等,并将返回值存入了专门存放返回值的寄存器 v0v0v0(相等为 1,不相等为 0)。接下来就是一个比较过程,根据注释食用哈~

   0x00400d6c <+0>:		addiu	sp,sp,-320x00400d70 <+4>:		sw	ra,28(sp)0x00400d74 <+8>:		sw	s8,24(sp)0x00400d78 <+12>:	move	s8,sp	//为程序段开辟栈空间0x00400d7c <+16>:	sw	a0,32(s8)0x00400d80 <+20>:	lw	a0,32(s8)	//重新使输入的字符串入栈?0x00400d84 <+24>:	lui	v0,0x400x00400d88 <+28>:	addiu	a1,v0,10092	//a1 = m[0x40 + 10092],到内存中找到我们要比较的字符串0x00400d8c <+32>:	jal	0x401cf8 <strings_not_equal>	//进入函数比较字符串是否相等,相等则返回1,不相等返回0,这个返回存入了寄存器 $v0 中!0x00400d90 <+36>:	nop0x00400d94 <+40>:	beqz	v0,0x400da4 <phase_1+56>	//$v0 为 0 则继续(炸弹爆炸),不为 0 跳转到 <+56> 的位置,可以看到跳转后便跳过了炸弹,第一个炸弹 avoided!0x00400d98 <+44>:	nop0x00400d9c <+48>:	jal	0x4021f0 <explode_bomb>0x00400da0 <+52>:	nop0x00400da4 <+56>:	move	sp,s8	//这后面就是归还栈空间,并跳回 $ra 寄存器中的地址,即return,返回主函数了!0x00400da8 <+60>:	lw	ra,28(sp)0x00400dac <+64>:	lw	s8,24(sp)0x00400db0 <+68>:	addiu	sp,sp,320x00400db4 <+72>:	jr	ra0x00400db8 <+76>:	nop

炸弹一总结

爆炸点1: 比较输入字符串是否与 "Let′sbeginnow!""Let's\ begin\ now!""Lets begin now!" 是否相等,不相等就 BOOM!BOOM!BOOM!

phase_2

   0x00400dbc <+0>:		addiu	sp,sp,-640x00400dc0 <+4>:		sw	ra,60(sp)0x00400dc4 <+8>:		sw	s8,56(sp)0x00400dc8 <+12>:	move	s8,sp0x00400dcc <+16>:	lui	gp,0x420x00400dd0 <+20>:	addiu	gp,gp,-200800x00400dd4 <+24>:	sw	gp,16(sp)0x00400dd8 <+28>:	sw	a0,64(s8)0x00400ddc <+32>:	addiu	v0,s8,280x00400de0 <+36>:	lw	a0,64(s8)0x00400de4 <+40>:	move	a1,v00x00400de8 <+44>:	jal	0x401ba8 <read_six_numbers>0x00400dec <+48>:	nop0x00400df0 <+52>:	lw	gp,16(s8)0x00400df4 <+56>:	lw	v1,28(s8)0x00400df8 <+60>:	li	v0,10x00400dfc <+64>:	beq	v1,v0,0x400e10 <phase_2+84>0x00400e00 <+68>:	nop0x00400e04 <+72>:	jal	0x4021f0 <explode_bomb>

首先,我们找到这道题的切入点:

   0x00400de8 <+44>:	jal	0x401ba8 <read_six_numbers>

read_six_numbers(),很明显,这是在告诉我们需要输入 6 个数,那我们随便输入 6 个数:2 4 7 9 15 20。(注意:若输入的不是 6 个数会直接爆炸的,这是我偶然试错试出来的!)
做个记录:炸弹1:输入的数字个数必须是 6 个,否则爆炸!

接着往下,

   0x00400dfc <+64>:	beq	v1,v0,0x400e10 <phase_2+84>

将寄存器 v0v0v0 与寄存器 v1v1v1 进行了比较,那么我们可以使用 p $v0p $v1 来查看一下两个寄存器中的值:
在这里插入图片描述
发现 v0v0v0 中存正好是我们输入的第一个 2,这会不会是巧合呢?我们向上查找,有这么一句:

   0x00400df4 <+56>:	lw	v1,28(s8)	//m[$s8+28] = v1

v1v1v1 中的值是从内存中来的,如果我们的猜想正确的话,继续往后读 7 个数:
在这里插入图片描述
前 6 个是我们输入的数,第 7 个数由于我们没有输入,因此是无效数据,这就说明我们的猜想是正确的,内存 m[m[m[s8+28]$ 中存着我们输入的第一个数,并使用了 load 访存将它放入了寄存器 v1v1v1 中。

结合着这句话:

   0x00400dfc <+64>:	beq	v1,v0,0x400e10 <phase_2+84>

意识到是要将我们输入的第一个数与 1 作比较,相等则跳转到 phase_2+84 行去(也就避开了炸弹咯~),否则炸弹就爆炸了!

做个记录:炸弹2:第一个数必须是1,否则爆炸!

接下来循环开始,大家根据注释食用哈~

   0x00400e08 <+76>:	nop0x00400e0c <+80>:	lw	gp,16(s8)0x00400e10 <+84>:	li	v0,1	//循环变量,相当于 for 循环中的 i=10x00400e14 <+88>:	sw	v0,24(s8)	//m[$s8+24] = $v0,将该循环变量入栈0x00400e18 <+92>:	b	0x400ea8 <phase_2+236>	//循环开始,跳转到 phase_2+236 的位置进行条件判断0x00400e1c <+96>:	nop0x00400e20 <+100>:	lw	v0,24(s8)	//$v0 = m[$s8+24],取出栈中之前存好的 i,即v0=10x00400e24 <+104>:	nop0x00400e28 <+108>:	addiu	v0,v0,-1	//$v0--0x00400e2c <+112>:	sll	v0,v0,0x2	//左移操作,相当于 $v0 = $v0 * 4,把它变为int型变量的长度0x00400e30 <+116>:	addiu	v1,s8,24	//$s8+24 相当于到达了之前存循环变量 i 的地址0x00400e34 <+120>:	addu	v0,v1,v0	//$s8 + 24 + $v0 * 4,到达要取的数之前 4 个字节的位置(这一步看着有点奇怪,为什么不一步到位呢?我也是根据下一句才推出来的)0x00400e38 <+124>:	lw	a0,4(v0)	//a0 = m[$v0+4],将我们输入的第 i 个数存入 $a0 中0x00400e3c <+128>:	li	v1,12	//根据后面的0x00400e40 <+132>:	lw	v0,24(s8)	//$v0 = m[$s8+24],将之前的循环变量 i 读入0x00400e44 <+136>:	nop0x00400e48 <+140>:	subu	v0,v1,v0	//$v0 = $v1(12) - $v0(i)0x00400e4c <+144>:	lw	v1,-32660(gp)	//读取输入的学号0x00400e50 <+148>:	sll	v0,v0,0x2	//左移操作,将 $v0 变为int型变量的长度 0x00400e54 <+152>:	addu	v0,v1,v0	//$v0 = $v1 + $v0,到达了输入学号的倒数第 i 位的位置,具体可以使用 x $v0 进行查看0x00400e58 <+156>:	lw	v0,0(v0)	//$v0 = m[$v0],将学号的倒数第 i 为存入 $v00x00400e5c <+160>:	nop0x00400e60 <+164>:	mult	a0,v00x00400e64 <+168>:	mflo	a0	//上一句将 $a0 中的值与 $v0 中的值相乘,此句意为将结果存入 $a0 中0x00400e68 <+172>:	lw	v0,24(s8)	//$v0 = m[$s8+24],取出之前的循环变量 i0x00400e6c <+176>:	nop0x00400e70 <+180>:	sll	v0,v0,0x2	//将 i 变为int型变量的长度0x00400e74 <+184>:	addiu	v1,s8,240x00400e78 <+188>:	addu	v0,v1,v0	//$s8 + 24 + $v0 * 40x00400e7c <+192>:	lw	v0,4(v0)	//这里与<+116>处类似,但要注意的是,上面的 $v0 是自减之后,那时得到的是输入的第 i 个数;在这里,$v0 = i,没有自减,因此取得的是第 i + 1 个数!//<+192>有点难以想到~0x00400e80 <+196>:	nop0x00400e84 <+200>:	beq	a0,v0,0x400e98 <phase_2+220>	//比较乘积项 a0 与 输入的第 i+1 个数 v0 是否相等//是则跳转到 phase_2+220 处咯,避开了炸弹;否则就爆炸啦!0x00400e88 <+204>:	nop0x00400e8c <+208>:	jal	0x4021f0 <explode_bomb>0x00400e90 <+212>:	nop0x00400e94 <+216>:	lw	gp,16(s8)	//末尾循环体0x00400e98 <+220>:	lw	v0,24(s8)	//取得之前的循环变量 i0x00400e9c <+224>:	nop0x00400ea0 <+228>:	addiu	v0,v0,1	//相当于 i++ 啦0x00400ea4 <+232>:	sw	v0,24(s8)	//m[$s8+24] = v0,存入之前放置循环变量的位置0x00400ea8 <+236>:	lw	v0,24(s8)	//第一次条件判断是从 phase_2+92 跳转下来的哈~0x00400eac <+240>:	nop0x00400eb0 <+244>:	slti	v0,v0,6	//循环体条件判断,i < 6 ?0x00400eb4 <+248>:	bnez	v0,0x400e20 <phase_2+100>	//为1则跳转到 phase_2+100 处咯,继续循环体;否则就往下执行了~0x00400eb8 <+252>:	nop	//下面就是归还空间,跳回主函数啦~//Congratulations!你坚持了下来!0x00400ebc <+256>:	move	sp,s80x00400ec0 <+260>:	lw	ra,60(sp)0x00400ec4 <+264>:	lw	s8,56(sp)0x00400ec8 <+268>:	addiu	sp,sp,640x00400ecc <+272>:	jr	ra0x00400ed0 <+276>:	nop

做个记录:炸弹3:第 i 个输入数据与学号的倒数第 i 位相乘,结果若不与第 i + 1 个输入数据相同则爆炸!

炸弹二总结

爆炸点1: 输入的数字个数必须是 6 个,否则爆炸!
爆炸点2: 第一个数必须是1,否则爆炸!
爆炸点3: 第 i 个输入数据与学号的倒数第 i 位相乘,结果若不与第 i + 1 个输入数据相同则爆炸!

phase_3

首先,从 bomb.s 文件中 phase_3 前面的 $LC13 中可以看到输入的格式为 "%d%s%d""\%d\ \%s\ \%d""%d %s %d",即整数+字符+整数的格式。

接着往下:

   0x00400ed4 <+0>:		addiu	sp,sp,-560x00400ed8 <+4>:		sw	ra,52(sp)0x00400edc <+8>:		sw	s8,48(sp)0x00400ee0 <+12>:	move	s8,sp	//为程序留出空间0x00400ee4 <+16>:	lui	gp,0x420x00400ee8 <+20>:	addiu	gp,gp,-200800x00400eec <+24>:	sw	gp,24(sp)0x00400ef0 <+28>:	sw	a0,56(s8)0x00400ef4 <+32>:	lw	a0,56(s8)0x00400ef8 <+36>:	lui	v0,0x400x00400efc <+40>:	addiu	a1,v0,101120x00400f00 <+44>:	addiu	v1,s8,440x00400f04 <+48>:	addiu	v0,s8,400x00400f08 <+52>:	addiu	a2,s8,360x00400f0c <+56>:	sw	a2,16(sp)0x00400f10 <+60>:	move	a2,v10x00400f14 <+64>:	move	a3,v00x00400f18 <+68>:	lw	v0,-32636(gp)	//从 bomb.s 中可以得到这里调用了 scanf() 函数0x00400f1c <+72>:	nop0x00400f20 <+76>:	move	t9,v00x00400f24 <+80>:	jalr	t9	//这一步干了啥???

根据注释可以看出,以上的汇编主要是为了留出空间,调用 scanf() 输入,以及一个不知所踪的跳转(以至于在 GDB 中单步调试无法访存,根据后面盲猜是计算参数个数???)

马上就要遇见炸弹了,但还不知道 phase_3 是干嘛的,有点慌!!!!

不过往下看:

   0x00400f28 <+84>:	nop0x00400f2c <+88>:	lw	gp,24(s8)0x00400f30 <+92>:	slti	v0,v0,30x00400f34 <+96>:	beqz	v0,0x400f48 <phase_3+116>0x00400f38 <+100>:	nop0x00400f3c <+104>:	jal	0x4021f0 <explode_bomb>0x00400f40 <+108>:	nop

似乎这个炸弹是根据比较来进行是否爆炸的,跟谁比呢?<+92> 有语句 slti v0, v0, 3,说明是将 v0v0v0 的值与 3 进行了比较,那么 v0v0v0 又是什么呢?由于前面知道了输入应该是整数 + 字符 + 整数,因此此时查看 v0v0v0 的值是3:
在这里插入图片描述
嘶!!!这之前只进行了栈分配,输入,并没有出现 3 吖!栈分配无从下手,那就从输入下手!

没有输入3,却出现了 3,而我输入的是三个参数,会不会 v0v0v0 里面存的就是参数个数呢?为此,不妨改变输入试试!

我将输入改为只输入 2 个数,发现:
在这里插入图片描述
果然变成了 2,不妨输入 1 个参数试试,是变成了 1 的。因此,此时的比较就是判断我们输入的参数是不是小于 3 个,是则爆炸!(话说回来,这样的判断确实有点勉强,不过在猜想 + 试错的策略下确实可以解决一些问题哒!)此时小于的比较结果为 0(即参数个数 > 3,注意 beqz 和 benz区别!)就跳转,即避开了炸弹啦~

做个记录:爆炸点1:输入参数的个数若小于 3,则爆炸!

跳转过后,从 phase_3+116 看起:

   0x00400f44 <+112>:	lw	gp,24(s8)0x00400f48 <+116>:	lw	v0,44(s8)0x00400f4c <+120>:	nop0x00400f50 <+124>:	sltiu	v1,v0,80x00400f54 <+128>:	beqz	v1,0x401190 <phase_3+700>0x00400f58 <+132>:	nop<phase_3+700>0x00401190 <+700>:	li	v0,1200x00401194 <+704>:	sb	v0,32(s8)0x00401198 <+708>:	jal	0x4021f0 <explode_bomb>0x0040119c <+712>:	nop

这段汇编 load 了一些数据,gpgpgp 是一个全局指针,无从下手;v0v0v0 是一个寄存器,后面使用了它来进行比较,那么我们就先看看 v0v0v0 里面存的是什么:
在这里插入图片描述
可以看到,$s8+36 ~ $s8+44 分别存储着我们输入的三个参数 102, ‘t’, 5。(其中字符表示为ascii码,且数据均为 16 进制)

那么,phase_3+124 的比较语句也就是为了去比较我们输入的第一个参数是否小于 8。若比较结果不小于 8,则 $v1 = 1,不发生跳转;否则跳转至 phase_3+700 的位置,炸弹爆炸!

做个记录:爆炸点2:第一个参数若大于 8 ,则炸弹爆炸!

接下来是一个根据第一个参数来进行 switch…case… 的过程,大家根据注释食用:

   0x00400f5c <+136>:	sll	v1,v0,0x2	//将第一个输入参数 $v0 变为int型变量的长度0x00400f60 <+140>:	lui	v0,0x400x00400f64 <+144>:	addiu	v0,v0,101240x00400f68 <+148>:	addu	v0,v1,v00x00400f6c <+152>:	lw	v0,0(v0)	//以上三句是为了找到 switch...case... 语句中我们应该跳转到的子句的地址,并保存在 $v0 中//可以使用 x $v0 来进行查看跳转的地址0x00400f70 <+156>:	nop0x00400f74 <+160>:	jr	v0	//跳转到对应的 switch...case... 语句0x00400f78 <+164>:	nop

在这里插入图片描述
我现在输入的第一个参数是 5,因此跳转到 phase_3+504 的位置。

接下来是相应的一些 case 语句,下面就来说说 switch…case… 语句中的相关操作,我们就以 case 5 为例(从上面可以看到跳转到了 <+504> 的位置哈~):

   0x004010cc <+504>:	li	v0,116	//$v0 = 1160x004010d0 <+508>:	sb	v0,32(s8)	//m[$s8+32]=$v0=1160x004010d4 <+512>:	lw	v0,-32660(gp)0x004010d8 <+516>:	nop

首先,将立即数 116 存入了内存之中;

  0x004010dc <+520>:	lw	v1,44(v0)0x004010e0 <+524>:	lw	v0,36(s8)0x004010e4 <+528>:	nop0x004010e8 <+532>:	mult	v1,v00x004010ec <+536>:	mflo	v10x004010f0 <+540>:	li	v0,5130x004010f4 <+544>:	beq	v1,v0,0x4011e8 <phase_3+788>0x004010f8 <+548>:	nop0x004010fc <+552>:	jal	0x4021f0 <explode_bomb>0x00401100 <+556>:	nop

在这段汇编中,我们先查看 v1v1v1 中存的是什么,根据前面的经验以及不断试错可以发现,将全局指针 $gp-32660 给到 $v0,并读取 $v0+44 的数据时,我们读取到的是输入的学号的最后一位(假设是 7)。

同时,前面说过,$s8+36 的位置存储着我们输入的第三个参数(我输入的是102),现在将它加载到了 $v0 中。

接下来,根据 $v1 = $v1 * $v0 可知,这一句是将学号的最后一位和我们输入的第三个参数相乘(这里结果位714)。

接着,将 513 存入了 $v0 寄存器中,然后将我们相乘的结果和 $v0 进行了比较。若相等,则发生跳转,也就避开炸弹啦!否则炸弹爆炸!(显然 513 ≠ 714,炸弹爆炸!)

做个记录:爆炸点3:学号的最后一位与输入的第三个数据相乘,然后与不同 case 中的数字进行比较,若不相等则爆炸!

到此已经避开 3 个炸弹啦,别急!后面还有!同时,在这里做一个内存记录:内存中 $s8 + 32 的地方存的是 116 哈,忘了请看 phase_3+504 的位置!

   0x004011e8 <+788>:	nop0x004011ec <+792>:	b	0x4011f8 <phase_3+804>0x004011f0 <+796>:	nop0x004011f4 <+800>:	nop0x004011f8 <+804>:	lb	v0,40(s8)0x004011fc <+808>:	lb	v1,32(s8)0x00401200 <+812>:	nop0x00401204 <+816>:	beq	v1,v0,0x401218 <phase_3+836>0x00401208 <+820>:	nop0x0040120c <+824>:	jal	0x4021f0 <explode_bomb>0x00401210 <+828>:	nop0x00401214 <+832>:	lw	gp,24(s8)0x00401218 <+836>:	move	sp,s80x0040121c <+840>:	lw	ra,52(sp)0x00401220 <+844>:	lw	s8,48(sp)0x00401224 <+848>:	addiu	sp,sp,560x00401228 <+852>:	jr	ra0x0040122c <+856>:	nop

经过连续跳转,我们到了 <+804> 的位置,首先 $s8+40 存着我们输入的第二个字符参数,这里我输入的是 ‘t’,将它取到 $v0 中;

接着,根据刚刚的内存分析,$s8+32 存的是 116,将它取到 $v1 中;

下面就开始比较了,由于我们输入的第二个参数是字符,因此这是一个字符的比较。走到这里,实际上,我们之前的 116 不妨去查一查,其实是字符 ‘t’!因此这里是比较 case 里面设置的字符是不是和我们输入的第二个字符参数相等。若相等,跳转到 <+836> 避开炸弹啦!否则炸弹爆炸!

做个记录:爆炸点4:每个 case 里面设置了一个字符,若我们输入的第二个字符不与这个字符相等,则炸弹爆炸!

接下来,就请看注释了哈~。case 很多,所以注释也很多,其实看懂了上面的内容,所有 case 都是一个套路了!

炸弹三总结

爆炸点1: 输入参数的个数若小于 3,则爆炸!
爆炸点2: 第一个参数若大于 8 ,则炸弹爆炸!
爆炸点3: 学号的最后一位与输入的第三个数据相乘,然后与不同 case 中的数字进行比较,若不相等则爆炸!
爆炸点4: 每个 case 里面设置了一个字符,若我们输入的第二个字符不与这个字符相等,则炸弹爆炸!

phase_4

phase_4 是一个递归,由 phase_4 之前的 $LC14 发现我们输入的形式是 %d\%d%d 即一个整数。首先我们看调用递归函数之前的部分:

先看第一个炸弹怎样才能避开呢?

   0x004012bc <+0>:		addiu	sp,sp,-400x004012c0 <+4>:		sw	ra,36(sp)0x004012c4 <+8>:		sw	s8,32(sp)0x004012c8 <+12>:	move	s8,sp0x004012cc <+16>:	lui	gp,0x420x004012d0 <+20>:	addiu	gp,gp,-200800x004012d4 <+24>:	sw	gp,16(sp)0x004012d8 <+28>:	sw	a0,40(s8)0x004012dc <+32>:	lw	v1,40(s8)0x004012e0 <+36>:	lui	v0,0x400x004012e4 <+40>:	addiu	v0,v0,101560x004012e8 <+44>:	move	a0,v10x004012ec <+48>:	move	a1,v00x004012f0 <+52>:	addiu	v0,s8,240x004012f4 <+56>:	move	a2,v0	//前面是为了给函数申请空间0x004012f8 <+60>:	lw	v0,-32636(gp)	//调用输入函数进行输入0x004012fc <+64>:	nop0x00401300 <+68>:	move	t9,v00x00401304 <+72>:	jalr	t9	//去判断是否有输入???有输入返回1,无输入返回00x00401308 <+76>:	nop0x0040130c <+80>:	lw	gp,16(s8)
--Type <RET> for more, q to quit, c to continue without paging--c0x00401310 <+84>:	move	v1,v00x00401314 <+88>:	li	v0,1	//由于输入一个整数,这里就是去判断是否有输入0x00401318 <+92>:	bne	v1,v0,0x401330 <phase_4+116>	//与 1 进行比较,有输入则继续,五输入则跳转至 <+116>,炸弹爆炸!0x0040131c <+96>:	nop

在 phase_3 中,类似于 jalr t9 这样的指令是去计算参数个数,但在此处似乎不太一样。无论我的输入是两个数还是两位数,$v0 都返回 1;同时我们必须输入一个数。因此在这里我认为是为了判断有无输入了~

做个记录:爆炸点1:判断有无输入,直到有输入!

接着往下看:

   0x00401334 <+120>:	nop0x00401338 <+124>:	lw	gp,16(s8)0x0040133c <+128>:	nop0x00401340 <+132>:	lw	v0,-32660(gp)0x00401344 <+136>:	nop0x00401348 <+140>:	lw	v0,44(v0)0x0040134c <+144>:	nop

首先,我们在 phase_3 的分析中说过,全局指针 $gp-32660 之后,再访问其 +44 后的内存存储着我们输入的学号的最后一位,所以这一段意为取出输入的学号的最后一位到 $v0 中去。

下面这段汇编进行了一个与运算:

   0x00401350 <+148>:	andi	v0,v0,0x10x00401354 <+152>:	andi	v0,v0,0xff0x00401358 <+156>:	beqz	v0,0x40139c <phase_4+224>0x0040135c <+160>:	nop

前两句对学号的最后一位 $v0 进行了两个与运算以判断奇偶,举个例子:
(5)10=(00000101)2(5)_{10}=(00000101)_2(5)10=(00000101)2将它与 0x1 进行与运算∵0x1=(00000001)2∴(00000101)2&(00000001)2=(00000001)2\because 0x1=(00000001)_2 \\ \therefore (00000101)_2\ \ \& \ \ (00000001)_2=(00000001)_20x1=(00000001)2(00000101)2  &  (00000001)2=(00000001)2表示 5 是一个奇数。(这里我认为可以仅和 0x1 进行与运算就可以了,后面的再与 0xff 进行与运算没搞懂???)

然后便根据奇偶检验结果进行跳转:若为 1(表示学号最后一位为奇数),继续进行;若为 0(表示学号最后一位为偶数,发生跳转)。

假设这里我们输入的学号的最后一位为 7,那么继续进行!

往下看:

   0x0040135c <+160>:	nop0x00401360 <+164>:	lw	v0,24(s8)0x00401364 <+168>:	nop0x00401368 <+172>:	move	a0,v00x0040136c <+176>:	jal	0x401230 <func4>0x00401370 <+180>:	nop0x00401374 <+184>:	lw	gp,16(s8)0x00401378 <+188>:	move	v1,v00x0040137c <+192>:	li	v0,80x00401380 <+196>:	beq	v1,v0,0x4013d0 <phase_4+276>0x00401384 <+200>:	nop0x00401388 <+204>:	jal	0x4021f0 <explode_bomb>0x0040138c <+208>:	nop

首先,load 进了一个数,通过在 GDB 中使用 p $v0 可以看到,此时 $v0 中的数是我们输入的那个数。
在这里插入图片描述
接着,我们将 $v0 中的值(即56)传入了寄存器 $a0 中,然后便进入递归函数 func4 了。下面我们就去看看这个递归函数干了什么!(大家根据注释食用哈~)

   0x00401230 <+0>:		addiu	sp,sp,-400x00401234 <+4>:		sw	ra,36(sp)0x00401238 <+8>:		sw	s8,32(sp)0x0040123c <+12>:	sw	s0,28(sp)0x00401240 <+16>:	move	s8,sp	//栈空间申请0x00401244 <+20>:	sw	a0,40(s8)	//m[$s8+40]=$a0=56,这个 $a0 相当于传入的参数,在 phase_4 跳转之前已经设置为我们输入的数 56 啦!0x00401248 <+24>:	lw	v0,40(s8)	//$v0=m[$s8+40],将我们传入的参数给 $v00x0040124c <+28>:	nop0x00401250 <+32>:	slti	v0,v0,2	//比较这个参数是否小于 20x00401254 <+36>:	bnez	v0,0x40129c <func4+108>	//若小于 2,$v0=1,跳转到 <+108> 的地方去;反之则继续!//先到 <+108> 看看发生了什么哈~0x00401258 <+40>:	nop0x0040125c <+44>:	lw	v0,40(s8)	//$v0=m[$s8+40]=56,将之前存在内存中的参数给到 $v00x00401260 <+48>:	nop0x00401264 <+52>:	addiu	v0,v0,-1	//$v0--;相当于 $v0 = 55了0x00401268 <+56>:	move	a0,v0	//$a0=$v0=550x0040126c <+60>:	jal	0x401230 <func4>	//再次执行函数,就是递归调用了//仔细看,这里传入的参数是 55,联系函数开始时是通过 $a0 传参的,这一切就说得通了//这一步相当于是递归调用 f(x-1)!0x00401270 <+64>:	nop0x00401274 <+68>:	move	s0,v0	//$v0 是函数的返回值,因此这里就是 f(x-1) 函数执行后的返回值//若在这一步在 GDB 中进行调试,会有大量continue需要输入,因此是个递归函数hhh0x00401278 <+72>:	lw	v0,40(s8)	//$v0=m[$s8+40]=56,同样将之前存在内存中的参数给到 $v00x0040127c <+76>:	nop0x00401280 <+80>:	addiu	v0,v0,-2	//$v0 -= 2; $v0 自减2
--Type <RET> for more, q to quit, c to continue without paging--c0x00401284 <+84>:	move	a0,v0	//又是一个传参0x00401288 <+88>:	jal	0x401230 <func4>	//再次执行函数,相当于递归调用 f(x-2) 啦~0x0040128c <+92>:	nop0x00401290 <+96>:	addu	v0,s0,v0	//此时 $s0 中是 f(x-1) 的返回值, $v0 是 f(x-2) 的返回值,二者相加返回给 $v00x00401294 <+100>:	b	0x4012a0 <func4+112>	//跳转到 <+112> 归还空间啦~0x00401298 <+104>:	nop0x0040129c <+108>:	li	v0,1	//$v0=1,下面就是归还栈空间了。这就是递归的终止条件//if($v0 < 2) return 1;0x004012a0 <+112>:	move	sp,s80x004012a4 <+116>:	lw	ra,36(sp)0x004012a8 <+120>:	lw	s8,32(sp)0x004012ac <+124>:	lw	s0,28(sp)0x004012b0 <+128>:	addiu	sp,sp,400x004012b4 <+132>:	jr	ra0x004012b8 <+136>:	nop

因此,这个递归函数实质上就是:

if (x < 2)return 1;
elsereturn f(x-1) + f(x-2);

这个递归函数就是一个求斐波那契数列的值,得到了递归函数的返回值并保存在 $v0 中,我们继续看下面的汇编:(根据注释食用哈!)

   0x00401370 <+180>:	nop0x00401374 <+184>:	lw	gp,16(s8)0x00401378 <+188>:	move	v1,v0	//将函数调用的返回值给到 $v10x0040137c <+192>:	li	v0,8	//$v0 = 80x00401380 <+196>:	beq	v1,v0,0x4013d0 <phase_4+276>	//比较 $v1 和 $v0 中的值,即函数返回值是否等于 8,相等则跳转,避开了炸弹;否则炸弹爆炸啦!0x00401384 <+200>:	nop0x00401388 <+204>:	jal	0x4021f0 <explode_bomb>0x0040138c <+208>:	nop0x00401390 <+212>:	lw	gp,16(s8)0x00401394 <+216>:	b	0x4013d0 <phase_4+276>0x00401398 <+220>:	nop...0x004013d0 <+276>:	move	sp,s8	//跳转到 <+276>,就是简单归还空间了!0x004013d4 <+280>:	lw	ra,36(sp)0x004013d8 <+284>:	lw	s8,32(sp)0x004013dc <+288>:	addiu	sp,sp,400x004013e0 <+292>:	jr	ra0x004013e4 <+296>:	nop

以上是奇数的情况,偶数的情况相同,只是对递归函数返回值的要求不同,我们直接看需要比较的值:

...0x004013ac <+240>:	nop0x004013b0 <+244>:	lw	gp,16(s8)0x004013b4 <+248>:	move	v1,v00x004013b8 <+252>:	li	v0,130x004013bc <+256>:	beq	v1,v0,0x4013d0 <phase_4+276>0x004013c0 <+260>:	nop
...

是 13!

因此,为了避开这个炸弹,就要使我们的斐波那契数列 f(input)f(input)f(input) 的返回值等于相应的值:
Inorderavoidthebomb,then{f(input)=8,if学号最后一位是奇数f(input)=13,if学号最后一位是偶数In\ order\ avoid\ the\ bomb, then\left\{ \begin{aligned} &f(input) = 8, &if \text{学号最后一位是奇数}\\ &f(input) = 13, &if \text{学号最后一位是偶数} \end{aligned} \right.In order avoid the bomb,then{f(input)=8,f(input)=13,if学号最后一位是奇数if学号最后一位是偶数所以,奇数输入 5,偶数输入 6,成功啦!

做个记录:爆炸点2:输入n,斐波那契求值得到 f(n)。根据学号最后一位的奇偶性来判断 f(n) 是否与目标值相等。不相等则爆炸!

炸弹4总结

爆炸点1: 判断有无输入;
爆炸点2: 输入n,斐波那契求值得到 f(n)。根据学号最后一位的奇偶性来判断 f(n) 是否与目标值相等。不相等则爆炸!

phase_5

首先,说个题外话,在看这道题之前我们看怎样输入时,发现 $LC15 那儿有一个 “giants”,不是平常的 %d\%d%d 什么的。莫非…就是输入“giants”?尝试输入了一下,似乎并不是hhh

直接看汇编吧!(第一个炸弹之前)

   0x004013e8 <+0>:		addiu	sp,sp,-720x004013ec <+4>:		sw	ra,68(sp)0x004013f0 <+8>:		sw	s8,64(sp)0x004013f4 <+12>:	move	s8,sp	//申请空间0x004013f8 <+16>:	sw	a0,72(s8)0x004013fc <+20>:	lw	a0,72(s8)0x00401400 <+24>:	jal	0x401c78 <string_length>0x00401404 <+28>:	nop0x00401408 <+32>:	move	v1,v00x0040140c <+36>:	li	v0,60x00401410 <+40>:	beq	v1,v0,0x401420 <phase_5+56>0x00401414 <+44>:	nop0x00401418 <+48>:	jal	0x4021f0 <explode_bomb>0x0040141c <+52>:	nop

前四句,常规申请空间操作。接下来存了又读了个啥?(我随便输入了“youbom”)在 GDB 中看看:
在这里插入图片描述
可以看到,$a0 里面存入的就是我们输入的字符串,

...0x004013f8 <+16>:	sw	a0,72(s8)0x004013fc <+20>:	lw	a0,72(s8)
...

这两句将它存入了 m[$s8+72] 的位置。
接下来发生了跳转:

...0x00401400 <+24>:	jal	0x401c78 <string_length>
...

顾名思义,这里跳转到了计算输入字符串的长度,并将返回结果存入了专门存储返回值的寄存器 $v0 中。接下来比较了字符串的长度是否为 6,否则炸弹爆炸!(请看下面注释了)

   0x00401400 <+24>:	jal	0x401c78 <string_length>	//字符串长度,存储在 $v0 中0x00401404 <+28>:	nop0x00401408 <+32>:	move	v1,v0	//$v1=$v0,将长度放入 $v0 中0x0040140c <+36>:	li	v0,6	//$v0=60x00401410 <+40>:	beq	v1,v0,0x401420 <phase_5+56>	//比较字符串长度是否为6,是则跳转,就避开炸弹啦;否则炸弹爆炸!0x00401414 <+44>:	nop0x00401418 <+48>:	jal	0x4021f0 <explode_bomb>0x0040141c <+52>:	nop

做个记录:爆炸点1:输入的字符串长度若不为 6,炸弹爆炸!

接着往下,

   0x00401420 <+56>:	sw	zero,24(s8)	//m[$s8+24]=0,初始化循环变量 i = 00x00401424 <+60>:	b	0x4014a8 <phase_5+192>	//跳转至条件判断,下面请看 <+192> 了哈!0x00401428 <+64>:	nop0x0040142c <+68>:	lw	v0,24(s8)	//$v0=m[$s8+24],拿到之前的循环变量0x00401430 <+72>:	lw	v1,24(s8)	//$v1=m[$s8+24],拿到之前的循环变量0x00401434 <+76>:	lw	a0,72(s8)	//$a0=m[$s8+72],拿到之前的字符串!
--Type <RET> for more, q to quit, c to continue without paging--c0x00401438 <+80>:	nop0x0040143c <+84>:	addu	v1,a0,v1	//到达字符串中的第 i+1 个字符,注意 i 是从 0 开始的哈!第一次拿到第 1 个字符,后面以此类推~0x00401440 <+88>:	lb	v1,0(v1)	//拿到对应的第 i+1 个字符0x00401444 <+92>:	nop0x00401448 <+96>:	andi	v1,v1,0xff0x0040144c <+100>:	andi	v1,v1,0xf	//取得字符所表示的二进制数的后四位//比如:第一个字符‘y’,其ASCII码的十六进制表示为:0x79(01111001),后四位:10010x00401450 <+104>:	sll	v0,v0,0x2	//将 $v0 中的值变为int型变量的长度0x00401454 <+108>:	addiu	a0,s8,24	//到达之前存储循环变量 i 的地址0x00401458 <+112>:	addu	v0,a0,v0	//从这个地址向后移 4 位0x0040145c <+116>:	sw	v1,12(v0)	//将字符的后四位存储在 m[$s8 + 24 + 12 + i * 4] 的位置
...0x004014a8 <+192>:	lw	v0,24(s8)	//拿到之前存储的循环变量i0x004014ac <+196>:	nop0x004014b0 <+200>:	slti	v0,v0,6	//循环条件判断,i < 6 ?0x004014b4 <+204>:	bnez	v0,0x40142c <phase_5+68>	//不为 0 则跳转//若 i < 6, $v0 = 1,跳转至循环体部分;若 i >= 6, $v0 = 0,循环结束,继续执行。0x004014b8 <+208>:	nop

以上的汇编目的就是对我们输入的字符串的每一个字符,取 ASCII 码的后四位并保存。

紧接着:

   0x00401460 <+120>:	lw	a0,24(s8)	//取得之前的循环变量 i0x00401464 <+124>:	lw	v0,24(s8)	//取得之前的循环变量 i0x00401468 <+128>:	nop0x0040146c <+132>:	sll	v0,v0,0x2	//将 i 变为int型长度0x00401470 <+136>:	addiu	v1,s8,24	//读得 i 的地址0x00401474 <+140>:	addu	v0,v1,v00x00401478 <+144>:	lw	v1,12(v0)	//回想之前存字符后四位的地方,这里就是取得字符的后四位,存到 $v1 中0x0040147c <+148>:	lui	v0,0x410x00401480 <+152>:	addiu	v0,v0,125240x00401484 <+156>:	addu	v0,v1,v00x00401488 <+160>:	lb	v1,0(v0)0x0040148c <+164>:	addiu	v0,s8,240x00401490 <+168>:	addu	v0,v0,a00x00401494 <+172>:	sb	v1,4(v0)

看到 <+148> 时,有点懵,这是干了啥?4 句过后,又是去访问循环变量的地址。因此,我们在这一句设断点看看发生了啥。
在这里插入图片描述
这个 +9 提示了我们,莫非前面还有字符?我们使用 x/s $v0-9 发现:
在这里插入图片描述
这个地址存的就是这样一个字符串。现在我们回到那 4 句汇编:

   0x00401478 <+144>:	lw	v1,12(v0)0x0040147c <+148>:	lui	v0,0x410x00401480 <+152>:	addiu	v0,v0,12524	//找到这个内置字符串的开头地址0x00401484 <+156>:	addu	v0,v1,v0	//从开头地址往后移动 $v0 位//$v0 是什么?字符的后四位。我们第一个输入的字符是 ‘y’,后四位 1001,也就是 9。//我们在第一次使用 x/s $v0 输出的时候,输出就是从第 9 位开始的!//说明我们字符的后四位就是为了在这个内置字符串中找到相应的字符!0x00401488 <+160>:	lb	v1,0(v0)	//为了验证上面的猜想,我们在这里输出 $v1 试试

在这里插入图片描述
发现就是 ‘b’。那么基本思路就有了:首先根据我们输入的字符串中的每个字符取 ASCII 码的后四位;再根据这后四位再内置字符串中找到对应的字符(举个栗子:见下面的表格);接着…(往后看吧)

‘y’‘o’‘u’‘b’‘o’‘m’
后四位100111110101001011111101
十进制915521513
“isrveawhobpnutfg”‘b’‘g’‘a’‘r’‘g’‘t’

有了这个字符串:接着往下:

   0x00401488 <+160>:	lb	v1,0(v0)0x0040148c <+164>:	addiu	v0,s8,240x00401490 <+168>:	addu	v0,v0,a00x00401494 <+172>:	sb	v1,4(v0)	//将它们存入 m[$s8 + 28 + i]的位置

Going on:

   0x004014b8 <+208>:	nop0x004014bc <+212>:	sb	zero,34(s8)	//字符串的尾部置空 '\0'0x004014c0 <+216>:	addiu	v0,s8,280x004014c4 <+220>:	move	a0,v0	//这两句到达之前存经过映射后的字符串('bgargt')的位置0x004014c8 <+224>:	lui	v0,0x400x004014cc <+228>:	addiu	a1,v0,10160	//另一个比较的字符串,GDB 查看查看0x004014d0 <+232>:	jal	0x401cf8 <strings_not_equal>0x004014d4 <+236>:	nop

在这里插入图片描述
居然是 “giants”,之前差点当作输入!破案了,就是要通过映射将输入映射为 “giants” !

做个记录:爆炸点2:通过内置字符串映射,若结果为“giants”,则通过;否则炸弹爆炸!

下面就看注释了哈!

   0x004014d8 <+240>:	beqz	v0,0x4014e8 <phase_5+256>	//比较结果//注意!string_NOT_equal,若相等返回的是 0,而不是 10x004014dc <+244>:	nop0x004014e0 <+248>:	jal	0x4021f0 <explode_bomb>0x004014e4 <+252>:	nop0x004014e8 <+256>:	move	sp,s8	//归还空间,拆弹结束!0x004014ec <+260>:	lw	ra,68(sp)0x004014f0 <+264>:	lw	s8,64(sp)0x004014f4 <+268>:	addiu	sp,sp,720x004014f8 <+272>:	jr	ra0x004014fc <+276>:	nop

炸弹五总结

爆炸点1: 输入的字符串长度若不为 6,炸弹爆炸!
爆炸点2: 通过内置字符串映射,若结果为“giants”,则通过;否则炸弹爆炸!

phase_6

   0x00401500 <+0>:		addiu	sp,sp,-960x00401504 <+4>:		sw	ra,92(sp)0x00401508 <+8>:		sw	s8,88(sp)0x0040150c <+12>:	move	s8,sp0x00401510 <+16>:	lui	gp,0x420x00401514 <+20>:	addiu	gp,gp,-200800x00401518 <+24>:	sw	gp,16(sp)0x0040151c <+28>:	sw	a0,96(s8)0x00401520 <+32>:	lui	v0,0x410x00401524 <+36>:	addiu	v0,v0,125920x00401528 <+40>:	sw	v0,32(s8)0x0040152c <+44>:	addiu	v0,s8,360x00401530 <+48>:	lw	a0,96(s8)0x00401534 <+52>:	move	a1,v00x00401538 <+56>:	jal	0x401ba8 <read_six_numbers>

前面就是申请程序空间,然后 read_six_numbers() 读取 6 个数。

接下来就是循环开始了:

   0x0040153c <+60>:	nop0x00401540 <+64>:	lw	gp,16(s8)0x00401544 <+68>:	sw	zero,28(s8)	//m[$s8+28]=0,设置循环变量 i = 00x00401548 <+72>:	b	0x40163c <phase_6+316>	//跳转到第一重循环的条件判断,看<+316>的位置哈0x0040154c <+76>:	nop
--Type <RET> for more, q to quit, c to continue without paging--c0x00401550 <+80>:	lw	v0,28(s8)	//取得循环变量 i0x00401554 <+84>:	nop0x00401558 <+88>:	sll	v0,v0,0x2	//将 i 变成int型长度0x0040155c <+92>:	addiu	v1,s8,240x00401560 <+96>:	addu	v0,v1,v00x00401564 <+100>:	lw	v0,12(v0)	//取得输入的第 i 个数,可以在 GDB 中 ‘p $v0’ 查看0x00401568 <+104>:	nop0x0040156c <+108>:	slti	v0,v0,7	//条件判断,第 i 个数是否小于 7,input[i] < 7?0x00401570 <+112>:	beqz	v0,0x40159c <phase_6+156>	//若大于 7,则跳转到<+156>,炸弹爆炸;否则继续0x00401574 <+116>:	nop0x00401578 <+120>:	lw	v0,28(s8)	//取得之前的循环变量 i0x0040157c <+124>:	nop0x00401580 <+128>:	sll	v0,v0,0x2	//将 i 变成int型长度0x00401584 <+132>:	addiu	v1,s8,240x00401588 <+136>:	addu	v0,v1,v00x0040158c <+140>:	lw	v0,12(v0)	//取得输入的第 i 个数,可以在 GDB 中 ‘p $v0’ 查看0x00401590 <+144>:	nop0x00401594 <+148>:	bgtz	v0,0x4015a8 <phase_6+168>	//判断第 i 个数是否大于 0,若是,则发生跳转,避开了炸弹;否则炸弹爆炸!0x00401598 <+152>:	nop0x0040159c <+156>:	jal	0x4021f0 <explode_bomb>0x004015a0 <+160>:	nop0x004015a4 <+164>:	lw	gp,16(s8)0x004015a8 <+168>:	lw	v0,28(s8)	//取得之前的循环变量 i0x004015ac <+172>:	nop0x004015b0 <+176>:	addiu	v0,v0,1	//第二重循环变量的设定,j = i + 10x004015b4 <+180>:	sw	v0,24(s8)	//m[$s8+24]=v0,将第二重循环的循环变量进行存储0x004015b8 <+184>:	b	0x401618 <phase_6+280>	//第二重循环的条件判断,下面看<+280>哈0x004015bc <+188>:	nop0x004015c0 <+192>:	lw	v0,28(s8)	//取得第一重循环变量 i0x004015c4 <+196>:	nop0x004015c8 <+200>:	sll	v0,v0,0x2	//将 i 变成int型长度0x004015cc <+204>:	addiu	v1,s8,240x004015d0 <+208>:	addu	v0,v1,v00x004015d4 <+212>:	lw	v1,12(v0)	//取得输入的第 i 个数字0x004015d8 <+216>:	lw	v0,24(s8)	//取得第二重循环变量 j0x004015dc <+220>:	nop0x004015e0 <+224>:	sll	v0,v0,0x20x004015e4 <+228>:	addiu	a0,s8,240x004015e8 <+232>:	addu	v0,a0,v00x004015ec <+236>:	lw	v0,12(v0)	//取得第 j 个数字0x004015f0 <+240>:	nop0x004015f4 <+244>:	bne	v1,v0,0x401608 <phase_6+264>	//比较 $v0 和 $v1 的值,若不相等则跳转,避开了炸弹;不相等则炸弹爆炸!0x004015f8 <+248>:	nop0x004015fc <+252>:	jal	0x4021f0 <explode_bomb>0x00401600 <+256>:	nop0x00401604 <+260>:	lw	gp,16(s8)0x00401608 <+264>:	lw	v0,24(s8)	//取得第二重循环变量 j0x0040160c <+268>:	nop0x00401610 <+272>:	addiu	v0,v0,1	//j++0x00401614 <+276>:	sw	v0,24(s8)0x00401618 <+280>:	lw	v0,24(s8)	//第二重循环的条件判断0x0040161c <+284>:	nop0x00401620 <+288>:	slti	v0,v0,6	//条件判断,i < 6 ?0x00401624 <+292>:	bnez	v0,0x4015c0 <phase_6+192>	//若 < 6,则 $v0 为1,再次执行循环题;反之,则继续往下执行啦~0x00401628 <+296>:	nop0x0040162c <+300>:	lw	v0,28(s8)0x00401630 <+304>:	nop0x00401634 <+308>:	addiu	v0,v0,10x00401638 <+312>:	sw	v0,28(s8)0x0040163c <+316>:	lw	v0,28(s8)	//取得之前的循环变量0x00401640 <+320>:	nop0x00401644 <+324>:	slti	v0,v0,6	//第一重循环条件判断,i < 6 ?0x00401648 <+328>:	bnez	v0,0x401550 <phase_6+80>0x0040164c <+332>:	nop

做个记录:爆炸点1:若输入的数 < 0 或者 > 7,则炸弹爆炸!
做个记录:爆炸点2:若第 i 个数和它后面的某个数相等,则炸弹爆炸!

在第一个部分,进行了一个二重循环。其中第一重循环判断输入的数是不是在 [0,7][0, 7][0,7] 的范围内,第二重循环判断第 i 个数是否和它之后的某个数相等;

phase_6 中基本都是循环,所以接下来第二个循环开始啦:

   0x0040164c <+332>:	nop0x00401650 <+336>:	sw	zero,28(s8)	//设置第一重循环变量 i = 00x00401654 <+340>:	b	0x4016f8 <phase_6+504>	//第一重循环条件判断,下面看<+504>部分哈!0x00401658 <+344>:	nop0x0040165c <+348>:	lui	v0,0x410x00401660 <+352>:	addiu	v0,v0,125920x00401664 <+356>:	sw	v0,32(s8)

看到<+352>的时候会有些疑惑,究竟存储了个什么。我们在 GDB 中进行调试看看:
在这里插入图片描述
是一个 node1 标识,盲猜是一个节点,不妨先放在这儿,等会儿回来推一下

   0x0040164c <+332>:	nop0x00401650 <+336>:	sw	zero,28(s8)	//设置第一重循环变量 i = 00x00401654 <+340>:	b	0x4016f8 <phase_6+504>	//第一重循环条件判断,下面看<+504>部分哈!0x00401658 <+344>:	nop0x0040165c <+348>:	lui	v0,0x410x00401660 <+352>:	addiu	v0,v0,125920x00401664 <+356>:	sw	v0,32(s8)0x00401668 <+360>:	li	v0,1	//设置第二重循环变量 j = 10x0040166c <+364>:	sw	v0,24(s8)	//m[$s8+24]=$v00x00401670 <+368>:	b	0x40169c <phase_6+412>	//第二重循环条件判断,下面看 <+412> 吧0x00401674 <+372>:	nop0x00401678 <+376>:	lw	v0,32(s8)	//找到一个位置0x0040167c <+380>:	nop0x00401680 <+384>:	lw	v0,8(v0)	//将它向后移0x00401684 <+388>:	nop0x00401688 <+392>:	sw	v0,32(s8)	//保存移动之后的位置0x0040168c <+396>:	lw	v0,24(s8)	//取得第二重循环变量 j0x00401690 <+400>:	nop0x00401694 <+404>:	addiu	v0,v0,1	//j++0x00401698 <+408>:	sw	v0,24(s8)0x0040169c <+412>:	lw	v0,28(s8)	//获得第一重循环变量 i0x004016a0 <+416>:	nop0x004016a4 <+420>:	sll	v0,v0,0x2	//将 i 变成int型长度0x004016a8 <+424>:	addiu	v1,s8,240x004016ac <+428>:	addu	v0,v1,v00x004016b0 <+432>:	lw	v1,12(v0)	//取得输入的第 i 个数0x004016b4 <+436>:	lw	v0,24(s8)	//取得第二重循环变量 j0x004016b8 <+440>:	nop0x004016bc <+444>:	slt	v0,v0,v1	//判断 j 是否小于输入的第 i 个数。若是,则 1 ,集训循环;若不是,则 0,结束循环向下执行0x004016c0 <+448>:	bnez	v0,0x401678 <phase_6+376>0x004016c4 <+452>:	nop

在执行到 <+388> 的位置时,我们每次查看 $v0 中的值,可以发现:
在这里插入图片描述
在这里插入图片描述
根据这些标识,可以确定,这是一个链表类型的题目。

那么我们的第二重循环代表什么呢?仔细回想一下可以发现,就是这段C语言代码:

for (int j = 1; j < a[i]; ++j)node = node->next

意思就是根据我们输入的数 s,去找到第 s 个节点。

那么找到了我们要干些什么?接着往下看:

   0x004016c4 <+452>:	nop0x004016c8 <+456>:	lw	v0,28(s8)	//第一重循环变量0x004016cc <+460>:	nop0x004016d0 <+464>:	sll	v0,v0,0x20x004016d4 <+468>:	addiu	v1,s8,240x004016d8 <+472>:	addu	v0,v1,v00x004016dc <+476>:	lw	v1,32(s8)	//$v1=m[$s8+32],取了一个数是什么呢?可以在 GDB 中看一看(下面有截图哈!),可以看到 $v1 存的就是对应节点的值0x004016e0 <+480>:	nop0x004016e4 <+484>:	sw	v1,36(v0)	//m[$v0+36]=$v1,将这个值存到内存中。//由于这是一个循环,因此存的地址是连续的。因此可以看作将节点的值存到一个数组里面去了,假设这个数组就是newList哈~0x004016e8 <+488>:	lw	v0,28(s8)	//取得第一重循环变量 i0x004016ec <+492>:	nop0x004016f0 <+496>:	addiu	v0,v0,1	//++i0x004016f4 <+500>:	sw	v0,28(s8)	//保存循环变量0x004016f8 <+504>:	lw	v0,28(s8)	//取得第一重循环变量0x004016fc <+508>:	nop0x00401700 <+512>:	slti	v0,v0,6	//第一重循环条件判断,i < 6 ?  若不小于,则继续循环;若小于则结束循环往下执行啦!0x00401704 <+516>:	bnez	v0,0x40165c <phase_6+348>0x00401708 <+520>:	nop

在 <+476> 对 $v1 进行查看时发现:
在这里插入图片描述
node3 中的值是 0x12d,由于我第一个输入为 3,所以刚好也满足前面的推断。接下来把所有节点的值都列出来啦:

节点
node10x0fd
node20x2d5
node30x12d
node40x3e5
node50x0d4
node60x1b0

接下来:

   0x0040170c <+524>:	lw	v0,60(s8)0x00401710 <+528>:	nop0x00401714 <+532>:	sw	v0,32(s8)	//到达 newList[0]0x00401718 <+536>:	li	v0,1	//设置循环变量 i = 10x0040171c <+540>:	sw	v0,28(s8)	//m[$s8+28]=$v0,存储循环变量0x00401720 <+544>:	b	0x40177c <phase_6+636>	//循环条件判断,下面看<+636>哈0x00401724 <+548>:	nop0x00401728 <+552>:	lw	v0,28(s8)	//获得循环变量 i0x0040172c <+556>:	nop0x00401730 <+560>:	sll	v0,v0,0x2	//将 i 变成int型长度0x00401734 <+564>:	addiu	v1,s8,240x00401738 <+568>:	addu	v0,v1,v00x0040173c <+572>:	lw	v1,36(v0)	//得到 newList[i],第一次中这个是第二个节点哦!0x00401740 <+576>:	lw	v0,32(s8)	//第一次中这是第一个节点node0x00401744 <+580>:	nop0x00401748 <+584>:	sw	v1,8(v0)	//node->next=newList[i]0x0040174c <+588>:	lw	v0,28(s8)	//取得循环变量 i0x00401750 <+592>:	nop0x00401754 <+596>:	sll	v0,v0,0x2	//将 i 变成int型长度0x00401758 <+600>:	addiu	v1,s8,240x0040175c <+604>:	addu	v0,v1,v00x00401760 <+608>:	lw	v0,36(v0)	//node = node->next0x00401764 <+612>:	nop0x00401768 <+616>:	sw	v0,32(s8)	//m[$s8+32]=$v0,存储当前节点0x0040176c <+620>:	lw	v0,28(s8)	//获得循环变量 i0x00401770 <+624>:	nop0x00401774 <+628>:	addiu	v0,v0,1	//++i0x00401778 <+632>:	sw	v0,28(s8)	//保存循环变量0x0040177c <+636>:	lw	v0,28(s8)	//获得循环变量0x00401780 <+640>:	nop0x00401784 <+644>:	slti	v0,v0,6	//条件判断,i < 6 ?0x00401788 <+648>:	bnez	v0,0x401728 <phase_6+552>	//若 < 6,则跳转;否则继续往下执行0x0040178c <+652>:	nop

这一段汇编的意思就是:在之前我们已经按照我们输入的顺序,将原链表上的节点保存在了数组的对应位置上。此时我们要做的就是将链表按照在数组中的顺序进行一个重新连接。

这两部合二为一,即按照我们输入的顺序重构了链表!

接着往下看:

   0x00401790 <+656>:	lw	v0,32(s8)	//经过上面的连接,现在node处于最后一个节点0x00401794 <+660>:	nop0x00401798 <+664>:	sw	zero,8(v0)	//node->next = null0x0040179c <+668>:	lw	v0,60(s8)0x004017a0 <+672>:	nop0x004017a4 <+676>:	sw	v0,32(s8)	//重置node至链表的firstNode0x004017a8 <+680>:	sw	zero,28(s8)	//设置循环变量 i = 00x004017ac <+684>:	b	0x401878 <phase_6+888>	//条件判断0x004017b0 <+688>:	nop0x004017b4 <+692>:	lw	v0,-32660(gp)0x004017b8 <+696>:	nop0x004017bc <+700>:	lw	v0,44(v0)	//获得学号的最后一位0x004017c0 <+704>:	nop0x004017c4 <+708>:	andi	v0,v0,0x10x004017c8 <+712>:	andi	v0,v0,0xff	//老规矩,判断奇偶0x004017cc <+716>:	beqz	v0,0x401818 <phase_6+792>	//奇数 1,不跳转;偶数 0,跳转0x004017d0 <+720>:	nop0x004017d4 <+724>:	lw	v0,32(s8)	//找到前一个节点 node0x004017d8 <+728>:	nop0x004017dc <+732>:	lw	v1,0(v0)	//获得前一个节点的值0x004017e0 <+736>:	lw	v0,32(s8)	//重置node0x004017e4 <+740>:	nop0x004017e8 <+744>:	lw	v0,8(v0)	//node = node->next0x004017ec <+748>:	nop0x004017f0 <+752>:	lw	v0,0(v0)	//获得后一个节点的值0x004017f4 <+756>:	nop0x004017f8 <+760>:	slt	v0,v1,v0	//比较,升序则 $v0 = 1,反之 $v0 = 00x004017fc <+764>:	beqz	v0,0x401854 <phase_6+852>	//升序则不跳转,炸弹爆炸;反之避开炸弹0x00401800 <+768>:	nop0x00401804 <+772>:	jal	0x4021f0 <explode_bomb>0x00401808 <+776>:	nop0x0040180c <+780>:	lw	gp,16(s8)0x00401810 <+784>:	b	0x401854 <phase_6+852>0x00401814 <+788>:	nop0x00401818 <+792>:	lw	v0,32(s8)0x0040181c <+796>:	nop0x00401820 <+800>:	lw	v1,0(v0)	//$v1 是前一个节点0x00401824 <+804>:	lw	v0,32(s8)0x00401828 <+808>:	nop0x0040182c <+812>:	lw	v0,8(v0)0x00401830 <+816>:	nop0x00401834 <+820>:	lw	v0,0(v0)	//$v0 是后一个节点0x00401838 <+824>:	nop0x0040183c <+828>:	slt	v0,v0,v1	//降序则 1,反之则 00x00401840 <+832>:	beqz	v0,0x401854 <phase_6+852>	//降序则爆炸;反之避开炸弹;0x00401844 <+836>:	nop0x00401848 <+840>:	jal	0x4021f0 <explode_bomb>0x0040184c <+844>:	nop0x00401850 <+848>:	lw	gp,16(s8)0x00401854 <+852>:	lw	v0,32(s8)	//更新 node0x00401858 <+856>:	nop0x0040185c <+860>:	lw	v0,8(v0)	//node = node->next0x00401860 <+864>:	nop0x00401864 <+868>:	sw	v0,32(s8)	//保存当前节点地址0x00401868 <+872>:	lw	v0,28(s8)	//获得循环变量 i0x0040186c <+876>:	nop0x00401870 <+880>:	addiu	v0,v0,1	//++i0x00401874 <+884>:	sw	v0,28(s8)	//保存循环变量0x00401878 <+888>:	lw	v0,28(s8)	//获得循环变量0x0040187c <+892>:	nop0x00401880 <+896>:	slti	v0,v0,5	//条件判断,i < 5 ?0x00401884 <+900>:	bnez	v0,0x4017b4 <phase_6+692>	//是则跳转,反之结束循环0x00401888 <+904>:	nop0x0040188c <+908>:	move	sp,s8	//归还空间0x00401890 <+912>:	lw	ra,92(sp)0x00401894 <+916>:	lw	s8,88(sp)0x00401898 <+920>:	addiu	sp,sp,960x0040189c <+924>:	jr	ra0x004018a0 <+928>:	nop

做个记录:爆炸点3:学号最后一位若是奇数,节点值排序后是升序,则爆炸!若是偶数,节点值排序后是降序,则爆炸!

炸弹六总结

爆炸点1: 若输入的数 < 0 或者 > 7,则炸弹爆炸!
爆炸点2: 若第 i 个数和它后面的某个数相等,则炸弹爆炸!
爆炸点3: 学号最后一位若是奇数,节点值排序后是升序,则爆炸!若是偶数,节点值排序后是降序,则爆炸!(现在是2021-11-21 1:29)

secret_phase

拆完这 6 个炸弹,如果能够理解前面的汇编语句,算是一种成功。但听说这之中还有一个隐藏炸弹,我们现在来看看。
在这里插入图片描述
我们首先对phase_defused进行单步调试发现,在这里有一个输出为 %d%s\%d\ \%s%d %s 的形式,接下来就是 string_not_equal 的比较了,那么很显然后面比较的字符串就是通过这一步来进行输入的。

再回想我们整个拆弹过程,能按照 %d%s\%d\ \%s%d %s 的格式输入的似乎只有 phase_4,虽然那时只是输入了一个整数,但是确实是离这个输入最近的。因此,我们再 phase_4 的输入时后面再输入一个字符串 “helloworld”,接着在 string_not_equal 的地方进行查看:

...0x004022e0 <+124>:	nop0x004022e4 <+128>:	addiu	v0,s8,240x004022e8 <+132>:	move	a0,v00x004022ec <+136>:	lui	v0,0x400x004022f0 <+140>:	addiu	a1,v0,104160x004022f4 <+144>:	jal	0x401cf8 <strings_not_equal>	//出现了一个字符串的比较。一个炸弹已经拆解的函数,为什么要比较字符串呢?我们在 GDB 中进行查看0x004022f8 <+148>:	nop0x004022fc <+152>:	lw	gp,16(s8)0x00402300 <+156>:	bnez	v0,0x402354 <phase_defused+240>0x00402304 <+160>:	nop0x00402308 <+164>:	lui	v0,0x400x0040230c <+168>:	addiu	a0,v0,104320x00402310 <+172>:	lw	v0,-32712(gp)0x00402314 <+176>:	nop0x00402318 <+180>:	move	t9,v00x0040231c <+184>:	jalr	t90x00402320 <+188>:	nop0x00402324 <+192>:	lw	gp,16(s8)0x00402328 <+196>:	lui	v0,0x400x0040232c <+200>:	addiu	a0,v0,104720x00402330 <+204>:	lw	v0,-32712(gp)0x00402334 <+208>:	nop0x00402338 <+212>:	move	t9,v00x0040233c <+216>:	jalr	t90x00402340 <+220>:	nop0x00402344 <+224>:	lw	gp,16(s8)0x00402348 <+228>:	jal	0x401990 <secret_phase>0x0040234c <+232>:	nop

在这里插入图片描述
可以看到:$a0 中存储着我们之前输入的字符串,而 $a1 中存的是一个“austinpowers”,相等返回 1,继续向下执行,也就进入 secret_phase 啦!
接下来,大家就根据注释食用吧!

   0x00401990 <+0>:		addiu	sp,sp,-400x00401994 <+4>:		sw	ra,36(sp)0x00401998 <+8>:		sw	s8,32(sp)0x0040199c <+12>:	move	s8,sp0x004019a0 <+16>:	lui	gp,0x420x004019a4 <+20>:	addiu	gp,gp,-200800x004019a8 <+24>:	sw	gp,16(sp)0x004019ac <+28>:	jal	0x401fec <read_line>0x004019b0 <+32>:	nop0x004019b4 <+36>:	lw	gp,16(s8)0x004019b8 <+40>:	sw	v0,28(s8)	//存储输入的数据(这里我输入的是 10)0x004019bc <+44>:	lw	v0,28(s8)	//load 保存输入的数据0x004019c0 <+48>:	nop0x004019c4 <+52>:	move	a0,v00x004019c8 <+56>:	move	a1,zero0x004019cc <+60>:	li	a2,100x004019d0 <+64>:	lw	v0,-32656(gp)0x004019d4 <+68>:	nop0x004019d8 <+72>:	move	t9,v00x004019dc <+76>:	jalr	t90x004019e0 <+80>:	nop
--Type <RET> for more, q to quit, c to continue without paging--c0x004019e4 <+84>:	lw	gp,16(s8)0x004019e8 <+88>:	sw	v0,24(s8)	//保存输入的数据(10)0x004019ec <+92>:	lw	v0,24(s8)	//load 输入的数据0x004019f0 <+96>:	nop0x004019f4 <+100>:	addiu	v0,v0,-1	//$v0--(9)0x004019f8 <+104>:	sltiu	v0,v0,1001	//条件判断,$v0 < 1001?0x004019fc <+108>:	bnez	v0,0x401a10 <secret_phase+128>	//是则跳转,反之炸弹爆炸!0x00401a00 <+112>:	nop0x00401a04 <+116>:	jal	0x4021f0 <explode_bomb>0x00401a08 <+120>:	nop0x00401a0c <+124>:	lw	gp,16(s8)0x00401a10 <+128>:	lui	v0,0x410x00401a14 <+132>:	addiu	a0,v0,12676	//$a0 = 0x240x00401a18 <+136>:	lw	a1,24(s8)	//输入的数据(10)0x00401a1c <+140>:	jal	0x4018a4 <fun7>	//跳转至 fun7 啦

做个记录:爆炸点1:输入数据若 > 1001,则爆炸!

这里发生了一个跳转,那么接下来我们看看 fun7 干了啥:

   0x004018a4 <+0>:		addiu	sp,sp,-320x004018a8 <+4>:		sw	ra,28(sp)0x004018ac <+8>:		sw	s8,24(sp)0x004018b0 <+12>:	move	s8,sp0x004018b4 <+16>:	sw	a0,32(s8)	//$a0=0x24,第二次:$a0=0x080x004018b8 <+20>:	sw	a1,36(s8)	//$a1=10,第二次:$a1=100x004018bc <+24>:	lw	v0,32(s8)	//$v0=0x24,第二次:$v0=0x080x004018c0 <+28>:	nop0x004018c4 <+32>:	bnez	v0,0x4018d8 <fun7+52>	//$a0=0x24,不为 0,所以跳转至 <+52>0x004018c8 <+36>:	nop0x004018cc <+40>:	li	v0,-10x004018d0 <+44>:	b	0x401978 <fun7+212>0x004018d4 <+48>:	nop0x004018d8 <+52>:	lw	v0,32(s8)	//$v0=0x24(n1),第二次$v0=0x080x004018dc <+56>:	nop0x004018e0 <+60>:	lw	v1,0(v0)	//$v1=36,第二次 $v1=80x004018e4 <+64>:	lw	v0,36(s8)	//$v0=10,第二次 $v0=100x004018e8 <+68>:	nop0x004018ec <+72>:	slt	v0,v0,v1	//$v0 < $v1 ? 若是返回 1,不跳转;反之则跳转0x004018f0 <+76>:	beqz	v0,0x401924 <fun7+128>0x004018f4 <+80>:	nop
--Type <RET> for more, q to quit, c to continue without paging--c0x004018f8 <+84>:	lw	v0,32(s8)	//$v0 = 0x24(n1)0x004018fc <+88>:	nop0x00401900 <+92>:	lw	v0,4(v0)	//$v0 = 0x08(n21)0x00401904 <+96>:	nop0x00401908 <+100>:	move	a0,v0	//$a0 = 0x08(n21)0x0040190c <+104>:	lw	a1,36(s8)	//$a1 = 100x00401910 <+108>:	jal	0x4018a4 <fun7>0x00401914 <+112>:	nop0x00401918 <+116>:	sll	v0,v0,0x1	//左移 1 位,末尾补 10x0040191c <+120>:	b	0x401978 <fun7+212>0x00401920 <+124>:	nop0x00401924 <+128>:	lw	v0,32(s8)	//第二次:$v0=0x080x00401928 <+132>:	nop0x0040192c <+136>:	lw	v1,0(v0)	//第二次:$v1=80x00401930 <+140>:	lw	v0,36(s8)	//第二次:$v0=100x00401934 <+144>:	nop0x00401938 <+148>:	slt	v0,v1,v0	//$v1 < $v0 ? 若是,则不跳转,反之跳转0x0040193c <+152>:	beqz	v0,0x401974 <fun7+208>0x00401940 <+156>:	nop0x00401944 <+160>:	lw	v0,32(s8)	//第二次:$v0=0x080x00401948 <+164>:	nop0x0040194c <+168>:	lw	v0,8(v0)	//第二次:$v0=0x160x00401950 <+172>:	nop0x00401954 <+176>:	move	a0,v0	//$a0=0x160x00401958 <+180>:	lw	a1,36(s8)	//$a1=100x0040195c <+184>:	jal	0x4018a4 <fun7>0x00401960 <+188>:	nop0x00401964 <+192>:	sll	v0,v0,0x10x00401968 <+196>:	addiu	v0,v0,1	//左移 1 位,末尾补 10x0040196c <+200>:	b	0x401978 <fun7+212>0x00401970 <+204>:	nop0x00401974 <+208>:	move	v0,zero0x00401978 <+212>:	move	sp,s80x0040197c <+216>:	lw	ra,28(sp)0x00401980 <+220>:	lw	s8,24(sp)0x00401984 <+224>:	addiu	sp,sp,320x00401988 <+228>:	jr	ra0x0040198c <+232>:	nop

那么,这个函数就相当于是一个二叉搜索树,根据 $v0 和 $v1 的大小关系来确定下一节点是左孩子还有右孩子。同时,没执行结束一个函数都会将 $v0 向左移动一位,末位补 1.

下面来看这个返回值在 secret_phase 中的作用:

   0x00401a20 <+144>:	nop0x00401a24 <+148>:	lw	gp,16(s8)0x00401a28 <+152>:	move	v1,v0	//$v1=$v0,即将返回值给 $v10x00401a2c <+156>:	li	v0,7	//$v0 = 70x00401a30 <+160>:	beq	v1,v0,0x401a44 <secret_phase+180>	//$v1 = 7 ? 相等则跳转,避开炸弹;反之炸弹爆炸!0x00401a34 <+164>:	nop0x00401a38 <+168>:	jal	0x4021f0 <explode_bomb>0x00401a3c <+172>:	nop0x00401a40 <+176>:	lw	gp,16(s8)0x00401a44 <+180>:	lui	v0,0x40

按照之前所说,每一次函数的执行,都会将 $v0 左移 1 位,末尾补 1,那么
000→001→011→111000 \rightarrow 001 \rightarrow 011 \rightarrow 111000001011111因此,我们需要做的就是控制函数的执行次数是 3 次。即二叉搜索树的搜索次数是 3 次。

做个记录:爆炸点2:二叉搜索数的搜索次数为 3 次,否则爆炸

在 GDB 中进行查看,很容易得到这棵二叉搜索树,我们给出其节点值(作图如下):
在这里插入图片描述

隐藏炸弹总结

做个记录:爆炸点1: 输入数据若 > 1001,则爆炸!
做个记录:爆炸点2: 二叉搜索数的搜索次数为 3 次,否则爆炸!

总结

写完文章,CSDN 已经开始卡顿了hhh,不妨静下心来一个一个看这些炸弹,跟着思路走一走。相信真正理解了这些汇编,这个实验才算真正达到了目的吧!

有什么问题烦请告知哈!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/565072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习入门(2)之模型评估与选择

目录 一、误差与拟合 1. 泛化误差与经验误差 2. 损失函数与训练误差 3. 过拟合与欠拟合 4. 过拟合的另一种现象&#xff1a;数据泄露 二、评估方法 1. 留出法 2. 交叉验证法&#xff08;留一法&#xff09; 3. 自助法 4. 调参与最终模型 三、性能度量 1. 混淆矩阵 …

pytorch MNIST 手写数字识别 + 使用自己的测试集 + 数据增强后再训练

文章目录1. MNIST 手写数字识别2. 聚焦数据集扩充后的模型训练3. pytorch 手写数字识别基本实现3.1完整代码及 MNIST 测试集测试结果3.1.1代码3.1.2 MNIST 测试集测试结果3.2 使用自己的图片进行测试3.2.1 测试图片预处理代码3.2.2 测试图片结果4. 数据增强4.1 手动读取 MNIST …

python基础(13)之数组

目录 数组 一、访问数组的元素 二、数组的长度 三、修改数组 四、数组的其它操作 数组 Python 没有对数组的内置支持&#xff0c;但可以使用Python 列表代替。 例如&#xff1a; ben ["笨小孩1", "笨小孩2", "笨小孩3"]一、访问数组的元…

C语言归并排序(合并排序)

归并排序也称合并排序&#xff0c;其算法思想是将待排序序列分为两部分&#xff0c;依次对分得的两个部分再次使用归并排序&#xff0c;之后再对其进行合并。仅从算法思想上了解归并排序会觉得很抽象&#xff0c;接下来就以对序列A[0], A[l]…, A[n-1]进行升序排列来进行解说&a…

python基础(14)之 类和对象

目录 Python类和对象 一、创建类 二、创建对象 三、init() 函数 四、对象方法 五、自参数 六、对象及其属性更改 七、pass语句 Python类和对象 Python 类/对象。Python 是一种面向对象的编程语言。Python 中的几乎所有东西都是一个对象&#xff0c;有它的属性和方法。…

C语言顺序查找

顺序査找是一种简单的査找算法&#xff0c;其实现方法是从序列的起始元素开始&#xff0c;逐个将序列中的元素与所要查找的元素进行比较&#xff0c;如果序列中有元素与所要查找的元素相等&#xff0c;那么査找成功&#xff0c;如果査找到序列的最后一个元素都不存在一个元素与…

python基础(15)之 继承

目录 Python继承 一、创建父类 二、创建子类 三、添加 init() 函数 四、使用 super() 函数 五、添加属性 六、添加方法 Python继承 继承允许我们定义一个从另一个类继承所有方法和属性的类。父类是被继承的类&#xff0c;也称为基类。子类是从另一个类继承的类&#xff…

C语言二分查找(折半查找)

二分査找也称折半査找&#xff0c;其优点是查找速度快&#xff0c;缺点是要求所要査找的数据必须是有序序列。该算法的基本思想是将所要査找的序列的中间位置的数据与所要査找的元素进行比较&#xff0c;如果相等&#xff0c;则表示査找成功&#xff0c;否则将以该位置为基准将…

python基础(16)之 日期

目录 Python日期 一、日期输入输出 二、创建日期对象 三、strftime() 方法 Python日期 Python 中的日期不是它自己的数据类型&#xff0c;但我们可以导入一个名为的模块datetime来处理日期作为日期对象。 一、日期输入输出 导入 datetime 模块并显示当前日期&#xff1a;…

python基础(17)之 JSON

Python JSON JSON 是一种用于存储和交换数据的语法。JSON 是文本&#xff0c;用 JavaScript 对象表示法编写。 Python 有一个名为 的内置包json&#xff0c;可用于处理 JSON 数据。 导入 json 模块&#xff1a; import json一.从 JSON 转换为 Python 如果您有 JSON 字符串&am…

python基础(18)之 异常处理

目录 异常处理 一、异常处理 二、else搭配 三、finally语句 四、引发异常 异常处理 try块可让您测试代码块的错误。except块可让您处理错误。finally无论 try- 和 except 块的结果如何&#xff0c;该块都允许您执行代码。 一、异常处理 例如该try块将产生异常&#xff0…

python基础(19)之 输入输出

目录 用户输入 一、格式化输入输出 二、格式化字符串字面值 三、字符串 format() 方法 四、手动格式化字符串 五、旧式字符串格式化方法 用户输入 实在太简单了&#xff0c;就是使用一个input(),将输入后的值传递给另一个变量&#xff0c;相当于动态赋值、 例如&#xff…

C语言函数返回值详解

函数的返回值是指函数被调用之后&#xff0c;执行函数体中的代码所得到的结果&#xff0c;这个结果通过 return 语句返回。 return 语句的一般形式为&#xff1a; return 表达式;或者&#xff1a; return (表达式);有没有( )都是正确的&#xff0c;为了简明&#xff0c;一般…

机器学习之线性回归(python)

目录 一、基本概念 二、概念的数学形式表达 三、确定w和b 1.读取或输入数据 2.归一化、标准化 2.1 均值 2.2 归一化 2.3 标准化 3.求解w和b 1.直接解方程 2.最小二乘法&#xff08;least square method&#xff09;求解&#xff1a; 4. 评估回归模型 四、sklearn中…

C语言函数的调用

函数调用&#xff08;Function Call&#xff09;&#xff0c;就是使用已经定义好的函数。 函数调用的一般形式为&#xff1a; functionName(param1, param2, param3 ...);functionName 是函数名称&#xff0c;param1, param2, param3 …是实参列表。实参可以是常数、变量、表…

机器学习之线性回归(matlab)

目录 一、基本概念 二、概念的数学形式表达 三、确定w和b 1.读取或输入数据 2.归一化、标准化 2.1 均值 2.2 归一化 2.3 标准化 3.求解w和b 1.直接解方程 2.最小二乘法&#xff08;least square method&#xff09;求解&#xff1a; 4. 评估回归模型 四、regress线…

C语言函数声明以及函数原型

C语言代码由上到下依次执行&#xff0c;原则上函数定义要出现在函数调用之前&#xff0c;否则就会报错。但在实际开发中&#xff0c;经常会在函数定义之前使用它们&#xff0c;这个时候就需要提前声明。 函数声明&#xff08;Declaration&#xff09;&#xff0c;就是告诉编译…

python(20)之读写文件

目录 读写文件 1.简单介绍 2.从文件中读取单行数据 3.从文件中读取多行 4.把 string&#xff08;字符串&#xff09; 的内容写入文件 5.写入其他类型的对象 本节知识总结 mode 参数 file 对象 读写文件 1.简单介绍 最常用的参数有两个: open(filename, mode) f op…

C语言全局变量和局部变量深入

局部变量 定义在函数内部的变量称为局部变量&#xff08;Local Variable&#xff09;&#xff0c;它的作用域仅限于函数内部&#xff0c; 离开该函数后就是无效的&#xff0c;再使用就会报错。 示例 int f1(int a){int b,c; //a,b,c仅在函数f1()内有效return abc; } int ma…

python实例之 67,68

目录 67.题目&#xff1a;输入数组&#xff0c;最大的与第一个元素交换&#xff0c;最小的与最后一个元素交换&#xff0c;输出数组。 68.题目&#xff1a;有 n 个整数&#xff0c;使其前面各数顺序向后移 m 个位置&#xff0c;最后 m 个数变成最前面的 m 个数 今天这个不知道…