<__isoc_sscanf, read_six_numbers 분석 결론>
Phase_2에서 read_six_numbers에 대해 분석해 얻은 결론을 떠올리면 다음과 같습니다.
- %rsi에서 전달한 문자열을 통해 __isoc99_sscanf에서 어떤 형식으로 입력을 받을지 결정할 수 있습니다. 지금 경우에는 "%d %d %d %d %d %d" 가 전달되었으므로 6개의 정수를 읽는다고 판단할 수 있습니다.
- 입력한 문자열이 __isoc99_sscanf 를 진행하면서 %rdi에서 전달되어 sscanf 함수를 통해 정수 배열형태로 %rsp에 저장됩니다.
- read_six_numbers에서 정수 배열 개수가 5개 이하라면 폭탄을 터뜨립니다.
- read_six_numbers는 반환값으로 입력한 정수의 개수를 반환한다. 더 자세히 말하면 read_six_numbers는가 %rax에 저장하지 않으므로, __isoc99_sscanf 함수가 %rax에 입력한 정수의 개수를 반환합니다.
Phase_6
Dump of assembler code for function phase_6:
0x0000555555401510 <+0>: push %r14
0x0000555555401512 <+2>: push %r13
0x0000555555401514 <+4>: push %r12
0x0000555555401516 <+6>: push %rbp
0x0000555555401517 <+7>: push %rbx
0x0000555555401518 <+8>: sub $0x60,%rsp
0x000055555540151c <+12>: mov %fs:0x28,%rax
0x0000555555401525 <+21>: mov %rax,0x58(%rsp)
0x000055555540152a <+26>: xor %eax,%eax
0x000055555540152c <+28>: mov %rsp,%r13
0x000055555540152f <+31>: mov %r13,%rsi
0x0000555555401532 <+34>: callq 0x555555401acc <read_six_numbers>
0x0000555555401537 <+39>: mov %r13,%r12
0x000055555540153a <+42>: mov $0x0,%r14d
0x0000555555401540 <+48>: jmp 0x555555401567 <phase_6+87>
0x0000555555401542 <+50>: callq 0x555555401a90 <explode_bomb>
0x0000555555401547 <+55>: jmp 0x555555401576 <phase_6+102>
0x0000555555401549 <+57>: add $0x1,%ebx
0x000055555540154c <+60>: cmp $0x5,%ebx
0x000055555540154f <+63>: jg 0x555555401563 <phase_6+83>
0x0000555555401551 <+65>: movslq %ebx,%rax
0x0000555555401554 <+68>: mov (%rsp,%rax,4),%eax
0x0000555555401557 <+71>: cmp %eax,0x0(%rbp)
0x000055555540155a <+74>: jne 0x555555401549 <phase_6+57>
0x000055555540155c <+76>: callq 0x555555401a90 <explode_bomb>
0x0000555555401561 <+81>: jmp 0x555555401549 <phase_6+57>
0x0000555555401563 <+83>: add $0x4,%r13
0x0000555555401567 <+87>: mov %r13,%rbp
0x000055555540156a <+90>: mov 0x0(%r13),%eax
0x000055555540156e <+94>: sub $0x1,%eax
0x0000555555401571 <+97>: cmp $0x5,%eax
0x0000555555401574 <+100>: ja 0x555555401542 <phase_6+50>
0x0000555555401576 <+102>: add $0x1,%r14d
0x000055555540157a <+106>: cmp $0x6,%r14d
0x000055555540157e <+110>: je 0x555555401585 <phase_6+117>
0x0000555555401580 <+112>: mov %r14d,%ebx
0x0000555555401583 <+115>: jmp 0x555555401551 <phase_6+65>
0x0000555555401585 <+117>: lea 0x18(%r12),%rcx
0x000055555540158a <+122>: mov $0x7,%edx
0x000055555540158f <+127>: mov %edx,%eax
0x0000555555401591 <+129>: sub (%r12),%eax
0x0000555555401595 <+133>: mov %eax,(%r12)
0x0000555555401599 <+137>: add $0x4,%r12
0x000055555540159d <+141>: cmp %r12,%rcx
0x00005555554015a0 <+144>: jne 0x55555540158f <phase_6+127>
0x00005555554015a2 <+146>: mov $0x0,%esi
0x00005555554015a7 <+151>: jmp 0x5555554015c3 <phase_6+179>
0x00005555554015a9 <+153>: mov 0x8(%rdx),%rdx
0x00005555554015ad <+157>: add $0x1,%eax
0x00005555554015b0 <+160>: cmp %ecx,%eax
0x00005555554015b2 <+162>: jne 0x5555554015a9 <phase_6+153>
0x00005555554015b4 <+164>: mov %rdx,0x20(%rsp,%rsi,8)
0x00005555554015b9 <+169>: add $0x1,%rsi
0x00005555554015bd <+173>: cmp $0x6,%rsi
0x00005555554015c1 <+177>: je 0x5555554015d9 <phase_6+201>
0x00005555554015c3 <+179>: mov (%rsp,%rsi,4),%ecx
0x00005555554015c6 <+182>: mov $0x1,%eax
0x00005555554015cb <+187>: lea 0x202c5e(%rip),%rdx # 0x555555604230 <node1>
0x00005555554015d2 <+194>: cmp $0x1,%ecx
0x00005555554015d5 <+197>: jg 0x5555554015a9 <phase_6+153>
0x00005555554015d7 <+199>: jmp 0x5555554015b4 <phase_6+164>
0x00005555554015d9 <+201>: mov 0x20(%rsp),%rbx
0x00005555554015de <+206>: mov 0x28(%rsp),%rax
0x00005555554015e3 <+211>: mov %rax,0x8(%rbx)
0x00005555554015e7 <+215>: mov 0x30(%rsp),%rdx
0x00005555554015ec <+220>: mov %rdx,0x8(%rax)
0x00005555554015f0 <+224>: mov 0x38(%rsp),%rax
0x00005555554015f5 <+229>: mov %rax,0x8(%rdx)
0x00005555554015f9 <+233>: mov 0x40(%rsp),%rdx
0x00005555554015fe <+238>: mov %rdx,0x8(%rax)
0x0000555555401602 <+242>: mov 0x48(%rsp),%rax
0x0000555555401607 <+247>: mov %rax,0x8(%rdx)
0x000055555540160b <+251>: movq $0x0,0x8(%rax)
0x0000555555401613 <+259>: mov $0x5,%ebp
0x0000555555401618 <+264>: jmp 0x555555401623 <phase_6+275>
0x000055555540161a <+266>: mov 0x8(%rbx),%rbx
0x000055555540161e <+270>: sub $0x1,%ebp
0x0000555555401621 <+273>: je 0x555555401634 <phase_6+292>
0x0000555555401623 <+275>: mov 0x8(%rbx),%rax
0x0000555555401627 <+279>: mov (%rax),%eax
0x0000555555401629 <+281>: cmp %eax,(%rbx)
0x000055555540162b <+283>: jge 0x55555540161a <phase_6+266>
0x000055555540162d <+285>: callq 0x555555401a90 <explode_bomb>
0x0000555555401632 <+290>: jmp 0x55555540161a <phase_6+266>
0x0000555555401634 <+292>: mov 0x58(%rsp),%rax
0x0000555555401639 <+297>: xor %fs:0x28,%rax
0x0000555555401642 <+306>: jne 0x555555401651 <phase_6+321>
0x0000555555401644 <+308>: add $0x60,%rsp
0x0000555555401648 <+312>: pop %rbx
0x0000555555401649 <+313>: pop %rbp
0x000055555540164a <+314>: pop %r12
0x000055555540164c <+316>: pop %r13
0x000055555540164e <+318>: pop %r14
0x0000555555401650 <+320>: retq
0x0000555555401651 <+321>: callq 0x555555400f20 <__stack_chk_fail@plt>
<+28 ~ 48>
0x000055555540152c <+28>: mov %rsp,%r13
0x000055555540152f <+31>: mov %r13,%rsi
0x0000555555401532 <+34>: callq 0x555555401acc <read_six_numbers>
0x0000555555401537 <+39>: mov %r13,%r12
0x000055555540153a <+42>: mov $0x0,%r14d
0x0000555555401540 <+48>: jmp 0x555555401567 <phase_6+87>
<+28> : %rsp는 입력한 여섯개의 정수 배열을 가리키는 포인터입니다. 이를 %r13에도 저장합니다.
<+31> : %rsi에도 이를 저장하지만, 테스트 결과 %rsi에 저장된 값을 read_six_numbers가 리턴되었을 때에는 이미 소멸됨을 확인했습니다.
<+34> : %r12에도 저장합니다.
<+42> : %r14d에 0을 저장합니다.
<+48> : <+87>로 점프합니다.
<+87 ~ 100>
: %r13, %rbp가 가리키는 숫자가 1 이상 6이하인지 검사
0x0000555555401567 <+87>: mov %r13,%rbp
0x000055555540156a <+90>: mov 0x0(%r13),%eax
0x000055555540156e <+94>: sub $0x1,%eax
0x0000555555401571 <+97>: cmp $0x5,%eax
0x0000555555401574 <+100>: ja 0x555555401542 <phase_6+50>
<+87> : %rbp에도 포인터를 넘겨줍니다.
<+90> : 입력한 첫번째 정수를 %eax에 복사합니다.
<+94> : %eax에서 1을 뺍니다.
<+97 ~ 100> : 5와 비교해서 5보다 크거나 0보다 작으면 <+50>으로 점프합니다. <+50>은 폭탄이므로 입력하는 첫번째 정수는 1 이상 6이하여야 합니다.
0x0000555555401542 <+50>: callq 0x555555401a90 <explode_bomb>
<+102 ~ 115>
: %r14d는 Outer Loop의 현재 인덱스(%r13과, %rbp가 가리키는 인덱스와 같음)
0x0000555555401576 <+102>: add $0x1,%r14d
0x000055555540157a <+106>: cmp $0x6,%r14d
0x000055555540157e <+110>: je 0x555555401585 <phase_6+117>
0x0000555555401580 <+112>: mov %r14d,%ebx
0x0000555555401583 <+115>: jmp 0x555555401551 <phase_6+65>
<+102> : %r14d에 1을 더합니다.
<+106> : %r14d를 6과 비교합니다.
<+110> : 6과 같으면 <+117>로 점프합니다. 여기서는 점프하지 않고 넘어갑니다.
<+112> : %r14d의 값을 %ebx에 복사합니다.
<+115> : <+65>로 점프합니다.
<+65 ~ 76>
: %rbp가 가리키는 숫자(Outer Loop의 현재 원소)와 %eax(Inner Loop의 현재 원소)가 다른지 검사.
0x0000555555401551 <+65>: movslq %ebx,%rax
0x0000555555401554 <+68>: mov (%rsp,%rax,4),%eax
0x0000555555401557 <+71>: cmp %eax,0x0(%rbp)
0x000055555540155a <+74>: jne 0x555555401549 <phase_6+57>
0x000055555540155c <+76>: callq 0x555555401a90 <explode_bomb>
<+65> : %rax에 %ebx값을 복사합니다.
<+68> : %eax에 %rsp에서 현재 저장된 인덱스만큼 이동한 원소의 값을 저장합니다.
<+71> : %eax와 %rbp가 가리키는 값을 비교합니다.
<+74> : <+76>에서 폭탄이 터지므로 %eax와 %rbp가 가리키는 값은 달라야합니다.
<+57 ~ 71>
: %ebx는 Inner Loop의 인덱스
0x0000555555401549 <+57>: add $0x1,%ebx
0x000055555540154c <+60>: cmp $0x5,%ebx
0x000055555540154f <+63>: jg 0x555555401563 <phase_6+83>
0x0000555555401551 <+65>: movslq %ebx,%rax
0x0000555555401554 <+68>: mov (%rsp,%rax,4),%eax
0x0000555555401557 <+71>: cmp %eax,0x0(%rbp)
0x000055555540155a <+74>: jne 0x555555401549 <phase_6+57>
0x000055555540155c <+76>: callq 0x555555401a90 <explode_bomb>
<+57> : %ebx에 1을 더합니다.
<+60 ~ 63> : %ebx에 6이 되었다면 <+83>로 점프합니다.
<+65 ~ 71>을 수행한 후 %ebx가 6이 되기 전까지 반복합니다.
<중간결론1>
2중 Loop 구조입니다.
<+87 ~ 115> 는 Outer Loop입니다. 여기서는 %rbp, %r13을 1부터 6까지 순회합니다.(<+83>에서 오른쪽으로 한칸씩 옮겨감) 그리고 그 인덱스를 %r14d가 소유합니다.
Outer Loop를 순회하면서 각 원소마다 Inner Loop인 <+57 ~ 71>을 순회합니다.
여기서는 Outer Loop의 숫자와 Inner Loop의 숫자가 같으면 폭탄이 터지도록 합니다.
모든 검사가 끝나면 <+110>에서 현재 인덱스가 6인지 검사해서 순회가 끝났으므로 <+117>로 이동해 다음 과정들을 순회하게 됩니다.
즉, 모든 숫자는 1 이상 6 이하여야하고, 그리고 그 숫자들은 모두 달라야합니다.
마지막 순간에는 다음과 같은 형태가 될겁니다.
조건들을 만족했으니 <+117>로 가야합니다.
<+117 ~ 144, +146 ~ 151>
0x0000555555401585 <+117>: lea 0x18(%r12),%rcx
0x000055555540158a <+122>: mov $0x7,%edx
0x000055555540158f <+127>: mov %edx,%eax
0x0000555555401591 <+129>: sub (%r12),%eax
0x0000555555401595 <+133>: mov %eax,(%r12)
0x0000555555401599 <+137>: add $0x4,%r12
0x000055555540159d <+141>: cmp %r12,%rcx
0x00005555554015a0 <+144>: jne 0x55555540158f <phase_6+127>
<+117> : %rcx가 배열의 마지막 원소의 끝 주소를 가지게 합니다. (맨 마지막의 다음 숫자는 0입니다)
<+122 ~ 127> : %eax와 %edx에 7을 복사합니다.
<+129 ~ 133> : %r12가 가리키는 값을 a라고 할 때 7-a를 가지도록합니다.
<+141 ~ 144> : 만약 원소의 끝 주소에 도달하지 않았다면 <+117 ~ 144>를 반복합니다.
결론적으로, 입력한 배열의 숫자를 모두 7에서 뺀 값으로 저장하게합니다.
<+146 ~ 151> : 순회를 마치고, <+146>으로 진입한 후에 %esi에 0을 저장하고 <+179>로 점프합니다.
현재 상황은 밑과 같습니다.
<+179 ~ 197>
<+179>: mov (%rsp,%rsi,4),%ecx
<+182>: mov $0x1,%eax
<+187>: lea 0x202c5e(%rip),%rdx # 0x555555604230 <node1>
<+194>: cmp $0x1,%ecx
<+197>: jg 0x5555554015a9 <phase_6+153>
<+199>: jmp 0x5555554015b4 <phase_6+164>
<+179> : %ecx는 R[%rsi]번째 숫자가 됩니다. 지금은 0번째 인덱스 숫자인 6을 저장합니다.
<+182> : %eax에 1을 저장합니다.
<+187> : 우선은 %rdx가 0x555555604230 주소값이 저장된다고 까지만 생각하고 넘어가겠습니다.
<+194 ~ 197> : R[%ecx]와 1을 비교해서 1 초과라면 <+153>로 넘어가고, 1 이면 <+164>로 넘어갑니다.
<+153 ~ 162>
: %rdx가 node 배열의 인덱스 (R[%ecx] - 1)의 값이 가리키는 대상을 가지게 된다.
0x00005555554015a9 <+153>: mov 0x8(%rdx),%rdx
0x00005555554015ad <+157>: add $0x1,%eax
0x00005555554015b0 <+160>: cmp %ecx,%eax
0x00005555554015b2 <+162>: jne 0x5555554015a9 <phase_6+153>
(gdb) x/20xg 0x555555604230
0x555555604230 <node1>: 0x00000001000003aa 0x0000555555604240
0x555555604240 <node2>: 0x0000000200000206 0x0000555555604250
0x555555604250 <node3>: 0x0000000300000076 0x0000555555604260
0x555555604260 <node4>: 0x000000040000039c 0x0000555555604270
0x555555604270 <node5>: 0x0000000500000277 0x0000555555604110
0x555555604280 <host_table>: 0x0000555555402e47 0x0000555555402e61
0x555555604290 <host_table+16>: 0x0000555555402e7b 0x0000555555402e94
0x5555556042a0 <host_table+32>: 0x0000555555402eae 0x0000555555402ebc
0x5555556042b0 <host_table+48>: 0x0000555555402ecb 0x0000000000000000
0x5555556042c0 <host_table+64>: 0x0000000000000000 0x0000000000000000
<+153> : %rdx의 참조를 8바이트 씩 이동해서 복사하므로 보기편하도록 8바이트 배열 관점에서 관찰했습니다. 그리고 %rdx를 배열에서 한칸 옮긴 값의 참조를 저장합니다. 또한 앞으로 저 배열을 node 배열이라고 하겠습니다.
node 배열에서 한번 이동 : M[0x555555604230 + 0x8] 이 저장된다. 즉 0x555555604240가 저장된다.
0x555555604240 <node2>: 0x0000000200000206 0x0000555555604250
또한, 지금 서로의 주소값을 지니고 있는데 잘 관찰해보면 node배열은 다음과 같은 구조를 가집니다.
&node a는 노드 a의 주소값입니다.
<+157> : %eax에 1을 더합니다.
<+160 ~ 162> : %ecx는 R[%rsi]번째 숫자입니다. 이를 %eax와 비교해서 같을때까지 <+153 ~ 162>를 반복합니다.
%rsi는 0으로 고정입니다. 즉 %ecx는 0번째 숫자입니다.
또한 %eax가 1부터 %ecx가 될때까지 반복하므로, %ecx번 반복한다고 생각할 수 있고.
한번 더 나아가서, %rdx를 node 배열에서 %ecx번 이동를 반복하게 합니다.
예를들어, node1에서 5번 이동하면 %rdx는 node6 주소를 값으로 가집니다.
마지막의 상태는 다음과 같습니다.
<+164 ~ 177>
: 8바이트 단위로 옮겨서 배열이 저장되는 위치에서 0x20 떨어진곳에 %rdx가 가리키는값 저장.
0x00005555554015b4 <+164>: mov %rdx,0x20(%rsp,%rsi,8)
0x00005555554015b9 <+169>: add $0x1,%rsi
0x00005555554015bd <+173>: cmp $0x6,%rsi
0x00005555554015c1 <+177>: je 0x5555554015d9 <phase_6+201>
<+164> : 우리가 입력한 배열이 저장되는 위치에서 0x20 떨어진곳에 8바이트 단위로 옮겨서 %rdx가 node 배열에 가리키는 값을 저장합니다.
<+169 ~ 177> : %rsi가 6이 되었다면 <+201>로 점프하고, 아니라면 <+179>에서 진행합니다.
<중간결론2>
이번에도 2중 Loop 구조입니다.
<+179 ~ 197>가 Outer Loop, <+153 ~ 177> 가 Inner Loop 입니다.
R[%ecx]가 1이면 %rdx가 가리키는 위치를 옮길 필요가 없기때문에 <+153 ~ 162>를 진행하지 않는것 뿐이고,
결론적으로 %rdx가 가리키는 index (R[%ecx] - 1) 값을 우리가 저장한 배열 오른쪽에 저장해놓는겁니다.
R[%rsi] 가 가리키는 인덱스가 현재 진행하는 Outer Loop의 index입니다.
%ecx는 R[%rsi]번째 숫자만큼 이동해서 가지는 값을 0x20 + R[%rsp]부터 8바이트씩 옮겨 복사합니다.
즉, 1 2 3 4 5 6을 입력한 우리의 경우 배열에는 6 5 4 3 2 1( = 7-6, 7-5, 7-4, 7-3, 7-2, 7-1)이 저장되었고
그 오른쪽 주소에 %rdx가 가리키는 node배열의 node 6, node 5, node 4, node 3, node 2, node 1 의 값들이 차례대로 저장되는겁니다.
사진을 보면 다음과 같게 됩니다.
(배열의 주소는 보기 편하도록 한것이지, 실제 주소가 아닙니다)
즉, R[%rsi]가 6이 되어 Outer Loop까지 종료되었을때,
<+201> 부터 코드가 진행됩니다.
<+201 ~ 206>
0x00005555554015d9 <+201>: mov 0x20(%rsp),%rbx
0x00005555554015de <+206>: mov 0x28(%rsp),%rax
0x00005555554015e3 <+211>: mov %rax,0x8(%rbx)
0x00005555554015e7 <+215>: mov 0x30(%rsp),%rdx
0x00005555554015ec <+220>: mov %rdx,0x8(%rax)
0x00005555554015f0 <+224>: mov 0x38(%rsp),%rax
0x00005555554015f5 <+229>: mov %rax,0x8(%rdx)
0x00005555554015f9 <+233>: mov 0x40(%rsp),%rdx
0x00005555554015fe <+238>: mov %rdx,0x8(%rax)
0x0000555555401602 <+242>: mov 0x48(%rsp),%rax
0x0000555555401607 <+247>: mov %rax,0x8(%rdx)
0x000055555540160b <+251>: movq $0x0,0x8(%rax)
0x0000555555401613 <+259>: mov $0x5,%ebp
0x0000555555401618 <+264>: jmp 0x555555401623 <phase_6+275>
결론적으로 보면 기존에 오른쪽같이 연결되어있던 노드들을
위에서 0x20(%rsp)에 저장되어 있는 순서대로 재배치하는 코드입니다.
여기서부터는 6 3 1 2 5 4 을 입력했다고 가정하고 진행해보겠습니다.
6 3 1 2 5 4을 입력한 숫자들은 1 4 6 5 2 3 으로 변환되고, 0x20(%rsp)에 node1, node4, node6, node5, node2, node3의 주소가 배치되게 됩니다.
그리고 <+201 ~ 206> 을 진행하고 나면, 노드사이 관계가 오른쪽과 같이 변경됩니다.
변경이 완료되면 <+275>로 점프합니다.
<+266 ~ 285>
0x000055555540161a <+266>: mov 0x8(%rbx),%rbx
0x000055555540161e <+270>: sub $0x1,%ebp
0x0000555555401621 <+273>: je 0x555555401634 <phase_6+292>
0x0000555555401623 <+275>: mov 0x8(%rbx),%rax
0x0000555555401627 <+279>: mov (%rax),%eax
0x0000555555401629 <+281>: cmp %eax,(%rbx)
0x000055555540162b <+283>: jge 0x55555540161a <phase_6+266>
0x000055555540162d <+285>: callq 0x555555401a90 <explode_bomb>
<+275> : %rax는 %rbx의 다음 노드 주소를 가집니다. 현재 %rbx는 첫번째 노드 주소이므로, %rax는 두번째 노드 주소입니다.
즉 R[%rbx]는 &node1, R[%rax]는 &node4 입니다.
<+279> : %eax는 두번째 노드인데, 그 앞의 8바이트만 가져오므로 v4를 저장합니다.
<+281 ~ 283> : 두번째 노드의 값인 v4와 첫번째 노드 값인 v1을 비교합니다. 그리고 v1 >= v4를 만족해야합니다. 그리고 <+266>으로 점프합니다, 아니라면 폭탄이 터집니다.
<+266> : %rbx를 다음 노드로 옮깁니다.
<+270 ~ 273> : %ebp를 1 감소합니다. 만약 %ebp가 0보다 작다면 <+292>로 이동합니다.
이를 반복하는데, 결국 %ebp가 5에서 1까지 반복하므로 총 5번 반복합니다. 그리고 %rbx를 다음 노드로 옮기면서 %eax는 %rbx의 다음 노드가 됩니다. 또한 그때마다 앞에놓인 노드의 v값이 뒤에놓인 노드의 v값보다 크거나 같아야합니다.
즉 v값을 기준으로 내림차순을 만족해야합니다.
<결론>
각 노드의 v값을 나열해보면 다음과 같습니다.
(gdb) x/30dw 0x555555604230
0x555555604230 <node1>: 938 1 1432371776 21845
0x555555604240 <node2>: 518 2 1432371792 21845
0x555555604250 <node3>: 118 3 1432371808 21845
0x555555604260 <node4>: 924 4 1432371824 21845
0x555555604270 <node5>: 631 5 1432371472 21845
(gdb) x/1dw 0x0000555555604110
0x555555604110 <node6>: 891
node |
v node |
1 |
938 |
2 |
518 |
3 |
118 |
4 |
924 |
5 |
631 |
6 |
891 |
노드가 큰 순서대로 나열하면
1 → 4 → 6 → 5 →2 →3
입니다.
그리고 이 노드들은 7에서 입력한 숫자를 뺀 값이므로,
6 3 1 2 5 4
로 변환됩니다.
즉 답은 다음과 같습니다.
6 3 1 2 5 4