一、实验任务:
程序包括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即可过关。

(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成功!
(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,然后将输入的a4和a5进行比较,如果不相等就触发炸弹。
于是我们输入2 4即可通过该关卡

(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过关
(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,不为0则a5复制到a0,a5中的值-1,递归调用func4函数,传入a5作为其参数,将a5中的值复制给a4,接着将a5乘a0,并将结果存储到a5中。分析可以得到func4是一个计算阶乘的函数。
所以跳过炸弹,需要满足输入的第二个数是第一个数的阶乘。
例如:输入2 2
三、实验结果
四、实验总结
本次实验,我们小组学会了如何用qemu模拟运行一个risc-v架构的可执行文件,对课上学习的汇编指令有了更深刻的理解和认识,还学习到了对关键代码的解读和代码之前的关系对分析程序有着至关重要的作用



