前言

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详解相关推荐

  1. CSAPP实验二——bomb lab实验

    CSAPP实验二-- bomb lab实验 实验前准备 第一部分(phase_1) 第二部分(phase_2) 第三部分(phase_3) 第四部分(phase_4) 第五部分(phase_5) 第六 ...

  2. 【计算机系统基础bomb lab】CSAPP实验:Bomb Lab

    [计算机系统基础bomb lab]CSAPP实验:Bomb Lab CSAPP 实验:Bomb Lab 实验内容简述 实验环境 实验过程:phase 1 phase 1 调试过程 实验过程:phase ...

  3. 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 ...

  4. 《深入理解计算机系统》实验二Bomb Lab下载和官方文档机翻

    前言 <深入理解计算机系统>官网:http://csapp.cs.cmu.edu/3e/labs.html 该篇文章是实验二Bomb Lab的Writeup机翻. 原文:http://cs ...

  5. 【转载】NeurIPS 2018 | 腾讯AI Lab详解3大热点:模型压缩、机器学习及最优化算法...

    原文:NeurIPS 2018 | 腾讯AI Lab详解3大热点:模型压缩.机器学习及最优化算法 导读 AI领域顶会NeurIPS正在加拿大蒙特利尔举办.本文针对实验室关注的几个研究热点,模型压缩.自 ...

  6. 《深入理解计算机系统》实验二Bomb Lab

    前言 <深入理解计算机系统>实验二Bomb Lab的下载和官网文档的机翻请看 <深入理解计算机系统>实验二Bomb Lab下载和官方文档机翻 用的调试工具是gdb,用到的指令如 ...

  7. 深入理解计算机系统(CSAPP)含lab详解 完结

    文章目录 深入理解计算机操作系统-第一章 1.1 信息就是位 + 上下文 1.2 程序被其他程序翻译成不同的格式 1.3 了解编译系统如何工作是大有益处的 1.4 处理器读并解释储存在内存中的指令 1 ...

  8. CSAPP Lab2 实验记录 ---- Bomb Lab(Secret Phase彩蛋解析)

    文章目录 Lab 总结博客链接 前引 Secret Phase 1.发现彩蛋出处 2.找寻Secret Phase 入口 3.剖析Phase Defused 寻觅进入彩蛋方法 4.终到Secret P ...

  9. 大唐杯仿真实验模拟满分操作详解(省级)

    本科A组省赛一等奖: 本科A组国赛一等奖: 考试要求: 仿真实践赛 答题时长及总分:60分钟,共300分,基于虚拟仿真平台答题. 赛道一(本科A组) 1)5G无线技术知识点(分数占比35%) 掌握无线 ...

最新文章

  1. 选频放大电路对于150kHz导航信号进行放大检波
  2. 在 CentOS7 安装 ELK【转】
  3. 前20个关于FLEX技术、源码、实例、技巧的网站.
  4. html5g与h5的区别,H5是什么?
  5. 二层交换机 不在同一子网_从二层交换机来说为什么三层交换机比路由器转发速率快...
  6. uniapp中针对H5端做微信分享功能总结
  7. deeping 系统连接外接显示器无法无法设置双屏(有可能是不同接口在不同显卡上)...
  8. 牛客网c语言笔试题库,【sql】牛客网练习题 (共 61 题)
  9. EndNote常见问题:更改DOI号为超链接/用URL代替DOI号等
  10. Cocos Creator方向与角度转换
  11. 西湖,半含春雨半垂丝
  12. Halcon 排线检测|固定颜色检测
  13. 互联网巨头们的地摊争夺战
  14. 执行引擎的工作过程、Java代码编译和执行的过程、解释器、JIT编译器
  15. java memorycache原理_CPU Cache 原理及操作
  16. Centos7 Yum安装MYSQL8.0详细安装步骤
  17. matplotlib之pyplot画饼图(pie)
  18. 用C语言实现my_strncat
  19. 安装并使用Panoply (netCDF, HDF and GRIB Data Viewer)
  20. qt---crc32

热门文章

  1. 安卓手机用WIFI无线调试adb
  2. 三种主要近场通信技术的对比
  3. ABBYY FineReader 14图像处理选项解析
  4. 顺序执行、并行和并发
  5. ffmpeg--h264帧内解码
  6. 荒野行动服务器信息连不上,荒野行动服务器连接不上怎么办_荒野行动服务器连接不上解决方法说明_3DM手游...
  7. 若依启动项目报Command line is too long. Shorten command line for……
  8. 微信PC端打开小程序(腾讯文档)、网页都显示空白和QQ/TIM的群文件无法显示等问题的解决方法
  9. 浏览器刷新和关闭事件
  10. 如何在其他电脑不登陆谷歌前提下获取之前所收藏的书签