올해 1월호부터 6회에 걸쳐 어셈블리 언어(MASM, TASM)에 대해서 알아보기로 하자. 주된 내용은 어셈블리 언어에 대한 문법적인 설명이 되겠지만 간간히 C 언어와의 결합도 다루어서 프로그래머들이 가장 선호하는 어셈블리 언어와 C 언어를 결합한 프로그램의 구현이 가능하도록 할 예정이다.
어셈블리가 필요한 이유
프로그래밍이라는 말이 보편화 된 요즘에는 헤아릴 수 없을 만큼 많은 종류의 언어Language가 있다. 그 많은 언어들 중에서 가장 두드러지게 부각되는 언어를 채으라고 한다면 대부분 C 언어를 꼽을 것이다. 그 까닭은 어느 곳에든지 이식이 가능한 언어의 범용성과 기존의 언어로는 다룰 수 없었던 아래쪽의 것들까지도 다룰 수 있도록 한 언어의 저급성 때문이 아닌가 생각한다. 하지만, 아무리 C 언어나 사용자 위주의 언어들이 많이 만들어진다고 하더라도 어셈블리 언어는 사라지지 않을 것이다. 물론 어셈블리 언어로는 C 언어와 다른 고급언어의 범용성에는 미치지 못한다. 하지만 프로그래밍을 함에 있어서 없어서는 안되는 언어가 바로 어셈블리 언어인 것이다. 그 까닭은 앞으로 어셈블리 언어를 배워 나감에 따라 독자 여러분 스스로도 충분히 느낄 수 있을 것이라고 생각한다. 한 예로 화면에 어셈블리 언어의 세계로 여러분을 초대합니다.라는 메시지를 출력하는 프로그램을 C 언어와 어셈블리 언어를 이용해서 작성해서 비교해 보기로 하자.
Code segment
assume cs:Code
org 100h
Start: mov dx,offset Print_Msg
mov ah,9
int 21h
ret
Print_Msg db 어셈블리 언어의 세계로 여러분을 초대합니다.,’$’
Code ends
end Start
[리스트 1] 어셈블리 언어로 작성한 비교 프로그램
main()
{
printf(어셈블리 언어의 세계로 여러분을 초대합니다.);
}
[ 리스트 2 ] C 언어로 작성한 비교 프로그램
<리스트 1>과 <리스트 2>에 각각 어셈블리 언어와 C 언어로 작성한 비교 프로그램을 실었다. 독자 여러분 각자가 실제로 프로그램을 작성하여 실행해 보기 바란다. 이제 이 두가지를 비교해 보기로 하자. 비교의 관점은 물론 실행속도를 보아야 하겠지만 그 문제는 텍스트 화면인 경우에는 크게 차이가 나는 것이 아니므로 여기서는 파일의 크기를 기준으로 해서 비교하기로 하자.
[표1]
<표 1>에서 보는 것처럼 어셈블리 언어로 프로그램을 작성하는 경우에는 C 언어로 작성하는 것에 비해서 소스 파일의 크기가 커진다. 그 까닭은 C 언어의 경우에는 언어 내에서 함수와 명령어를 라이브러리화 하여 명령어나 함수의 이름만을 써 줌으로써 라이브러리에 있는 함수를 호출한다. 때문에 단지 화면에 문자를 출력하는 함수인 ‘ printf ‘라는 함수명과 출력할 내용을 나타내는 문자열, 그리고 main 함수만 구성하면 그것으로 끝이다. 반면에 어셈블리어의 경우에는 프로그래머 자신이 직접 화면에 문자를 출력하는 함수를 구현해야 하므로 C 보다 소스 파일의 크기가 커지는 것은 당연한 이치일 것이다. 이런 이유로 <표 1>의 실행파일의 크기에서 커다란 차이가 생기는 것이다. <리스트 3>에 지금까지의 설명을 종합한다는 의미에서 0으로 나누는 경우에 발생하는 Zero 인터럽터의 내용을 변경하여 0으로 나뉘어졌다는 메시지를 출력하고 프로그램을 끝내는 간단한 프로그램을 예로 들어보겠다.
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> <>
;<> 프로그램의 컴파일과 링크 방법 <>
;<> <>
;<> 터보 어셈블리 컴파일 : tasm zero <>
;<> 링 크 : tlink /t zero <>
;<> <>
;<> 매크로 어셈블리 컴파일 : masm zero; <>
;<> 링 크 : link zero; <>
;<> 실행형 변환 : exe2bin zero zero.com <>
;<> <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 프로그램의 시작부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
ZeroVector segment
assume cs:ZeroVector,ds:ZeroVector
org 100h
ZeroVectorTop: jmp ZeroVectorBottom
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 데이터의 정의부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
ZeroVectorMsg db 0dh,0ah
db ‘Divied by Zero Interrupt is install !!!’,0dh,0ah
db ‘Copyright (C) 1991-12-02 by Wild Horse Software
Reserach !!!’
db 0dh,0ah,’$’
ZeroIsFound db 0dh,0ah
db ‘Divied by Zero detected !!!’,0dh,0ah
db ‘Copyright (C) 1991-12-02 by Wild Horse Software
Reserach !!!’
db 0dh,0ah,’$’
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 새로운 Zero 인터럽터의 설정 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
NewZeroVector proc far
sti
push ax
push bx
push cx
push dx
push si
push di
push bp
push ds
push es
mov ax,cs
mov ds,ax
mov dx,offset cs:ZeroIsFound
mov ah,9
int 21h
int 20h
pop es
pop ds
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
iret
NewZeroVector endp
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 새로운 Zero 인터럽터로 변경한다. <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
ZeroVectorBottom:
mov ax,2500h
mov dx,offset NewZeroVector
int 21h
mov dx,offset ZeroVectorMsg
mov ah,9
int 21h
ret
ZeroVector ends
end ZeroVectorTop
[ 리스트 3 ] 새로운 치명적 에러 벡터의 구성
어셈블리 언어의 개요
<리스트 3>의 예제를 보면서 이것이 모두인가 ?라고 생각하는 사람들을 위해서 가장 기본적으로 알아두어야 할 어셈블리 언어의 문법에 대해서 공부를 시작해 보기로 하자. 먼저 문법에 대한 설명에 들어가기에 앞서 앞으로의 설명에서 등장할 단어 몇 개의 의미부터 살펴보기로 하자.
* 기계어 : 기계가 이해할 수 있는 16진수 수치체계를 일컫는다. 즉, 지금과 같이 컴퓨터의 언어가 보편화되기 이전에는 프로그램을 작성하기 위해서는 프로그래머가 직접 기계어로 원하는 기능을 수행하는 프로그램을 작성해야만 했다. 하지만 세월이 지나고 컴퓨터라는 것이 보편화되면서 부터 어떻게 하면 사용자들에게 좀더 편리한 프로그래밍 환경을 제공할 수 있을까 하는 연구가 계속되어 왔고, 그 결과로 지금의 언어와 같은 많은 것들이 탄생하게 된 것이다. 이런 의미에서 언어라는 것의 역할을 한마디로 요약한다면 언어는 프로그래머와 기계어의 중간에서 프로그래머의 수고를 덜어주는 일종의 통역 요원이라고 할 수 있을 것이다.
* 어셈블리 언어 : 기계어와 일대일로 대응하는 언어의 한 종류이다. 어셈블리 언어도 하나의 언어라는 의미에서는 다른 것들과 같다. 하지만 어셈블리어는 다른 언어와는 다른 독특하면서도 월등한 기능을 가지고 있다. 그것은 바로 기계어와 일대일로 대응하는 유일한 언어라는 것이다.
* 컴파일러 : 언어로 쓰여진 프로그램을 기계어로 바꾸어 주는 프로그램. 기계어가 아닌 다른 언어 프로그램을 사용하여 작성한 프로그램(소스 프로그램)은 그 상태에서는 사용자인 우리들만이 이해할 수 있을 뿐이다. 기계어 밖에는 알지 못하는 기계는 결코 알 수 없는 것이다. 이런 소스 프로그램을 기계가 이해할 수 있는 기게어로 바꾸어 주는 것이 바로 컴파일러이다.
* 컴파일 : 에서 설명한 컴파일러가 수행하는 작업을 컴파일이라고 한다.
위의 1,2,3에서 설명한 것들은 비단 어셈블리 언어에만 적용되는 것이 아니라 다른 모든 컴파일러형 언어에 적용되는 사항들이다. 컴파일러형 언어에 대해서는 나중에 다시 다루기로 하고 우선은 위에서 알아본 낱말들을 이용해서 어셈블리 언어를 사용한 프로그램의 작성에 대해서 간단한 예를 통해서 함께 알아보기로 하자.
1 단계 : 소스 프로그램을 작성한다
어셈블리 언어를 이용해서 프로그램을 작성하는 첫번째의 단계는 어셈블리 언어를 사용한 소스 프로그램(명령어의 집합체)을 작성하는 것이다. 대부분의 고급언어들이 소스 프로그램을 작성하기 위한 에디터를 제공하는 것과는 달리 어셈블리 언어는 이것이 제공되지 않는다. 때문에 에디터는 독자들이 가지고 있는 기존의 에디터를 이용하기 바란다.
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> Main 프로그램의 시작부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
DemoProgram segment
assume cs:DemoProgram,ds:DemoProgram
org 100h
DemoProgramTop: mov dx,offset Copyright
mov ah,9
int 21h
ret
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 사용되는 데이터를 정의해 둔다. <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
Copyright db 0dh,0ah
db ‘Copyright (C) 1991-12-02 By Wild Horse
Software Reserach !!!’
db 0dh,0ah,’$’
DemoProgram ends
end DemoProgramTop
[리스트 4] 화면에 문자열을 출력하는 소스 프로그램
** 주의 **
소스 프로그램을 작성하는데 있어서 주의해야 할 점은 반드시 텍스트 형태로 저장되는 에디터를 사용해야만 한다는 것이다. 텍스트 형태임을 확인하는 방법은 작성한 파일을 도스의 Type 명령을 이용해서 화면에 출력했을때 독자들이 작성한 내용이 그대로 출력되는가를 보면 된다.
2 단계 : 소스 프로그램을 컴파일한다
1 단계가 끝나면 이제는 컴파일러를 이용해서 우리가 작성한 소스 프로그램을 기계가 이해할 수 있는 기계어로 번역하는 단계인 컴파일을 거쳐야 한다. 만약 이 단계에서 에러가 발생하지 않고 성공적으로 끝났다면 3단계로 넘어가면 되고, 에러가 발생한 경우에는 다시 1단계로 돌아가서 에러를 수정하고 다시 컴파일하는 단계를 반복해야 한다. 이 단계는 에러가 발생하지 않고 성공적으로 컴파일 될 때까지 반복해야 한다.
C:\WILD\TASM DEMO
또는
C:\WILD\MASM DEMO;
3 단계 : 목적 파일을 링크한다
이제는 만들어진 목적 파일을 실행이 가능한 형태로 만들어 주는 링크의 단계이다. 이 단계 역시 에러가 발생하지 않을 때까지 반복되어야 한다. 이제 성공적으로 실행파일이 만들어진 경우에는 도스 상태에서 프로그램을 이름을 입력하고 엔터키를 누름으로써 우리가 작성한 프로그램을 실행시킬 수 있다.
C:\WILD\TLINK /T DEMO
또는
C:\WILD\LINK DEMO;
C:\WILD\EXE2BIN DEMO DEMO.COM
** 참고 **
보통 링크의 단계를 거쳐서 만들어지는 프로그램의 형태는 EXE이다. 하지만 여기서 필자가 작성한 프로그램의 형태는 COM파일이므로 위의 3단계에 한가지가 더 추가되어야 한다. 이 단계는 터보 어셈블리의 경우에는 링크의 옵션에 /t만을 추가하는 것으로 가능하지만, 매크로 어셈블리의 경우에는 EXE2BIN을 이용해서 직접 바꾸어야 한다. 이에 대한 자세한 내용은 EXE와 COM의 비교에서 자세히 다루기로 하겠다.
컴파일형 언어와 인터프리터 언어의 비교
어셈블리 언어를 이용한 프로그램의 작성방법은 베이직과 같은 인터프리터형 언어를 사용해온 독자들에게는 아마도 낮설게 느껴질 것이다. 여기에서는 베이직으로 대표되는 인터프리터 언어와 우리가 배우고자 하는 어셈블리가 속한 컴파일 언어에 대해서 비교해서 알아보기로 하자.
인터프리터 언어
우선 베이직으로 대표되는 인터프리터 언어에 대해서 살펴보자. 인터프리터 언어는 사용자들의 편의를 도모하기 위해서 위에서 알아본 여러 단계를 거치지 않고 직접 베이직 프로그램 환경안에서 작성한 소스 프로그램의 실행이 가능하도록 만들어진 프로그램이다. 따라서 여러 단계를 거쳐서 만들어지는 컴파일 언어에 비하여 사용하기 쉽다는 장점이 있기는 하지만, 프로그램의 실행은 반드시 베이직 프로그램 환경안에서만 가능하기 때문에 완성도가 높은 프로그램을 작성하는 프로그래머들에게는 거의 사용되지 않는다.
컴파일 언어
인터프리터 언어와는 달리 컴파일 언어는 소스 프로그램을 작성하고, 그것을 어셈블하여 목적 파일을 만들고, 목적파일들을 묶어서 하나의 실행파일을 만드는 여러단게를 거치기는 하지만, 일단 만들어지기만 하면 그 이후부터는 새롭게 컴파일과 링크하지 않고, 기계어로 저장된 실행파일을 실행시키는 것만으로도 실행이 가능하다. 대부분의 프로그래밍 언어가 여기에 속한다.
COM 파일과 EXE 파일의 차이점
이번에는 좀 색다른 내용을 다루어 보기로 하자. ‘등잔밑이 어둡다’는 말이 가장 잘 적용될 수 있는 부분이 아닌가 한다. 우리는 컴퓨터(일반적인 퍼스널 컴퓨터)에서 어떤 프로그램을 동작시키기 위해서 파일이름을 명령라인에 쓰고 엔터키를 누름으로써 그것을 실행시킨다. 그러면 디렉토리에 있는 아무 파일이나 쓰고 엔터키를 누르면 실행이 가능한가 ? 물론 그렇지 않다. 실제로 일반 퍼스널 컴퓨터에서 실행가능한 파일은 두가지 종류밖에 없는데 그 두가지가 바로 여기서 다루고자 하는 실행파일(COM 파일과 EXE 파일)이다.
EXE 파일
대부분의 실행파일이 이 형태를 가진다. 앞에서 알아본 것처럼 EXE 파일은 어셈블과 링크의 단계를 거쳐서 만들어지는 실행파일이다.
COM 파일
EXE 파일의 단계에서 형태를 변경하는 EXE2BIN이라는 한단계를 더 거쳐서 만들어지는 실행파일이다.
[표 2]
<표 2>에서 각각의 형태가 가지는 장점을 정리하였다. 모든 면에서 COM 파일이 우세하지만, 하나의 세그먼트 밖에 사용할 수 없다는 것 때문에 보편적으로는 사용되지 않지만 프로그램의 크기나 속도가 민감하게 적용되는 경우에는 꼭 필요한 형태이다.
16비트 CPU
그럼 여기서 16비트 CPU에 간략하게 알아봄으로써 어셈블리 언어를 이해하는데 도움을 되도록 한다. 우리는 흔히 286, 386이라는 것으로 컴퓨터의 명칭을 대신한다. 여기서 286이나 386이나 하는 것이 바로 컴퓨터의 핵심이라고 할 수 있는 CPU의 종류이다. 보통은 8086 CPU를 16비트 CPU라고 하는데, 그 까닭은 CPU 내에 있는 레지스터가 16비트의 크기를 가지고 있고, 또한 주변장치와의 데이터 교환을 담당하는 데이터 버스도 16개를 가지고 있다. 그러므로 한번에 16비트의 데이터를 취급할 수 있기 때문에 16비트 CPU라고 한다. 참고로 XT의 경우는 8088 CPU를 사용하는데, 이 8088은 내부에서는 8086과 동일하게 동작은 하지만 외부와의 교환에 사용되는 데이터 버스가 8개밖에 없기 때문에 8086을 AT, 8088을 XT라하여 구분한다. ) 그러나 명령이나 데이터는 실제로 8비트를 단위로해서 구성되어 있어서 메모리에 위치하는 장소를 나타내는 번지도 1바이트마다 하나씩 할당되어 있다. 이것은 16비트 CPU가 8비트 CPU의 연장선에서 발전한 것이라는 점을 생각한다면 당연한 것이라 할 수 있다.
세그먼트와 오프셋
이제 어셈블리 언어의 가장 밑바탕이 된다고 할 수 있는 세그먼트라는 것과 레지스터라는 것에 대해서 자세히 알아보기로 하자. 이 두가지의 개념은 어셈블리 언어라는 커다란 산을 넘기 위해서 없어서는 안되는 등산화와 같은 것이므로 반드시 이해하고 넘어가기를 당부한다. 8086 CPU는 이전에 8비트 CPU가 64K 바이트 밖에 취급할 수 없던 것과는 달리 거의 16배의 메모리를 취급할 수 있게 되었다. 때문에 8비트 시절에 메모리를 취급하던 개념만으로는 이 많은 메모리를 취급할 수가 없었다. 이 문제점을 해결하기 위해서 등장한 것이 세그먼트와 오프셋이라는 개념이다. 이 두가지의 개념은 따로 떨어져서는 기능을 발휘할 수 없고 반드시 함께 사용되어야만 원하는 기능을 발휘할 수 있다.
세그먼트
8비트 시절에 비해 훨씬 많아진 메모리를 다루기 위한 방법으로 택한 것은 이전에는 하나의 레지스터만을 가지고 절대번지를 취급하던 방식을 바꾸어 두개의 레지스터를 조합하여 절대번지를 다루는 방식이었다. 이때에 앞자리를 가르키는 레지스터를 세그먼트 레지스터라고 한다.
오프셋
위에서 설명한 내용의 뒷자리를 표시하는 것이 오프셋이라는 개념이다.
절대번지의 지정
그러면 세그먼트와 오프셋를 이용해서 어떻게 그 많은 메모리를 관리할 수 있을까 ? 다음의 예에서 보면,
2000 H
+ 3456 H
———
23456 H
2000H라는 세그먼트와 3456H라는 오프셋이 더해져서 23456H라는 절대번지를 만들어 냈다. 이를 풀어서 쓰면 절대번지를 나타내기 위해서는 16비트의 세그먼트 레지스터와 16비트의 오프셋 레지스터 두개를 4비트 만큼 비껴서 덧셈하는 방법으로 그 많은 메모리를 관할한다라고 할 수 있다.
레지스터
위에서 세그먼트와 오프셋을 설명하기 위해서 레지스터라는 개념이 도입되었다. 이제는 이 레지스터 하나하나에 대해서 자세히 살펴보기로 하자.
레지스터의 구성
8086 CPU의 레지스터의 종류를 <표 3>에 정리하였다.
[표 3]
<표 3>에서 범용 레지스터 앞의 4가지는 8비트의 크기와 16비트의 크기 모두 사용이 가능한데, 그 까닭은 앞에서 살펴본 것처럼 8비트를 바탕으로 해서 16비트 CPU가 나왔기 때문이다.
세그먼트 레지스터의 기능
이제 각각의 레지스터가 하는 역할에 대해서 자세히 살펴보자. 여기서 주의 깊게 살펴야 할 것은 어떤 레지스터가 어디에 사용되는가 하는 것이다. 먼저 세그먼트 레지스터에 대해서 알아보기로 하자.
+—————+
CS : | 코드 세그먼트 |
+—————+
CPU가 실행해야 할 명령이 저장되어 있는 세그먼트를 지정한다.
+—————–+
DS : | 데이터 세그먼트 |
+—————–+
데이터가 저장되어 있는 세그먼트를 지정한다.
+—————+
SS : | 스택 세그먼트 |
+—————+
스택 동작을 수행하는 세그먼트를 지정한다.
+——————-+
ES : | 엑스트라 세그먼트 |
+——————-+
데이터의 전송 명령에 있어서, 데이터의 수신측 등에 사용되는 세그먼트를 지정한다.
레지스터의 기능
이제는 나머지 레지스터에 대해서 알아보자.
+———————–+
AX : | 어큐뮬레이트 레지스터 |
+———————–+
어큐뮬레이트 레지스터는 전송 및 연산에 사용된다. AX 레지스터를 사용하면 다른 레지스터를 사용하는 것보다 명령의 바이트 수가 짧게 되거나, 실행 시간이 짧게 되는 장점이 있다. 또한 곱셈 나눗셈에 있어서 피승수, 피제수의 저장, 부호확장 명령에는 반드시 이 레지스터를 사용해야만 한다.
+—————–+
BX : | 베이스 레지스터 |
+—————–+
이 레지스터도 AX 레지스터와 마찬가지로 전송이나 연산에 사용 가능하다. 뿐만 아니라 번지의 간접지정에도 많이 이용된다.
+—————–+
CX : | 카운트 레지스터 |
+—————–+
전송 및 연산에 사용된다. 루프 명령, 스트링 명령에 있어서의 반복 횟수의 자동 카운트에 사용된다. CL 레지스터는 특별히 논리연산명령의 시프트 수 지정에 사용된다.
+—————–+
DX : | 데이터 레지스터 |
+—————–+
전송 및 연산에 사용된다. 일부의 연산에 있어서는 AX레지스터와 함께 사용되기도 한다. ( 곱셈, 나눗셈 및 부호확장 명령의 경우 ) 그리고 데이터의 출력에 사용된다.
+————-+
SP : | 스택 포인터 |
+————-+
스택의 가장 최근의 데이터가 저장되어 있는 번지를 나타낸다. 전송 및 연산에도 사용이 가능은 하지만 새로 스택 포인터를 설정하는 경우 이외에는 거의 사용하지 않는다.
+—————+
BP : | 베이스 포인터 |
+—————+
스택상의 데이터 번지를 간접 지정하는 경우에 사용되는 이 레지스터는 사용자가 자유로이 변경할 수 있으므로 스택상의 저장된 데이터를 꺼내는데 사용한다. 다른 영역의 데이터를 간접지정하거나 전송 및 연산에도 사용된다.
+————-+
SI : | 소스 인덱스 |
+————-+
번지의 간접지정에 사용된다. 특히 스트링 명령에 있어서는 메모리로부터 레지스터로 데이터를 전송하는 전송측 번지의 지정에 사용된다. 그리고 일반적인 전송이나 연산에도 사용가능하다.
+———————+
DI : | 데스티네이션 인덱스 |
+———————+
번지의 간접지정에 사용된다. 특히 스트링 명령에 있어서는 소스 인덱스와 짝을 이루어 레지스터로부터 메모리에 데이터를 전송하기 위한 수신측 번지를 지정하는 경우에 사용된다.
+——————-+
IP : | 인스트럭션 포인터 |
+——————-+
다음에 실행하는 명령이 저장되어 있는 번지를 지시하는 이 레지스터는 명령을 실행함에 따라 CPU가 자동적으로 값을 변화시킨다. 그리고 특이한 것은 사용자는 결코 이 값을 조작할 수 없다는 것이다.
+—————–+
FL : | 플래그 레지스터 |
+—————–+
일반적인 레지스터와는 달라서, 하나하나의 비트가 의미를 가지고 독립적으로 동작한다. 연산의 결과에 따라서 변화하고 다음 연산이나 판단 조건 등에 데이터를 주고 받기 위한 비트와 플래그 레지스터 조작 명령에 의해서 세트되며, CPU의 동작상태를 제어하기 위한 비트도 있다.
예제 프로그램
프로그램을 작성하다가 보면(특히 어셈블리 언어의 경우) 많은 경우에 키보드의 어떤 키에 대한 코드를 알아야 할 필요가 생긴다. 이런 경우에 사용될 수 있는 간단한 프로그램을 예로 들었다. 이 예제를 통해서 매크로 어셈블러와 터보 어셈블러를 사용해서 소스 프로그램 컴파일하고 링크하는 방법에 대해서도 다루기로 하겠다.
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> <>
;<> 프로그램의 컴파일과 링크 방법 <>
;<> <>
;<> 터보 어셈블리 컴파일 : tasm viewcode <>
;<> 링 크 : tlink /t viewcode <>
;<> <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 프로그램의 시작부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
ViewCode segment
assume cs:ViewCode,ds:ViewCode
org 100h
ViewCodeTop: jmp ViewCodeBottom
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 데이터의 정의부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
ESC EQU 1
SPACE EQU 20h
Input_Message db 0dh,0ah,0dh,0ah
db ‘Press any key !!! ‘,’$’
Output_Message db 0dh,0ah
db ‘ASCII Code & SCAN Code is : ‘,’$’
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 서브루틴의 시작부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 1 Byte의 코드를 문자열로 만들어 준다. <>
;<> <>
;<> 입력 : 스택에 값을 푸쉬 <>
;<> 출력 : DX Register에 돌려준다. <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
MakeCode proc near
push bp
mov bp,sp
push ax
xor ah,ah
mov dl,byte ptr [bp+4]
mov al,dl
and al,11110000b
shr al,1
shr al,1
shr al,1
shr al,1
cmp al,9
ja HighIsAlpha1
add al,30h
jmp LowRoutine
HighIsAlpha1: add al,’A’-0ah
jmp LowRoutine
LowRoutine: mov dh,al
and dl,00001111b
cmp dl,9
ja HighIsAlpha2
add dl,30h
jmp ExitRoutine
HighIsAlpha2: add dl,’A’-0ah
jmp ExitRoutine
ExitRoutine:
pop ax
pop bp
ret
MakeCode endp
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 화면에 문자열을 출력하는 서브루틴 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
PrintMsg: mov ah,9
int 21h
ret
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 화면에 한문자를 출력하는 서브루틴 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
PrintByte: mov ah,2
int 21h
ret
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 코드의 시작부분 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
ViewCodeBottom: mov dx,offset Input_Message
call PrintMsg
mov ah,0 ; 한문자를 받는다.
int 16h
cmp ah,ESC ; ESC 키이면 끝낸다.
je Exit_Input
mov si,ax
mov dx,offset Output_Message
call PrintMsg
mov ax,si
xor bx,bx
mov bl,al
push bx ; 문자열을 만든다.
call MakeCode
add sp,2
xchg dh,dl ; 한문자 출력
call PrintBYte
xchg dh,dl ; 한문자 출력
call PrintByte
mov dl,SPACE ; 공백을 출력
call PrintByte
mov ax,si
xor bx,bx
mov bl,ah
push bx ; 문자열을 만든다.
call MakeCode
add sp,2
xchg dh,dl ; 한문자 출력
call PrintByte
xchg dh,dl ; 한문자 출력
call PrintByte
jmp ViewCodeBottom
Exit_input: ret
ViewCode ends
end ViewCodeTop
[ 리스트 5 ] ASCII 코드와 SCAN 코드를 출력하는 프로그램
어셈블리 언어가 아닌 다른 언어를 통해서 프로그래밍이라는 것에 눈을 뜬 사람들에게는 좀더 깊이 배울 수 있다는 점에서, 그리고 언어가 처음인 사람에게는 가장 확실한 밑바탕이라는 점에서 어셈블리 언어를 추천한다. 다른 언어와 달리 주어지는 함수가 하나도 없어 프로그래밍에 힘이 들 것이다. 하지만 그 노력의 댓가는 충분히 있다고 필자는 확신한다.

PC 어셈블러 