HNU计算机系统实验三 BombLab

写在前面

这个实验解题+写报告花了我假期两个半天的时间(16h左右),认真做了以后发现对掌握汇编语言和熟悉函数调用过程有很大的帮助!

当然,这个实验每个人分配到的bomb都是不一样的,关键是要学习解题思路哦。

1 实验项目

1.1 项目名称

实验3 BombLab

1.2 实验目的

(1)反汇编出汇编代码,结合c语言文件找到每个关卡的入口函数。然后分析汇编代码并利用gdb调试工具,得到每一关的通关密码,最终拆除炸弹;

(2)进一步加深对linux指令的理解,增加对gdb调试操作的了解;

(3)熟悉汇编语言,能根据汇编代码写出对应的c语言伪代码;

(4)熟悉并掌握函数调用过程中的栈帧结构的变化。

1.3 实验资源

(1)教材中Bomblab的相关内容;

(2)实验文件夹中相关的各文件及程序;

(3)gdb调试工具

2 实验任务

2.1 phase_1

首先,我们先使用objdump -d bomb > my_bomb.txt指令将可执行文件bomb反汇编成汇编代码,然后再查看汇编代码寻找答案。

从这里开始就是main函数了,我们继续往下寻找,就能找到phase_1的汇编代码。如下图所示:

我们可以看到,在进行参数传递以后,代码调用了一个名叫strings_not_equal的函数。从函数的名字我们不难推断,这是一个判断两个字符串是否相等的函数。在函数执行完以后,test %eax,%eax指令对寄存器eax中的值进行&操作,如果eax中的值为0,test操作就会得到0(设置ZF为1);而如果eax中的值不为0,test操作得到的结果就不是0(ZF不被设置)。接下来执行je操作时,如果上面test操作得到的是0也就是eax中的值为0,就会跳转到add操作那一行,此时炸弹不爆炸。如果eax中的值不为0,就不会跳转,顺序调用explode_bomb函数,炸弹爆炸。因此,上面的strings_not_equal函数的具体意义是,如果传入的两个字符串相等,则返回0到寄存器eax中,反之则返回1到寄存器eax中。为了使炸弹不爆炸,我们需要让传入的两个字符串参数相等。从调用strings_not_equal函数之前的汇编代码我们可以看出,我们自己输入的参数(字符串)是通过下面这两行代码传递的:

而另一个参数则是通过如下这一行代码传递的:

因此我们不难得出,需要我们输入的字符串即为保存在内存地址0x804a1a4中的字符串。那么接下来,我们可以用gdb调试查看一下内存地址0x804a1a4处的内容。具体操作如下,其中0x804a1a4是我们感兴趣的内存地址,x是检查内存的命令,/s是指示gdb以字符串的形式显示内存内容。

顺利地,我们得到了需要的答案:"Houses will begat jobs, jobs will begat houses."这个字符串。接下来,我们输入答案进行验证:

可以看到,Phase 1成功解决,验证成功。

2.2 phase_2

接下来,我们进行phase_2的解决。反汇编得到的phase_2汇编代码如下:

前面一部分是开辟栈帧和传参的操作,这里不再赘述。然后我们可以看到,代码调用了一个名叫read_six_numbers的函数,顾名思义,这个函数就是要读取我们输入的6个数,然后与答案进行比较,如果相同的话这一关就通过了。

那么接下来我们先研究第一个数。

由上面这几行汇编代码可以看出,程序先将0x18(%esp)这个地址处的参数值与1进行比较,如果相同则跳转下去继续执行,如果不同则调用炸弹函数发生爆炸。因此我们可以得出,我们需要的第一个数就是1。

而接下来这两行,把两个地址分别传入了寄存器ebx和寄存器esi。那么这两个地址是什么的地址呢?我们可以进行一下推测。上面我们得到,第一个数的地址为0x18(%esp),而函数read_six_numbers又要读取6个数,根据32位Ubuntu的传参规则,其余5个参数的起始地址分别为为0x1c(%esp),0x20(%esp),0x24(%esp),0x28(%esp),0x2c(%esp)。因此,我们可以得到,寄存器ebx里存的是第二个参数的起始地址,寄存器esi里存的是第六个参数的结束地址。

接下来,将地址-0x4(%ebx)处的值赋给寄存器eax,由于寄存器ebx里存的是第二个参数的起始地址,那么地址-0x4(%ebx)即为第一个参数的起始地址,所以寄存器eax的值为第一个参数1。接下来的add指令使得%eax中的值变为2。然后使用cmp指令比较%eax中的值与地址(%ebx)处的值,即比较2和第二个参数的值。如果相等则跳转继续执行,如果不等则顺序执行,调用爆炸函数。因此,我们需要的第二个数即为2。

接下来,将%ebx中的值加4,此时寄存器ebx里存的是第三个参数的起始地址。然后再比较%esi和%ebx的值,如果不等即还没有遍历完剩下的参数,则跳转到上面的地址处继续执行。这里其实就是一个循环,直到访问完所有的参数以后循环才会结束。

现在再执行这些操作时,会将第二个参数的两倍与第三个参数进行比较,不相等则爆炸,相等则继续。因此我们需要的第三个数的值即为第二个数的两倍,也就是4。以此类推,需要的第四个数的值为第三个数的两倍,即8;需要的第五个数的值为第四个数的两倍,即16;需要的第六个数的值为第五个数的两倍,即32。

当这些数都遍历过后,下面这三行指令最后一次执行,这次%esi和%ebx中的值相等,不会跳转,顺序执行关闭栈帧及恢复寄存器等操作。

因此,要顺利通过phase_2,我们需要输入的6个数为:1、2、4、8、16、32。接下来,我们输入答案进行验证:

可以看到,Phase 2成功解决,验证成功。

2.3 phase_3

接下来,我们进行phase_3的解决。反汇编得到的phase_3汇编代码如下:

我们可以看到,前面首先进行了一系列的参数传递操作,然后调用了sscanf函数,那么前面那些参数是什么东西呢?这需要我们了解C语言中sscanf函数的相关知识。

在C语言中,sscanf函数使用一个格式字符串来解析一个字符串中的数据。

sscanf函数首先将我们输入的东西当成一个字符串,输入字符串的地址位于第一个参数位置。

而第二个参数是格式字符串的地址,格式字符串告诉sscanf如何从输入的字符串中解析数据。

第三个和第四个参数是额外的指针参数,它们提供了存储解析后的数据的位置。

那么首先,我们来看一下0x804a3c3这个地址处的格式字符串是什么:

可以看到,0x804a3c3处的字符串内容是"%d %d",那么`sscanf`期望的参数列表如下:

1. 输入字符串的地址:这是一个指向包含要解析的实际字符数据的内存位置的指针。

2. 格式字符串的地址:这是一个指向格式字符串 "%d %d" 的内存位置的指针,它告诉 sscanf 函数如何从输入字符串中解析数据。

3. 第一个整数存储位置的地址0x18(%esp):这是一个指向 int 类型变量的指针,sscanf 函数将在这里存储从输入字符串中解析出的第一个整数。

4. 第二个整数存储位置的地址0x1c(%esp):这是另一个指向 int 类型变量的指针,sscanf 函数将在这里存储从输入字符串中解析出的第二个整数。

我们继续分析调用sscanf函数返回后要执行的汇编代码。

sscanf 函数的返回值是一个整数,表示成功填充到传入参数的输入格式的数量。例如上面的格式字符串 "%d %d",如果输入字符串包含两个有效的整数,则 sscanf 将返回 2,表示两个整数都被成功解析并存储到了提供的地址。

因此这的代码意思是,如果%eax里的返回值比1大也就是我确实输入了2个整数,就跳转继续执行;如果我没有按格式输入,就爆炸。

我们可以看到,接下来的jmp指令是间接跳转,这很明显是我们学过的跳转表的寻址方式。因此,前面cmpl指令是将地址0x18(%esp)处的值也即解析出的第一个整数与7进行比较,这第一个整数便是switch语句中的n。注意,下面的ja是无符号数大于就跳转,因此,如果n大于7,或者n小于0(负数的二进制表示用无符号数来解释的话,会变成一个很大的数),都会跳转到地址0x8048c5a处,触发炸弹。因此,第一个整数即n的范围是0≤n≤7。

从这条语句中可以看出,跳转表首地址为0x804a200,又因为n一共有8个取值可能,因此我们用gdb来看一下跳转表里的内容。

可以看出,每个n都对应了一段代码。接下来我们可以对n取不同值的情况分别进行分析。

在分析过程中,我发现汇编代码最后有一个验证环节。首先,将地址0x18(%esp)处的值也即n与5进行比较,比5大的话会跳转到爆炸函数。因此我们又要把n的范围缩小到0≤n≤5。然后,如果继续执行,会将地址0x1c(%esp)处的值也即我们输入的第二个整数与%eax即我们在不同分支算出来的数相比较,如果相等就跳转,通过测试;如果不等就会顺序执行到爆炸函数。

现在,phase_3的解决方法已经很明了了。我们要输入两个数,第一个数n要在0~5之内,第二个数是根据n的值选择分支算出来的数。

经过计算,可行的搭配如下:0 -519,1 -989,2 -77,3 -296,4 0,5 -296。

接下来,我们输入答案进行验证:

  

  

可以看到,Phase 3成功解决,验证成功。

2.4 phase_4

接下来,我们进行phase_4的解决。反汇编得到的phase_4汇编代码如下:

与phase_3中一样,我们可以看到,前面首先进行了一系列的参数传递操作,准备了四个参数,然后调用了sscanf函数。

我们知道,0x804a3c3这个地址处的格式字符串是"%d %d"。

那么到这里与phase_3可以说是完全相同的。

接下来将%eax中的返回值与2进行比较,如果输入的数的个数不为2,则会爆炸。然后,js跳转表明如果输入的第一个数是负数,会爆炸。此外,jle跳转表明,如果输入的第一个数大于14,也会爆炸。因此,从这一块代码我们得知,输入的第一个数的范围是0≤n≤14。

继续看代码,又进行了一系列传参,然后调用了func4函数。第一个参数是我们输入的第一个数n,第二个参数是0,第三个参数是14。那么接下来,我们来看一下这个func4函数做了什么,以及它返回什么。

func4函数汇编代码如下:

经过长时间的分析,我得出这个函数其实是一个二分查找函数。它有三个参数,第一次从phase_4进入这个函数时,%eax中存的是n,%edx中存的是0,%esi中存的是14。n就是我们要查找的值,0是查找范围的下界,14是查找范围的上界。

比如说输入的n为4,首先与中间值7比较,发现小于7,于是范围缩小到0~6。接着与中间值3比较,发现大于3,于是范围缩小到4~6。再与中间值5比较,发现小于5,最终范围缩小到4~4。最后一次递归调用func4后,得到我们要查找的值4。一层一层地返回,最终我们得到的返回值是4+5+3+7=19。即该函数返回值为输入的n加上查找它所需的所有中间值。

当然,还有一种直接得到返回值是什么的方法,这里我们使用这种方法先验证一下我们分析的正确性。这种方法不用分析func4函数,而是直接使用gdb调试工具在phase_4调用func4函数后设置断点,然后再查看一下寄存器eax,即可得到返回值。如图,我输入的n是4,调用func4后eax中的返回值确实是19。

现在我们知道了func4函数是干什么的,可以回到phase_4中继续往下看了。

可以看到,调用func4函数后,要将返回值与0xb也就是11进行比较,如果不等就跳转到爆炸函数。因此,我们输入的第一个数n要使得func4函数的返回值为11才行。根据上面n的取值范围为0~14以及对func4函数的分析,我们可以知道,只有输入n为0,1时返回值才为11。因此我们输入的第一个数有两个取值,为0或1。

然后,会将地址0x1c(%esp)处的值即我们输入的第二个数与11进行比较,如果相等就跳转,不爆炸;不等就会爆炸。因此,第二个数为11。

综上,可行的搭配如下:0 11,1 11。

接下来,我们输入答案进行验证:

可以看到,Phase 4成功解决,验证成功。

2.5 phase_5

接下来,我们进行phase_5的解决。反汇编得到的phase_5汇编代码如下:

首先有一个要注意的点,就是函数string_length的参数。

在大多数汇编语言和 C 语言的约定中,当使用函数string_length来计算字符串的长度时,该函数的参数是字符串的地址,而不是字符串本身。这是因为字符串可以非常长,并且在函数调用中传递整个字符串是不切实际的,特别是在栈空间有限的情况下。所以,通常会传递字符串的指针(或地址),这个指针指向内存中字符串的起始位置。函数然后通过这个指针遍历字符串,直到遇到字符串结束的标志,通常是空字符('\0'),然后返回计数的长度。

因此,汇编代码开头一部分先把我们输入的字符串的起始地址给%ebx,并作为函数string_length的参数,计算出字符串大小后,必须要等于6才不会爆炸。也就是说我们的字符串里有6个字符。

然后进入循环,我们可以看出,%eax中的值实际上是一个循环计数器i,它从0开始一直加到6。而movsbl (%ebx,%eax,1),%ecx这条指令实际上就是将str[i]这个字符的ASCII码值赋给%ecx,再通过与运算获得它的低四位。

得到低四位之后,用它作为索引从内存地址0x804a220(,%ecx,4)处取值加到%edx中。循环结束后,将%edx中获得的累加值与0x27即39进行比较,如果相等则通过,不相等则爆炸。

以上就是phase_5的大致流程。接下来为了解决它,我们需要先看一下内存地址0x804a220处的表中都有些什么值。用gdb调试工具查看内存如下:

这里选择查看从地址0x804a220开始的16个连续的四字节整数,并以十进制格式显示它们。因为ASCII码低四位的范围是0000~1111,也即0~15共16个取值。

我们取ASCII码表中前16个大写字母A~P,其末尾正好包含了0000~1111的所有取值。因此,我们可以得出对应关系:A=+10,B=+6,C=+1,D=+12,E=+16,F=+9,G=+3,H=+4,I=+7,J=+14,K=+5,L=+11,M=+8,N=+15,O=+13,P=+2。

    

因此,要使得最终的累加值为39,包含6个字符的字符串可以是“ABCDFC”,即10+6+1+12+9+1=39。当然,答案不唯一。

接下来,我们输入答案进行验证:

可以看到,Phase 5成功解决,验证成功。

2.6 phase_6

接下来,我们进行phase_6的解决。反汇编得到的phase_6汇编代码如下:

可以看到,phase_6的汇编代码非常的长,因此接下来我们逐块进行分析。

首先,这一块代码是传参部分。我们不难看出,phase_6需要我们输入6个数字,其中解析出的数字存放的起始地址为0x10(%esp)。

然后,这一部分可以看作是对我们输入的6个数字的合规性检查。首先,这6个数的数值都必须在1~6之间,不然会爆炸。其次,这6个数每个都必须不同,不然也会爆炸。

这两个要求在这里是通过内外两层循环来保证的,通过对汇编代码的分析,我写了一个C语言代码来比较清晰地展示这个过程。合规性检测如下:

合规性检测结束后,跳转到地址0x8048e05处。然后设置ebx=i,初始化为0,esi为ebx的一个备份。接下来取a[i]与1相比,如果a[i]=1,跳转到0x8048df7处;如果a[i]>1,跳转到0x8048ded处。

如果a[i]=1,我们则将地址0x28(%esp,%esi,4)处的值设置为%edx的值(此时为0x804c13c)。为了方便解释,我们将地址0x28(%esp,%esi,4)处的值标作b[i]。

而如果a[i]>1,我们则将%edx的值更新为地址0x8(%edx)处的值。然后将%eax的值加1并与a[i]比较,如果不等则继续循环,再将%edx的值更新为地址0x8(%edx)处的值,将%eax的值加1并与a[i]比较,直至%edx的值与a[i]相等。

为了搞明白这里进行的到底是什么操作,我们需要看一下地址0x804c13c往后的内存中存的值究竟是什么。使用gdb查看结果如下:

我们在这里选择查看从地址0x804c13c开始的6*3=18个连续的四字节,并以十六进制格式显示它们。为什么这么查看呢?因为我猜测,0x8(%edx)处的值也是一个地址,这样把它赋值给%edx后,才能使得后续循环时%edx中的值始终是一个地址。

现在一切变得明了一些了。我们可以发现,这实际上是一个链表型的结构,每个节点占12个字节,而这12个字节被分成三个4字节,用来存3样东西。通过对图片的分析我们可以得出,节点的第二项数据就是我们输入的a[i]的值;而每次的0x8(%edx)其实就是访问当前节点中的第三项数据,也就是下一节点的起始地址。

那么,如果a[i]=1时,b[i]=0x804c13c;如果a[i]>1,则通过循环找到a[i]这个数值对应的节点,并将节点的地址(0x804c148、0x804c154、0x804c160、0x804c16c、0x804c178中之一)移到b[i]中。

为了更直观地说明这个过程,编写的C代码如下:

接下来这一段汇编代码比较好理解,根据我写的注释可以看出,这实际上就是将链表进行了重排。

比如说一开始我们输入的a[0]到a[5]分别是6、2、4、5、3、1,那么初始链表0x804c13c—0x804c148—0x804c154—0x804c160—0x804c16c—0x804c178为b[5]—b[1]—b[4]—b[2]—b[3]—b[0];经过重排后,链表变成了b[0]—b[1]—b[2]—b[3]—b[4]—b[5] 也就是0x804c178—0x804c148—0x804c160—0x804c16c—0x804c154—0x804c13c即node6—node2—node4—node5—node3—node1。因此,最终的链表节点顺序即为我们输入的数字顺序。

现在到了最终环节。还是一个循环,这个循环中会比较我们上面经过重排后的链表中的每个节点的第一个数值。如果前一个节点的第一个数值小于后一个节点,就会发生爆炸。因此我们需要保证链表的节点的第一个数值是非严格递减的。

由我们用gdb查看的内存值可以得知,如果要保证链表的节点的第一个数值是非严格递减的,我们的链表应该为node6—node3—node5—node2—node4—node1,即我们的输入应该为6 3 5 2 4 1这六个数字。

接下来,我们输入答案进行验证:

可以看到,Phase 6成功解决,验证成功。

2.7 phase_defused

现在我们已经解决了6个关卡,然而,似乎还漏掉了些什么。

比如bomb.c这个源文件中,每次完成一个phase,它都会调用一次phase_defused()这个函数。此外,在phase_6的这一块代码后面,作者还给了一行注释,提醒我们似乎忽略了什么。于是意识到,还有隐藏关卡!

我们先来看看这个phase_defused函数的汇编代码:

首先我们可以看到进行了一个比较,把内存地址0x804c3cc处的值与6进行比较,如果不相等则跳出,相等则继续执行。这是什么呢?我们来看一下内存地址0x804c3cc处的值。

在程序开始之前,0x804c3cc处的值为0。

而在解决phase_1后,0x804c3cc处的值为1。

一步步调试发现,每完成一个拆弹任务,0x804c3cc处的值都会加1。因此,此处的跳转意为,如果前6关都通过则继续执行,否则跳出phase_defused函数。

然后是我们熟悉的传入五个参数然后调用sscanf函数。

查看格式字符串的地址处的值,得到“%d %d %s”,说明有个地方输入两个整数以后还要输入一个字符串。

如果输入的个数不是3个的话,跳转打印"Congratulations! You've defused the bomb!",不会触发隐藏关。

然后,准备调用strings_not_equal函数,第一个参数是我们输入的字符串的地址,第二个参数是地址0x804a3d2,我们用gdb查看内存地址0x804a3d2处的值,得到字符串“DrEvil”。

如果输入的字符串不是“DrEvil”的话,也会跳转打印"Congratulations! You've defused the bomb!",不会触发隐藏关。

那么,我们要在哪里输入两个整数以后再输入字符串“DrEvil”,从而触发隐藏关呢?

注意到,传入sscanf函数的第一个参数是我们输入的字符串的地址。由汇编代码可以看出,该地址为0x804c4d0。那么,我们设置断点在完成所有输入后,再使用gdb查看一下0x804c4d0处的值,不就能知道它看的是哪个输入了吗!

gdb查看内存信息如下:

可以看到,这是我们在phase_4的输入。那么,如果我们在第四关中输入两个整数后,再输入字符串“DrEvil”,就会触发隐藏关卡。

从下面这张图中显示的后续汇编代码也进一步验证了满足以上条件后,会进入隐藏关卡,即调用函数secret_phase。

其中,地址0x804a298和地址0x804a2c0处分别是提示进入隐藏关卡的两个字符串。

2.8 secret_phase

接下来,我们正式进入secret_phase的分析。其汇编代码如下:

首先,调用了read_line函数,即从输入中读取一行,其返回值为我们输入的字符串的起始地址,保存在%eax里。

接下来进行传参并调用strtol函数。strtol函数是一个用于字符串转换为长整型(long integer)的标准库函数。其全名是“string to long”,该函数可以解析一个字符串中的数字,并将其转换成一个长整数。

在C语言中,strtol的函数原型是long int strtol(const char *str, char **endptr, int base);它有三个参数,第一个参数str是指向要转换的字符串的指针。第二个参数endptr是一个字符指针的地址,用于存储原始字符串中不再进行转换的第一个字符的地址。如果不需要这个信息,可以设置为 NULL。第三个参数base用于指定转换时所用的基数。范围为2至36,在这里是0xa,即转换为十进制。

接下来,代码将转换得到的数字存入ebx,然后将eax减去1后与1000比较,可以看出eax的范围为0<=eax<=1000,否则会爆炸。也就是说,我们输入的数字必须在1~1001之间。

然后会调用fun7函数,其第二个参数为我们输入的十进制数,第一个参数是地址0x804c088,这个地址处的值我们可以用gdb查看一下,为0x24即36。

然后,会检查fun7函数的返回值,如果返回值是3则通过隐藏关,否则会爆炸。因此,我们需要具体分析fun7函数,从而使它的返回值为3。

fun7函数汇编代码如下:

首先,程序会用%edx保存我们传入的第一个参数:地址x,用%ecx保存我们传入的第二个参数:输入的数input。如果地址x的值为0,则会直接返回-1,表示错误。

接下来是fun7函数的重头戏。首先,比较地址x处的值与我们输入的数input。如果input小于地址x处的值,那么将参数1设置为地址(x+4)处的值,参数2还是我们的输入input,然后递归调用fun7函数,并最终返回2fun7()。

如果input等于地址x处的值,就直接返回0。

而如果input大于地址x处的值,则将参数1设置为地址(x+8)处的值,参数2还是我们的输入input,然后递归调用fun7函数,并最终返回2fun7()+1。

这种小于和大于分别继续递归寻找,等于就返回的操作,很像二叉树的查找操作。因此,以16进制查看一下从地址0x804c088开始的地址空间,可以看出,这确实是一个二叉检索树的结构。可以看到,一个节点有三个域,第一个域里面保存了数据,后两个域里面保存的都是地址。

为了更清楚的查看每个节点的值,我们换一种方式来查看内存。每次打印三个字,也就是一个节点的所有信息,并以从上到下、从左到右的顺序打印。如下图所示:

不难看出,每个节点的第一个值是它的数据,第二个值也就是地址(x+4)处的值是它的左孩子的地址,第三个值也即是地址(x+8)处的值是它的右孩子的地址。

为了更直观的展示,我将该树图形化,如下图所示:

也就是说,上面的fun7函数其实就是在二叉检索树中寻找我们的input值。如果input值比当前节点的数据域中的值小,那么就递归查看它的左孩子;如果input值比当前节点的数据域中的值大,那么就递归查看它的右孩子,直到找到我们的input值为止。那么我们如何使得它的返回值最终为3呢?

根据上面的分析,我们可以先写出fun7函数的总的表达式如下:

我们可以结合表达式和树来得出如果要使得返回值为3,input应该是多少。

首先,如果我们找到了这个二叉树中的一个值的话,最后一次fun7调用的返回值一定是0,因为最后一次input一定等于*x才是找到。

而3可以分解为2+1也就是2*1+1也就是2*(2*0+1)+1。因此,也就是先访问一次右孩子,再访问一次右孩子,然后找到了我们的input。对应树来看,访问两次右孩子找到我们的input,因此input为107。

接下来,我们输入答案进行验证:

可以看到,secret_phase成功解决,验证成功。

答案一次性验证

最后,我们可以把所有答案保存在ans.txt中,从而一次验证所有关卡。

一次验证结果如下:

到这里,BombLab这个实验算是圆满结束了。

3 总结

3.1 实验中出现的问题

1. 在一开始运行bomb的时候,遇到权限不够的问题。于是使用chmod更改bomb的权限,最终成功运行。

2. 在做题过程中,遇到很多只通过阅读汇编代码以及手写分析很难解决的问题,于是借助gdb调试工具,最终成功获得所需要的数据。

比如在寻找隐藏关卡的入口时,只阅读汇编代码其实并不能知道应该在哪里多输入一个字符串才能进入。而使用gdb查看相应参数所在的内存地址中存储的值,就能够很清楚的知道入口到底在我们的哪个输入了。

 

3. 实验所给的汇编代码非常多,其中有很多函数调用。然而,对于这些函数调用的汇编代码,如果每个都一行一行细看就会耽误大量时间。因此,对于有的函数,比如说strings_not_equal和read_six_numbers等,它们的名字就能够很好地表示它们的含义,因此我们不必再去逐句阅读它们的汇编代码了。此外,对于sscanf等函数,我们可以上网查询它们的传参规则、各个参数的含义以及返回值等等,这样能够让我们更好的理解对应的函数调用以及参数传递的汇编代码。

3.2 心得体会

纸上得来终觉浅,绝知此事要躬行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值