사용 개념: 런타임 스택과 PUSH·POP, 프로시저와 CALL·RET.
“이 코드가 끝났을 때 스택과 레지스터는?”이라는 문제는 같은 하위목표의 되풀이다. ① 출발점: 처음 ESP를 적는다 ② ESP 따라가기: push와 call은 4를 빼고, pop과 ret은 4를 더한다 ③ 칸 채우기: 4를 뺀 자리에 무엇이 들어갔는지 적는다(call이면 다음 명령의 주소) ④ 꺼낸 값 보내기: pop은 맨 위 값을 레지스터로, ret은 맨 위 값을 EIP로 보낸다 ⑤ 마무리: 끝났을 때의 레지스터와 ESP를 적는다1.
네 문제 모두 ESP는 00001000h에서 시작한다. 줄 앞의 여덟 자리 수는 그 명령의 주소다. 모든 답은 파이썬 흉내로 확인했다. 실제 MASM으로 돌린 것이 아니다.
문제 1 · 완전한 풀이
mov eax,11h
mov ebx,22h
push eax
push ebx
pop eax
pop ebx
- 출발점: ESP = 00001000h.
- ESP 따라가기와 칸 채우기:
| 실행한 줄 | ESP | 쓰거나 읽은 칸 |
|---|---|---|
push eax |
00000FFCh | 00000FFCh ← 00000011h |
push ebx |
00000FF8h | 00000FF8h ← 00000022h |
pop eax |
00000FFCh | 00000FF8h의 22h를 읽는다 |
pop ebx |
00001000h | 00000FFCh의 11h를 읽는다 |
- 꺼낸 값 보내기: 맨 위에 있던 22h가 EAX로, 그다음 11h가 EBX로 간다.
- 마무리: EAX = 22h, EBX = 11h, ESP = 00001000h. 올린 순서와 같은 순서로 내려서 두 값이 서로 바뀌었다.
문제 2 · 마지막 하위목표만 빈칸
main PROC
00000010 mov eax,5
00000015 call Twice
0000001A mov ebx,eax
exit
main ENDP
Twice PROC
00000030 add eax,eax
00000032 ret
Twice ENDP
- 출발점: ESP = 00001000h.
- ESP 따라가기와 칸 채우기:
call Twice가 ESP를 00000FFCh로 내리고 그 칸에 다음 명령의 주소 0000001Ah를 넣는다. EIP는 00000030h가 된다. - 꺼낸 값 보내기:
add eax,eax로 EAX = 0Ah.ret이 00000FFCh의 0000001Ah를 EIP에 넣고 ESP를 00001000h로 올린다. - 마무리:
mov ebx,eax까지 실행한 뒤 EBX = __, ESP = __.
답
EBX = 0000000Ah(10), ESP = 00001000h.
ret이 돌아온 곳이 call 다음 줄이라 mov ebx,eax가 실행된다. 올린 것(복귀 주소)을 ret이 내렸으니 ESP는 처음 값이다.
문제 3 · 하위목표 절반이 빈칸
main PROC
00000000 mov ecx,3
00000005 call Outer
0000000A exit
main ENDP
Outer PROC
00000020 push ecx
00000021 mov ecx,7
00000026 call Inner
0000002B pop ecx
0000002C ret
Outer ENDP
Inner PROC
00000040 mov edx,ecx
00000042 ret
Inner ENDP
- 출발점: ESP = 00001000h.
- ESP 따라가기와 칸 채우기:
Inner의mov edx,ecx를 실행하는 순간의 스택을 채우라.
00000FFC [ ______ ]
00000FF8 [ ______ ]
00000FF4 [ ______ ]←ESP
- 꺼낸 값 보내기:
Inner의ret은 0000002Bh로 돌아간다.pop ecx가 __ 를 ECX에 넣는다.Outer의ret은 __ 로 돌아간다. - 마무리: 끝났을 때 ECX = 3, EDX = __, ESP = 00001000h.
답
- 00000FFCh에 0000000Ah(
main으로 돌아갈 주소), 00000FF8h에 00000003h(올려 둔 ECX), 00000FF4h에 0000002Bh(Outer로 돌아갈 주소). pop ecx는 00000003h를 넣는다.Outer의ret은 0000000Ah로 돌아간다.- EDX = 7.
Inner가 ECX를 읽은 때는Outer가 7로 바꾼 뒤다.
복귀 주소 둘 사이에 올려 둔 레지스터가 끼어 있다.Outer가pop ecx를 해야 맨 위가 자기 복귀 주소가 된다.
문제 4 · 독립 문제
main PROC
00000000 mov eax,1
00000005 call Bad
0000000A exit
main ENDP
Bad PROC
00000050 push eax
00000051 mov eax,0Ah
00000056 ret
Bad ENDP
Bad의 ret을 실행한 직후의 EIP와 ESP를 구하라. 프로그램이 0000000Ah로 돌아오게 하려면 무엇을 고쳐야 하는가? 고친 뒤 main으로 돌아왔을 때 EAX는?
답
call이 ESP를 00000FFCh로 내리고 0000000Ah를 넣는다. push eax가 ESP를 00000FF8h로 내리고 00000001h를 넣는다. ret은 맨 위의 00000001h를 꺼내므로 EIP = 00000001h, ESP = 00000FFCh다. 복귀 주소 0000000Ah는 스택에 남는다.
고치는 법: ret 앞에 pop eax를 넣어 올린 만큼 내린다. 그러면 ret이 0000000Ah를 꺼낸다. 돌아왔을 때 EAX는 1이다. mov eax,0Ah로 넣은 값은 pop eax가 옛 값으로 덮어쓴다.
EAX = 0Ah를 돌려주려던 것이라면 push eax와 pop eax를 둘 다 지워야 한다. 돌려줄 레지스터는 치워 뒀다 되돌리면 안 된다(레지스터 매개변수와 USES).
-
줄 앞의 주소는 문제에서 정해 준 값이고 실제 기계어 길이와는 상관없다. 답은 문제 코드의 파이썬 흉내로 확인했다. ↩