CSAPP实验之Bomb Lab详解
前言
Bomb Lab来自《深入理解计算机系统》(CSAPP)一书的第三章“程序的机器级表示”的配套实验,该实验的目的是通过反汇编可执行程序,来反推出程序执行内容,进而能够正确破解”密码“,解除“炸弹”。
Bomb Lab文件目录如下:
├── bomb
├── bomb.c
└── README
- bomb: 可执行程序,我们需要对其进行反汇编和gdb调试。
- bomb.c: bomb的主函数main的源文件。
- README: 无关紧要。
通过阅读 bomb.c 可以知道该程序的用法:
/**************************************************************************** Dr. Evil's Insidious Bomb, Version 1.1* Copyright 2011, Dr. Evil Incorporated. All rights reserved.** LICENSE:** Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the* VICTIM) explicit permission to use this bomb (the BOMB). This is a* time limited license, which expires on the death of the VICTIM.* The PERPETRATOR takes no responsibility for damage, frustration,* insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other* harm to the VICTIM. Unless the PERPETRATOR wants to take credit,* that is. The VICTIM may not distribute this bomb source code to* any enemies of the PERPETRATOR. No VICTIM may debug,* reverse-engineer, run "strings" on, decompile, decrypt, or use any* other technique to gain knowledge of and defuse the BOMB. BOMB* proof clothing may not be worn when handling this program. The* PERPETRATOR will not apologize for the PERPETRATOR's poor sense of* humor. This license is null and void where the BOMB is prohibited* by law.***************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"/* * Note to self: Remember to erase this file so my victims will have no* idea what is going on, and so they will all blow up in a* spectaculary fiendish explosion. -- Dr. Evil */FILE *infile;int main(int argc, char *argv[])
{char *input;/* Note to self: remember to port this bomb to Windows and put a * fantastic GUI on it. *//* When run with no arguments, the bomb reads its input lines * from standard input. */if (argc == 1) { infile = stdin;} /* When run with one argument <file>, the bomb reads from <file> * until EOF, and then switches to standard input. Thus, as you * defuse each phase, you can add its defusing string to <file> and* avoid having to retype it. */else if (argc == 2) {if (!(infile = fopen(argv[1], "r"))) {printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);exit(8);}}/* You can't call the bomb with more than 1 command line argument. */else {printf("Usage: %s [<input_file>]\n", argv[0]);exit(8);}/* Do all sorts of secret stuff that makes the bomb harder to defuse. */initialize_bomb();printf("Welcome to my fiendish little bomb. You have 6 phases with\n");printf("which to blow yourself up. Have a nice day!\n");/* Hmm... Six phases must be more secure than one phase! */input = read_line(); /* Get input */phase_1(input); /* Run the phase */phase_defused(); /* Drat! They figured it out!* Let me know how they did it. */printf("Phase 1 defused. How about the next one?\n");/* The second phase is harder. No one will ever figure out* how to defuse this... */input = read_line();phase_2(input);phase_defused();printf("That's number 2. Keep going!\n");/* I guess this is too easy so far. Some more complex code will* confuse people. */input = read_line();phase_3(input);phase_defused();printf("Halfway there!\n");/* Oh yeah? Well, how good is your math? Try on this saucy problem! */input = read_line();phase_4(input);phase_defused();printf("So you got that one. Try this one.\n");/* Round and 'round in memory we go, where we stop, the bomb blows! */input = read_line();phase_5(input);phase_defused();printf("Good work! On to the next...\n");/* This phase will never be used, since no one will get past the* earlier ones. But just in case, make this one extra hard. */input = read_line();phase_6(input);phase_defused();/* Wow, they got it! But isn't something... missing? Perhaps* something they overlooked? Mua ha ha ha ha! */return 0;
}
可以看出该可执行程序要求从命令行或者文件以 行 为单位读入字符串,每行字符串对应一个phase的输入。如果phase执行完毕,会调用phase_defused 函数表明该 phase 成功搞定。
该实验共有6个 phase,难度是逐级提升,考点也不尽相同。
首先需要对bomb进行反汇编:
objdump -d bomb > bomb.s
可以在 bomb.s 中找到这6个phase的汇编:
0000000000400ee0 <phase_1>:.
...
0000000000400efc <phase_2>:
...
0000000000400f43 <phase_3>:
...
000000000040100c <phase_4>:
...
0000000000401062 <phase_5>:
...
00000000004010f4 <phase_6>:
...
对于汇编代码的解读,我自己的方法是三步走,第一步可读性调整,不改变汇编的顺序只是改变符号为c风格;第二步是c-like风格调整,将控制结构改为c风格;第三步是说人话风格调整,c代码。
phase 1
开胃菜,考点:字符串。
本题就无须三步了,直接对汇编稍作c-like风格调整后,可以表示为以下代码:
phase_1(rdi)
{esi = 0x402400;string_no_equal(rdi, esi);if (eax == 0)callq explode_bomb;retq
}
其中0x402400
是常量字符串的地址,我们可以通过 gdb 调试 bomb,来打印出这段字符串。
$ gdb bomb
...
...
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."
可以知道这串字符串是 Border relations with Canada have never been better.
。
那么这段汇编用c来表示就是:
void phase_1(char* output)
{if( string_not_equal(output, "Border relations with Canada have never been better.") == 0) explode_bomb();return;
}
其中 string_not_equal是接受两个参数,对两个字符串进行比较,如果不同则输出0。
因此phase_1的功能:
- 字符串比较,如果输入的字符串与预设的字符串不相等,则”炸弹爆炸“。
答案:“Border relations with Canada have never been better.”
phase 2
考点:
- 数组
- 循环
这里分三步走,首先对汇编进行可读性调整:
phase_2(rdi)
{rsi = rsp;callq read_six_number;if (*rsp == 1)goto 400f30;else callq explode_bomb;goto 400f30;
400f17:eax = *(rbx-4);eax += eax;if (eax == *rbx)goto 400f25;else callq explode_bomb;
400f25:rbx += 4; # 下一个元素if (rbx != rbp)goto 400f17; # 循环else goto 400f3c;
400f30:rbx = rsp + 4; # 初始化 initrbp = rsp + 24; # 数组结束goto 400f17;
400f3c:retq
}
然后我们接着分析汇编表达的意思,从以上代码可以看出,read_six_number接受两个参数 rdi和rsi,rdi是输入的字符串,rsi 是栈上分配的整数数组。
通过阅读 read_six_number 汇编代码,可以直到 read_six_number 是把字符串转为6个数字,存储在整数数组中。
从 400f17 到 400f3c,可以看出这是一个循环,从rbx到rbp,指向的是栈上的整数数组。
因此6个数字的地址为
rsp | rsp+0x4 | rsp+0x8 | rsp+0xc | rsp+0x14 | rsp+0x18 |
---|
第二步是c-like风格的可读性调整:
phase_2(edi)
{rsi = rsp;read_six_number(rdi, rsi);if (*rsp != 1)explode_bomb();rbx = rsp + 4;rbp = rsp + 24;do {eax = *(rbx-4);eax += eax;if (eax != *rbx)explode_bomb();rbx += 4;} while(rbx != rbp);retq;
}
形势逐渐明朗,可以比较明确看出phase_2程序所表达的意思:
读入字符串并解析,然后通过循环对数组的每个元素进行处理。
第三步,说人话阶段,使用c代码来表示 phase_2:
void phase_2(char* output)
{int array[6];read_six_numbers(output, array);if (array[0] != 1) explode_bomb();for (int i = 1; i < 6; i++) {array[i-1] += array[i-1];if (array[i] != array[i-1])explode_bomb();}return;
}
通过c代码,可以看出phase_2的功能:
- 将在字符串解析为6个数字的整数数组array
- array第一个整数必须为1
- array中每个元素必须是前一个元素的两倍
答案:1 2 4 8 16 32
phase 3
考点:
- switch-case
首先对汇编代码可读性调整:
phase_3(rdi)
{# 省略分配栈上空间rcx = rsp + 0xc;rdx = rsp + 0x8;esi = 0x4025cf;eax = 0;sccanf(rdi, esi, rdx, rcx);if (eax <= 1)explode_bomb();if(*(rsp + 8) > 7) {explode_bomb();}goto *(0x402470+rax*8);
400f7c:eax = 0xcf;goto 400fbe;
400f83:eax = 0x2c3;goto 400fbe;
400f8a:eax = 0x100;goto 400fbe;
400f91:eax = 0x185;goto 400fbe;
400f98:eax = 0xce;goto 400fbe;
400f9f:eax = 0x2aa;goto 400fbe;
400fa6:eax = 0x147;goto 400fbe;eax = 0;goto 400fbe;
400fb9:eax = 0x137;
400fbe:if (eax != *(rsp+0xc))explode_bomb();retq;
}
首先是通过 sscanf 解析字符串,通过gdb调试可看出sscanf的format是%d %d
。 因此推断输出两个数字。
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
其次通过一个跳转表来进行地址跳转,该跳转表内容同样可以用gdb查看:
(gdb) x/16a 0x402470
0x402470: 0x400f7c <phase_3+57> 0x400fb9 <phase_3+118>
0x402480: 0x400f83 <phase_3+64> 0x400f8a <phase_3+71>
0x402490: 0x400f91 <phase_3+78> 0x400f98 <phase_3+85>
0x4024a0: 0x400f9f <phase_3+92> 0x400fa6 <phase_3+99>
可以看出是正好对应着8个对eax赋值的位置:
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
address | 0x400f7c | 0x400fb9 | 0x400f83 | 0x400f8a | 0x400f91 | 0x400f98 | 0x400f9f | 0x400fa6 |
%eax | 207 | 311 | 707 | 256 | 389 | 206 | 682 | 327 |
c代码表示如下:
void phase_3(char* output)
{int x, y;if(sscanf(output, "%d %d", &x, &y) <= 1)explode_bomb();if(x > 7)explode_bomb();int num;switch(x) {case 0:num = 207;break;case 1:num = 311;break;case 2:num = 707;break;case 3:num = 256;break;case 4:num = 389;break;case 5:num = 206;break;case 6:num = 682;break;case 7:num = 327;}if (num != y)explode_bomb();return;
}
通过分析,phase_3的功能:
- 将字符串解析为两个数字x, y
- x不能大于7
- 通过switch-case可以将x的值映射为一个num值
- y必须等于num
答案(多选):
- 0 207
- 1 311
- 2 707
- 3 256
- 4 389
- 5 206
- 6 682
- 7 327
phase 4
考点:
- 递归
phase 4会有两个函数,先来看主函数,同样第一步先对原汇编进行可读性调整:
phase_4(rdi)
{# 省略分配栈上空间rcx = rsp + 12; # sscanf 参数 4rdx = rsp + 8; # sscanf 参数 3rsi = 0x4025cf; # sscanf 参数 2rax = 0;callq sscanf;if (rax != 2) { # sscanf 返回值判断goto 401035;}if (0x8(rsp) <= 14) { # 第一个数字goto 40103a;}
401035:goto explode_bomb;
40103a:edx = 14; # func4 参数 3rsi = 0; # func4 参数 2rdi = 0x8(rsp); # func4 参数 1goto func4;if (rax != 0) { # func4 返回值判断goto 401058;}if (0xc(rsp) == 0) { # # 第二个数字goto 40105d;}
401058:goto exlode_bomb;
40105d:return;
}
然后进行c-like风格调整:
phase_4(rdi)
{rcx = rsp + 12;rdx = rsp + 8;rsi = 0x4025cf;rax = 0;rax = sscanf(rdi, rsi, rdx, rcx);if (rax != 2) {explode_bomb();}if (*(rsp + 8) <= 14) {rdx = 14;rsi = 0;rdi = *(rsp + 8);rax = func4(rdi, rsi, rdx);if (rax != 0) {explode_bomb();}if (*(rsp + 12) != 0) {explode_bomb();}}retq;
}
从代码中可以看出大概分为两个阶段:
- 解析字符串
- 调用 func4
解析字符串部分,sscanf 的format部分可以通过gdb查看:
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
可以知道是将字符串解析为两个数字。
第二个阶段,是将第一个数字、0、14作为func4的参数来调用func4,第二个数字必须为0。
然后用c代码表示:
void phase_4(char* input)
{int x, y;int ret = sscanf(input, "%d %d", &x, &y);if (ret != 2 || x > 14) {explode_bomb();}else {int ret = func4(x, 0, 14);if (ret != 0 || y != 0) {explode_bomb();}}return;
}
通过c代码可以看出phase_4的功能:
解析字符串为两个数字x、y
x必须小于等于14
调用func4,x、0、14分别为其参数
func4返回值必须为0,y必须为0
因此一顿分析发现主要得看func4的实现,同样对func4进行汇编代码可读性调整:
func4(rdi, rsi, rdx)
{eax = edx;eax -= esi;ecx = eax;ecx >>= 31;eax += ecx;eax >>= 1; # 对eax一顿操作ecx = rax + rsi; # 对ecx一顿操作if (ecx <= edi) {goto 400ff2;}edx = rcx - 1;goto func4; # 递归eax += eax;goto 401007;
400ff2:eax = 0;if (ecx >= edi) { # 递归基goto 401007;}esi = rcx + 1;goto func4; # 递归eax = rax + rax + 1;
401007:return;
}
还是一脸懵比,只知道是递归,继续c-like风格调整:
func4(edi, esi, edx)
{eax = edx - esi;eax = (eax + eax >> 31) >> 1;ecx = rax + rsi;if (ecx <= edi) {eax = 0;if (ecx >= edi) {return;}esi = rcx + 1;rax = func4(edi, esi, edx);eax = 2 * rax + 1;}else {edx = rcx - 1;rax = func4(edi, esi, edx);eax = 2 * rax; }retq;
}
局势逐渐明朗,可以看出是该递归有两个分支,ecx <= edi 和 ecx > edi。
第一个分支,esi = rcx +1,然后递归;第二个分支,edx = rcx -1,然后递归。
c代码如下:
int func4(int x, int a, int b)
{int num = b - a;num = (num + num >> 31) / 2;int c = num + a;if (c <= x) {if (c >= x) return 0;return 2 * func4(x, num+1, b) + 1;}return 2 * func4(x, a, num-1);
}
其中c其实是通过 (b-a)/2 + a = b/2 + a/2 = c 得到,其中a和b的初始值是0,14,因此c的初始值是 0/2+14/2 = 7。
所以事实上 c 是求 (b-a)/2,然后通过 c 与 x 进行比较决定不同的递归分支,因此这是递归版的二分法。
因此func4的功能:
- 二分法找出元素x,目标值在数组的前半部分则返回奇数,后半部分则返回偶数;
- 找到元素x返回0;
- 如果目标值在二分查找过程中一直在左半边,则会返回0。
经验证,0 1 3 7在二分过程中一直在左半边。
答案:
- 0 0
- 1 0
- 3 0
- 7 0
phase 5
考点:
- 字符串
- 数组
- for循环
这一题的难度不小了,代码也需要细细打磨,因此继续三步走,先可读性调整:
phase_5(rdx)
{rbx = rdi;rax = fs:0x28; # 金丝雀*(rsp+0x18) = rax;eax = eax ^ eax; # 清0callq string_length; # 计算字符串长度if (eax == 6) {goto 4010d2;}else callq explode_bomb;goto 4010d2;
40108b:ecx = rbx + rax;*rsp = cl;rdx = *rsp;edx = edx & 0xf;edx = rdx + 0x4024b0;*(rsp+rax+0x10) = dl; # 数组操作rax += 1; # 自增if (rax != 6) # 循环退出条件goto 40108b;*(rsp + 0x16) = 0;esi = 0x40245e;rdi = rsp + 10;callq strings_not_equal;if (eax == 0)callq explode_bomb;noplgoto 4010d9;
4010d2:eax = 0; # initgoto 40108b; # 循环
4010d9:rax = *(rsp + 0x18);rax = rax ^ fs:0x28;if (rax == 0) # 栈是否被破坏goto 4010ee;elsecallq __stack_chk_fail@plt;
4010ee:retq;
}
通过这三个部分:
1:
rax += 1; # 自增
2:
if (rax != 6) # 循环退出条件
3:
eax = 0; # init
可以判断这是一个for loop,rax范围是从0到5。
干掉多余的部分和临时变量,继续c-like风格调整如下:
phase_5(rdi)
{rbx = rdi;eax = eax ^ eax;eax = string_length(rdi);if (eax == 6) {goto 4010d2;}else explode_bomb();for (eax = 0; eax != 6; eax++) {ecx = rbx + rax;*rsp = cl;rdx = *rsp;edx = edx & 0xf;edx = *(rdx + 0x4024b0);*(rsp+rax+0x10) = dl;}*(rsp + 0x16) = 0;esi = 0x40245e;rdi = rsp + 10;strings_not_equal(rdi, rsi);if (eax != 0) {explode_bomb();}
}
不容易理解的是这一段:
# 取output字符串rax位置的字符
ecx = rbx + rax;
*rsp = cl;
rdx = *rsp;
# 该值与0xf与操作
edx = edx & 0xf;
# 取0x4024b0数组edx位置的值
edx = *(rdx + 0x4024b0);
这一段事实上是在取output字符串的字符,然后逐次将每个字符与0xf与操作,得到的值做为0x4024b0
处字符串的下标。
与0xf
与操作意味着能取到0x4024b0
处字符串的范围是0-15,通过gdb查看0x4024b0
处字符串:
(gdb) x/s 0x4024b0
0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
可以得到前16为字符串:maduiersnfotvbyl
。
通过for循环能够生成一个字符串,该字符串由output的 每个字符&0xf
来作为maduiersnfotvbyl
的下标,来选择字符。
然后*(rsp + 0x16) = 0
是字符串结尾符,最后将该字符串与0x40245e
处的字符串进行比较。
0x40245e
处的字符串:
(gdb) x/s 0x40245e
0x40245e: "flyers"
说人话就是:
const char g_str[16] = "maduiersnfotvbyl";
void phase_5(char* output)
{char str[7];if (string_length(output) != 6) {explode_bomb();}// 9 15 14 5 6 7// I O N E F Gfor (int i = 0; i != 6; i++) {str[i] = g_str[output[i] & 0xf];}str[7] = '\0';if(string_not_equal(str, "flyers") != 0) {explode_bomb();}
}
最后可以分析出该代码的功能:
- output长度必须等于6
- 生成一个新的字符串str,由output每个字符&0xf作为
maduiersnfotvbyl
下标得到 - str必须为
flyers
可以发现flyers
六个字母对应maduiersnfotvbyl
的下标分别为 9 15 14 5 6 7。
查ascii表,可以找到六个&0xf分别为 9 15 14 5 6 7的字符:
- IONEFG
- 其他省略
phase 6
考点:
- 循环
- 结构体(异构数组)
- 链表
phase 6代码看似又臭由长,实际上也是比较难…
这一题需要细细品位,耐心打磨,先不遗余力地可读性调整:
phase_6(rdi)
{r13 = rsp;rsi = rsp;callq read_six_numbers;r14 = rsp;r12 = 0; # i = 0
401114:rbp = r13;eax = *r13;eax -= 1;if (eax <= 5)goto 401128;else callq explode_bomb;
401128:r12 += 1; # i++if (r12 == 6)goto 401153; # 外层循环退出条件ebx = r12;
401135:rax = ebx; # i = jeax = *(rsp+rax*4);if (*rbp != eax)goto 401145;else callq explode_bomb;
401145:ebx += 1; # j++if (ebx <= 5) # 内层循环退出条件goto 401135; # 内层循环r13 += 4;goto 401114; # 外层循环
--------------------------
401153: rsi = rsp + 0x18;rax = r14; # i = r14ecx = 7;
401160:edx = ecx;eax -= *rax;*rax - edx;rax += 4; # i += 4if (rax != rsi) # 循环退出条件goto 401160;
---------------------------esi = 0; # i = 0goto 401197;
401176:rdx = *(rdx+8);eax += 1; # j++if (eax != ecx) # 内层循环退出条件goto 401176;else goto 401188;
401183:edx = 0x6032d0;
401188:*(rsp+2*rsi+20) = rdx;rsi += 4; # i += 4if (rsi == 0x18) # 外层循环退出条件goto 4011ab;
401197:ecx = *(rsp+rsi)if (ecx <= 1)goto 401183;eax = 1; # j = 1edx = 0x6032d0;goto 401176;
-----------------------------
4011ab:rbx = *(rsp+0x20);rax = *(rsp+0x28); # i = *(rsp+0x28);rsi = *(rsp+0x50);rcx = rbx;
4011bd:rdx = *rax;*(rcx+8) = rdx;rax += 8; # i += 8if (rax == rsi) # 循环退出条件goto 4011d2;rcx = rdx;goto 4011bd;
-----------------------------
4011d2:*(rdx+8) = 0;ebp = 5; # i = 5
4011df:rax = *(rbx+0x8);eax = *rax;if(eax >= *(rbx))goto 4011ee;else callq explode_bomb;
4011ee:rbx = *(rbx+8);ebp -= 1; # i--if(ebp != 0) # 循环退出条件goto 4011df;
}
我们可以大致分出五部分,分别是五个循环,其中第一个和第三个是双重循环(果真是又长又臭)。
分析清楚整体逻辑,可以进一步改为c-like风格:
phase_6(rdi)
{r13 = rsp;rsi = rsp;read_six_numbers(rdi, rsi);r14 = rsp;for (r12 = 0; r12 != 6; r12++) {rbp = r13;eax = *r13;eax -= 1;if (eax > 5)explode_bomb();for (ebx = r12+1; ebx <= 5; ebx++) {rax = ebx;eax = *(rsp+rax*4);if (*rbp == eax)explode_bomb();}r13 += 4;}rsi = rsp + 0x18;rax = r14;ecx = 7;for (rax = r14; rax != rsi; rax += 4) {edx = ecx;eax -= *rax;*rax = edx;}for (rsi = 0; rsi != 0x18; rsi += 4) {ecx = *(rsp+rsi);if (ecx <= 1)edx = 0x6032d0;else {edx = 0x6032d0;for (eax = 1; eax != ecx; eax++) {rdx = *(rdx+8);}}*(rsp+2*rsi+20) = rdx;}rbx = *(rsp+0x20);rcx = rbx;for (rax = rsp+0x28; rax != rsp+0x50; rax += 8) {rdx = *rax;*(rcx+8) = rdx;rcx = rdx; }*(rdx+8) = 0;for (ebp = 5; ebp != 0; ebp -= 1) {rax = *(rbx+0x8);eax = *rax;if(eax < *(rbx))explode_bomb();rbx = *(rbx+8);}
还是一头雾水,需要细细分析这些逻辑的意思,我们首先大概知道需要输入6个数字。
- 第一个循环对这个整数数组做了一些判断,应该是要符合某种规则;
- 第二个循环修改了整数数组的值;
- 第三个循环根据某种可以 嵌套解指针的结构 构造了一个指针数组;
- 第四个循环对指针数组做了修改;
- 第五个循环对 嵌套解指针的结构 进行了一些判断,需要符合某种规则。
这里的难点是
- 判断数组符合某种规则的逻辑;
- 可以 嵌套解指针的结构 是什么;
经过查阅才恍然大悟,嵌套解指针的结构就是链表。
该链表的地址和值可以通过gdb查出:
332 168 924 691 477 443
0x6032d0->0x6032e0->0x6032f0->0x603300->0x603310->0x603320
这里有一个细节,第一个循环中将 eax -= 1,然后和5比较通过 jbe跳转,目的防止eax取负数和0,eax -= 1是防止取0,jbe防止取负数。
因为jbe跳转是用无符号数比较,负数在无符号数编码下是很大的数所以必然大于5,因此输入的数字范围是1-6。
经过整理可以得到c代码:
typedef struct {int val;ListNode* next;
} ListNode;void phase_6(char* output)
{int array[6];ListNode* node_array[6];read_six_numbers(output, array);// 数字范围必须为1-6且互不重复for (int i = 0; i != 6; i++) {int num = array[i];num--;if ((unsigned int)num > 5) // 最大为6explode_bomb();for (int j = i+1; j <= 5; j++) {if (array[i] == array[j]) // 每个元素都不重复explode_bomb();}}// 修改 arrayfor (int i = 0; i < 6; i ++) {array[i] = (7 - array[i]);}// 生成 node_arrayfor (int i = 0; i < 6; i ++) {int cur = array[i];ListNode* node = 0x6032d0; // 链表headif (cur > 1) {for (int j = 1; j < cur; j++) {node = node->next;}}node_array[i] = node;}for (int i = 0; i < 5; i++) {node_array[i]->next = node_array[i+1];}//0x6032d0 0x6032e0 0x6032f0 0x603300 0x603310 0x603320//332 168 924 691 477 443// 6 5 4 3 2 1ListNode* ptr = node_array[0];for (int i = 5; i > 0; i--) {if (ptr->val < ptr->next->val)explode_bomb();ptr = ptr->next;}
}
相信c代码写出,逻辑就比较清晰了,可以知道功能为下:
- 输入的数组array必须为 1-6,且不可重复;
- 对array每个数字 7 - array[i];
- 根据array的值,取到对应位置的listnode,并由ptr_array存储;
- 将ptr_array每个元素按序连接(链表重排);
- 链表必须为降序。
因此phase_6就是链表排序,输入的数字为原链表位置,输入的次序为新链表的位置。
又因为第二个循环对array每个数字 7 - array[i],因此输入的数字需要将链表重排成为升序。
原链表的从小到大顺序是 5 6 1 2 3 4, 重排后 4 3 2 1 6 5。
总结和感想
《CSAPP》其实看了挺久了,第三章汇编一直搁着是一个遗憾,总觉得是一块硬骨头(当时觉得太枯燥)。这次重新又杀回来通过视频+阅读+做题的方式对整个第三章来了一次横扫,结果是收获巨大,每次理解完一个知识点都有种恍然大悟之感。
看了相关教学视频后才知道第三章的重要性,整整花了五节课,占了将近课程量的1/3,可以看出这一章的重要性。
CSAPP的实验系列一直是经典,我认为在某种程度上书甚至可以不看,但lab不可不做…,配套的bomb lab更是如此。我从phase_1到phase_6的过程中对c的理解也是更上一层楼:
- 字符串的传递方式
- 数组的表示方法
- 函数入参和返回值
- 栈破坏检测
- 链表的操作方式
- 栈空间的使用
Bomb Lab带来的成就感是巨大的,所以尽量自己硬着头皮啃一啃,忍住不要看答案…
CSAPP实验之Bomb Lab详解相关推荐
- CSAPP实验二——bomb lab实验
CSAPP实验二-- bomb lab实验 实验前准备 第一部分(phase_1) 第二部分(phase_2) 第三部分(phase_3) 第四部分(phase_4) 第五部分(phase_5) 第六 ...
- 【计算机系统基础bomb lab】CSAPP实验:Bomb Lab
[计算机系统基础bomb lab]CSAPP实验:Bomb Lab CSAPP 实验:Bomb Lab 实验内容简述 实验环境 实验过程:phase 1 phase 1 调试过程 实验过程:phase ...
- CSAPP Lab2 实验记录 ---- Bomb Lab(Phase 1 - Phase 6详细解答 + Secret Phase彩蛋解析)
文章目录 Lab 总结博客链接 实验前提引子 实验需要指令及准备 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 Phase Secret(彩蛋Phas ...
- 《深入理解计算机系统》实验二Bomb Lab下载和官方文档机翻
前言 <深入理解计算机系统>官网:http://csapp.cs.cmu.edu/3e/labs.html 该篇文章是实验二Bomb Lab的Writeup机翻. 原文:http://cs ...
- 【转载】NeurIPS 2018 | 腾讯AI Lab详解3大热点:模型压缩、机器学习及最优化算法...
原文:NeurIPS 2018 | 腾讯AI Lab详解3大热点:模型压缩.机器学习及最优化算法 导读 AI领域顶会NeurIPS正在加拿大蒙特利尔举办.本文针对实验室关注的几个研究热点,模型压缩.自 ...
- 《深入理解计算机系统》实验二Bomb Lab
前言 <深入理解计算机系统>实验二Bomb Lab的下载和官网文档的机翻请看 <深入理解计算机系统>实验二Bomb Lab下载和官方文档机翻 用的调试工具是gdb,用到的指令如 ...
- 深入理解计算机系统(CSAPP)含lab详解 完结
文章目录 深入理解计算机操作系统-第一章 1.1 信息就是位 + 上下文 1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释储存在内存中的指令 1 ...
- CSAPP Lab2 实验记录 ---- Bomb Lab(Secret Phase彩蛋解析)
文章目录 Lab 总结博客链接 前引 Secret Phase 1.发现彩蛋出处 2.找寻Secret Phase 入口 3.剖析Phase Defused 寻觅进入彩蛋方法 4.终到Secret P ...
- 大唐杯仿真实验模拟满分操作详解(省级)
本科A组省赛一等奖: 本科A组国赛一等奖: 考试要求: 仿真实践赛 答题时长及总分:60分钟,共300分,基于虚拟仿真平台答题. 赛道一(本科A组) 1)5G无线技术知识点(分数占比35%) 掌握无线 ...
最新文章
- 选频放大电路对于150kHz导航信号进行放大检波
- 在 CentOS7 安装 ELK【转】
- 前20个关于FLEX技术、源码、实例、技巧的网站.
- html5g与h5的区别,H5是什么?
- 二层交换机 不在同一子网_从二层交换机来说为什么三层交换机比路由器转发速率快...
- uniapp中针对H5端做微信分享功能总结
- deeping 系统连接外接显示器无法无法设置双屏(有可能是不同接口在不同显卡上)...
- 牛客网c语言笔试题库,【sql】牛客网练习题 (共 61 题)
- EndNote常见问题:更改DOI号为超链接/用URL代替DOI号等
- Cocos Creator方向与角度转换
- 西湖,半含春雨半垂丝
- Halcon 排线检测|固定颜色检测
- 互联网巨头们的地摊争夺战
- 执行引擎的工作过程、Java代码编译和执行的过程、解释器、JIT编译器
- java memorycache原理_CPU Cache 原理及操作
- Centos7 Yum安装MYSQL8.0详细安装步骤
- matplotlib之pyplot画饼图(pie)
- 用C语言实现my_strncat
- 安装并使用Panoply (netCDF, HDF and GRIB Data Viewer)
- qt---crc32
热门文章
- 安卓手机用WIFI无线调试adb
- 三种主要近场通信技术的对比
- ABBYY FineReader 14图像处理选项解析
- 顺序执行、并行和并发
- ffmpeg--h264帧内解码
- 荒野行动服务器信息连不上,荒野行动服务器连接不上怎么办_荒野行动服务器连接不上解决方法说明_3DM手游...
- 若依启动项目报Command line is too long. Shorten command line for……
- 微信PC端打开小程序(腾讯文档)、网页都显示空白和QQ/TIM的群文件无法显示等问题的解决方法
- 浏览器刷新和关闭事件
- 如何在其他电脑不登陆谷歌前提下获取之前所收藏的书签