------------------------------------------------------------------------------
[MASM6.0/2]인스톨, 에디터를 맘에 들게..
이번 강좌에서는 매즘 6.0의 인스톨 방법과 에디터를 사용자 맘에 들
게 바꾸는 것에 대해 얘기를 하겠습니다.
**** 매즘 6.0의 인스톨에 대해 ****
1. 인스톨 방법에 대해..
--> 매즘 6.0의 인스톨 방법은 아주 간단합니다. SETUP디스크를 A 드라
이브에 넣고 SETUP을 타이프 하면 다음과 같은 메뉴가 나옵니다.
(편의상 번호를 삽입합니다.)
1. Install the Microsoft Macro Assembler
2. Install the Macrro Assembler using defaults
3. Run SETUP without installing any files
4. View important documentation notes (README.DOC)
5. View the packing list (PACKING.LST)
6. Copy a file from the distribution disks
7. Exit SETUP
만약 여러 프로그램들을 다루신 분들이라면 설명이 없이도 금방 인
스톨하시리라 봅니다. 가장 간단한 방법은 2번을 고르면 금방 됩니
다. 그러면 여러 결정 상황 ( SAMPLE프로그램들을 복사할 것인가의
여부, BRIEF 에디터의 에뮬레이션을 할 것인가의 여부 등등 )
을 보여주고, 거기서 바꿀만한 것을 바꾸고 메뉴 맨 위에 위치한
NO CHANGE를 선택하면 곧바로 인스톨을 시작합니다.
모든 결정상황에 대해 계속 문답식으로 내용을 결정하면서 인스톨
을 하고자 1번을 선택하면 됩니다. 그런데 제가 하나 터보 계열의
에디터을 쓰시는 분께 권하고 싶은 것은, 디폴트로 되어 있지는
않지만 BRIEF 에디터의 에뮬레이션을 선택하는 것이 좋을 듯 싶습
니다. 터보 계열과 키가 많이 비슷하므로 조금만 바꾸면 별 어려움
없이 에디터를 사용할 수 있기 때문입니다.
2. 인스톨 후에 해야 할 것들..
--> 위의 방법으로 다 인스톨을 하고 난다음 해야 할 중요한 것은 바로
AUTOEXEC.BAT과 CONFIG.SYS를 바꾸는 것입니다. 그리고 TOOLS.PRE
라는 파일을 TOOLS.INI로 이름을 바꾸어야 합니다.
AUTOEXEC.BAT와 CONFIG.SYS에 첨가할 내용이 들어있는 파일은 기본
디렉토리인 C:/MASM/BIN에 NEW-VARS.BAT과 NEW-CONF.SYS가 있습니
다. 이것을 각각 AUTOEXEC.BAT과 CONFIG.SYS에 삽입하면 됩니다.
NEW-CONF.SYS는 FILES과 BUFFERS를 정하는 것으로 별로 중요하지는
않으나 NEW-VARS는 헬프 파일과 INCLUDE파일 등의 경로가 들어 있
으므로 꼭 AUTOEXEC.BAT에 삽입을 해 주어야 합니다.
또, TOOLS.PRE가 기본디렉토리인 C:/MASM/INIT에 있는데 이를
TOOLS.INI로 바꾸어 주어야 합니다.
**** PWB의 간략한 소개와 에디터를 맘에 들게 바꾸는 법 ****
1. Programmer's WorkBench (PWB)에 대해서..
--> 저번에도 말했듯이 PWB는 윈도우 방식의 통합 개발 환경으로서 거
기서 소스 작성, 어셈블, 링킹, 디버깅, 소스 브라우져(browser)
기능, 완벽한 온라인 헬프 시스템을 이용할 수 있습니다.
PWB를 실행시키려면 간단히 PWB라고 치면 됩니다. PWB를 사용하는
방법은 터보나 볼랜드 계열의 IDE(통합 개발 환경)과 비슷하고
또한 온라인 헬프에 아주 자세히 나와 있으므로 생략을 합니다.
2. 에디터 사용에 관해서...
--> 여러 에디터를 씀에 따라 문제가 되는 것은 각각 에디터 마다 쓰는
키가 다르다는 것입니다. 원래 pwb에디터의 키는 많이 쓰이고 있는
터보 계열과 많이 달라서 애를 먹습니다. 가령 터보 계열에서는
파일의 맨 처음으로 가는 키는 Ctrl+PageUp 키이나 pwb에서는
Ctrl+Home키 입니다. 비교적 비슷한 것이 위에서 말한 Brief에디터
입니다. 그래서 터보계열을 많이 쓰시는 분은 위에서 인스톨을 할
때 Brief에디터 에뮬레이션을 선택하라고 권고 했습니다. 그럼
이번에는 에디터의 키를 자기 맘대로 바꾸는 것에 대해 이야기 하
겠습니다.
에디터의 키를 바꾸려면 pwb의 메뉴에서 OPTION의 Key Assignments
를 선택을 합니다. 그러면 그냥 어떤 파일이 화면에 뜨게 됩니다.
그 내용은 함수 : 키 의 형식이 되어 있는데 단순히 키를 변환 시
키면 됩니다.
가령 파일의 맨 처음으로 오는 함수의 이름은 pwb에서 begfile입니
다. 만약 Brief 에뮬레이션 모드를 선택하지 않고 디폴트로 인스톨
을 했으면 다음과 같이 나올 것입니다.
begfile : Ctrl+Home
여기서 만약 그 키를 Ctrl+PgUp으로 바꾸고 싶으면 단순히 다른 에
디터를 쓰는 것처럼 Del키를 이용해서 위의 Home을 PgUp으로 바꾸
면 됩니다. 그러면 바뀌었다는 것을 알려 주기 위해서 바뀌어진 줄
의 색깔이 바뀌게 됩니다. 만약 인식할 수 없는 키를 적거나 ( 가
령 sadf처럼) 잘못된 형식으로 적거나 ( 키의 조합은 +로 연결되
어야 하는데 -으로 연결을 할때)하면 다음줄로 내려 가지 못하게
되어 키를 제대로 써 주어야 계속 키를 바꿀 있게 됩니다.
그러면 많이 쓰이면서도 에디터 마다 다른 함수를 열거해보죠.
begfile --> 파일의 맨 처음에 커서를 위치.
endfile --> 파일의 맨 끝에 커서를 위치.
insertmode --> insert, overwrite 상태를 토글(toggle)
ldelete --> 한 줄을 지움
menukey --> 메뉴를 부름
다음은 pwb기본 에디터에는 함수 인데 brief에디터를 에뮬레이션하
기 위해 만들어지 매크로 입니다. 즉 이것들은 brief에뮬레이션을
선택했을 경우에만 나옵니다.
beginnig_of_line --> 줄의 맨처음으로 간다. ( 칼럼 1)
( 기본 함수에 begline이라고 있는데 이것은
앞의 공백문자를 무시한, 맨 처음 문자로
가는 함수 입니다. )
delete_next_word --> 앞의 한 단어를 지운다.
delete_previous_word --> 뒤의 한 단어를 지운다.
delete_to_eol --> 커서의 위치 부터 줄 끝까지 지운다.
아마 이 정도만 바꾸어도 그리 불편하지 않게 쓰실 수 있을 것입니
다. 다른 기능들, 가령 컴파일하는 키라던지 찾기의 단축키라던지
등을 바꾸시려면 그에 해당하는 키를 바꾸시면 됩니다. 더 능력이
되시면 스스로 매크로를 만들어서 tools.ini에 삽입하여 자기만의
기능을 에디터에 삽입할 수도 있습니다. 위의 delete_next_word도
매크로로 만든 예입니다. 인스톨시 Brief 에디터 에뮬레이션을 선
택한 후 tools.pre 혹은 tools.ini를 보시면 그 안에 매크로를 정
의한 것을 보실 수 있을 것입니다. 함수에 대한 설명이나 매크로를
만드는 방법은 도움말에 자세히 나오니 참조하시길 바랍니다.
그리고 키를 바꾸실 때 어떤 함수의 이름이 나왔는데 그 함수를 잘
모르시겠다면 커서를 그 함수 이름에다 위치하고 F1키를 누르시면
곧바로 잘 된 설명을 보실수 있을 것입니다. 마우스로는 마우스 커
서를 위치하고 오른쪽 버튼을 누르면 도움말이 나옵니다. 실제로
모든 것이 이런식으로 도움말을 볼 수 있는데 가령 소스를 보다가
inc라는 명령어가 나왔는데 이것이 어떤 명령어인지 잘 모르겠다면
커서를 inc위에다 위치시키고 마우스를 버튼을 누르거나 F1키를 누
르면 곧 도움말을 볼 수 있습니다.
!!!! 주의할 점 !!!!
위의 키를 바꾼것을 계속 유지하려면 꼭 세이브를 해야합니다. 메
뉴에서 세이브를 선택하던지 세이브 단축키를 이용하면 됩니다.
그리고 그밖의 에디터에 관한 옵션들 가령 화면 색이라던지 탭을
몇 칸 뛰울 것이라던지 하는 것은 Options에 Editor Settings을
고르고 위의 키 바꾸는 것처럼 값을 바꾸어 주면 됩니다.
** 블럭지정에 관한 팁 **
블럭을 지정하고 블럭 지우기, 버퍼에 복사하기 같은 것을 하는데
이런 키들은 볼랜드 계열의 키와 비슷합니다. 즉 블럭을 지정하는
것은 shift키를 누르고 커서 이동키를 누르면 되고요. 카피하는 것
은 Ctrl+Ins 이고 지우는 것은 Ctrl+Del이고 뭐 이런 것은 다 같
은데 하나 좋은 것은 블럭을 지정을 할 때 한글의 F4를 지정해서
블럭을 설정을 할 때처럼 박스모양의 블럭을 지정할 수 있다는 것
입니다. 즉 마우스로 블럭을 지정할 때 오른쪽 버튼을 누르면
박스모양의 블럭과 일반 줄단위의 블럭을 토글하면서 지정을 할
수 있습니다. 한 번 해보세요. 키는 Ctrl+B를 누르면 두 지정방
식간의 토글을 할 수 있습니다.
───────────────────────────────────────
제목: [강좌] MASM 6.0 사용법 3/8
------------------------------------------------------------------------------
[MASM6.0/3] 소스를 빌드하는 방법..
이번 강좌에서는 소스 프로그램을 빌드(Build)를 하여 실행 파일로
만드는 방법을 알아보도록 하겠습니다.
**** 소스 프로그램을 빌드 하는 법 ****
소스 프로그램을 빌드하는 방법은 이미 고급언어로 실행 파일을 만
어본 경험이 있으신 분은 아마 쉽게 하리라고 봅니다. 그러나 몇가지
주의할 점이 있고 또한 여러 모듈로 된 프로그램을 빌드하는 방법에
대해 잘 모르시는 분이 있으실 것 같아 간단히 설명해 보겠습니다.
1. 하나의 소스로 구성된 프로그램을 빌드하는 방법
-->어떤 소스가 파일로 저장되어 있다고 가정을 퇸襟때 그것을 어셈블
을 시키기 전에 먼저 해주어야 될 것은 어셈블할 프로그램의 타입
을 정해주어야 합니다. 매즘 6.0에서 제공하는 타입은 여러가지가
있는데, 가령 크게 나누어서 도스용 실행파일과 OS용 실행파일이
있는데 각각 실행파일의 형태가 달라지기 때문에 이러한 것들을
정해주어야 합니다. 또한 도스용 실행 파일중에서도 확장자가 EXE
인 것도 있고 COM인 것도 있는데 이 둘 역시 실행파일에 있어서
차이가 나기 때문에 이러한 타입들을 정해 주는 과정이 필요합니다.
그래서 어셈블할 프로
?텝타입을 정해주려면..
(1) Options메뉴에서 Build Options을 선택합니다.
(2) Set Main Language를 선택하여 assembler를 선택합니다.
(3) Set Initial Build Options을 선택하여 어셈블할 파일의 타입을
정합니다. 만약 EXE파일이라면 DOS EXE를 COM 파일이면 DOS COM
을 선택하면 되겠습니다.
프로그램의 타입과 함께 고려해야 할 것은 실행파일을 어떤 버젼으로
만드는가를 결정하는 것입니다. 여기서 버젼이라함은 Release 혹은
Debug버젼을 말하는 것으로 두 버젼의 차이점을 말하자면5 Release
버젼은 Debug버젼과는 달리 실행 파일내에 디버그 정보를 포함하지 않
습니다. 말 그대로 Release버젼은 프로그램이 버그가 없어서 곧 발표(?)
해도 될 때 Release버젼으로 프로그램을 빌드하는 것입니다. 만약 어떤
에라가 있어서 계속 디버깅을 한다면은 Debug버젼으로 빌드를 하는 것이
좋습니다. 왜냐하면 Debug버젼으로 프로그램을 빌드하면 코드뷰라는
디버거의 기능을 십분 발휘할 수 있기 때문입니다. 그러나 Debug버젼은
실행 파일내에 디버그 정보가 들어가기 때문에 실행파일이 더 커지게
됩니다. 그러므로 상황에 맞게 버젼을 선택해야 하겠습니다. 두 버젼을
선택하는 것은 위의 Options 메뉴의 Build Options에서 정할 수 있습
니다.
2. 여러개의 모듈로 이루어진 프로그램을 빌드하는 법
-->하나의 소스로 이루어진 프로그램과는 달리 여러개의 파일로 이루어진
프로그램을 빌드할 때는 Program List를 만들어야 합니다. Program
List란 어떤 프로그램을 구성하는 소스 파일의 리스트를 말하는 것으로
PWB는 이를 가지고 make file을 만들게 됩니다. make file이란 고급언
어에서 프로젝트 파5일과 유사한 역할을 하는 파일로 확장자가 .MAK입
니다. 아마 볼랜드 C같은 데서 프로젝트 파일을 만들어본 경험이 있으신
분은 쉽게 프로그램 리스트를 작성하실 것이라고 봅니다.
그럼 실제로 얘를 들면서 여러개의 모듈로 이루어진 프로그램을 빌드
하는 방법을 보도록 합시다.
다음의 예제 프로그램은 화면에 Hello, world를 찍는 프로그램으로
HELLO.ASM과 PUTSTR.ASM 두개의 파일로 이루어진 프로그램입니다. 메
인 모듈인 HELLO.ASM에서 외부 함수인 PutStr불러서 화면에 문자를 뿌
리게 되어 있습5니다. 소스의 곳곳에 매즘 5.1에서는 생소한 것들이 있는
데 추후 강좌를 통해서 설명하기로 하고 일단은 소스를 살펴봅시다.
메인 모듈 - HELLO.ASM
; HELLO.ASM defines a string and calls the procedure PutStr to
; display the text. PutStr is in a separate module PUTSTR.ASM
.MODEL small, c
; Tell assembler PutStr's argument type and how to call PutStr:
PutStr PROTO pMsg:PTR BYTE
.DOSSEG
.STACK
.DAT5A
msg BYTE "Hello, world.", 13, 10, 0 ; null-terminated
; string
.CODE
.STARTUP ; Initialize data and stack
; segments
INVOKE PutStr, ADDR msg ; call external procedure
.ENDW
ret
PutStr ENDP
END
그러면 위얹텝소스를 각각 HELLO.ASM과 PUTSTR.ASM로 저장을 합니다.
자 이제부터 위의 프로그램을 빌드하는 방법을 단계를 밟아 가면서
살펴보도록 하죠.
(1) 빌드할 프로그램의 타입을 정한다.
--> 위에서 설명했든이 메뉴의 Options의 Build Options을 선택
하여 Set Main Language을 선택하여 assembler를 선택을 하고
Set Initial Build Optins을 선택하여 DOS EXE를 선택을 합니
다. 버젼은 아직 디버그가 안되었다고 보고 Debug버젼을 선택
을 합니다.
(2) Prog5ram List를 만든다.
--> Make메뉴에서 Set Program List를 선택을 합니다. 그러면 다이
알로그 박스가 나오는데 메인 프로그램의 파일 이름을 적습니
다.(HELLO). 그러면 PWB가 HELLO.MAK가 있다면 그 파일을 로드
하게 됩니다. 없을 때는 새로운 makefile을 만들것이냐고 물어
보는데 Yes를 선택을 합니다. 그러면 다이알로그 박스가 나
오는데 거기엔 현재 디렉토리에 있는 파일들의 리스트가 나
오게 됩니다. 거기서 프로그램을 구성하는 파일들얹밗선택하면
됩니다.
위의 예에서는 HELLO.ASM, PUTSTR.ASM이 되겠죠. 선택이 끝났
으면 Save List를 선택합니다. 그러면 자동적으로 PWB가 HELLO
.MAK이라는 makefile을 만들게 됩니다.
(3) 프로그램을 빌드한다.
--> Set Program List를 선택하여 확장자가 .MAK인 makefile을
불러 옵니다. 위처럼 Program List를 바로 만드후면 다시 부를
필요는 없습니다. makefile을 로드한 후5에 여러 옵션들을 정합
니다. MASM 옵션도 있고 BROWSE 옵션, LINK, NMAKE옵션 등이
있지만 처음엔 디폴트 옵션으로 빌드를 하고 나중에 필요가
있다면 적절하게 옵션들을 정합니다. 옵션을 정한후에는 MAKE
메뉴의 Build 혹은 Rebuild All을 선택하여 프로그램을 빌드
합니다. 아무 에라가 없으면 실행 파일이 만들어집니다.
───────────────────────────────────────
제목: [강좌] MASM 6.0 사용법 4/8
------------------------------------------------------------------------------
[MASM6.0/4] 브라우져, 코드뷰 사용법..
그럼 디버깅에 유용한 소스 브라우져와 코드뷰의 간단한 사용
법에 알아보도록 합시다.
**** 소스 브라우져와 코드뷰의 간단한 사용법 ****
1. 소스 브라우져의 사용법
-->소스 브라우져(Source Browser)는 함수와 변수들의 관계에
관한 정보를 보여주는 역할을 합니다. 즉 함수와 변수들의
정의(definition)와 그것들이 어디서 참조(reference)되었는
지를 화면에 보여줍니다. 실제로 사용해 보시면 더욱 이해가
쉬울 것이고요 온라인 헬프에 아주 잘나와있으므로 그것을 참
조하면 좋을것입니다. (물론 조금의 영어 실력이 필요하죠.)
소스 브라우져는 메뉴의 Browse 명령을 통해서 이용할 수 있
는데 먼저 이를 이용하려면 데이타 베이스를 정의하는 준비
가 필요합니다.
데이타 베이스를 정의하려면....
(1) MAKE메뉴에서 Set Program List를 선택을 하여 program
list를 만듭니다. 이 program list는 데이타 베이스에
필요하게 됩니다.
(2) 메뉴의 Options에서 Browse Options을 선택합니다. 거기
서 Generate Browse Information을 꼭 선택을 해야 합니
다.
(3) MAKE 메뉴에서 빌드를 하면 확장자가 .BSC(Browser Source
Cache)인 파일이 생기는데 이 파일은 브라우져에 이용되는
자료가 저장되어 있습니다.
위와 같이 데이타 베이스를 정의하면 다음과 Browse메뉴에서
다음과 같은 명령어를 사용할 수 있습니다.
(1) Goto Definition
--> 이 명령어는 변수나 함수, 매크로 등이 정의된 곳으로
커서를 옮겨 줍니다. 먼저 다이알로그 박스에 변수,함
수, 매크로들의 이름이 나오는 데 그 중 커서를 옮길
곳을 선택하고 O.K 버튼을 선택하면 곧바로 그것이 정
의된 소스로 커서를 옮겨 줍니다. 아주 긴 소스를 보거
나 여러개의 파일로 된 소스를 볼 때 이 명령을 사용하
면 어떤 함수나, 매크로, 변수가 정의된 곳을 쉽게 찾
아 볼 수 있습니다.
(2) Goto Reference
-->이 명령어는 함수나 변수, 매크로 등이 참조된 파일과
줄번호들을 보여 줍니다.
(3) View Relationships
-->이 명령어는 프로그램에 관련된 여러 정보를 자세하게
보여줍니다. 이 명령어를 사용하는 데는 다음과 같은
절차가 필요합니다.
1. 오브젝트를 설정한다.
-->여기서 오브젝트란 각종 심볼(함수, 변수..)를 말합
니다.
2. 오브젝트에 적용될 오퍼레이션을 정한다.
-->오퍼레이션은 여러 가지가 있는 데 여기서 다 설명을
하는 것은 어려우므로 실제적으로 한번 사용해보시면
쉽게 이해를 하시리라 봅니다.
3. 보여줄 것들을 정한다.
-->보여줄 것이라함은 함수, 매크로, 변수등을 말하는
데 예를 들어서 2번의 오퍼레이션을 USE를 선택하면
어떤 함수나 매트로 등이 사용한 것들을 죽 나열하게
되어 있는데 여기서 보여줄 것을 함수와 변수만을 선
택하면 그 함수가 사용한 함수와 변수만을 보여주게
됩니다.
(4) List References
--> 이 명령어는 함수, 매크로, 변수, 심볼 등이 어떤 파일
어떤 함수에 의해서 참조되었나를 쭉 나열하게 됩니다.
(5) Call Tree
-->이 명령어는 함수간의 관계, 즉 어떤 함수가 어떤 다른
함수를 부르는 가를 트리구조로 보여줍니다.
이상 소스 브라우져 사용법을 간단히 알아 보았는데 실제로 한번
써보시면 쉽게 그 기능을 이해하시리라 봅니다.
그럼 코드뷰라는 디버거에 대해서 간단히 알아보도록 합니다.
**** 코드뷰의 간단한 사용법 ****
먼저 코드뷰를 사용하기 전에...
코드뷰를 사용하려면 저번 강좌에서 말했듯이 준비해야 할 것이
있습니다. 즉 빌드를 할 때 Debug 버젼으로 프로그램을 빌드해야
합니다. 잊어버리신 분을 위하여 다시 말씀해 드리면 Options 메뉴
에 Build Options을 선택을 해서 Debug버젼을 선택을 하시면 됩니
다. 그러면 실행파일에 Debug 정보가 들어가는데 코드뷰는 이를 이
용해서 여러가지 정보를 제공해 줍니다. 물론 보통 EXE파일도 코드
뷰를 이용할 수는 있지만 소스 레벨에서의 디버깅이 안되므로 코드
뷰를 잘 사용하시려면 Debug버젼으로 빌드하시고 사용하시는 것이
좋겠습니다.
코드뷰가 보여주는 것들...
RUN 메뉴에서 Debug 명령어를 선택하면 곧바로 코드뷰 화면으로
넘어가는 데 화면에 코드뷰가 보여주는 것들은 다음과 같습니다.
먼저 프로그램을 보여주는 데 다음과 같은 세가지 방법으로 보여
줄 수 있습니다. ( Options의 Source Window를 선택 )
1. 매즘 소스 코드 (Source Code)
-->이것은 기본 선택 사항으로 PWB에서 짠 소스 코드를 그대로 보
여줍니다.
2. 디스어셈블된 소스 코드
-->소서와 같은 디스어셈블러와 같이 디스어셈블된 어셈블리 코드
를 보여주므로 1번과 같이 심볼들을 볼 수 없게 됩니다.
3. 1과 2의 혼합
-->즉 매즘 소스 코드와 함께 그 소스 코드가 디스어셈블된 코드가
같이 나오게 됩니다. 이 경우는 매크로나 지시어등이 어떻게 기
계어로 바뀌었는지 살펴 볼때 좋습니다.
위의 프로그램을 보여주는 것 뿐만 아니라 디버깅에 유용한 여러정
보들을 화면에 보여 줍니다.
즉 메모리, 레지스터, 지역 변수 등을 보여주는데 이는 코드뷰 메뉴
에서 View 명령어를 선택하여 보고 싶은 것만 볼 수 있도록 합니다.
기타 여러 기능을 사용하는 방법 - Watch를 사용하거나, Go, Trace
Step 하는 방법, Break Point를 지정하는 방법 등은 일반 터보 계열이
나 볼랜드 계열의 고급언어에서 제공하는 것과 비슷하므로 생략하기로
합니다.
다음 강좌부터는 일반 환경 사용법이 아닌 언어상으로 매즘 6.0에서
나아진 점에 대해 살펴보도록 합시다.
───────────────────────────────────────
제목: [강좌] MASM 6.0 사용법 5/8
------------------------------------------------------------------------------
[MASM6.0/5] 고급언어적 지시어들..
먼저 강좌가 늦어진 점에대해 죄송하게 생각합니다. 그럼 요번강좌
에 서는 MASM 6.0에서 획기적으로 나아졌다고 할수 있는 고급언어적
인 지시어들에 대해 설명을 해보겠습니다. 이 기능으로 말미암아 어
셈 프로그래밍이 한결 편해졌으며 소스를 보는 것이 매우 쉬어진,
그야말로 MASM6.0에서 가장 맘에 드는 변화일 것입니다. 간단하게
기능을 설명을 하자면 마치 고급언어(특히 C)적인 명령어를 제공하게
된 것입니다. 즉 while, if, else, repeat 등의 기능과 비슷한 기능을
이용할 수 있게 된 것입니다. 그럼 하나 하나의 기능들을 살펴 보기로
합시다.
1. 비교 판단 지시어
-->비교 판단 지시어로는 .IF, .ELSEIF, .ELSE 지시어가 있습니다.
구문을 살펴보면..
.IF 조건1
명령문
[.ELSEIF 조건2
명령문]
[.ELSE
명령문]
.ENDIF
여기서 []는 그 안에 있는 것은 생략될 수 있음을 말합니
다. 예를 들어서 살펴보면..
예제)
.IF cx == 20
mov dx, 20
.ELSE
mov dx, 30
.ENDIF
만약 위와 같은 프로그램은 실제로 어셈 명령어로 바뀔 때 다음과
같이 번역됩니다.
.IF cx == 20
cmp cx, 014h ; 위의 고급언어적
jne @c0001 ; 지시어가 프로세서
mov dx, 20 ; 명령어로 바뀐 코드
.ELSE
jmp @c0003 ; ""
@c0001:
mov dx, 30 ; ""
.ENDIF
@c0003:
비교에 보시면 아시겠지만 고급 언어적인 지시어를 사용해서 프
로그래밍을 하면 훨씬 편하며 보기도 쉬움을 알 수 있을 것입니
다.
2. 루프 지시어
-->고급 언어에서 처럼 루프를 실행할 수 있는 새로운 지시어가 생겼
는데, 고급언어의 WHILE물 같은 .WHILE, .ENDW, REPEAT문의 역할
을 하는 .REPEAT, .UNTIL, .REPEAT, .UNTILCX 등이 있습니다. 그러
나, 이런 새로운 지시어를 사용함에 있어서 새겨두어야 할 것이 있
는데..
1. 위의 지시어들은 새로운 명령어가 아니라 적당하게 프로세서 명
령어들로 바뀌어지는 것입니다.
2. 위의 루프 지시어들은 조건을 가지고 판단하기 때문에 부호있는
데이타의 선언에 주의를 해야 합니다.
2번을 부가 설명을 하자면, 조건을 底은疵 평가함에 있어서 그
조건에 있는 오퍼랜드가 부호가 있는가의 여부를 알아야 합니다.그
것을 확실히 해주지 않으면 예상하지 않은 결과가 나올 수 있기 때
문입니다. 구체적으로 조건이 음수를 담고 있는 메모리를 참조하고
자 할때는 그 메모리를 할당을 할때 새로생긴 지시어인 SBYTE
(signed bytes), SWORD (signed words), SDWORD (signed double
words)를 사용해야 하거나 PTR 오퍼레이터를 써야 합니다.
2.1 .WHILE 문
-->.WHILE문을 구현하려면 두 지시어 .WHILE, .ENDW을 사용합니다.
여기서 .ENDW은 WHILE문의 끝을 알리는 데 쓰입니다.
구문)
.WHILE 조건
명령문
.ENDW
한가지 예를 들어봅시다. 다음 예제는 한 버퍼에서 다른 버퍼까
지 데이타를 카피하는 것인데 데이타의 끝을 나타내는데 달러
마크($)가 사용됩니다.
.DATA
buf1 BYTE "This is a string", '$'
buf2 BYTE 100 DUP (?)
.CODE
sub bx, bx ;BX를 0으로
.WHILE (buf1[BX] != '$')
mov al, buf[bx] ; 한문자를 읽음
mov buf2[bx], al ; buf2에 카피
inc bx ; 다음 문자
.ENDW
2.2 .REPEAT 문
-->.REPEAT문은 C에서 do문, 파스칼에서 repeat문과 같은 역할을
하는 것입니다. 지시어는 .REPEAT 과 .UNTIL(또는 UNTILCXZ)을
사용합니다.
구문)
.REPEAT
명령문
.UNTIL 조건
.REPEAT
명령문
.UNTILCX [조건]
예를 보면 확실히 이해가 갈겁니다. 다음 예제는 키보드로 입력
을 받아서 버퍼에 넣어주는 예제입니다. 엔터를 누르면 루프는
끝이 나도록 되어 있습니다.
.DATA
buffer BYTE 100 DUP (?)
.CODE
sub bx, bx
.REPEAT
mov ah, 01h
int 21h ; 키보드로 부터 입력
mov buffer[bx], al ; 버퍼에 저장
inc bx ; 다음 버퍼 포인트
.UNTIL (al == 13) ; 엔터일때 까지 루프
여기서 .UNTIL 지시어는 비교 점프 명령어를 생성하고
.UNTILCXZ는 loop 명령어를 생성합니다. 다음의 예제는 지시어
를 명령어 코드로 바꾸어 표시한 것인데 이를 살펴보면 이해가
갈 것입니다.
ASSUME bx:PTR SomeStruct
.REPEAT
@C001:
incax
.UNTIL ax == 6
cmp ax, 06h ;비교 점프 명령어로
jne @C001 ;바뀜
.REPEAT
@C003:
mov ax, 1
.UNTILCXZ
loop @C003 ;루프 명령어로 바뀜
.REPEAT
@C004:
.UNTILCXZ [bx].field != 6 ; 루프 명령어로
cmp [bx].field, 06h ; 바뀜
loope @C004 ;
2.3 .BREAK, .CONTINUE 문
-->.BREAK와 .CONTINUE문은 .REPEAT 이나 .WHILE문에서 루프를 빠
져 나가거나 계속 루프를 돌릴 때 쓰입니다. 마치 C언어에서
break와 continue와 비슷합니다.
구문)
.BREAK [.IF 조건]
.CONTINUE [.IF 조건]
이 구문들에서 주의할 것은 .IF다음에 .ENDIF가 쓰이지 않는 것
입니다. 당연하죠 보통의 .IF문처럼 조건이 맞으면 그 아래에
있는 명령어가 수행되는 것이 아니라 그냥 루프를 빠져 나오거
나 다시 루프를 시작할 때 쓰이니까요. 그럼 예제를 보면서 살
펴봅시다. 다음 예제는 '0' 부터 '9'사이의 키만 받아들이고 그
문자를 화면에 내보냅니다. 그 외의 다른 문자가 들어오는 경우
는 계속 키를 입력을 받습니다. 그리고 엔터가 들어면 끝이나게
되어있습니다.
.WHILE 1 ; 무한 루프
mov ah, 08h
int 21h ; get key without echo
.BREAK .IF al == 13 ; 엔터면 루프를 나감
.CONTINUE .IF (al<'0') || (al>'9')
; 숫자가 아니면 다시 루프
mov dl, al
mov ah, 02h
int 21h ; 문자를 화면에 내보냄
.ENDW
만약 위의 예제를 /Fl, /Sg 옵션을 써서 어셈블을 하고 리스팅
파일을 살펴보면 다음과 같이 나옵니다.
.WHILE 1 ; 무한 루프
0017 *@C0001:
0017 B4 08 mov ah, 08h
0019 CD 21 int 21h ; get key without echo
.BREAK .IF al == 13 ;엔터면 루프를 나감
001B 3C 0D * cmp al, 00Dh
001D 74 10 * je @C0002
.CONTINUE .IF (al<'0') || (al>'9')
001F 3C 30 * cmp al, '0'
0021 72 F4 * jb @C0001
0023 3C 39 * cmp al, '9'
0025 77 F0 * ja @C0001
; 숫자가 아니면 다시 루프
0027 8A D0 mov dl, al
0029 B4 02 mov ah, 02h
002B CD 21 int 21h ; 문자를 화면에 내보냄
.ENDW
002D EB E8 * jmp @C0001
002F *@C0002:
주의 : 칼럼 제한으로 리스팅 파일을 일부 고쳤습니다.
3. 조건에 대해서
-->.IF, .REPEAT, .WHILE 지시어의 조건을 씀에 있어서 관계형 오퍼레
이터나 속성을 나타내는 PTR을 쓸 수 있습니다. 여기서는 조건에
쓰이는 각종 오퍼레이터와 오퍼랜드, 속성, 우선순위에 대해 알아
보도록 합시다.
3.1 오퍼레이터에 대해서
-->조건문에 쓰이는 오퍼레이터는 C의 오퍼레이터와 똑같습니다.
오퍼레이터 의미
---------- ----
> 크다.
>= 크거나 같다.
< 작다
<= 작거나 같다.
& bit test
! logical NOT
&& logical AND
|| logical OR
오퍼레이터가 없는 조건문은 c언어에서와 같이 다룹니다. 즉 예
를 들어서 .WHILE (x)는 .WHILE(x != 0)과 같고, .WHILE(!x)는
.WHILE (x == 0)과 같습니다.
또한, 플랙 이름(ZERO?, CARRY?, OVERFLOW, SIGN?, PARITY?)을
써서 조건을 나타 낼 수도 있는데 즉 .WHILE (CARRY?)같이 쓸
수 있습니다.
3.2 Signed, Unsigned 오퍼랜드에 대해
-->조건에 쓰일 수 있는 오퍼랜드는 레지스터, 상수, 메모리가 될
수 있습니다. 기본적으로 조건문에 있는 오퍼랜若 unsigned로
가정을 합니다. 그런데 만약에 비교되는 오퍼랜드가 singed라면
문제가 생기게 됩니다. 이럴땐 PTR 오퍼레이터로 특정한 오퍼랜
드가 singed인 것을 알려줄 수 있습니다. 예를 들어서..
.WHILE SWORD PTR [bx] <= 0
.IF SWORD PTR mem1 > 0
만약 여기서 PTR 오퍼레이터가 안쓰였다면 bx의 내용을
unsigned로 가정하고 명령어를 생성했을 것입니다.
위에서 말했다시피 메모리 오퍼렌드의 속성을 지정할 수도 있습
니다. 즉..
.DATA
mem1 SBYTE ?
mem2 WORD ?
.IF mem1 > 0
.WHILE mem2 < bx
.WHILE SWORD ptr ax < count
위의 예제에서는 메모리를 할당을 할 때 미리 SBYTE라고 알려주
었기 때문에 전의 예제처럼 SWORD PTR을 쓸 필요가 없습니다.
3.3 우선 순위에 대해
-->C언어처럼 &&, ||, !으로 조건들을 연결을 해서 쓸 수 있는데
이렇게 쓸 때 우선순위는 !,&&, ||순입니다. 또한 C처럼 왼쪽
에서 오른쪽으로 조건을 살핍니다.
오늘 강좌에서 보았듯이 MASM 6.0에서는 마치 고급언어(특히 C)와
비슷한 구문을 제공하여 프로그래밍을 매우 편하게 그리고 소스를 보
기 쉽게 만들어 주고 있습니다. 다음 강좌에서는 프로시져에 대한 일
반적인 얘기와 MASM 6.0에서 나아진 프로시져 지시어들에 대해서 알아
보도록 합시다.
───────────────────────────────────────
제목: [강좌] MASM 6.0 사용법 6/8
------------------------------------------------------------------------------
[MASM6.0/6] 확장된 프로시져 기능(1)
요번 강좌에서는 프로시져 지시어를 이용해서 효과적으로 파라메터를
주고받는 방법과 파라메터의 수가 가변적인 것을 다루는 방법에 대해서
얘기해 보도록 하겠습니다.
1.1 프로시져에 인수(argument)를 전달하기
프로시져에 인수를 전달하는 방법은 여러 가지 방법이 있는데, 예를
들면 레지스터를 통해서 전달하는 방법과 global 변수를 통해 전달
하는 방법 등이 있다. 또한 고급언어에서 쓰는 방법으로 가장 일반
적인 전달 방법으로 쓰이는 것은 스택을 통해서 전달하는 것이다.
그러나 인수를 스택에 전달하는 방법도 언어에 따라서 다른데 이렇
게 스택을 이용하여 인수를 전달하는 관행을 calling convention(호
출 관행)이라고 한다.
다음의 예는 c 스타일의 호출 관행에 따라 프로시져를 정의하고
스택을 통하여 인수를 전달하는 부분이다.
C에서 addup이라는 3개의 인수들을 받아 그 인수들을 모두 더해서
값으로 돌려주는 함수를 생각하자. c 프로그램에서 addup ( arg1
,arg2, 10)이라고 함수를 호출하면 아래와 같은 어셈블리로 번역될
것이다. 단 arg1, arg2를 word크기의 변수라고 생각을 하자(즉 int
와 같이 2 byte 크기)
mov ax, 10
push ax ;세번째 인수를 스택에 저장
push arg2 ;두번째 인수를 스택에 저장
push arg3 ;첫번째 인수를 스택에 저장
call addup ;함수를 호출
add sp, 6 ;인수의 스택 프레임을 없앰
...
...
addup PROC NEAR ;near형 프로시져 이므로 스택에
;어드레스 저장으로 2byte를 차지
push bp ;base pointer 를 저장 (2bytes)
mov bp, sp ; bp=sp
mov ax, [bp+4] ; 첫번째 인수를 읽어드림
add ax, [bp+6] ; 두번째 인수랑 더함
add ax, [bp+8] ; 세번째 인수랑 더함
mov sp, bp
pop bp
ret ;결과값을 전달해줌
addup ENDP
위의 소스를 가지고 스택이 어떻게 변화되는가를 그림으로 설명을
하면 다음과 같다.
1. call addup 하기전 2. call addup 후
상위 | | 상위 | |
메모리 +-----------+ 메모리 +-----------+
|세번째 인수| |세번째 인수|
+-----------+ +-----------+
|두번째 인수| |두번째 인수|
+-----------+ +-----------+
|첫번째 인수|<-SP |첫번째 인수|
+-----------+ +-----------+
| | | 리턴 주소 |<-SP
+-----------+ +-----------+
하위 | | 하위 | |
메모리 +-----------+ 메모리 +-----------+
| | | |
3. push bp 4. pop bp 한 후
mov bp, sp 한후
상위 | | 상위 | |
메모리 +-----------+ 메모리 +-----------+
|세번째 인수|<-BP+8 |세번째 인수|
+-----------+ +-----------+
|두번째 인수|<-BP+6 |두번째 인수|
+-----------+ +-----------+
|첫번째 인수|<-BP+4 |첫번째 인수|
+-----------+ +-----------+
| 리턴 주소 | | 리턴 주소 |<-SP
+-----------+ +-----------+
하위 |이전 BP 값 |<-BP/SP 하위 | |
메모리 +-----------+ 메모리 +-----------+
| | | |
5. ret 한 후 6. add sp, 6 한 후
상위 | | 상위 | |->SP
메모리 +-----------+ 메모리 +-----------+
|세번째 인수| | |
+-----------+ +-----------+
|두번째 인수| | |
+-----------+ +-----------+
|첫번째 인수|<-SP | |
+-----------+ +-----------+
| | | |
+-----------+ +-----------+
하위 | | 하위 | |
메모리 +-----------+ 메모리 +-----------+
| | | |
좀더 부연 설명을 하자면, 위에서 보다시피 c의 호출 관행에서는
인수의 마지막 것부터, 즉 위의 소스에서는 상수(10)를 먼저 스택에
저장을 하고 그리고 두번째, 첫번째 이렇게 거꾸로 스택에 인수를
저장을 한다. 그러나 파스칼은 이와는 달리 순행으로 스택에 인수를
저장한다. 그리고 c에서는 위의 예제와 같이 호출한 쪽에서 인수를
위한 스택 프레임을 없애기 때문에(위의 소스에서 add sp, 6) 인수
의 갯수를 가변적으로 할 수 있으나 파스칼은 호출된 쪽, 즉 프로시
져 자체에서 인수를 위한 스택 프레임을 없애기 때문에 가변적인 인
수를 가질 수 없다. 이렇듯 언어마다 호출관행이 다르므로 나중에
혼합 언어 프로그래밍을 할때 이러한 점을 잘 고려하여 프로그램을
작성해야 한다.
1.2 프로시져 정의하기
PROC 지시어를 사용해서 프로시져를 정의할 때에는 저장되어야할 레
지스터들과 프로시져에서 받는 파라메터들을 정의할 수 있다. 이전
버젼과 달리 파라메터들을 프로시져 정의할 때 정의함으로써 편하게
파라메터들을 이용할 수 있다. 그럼 예를 들어서 C 형식의 프로시져
를 정의하면 다음과 같다.
myproc PROC FAR C PUBLIC USES di si, var1:WORD, arg1:VARARG
| | | | | | | |
+--+-+ +----+-----+ +---+----+ +---+----+
| | | |
label attributes reglist parameters
일반적으로 프로시져의 구문을 살펴보면..
label PROC [attributes] [USES reglist] [,parameter[:tag]...]
구성 요소 설 명
--------- ------------
label 프로시져의 이름
attributes distance와 언어 종류, visibility와 같
은 속성을 나타낸다.
reglist USES 지시어에 따르는 레지스터들은 프로
시져의 시작부분에 저장된다. 레지스터
들은 빈칸이나 탭으로 구분되어야 하며
콤마로 구분되면 안된다. USES 지시어가
오면 자동적으로 어셈블러가 프로시져의
앞부분(프롤로그)에는 push 명령어로 레
지스터들을 스택에 보관하고 프로시져의
끝부분(에필로그)에는 pop 명령어로 다시
저장된 레지스터 값을 복귀한다. 프로시
져가 레지스터의 내용을 바꾸게 될 때
USES reglist를 사용함으로써 프로시져가
호출된 쪽으로 복귀해도 레지스터의 내용
이 바뀌지 않도록 할 때 사용한다.
parameter 스택을 통해서 프로시져에전달되는 파라
메터 리스트들. 리스트가 한 줄을 넘어서
게되면 줄 끝에 콤마를 하고 다음줄에 연
결하여 나타낼 수 있다.
1.2.1 Attributes
프로시져의 attribute 구문은 다음과 같다.
[distance] [langtype] [visibility] [<prologuearg>]
구성요소 설 명
--------- --------
distance RET 명령어의 형태(retn, retf)를 결정하게 되는
요소이다. NEAR나 FAR 둘중의 하나가 될 수 있
다. 만약에 distance가 기술되지 않았다면
.MODEL에 의해서 결정된다. TINY, SMALL,
COMPACT, FLAT은 NEAR가 되고 MEDIUM, LARGE,
HUGE는 FAR가 된다.
langtype 위에서 언급한 호출관행(calling convention)을
결정하게 되는 요소이다. BASIC, FORTRAN,
PASCAL은 프로시져의 이름을 대문자로 변환하고
마지막 파라메터가 스택의 가장 처음에 위치하게
된다. (가장 낮은 메모리에 위치하게 된다.) RET
n 명령어를 통해서 스택프레임을 프로시져에서
없애게 되어 가변적인 파라메터를 구현할 수 없
다.
C나 STDCALL은 프로시져의 scope가 PUBLIC이거나
EXPORT일 경우는 프로시져 이름의 맨 앞부분에
밑줄('_')을 붙이고 첫번째 파라메터가 스택의
가장 처음에 위치하게 된다. (가장 낮은 메모리
에 위치하게 된다.) SYSCALL은 C의 호출관행과
똑같은데 다만 프로시져 이름의 앞부분에 밑줄을
붙이지 않는다. STDCALL은 프로시져를 호출한 쪽
에서 스택 프레임을 없애기 때문에 파라메터의
수가 가변적일 수 있다.
visibility 프로시져가 다른 모듈에서 사용 가능한가를 나타
낸다. visibility에는 PRIVATE, PUBLIC, EXPORT
가 될 수 있다. 프로시져는 PRIVATE이라고 명시
되지 않은 이상 기본적으로 PUBLIC로 간주된다.
만약에 visibility가 EXPORT인 경우는 linker가
프로시져의 이름을 export table에 올린다. 당연
히 EXPORT는 PUBLIC을 포함하게 된다. OPTION 지
시어를 써서 default visibility를 정할 수 있
다. OPTION PROC:PUBLIC 이라고 하면 프로시져의
기본 visibility는 PUBLIC이 된다.
prologuearg 프로시져의 처음과 끝부분의 코드 즉 prologue와
epilogue 코드를 생성하는 데 영향을 주는 요소
이다. 나중에 설명할 것이다.
1.2.2 Parameters
만약 PROC 지시어에 reglist가 존재하면 레지스터 리스트와 파
라메터는 콤마로 구분된다.
파라메터의 구문을 살펴보면..
parmname[:tag]
parmnane은 파라메터의 이름이고 tag는 BYTE나 WORD와 같은 타
입이나 가변 파라메터를 나타내는 키워드 VARARG가 될 수 있
다. VARARG 키워드는 항상 파라메터의 리스트의 맨끝에 와야한
다.
Parmname -------+ +-- Qualifiedtype
EX) | |
myproc PROC FAR C PUBLIC USES di si, var1:WORD, arg1:VARARG
레지스터 리스트와 | | | |
콤마로 구분된다. -----------+ +---+---+ |
| |
Parameters |
VARARG는 파라메터의 -------+
맨끝에 온다.
다음의 예는 위의 1.1의 예제 중에서 프로시져 정의부분을 파
라메터 리스트를 나열하는 기능을 이용하여 다시 고쳐 쓴 것이
다. 역할은 1.1.의 프로그램과 똑같으나 훨씬 보기쉽고 프로그
래밍도 쉬워진다는 것을 알 수 있다.
addup PROC NEAR C,
arg1:WORD, arg2:WORD, count:WORD
mov ax, arg1
add ax, count
add ax, arg2
ret
addup ENDP
만약에 프로시져의 argument가 포인터 타입이라면 위의 예제와
같이 단순히 mov 명령어 하나로 포인터가 가르키는 곳의 값을
얻을 수 없다. 만약에 그 값을 얻으려면 포인터 타입의
argument를 어드레스로 간주하고 값을 얻어 와야 한다. 만약에
포인터가 near 포인터라면 mov 명령어를 두번 써서 그 값을 구
할 수가 있다. 처음 mov 명령어는 파라메터의 어드레스를 얻어
오고 두번째 mov 명령어는 실제 파라메터의 값을 얻어오는 것
이다. 다음의 예제를 보면 쉽게 이해할 것이다.
; Call from C as a FUNCTION returning an integer
.MODEL medium, c
.CODE
myadd PROC arg1:NEAR PTR WORD, arg2:NEAR PTR WORD
mov bx, arg1 ; argument의 주소를 얻는다.
mov ax, [bx] ; 실제 값을 얻는다.
mov bx, arg2
add ax, [bx] ; 두번째 값과 더한다.
ret
myadd ENDP
END
만약에 포인터 타입이 near인지 far인지 잘 모를 경우나 여러
모델에 상관없이 값을 제대로 얻으려면 conditional-assembly
지시어(directive)를 이용하여 해결할 수 있다. 아래 예제는
위의 myadd라는 프로시져가 far나 near에 상관없이 포인터가
가르키는 값을 얻어서 제대로 수행할 수 있게 고친것이다.
.MODEL medium, c ; 아무 모델이나 상관없다.
.CODE
myadd PROC arg1:PTR WORD, arg2:PTR WORD
IF @DataSize
les bx, arg1 ; far인 파라메터
mov ax, es:[bx]
les bx, arg2
add ax, es:[bx]
ELSE
mov bx, arg1 ; near인 파라메터
mov ax, [bx]
mov bx, arg2
add ax, [bx]
ENDIF
ret
myadd ENDP
END
NOTE : 위의 예제에서 @DataSize는 메모리 모델이 tiny,
small, medium, flat일 경우는 0, compact, large는
1, huge는 2의 값을 주는 predefined symbol이다. 위
의 예제에서는 @DataSize의 값이 0인 경우는 데이타가
near에 위치하고 그 외에는 far에 위치함을 이용한 것
이다.
1.2.3 파라메터의 수가 가변적인 경우( Using VARARG )
위에서 언급한 것과 같이 호출관행이 C, SYSCALL, STDCALL인 경우
는 PROC 지시어를 이용하여 파라메터의 맨끝에 VARARG를 이용함으
로써 파라메터의 수가 가변적인 것도 받아 들일 수가 있다. 다음
의 예제를 살펴보자.
addup3 PROTO NEAR C, argcount:WORD, arg1:VARARG
invoke addup3, 3, 5, 2, 4
ddup3 PROTO NEAR C, argcount:WORD, arg1:VARARG
sub ax, ax ; ax=0
sub si, si ; si=0
.WHILE argcount > 0
add ax, arg1[si]
inc si
inc si
dec argcount
.ENDW
ret
addup3 ENDP
예제의 둘째줄의 invoke는 MASM 6.0에서 처음으로 생긴 프로세
서 명령어가 아닌 어셈블러 지시어인데 프로시져를 호출할 때 파
라메터까지 넘겨주는 기능과 또 다른 여러가지 기능으로 마치 고
급언어에서 함수를 호출하도록 할 수 있도록 하였다. 이에 대해서
는 다음 강좌 때 설명하기로 하겠다.
위의 addup3라는 프로시져는 3개의 숫자를 더하는 프로시져이
다. 3개의 숫자는 파라메터로 받는데 VARARG를 써서 파라메터의
수를 가변적으로 받고 있다. invoke addup3, 3, 5, 2, 4라고 하
면 addup3라는 프로시져를 부르게 되며 처음에 나오는 3은
argcount라는 파라메터 값으로 들어가게 된다. addup3 프로시져에
서 argcount 값은 가변적인 파라메터의 갯수를 나타내는 데 첫번
째 3은 뒤 나오는 더해질 숫자(5, 2, 4)들의 갯수를 나타내는
것이다. addup3 프로시져에서는 .WHILE 루프에서 add ax,
arg1[si]으로 각 파라메터를 더하게 되어 있으며 si+2를 함으로써
(위의 inc si 명령어 2개) 다음 파라메터를 읽어올 수 있게 되어
있다. 루프의 종료 조건으로 argcount를 매 루프마다 1씩 줄임으
로써 파라메터의 갯수를 가변적으로 할 수 있게 했다.
위의 예제에서 invoke addup3, 5, 1, 2, 3, 4, 5라고 하면 1부터
5까지 다섯개의 숫자를 더하게 됨을 볼 수 있다.
───────────────────────────────────────
제목: [강좌] MASM 6.0 사용법 7/8
------------------------------------------------------------------------------
[MASM6.0/7] 확장된 프로시져 기능(2)
고급언어에서 지역 변수(local variables)는 대부분 스택에 저장된다.
어셈블리 언어에서도 지역 변수를 구현할 수 있는데, 맨처음에는 기본적
으로 어셈블리 언어에서 지역 변수를 구현하는 방법과 다음에는 LOCAL
지시어를 이용하여 자동적으로 지역 변수를 만들고 이용하는 방법에 대
해 알아보겠다.
1.1 기본적인 지역 변수 사용법
우선 지역 변수를 사용하려면 프로시져의 시작 부분에 지역 변수를 위
한 공간을 스택에 마련해야 한다. 그런다음 각각의 지역 변수는 스택
에 존재하는 위치를 통해서 참조할 수 있다. 프로시져의 끝에는 스택
포인터를 적절히 바꾸어 줌으로써 지역 변수를 위해 할당된 스택 공간
을 없애주어야 한다.
다음의 예재는 지역 변수를 위해서 스택에 공간을 마련하는 것과 base
pointer를 통해서([bp-2]) 지역변수를 참조하는 방법을 보여주고 있
다.
push ax ; 인수를 스택에 저장
call task
...
...
task PROC NEAR
push bp ; save base pointer
mov bp, sp ; bp<--sp
sub sp, 2 ; 지역 변수를 위해 스택
; 공간을 확보
...
...
mov WORD PTR [bp-2], 3 ; 지역 변수를 3으로
; 초기화한다.
add ax, [bp-2] ; 지역변수를 ax에 더함
sub [bp+4], ax ; 인수에서 ax를 뺌
...
...
mov sp, bp ; 로컬 변수를 위한 공간을
; 없앤다.
pop bp ; restore base pointer
ret 2
task ENDP
위의 예제 끝부분에서 mov sp, bp함으로써 SP의 원래값으로 바꾸어주
고 있다. mov sp, bp는 sp의 내용이 프로시져안에서 바뀔 때 필요하게
되는데 일반적으로 지역 변수를 할당할 때 위에서 sub sp, 2와 같이
sp의 내용이 바뀌게 되므로 필요하게 되는 것이다. ret 2에서 2는상
수로써 스택 포인터를 상수만큼 더함으로써 스택 프레임을 없애는 역
할을 한다. 바로 전 강좌에서 언급했듯이 PASCAL 같은 언어는 이렇게
프로시져 안에서 ret n(n은 상수)를 이용해서 인수를 위한 스택 프레
임을 없애게 되어 가변 파라메터를 사용할 수 없게 된다. 이해가 잘
안되면 바로 전 강좌를 살펴보기 바란다.
예제의 명령문에 따른 스택의 상태를 그림으로 보면 쉽게 이해가 갈
것이다.
1. call task 하기전 2. call task 후
상위 | | 상위 | |
메모리 +-----------+ 메모리 +-----------+
| 인 수 |<-SP | 인 수 |
+-----------+ +-----------+
| | | 리턴 주소 |<-SP
+-----------+ +-----------+
| | | |
+-----------+ +-----------+
| | | |
+-----------+ +-----------+
하위 | | 하위 | |
메모리 +-----------+ 메모리 +-----------+
| | | |
3. push bp 4. sub sp, 2 한 후
mov bp, sp 한후
상위 | | 상위 | |
메모리 +-----------+ 메모리 +------------+
| 인 수 |<-BP+4 | 인 수 |<-BP+4
+-----------+ +------------+
| 리턴 주소 | | 리턴 주소 |
+-----------+ +------------+
| 이전 BP값 |<-BP/SP | 이전 BP값 |<-BP
+-----------+ +------------+
| | |지역변수공간|<-BP-2
+-----------+ +------------+
하위 | | 하위 | |
메모리 +-----------+ 메모리 +------------+
| | | |
5. mov sp, bp 6. ret 2 한 후
pop bp 한후
상위 | | 상위 | |->SP
메모리 +-----------+ 메모리 +-----------+
| 인 수 | | |
+-----------+ +-----------+
| 리턴 주소 |<-SP | |
+-----------+ +-----------+
| | | |
+-----------+ +-----------+
| | | |
+-----------+ +-----------+
하위 | | 하위 | |
메모리 +-----------+ 메모리 +-----------+
| | | |
1.2 LOCAL 지시어를 통한 지역 변수 사용
LOCAL 지시어를 사용하면 지역 변수를 사용하는 것이 훨씬 쉽고 시간
도 절약되며 프로그램이 보기에도 쉬워진다. 이 지시어를 사용하여 지
역 변수의 이름과 타입을 주면 어셈블러가 자동으로 스택에 얼마만큼
의 공간이 필요한지를 계산하고 그에 따라서 그만큼의 공간을 확보하
기위해 SP를 적절히 맞추어주는 명령어를 생성한다. 그리고 프로시져
의 끝, 즉 프로시져가 리턴하기 전에 SP를 다시 원래의 값을 되돌려
주는 명령어를 생성하게 된다. 즉, 위의 1.1의 예제에서 프로시져의
맨처음과 끝의 명령어들('...'으로 양끝을 구분했음)을 자동적으로 생
성하게 된다. 그럼 LOCAL 지시어를 사용하여 1.1의 예제를 다시 고쳐
써보자.
task PROC NEAR arg:WORD
LOCAL loc:WORD
...
...
mov loc, 3 ; 지역 변수를 3으로
; 초기화한다.
add ax, loc ; 지역변수를 ax에 더함
sub arg, ax ; 인수에서 ax를 뺌
...
...
ret
task ENDP
LOCAL 지시어는 PROC 지시어 바로 다음에 와야되며 LOCAL 지시어 전에
어떠한 명령어도 오면 안된다. LOCAL 지시어의 구문은 다음과 같다.
LOCAL vardef[,vardef]...
각각의 vardef는 지역 변수를 정의하게 되는데 다음과 같이 구성된다.
label [[count]][:qualifiedtype]
구성 요소 설 명
------------ ----------
label 지역 변수의 이
count 주어진 label로 몇개를 할당할 것인가를 나
타낸다. 쉽게 말해서 배열과 같은 기능을 하
는 것이다. count의 양쪽에 '[]'을 표시해야
한다. count가 명시되지 않으면 하나의
element로 간주한다.
qualifiedtype WORD, BYTE와 같은 타입을 말한다.
지역 변수 리스트가 한줄을 넘어서게 되면 줄의 끝에 콤마를 찍고
다음줄에 계속 나열하거나 다시 LOCAL 지시어를 쓰면 된다.
어셈블러는 지역 변수를 초기화하지 않으므로 따로 초기화를 하는
프런瀏 코드를 넣어주어야 한다. 다음의 예제는 LOCAL 지시어를
이용하여 배열형의 지역 변수를 생성하고 0으로 초기화하는 예제이
다.
arraysz EQU 20
aprocPROC USES di
LOCAL var1[arraysz]:WORD, var2:WORD
...
...
; 지역 변수를 0으로 초기화한다.
push ss
pop es ; ss=es
lea di, var1 ; ES:DI는 array를 가르키게
; 된다.
mov cx, arraysz ; 카운트
sub ax, ax
retp stosw ; 0으로 초기화
...
...
ret
aproc ENDP
스택에 존재하는 지역 변수를 지역 변수의 이름으로 참조를 하더래
도 어셈블러는 지역 변수를 BP로부터의 상대적 위치로 간주하게 된
다. 그러므로 다른 프로시져는 이 지역 변수를 참조할 수 없게 된
다. 그런데 지역 변수를 사용할 때 주의할 점이 있는데 그것은 지역
변수가 BP에 대한 상대적 위치로 나타내어지므로 BX랑은 결합이 될
수 없다는 점이다. BX와 BP는 SI와 DI랑 결합할 수 있다. 즉
[BP+BX+2]와 같은 표현은 안되고 [BP+SI+2]나 [BP+DI+2]는 가능하
다.
다음의 예제를 살펴보고 무엇이 잘못ed낮나 알아보자.
index EQU 10
test PROC NEAR
LOCAL array[index]:WORD
...
...
mov bx, index
; mov array[bx], 5 ; 잘못된 문장
위의 예제에서 array[bx]는 [bp+bx+arrayoffset]같이 bp와 bx가 결
합하므로 잘못된 것이다. 이러한 에러는 지역 변수가 bp의 상대적
위치로 표현된다는 것을 잘 모르면 찾아내기 어려우므로 이와 같은
점을 알아둬야한다.
───────────────────────────────────────
제목: [강좌] MASM 6.0 사용법 8/8
------------------------------------------------------------------------------
[MASM6.0/8] 확장된 프로시져 기능(3)
저번 강좌에서 설명했듯이 호출 관행(calling convention)에 따라서
파라메터를 스택에 넣는 방식은 달라지게 된다. 그러나 앞으로 설명하
게될 INVOKE 지시어를 이용하면 호출 관행에 상관없이 자동적으로 알맞
게 파라메터를 스택에 넣어주고, 프로시져 호출에 관련된 여러가지 일
들을 관리해주는 기능을 한다.
INVOKE 지시어를 이용하려면, 먼저 PROC 지시어로 프로시져가 정의되
어야 한다. 또한, C언어에서와 같이 함수의 프로토타입(prototype)을
앞에다 정의할 수 있는데, 이때는 PROTO라는 지시어를 이용하게된다.
이는 어셈블러에게 함수가 필요로하는 인수의 갯수와 타입을 알려주어
나중에 INVOKE로 함수가 호출되었을 때 건내받는 인수의 갯수와 타입을
자동적으로 검사하도록 한다.
1.1 프로시져의 프로토타입(prototype) 정의하기
위에서 말했듯이 MASM 6.0에서의 프로토타입은 C 언어나 다른 고급
언의 프로토타입과 비슷한 기능을 하게된다. 프로시져의 프로토타
입은 프로시져의 이름, 타입, 그리고 옵션으로 파라메터의 모든 이
름을 넣을 수가 명시할 수 있다. 프로토타입은 대체적으로 프로그
램의 맨 처음부분이나 따로 분리된 인클루드(include) 파일에 위치
한다. 프로토타입은 특히 다른 모듈이나 다른 언어에서 프로시져가
호출될 경우 유용한데, 이는 어셈블러가 타입이나 갯수가 맞지 않
는 인수들을 체크할 수 있도록 하기 때문이다.
프로시져의 프로토타입을 정의하는 것은 옵션으로 C처럼 꼭 해야하
는 것은 아니다. 그러므로, MASM 5.X이하의 버젼 방식인 PROC과
CALL이용하여 프로시져를 호출, 이용할 수 있다.
프로토타입의 정의 구문은 프로시져 정의 구문에서 레지스터 리스
트와 prologuearg 리스트, 프로시져의 scope를 제외하고는 동일하
다. 또한, 프로토타입은 프로시져와 같이 언어종류(langtype)와 거
리(distance)를 나타내는 속성을 정의할 수 있는데 이 속성들이 생
략되면 .MODEL 이나 OPTION LANGUAGE 명령문에 준거하여 정해진다.
또한 프로시져에서 정의되는 :VARARG는 옵션으로, 써도되고 안써도
된다.
한 함수에 대해, PROC문과 PROTO문이 같이 나오면, 둘의 속성과 파
라메터의 갯수, 파라메터의 타입이 일치되어야 한다. 프로토타입을
정의하는 가장쉬운 방법은 에디터로 PROC문을 카피한 후 PROC을
PROTO로 바꾸어주고 USES reglist와 prologuearg, visibility를 지
우는 것이다.
예)
; Procedure prototypes
addup PROTO NEAR C, argount:WORD, arg2:WORD, arg3:WORD
myproc PROTO FAR C, argcount:WORD, arg2:VARARG
; Procedure declarations
addup PROC NEAR C, argount:WORD, arg2:WORD, arg3:WORD
myproc PROC FAR C PUBLIC <callcount> USES di, si,
argcount:WORD,
arg2:VARARG
INVOKE로 프로시져를 호출하면, 어셈블러는 INVOKE에서 주어진 인
수와 PROC에서 정의된 인수를 비교하여 체크한다. 만약에 둘 사이
의 타입이 맞이 않으면, MASM은 에라를 내거나, INVOKE로 주어진
타입을 프로시져에서 요구하는 타입으로 맞게 바꾸게 된다.
1.2 INVOKE로 프로시져 호출하기
INVOKE는 인수를 스택에 넣고 프로시져를 호출하는 일련의 프로세
서 명령어를 자동적으로 생성한다. INVOKE는 또한 프로시져를 호출
할 때 다음과 같은 작업을 자동적으로 하게된다.
* 인수를 기대되는 타입으로 바꾸어준다.
* 인수를 올바른 순서로 스택에 넣는다.
* 프로시져가 수행을 마친후 스택 프레임을 없앤다.
INVOKE의 구문은 다음과 같다.
INVOKE expression, [,arguments]
여기서 expression은 프로시져의 이름을 나타낸다. arguments는
12*2 와 같은 expression이거나, 레지스터, 또는 ADDR로 나타내어
지는 exprssion이다.
예)
addup PROTO NEAR C, argount:WORD, arg2:WORD, arg3:WORD
myproc PROTO FAR C, argcount:WORD, arg2:VARARG
addup PROC NEAR C, argount:WORD, arg2:WORD, arg3:WORD
myproc PROC FAR C PUBLIC <callcount> USES di, si,
argcount:WORD,
arg2:VARARG
프로토타입과 프로시져 구문이 위와 같이 주어졌을 때 INVOKE는
다음과 같이 쓸 수 있다.
INVOKE addup, ax, x, y
INVOKE myproc, bx, cx, 100, 10
어셈블러는 타입이 안맞는 인수들을 적당한 타입에 맞추어 바꾸어
줄 수 있다. 예를 들면 위의 addup 프로시져는 WORD형타입을 요구
하지만 아래의 경우도 문제없이 쓸 수 있다.
* BYTE, SBYTE, WORD, SWORD
* 8 비트 또는 16비트 레지스터
* -32K 부터 64K 까지의 상수
* NEAR 포인터
만약에 요구되는 타입보다 넘어오는 인수의 타입이 크기가 작을 경
우, 어셈블러는 넘어오는 타입의 크기를 늘리게 된다.
1.2.1 에라 찾기
어셈블러가 인수의 크기를 늘리려고 할 때, 어떤 레지스터를 이
용하게 되는데, 이는 레지스터값으로 들어오는 인수의 값과 중복
될 수 있어서 이는 에라의 원인이 된다. 예를 들면 다음과 같이
C 언어 타입의 호출이 있다고 하자.
INVOKE myprocA, ax, cx, 100, arg
위에서 arg가 BYTE 크기의 인수이고 myprocA는 다 WORD크기의 인
수를 요구한다고 하면 어셈블러는 BYTE크기의 arg를 WORD 크기로
늘려서 스택에 넣어야 한다. 즉, 다음과 같은 코드를 생성하게
된다.
mov al, DGROUP:arg
xor ah, ah
push ax
C는 저번 강좌에서 말했듯이, 인수를 역순으로 스택에 넣는다.
즉 위의 예에서는 arg, 100, cx, ax 순으로 넣는다. 그러나 위의
코드처럼 arg를 늘리게 될 때 쓰는 레지스터가 ax이므로 맨처음
의 인수, ax는 중복되게 되므로 내용이 달라지게 된다. 이 경우
에는 어셈블러가 에라 메세지를 내게 된다. 이런 경우은 invoke
를 다시 적당히 고쳐주어야 한다.
INVOKE는 되도록이면 인수의 크기를 늘이기 위해 적은 레지스터
를 사용한다. 그러나 인수를 늘리게 되면, 불가피하게 레지스터
를 사용하게 되는데, AX 레지스터를 사용하게 되고, 어쩔때는 DX
레지스터도 사용하게 된다. 결과적으로 AL, AH, AX는 빈번하게
사용되므로 이 레지스터를 이용하여 인수를 넘기는 것은 되도록
이면 삼가하는 게 좋고 많이 쓰이지 않는 DX같은 레지스터를 쓰
는 것이 좋다.
1.2.2 생성된 코드 검사
INVOKE를 이용하면 위와 같이 인수의 크기를 늘이게 되는 작업을
하는 코드를 자동적으로 생성하게 된다. 자동적으로 생성된 코드
를 확인하려면 코맨드 라인(command-line) 옵션에서 /Sg 옵션을
이용해서 리스트 파일을 보면 어떻게 코드가 생성ed낮는지 확인할
수 있다.
강좌를 마치면서...
사실은 강좌를 몇번 더 해야되는데, 제가 곧 군입대를 하게 되어
서 강좌를 이쯤 마치게 되니 죄송하게 되었습니다. 변변치 않은
강좌를 게으르게 쓰느라고 미안한 점도 많네요. 그동안 도움이
되셨는지 모르겠습니다. 송구스런 마음으로 이만 강좌를 마칠까
합니다.
'C/C++언어' 카테고리의 다른 글
함수포인터란 ? (0) | 2007.09.06 |
---|---|
[본문스크랩] 파일 입.출력 (0) | 2007.09.06 |
[본문스크랩] VC++ Article (0) | 2007.09.06 |
어셈블리 초급 기초 #1(정태식님) (0) | 2007.09.06 |
어셈블리 초급 기초 #2(정태식님) (0) | 2007.09.06 |