0%

国内无法访问raw.githubusercontent.com的原因基本是DNS污染,因此修改hosts可以解决该问题,例如使用IPAddress.com等网站查到真实IP后自行修改。同时,GitHub上也存在项目GitHub520对其进行实时更新(强烈推荐该项目)。

hosts的位置:

1
2
3
4
5
# Windows
C:\Windows\System32\drivers\etc\hosts

# Linux
/etc/hosts

phase_1

采用disas phase_1生成对应汇编代码:

1
2
3
4
5
6
7
8
0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: call 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: call 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: ret

<phase_1+9>处可以看出该句执行的是字符串比较,如果输入字符与0x402400处的不一致就发生爆炸。

使用print (char*)0x402400打印目标字符串,结果为Border relations with Canada have never been better.

故phase_1的答案为:

1
Border relations with Canada have never been better.

phase_2

采用disas phase_2生成对应汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0x0000000000400efc <+0>:     push   %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: ret

<phase_2+9>处表明,该函数需要输入的是6个数值,<phase_2+48>处表明,该函数存在一个循环,循环节如下:

1
2
3
4
5
6
7
8
0x0000000000400f17 <+27>:    mov    -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>

显然为一个数组结构,当a[i] + a[i] != a[i + 1]时被引爆。

同时,由<phase_2+14>处可以推出,a[0] = 1

故phase_2的答案为:

1
1 2 4 8 16 32

phase_3

采用disas phase_3生成对应汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
0x0000000000400f43 <+0>:     sub    $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmp *0x402470(,%rax,8)
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: ret

开始时解析输入字符串:

1
2
3
4
5
0x0000000000400f47 <+4>:     lea    0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>

print (char*)0x4025cf获得"%d %d",即输入两个整数。

由<phase_3+39>可知,输入的第一个数一定在被视为无符号整数时小于等于7。

在<phase_3+50>处可知,根据输入的第一个参数进行跳转表查询后进行跳转。使用x/8xg 0x402470获取跳转表:

1
2
3
4
0x402470:       0x0000000000400f7c      0x0000000000400fb9
0x402480: 0x0000000000400f83 0x0000000000400f8a
0x402490: 0x0000000000400f91 0x0000000000400f98
0x4024a0: 0x0000000000400f9f 0x0000000000400fa6

跳转表中全部值均对应下述分支。

在<phase_3+127>处可知,第二个参数应该和第一个数跳转后的赋值相同。

故答案有多个,依次为(一行一个答案):

1
2
3
4
5
6
7
8
0 207
1 311
2 707
3 256
4 389
5 206
6 682
7 327

phase_4

采用disas phase_4生成对应汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x000000000040100c <+0>:     sub    $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
0x000000000040101a <+14>: mov $0x4025cf,%esi
0x000000000040101f <+19>: mov $0x0,%eax
0x0000000000401024 <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp $0x2,%eax
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp)
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: call 0x400fce <func4>
0x000000000040104d <+65>: test %eax,%eax
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: ret

如上文,0x4025cf处为"%d %d",即解析两个整数。

<phase_4+34>处表明,第一个参数应当小于等于15。

<phase_4+60>调用了函数func4。其中func4第一个参数与phase_4第一个参数相同,第二、三个参数分别为0和14。

<phase_4+65>处表明,func4的返回值应当为0。

<phase_4+65>处表明,执行func4后第二个参数应当为0。

使用disas func4func4反编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x0000000000400fce <+0>:     sub    $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax
0x0000000000400fd4 <+6>: sub %esi,%eax
0x0000000000400fd6 <+8>: mov %eax,%ecx
0x0000000000400fd8 <+10>: shr $0x1f,%ecx
0x0000000000400fdb <+13>: add %ecx,%eax
0x0000000000400fdd <+15>: sar %eax
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx
0x0000000000400fe2 <+20>: cmp %edi,%ecx
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx
0x0000000000400fe9 <+27>: call 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: call 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: ret

可以看出func4是一个递归程序,翻译成C语言代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int func4(int edi, int esi, int edx) {
int eax = edx - esi;
eax = (eax + (eax < 0)) / 2;
int ecx = eax + esi;
if(ecx <= edi) {
eax = 0;
if(ecx >= edi) {
// 1
return eax;
}
// 2
esi = ecx + 1;
eax = func4(edi, esi, edx);
return 2 * eax + 1;
} else {
// 3
edx = ecx - 1;
eax = func4(edi, esi, edx);
return 2 * eax;
}
}

于是问题变为使func4(x, 0, 14)为0的x值。

观察可得,只有当递归末端从13出口出,其他递归层均从3出口出时才为0。

同时,函数参数有如下变化:

1
2
3
4
5
6
# 2出口进入时
edi = edi, esi = ecx + 1, edx = edx
# 3出口进入时
edi = edi, esi = esi, edx = ecx - 1

ecx = esi + (edx > esi ? (edx - esi) / 2 : (edx - esi + 1) / 2)

因为输入的esi = 0edx = 14,每次要求均从13出口出,所以有:

1
1: ecx = 7, 此时可以有edi = ecx <= 15,故可以直接另x = 15,直接返回。

故答案是:

1
7 0

phase_5

采用disas phase_5获得对应汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
0x0000000000401062 <+0>:     push   %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: call 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: call 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: call 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: call 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: call 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: ret

由<phase_5+24>可知输入的是字符串,根据<phase_5+29>可知长度为6。

很显然,存在一个循环,循环节为:

1
2
3
4
5
6
7
8
9
0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>

该循环将位于0x4024b0的字符串按照一定规则搬运到0x10(%rsp)处,其中每一个规则为映射后的字符串是0x4024b0的字符串取对应下标的输入字符串对应的整数的后4位的结果。

<phase_5+91>表明获得的字符串应当与位于0x40245e的字符串相同。

使用p (char*)0x4024b0打印0x4024b0字符串,取前16位的结果如下:

1
0x4024b0 <array> "maduiersnfotvbyl"

使用p (char*)0x40245e打印0x40245e字符串,结果如下:

1
0x40245e "flyers"

即输入的字符串对应的后4位的值应为:

1
0x9 0xf 0xe 0x5 0x6 0x7

为了便于输入,将其映射到a-o即可,故答案为:

1
ionefg

phase_6

采用disas phase_6获得对应汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
0x00000000004010f4 <+0>:     push   %r14
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx
0x00000000004010fc <+8>: sub $0x50,%rsp
0x0000000000401100 <+12>: mov %rsp,%r13
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: call 0x40145c <read_six_numbers>
0x000000000040110b <+23>: mov %rsp,%r14
0x000000000040110e <+26>: mov $0x0,%r12d
0x0000000000401114 <+32>: mov %r13,%rbp
0x0000000000401117 <+35>: mov 0x0(%r13),%eax
0x000000000040111b <+39>: sub $0x1,%eax
0x000000000040111e <+42>: cmp $0x5,%eax
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
0x0000000000401123 <+47>: call 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d
0x000000000040112c <+56>: cmp $0x6,%r12d
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov %r12d,%ebx
0x0000000000401135 <+65>: movslq %ebx,%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp)
0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
0x0000000000401140 <+76>: call 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx
0x0000000000401148 <+84>: cmp $0x5,%ebx
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add $0x4,%r13
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi
0x0000000000401158 <+100>: mov %r14,%rax
0x000000000040115b <+103>: mov $0x7,%ecx
0x0000000000401160 <+108>: mov %ecx,%edx
0x0000000000401162 <+110>: sub (%rax),%edx
0x0000000000401164 <+112>: mov %edx,(%rax)
0x0000000000401166 <+114>: add $0x4,%rax
0x000000000040116a <+118>: cmp %rsi,%rax
0x000000000040116d <+121>: jne 0x401160 <phase_6+108>
0x000000000040116f <+123>: mov $0x0,%esi
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx
0x000000000040117a <+134>: add $0x1,%eax
0x000000000040117d <+137>: cmp %ecx,%eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
0x0000000000401183 <+143>: mov $0x6032d0,%edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
0x000000000040119f <+171>: mov $0x1,%eax
0x00000000004011a4 <+176>: mov $0x6032d0,%edx
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi
0x00000000004011ba <+198>: mov %rbx,%rcx
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
0x00000000004011c4 <+208>: add $0x8,%rax
0x00000000004011c8 <+212>: cmp %rsi,%rax
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx)
0x00000000004011da <+230>: mov $0x5,%ebp
0x00000000004011df <+235>: mov 0x8(%rbx),%rax
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx)
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: call 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: ret

<phase_6+18>表明,输入的是6个数值,解析后的数值放入栈中。

接下来进入一段循环中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x000000000040110b <+23>:    mov    %rsp,%r14
0x000000000040110e <+26>: mov $0x0,%r12d
0x0000000000401114 <+32>: mov %r13,%rbp
0x0000000000401117 <+35>: mov 0x0(%r13),%eax
0x000000000040111b <+39>: sub $0x1,%eax
0x000000000040111e <+42>: cmp $0x5,%eax
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
0x0000000000401123 <+47>: call 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d
0x000000000040112c <+56>: cmp $0x6,%r12d
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov %r12d,%ebx
0x0000000000401135 <+65>: movslq %ebx,%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp)
0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
0x0000000000401140 <+76>: call 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx
0x0000000000401148 <+84>: cmp $0x5,%ebx
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add $0x4,%r13
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>

该循环依次遍历数组中的每个元素,并对其进行判断,要求每个元素两两均不相等且均在1-6之间。

接下来进入另一个循环:

1
2
3
4
5
6
7
8
9
0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi
0x0000000000401158 <+100>: mov %r14,%rax
0x000000000040115b <+103>: mov $0x7,%ecx
0x0000000000401160 <+108>: mov %ecx,%edx
0x0000000000401162 <+110>: sub (%rax),%edx
0x0000000000401164 <+112>: mov %edx,(%rax)
0x0000000000401166 <+114>: add $0x4,%rax
0x000000000040116a <+118>: cmp %rsi,%rax
0x000000000040116d <+121>: jne 0x401160 <phase_6+108>

该循环依次遍历数组中的每个元素,并使用7减去其之后的值替代原值。

接下来又进入新循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x000000000040116f <+123>:   mov    $0x0,%esi
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx
0x000000000040117a <+134>: add $0x1,%eax
0x000000000040117d <+137>: cmp %ecx,%eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
0x0000000000401183 <+143>: mov $0x6032d0,%edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
0x000000000040119f <+171>: mov $0x1,%eax
0x00000000004011a4 <+176>: mov $0x6032d0,%edx
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>

其中存在一组小循环:

1
2
3
4
5
0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx
0x000000000040117a <+134>: add $0x1,%eax
0x000000000040117d <+137>: cmp %ecx,%eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>

该循环显然处理的是一个链表结构,其从rdx处返回ecx位置的链表节点。

接下来进入另一个小循环:

1
2
3
4
5
6
7
8
9
10
11
0x0000000000401183 <+143>:   mov    $0x6032d0,%edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
0x000000000040119f <+171>: mov $0x1,%eax
0x00000000004011a4 <+176>: mov $0x6032d0,%edx
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>

遍历7减操作后的数组,该循环用于根据处理后的取出对应链表节点指针并将其存放为数组,链表的头节点地址为0x6032d0

故本段的作用是根据输入数组取出节点形成节点指针数组,指针节点数组的位置在0x20(%rsp)处。

接下来进行第三段大循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi
0x00000000004011ba <+198>: mov %rbx,%rcx
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
0x00000000004011c4 <+208>: add $0x8,%rax
0x00000000004011c8 <+212>: cmp %rsi,%rax
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx)
0x00000000004011da <+230>: mov $0x5,%ebp
0x00000000004011df <+235>: mov 0x8(%rbx),%rax
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx)
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: call 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>

一开始是一段小循环:

1
2
3
4
5
6
7
0x00000000004011bd <+201>:   mov    (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
0x00000000004011c4 <+208>: add $0x8,%rax
0x00000000004011c8 <+212>: cmp %rsi,%rax
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>

该段循环重建链表,使得其顺序与链表节点数组顺序相同。

1
2
3
4
5
6
7
8
9
10
0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)
0x00000000004011da <+230>: mov $0x5,%ebp
0x00000000004011df <+235>: mov 0x8(%rbx),%rax
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx)
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: call 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>

该段对重排后的链表节点进行判定,要求链表单调递减。

现在开始反推:

  1. 要求最终节点单调递减。

  2. 进行了链表重构,重构顺序为7减数组指定链表节点顺序。

  3. 故输入应该为1-6的组合,并且其7减数组使得单调递减。

  4. 7减数组转为原数组即可。

x/1xw 0x6032d0得:

1
0x6032d0 <node1>:       0x0000014c

x/2xg 0x6032d0得:

1
0x6032d0 <node1>:       0x000000010000014c      0x00000000006032e0

x/1xw 0x6032e0得:

1
0x6032e0 <node2>:       0x000000a8

x/2xg 0x6032e0得:

1
0x6032e0 <node2>:       0x00000002000000a8      0x00000000006032f0

x/1xw 0x6032f0得:

1
0x6032f0 <node3>:       0x0000039c

x/2xg 0x6032f0得:

1
0x6032f0 <node3>:       0x000000030000039c      0x0000000000603300

x/1xw 0x603300得:

1
0x603300 <node4>:       0x000002b3

x/2xg 0x603300得:

1
0x603300 <node4>:       0x00000004000002b3      0x0000000000603310

x/1xw 0x603310得:

1
0x603310 <node5>:       0x000001dd

x/2xg 0x603310得:

1
0x603310 <node5>:       0x00000005000001dd      0x0000000000603320

x/1xw 0x603320得:

1
0x603320 <node6>:       0x000001bb

x/2xg 0x603320得:

1
0x603320 <node6>:       0x00000006000001bb      0x0000000000000000

希望值为单调递减的索引应为3 4 5 6 1 2

7减数组应当为:3 4 5 6 1 2

故答案应为:

1
4 3 2 1 6 5

secret_phase

实际上还存在一个secret_phase,其入口在phase_defused中。

使用disas phase_defused获取phase_defused源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
0x00000000004015c4 <+0>:     sub    $0x78,%rsp
0x00000000004015c8 <+4>: mov %fs:0x28,%rax
0x00000000004015d1 <+13>: mov %rax,0x68(%rsp)
0x00000000004015d6 <+18>: xor %eax,%eax
0x00000000004015d8 <+20>: cmpl $0x6,0x202181(%rip) #0x603760 <num_input_strings>
0x00000000004015df <+27>: jne 0x40163f <phase_defused+123>
0x00000000004015e1 <+29>: lea 0x10(%rsp),%r8
0x00000000004015e6 <+34>: lea 0xc(%rsp),%rcx
0x00000000004015eb <+39>: lea 0x8(%rsp),%rdx
0x00000000004015f0 <+44>: mov $0x402619,%esi
0x00000000004015f5 <+49>: mov $0x603870,%edi
0x00000000004015fa <+54>: call 0x400bf0 <__isoc99_sscanf@plt>
0x00000000004015ff <+59>: cmp $0x3,%eax
0x0000000000401602 <+62>: jne 0x401635 <phase_defused+113>
0x0000000000401604 <+64>: mov $0x402622,%esi
0x0000000000401609 <+69>: lea 0x10(%rsp),%rdi
0x000000000040160e <+74>: call 0x401338 <strings_not_equal>
0x0000000000401613 <+79>: test %eax,%eax
0x0000000000401615 <+81>: jne 0x401635 <phase_defused+113>
0x0000000000401617 <+83>: mov $0x4024f8,%edi
0x000000000040161c <+88>: call 0x400b10 <puts@plt>
0x0000000000401621 <+93>: mov $0x402520,%edi
0x0000000000401626 <+98>: call 0x400b10 <puts@plt>
0x000000000040162b <+103>: mov $0x0,%eax
0x0000000000401630 <+108>: call 0x401242 <secret_phase>
0x0000000000401635 <+113>: mov $0x402558,%edi
0x000000000040163a <+118>: call 0x400b10 <puts@plt>
0x000000000040163f <+123>: mov 0x68(%rsp),%rax
0x0000000000401644 <+128>: xor %fs:0x28,%rax
0x000000000040164d <+137>: je 0x401654 <phase_defused+144>
0x000000000040164f <+139>: call 0x400b30 <__stack_chk_fail@plt>
0x0000000000401654 <+144>: add $0x78,%rsp
0x0000000000401658 <+148>: ret

在<phase_defused+20>中,要求将0x202181(%rip)等于6,否则不会进入rip寄存器保存下一条指令地址,即最终目标为0x202181 + 0x4015df = 0x603760x/1xw 0x603760得0,且其对应符号为num_input_strings

objdump -d bomb > bomb.s得到的bomb.s文件中查找num_input_strings,发现只在read_line中使用。

接下来,是对一段字符串的解析,0x603870为源字符串。p (char *)0x402619结果为%d %d %s。故应当解析出两个整数和一个字符串。

接下来进行字符串相等判断,p (char *)0x402622结果为"DrEvil"。因此第三个参数应该为DrEvil

接下来打印两行字符串,p (char *)0x4024f8p (char *)0x402520结果分别为"Curses, you've found the secret phase!""But finding it and solving it are quite different..."

之后进入。结束后进行正常的资源释放。

使用disas read_line获取read_line源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
0x000000000040149e <+0>:     sub    $0x8,%rsp
0x00000000004014a2 <+4>: mov $0x0,%eax
0x00000000004014a7 <+9>: call 0x4013f9 <skip>
0x00000000004014ac <+14>: test %rax,%rax
0x00000000004014af <+17>: jne 0x40151f <read_line+129>
0x00000000004014b1 <+19>: mov 0x202290(%rip),%rax # 0x603748 <stdin@@GLIBC_2.2.5>
0x00000000004014b8 <+26>: cmp %rax,0x2022a9(%rip) # 0x603768 <infile>
0x00000000004014bf <+33>: jne 0x4014d5 <read_line+55>
0x00000000004014c1 <+35>: mov $0x4025d5,%edi
0x00000000004014c6 <+40>: call 0x400b10 <puts@plt>
0x00000000004014cb <+45>: mov $0x8,%edi
0x00000000004014d0 <+50>: call 0x400c20 <exit@plt>
0x00000000004014d5 <+55>: mov $0x4025f3,%edi
0x00000000004014da <+60>: call 0x400ae0 <getenv@plt>
0x00000000004014df <+65>: test %rax,%rax
0x00000000004014e2 <+68>: je 0x4014ee <read_line+80>
0x00000000004014e4 <+70>: mov $0x0,%edi
0x00000000004014e9 <+75>: call 0x400c20 <exit@plt>
0x00000000004014ee <+80>: mov 0x202253(%rip),%rax # 0x603748 <stdin@@GLIBC_2.2.5>
0x00000000004014f5 <+87>: mov %rax,0x20226c(%rip) # 0x603768 <infile>
0x00000000004014fc <+94>: mov $0x0,%eax
0x0000000000401501 <+99>: call 0x4013f9 <skip>
0x0000000000401506 <+104>: test %rax,%rax
0x0000000000401509 <+107>: jne 0x40151f <read_line+129>
0x000000000040150b <+109>: mov $0x4025d5,%edi
0x0000000000401510 <+114>: call 0x400b10 <puts@plt>
0x0000000000401515 <+119>: mov $0x0,%edi
0x000000000040151a <+124>: call 0x400c20 <exit@plt>
0x000000000040151f <+129>: mov 0x20223b(%rip),%edx # 0x603760 <num_input_strings>
0x0000000000401525 <+135>: movslq %edx,%rax
0x0000000000401528 <+138>: lea (%rax,%rax,4),%rsi
0x000000000040152c <+142>: shl $0x4,%rsi
0x0000000000401530 <+146>: add $0x603780,%rsi
0x0000000000401537 <+153>: mov %rsi,%rdi
0x000000000040153a <+156>: mov $0x0,%eax
0x000000000040153f <+161>: mov $0xffffffffffffffff,%rcx
0x0000000000401546 <+168>: repnz scas %es:(%rdi),%al
0x0000000000401548 <+170>: not %rcx
0x000000000040154b <+173>: sub $0x1,%rcx
0x000000000040154f <+177>: cmp $0x4e,%ecx
0x0000000000401552 <+180>: jle 0x40159a <read_line+252>
0x0000000000401554 <+182>: mov $0x4025fe,%edi
0x0000000000401559 <+187>: call 0x400b10 <puts@plt>
0x000000000040155e <+192>: mov 0x2021fc(%rip),%eax # 0x603760 <num_input_strings>
0x0000000000401564 <+198>: lea 0x1(%rax),%edx
0x0000000000401567 <+201>: mov %edx,0x2021f3(%rip) # 0x603760 <num_input_strings>
0x000000000040156d <+207>: cltq
0x000000000040156f <+209>: imul $0x50,%rax,%rax
0x0000000000401573 <+213>: movabs $0x636e7572742a2a2a,%rdi
0x000000000040157d <+223>: mov %rdi,0x603780(%rax)
0x0000000000401584 <+230>: movabs $0x2a2a2a64657461,%rdi
0x000000000040158e <+240>: mov %rdi,0x603788(%rax)
0x0000000000401595 <+247>: call 0x40143a <explode_bomb>
0x000000000040159a <+252>: sub $0x1,%ecx
0x000000000040159d <+255>: movslq %ecx,%rcx
0x00000000004015a0 <+258>: movslq %edx,%rax
0x00000000004015a3 <+261>: lea (%rax,%rax,4),%rax
0x00000000004015a7 <+265>: shl $0x4,%rax
0x00000000004015ab <+269>: movb $0x0,0x603780(%rcx,%rax,1)
0x00000000004015b3 <+277>: add $0x1,%edx
0x00000000004015b6 <+280>: mov %edx,0x2021a4(%rip) # 0x603760 <num_input_strings>
0x00000000004015bc <+286>: mov %rsi,%rax
0x00000000004015bf <+289>: add $0x8,%rsp
0x00000000004015c3 <+293>: ret

在其中存在如下代码将num_input_strings的值增加1。

1
2
3
0x000000000040155e <+192>:   mov    0x2021fc(%rip),%eax        # 0x603760 <num_input_strings>
0x0000000000401564 <+198>: lea 0x1(%rax),%edx
0x0000000000401567 <+201>: mov %edx,0x2021f3(%rip) # 0x603760 <num_input_strings>

接下来关注跳转语句,发现除了以下代码后,其他的跳转语句不能避开该上述指令。

1
2
3
4
5
6
7
0x000000000040153a <+156>:   mov    $0x0,%eax
0x000000000040153f <+161>: mov $0xffffffffffffffff,%rcx
0x0000000000401546 <+168>: repnz scas %es:(%rdi),%al
0x0000000000401548 <+170>: not %rcx
0x000000000040154b <+173>: sub $0x1,%rcx
0x000000000040154f <+177>: cmp $0x4e,%ecx
0x0000000000401552 <+180>: jle 0x40159a <read_line+252>

该句计算输入语句的长度,若为0时跳过递增指令。

经过上述分析,可明白num_input_strings代表使用read_line读入过的的非0字符串数。

接下来需要追踪0x603870处字符串由谁修改。显然该字符串是输入字符串(内部缓存会被复用,信息会被清除)。故是read_line的返回值。接下来查看read_line的返回值信息:

1
0x00000000004015bc <+286>:   mov    %rsi,%rax

故查看rsi的最后修改处:

1
2
3
4
5
0x000000000040151f <+129>:   mov    0x20223b(%rip),%edx        # 0x603760 <num_input_strings>
0x0000000000401525 <+135>: movslq %edx,%rax
0x0000000000401528 <+138>: lea (%rax,%rax,4),%rsi
0x000000000040152c <+142>: shl $0x4,%rsi
0x0000000000401530 <+146>: add $0x603780,%rsi

故返回值地址为0x603780 + 16 * 5 * num_input_strings,当其为0x603870时,解得num_input_strings为3,故应当在phase_4的答案结尾加上DrEvil,修改后的phase_4答案为:

1
7 0 DrEvil

使用disas secret_phase获取secret_phase源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x0000000000401242 <+0>:     push   %rbx
0x0000000000401243 <+1>: call 0x40149e <read_line>
0x0000000000401248 <+6>: mov $0xa,%edx
0x000000000040124d <+11>: mov $0x0,%esi
0x0000000000401252 <+16>: mov %rax,%rdi
0x0000000000401255 <+19>: call 0x400bd0 <strtol@plt>
0x000000000040125a <+24>: mov %rax,%rbx
0x000000000040125d <+27>: lea -0x1(%rax),%eax
0x0000000000401260 <+30>: cmp $0x3e8,%eax
0x0000000000401265 <+35>: jbe 0x40126c <secret_phase+42>
0x0000000000401267 <+37>: call 0x40143a <explode_bomb>
0x000000000040126c <+42>: mov %ebx,%esi
0x000000000040126e <+44>: mov $0x6030f0,%edi
0x0000000000401273 <+49>: call 0x401204 <fun7>
0x0000000000401278 <+54>: cmp $0x2,%eax
0x000000000040127b <+57>: je 0x401282 <secret_phase+64>
0x000000000040127d <+59>: call 0x40143a <explode_bomb>
0x0000000000401282 <+64>: mov $0x402438,%edi
0x0000000000401287 <+69>: call 0x400b10 <puts@plt>
0x000000000040128c <+74>: call 0x4015c4 <phase_defused>
0x0000000000401291 <+79>: pop %rbx
0x0000000000401292 <+80>: ret

<secret_phase+19>处表明输入的是一个数字,<secret_phase+30>表明该数字为正数且小于等于0x3e9

<secret_phase+49>调用函数fun7,之后对返回值进行判断,要求其返回值为2。

之后打印一些信息后便结束了。

使用disas fun7获取fun7源码,其第一个参数为0x6030f0,第二个参数为输入参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0x0000000000401204 <+0>:     sub    $0x8,%rsp
0x0000000000401208 <+4>: test %rdi,%rdi
0x000000000040120b <+7>: je 0x401238 <fun7+52>
0x000000000040120d <+9>: mov (%rdi),%edx
0x000000000040120f <+11>: cmp %esi,%edx
0x0000000000401211 <+13>: jle 0x401220 <fun7+28>
0x0000000000401213 <+15>: mov 0x8(%rdi),%rdi
0x0000000000401217 <+19>: call 0x401204 <fun7>
0x000000000040121c <+24>: add %eax,%eax
0x000000000040121e <+26>: jmp 0x40123d <fun7+57>
0x0000000000401220 <+28>: mov $0x0,%eax
0x0000000000401225 <+33>: cmp %esi,%edx
0x0000000000401227 <+35>: je 0x40123d <fun7+57>
0x0000000000401229 <+37>: mov 0x10(%rdi),%rdi
0x000000000040122d <+41>: call 0x401204 <fun7>
0x0000000000401232 <+46>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401236 <+50>: jmp 0x40123d <fun7+57>
0x0000000000401238 <+52>: mov $0xffffffff,%eax
0x000000000040123d <+57>: add $0x8,%rsp
0x0000000000401241 <+61>: ret

一个典型的二叉树递归,其对应的C代码应为:

1
2
3
4
5
6
7
8
9
10
11
12
int fun7(TreeNode *root, long value) {
if(NULL == root) {
return -1;
}
if(root -> value < value) {
return 2 * func7(root->more, value) + 1;
} else if(root -> value > value) {
return 2 * func7(root->less, value);
} else {
return 0;
}
}

接下来查看二叉树结点:

x/3xg 0x6030f0

1
2
0x6030f0 <n1>:  0x0000000000000024      0x0000000000603110
0x603100 <n1+16>: 0x0000000000603130

x/3xg 0x603110

1
2
0x603110 <n21>: 0x0000000000000008      0x0000000000603190
0x603120 <n21+16>: 0x0000000000603150

x/3xg 0x603130

1
2
0x603130 <n22>: 0x0000000000000032      0x0000000000603170
0x603140 <n22+16>: 0x00000000006031b0

x/3xg 0x603190

1
2
0x603190 <n31>: 0x0000000000000006      0x00000000006031f0
0x6031a0 <n31+16>: 0x0000000000603250

x/3xg 0x603150

1
2
0x603150 <n32>: 0x0000000000000016      0x0000000000603270
0x603160 <n32+16>: 0x0000000000603230

x/3xg 0x603170

1
2
0x603170 <n33>: 0x000000000000002d      0x00000000006031d0
0x603180 <n33+16>: 0x0000000000603290

x/3xg 0x6031b0

1
2
0x6031b0 <n34>: 0x000000000000006b      0x0000000000603210
0x6031c0 <n34+16>: 0x00000000006032b0

x/3xg 0x6031f0

1
2
0x6031f0 <n41>: 0x0000000000000001      0x0000000000000000
0x603200 <n41+16>: 0x0000000000000000

x/3xg 0x603250

1
2
0x603250 <n42>: 0x0000000000000007      0x0000000000000000
0x603260 <n42+16>: 0x0000000000000000

x/3xg 0x603270

1
2
0x603270 <n43>: 0x0000000000000014      0x0000000000000000
0x603280 <n43+16>: 0x0000000000000000

x/3xg 0x603230

1
2
0x603230 <n44>: 0x0000000000000023      0x0000000000000000
0x603240 <n44+16>: 0x0000000000000000

x/3xg 0x6031d0

1
2
0x6031d0 <n45>: 0x0000000000000028      0x0000000000000000
0x6031e0 <n45+16>: 0x0000000000000000

x/3xg 0x603290

1
2
0x603290 <n46>: 0x000000000000002f      0x0000000000000000
0x6032a0 <n46+16>: 0x0000000000000000

x/3xg 0x603210

1
2
0x603210 <n47>: 0x0000000000000063      0x0000000000000000
0x603220 <n47+16>: 0x0000000000000000

x/3xg 0x6032b0

1
2
0x6032b0 <n48>: 0x00000000000003e9      0x0000000000000000
0x6032c0 <n48+16>: 0x0000000000000000

这是一棵4层的满二叉树,结点的层序遍历后结果值序列如下:

1
0x24 0x8 0x32 0x6 0x16 0x2d 0x6b 0x1 0x7 0x14 0x23 0x28 0x2f 0x63 0x3e9

此时有以下root移动方式使得结果为2:

  1. less more

  2. less more less

故结果值可能为0x160x14,均符合条件。

故答案为(一行一个):

1
2
22
20

通过截图

最后附上通过的截图:

bitXor

题目

描述:仅使用~&实现^操作。

允许使用的操作符:~ &

允许使用的操作符最大数量:14

实现

1
2
3
int bitXor(int x, int y) {
return (~((~x) & (~y))) & (~(x & y));
}

解析

有:

  1. x ^ y = (x | y) & (~(x & y))

  2. x | y = ~((~x) & (~y))

故:

x ^ y = (~((~x) & (~y))) & (~(x & y))

tmin

题目

描述:返回二进制补码整数的最小值。

允许使用的操作符: ~ & ^ | + << >>

允许使用的操作符最大数量:4

实现

1
2
3
int tmin(void) {
return (1 << 31);
}

解析

INT_MIN0b10[31],故将1左移31位即可

isTmax

题目

描述:判断输入参数是否是二进制补码整数所能表示的最大值,是则返回1,否则返回0。

允许使用的操作符: ~ & ^ | +

允许使用的操作符最大数量:10

实现

1
2
3
int isTmax(int x) {
return !(((x + 1) ^ (~x)) | !(~x));
}

解析

INT_MAX0b01[31],可知~INT_MAX = INT_MAX + 1。下面证明除了INT_MAX0b1[32]外无其他数使得~x = x + 1

对于任何a,除了0b1[32]外,均有a = A01[n]。此时~a = ~A10[n]a + 1 = A10[n],若希望~a = a + 1,要求A = ~A,这只有当A为空的情况下成立。

同时,~0b1[32] = 0b0[32]0b1[32] + 1 = 0b0[32],同样成立。

因此,本题实质上进行如下判断:

  1. x + 1 == ~x,当(x + 1) ^ ~x为零时成立

  2. x != 0b0[32],当~x不为零时成立。

allOddBits

题目

描述:判断输入参数的所有奇数位上的数值是否为1,下标从0开始。是则返回1,否则返回0。

允许使用的操作符: ~ & ^ | + << >>

允许使用的操作符最大数量:12

实现

1
2
3
4
5
6
7
8
int allOddBits(int x) {
x = x >> 1;
x = x & (x >> 2);
x = x & (x >> 4);
x = x & (x >> 8);
x = x & (x >> 16);
return x & 1;
}

解析

其实本题最简单的思路是构造mask = 0xAAAAAAAA,判断x & mask是否等于mask即可。

目前采用的思路是遍历。利用&操作符的x & (y & z) = x & y & z的性质逐层计算,最终获得结果。原理示意图如下图:

negate

题目

描述:返回输入参数的相反数。

允许使用的操作符: ~ & ^ | + << >>

允许使用的操作符最大数量:5

实现

1
2
3
int negate(int x) {
return (~x) + 1;
}

解析

x + (-x) = 0b0[32] = 0b1[32] + 0b1

所以有:

(-x) = 0b1[32] + 0b1 - x = (~x) + 0b1

isAsciiDigit

题目

描述:返回输入参数的值是否在[0x30, 0x39]区间内,是则返回1,否则返回0。

允许使用的操作符: ~ & ^ | + << >>

允许使用的操作符最大数量:15

实现

1
2
3
4
int isAsciiDigit(int x) {
return !((x & (~0x3f)) | ((x & 0x30) ^ 0x30) |
((!!(x & 0x8)) & (!!((x & 0xe) ^ 0x8))));
}

解析

经分析,有如下条件:

  1. x <= 0x3f,故有x & (~0x3f)为0

  2. x >= 0x30,故有(x & 0x30) ^ 0x30为0

  3. x <= 0x39,根据其对应二进制特征,可分为以下两类:

    • 0x30 <= x <= 0x37,其特征为后4个比特为0b0xxx,故有x & 0x8为0

    • 0x38 <= x <= 0x39,其特征为后4个比特为0b100x,故有(x & 0xe) ^ 0x8为0

    上述两项只要有一项为0即可,故采用&进行连接。为了保证二者均不为0时结果也不为0,使用!!对剩下的位数进行对齐。

conditional

题目

描述:要求函数行为与x ? y : z一致。

允许使用的操作符: ~ & ^ | + << >>

允许使用的操作符最大数量:16

实现

1
2
3
4
5
6
7
8
9
int conditional(int x, int y, int z) {
x = !x;
x = x | (x << 1);
x = x | (x << 2);
x = x | (x << 4);
x = x | (x << 8);
x = x | (x << 16);
return ((~x) & y) | (x & z);
}

解析

做出如下mask,当x = 0时,mask = 0b1[32];当x != 0时,mask = 0b0[32]。之后对输入进行&操作即可。

isLessOrEqual

题目

描述:判断是否有x <= y,是则返回1,否则返回0。

允许使用的操作符: ~ & ^ | + << >>

允许使用的操作符最大数量:24

实现

1
2
3
int isLessOrEqual(int x, int y) {
return !((((~x) + 1 + y) & (1 << 31)));
}

解析

进行如下判断:(-x) + y >= 0。又有(-x) = ~x + 0b1。故进行计算后判断符号位即可。

logicalNeg

题目

描述:行为应与!x相同。

允许使用的操作符:~ & ^ | + << >>

允许使用的操作符最大数量:12

实现

1
2
3
4
5
6
7
8
int logicalNeg(int x) {
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
return (~x) & 1;
}

解析

制作如下maskx = 0时,mask = 0b0[32]x != 0时,mask = 0b1[32]。最后按位取反并取最后一位即可。

howManyBits

题目

描述:返回表示x需要的最少位数。

允许使用的操作符:! ~ & ^ | + << >>

允许使用的操作符最大数量:90

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int howManyBits(int x) {
int mark2;
int mark4;
int mark8;
int mark16;
int mark32;

mark2 = 0x55;
mark2 |= mark2 << 8;
mark2 |= mark2 << 16;

mark4 = 0x33;
mark4 |= mark4 << 8;
mark4 |= mark4 << 16;

mark8 = 0xf;
mark8 |= mark8 << 8;
mark8 |= mark8 << 16;

mark16 = 0xff;
mark16 |= mark16 << 16;

mark32 = 0xff;
mark32 |= mark32 << 8;

// 将负数取反,正数不变
x = x ^ (x >> 31);

// 将x转化为0b0[n]1[32-n]的形式
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);

// 对1的个数进行求和
x = (x & mark2) + ((x >> 1) & mark2);
x = (x & mark4) + ((x >> 2) & mark4);
x = (x & mark8) + ((x >> 4) & mark8);
x = (x & mark16) + ((x >> 8) & mark16);
x = (x & mark32) + ((x >> 16) & mark32);

// 加上符号位
return x + 1;
}

解析

本次实验中最难的题,根据题意,其实际上是在找去除多余符号位后的整数补码表示位数。

首先,为了简化情况,观察到howManyBits(x) = howManyBits(~x),于是选择将负数取反。这一步中涉及一些位运算技巧:

1. 对0的`^`相当于取反,对1的`^`则保持原值。

2. `int`的右移为算数右移,右移31位的结果相当于获得一个由符号位填充的`mask`。

之后,为了便于计算,将x转化为0b0[n]1[32-n]的形式(之前的负数要取反也是为了统一该步的操作)。

接下来,使用mask和移位操作,统计x中1的个数,其过程示意图如下图:

将得到的x加上一位符号位获得最终结果。

floatScale2

题目

描述:返回2 * x的二进制表示,对于INFNaN原样返回。

允许使用的操作符:任意int/unsigned的算数运算符、||&&运算符和ifwhile关键字。

允许使用的操作符最大数量:30

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned floatScale2(unsigned uf) {
unsigned s = uf >> 31;
unsigned exp = (uf >> 23) & 0xff;
unsigned frac = uf & 0x7fffff;

if(exp == 0xff) {
return uf;
}

if(exp == 0) {
frac <<= 1;
} else {
exp += 1;

if(exp == 0xff) {
frac = 0;
}
}
return (s << 31) | (exp << 23) | frac;
}

解析

根据浮点数的结构,将其拆分成sexpfrac

  1. 对于NaNINF,直接返回原数据。

  2. 对于非规格化数,将frac右移一位即可。注意,此时非规格化数可能会转化成规格化数,但转化后的结果为exp = 1frac & ~0x800000,在组装成为浮点数后的结果与直接进行组装相同,故不需要进行特殊处理。

  3. 对于规格化数,需要将exp加1。但在操作后有可能导致溢出,故需要将frac置0,将结果设为INF而不是NaN

floatFloat2Int

题目

描述:返回(int)x的返回值,对于INFNaN和溢出值返回0x80000000

允许使用的操作符:任意int/unsigned的算数运算符、||&&运算符和ifwhile关键字。

允许使用的操作符最大数量:30

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int floatFloat2Int(unsigned uf) {
unsigned s = uf >> 31;
unsigned exp = (uf >> 23) & 0xff;
unsigned frac = uf & 0x7fffff;

frac |= 0x800000;

if(exp <= 0x96) {
if(exp < 0x7f) {
return 0;
}

frac >>= 0x96 - exp;
} else {
if(exp >= 0x9e) {
return 0x80000000;
}

frac <<= exp - 0x96;
}
return s ? (~frac) + 1 : frac;
}

解析

根据浮点数的结构,将其拆分成sexpfrac

  1. 对于INFNaN,直接返回0x80000000

  2. 对于非规格化数,直接返回0

  3. 对于规格化数,需要现根据exp判断其是否溢出或为0。注意,由于我们将目前的计算视为正数,故原则上需要特别INT_MIN,但应为本次溢出返回值即为INT_MIN,因此可以两者进行合并处理,只要发生正数溢出便返回INT_MIN

  4. 根据符号位将其转化成为补码。

代码中各个数值的出现的原因:

  1. 0x7f:即加上偏置值后exp实际为0时的exp值。若exp小于该值,则该浮点数不存在整数部分。

  2. 0x96:因为在取frac的时候是整数形式,因此实际上暗中给其加上了一层偏置,大小为frac小数部分位数23。故有0x7f + 0x17 = 0x96。若exp小于该值,则frac需要右移,否则需要左移。

  3. 0x9e:对于规格化整数,在其frac中的整数部分的1到达符号位时发生溢出,此时右移位数为31。固有0x7f + 0x1f = 0x9e。若exp小于该值,则规格化数不会溢出,否则发生溢出。

floatFloat2Int

题目

描述:返回2.0 ^ x的二进制表示,如果结果过大则返回+INF

允许使用的操作符:任意int/unsigned的算数运算符、||&&运算符和ifwhile关键字。

允许使用的操作符最大数量:30

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned floatPower2(int x) {
if(x >= 128) {
return 0x7f800000;
}

if(x > -127) {
return (0x7f + x) << 23;
}

if(x < -150) {
return 0;
}

return 0x800000 >> (-x - 126);
}

解析

  1. x大于等于128时,超过浮点数最大值,发生溢出,返回+INF

  2. x大于-127小于128时,是规格化数,只需要处理exp部分。

  3. x小于-150时(-150 = -127 + -23),超出浮点数精度,返回0。

  4. 否则,为非规格化数,只需要处理frac部分。

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <gst/gst.h>

int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;

/* Initialize GStreamer */
gst_init(&argc, &argv);

/* Build the pipeline */
pipeline = gst_parse_launch("playbin uri=file:///root/video.mp4", NULL);

/* Start playing */
gst_element_set_state(pipeline, GST_STATE_PLAYING);

/* Wait until error or EOS */
bus = gst_element_get_bus(pipeline);
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

/* Free resources */
if(msg != NULL)
gst_message_unref(msg);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}

效果如下图:

关键代码解析

1
gst_init(&argc, &argv);

gst_init用于初始化,必须在其他GStreamer接口之前被调用,不需要其处理命令行参数时可将参数赋值为NULL。

1
GstElement *pipeline = gst_parse_launch("playbin uri=file:///root/video.mp4", NULL);

创建了一个Pipeline,该Pipeline是Playbin类型的。

GStreamer是一个基于管道机制的多媒体库,一个典型的GStreamer程序包含了一个或多个Pipeline,每个Pipeline通过其拥有的一系列Element完成对多媒体数据的处理。同时,虽然是由C语言实现的,但GStreamer实现了自己的面向对象机制,因此其各类型间具有继承关系。

本句中涉及了两个类型,GstPipelineplaybin

其中,GstPipeline是一个在GStreamer当中十分重要的类型,其继承关系如下所示。

1
2
3
4
5
6
GObject
╰── GInitiallyUnowned
╰── GstObject
╰── GstElement
╰── GstBin
╰── GstPipeline

GstElement是一个抽象基类,定义了Element、Pipeline等的接口,在GStreamer中该接类被广泛的用作基类。

GstBin是一个能够容纳和管理其他GstElement的类型,其能够将用户的操作分发到其管理的多个GstElement中,降低了使用复杂性。同时,GstBin还提供了对GstMessage的拦截功能。

GstPipeline通常被用作顶层的容器,其进一步的拓展了GstBin的功能,对内提供了用于多媒体数据同步的全局时钟GstClock,对外提供了统一的消息接口GstBus

playbin则是扩展了GstPipeline的功能,其能够通过有效的Uri获取数据并将其渲染到屏幕上。因为其功能的完整性和高度的集成化,本文不对其进行展开叙述。

gst_parse_launch则通过解析传入字符串来创建指定的GstPipeline

1
gst_element_set_state(pipeline, GST_STATE_PLAYING);

gst_element_set_state用于设置GstElement的状态,该句将pipeline设为了播放状态,使得视频开始能够播放。

正如上文所说的,GstBin能对其管理的GstElement进行统一操作。实际上在该句之后pipeline中包含的所有GstElement都转变为了播放状态,这才使得多媒体数据顺利播放。

每一个GstElement都具有内部状态,通常使用的是以下几个:

1. GST_STATE_NULL:默认状态,该状态下[GstElement](https://gstreamer.freedesktop.org/documentation/gstreamer/gstelement.html?gi-language=c)不会具有任何资源。转入该状态后会释放持有的资源。

2. GST_STATE_READY:在该状态下[GstElement](https://gstreamer.freedesktop.org/documentation/gstreamer/gstelement.html?gi-language=c)将获得全部需要的资源,包括打开的设备、缓冲区等,但流还是会处于关闭状态并被置零。如果在流打开过的情况下转入该状态,将会重置流的配置和位置。

3. GST_STATE_PAUSED:在该状态下[GstElement](https://gstreamer.freedesktop.org/documentation/gstreamer/gstelement.html?gi-language=c)不仅具有GST_STATE_READY分配的全部资源,还会将流打开,使得能够对流进修改。但不会具有运行时钟,流不会随着时钟而发生变化。

4. GST_STATE_PLAYING:在该状态下[GstElement](https://gstreamer.freedesktop.org/documentation/gstreamer/gstelement.html?gi-language=c)的情况与GST_STATE_PAUSED基本相同。但是会具有运行时钟,流会自然地随时钟信号进行处理。
1
2
3
GstBus *bus = gst_element_get_bus(pipeline);
GstMessage *msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

这两句分别用于获取消息总线和等待播放程序运行完毕。

gst_element_get_bus返回传入的GstElement对应的GstBus

GstBus是一个能够传递GstMessage的类型。GstBus主要用于解决多线程通信的问题。在播放时,GStreamer内部可能会创建多个线程进行处理,这使得GStreamer需要一个统一的消息传递机制来降低应用程序的复杂度。

gst_bus_timed_pop_filtered则用于监听传入的GstBus,等待目标消息的返回。根据该句的传入参数,程序将会无限等待,直到有发生错误或播放完毕的消息传回。

GstMessage是GStreamer封装的消息类型。一个常用的读取程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if(msg != NULL) {
GError *err;
gchar *debug_info;

switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err);
g_free (debug_info);
break;
case GST_MESSAGE_EOS:
g_print ("End-Of-Stream reached.\n");
break;
default:
/* We should not reach here because we only asked for ERRORs and EOS */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
1
2
3
4
5
if(msg != NULL)
gst_message_unref(msg);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);

本段进行了资源释放。由于C语言没有析构函数和垃圾回收等资源管理机制,因此仍然需要进行手动管理。

GStreamer使用引用计数的方式来管理资源。通过在每个类型中放置一个引用数,并在引用数归零是才进行资源释放的方式,能够避免资源的过早释放,因此在使用GStreamer提供的API时,需要着重关注其对引用计数的影响。

gst_object_unref能够将输入的变量的引用计数减一,归零时自动释放。

与之对应的gst_object_ref能够将输入的变量的引用计数加一。

安装GStreamer

使用以下命令进行安装GStreamer

1
sudo apt-get install libgstreamer* gstreamer1.0-* -y

测试编译环境

使用如下命令查看GStreamer的编译命令。

1
2
pkg-config --cflags gstreamer-1.0 # 获得头文件路径
pkg-config --libs gstreamer-1.0 # 获得库文件路径

使用如下demo.c进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <gst/gst.h>

int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;

/* Initialize GStreamer */
gst_init(&argc, &argv);

/* Build the pipeline, please change the URI to the correct one */
pipeline = gst_parse_launch("playbin uri=file:///root/video.mp4", NULL);

/* Start playing */
gst_element_set_state(pipeline, GST_STATE_PLAYING);

/* Wait until error or EOS */
bus = gst_element_get_bus(pipeline);
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

/* Free resources */
if(msg != NULL)
gst_message_unref(msg);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}

使用如下命令进行编译:

1
gcc demo.c -o demo `pkg-config --cflags --libs gstreamer-1.0`

使用如下命令运行:

1
./demo

出现一个窗口播放视频,证明安装成功:

安装桌面环境

由于之前的开发环境中未安装桌面环境,为了能够看到GStreamer程序执行结果,因此需要安装新的桌面环境。

使用下列命令最小化安装gnome,并设置startx启动:

1
2
3
sudo apt install xorg gnome-core
echo "exec gnome-session" > ~/.xinitrc
startx

之后可以看到启动后的gnome桌面。

设置默认进入命令行

为了提高开机速度和降低性能损耗,希望开机默认进入命令行,只在需要时进入桌面环境。

修改/etc/default/grub文件,将GRUB_CMDLINE_LINUX的值从""改为"text"

之后执行以下命令:

1
2
sudo update-grub
sudo systemctl set-default multi-user.target

重启后会发现直接进入命令行。

post_asset_folder

post_asset_foldertrue时,hexo new <title>指令会在对应的文件生成路径下生成一个同名文件夹。可以使用asset_*类的选项该文件夹内的资源,如加载图片是可以使用{% asset_img image.jpg %}进行加载。

hexo-renderer-marked 3.1.0后提供了新选项,可以使用![](image.jpg)的方式加载图片,具体打开形式如下:

1
2
3
4
post_asset_folder: true
marked:
prependRoot: true
postAsset: true

表示文章的永久链接格式,通俗来说就是访问该文章是域名后面的部分。通常将其设置为:

1
permalink: :year/:month/:day/:name/

new_post_name

创建新文章的路径名。通常将其设置为:

1
new_post_name: :year/:month/:day/:title.md

这样可以使得不同日期写的文章放置在不同的文件夹下,便于管理。

问题

现存如下GStreamer管道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
typedef struct g_pipeline0_style {
GstElement *pipeline;

GstElement *src;
GstElement *yuy2_filter;
GstElement *v4l2convert;
GstElement *rgb_filter;
GstElement *appsink;
} g_pipeline0_style;

typedef struct g_pipeline1_style {
GstElement *pipeline;

GstElement *appsrc;
GstElement *rgb_filter;
GstElement *waylandsink;
} g_pipeline1_style;

void init_gstreamer(g_pipeline0_style *pipeline0, g_pipeline1_style *pipeline1) {
g_assert(pipeline0 && pipeline1);

// 创建
pipeline0->pipeline = gst_pipeline_new("pipeline0");
pipeline0->src = gst_element_factory_make(GST_SRC, "src");
pipeline0->yuy2_filter = gst_element_factory_make("capsfilter", "videoconvert");
pipeline0->v4l2convert = gst_element_factory_make("v4l2convert", "v4l2convert");
pipeline0->rgb_filter = gst_element_factory_make("capsfilter", "rgb_filter");
pipeline0->appsink = gst_element_factory_make("appsink", "appsink");

pipeline1->pipeline = gst_pipeline_new("pipeline1");
pipeline1->appsrc = gst_element_factory_make("appsrc", "appsrc");
pipeline1->rgb_filter = gst_element_factory_make("capsfilter", "rgb_filter");
pipeline1->waylandsink = gst_element_factory_make("waylandsink", "waylandsink");

g_assert(pipeline0->pipeline && pipeline0->src && pipeline0->yuy2_filter &&
pipeline0->v4l2convert && pipeline0->rgb_filter && pipeline0->appsink &&
pipeline1->pipeline && pipeline1->appsrc && pipeline1->rgb_filter &&
pipeline1->waylandsink);

// 设置
GstCaps *caps_yuy2 = gst_caps_new_simple("video/x-raw",
"width", G_TYPE_INT, DEFAULT_WIDTH,
"height", G_TYPE_INT, DEFAULT_HEIGHT,
"format", G_TYPE_STRING, "YUY2",
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
g_assert(caps_yuy2);
g_object_set(G_OBJECT(pipeline0->yuy2_filter), "caps", caps_yuy2, NULL);
gst_caps_unref(caps_yuy2);

g_object_set(pipeline0->v4l2convert, "disable-passthrough", TRUE, NULL);
gst_util_set_object_arg(G_OBJECT(pipeline0->v4l2convert), "output-io-mode", "dmabuf-import");
gst_util_set_object_arg(G_OBJECT(pipeline0->v4l2convert), "capture-io-mode", "dmabuf");

GstCaps *caps_rgb0 = gst_caps_new_simple("video/x-raw",
"width", G_TYPE_INT, DEFAULT_WIDTH,
"height", G_TYPE_INT, DEFAULT_HEIGHT,
"format", G_TYPE_STRING, "RGB",
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
g_assert(caps_rgb0);
g_object_set(G_OBJECT(pipeline0->rgb_filter), "caps", caps_rgb0, NULL);
gst_caps_unref(caps_rgb0);

GstAppSinkCallbacks app_sink_callback = { NULL, NULL, on_new_sample };
gst_app_sink_set_callbacks(GST_APP_SINK(pipeline0->appsink), &app_sink_callback,
pipeline1->appsrc, NULL);

GstCaps *caps_rgb1 = gst_caps_new_simple("video/x-raw",
"width", G_TYPE_INT, DEFAULT_WIDTH,
"height", G_TYPE_INT, DEFAULT_HEIGHT,
"format", G_TYPE_STRING, "RGB",
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
g_assert(caps_rgb1);
g_object_set(G_OBJECT(pipeline1->rgb_filter), "caps", caps_rgb1, NULL);
gst_caps_unref(caps_rgb1);

g_object_set(G_OBJECT(pipeline1->waylandsink), "sync", FALSE, NULL);

// 将元素添加到管道中
gst_bin_add_many(GST_BIN(pipeline0->pipeline), pipeline0->src, pipeline0->yuy2_filter,
pipeline0->v4l2convert, pipeline0->rgb_filter, pipeline0->appsink, NULL);
gst_bin_add_many(GST_BIN(pipeline1->pipeline), pipeline1->appsrc, pipeline1->rgb_filter,
pipeline1->waylandsink, NULL);

// 连接元素
gst_bin_link_many(pipeline0->src, pipeline0->yuy2_filter, pipeline0->v4l2convert,
pipeline0->rgb_filter, pipeline0->appsink, NULL);
gst_bin_link_many(pipeline1->appsrc, pipeline1->rgb_filter, pipeline1->waylandsink, NULL);
}

两个管道间使用如下函数进行链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
GstFlowReturn on_new_sample(GstAppSink *sink, gpointer user_data) {
GstFlowReturn ret = GST_FLOW_ERROR;

GstSample *sample = gst_app_sink_pull_sample(sink);
GstAppSrc *app_src = (GstAppSrc *)user_data;
g_assert(sample && app_src);

if(!gst_sample_is_writable(sample)) {
sample = gst_sample_make_writable(sample);
}

g_assert(gst_sample_is_writable(sample));

GstBuffer *buffer = gst_sample_get_buffer(sample);
g_assert(buffer);

if(!gst_buffer_is_writable(buffer)) {
buffer = gst_buffer_make_writable(buffer);
}

g_assert(gst_buffer_is_writable(buffer));

gboolean ok;
GstMapInfo info;
ok = gst_buffer_map(buffer, &info, GST_MAP_READWRITE);
g_assert(ok);

GstCaps *caps = gst_sample_get_caps(sample);
gint width, height;
GstStructure *structure = gst_caps_get_structure(caps, 0);
gst_structure_get_int(structure, "width", &width);
gst_structure_get_int(structure, "height", &height);

do_something(info.data, height, width);

gst_buffer_unmap(buffer, &info);

gst_sample_set_buffer(sample, buffer);

ret = gst_app_src_push_sample(GST_APP_SRC(app_src), sample);
gst_sample_unref(sample);
return ret;
}

但在运行时报错:

1
2
3
GStreamer-Wayland:ERROR:../gst-plugins-bad-1.22.0/gst-libs/gst/wayland/gstwlbuffer.c:178:gstmemory_disposed: assertion failed: (!priv->used_by_compositor)
Bail out! GStreamer-Wayland:ERROR:../gst-plugins-bad-1.22.0/gst-libs/gst/wayland/gstwlbuffer.c:178:gstmemory_disposed: assertion failed: (!priv->used_by_compositor)
Aborted (core dumped)

查看日志,发现第一条错误如下:

1
ERROR waylandsink gstwaylandsink.c:1181:gst_wayland_sink_show_frame:<waylandsink> buffer buffer: 0x55a234bbf0, pts 0:00:00.000000000, dts 0:00:00.000000000, dur 0:00:00.033333333, size 3072000, offset none, offset_end none, flags 0x40 cannot have a wl_buffer

原因

主要是因为缓冲区类型和数据类型不匹配导致的。WaylandSink仅支持DMA Buffer携带的RGB数据,但在复制后,Buffer类型变成了Share Memory Buffer,这是WaylandSink不支持的,因此报错。

解决

虽然从理论上来说将复制出的Buffer转换为DMA Buffer就可以解决该问题。但笔者工作较忙没有进行论证。

还有一种妥协性的解决方法,就是去除pipeline0->v4l2convertpipeline0->rgb_filter,将pipeline1->rgb_filter类型转换为YUY2,这样到达WaylandSink的就是YUY2,而WaylandSink支持携带YUY2数据的Share Memory Buffer

现象

在Ubuntu 22.04默认的图形环境中无法打开终端。

原因

当前系统使用语言与格式不匹配。如使用语言为English,但使用格式为Chinese。

解决方案

在“Setting”中的“Language & Region”将二者修改为一致。如“English (United States)”与“United States”或“中文”与“中国”。

调整控制台输出行、列数

使用stty命令,若要调整为24行80列,则命令如下:

1
stty rows 24 cols 80

调整控制台字体

使用dpkg-reconfigure console-setup命令,可以调整字符编码、字体、字号等。

因为要配置使用https的源,这需要先确保安装证书认证工具:

1
sudo apt install ca-certificates -y

编辑系统文件/etc/apt/sources.list,其中阿里云源如下:

1
2
3
4
5
6
7
8
9
10
11
deb https://mirrors.aliyun.com/debian/ bookworm main non-free non-free-firmware contrib
deb-src https://mirrors.aliyun.com/debian/ bookworm main non-free non-free-firmware contrib

deb https://mirrors.aliyun.com/debian-security/ bookworm-security main
deb-src https://mirrors.aliyun.com/debian-security/ bookworm-security main

deb https://mirrors.aliyun.com/debian/ bookworm-updates main non-free non-free-firmware contrib
deb-src https://mirrors.aliyun.com/debian/ bookworm-updates main non-free non-free-firmware contrib

deb https://mirrors.aliyun.com/debian/ bookworm-backports main non-free non-free-firmware contrib
deb-src https://mirrors.aliyun.com/debian/ bookworm-backports main non-free non-free-firmware contrib

使用sudo apt update更新源。