세 가지 함수를 분석하려고 합니다.
phase_1
strings_no_equal
string_length
string_length
Dump of assembler code for function string_length:
0x000000000000176f <+0>: cmpb $0x0,(%rdi)
0x0000000000001772 <+3>: je 0x1786 <string_length+23>
0x0000000000001774 <+5>: mov %rdi,%rdx
0x0000000000001777 <+8>: add $0x1,%rdx
0x000000000000177b <+12>: mov %edx,%eax
0x000000000000177d <+14>: sub %edi,%eax
0x000000000000177f <+16>: cmpb $0x0,(%rdx)
0x0000000000001782 <+19>: jne 0x1777 <string_length+8>
0x0000000000001784 <+21>: repz retq
0x0000000000001786 <+23>: mov $0x0,%eax
0x000000000000178b <+28>: retq

string_length는 문자열 길이를 반환하는 함수라고 과감히 예상할 수 있습니다.
그리고 바로 %rdi를 사용하는것으로 보아, %rdi에 문자열이 전달되었다고 생각할 수 있습니다.
전달될 때는 첫 element의 주소가 저장됩니다.
<+0 ~ 3>
(%rdi) - 0x0을 계산하고 결과값이 0과 같으면 23번 줄로 점프합니다.
다시말하면, (%rdi)는 현재 배열에서 첫번째 value고 이 value가 '\0'임을 체크합니다.
만약 '\0'이면 <+23>으로 점프해서 eax에 0을 저장하고 리턴하고, 아니라면 다음으로 진행합니다.
0x000000000000176f <+0>: cmpb $0x0,(%rdi)
0x0000000000001772 <+3>: je 0x1786 <string_length+23>
0x0000000000001786 <+23>: mov $0x0,%eax
0x000000000000178b <+28>: retq

<+5 ~ 14>
%rdx에 %rdi값을 저장했습니다. 즉 %rdx도 첫번째 원소를 가리키는 포인터값을 지니게 됩니다.
그 후 1을 더하므로 다음 원소를 가리키게 되고, %eax가 %rdx의 포인터값의 일부를 가지도록 합니다.
그 후 %eax에서 %edi를 빼므로 %rdx와 %rdi의 인덱스 차이를 가지게 됩니다.
0x0000000000001774 <+5>: mov %rdi,%rdx
0x0000000000001777 <+8>: add $0x1,%rdx
0x000000000000177b <+12>: mov %edx,%eax
0x000000000000177d <+14>: sub %edi,%eax
<+16 ~ 19>
%rdx가 가리키는 주소값의 내용, 즉 두번째 원소가 null인지 비교합니다. (아까 했던 과정임)
'\0'이 아니라면 <+8>로 점프합니다.
0x000000000000177f <+16>: cmpb $0x0,(%rdx)
0x0000000000001782 <+19>: jne 0x1777 <string_length+8>

결론
여기서부터 %rdx를 1더하고
%eax는 %rdx에서 %rdi의 간격차를 계산한 값을 가지게 되는데
이를 %rdx가 '\0'이 될때까지 반복하므로 %eax가 결국에는 %rdi에 전달받은 문자열이 길이를 가지게 됨을 알 수 있습니다.
string_no_equal
Dump of assembler code for function strings_not_equal:
0x000000000000178c <+0>: push %r12
0x000000000000178e <+2>: push %rbp
0x000000000000178f <+3>: push %rbx
0x0000000000001790 <+4>: mov %rdi,%rbx
0x0000000000001793 <+7>: mov %rsi,%rbp
0x0000000000001796 <+10>: callq 0x176f <string_length>
0x000000000000179b <+15>: mov %eax,%r12d
0x000000000000179e <+18>: mov %rbp,%rdi
0x00000000000017a1 <+21>: callq 0x176f <string_length>
0x00000000000017a6 <+26>: mov $0x1,%edx
0x00000000000017ab <+31>: cmp %eax,%r12d
0x00000000000017ae <+34>: je 0x17b7 <strings_not_equal+43>
0x00000000000017b0 <+36>: mov %edx,%eax
0x00000000000017b2 <+38>: pop %rbx
0x00000000000017b3 <+39>: pop %rbp
0x00000000000017b4 <+40>: pop %r12
0x00000000000017b6 <+42>: retq
0x00000000000017b7 <+43>: movzbl (%rbx),%eax
0x00000000000017ba <+46>: test %al,%al
0x00000000000017bc <+48>: je 0x17e5 <strings_not_equal+89>
0x00000000000017be <+50>: cmp 0x0(%rbp),%al
0x00000000000017c1 <+53>: jne 0x17ec <strings_not_equal+96>
0x00000000000017c3 <+55>: add $0x1,%rbx
0x00000000000017c7 <+59>: add $0x1,%rbp
0x00000000000017cb <+63>: movzbl (%rbx),%eax
0x00000000000017ce <+66>: test %al,%al
0x00000000000017d0 <+68>: je 0x17de <strings_not_equal+82>
0x00000000000017d2 <+70>: cmp %al,0x0(%rbp)
0x00000000000017d5 <+73>: je 0x17c3 <strings_not_equal+55>
0x00000000000017d7 <+75>: mov $0x1,%edx
0x00000000000017dc <+80>: jmp 0x17b0 <strings_not_equal+36>
0x00000000000017de <+82>: mov $0x0,%edx
0x00000000000017e3 <+87>: jmp 0x17b0 <strings_not_equal+36>
0x00000000000017e5 <+89>: mov $0x0,%edx
0x00000000000017ea <+94>: jmp 0x17b0 <strings_not_equal+36>
0x00000000000017ec <+96>: mov $0x1,%edx
0x00000000000017f1 <+101>: jmp 0x17b0 <strings_not_equal+36>
string_no_eqaul, 문자열 두 개 받아서 서로 다른지 비교하는 기능을 가졌을 것으로 보입니다.

<+4 ~ 7>
인자 전달로 자주쓰이는 %rdi, %rsi를 %rbx, %rbp로 전달했습니다.
이는 callee-saved register인 레지스터에 저장해서 caller-saved register인 %rdi, %rsi가 string_no_eqaul 함수에서 변하지 않도록 하기위함을 알 수 있습니다.
이제 %rbx, %rbp가 각각 문자열의 첫 원소를 가리키는 포인터를 가집니다.
<+10 ~ 31>
위의 분석해서 알 수 있듯이 string_length 함수는 %rdi를 인자로 전달받는 함수입니다.
즉, 위 문자열에서 "abcdefg" 문자열이 전달되고 %eax에 7이 반환되고 이를 %r12d에 저장합니다.
그리고 <+18> 에서 %rbp의 값을 %rdi에 전달합니다. 이는 아래 문자열을 string_length의 인자로 전달하기 위함임을 알 수 있습니다.
함수리 리턴되면 %eax에 7이 전달될겁니다.
<+26>
%edx 에 1을 저장하고 있습니다.( 어떤 역할인지는 밑에서 설명합니다 )

<+31 ~ 34>
%r12d과 %eax 둘이 같은지 비교합니다.
다시말하면, 첫번째 문자열의 길이와 두번째 문자열의 길이를 비교합니다.
- 만약 같다면 <+43>으로 점프합니다.
- 만약 다르면, 두 문자열이 서로 다름이 판정되었으므로 점프하지 않고 <+36 ~ 42>에서 %edx를 %eax에 저장해 반환함으로써 결과적으로 1을 반환합니다.
-> 즉 %edx는 문자열이 다른지 같은지를 알려주는 반환값의 역할을 하는 레지스터입니다.
<+43 ~ 48>
여기부터는 두 문자열의 길이가 같음을 보장한 후 진행하게됩니다. (다른경우 이미 리턴됐으므로)
(%rbx)는 첫번째 문자열에서 가리키고 있는 값입니다. 이 경우에는 a입니다. 그리고 a를 %eax에 저장합니다.
그리고 저장한 %eax는 1바이트 값이므로 %al이 '\0'인지 아닌지 체크합니다.
- '\0' 인경우 : 만약 '\0'이라면 두 문자열이 모두 비었음을 의미하고, 길이가 같으므로 두 문자열이 같습니다. 후에 <+89>로 이동해서 %edx를 0으로 만들어 <+36>으로 이동해 0을 반환하게 됩니다.
- '\0' 이 아닌경우 : 두 문자열이 모두 빈 문자열이 아님을 알 수 있습니다. 이제부터는 각 문자를 순서대로 하나씩 비교해봐야합니다.
우선 <+50 ~ 53>에서 첫번째 원소를 비교합니다.
0x00000000000017be <+50>: cmp 0x0(%rbp),%al
0x00000000000017c1 <+53>: jne 0x17ec <strings_not_equal+96>
%al은 첫번째 문자열의 첫번째값을 지니고, 0x0(%rbp)는 두번째 문자열의 첫번째 값을 가집니다.
<53>에서 두 문자열이 다르면 <+96> 으로 이동해서
0x00000000000017ec <+96>: mov $0x1,%edx
0x00000000000017f1 <+101>: jmp 0x17b0 <strings_not_equal+36>
%edx에 1(두 문자열이 다름)을 저장하고 <+36>으로 점프해서 1을 반환합니다.

<+55 ~ 73>
%rbx, %rbp에 각각 1을 더해서 다음 원소를 가리키도록합니다.
다시 %eax에는 첫번째 문자열에서 %rbx가 가리키는 값을 가지도록하므로,
앞으로 나오는 %al은 첫번째 문자열의 원소입니다. (%al은 %eax의 일부입니다)
그리고 오른쪽의 상태를 가집니다.
이제 <+66>에서 %al이 '\0'인지 아닌지 체크합니다
'\0'인경우 : 두 문자열이 길이가 같지만, 가리키는 첫번째 문자열의 원소가 '\0'이므로 가리키는 두번째 문자열의 원소 또한 '\0'입니다. 즉 두 문자열이 같습니다. 때문에 <+82>로 넘어가서 0을 리턴합니다.
'\0'이 아닌경우 : 가리키는 두 원소가 같은지 체크해야합니다. 만약 다르다면 두 문자열이 다르므로 <+75>로 가서 1을 리턴합니다.
0x00000000000017d7 <+75>: mov $0x1,%edx
0x00000000000017dc <+80>: jmp 0x17b0 <strings_not_equal+36>
만약 같다면 <+55>로 돌아가서 <+55 ~ 73> 과정을 반복해서 문자열의 같은지 여부를 %rax에 리턴하게 됩니다.
<+55>: add $0x1,%rbx
<+59>: add $0x1,%rbp
<+63>: movzbl (%rbx),%eax
<+66>: test %al,%al
<+68>: je 0x17de <strings_not_equal+82>
<+70>: cmp %al,0x0(%rbp)
<+73>: je 0x17c3 <strings_not_equal+55>
두 문자열이 같은경우에 마지막 순간은 아래와 같은 상태가 됩니다.

phase_1
Dump of assembler code for function phase_1:
=> 0x00005555554012e4 <+0>: sub $0x8,%rsp
0x00005555554012e8 <+4>: lea 0x17e1(%rip),%rsi # 0x555555402ad0
0x00005555554012ef <+11>: callq 0x55555540178c <strings_not_equal>
0x00005555554012f4 <+16>: test %eax,%eax
0x00005555554012f6 <+18>: jne 0x5555554012fd <phase_1+25>
0x00005555554012f8 <+20>: add $0x8,%rsp
0x00005555554012fc <+24>: retq
0x00005555554012fd <+25>: callq 0x555555401a90 <explode_bomb>
0x0000555555401302 <+30>: jmp 0x5555554012f8 <phase_1+20>
위에서 관찰했듯이 strings_not_eqaul 함수는 %rdi, %rsi를 매개변수로 가집니다.
<+4 ~ 18>
<+4>를 보면 0x17e1(%rip)를 %rsi에 저장하고 있습니다.
<+11>에서 strings_not_eqaul을 호출해서 %eax에 두 문자열이 같은지 다른지를 판별한 값이 들어갑니다.
<+16 ~ 18>에서 %eax가 1이면 <+25>로 가서 폭탄이 터지므로 두 문자열이 같도록해서 <+24>에서 정상적으로 phase_1이 리턴되도록 해야합니다.
즉, strings_not_eqaul의 %rdi, %rsi 중 하나는 입력한 값이고, 하나는 답이므로 <+11>전까지 진행해서 값을 꺼내보면 됩니다.
abcd를 입력해서 값을 꺼내보면,

%rdi에 입력한 값이 들어갔으므로, %rsi에 답이 있다고 추론할 수 있습니다.
즉, 답은 다음과 같습니다.
When I get angry, Mr. Bigglesworth gets upset.
'CS:APP' 카테고리의 다른 글
[CS:APP] BombLab Phase6 해설 (0) | 2023.10.20 |
---|---|
[CS:APP] BombLab Phase5 해설 (0) | 2023.10.20 |
[CS:APP] BombLab Phase4 해설 (0) | 2023.10.20 |
[CS:APP] BombLab Phase3 해설 (0) | 2023.10.20 |
[CS:APP] BombLab Phase2 해설 (0) | 2023.10.19 |