计组实验一:拆炸弹实验

发布时间 2023-11-03 17:01:33作者: Smera1d0

一、实验任务:

程序包括5关,每一关需要你输入若干数字(多个数字用空格隔开)。每一关的答案藏在对应关卡函数的汇编代码中。

二、实验步骤:

1.反汇编生成汇编代码

需要将二进制可执行文件lab1反汇编,生成汇编代码,为此,可在虚拟机lab1文件所在目录中打开终端,执行如下指令 riscv64-linux-gnu-objdump -d lab1 > lab1.dump得到汇编代码。

2.分析phase代码:

(1)phase_1分析:

汇编代码,通过分析,已经将每一行的作用用注释的方式给出:

   102c6:	7179                	add	sp,sp,-48  
   //add sp, sp, -48:将栈指针 sp 减去 48,分配 48 字节的栈空间。
   102c8:	f406                	sd	ra,40(sp)
   //sd ra, 40(sp):将寄存器 ra 的值存储到栈指针 sp 加上 40 的偏移处。
   102ca:	f022                	sd	s0,32(sp)
   //sd s0, 32(sp):将寄存器 s0 的值存储到栈指针 sp 加上 32 的偏移处。
   102cc:	1800                	add	s0,sp,48
   //add s0, sp, 48:计算栈指针 sp 加上 48 的结果,并将结果存储到寄存器 s0 中。
   102ce:	fca43c23          	sd	a0,-40(s0)
   //sd a0, -40(s0):将寄存器 a0 的值存储到寄存器 s0 加上 -40 的偏移处。
   102d2:	fe840793          	add	a5,s0,-24
   //add a5, s0, -24:计算寄存器 s0 加上 -24 的结果,并将结果存储到寄存器 a5 中。
   102d6:	863e                	mv	a2,a5
   //mv a2, a5:将寄存器 a5 的值移动到寄存器 a2 中。
   102d8:	000257b7          	lui	a5,0x25
   //lui a5, 0x25:将 0x25 加载到寄存器 a5 的高 20 位。
   102dc:	8a878593          	add	a1,a5,-1880 # 248a8 <__clzdi2+0x182>
   //add a1, a5, -1880:将寄存器 a5 加上 -1880 的结果存储到寄存器 a1 中。
   102e0:	fd843503          	ld	a0,-40(s0)
   //ld a0, -40(s0):将寄存器 s0 加上 -40 的偏移处的值加载到寄存器 a0 中。
   102e4:	483000ef          	jal	10f66 <sscanf>
   //jal 10f66 <sscanf>:跳转并链接到地址 10f66 处执行函数。
   102e8:	87aa                	mv	a5,a0
   //mv a5, a0:将寄存器 a0 的值移动到寄存器 a5 中。
   102ea:	fef42623          	sw	a5,-20(s0)
   //sw a5, -20(s0):将寄存器 a5 的值存储到寄存器 s0 加上 -20 的偏移处。
   102ee:	fec42783          	lw	a5,-20(s0)
   //lw a5, -20(s0):将寄存器 s0 加上 -20 的偏移处的值加载到寄存器 a5 中。
   102f2:	0007871b          	sext.w	a4,a5
   //sext.w a4, a5:将寄存器 a5 的值符号扩展为 32 位,并存储到寄存器 a4 中。
   102f6:	4785                	li	a5,1
   //li a5, 1:将值 1 存储到寄存器 a5 中。
   102f8:	00f70463          	beq	a4,a5,10300 <phase_1+0x3a>
   //beq a4, a5, 10300 <phase_1+0x3a>:如果寄存器 a4 的值等于寄存器 a5 的值,则    跳转到地址 10300 处继续执行。
   102fc:	f29ff0ef          	jal	10224 <explode_bomb>
   //jal 10224 <explode_bomb>:跳转并链接到地址 10224 处执行函数。根据函数名来看,这    是炸弹爆炸了
   10300:	fe842783          	lw	a5,-24(s0)
   //lw a5, -24(s0):将寄存器 s0 加上 -24 的偏移处的值加载到寄存器 a5 中。
   10304:	873e                	mv	a4,a5
   //mv a4, a5:将寄存器 a5 的值移动到寄存器 a4 中。
   10306:	3e400793          	li	a5,996
   //li a5, 996:将值 996 存储到寄存器 a5 中。

这个996显得很突兀,思考很可能与答案有关,先记录下来

1030a:	00f70463          	beq	a4,a5,10312 <phase_1+0x4c>
//beq a4, a5, 10312 <phase_1+0x4c>:如果寄存器 a4 的值等于寄存器 a5 的值,则跳转到地址 10312 处继续执行。结合上面的996,猜测,是否是输入一个数等于996就可以,尝试了一下,果然成功了
1030e:	f17ff0ef          	jal	10224 <explode_bomb>
10312:	000257b7          	lui	a5,0x25
10316:	8b078513          	add	a0,a5,-1872 # 248b0 <__clzdi2+0x18a>
1031a:	387000ef          	jal	10ea0 <puts>
1031e:	0001                	nop
10320:	70a2                	ld	ra,40(sp)
10322:	7402                	ld	s0,32(sp)
10324:	6145                	add	sp,sp,48
10326:	8082                	ret

输入996即可过关。

屏幕截图 2023-11-03 113450.png

(2)phase_2分析:

汇编代码及分析:

0000000000010328 <phase_2>:
   10328:	7179                	add	sp,sp,-48
   //首先在栈上为函数/程序分配了一些空间,通过 add sp, sp, -48 来为局部变量和寄存器保存空间
   1032a:	f406                	sd	ra,40(sp)
   //sd ra, 40(sp): 保存寄存器 ra 到栈偏移 40 处。这是为了在函数结束后能够正确返回。
   1032c:	f022                	sd	s0,32(sp)
   //sd s0, 32(sp): 保存寄存器 s0 到堆栈偏移 32 处
   1032e:	1800                	add	s0,sp,48
   add s0, sp, 48: 将堆栈指针 sp 的值复制到寄存器 s0,用于访问局部变量和参数。
   10330:	fca43c23          	sd	a0,-40(s0)
   //sd a0, -40(s0): 保存寄存器 a0 的值到地址 s0 - 40 的内存位置,可能是将参数保存到局部变量中。
   10334:	fe440713          	add	a4,s0,-28
   10338:	fe840793          	add	a5,s0,-24
   //add a4, s0, -28 和 add a5, s0, -24: 将地址 s0 - 28 和 s0 - 24 存储到寄存器 a4 和 a5 中,这可能是为了将某些变量的地址加载到寄存器中以备后用。
   1033c:	86ba                	mv	a3,a4
   1033e:	863e                	mv	a2,a5
   //mv a3, a4 和 mv a2, a5:这两行指令将寄存器 a4 的值传送到寄存器 a3,将寄存器 a5 的值传送到寄存器 a2。这些寄存器之间的数据传送可能是为了备份或传递参数。
   10340:	000257b7          	lui	a5,0x25
   //将立即数 0x25 的高 20 位加载到寄存器 a5 中。通常,这种操作用于加载常数值到寄存器。
   10344:	8d878593          	add	a1,a5,-1832 # 248d8 <__clzdi2+0x1b2>
   //将 a5 寄存器的值与 -1832 相加,并将结果存储在 a1 寄存器中。
   10348:	fd843503          	ld	a0,-40(s0)
   //ld a0,-40(s0): 将栈帧中偏移量为 -40 的位置的值加载到寄存器 a0,作为 sscanf 函数的第一个参数。
   1034c:	41b000ef          	jal	10f66 <sscanf>
   //jal 10f66 <sscanf>: 调用 sscanf 函数,跳转到地址 10f66 处执行。
   10350:	87aa                	mv	a5,a0
   //mv a5,a0: 将 sscanf 函数的返回值存储到寄存器 a5。
   10352:	fef42623          	sw	a5,-20(s0)
   //sw a5,-20(s0): 将寄存器 a5 的值存储到栈帧中偏移量为 -20 的位置。
   10356:	fec42783          	lw	a5,-20(s0)
   //lw a5,-20(s0): 将栈帧中偏移量为 -20 的位置的值加载到寄存器 a5。
   1035a:	0007871b          	sext.w	a4,a5
   1035e:	4789                	li	a5,2
   10360:	00f70463          	beq	a4,a5,10368 <phase_2+0x40>
   // 如果寄存器 a4 的值等于寄存器 a5 的值,则跳转到地址 10368 处执行。!!!这里应该是关键,我们直接跳到10368去
   10364:	ec1ff0ef          	jal	10224 <explode_bomb>
   10368:	fe842703          	lw	a4,-24(s0)
   //lw a4,-24(s0): 将栈帧中偏移量为 -24 的位置的值加载到寄存器 a4。
   1036c:	fe442783          	lw	a5,-28(s0)
   //lw a5,-28(s0): 将栈帧中偏移量为 -28 的位置的值加载到寄存器 a5。
   10370:	9fb9                	addw	a5,a5,a4
   //addw a5,a5,a4: 将寄存器 a5 和 a4 的值相加,并将结果存储到寄存器 a5。
   10372:	2781                	sext.w	a5,a5
   //sext.w a5,a5: 将寄存器 a5 的值进行符号扩展,并存储到寄存器 a5。
   10374:	873e                	mv	a4,a5
   //mv a4,a5: 将寄存器 a5 的值移动到寄存器 a4。
   10376:	3e400793          	li	a5,996
   1037a:	00f70463          	beq	a4,a5,10382 <phase_2+0x5a>
   1037e:	ea7ff0ef          	jal	10224 <explode_bomb>
   10382:	000257b7          	lui	a5,0x25
   10386:	8e078513          	add	a0,a5,-1824 # 248e0 <__clzdi2+0x1ba>
   1038a:	317000ef          	jal	10ea0 <puts>
   //jal 10ea0 <puts> 是另一个函数调用指令,跳转到函数 puts,通常用于将字符串打印到标准输出。
   这是分析10ea的函数作用
   0000000000010ea0 <puts>:
   10ea0:	85aa                	mv	a1,a0
   10ea2:	7501b503          	ld	a0,1872(gp) # 28380 <_impure_ptr>
   10ea6:	bfad                	j	10e20 <_puts_r>
//mv a1, a0 将参数 a0 中的值移动到参数 a1 中。
//ld a0, 1872(gp) 从全局指针 gp 处加载值,并将其存储在参数 a0 中。这可能是用于访问某些全局数据或状态。
//j 10e20 <_puts_r> 是一个跳转指令,将程序的控制流转移到地址 10e20 处,即 _puts_r 函数的实现,它可能用于实际执行 puts 函数的功能。分析后,10e20的函数应该是输出拆除成功的文字
   1038e:	0001                	nop
   10390:	70a2                	ld	ra,40(sp)
   10392:	7402                	ld	s0,32(sp)
   10394:	6145                	add	sp,sp,48
   10396:	8082                	ret

总结分析过程,应该是输入两个数相加等于996即可,输入1 995成功!

piMAI8f.png

(3)phase_3分析:

0000000000010398 <phase_3>:
   10398:	7179                	addi	sp,sp,-48 //将栈指针sp减小48字节,为函数的栈帧分配空间。
   1039a:	f406                	sd	ra,40(sp) //设置了返回地址
   1039c:	f022                	sd	s0,32(sp) //设置了栈帧指针
   1039e:	1800                	addi	s0,sp,48 //将s0设置为栈指针sp+48,建立一个新的栈帧。
   103a0:	fca43c23          	sd	a0,-40(s0) //将参数 a0 存储在当前栈帧的偏移地址 -40。
   103a4:	fe440713          	addi	a4,s0,-28
   103a8:	fe840793          	addi	a5,s0,-24
   103ac:	86ba                	mv	a3,a4
   103ae:	863e                	mv	a2,a5
   103b0:	000257b7          	lui	a5,0x25
   103b4:	8d878593          	addi	a1,a5,-1832 # 248d8 <__clzdi2+0x1b2>
   103b8:	fd843503          	ld	a0,-40(s0)
   103bc:	3ab000ef          	jal	ra,10f66 <sscanf> //调用了一个名为sscanf的函数,该函数从输入中读取一个整数,并将其存储在 a0 寄存器中。
   103c0:	87aa                	mv	a5,a0 //将 a0 寄存器的值复制到 a5 寄存器,这是后续操作的输入值。
   103c2:	fef42623          	sw	a5,-20(s0) //存储 a5 寄存器的值在栈帧中。
   103c6:	fec42783          	lw	a5,-20(s0) //加载栈帧中的 a5 寄存器值。
   103ca:	0007871b          	sext.w	a4,a5 //将 a5 寄存器的值符号扩展为 a4 寄存器。
   103ce:	4789                	li	a5,2 //将 a5 寄存器设置为值2。
   103d0:	00f70463          	beq	a4,a5,103d8 <phase_3+0x40> //如果 a4 和 a5 相等,跳转到地址 103d8,否则继续执行。
   103d4:	e51ff0ef          	jal	ra,10224 <explode_bomb> //如果 a4 和 a5 不相等,会触发一个炸弹,程序终止。
   103d8:	fe842783          	lw	a5,-24(s0)
   103dc:	0027979b          	slliw	a5,a5,0x2 //将 a5 寄存器左移2位,相当于乘以4。
   103e0:	0007871b          	sext.w	a4,a5 //再次对 a5 执行符号扩展,结果存储在 a4 中。
   103e4:	fe442783          	lw	a5,-28(s0) //从栈帧中加载 a5 寄存器的值。
   103e8:	00f70463          	beq	a4,a5,103f0 <phase_3+0x58> //如果 a4 和 a5 相等,跳转到地址 103f0,否则继续执行。
   103ec:	e39ff0ef          	jal	ra,10224 <explode_bomb> //如果 a4 和 a5 不相等,会触发一个炸弹,程序终止。
   103f0:	000257b7          	lui	a5,0x25
   103f4:	90878513          	addi	a0,a5,-1784 # 24908 <__clzdi2+0x1e2>
   103f8:	2a9000ef          	jal	ra,10ea0 <puts> //设置 a0 寄存器的值,用于调用 puts 函数,打印一条信息。
   103fc:	0001                	nop
   103fe:	70a2                	ld	ra,40(sp)
   10400:	7402                	ld	s0,32(sp)
   10402:	6145                	addi	sp,sp,48
   10404:	8082                	ret

代码逻辑:程序将输入的值保存在a4中,然后将a5的值设为2,然后检查a5是否等于a4,如果不等于,则触发炸弹,然后又将a5的值左移两位,也就是相当于×2,然后将输入的a4a5进行比较,如果不相等就触发炸弹。

于是我们输入2 4即可通过该关卡

image.png

(4)phase_4分析:

0000000000010406 <phase_4>:
   10406:	715d                	addi	sp,sp,-80 
   10408:	e486                	sd	ra,72(sp)
   1040a:	e0a2                	sd	s0,64(sp)
   1040c:	0880                	addi	s0,sp,80
   1040e:	faa43c23          	sd	a0,-72(s0) //从栈帧中的偏移地址-72处加载值到 a0 寄存器中。这是函数参数的处理。
   10412:	fc040793          	addi	a5,s0,-64
   10416:	85be                	mv	a1,a5
   10418:	fb843503          	ld	a0,-72(s0)
   1041c:	0e0000ef          	jal	ra,104fc <read_six_numbers> //调用函数 read_six_numbers,该函数用于读取六个数字,并存储在内存中。
   10420:	fc040793          	addi	a5,s0,-64
   10424:	fef43423          	sd	a5,-24(s0)
   10428:	fc040793          	addi	a5,s0,-64
   1042c:	07d1                	addi	a5,a5,20
   1042e:	fef43023          	sd	a5,-32(s0)
   10432:	a805                	j	10462 <phase_4+0x5c>
   10434:	fe843783          	ld	a5,-24(s0)
   10438:	439c                	lw	a5,0(a5) //从 a5 寄存器指向的地址加载一个32位值到 a5 寄存器中。这个值是用户输入的数字之一。
   1043a:	0017979b          	slliw	a5,a5,0x1 //将 a5 寄存器中的值左移1位,相当于乘以2。这个操作可能是将输入数字乘以2。
   1043e:	fcf42e23          	sw	a5,-36(s0) //将 a5 寄存器的值保存在栈帧中的偏移地址-36处。这是对输入数字的处理结果。
   10442:	fe843783          	ld	a5,-24(s0)
   10446:	0791                	addi	a5,a5,4
   10448:	4398                	lw	a4,0(a5) //从 a5 寄存器指向的地址加载一个32位值到 a4 寄存器中。这个值是用户输入的数字之一。
   1044a:	fdc42783          	lw	a5,-36(s0) //从栈帧中的偏移地址-36处加载值到 a5 寄存器中。这是对输入数字的处理结果。
   1044e:	2781                	sext.w	a5,a5 //将 a5 寄存器中的值符号扩展为64位。这可能是将32位数字扩展为64位,以便与其他64位值进行比较。
   10450:	00e78463          	beq	a5,a4,10458 <phase_4+0x52> //继续执行。这是比较用户输入的数字和处理后的数字,如果相等,则跳转。
   10454:	dd1ff0ef          	jal	ra,10224 <explode_bomb> //然后跳转到地址 10224。如果比较不相等,将引发异常并调用 explode_bomb 函数。
   10458:	fe843783          	ld	a5,-24(s0)
   1045c:	0791                	addi	a5,a5,4
   1045e:	fef43423          	sd	a5,-24(s0)
   10462:	fe843703          	ld	a4,-24(s0)
   10466:	fe043783          	ld	a5,-32(s0)
   1046a:	fcf765e3          	bltu	a4,a5,10434 <phase_4+0x2e> //如果无符号整数比较 a4 寄存器小于 a5 寄存器,跳转到地址 10434 继续执行。这是另一个比较,用于确定是否继续执行函数。
   1046e:	000257b7          	lui	a5,0x25
   10472:	93078513          	addi	a0,a5,-1744 # 24930 <__clzdi2+0x20a>
   10476:	22b000ef          	jal	ra,10ea0 <puts>
   1047a:	0001                	nop
   1047c:	60a6                	ld	ra,72(sp)
   1047e:	6406                	ld	s0,64(sp)
   10480:	6161                	addi	sp,sp,80
   10482:	8082                	ret

代码逻辑:输入六个数,需要满足前一个数是后一个数的二倍

输入1 2 4 8 16 32过关

piMECMF.png

(5)phase_5 分析:

000000000010484 <phase_5>:
   10484:	7179                	addi	sp,sp,-48	//在栈中创建6个双字空间
   10486:	f406                	sd	    ra,40(sp)	//保存ra到sp+40
   10488:	f022                	sd	    s0,32(sp)	//保存s0寄存器到sp+32
   1048a:	1800                	addi	s0,sp,48	//设置s0寄存器为当前sp+48
   1048c:	fca43c23          	    sd	    a0,-40(s0)	//将传递给phase_5的第一个参数保存到s0-40
   10490:	fe440713          	    addi	a4,s0,-28	//计算s0减28的地址,将结果储存在寄存器a4中
   10494:	fe840793          	    addi	a5,s0,-24	//计算s0减24的地址,将结果储存在寄存器a4中
   10498:	86ba                	mv	    a3,a4		//将寄存器a4的值复制到a3
   1049a:	863e                	mv	    a2,a5		//将a5的值复制到a2
   1049c:	000257b7          	    lui	    a5,0x25		//将立即数0x25的高20位加载到寄存器a5的上半部分
   104a0:	8d878593          	    addi	a1,a5,-1832 # 248d8 <__clzdi2+0x1b2>	
   104a4:	fd843503          	    ld	    a0,-40(s0)	//将s0-40的位置加载值到寄存器a0
   104a8:	2bf000ef          	    jal	    ra,10f66 <sscanf>	//跳转并链接到地址为10f66处的ssacanf函数
   104ac:	87aa                	mv	    a5,a0		//将寄存器a0的值复制到寄存器a5
   104ae:	fef42623          	    sw	    a5,-20(s0)	//将寄存器a5的值存储到s0-20
   104b2:	fec42783          	    lw	    a5,-20(s0)	//从s0-20的位置加载值到寄存器a5
   104b6:	0007871b          	    sext.w	a4,a5		//将寄存器a5的值进行符号扩展,并将结果存储到寄存器a4中
   104ba:	4789                	li	    a5,2		//将立即数2加载到寄存器a5
   104bc:	00f70463          	    beq	    a4,a5,104c4 <phase_5+0x40>	//如果寄存器a4的值等于寄存器a5的值,则跳转到地址104c4处的指令,从而避开了炸弹
   104c0:	d65ff0ef          	    jal	    ra,10224 <explode_bomb>
   104c4:	fe842783          	    lw	    a5,-24(s0)	//从s0-24的位置加载值到寄存器a5
   104c8:	853e                	mv	    a0,a5		//将寄存器a5的值复制到a0
   104ca:	0a4000ef          	    jal	    ra,1056e <func4>	//跳转并连接到地址1056e处的func4函数(该函数作用解释在后)
   104ce:	87aa                	mv	    a5,a0		//将寄存器a0的值复制到a5
   104d0:	fef42623          	    sw	    a5,-20(s0)	
   104d4:	fe442703          	    lw	    a4,-28(s0)
   104d8:	fec42783          	    lw	    a5,-20(s0)
   104dc:	2781                	sext.w	a5,a5
   104de:	00e78463          	    beq	    a5,a4,104e6 <phase_5+0x62>	//等于跳转指令,用于跳过炸弹
   104e2:	d43ff0ef          	    jal	    ra,10224 <explode_bomb>
   104e6:	000257b7          	    lui	    a5,0x25
   104ea:	95078513          	    addi	a0,a5,-1712 # 24950 <__clzdi2+0x22a>
   104ee:	1b3000ef          	    jal	    ra,10ea0 <puts>
   104f2:	0001                	nop
   104f4:	70a2                	ld	    ra,40(sp)
   104f6:	7402                	ld	    s0,32(sp)
   104f8:	6145                	addi	sp,sp,48
   104fa:	8082                	ret

000000000001056e <func4>:
   1056e:	1101                	addi	sp,sp,-32	//从栈中创建4个双字空间
   10570:	ec06                	sd	    ra,24(sp)	
   10572:	e822                	sd	    s0,16(sp)
   10574:	1000                	addi	s0,sp,32	//创建新的s0为栈指针sp+32
   10576:	87aa                	mv	    a5,a0		//将a0的值复制给a5
   10578:	fef42623          	    sw	    a5,-20(s0)
   1057c:	fec42783          	    lw	    a5,-20(s0)
   10580:	2781                	sext.w	a5,a5
   10582:	e399                	bnez	a5,10588 <func4+0x1a>	//判断a5中的值是否为0,若不为0,跳转到10588
   10584:	4785                	li	    a5,1		//将立即数1加载到寄存器a5中
   10586:	a839                	j	    105a4 <func4+0x36>	//无条件跳转到105a4
   10588:	fec42783          	    lw	    a5,-20(s0)
   1058c:	37fd                	addiw	a5,a5,-1	//将寄存器a5的值-1,将结果存储在a5中
   1058e:	2781                	sext.w	a5,a5
   10590:	853e                	mv	    a0,a5		//将寄存器a5的值复制到a0
   10592:	fddff0ef          	    jal	    ra,1056e <func4>	//跳转并链接到func4
   10596:	87aa                	mv	    a5,a0		//将寄存器a0的值复制给a5
   10598:	873e                	mv	    a4,a5		//将寄存器a5的值复制给a4
   1059a:	fec42783          	    lw	    a5,-20(s0)
   1059e:	02e787bb          	    mulw	a5,a5,a4	//a5中的值与a4中的值相乘,存储到寄存器a5中
   105a2:	2781                	sext.w	a5,a5
   105a4:	853e                	mv	    a0,a5		//将a5中的值复制到a0中
   105a6:	60e2                	ld	    ra,24(sp)
   105a8:	6442                	ld	    s0,16(sp)
   105aa:	6105                	addi	sp,sp,32
   105ac:	8082                	ret

代码逻辑分析:

​ phase_5函数实现的功能主要是调用func4函数递归地传入a5作为参数,并将返回值存储在a5中,递归结束条件为a5=0,此时将a5中的值赋为1。如果a4==a5,则跳过炸弹。

​ 再分析func4函数,其逻辑是先判断a5中的值是否为0,不为0a5复制到a0a5中的值-1,递归调用func4函数,传入a5作为其参数,将a5中的值复制给a4,接着将a5a0,并将结果存储到a5中。分析可以得到func4是一个计算阶乘的函数。

​ 所以跳过炸弹,需要满足输入的第二个数是第一个数的阶乘。

​ 例如:输入2 2

piMVpFI.png

三、实验结果

piMViSf.png

四、实验总结

本次实验,我们小组学会了如何用qemu模拟运行一个risc-v架构的可执行文件,对课上学习的汇编指令有了更深刻的理解和认识,还学习到了对关键代码的解读和代码之前的关系对分析程序有着至关重要的作用