JSCC: JavaScript로 개발하는 C Compiler

코스 전체목록

닫기

CALL 명령과 RET 명령을 이용한 프로시저 호출과 복귀

3.10) CALL 명령과 RET 명령을 이용한 프로시저 호출과 복귀

지금까지는 프로시저를 호출할 때 INVOKE 매크로를프로시저를 호출한 곳으로 복귀할 때는 RETURN 매크로를 사용했다그렇게 강조해서 표현하지 않았지만사실 이 둘은 명령이 아니다지금까지 프로시저 호출에 사용한INVOKE는 CALL 명령과 POP 명령의 조합으로 이루어져있는 매크로다복귀에 사용하는 RETURN은 프로시저 종료 시에 자동으로 메모리를 정리하도록 구현되어있다우리가 앞으로 구현할 컴파일러는 CALL 명령을 이용하는데그 이유는 INVOKE 매크로가 CALL 명령을 이용하여 구현되었기 때문이다여기서는 CALL과 RET 명령을 이용하여 프로시저를 다뤄보자이 내용은 중요하기 때문에 잘 이해하고 있어야 한다.

다음은 프로시저를 CALL 명령과 RET 명령을 이용하여 사용하는 코드다.

ProcNaked.c

#include "CIL.h"

STRING sHelloWorld = "Hello, world!\n";

 

PROC(main)

 

// naked_proc 프로시저를 호출합니다.

CALL(naked_proc)

 

ENDP

 

// NAKED 프로시저를 정의합니다.

PROC_NAKED(naked_proc)

PUSH(bp) // 이전 스택 시작 주소를 푸시하여 보관합니다.

MOVL(bp, sp) // 스택 시작 주소를 현재 스택 포인터로 맞춥니다.

 

PUSH(sHelloWorld)

INVOKE(print_str)

ADD(sp, 4)

 

MOVL(sp, bp) // 현재 스택 포인터를 스택 시작 주소로 맞춥니다.

POP(bp) // 보관했던 이전 스택 시작 주소를 불러옵니다.

RET() // 복귀 지점으로 돌아갑니다.

ENDP_NAKED

여기서는 PROC_NAKED 키워드를 사용하여 프로시저를 정의하고 있다이를 자세히 들여다보자.

main 프로시저가 호출된 직후 메모리 상태는 다음과 같다.

ip는 이후에 실행할 명령의 위치를 기록하는 숨겨진 기본 변수다값이 main_1인 건 main 프로시저에서 다음에 수행할 명령이 main_1이라는 뜻이다.

이제 CALL 명령을 이용해 naked_proc 프로시저를 호출하면현재 명령의 다음 위치가 스택에 푸시 되고naked_proc 프로시저로 진입한다.

main_2가 푸시 된 건, main_1은 CALL 명령이었으니 그 다음에 수행할 명령을 푸시해야 하기 때문이다이후 bp를 스택에 푸시 하는데주석에도 쓰여 있지만 이는 이전 프로시저의 스택 시작 지점을 보관하기 위함이다.

이제 bp에 현재 스택 포인터 sp의 값을 대입하여 스택 시작 주소를 갱신한다.

바로 여기까지가 INVOKE 매크로가 수행하는 과정이다. INVOKE 매크로는 내부적으로 스택을 알아서 정리하는 기능을 가지고 있기 때문에 실제로 사용하기엔 더 편하지만만약 당신이 프로그램을 디버깅 할 일이 생겨서 디스어셈블리 파일을 분석하게 된다면 모든 프로시저의 시작은 이 형태로 나타난다매크로의 내부가 그렇게 어렵지 않고앞으로 진행할 때도 INVOKE가 아닌 CALL을 기준으로 구현할 것이다따라서 반드시 이 형태에 익숙해져야 한다.

이제 프로시저의 호출을 보았으니이전 프로시저로 복귀하는 모습을 볼 차례다. print_str 프로시저 호출에 대해서는 이제 모두 알고 있으리라 생각하므로, ADD 명령이 수행되었을 때의 메모리 상태만 보이겠다.

사실 위에 보인 그림과 다르지 않다다음은 이전 프로시저에 복귀하기 위해 sp를 현재 스택 메모리의 시작으로 옮기는 과정이다.

바뀐 것이 없는데왜냐하면 이 프로시저에서는 지역 변수가 선언되지 않았기 때문이다. sp를 bp로 맞춤으로써 해당 지역 변수를 모두 사용하지 않는 상태로 만드는 것인데지역 변수가 없으므로 상태가 변하지 않는다이는 다른 프로시저를 직접 그려가면서 학습하면 납득할 수 있다.

이제 POP 연산을 통해 bp에 이전 스택 시작 주소를 복원한다.

POP 연산이 sp도 움직이고 있음에 주목해야 한다이제 마지막으로 RET 명령을 이용하여 이전 프로시저로 복귀한다.

이로써 프로시저의 호출과 복귀가 모두 끝난다.

댓글

댓글 본문
graphittie 자세히 보기