요약

어셈블리어 프로그램은 매번 똑같은 뼈대 위에 쓴다. 이력서 양식처럼 칸이 미리 나뉘어 있어서, 변수는 변수 칸에, 실행할 코드는 코드 칸에 채워 넣으면 된다. 뼈대를 한 번 익히면 새 프로그램마다 바뀌는 곳은 두 칸뿐이다. 다만 뼈대의 줄들은 순서와 짝이 맞아야 하고, 끝내는 줄을 빼먹으면 프로그램이 제대로 끝나지 않는다.

예시로 보기

실제로 하고 싶은 일은 “5에 6을 더한다” 두 줄이다. 그런데 어셈블러는 이 두 줄만으로는 프로그램을 만들지 못한다. 어떤 프로세서용인지, 코드가 어디서 시작하는지, 끝나면 무엇을 할지를 알려 주어야 한다. 가장 작은 완성 프로그램은 이렇다.

; AddTwo.asm – adds two 32-bit integers

.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.code
main PROC
    mov  eax,5        ; move 5 to the EAX register
    add  eax,6        ; add 6 to the EAX register

    INVOKE ExitProcess,0
main ENDP
END main

가운데 mov와 add 두 줄만 프로세서가 실행하는 계산이다. 나머지는 뼈대다. 두 줄을 실행하면 EAX는 5 + 6 = 11, 16진수로 0000000Bh가 된다.

검증: AddTwo의 EAX = 0000000Bh, AddSub2의 EAX = 00030000h와 finalVal, 두 프로그램이 끝날 때의 플래그, 디버거 화면의 EAX=00030000·EFL=00000206이 AddSub2의 결과와 맞고 AddTwo의 결과와는 맞지 않는 것, 카드의 값 — 10_asm-program-template_verify.py. 파이썬 흉내로 확인했다.

틀

새 프로그램을 쓸 때 베껴 쓰는 뼈대다.

; Program Template           (Template.asm)

; Program Description:
; Author:
; Creation Date:
; Revisions:
; Date:              Modified by:

.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
    ; declare variables here
.code
main PROC
    ; write your code here
    INVOKE ExitProcess,0
main ENDP
; (insert additional procedures here)
END main

줄마다 하는 일은 다음과 같다1.

줄 하는 일
.386 32비트 프로세서용 프로그램이라고 알린다. 32비트 레지스터와 주소를 쓸 수 있게 된다
.model flat,stdcall 메모리를 쓰는 방식(flat: 구역을 나누지 않은 하나의 주소 공간)과 프로시저를 부르는 약속(stdcall)을 고른다
.stack 4096 스택으로 쓸 메모리를 4,096바이트 잡는다
ExitProcess PROTO, dwExitCode:DWORD 운영체제의 ExitProcess라는 프로시저를 쓰겠다고 미리 알린다. 32비트 인수 하나를 받는다
.data 여기부터 변수를 둔다
.code 여기부터 코드를 둔다
main PROC … main ENDP main이라는 프로시저의 시작과 끝
INVOKE ExitProcess,0 운영체제에 “끝났다”고 알리고 프로그램을 끝낸다. 0은 정상 종료라는 뜻으로 넘기는 값이다
END main 원본의 마지막 줄이다. 프로그램이 main에서 시작한다고도 알린다

표의 줄은 모두 어셈블러에게 하는 지시(지시어)이고 프로세서가 실행하는 명령이 아니다. INVOKE만은 어셈블러가 그 자리에 프로시저를 부르는 명령어를 써넣는다: 지시어와 명령어 비교. 프로그램은 코드, 데이터, 스택 세 구역(세그먼트)으로 이루어진다.

변수를 쓰는 프로그램

.data 칸에 변수를 두면 이렇게 된다.

TITLE Add and Subtract, Version 2         (AddSub2.asm)

; This program adds and subtracts 32-bit unsigned
; integers and stores the sum in a variable.

INCLUDE Irvine32.inc
.data
val1     DWORD  10000h
val2     DWORD  40000h
val3     DWORD  20000h
finalVal DWORD  ?
.code
main PROC
    mov  eax,val1         ; start with 10000h
    add  eax,val2         ; add 40000h
    sub  eax,val3         ; subtract 20000h
    mov  finalVal,eax     ; store the result (30000h)
    call DumpRegs         ; display the registers
    exit
main ENDP
END main
실행한 줄 EAX finalVal
(시작) 정해지지 않음 정해지지 않음 (?)
mov eax,val1 00010000h 그대로
add eax,val2 00050000h 그대로
sub eax,val3 00030000h 그대로
mov finalVal,eax 00030000h 00030000h

이 프로그램은 뼈대의 앞 네 줄 대신 INCLUDE Irvine32.inc 한 줄을 쓴다. 이 파일 안에 .model과 .stack 같은 줄과 라이브러리 프로시저의 선언이 들어 있기 때문이다. exit는 INVOKE ExitProcess,0의 줄임말이다2. call DumpRegs는 레지스터와 플래그를 화면에 찍는 라이브러리 프로시저를 부른다: Irvine32 라이브러리. 이 프로그램에서는 EAX가 00030000으로 찍힌다. 화면 전체의 모양은 x86 레지스터의 예제에 있다.

권하는 쓰기 규칙

어셈블러가 강제하는 것은 아니고, 읽기 좋게 쓰자는 약속이다.

  • 대문자 쓰기는 한 가지로 정해 끝까지 지킨다. 전부 소문자, 전부 대문자, 예약어만 대문자, 지시어와 연산자만 대문자 가운데 고른다.
  • 이름은 뜻이 드러나게 짓는다.
  • 레이블은 들여 쓰지 않고, 실행하는 명령은 4~5칸 들여 쓴다.
  • 니모닉과 피연산자 사이는 1~3칸 띄운다. 예: mov ax,bx.
  • 주석은 줄 오른쪽에 세로로 맞춘다.
  • 프로시저 사이에 빈 줄을 1~2줄 둔다.

활용

  • 흔한 실수: main ENDP와 END main을 같은 것으로 보고 하나를 빼는 것. 앞의 것은 프로시저의 끝, 뒤의 것은 원본 파일의 끝이다.
  • 흔한 실수: INVOKE ExitProcess,0(또는 exit)을 빼는 것. 프로세서는 main의 마지막 명령 다음에 메모리에 놓인 것을 계속 명령으로 읽어 나간다1.
  • .model의 stdcall이 정하는 것: 호출 규약. PROTO와 INVOKE: INVOKE·PROC·PROTO 지시어.

연결

확인 문제

C1 AddSub2에서 세 변수를 val1 DWORD 5000h, val2 DWORD 3000h, val3 DWORD 1000h로 바꿨다. 명령 네 줄을 차례로 실행할 때 EAX와 finalVal의 값을 줄마다 쓰라.

답: mov eax,val1 뒤 EAX = 5000h. add eax,val2 뒤 8000h. sub eax,val3 뒤 7000h. mov finalVal,eax 뒤 EAX는 그대로 7000h이고 finalVal = 7000h.
흔한 오답: 마지막 줄 뒤 EAX가 0이 된다고 적는 것. mov는 값을 베끼는 것이라 원래 자리의 값은 그대로다.

C2 AddTwo 프로그램 전체가 하는 일을 한 문장으로 쓰라. 프로그램이 끝났을 때 EAX의 값은?

답: EAX 레지스터에 5를 넣고 6을 더한 뒤, 운영체제에 끝났다고 알리고 끝낸다. EAX = 0000000Bh(11).

C3 main ENDP와 END main은 각각 무엇을 끝내는가? .data와 .code의 순서를 바꿔 .code를 먼저 써도 두 줄의 뜻은 그대로인가?

답: main ENDP는 main 프로시저를 끝낸다. END main은 원본 파일을 끝내고, 프로그램이 main에서 시작한다고 알린다. .data와 .code는 “여기부터 어느 구역”이라는 표시일 뿐이라 순서를 바꿔도 각 줄이 자기 구역에 들어가는 것은 같다.
이유: ENDP는 PROC와 짝이고, END는 파일에 하나뿐이다. 구역 표시는 다음 구역 표시가 나올 때까지 이어진다1.

  1. 표의 풀이는 교재 Irvine, Assembly Language for x86 Processors 7판, 3.2절을 따랐다. ↩ ↩2 ↩3

  2. 추적 표는 검증 코드의 흉내 실행 결과다. .MODEL flat,stdcall, .STACK 4096, exit EQU <INVOKE ExitProcess,0>은 Irvine32.inc가 불러오는 정의 파일 SmallWin.inc에 적혀 있다. ↩