JSCC: JavaScript로 개발하는 C Compiler

코스 전체목록

닫기

뼈대 프로그램(stub program)

4.1) 뼈대 프로그램(stub program)

그럼 이제 본격적으로 NASM을 이용하여 어셈블리 프로그래밍을 해보자포스트에 적힌 방법대로 프로젝트를 생성하고 파일을 처음으로 만들면 다음과 같이 당황스러운 코드를 만나게 된다.

HelloWorld.asm

%include 'handy/handy.inc'

 

segment .data

sHelloWorld db 'Hello, world!', 10, 0

 

segment .text

global _main

 

 

_main:

push ebp

mov ebp, esp

 

push sHelloWorld

call print_string

add esp, 4

 

mov eax, 0

mov esp, ebp

pop ebp

ret

실행 결과

Hello, world!

그런데 이게 정말 당황스러운 코드일까사실 우리는 이와 비슷한 코드를 이미 본 적이 있다이전 문서의 마지막 예제를 다시 가져오겠다.

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

naked_proc의 정의와 _main 레이블 이하의 코드를 유심히 쳐다보면두 코드가 거의 비슷하다는 사실을 알 수 있다주석을 제거하고 둘을 하나로 합쳐서 보자.

코드 비교 표

%include 'handy/handy.inc'

 

segment .data

sHelloWorld db 'Hello, world!', 10, 0

 

segment .text

global _main

 

_main:

push ebp

mov ebp, esp

 

push sHelloWorld

call print_string

add esp, 4

 

mov eax, 0

mov esp, ebp

pop ebp

ret

#include "CIL.h"

 

 

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

 

 

 

 

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

코드가 각각의 줄에 거의 대응하도록 변경하고 살펴보니몇몇 차이를 제외하면 두 코드가 아주 비슷함을 알 수 있다우리는 CIL을 배웠고, CIL은 어셈블리를 보다 쉽게 익힐 수 있도록 고안된 언어다, CIL을 이해하고 있는 우리는 어셈블리도 어렵지 않게 익힐 수 있다그럼 이 뼈대 프로그램을 먼저 분석하는 것으로 어셈블리 언어의 문법에 대한 감을 잡아보자.

- %include 'handy/handy.inc'

첫 줄은 handy 폴더에 있는 handy.inc 파일을 포함하는 전처리기 지시어(preprocessor directive)지시어(directive)란 소스 코드 중 실제 기계어로 변환 가능한 명령어가 아니라 소스 코드를 변환하는 프로그램에 전달하는 메시지를 말하며이 경우 handy.inc 파일을 포함하는 작업을 전처리기가 수행하기 때문에 %include는 전처리기에 대한 지시어가 된다.

- segment .data

3.1절에서 프로세스의 메모리를 크게 네 단계로 나눌 수 있다고 했다. segment는 소스 코드에 영역 별로 메모리를 정의하고 싶을 때 사용하는 어셈블러 지시어이고, segment 다음에 영역을 넘겨서 해당 영역에 메모리를 정의할 수 있도록 한다따라서 이는 이 지시어 다음에 나오는 모든 소스는 데이터 세그먼트를 정의하는 데 사용됨을 나타낸다.

- sHelloWorld db 'Hello, world!', 10, 0

문자열을 정의한다. db는 byte 형식의 데이터를 의미한다(후에 자세히 다룬다). 문자열 뒤에 정수 10이 들어가 있는 이유가 궁금할 텐데바로 10은 개행 문자의 ASCII 코드 값이기 때문이다이 문장은 파고들면 설명할 것이 아주 많이 나오지만지금 설명하면 독자가 혼란스럽게 느낄 수 있는 만큼 후에 자세히 다루겠다일단은sHelloWorld는 byte의 배열로 정의된 레이블(label)이고개행 문자의 ASCII 코드 값이 10이기 때문에 개행 문자를 표시하기 위해 10을 넣었다는 점널 문자(\0)로 문자열을 끝내기 위해 0을 마지막에 넣었다는 점만 기억하고 있으면 된다.

- segment .text

segment에 대해서는 설명했다. ‘.text’는 해당 지시어 다음에 등장하는 모든 소스가 코드 세그먼트에 대한 것이라고 어셈블러에게 전달하는 어셈블러 지시어다.

- global _main

_main 레이블이 전역에 선언된 레이블임을 의미하는 어셈블러 지시어다기본적으로 어셈블리 언어의 레이블은 모두 내부 선언되어있다(이는 언어에서 함수의 원형을 선언하면 기본적으로 전역에 선언된다는 점과 대조되는 중요한 특성이다). 따라서 이 지시어가 없이 레이블을 정의만 한 상태라면 다른 파일에서 이 레이블에 접근하는 것이 불가능하다즉 global은 다른 파일에서 레이블에 접근할 수 있도록 만들어준다.

- _main:

_main 프로시저를 정의한다어셈블리에서는 프로시저의 정의와 레이블의 정의가 서로 같은데 이에 대해서는 후에 다루겠다.

이와 같이 두 코드는 내부 구조가 완전히 동일하다이 프로그램을 이용하여 자신이 원하는 다른 문장을 화면에 출력하는 코드를 작성할 수 있다면 뼈대 프로그램을 사용하는 방법은 완전히 이해한 것이나 다름없다.

댓글

댓글 본문
graphittie 자세히 보기