Phase 6
Phase 6 的汇编又臭又长,先把 Phase 6 反汇编出来
完整的代码就不放了,先来一段段分析
Section 1
00000000004010f4 <phase_6>: | |
4010f4: 41 56 push %r14 | |
4010f6: 41 55 push %r13 | |
4010f8: 41 54 push %r12 | |
4010fa: 55 push %rbp | |
4010fb: 53 push %rbx | |
4010fc: 48 83 ec 50 sub $0x50,%rsp | |
401100: 49 89 e5 mov %rsp,%r13 | |
401103: 48 89 e6 mov %rsp,%rsi | |
401106: e8 51 03 00 00 callq 40145c <read_six_numbers> |
这一段是函数调用相关的,不用过多的在意,不过注意最后一行调用了 read_six_numbers
,根据之前的练习中,这个函数会读取 6 个数字并放在 %rsp~%rsp+0x14
中(这里可以用 GDB 的 x/d
打印检查)
Section 2
下面开始正文了,先上代码
40110b: 49 89 e6 mov %rsp,%r14 | |
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d | |
; 外层循环 | |
401114: 4c 89 ed mov %r13,%rbp | |
401117: 41 8b 45 00 mov 0x0(%r13),%eax | |
40111b: 83 e8 01 sub $0x1,%eax ; eax -= 0x1 | |
40111e: 83 f8 05 cmp $0x5,%eax ; eax <= 0x5 | |
401121: 76 05 jbe 401128 <phase_6+0x34> | |
401123: e8 12 03 00 00 callq 40143a <explode_bomb> | |
401128: 41 83 c4 01 add $0x1,%r12d ; r12d += 1 | |
40112c: 41 83 fc 06 cmp $0x6,%r12d ; r12d == 0x6 | |
401130: 74 21 je 401153 <phase_6+0x5f> | |
401132: 44 89 e3 mov %r12d,%ebx ; ebx = r12d | |
; 内层循环 | |
401135: 48 63 c3 movslq %ebx,%rax ; rax = ebx | |
401138: 8b 04 84 mov (%rsp,%rax,4),%eax ; eax = *(rsp+rax*4) | |
40113b: 39 45 00 cmp %eax,0x0(%rbp) ; *(rbp) != eax | |
40113e: 75 05 jne 401145 <phase_6+0x51> | |
401140: e8 f5 02 00 00 callq 40143a <explode_bomb> | |
401145: 83 c3 01 add $0x1,%ebx ; rbx += 1 | |
401148: 83 fb 05 cmp $0x5,%ebx ; ebx <= 5 | |
40114b: 7e e8 jle 401135 <phase_6+0x41> | |
40114d: 49 83 c5 04 add $0x4,%r13 ; r13 += 0x4 | |
401151: eb c1 jmp 401114 <phase_6+0x20> |
最后一行的 401151
可以看出外层嵌套了一个大的循环,401117
中,将 eax
赋值为 r13
指向的地址,也就是第 r12d
个输入变量,然后 40111e
的比较确保了输入变量是小于 6 的
内层循环中,则是取出第 r12d
个输入变量后边的,依次与 eax
比较,确保每个变量都不相同
Section 3
; 每一个输入用 7 减去 | |
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi | |
401158: 4c 89 f0 mov %r14,%rax | |
40115b: b9 07 00 00 00 mov $0x7,%ecx | |
; 循环在这里 | |
401160: 89 ca mov %ecx,%edx | |
401162: 2b 10 sub (%rax),%edx | |
401164: 89 10 mov %edx,(%rax) | |
401166: 48 83 c0 04 add $0x4,%rax | |
40116a: 48 39 f0 cmp %rsi,%rax | |
40116d: 75 f1 jne 401160 <phase_6+0x6c> |
rax
每次增加 4 个 Byte 来读取下一个 Int ,并且将 rax
与 rsp+0x18
比较
每次用 0x7
减去
Section 4
40116f: be 00 00 00 00 mov $0x0,%esi ; esi=0 | |
401174: eb 21 jmp 401197 <phase_6+0xa3> | |
; 链表索引,直到得到链表第 ecx 项,讲第 ecx 项的地址存入 rdx | |
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx ; rax = *(rdx+0x8) | |
40117a: 83 c0 01 add $0x1,%eax ; eax += 1 | |
40117d: 39 c8 cmp %ecx,%eax ; eax != ecx | |
40117f: 75 f5 jne 401176 <phase_6+0x82> | |
401181: eb 05 jmp 401188 <phase_6+0x94> | |
401183: ba d0 32 60 00 mov $0x6032d0,%edx ; edx = 0x6032d0 | |
401188: 48 89 54 74 20 mov % rdx,0x20 (% rsp,% rsi,2) ; *(0x20+rsp+rsi*2) = rdx 在 Stack 顶部存入 rdx | |
40118d: 48 83 c6 04 add $0x4,% rsi ; rsi += 4 枚举下一个输入 | |
401191: 48 83 fe 18 cmp $0x18,% rsi ; rsi == 0x18 检测是否到达输入末尾 (0x18 = 24 = 4 INT) | |
401195: 74 14 je 4011ab <phase_6+0xb7> | |
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx ; ecx=*(rsp+rsi) input[rsi/4] | |
40119a: 83 f9 01 cmp $0x1,%ecx ; ecx <= 1 | |
40119d: 7e e4 jle 401183 <phase_6+0x8f> | |
40119f: b8 01 00 00 00 mov $0x1,%eax ; eax = 1 | |
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx | |
4011a9: eb cb jmp 401176 <phase_6+0x82> |
这里就开始比较恶心了,先看里边有个 Magic Number 0x6032d0 ,用 GDB 打印出来发现是这样的:
1 | (gdb) x/24 0x6032d0 |
说实话我也不懂这个是什么 (???),一番百度 Google 后发现这是一个链表,结构大概是这样的
注意这里的 Next地址
是十进制表示的,要记得把它转换成十六进制
地址 | 值 | 序号 | Next 地址 |
---|---|---|---|
0x6032d0 | 332 | 1 | 0x6032e0 |
0x6032e0 | 168 | 2 | 0x6032f0 |
0x6032f0 | 924 | 3 | 0x603300 |
0x603300 | 691 | 4 | 0x603310 |
0x603310 | 477 | 5 | 0x603320 |
0x603320 | 443 | 6 | 0 |
循环中, ecx
用来存取当前所读取的输入变量,最后一行跳转到 401176
,因为链表的每个 Node 都是两个 Int 和一个 Pointer 组成的,所以 401176
中获得下一个 Node 的地址,并且用 eax
来做计数变量,可以看出这几行的作用就是使得 rdx
中存的是第 ecx
个链表元素
然后下边的就是在 0x20+rsp+rsi*2
后边添加上第 ecx
个 Node 的地址
举个例子,如果输入是 6 5 4 3 2 1
这一步就会把链表第六个 Node 的地址放到栈顶第一位,第五个放到第二位…
Section 5
; 按照上一个循环得到的顺序重新链接链表 | |
4011ab: 48 8b 5c 24 20 mov 0x20 (% rsp),% rbx ; rbx = *(0x20+rsp) 这里是按照输入存取链表的地方,得到第一个指针 | |
4011b0: 48 8d 44 24 28 lea 0x28 (% rsp),% rax ; rax = rsp+0x28 得到链表下一个 Node 的地址 | |
4011b5: 48 8d 74 24 50 lea 0x50 (% rsp),% rsi ; rsi = rsp+0x50 链表结尾 (?) | |
4011ba: 48 89 d9 mov %rbx,%rcx ; rcx = rbx | |
4011bd: 48 8b 10 mov (% rax),% rdx ; rdx = *(rax) 得到链表的下一项地址 | |
4011c0: 48 89 51 08 mov % rdx,0x8 (% rcx) ; *(rcx+0x8) = rdx 把当前的 Node->Next 设置为 rdx | |
4011c4: 48 83 c0 08 add $0x8,% rax ; rax += 0x8 rax 指向链表的 Pointer 区 | |
4011c8: 48 39 f0 cmp % rsi,% rax ; rsi == rax 判断是否遍历所有链表 | |
4011cb: 74 05 je 4011d2 <phase_6+0xde> | |
4011cd: 48 89 d1 mov % rdx,% rcx ; rcx = rdx 本循环中,rcx 存取当前 Node,rdx 存取下一个 Node | |
4011d0: eb eb jmp 4011bd <phase_6+0xc9> | |
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8 (% rdx) ; *(rdx+0x8) = 0 链表最后一项 ->Next 设为 NULL |
这里的工作就像注释所说的:把新的链表重新连接起来
Section 6
; rbx 存取的仍然是 *(0x20+rsp),也就是链表第一项 | |
4011d9: 00 | |
4011da: bd 05 00 00 00 mov $0x5,%ebp ; ebp = 0x5 | |
4011df: 48 8b 43 08 mov 0x8 (% rbx),% rax ; rax = *(rbx+0x8) rax 指向链表下一项 | |
4011e3: 8b 00 mov (% rax),% eax ; eax = *(rax) eax 设置为链表第 rax 项的值域 | |
4011e5: 39 03 cmp %eax,(%rbx) ; *(rbx) >= eax | |
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> | |
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> | |
4011ee: 48 8b 5b 08 mov 0x8 (% rbx),% rbx ; rbx = *(rbx+0x8) rbx 递增 | |
4011f2: 83 ed 01 sub $0x1,%ebp ; ebp -= 0x1 | |
4011f5: 75 e8 jne 4011df <phase_6+0xeb> |
这里比较的是新链接的链表的前一个元素的 Value 大于后一个元素的
Final
综上,这个逻辑总算是理清楚了:首先从输入读取 6 个数字,确保每个数字都是小于等于 6 并且只出现一次,然后将链表重新排序为按照输入的顺序,最后验证新的链表的前一项大于后一项
链表中的值分别是 332 168 924 691 477 433
,所以输入就是 4 3 2 1 6 5
有时间把之前的 Phase 补齐吧 (Flag)
EOF.