어셈블리

어셈블리 강좌6

RSMaster 2019. 7. 9. 20:08

6.5 프로시듀어의 배치
--------------------------------------------------------------------------------
PROC ~ ENDP는 사람이 보기 편하기 위한 구분에  불과하다. 어셈블러는 실제로 프로시
듀어를 구별하지 않는다.
예제)CALL2.ASM
CODE    SEGMENT
        ASSUME  CS:CODE
EOF     EQU     1AH

START:  CALL    GETCH
        JC      EXITP  ;--+여기에서 프로시듀어를 호출하고 있다.
        MOV     DL, AL     |
        CALL    PUTCH  ;--+
        JMP     START
EXITP:  MOV     AH,4CH
        INT     21H

GETCH   PROC
        MOV     AH,8
        INT     21H
        CMP     AL, EOF
        JNE     GETCHEND
        STC
        RET
GETCHEND:                 ;긴 이름의 라벨도 사용 가능 그때 라벨만의 행으로 해도 좋다
        CLC
        RET
GETCH   ENDP

PUTCH   PROC
        MOV     AH,2
        INT     21H
        RET
PUTCH   ENDP

CODE    ENDS
        END     START    ;실행 시작을 알리는 문장

END 문이 가지는 기능: 프로그램의 시작 번지를 지정한다.
이와 같은 것은 여러 개의 코드 세그먼트를 가진 프로그램을 작성한다든지 , 프로시듀어와
메인 루틴의 순서를 바꾼 프로그램을 작성하는 경우에는 절대로 필요하다.


예제) CALL2-2.ASM
CODE    SEGMENT
        ASSUME  CS:CODE
EOF     EQU     1AH

GETCH   PROC
        MOV     AH,8
        INT     21H
        CMP     AL, EOF
        JNE     GETCHEND
        STC
        RET
GETCHEND:                 ;긴 이름의 라벨도 사용 가능 그때 라벨만의 행으로 해도 좋다
        CLC
        RET
GETCH   ENDP

PUTCH   PROC
        MOV     AH,2
        INT     21H
        RET
PUTCH   ENDP

START:  CALL    GETCH  ; 메인 루틴의 시작           <--------------------+
        JC      EXITP  ;--+여기에서 프로시듀어를 호출하고 있다.          |
        MOV     DL, AL     |                                              |
        CALL    PUTCH  ;--+                                              |
        JMP     START                                                    |
EXITP:  MOV     AH,4CH                                                   |
        INT     21H                                                      |
                                                                         |
CODE    ENDS                                                             |
        END     START    ;실행 시작을 알리는 문장      >------------------+

 

 


제7장   모듈별 프로그램의 작성법
--------------------------------------------------------------------------------

7.1 INCLUDE의 방법
--------------------------------------------------------------------------------
모듈화의 이점: 프로그램을 기능 별로 분할하여 알기 쉽게 하는 것과 걔 발효율을
               향상할 수 있다
아무리 같은 루틴을 사용할 수 있다고 하더라도 , 다른 프로그램을  만들 때마다. 같은
루틴을 키보드로부터 입력하지 않으면 안 된다는 것은 , 역시 귀찮은 작업입니다.
그래서 완성된 루틴의 부분을 미리 다른 파일로부터 작성해두고, 메인 루틴만을 에디
터로부터 입력하여, 서브루틴인 부분은 나중에 파일 단위로 결합시키는 방법을 생각해
내었습니다. 이것이 어셈블러에 있는 인클루드(INCLUDE) 기능입니다.
이 기능을 사용할 때에는 우선 , 서브루틴 등의 부분을 독립시킨 파일로서 작성해두고 ,
메인 루틴을 작성할 때에 그중 필요한 부분으로 INCLUDE 의사 명령을 사용하여
"INCLUDE 파일 이름 " 으로 한다음 ,결합하는 화일 이름을 지정합니다. 이렇게 해두면
메인 루틴을 어셈블 할 때에 자동적으로 필요한 파일이 결합되어 어셈블 됩니다.

CODE    SEGMENT
        ASSUME  CS:CODE, DS:CODE
;
INCLUDE PUTAL.SUB       ; PUTAL.SUB 파일의 내용을 인용한다.
;
START:  MOV     AX, CODE
        MOV     DS, AX

        MOV     AL,3FH
        CALL    PUTAL

        MOV     AH,4CH
        INT     21H

CODE    ENDS
        END     START

B 드라이브에 PUTAL.SUB 파일이 들어 있을 때
INCLUDE B:PUTAL.SUB

INCLUDE로 전개된  부분의 어셈블 된 리스트 파일은 "C " 가 붙어있다.

동일 변수에 , 여러 개의 데이터를 나열할 때에는 다음 행에 변수 이름을 가지지 않는
DB(define byte) 선언 등을 계속한다.
        NUM     DB      12H,34H

        NUM     DB      12H
                DB      34H
라고 한 것과 같다.

레지스터의 대피:
MOV     BXSAVE, BX  --+
CALL    PUTAL        | BX 레지스터의 값을 대피했다가 복귀한다.
MOV     BX, BXSAVE  --+
        ..........
BXSAVE  DW?      ; 변수 선언

7.2 PUSH, POP 명령
--------------------------------------------------------------------------------
PUSH,POP 명령은 , 스택 영역이라고 부르는 특별한 영역에 대하여 데이터를  기록, 읽어
내는 명령입니다.

스택 동작(stack operation)
마지막에 넣은 것을 처음에 꺼내는 동작(LIFO, Last In First Out)을 스택 동작
메모리는 순서대로 나열되어 있으므로 , 현재 어디까지 데이터가 쌓여있는지를 나타내기
위하여, 스택 포인터(stack pointer, ST 레지스터의 일종)에 마지막에 놓인  데이터가 
있는 메모리의 번지를 기억하도록 되어있습니다.
새로운 데이터를 쌓아 올리면 SP 값은 감소하고, 데이터를 꺼내면 SP 값은  증가
    번지    메모리
하위 번지 |        |
    FFFA  +-------+
    FFFB  +-------+
    FFFC  +-------+  <-- SP(스택 포인터)에 의해
    FFFD  +-------+         지시되는 메모리(마지막에 넣어진 데이터가
    FFFE  +-------+         들어 있다.)
    FFFF  +-------+

주의) 보통의 경우와 , 데이터의 저장법과 순서가 거꾸로 되어 있으므로 주의가 요함
8086의 PUSH, POP 명령에서는 반드시  2 바이트의 데이터가 한 번에 읽어 들여지고 꺼내
어지므로, SP의 값은 항상  2씩 증감합니다.
스택 영역은 보통 메모리의 최상위 번지로 설정되지만  이것은 8비트 CPU와 같이 메모
리 공간이 좁은 시스템에 있어서 프로그램과 스택 상의 데이터가 충돌하지 않게 하기 위
해서였습니다.
8086에서는 보다 넓은 메모리 공간을 사용할 수가 있으므로 , 스택 동작을 위해서 스택 
세그먼트를 독립하여 설정하고 있습니다.
SP 가 가리키는 번지는 실제는 스택 세그먼트(SS)가 가리키는 베이스 번지로부터의 오
포셋 번지입니다.

스택 세그먼트의 설정법에 의해 프로그램이나 데이터와 스택롸를 독립하여 배치할 수도
있고, 또한 8 지트 CPU와 같이 프로그램과 스택을 동일한 메모리 공간에 배치할  메모리 
공간에 배치할 수도 있습니다.
어느 경우에서도 스택 포인터의 초기치를 적당하게 설정함으로써 적당한 번지를 스택 영
역의 시작 번지로 할 수가 있는데 , 보통은 최상위 번지로 부터시작합니다.

CS -->+--------+--- 프로그램            |            |
      +--------+                CS, SS ->+------------|
      +--------+                        |            |프로그램
SS -->+--------+                        +------------+
      +--------+ SP스택 영역             +------------+<-- SP
      +--------+                        +------------+    스택영역
CS와 SS를 독립시킨 경우          CS와 SS를 일치시킨 경우
                                 ( 8비트 CPU와 같은 사용법 )
+-----------------------------------------+
| PUSH  +-  16비트 범용 레지트 터 -+       |
|       |   세그먼트 레지스터      |      |
|       +-  16비트 메모리         -+      |
| POP   +-  16비트 범용 레지스터 -+       |
|       |   세그먼트 레지스터      |      |
|       +-  16비트 메모리         -+      |
+-----------------------------------------+
*. POP CS 명령만은 존재하지 않는다.
  IP를 PUSH, POP 하는 명령은 없다.
  플레그 레지스터를 PUSH, POP 하는 명령으로는 위와 별도로 PUSHF, POPF명령을 사용


PUSH, POP 명령의 실제 사용 예:
레지스터의 대피가 필요하게 된 것은  서브루틴 중에서 레지스터의 내용이 파괴되기
때문입니다. 어느 레지스터의 내용이  파괴되는지는 서브루틴의 내용을  보지 않고서는
알 수 없습니다. 그래서 레지스터의 대피, 복귀는 메인 루틴 쪽이 아니라 서브루틴 쪽에
서 하는 것이 적절합니다.

+--------------+                 +--------------+
| 메인 루틴     |                 |메인 루틴      |
|........     |                 |.....       |
| PUSH BX      |                 |CALL PUTAL    |
| CALL  PUTAL  |  <--+           |.....      |
| POP  BX      |     |           |              |
+--------------+     |           +--------------+
+--------------+     |           +---------------+
|서브루틴      +-----+           |서브루틴       |
+--------------+                 |PUSH BX        |
PUSH , POP를 메인 루틴 쪽에서      |.....        |
   행한 경우의 프로그램           |POP BX         |
                                 +---------------+
                                 PUSH, POP를 서브루틴 쪽에서 행한 경우의 프로그램
완전히 같은 동작을 하지만 오른쪽이 더 좋은 프로그램입니다.
메인 루틴쪽에서 어느 레지스터가 서브루틴 내에서 변경이나 파괴되는가를 생각하지
않아도 되기 때문입니다.
서브루틴을 완전히 블랙박스로서 사용할 수가 있기 때문입니다.
이와 같이 함으로써 서브루틴의 모듈화가 점점 효율적으로 됩니다.

예제) PUSH1.ASM
BL에 초기치로 주어진 어떤 8비트 값()을 1배, 2배,..., F배 한 수치를 출력한다.
CODE    SEGMENT
        ASSUME  CS:CODE, DS:CODE
;
INCLUDE PUTAL2.SUB
INCLUDE CRLF.SUB
;
ENTRY:  MOV     AX, CODE  -+
        MOV     DS, AX    -+DS설정
;
        MOV     BL, SEED ;초기 데이터 21H를 BL에 지정한다.
        MOV     CL,1
NEXT:   CMP     CL,10H  ;10H배이면 끝남.
        JE      EXITP
        MOV     AL, CL   -+초기 데이터의 BL배를 구한다.
        MUL     BL,     -+
        PUSH    AX       --+
        MOV     AL, AH   -+-+---- 상위 바이트 출력
        CALL    PUTAL   -+ |
        POP     AX       --+-- AX레지스터를 보존(출력에 AL 레지스터를 사용하기
        CALL    PUTAL  ;하위 바이트 출력                               때문  )
        CALL    CRLF   ;개행
        INC     CL
        UMP     NEXT
EXITP:  MOV     AH,4CH  -+
        INT     21H     -+끝내고 MS-DOS로 귀환
;
SEED    DB      21H  ;초기 데이터
CODE    ENDS
        END     ENTRY


PUTAL2.SUB
PUTAL:
   +--- PUSH    AX -+;서브루틴 속에서 레지스터
   |+-- PUSH    BX  | AX, BX, DX를 대피하고 있다
   ||+- PUSH    DX -+
   |||  MOV     BL,10H
   |||  MUL     BL
   |||  MOV     DL, AH
   |||  CALL    PUTHEX  ; 상위 자리를 출력
   |||  MOV     AH,0
   |||  MOV     BL,10H
   |||  DIV     BL
   |||  MOV     DL, AL
   |||  CALL    PUTHEX  ;하위 자리를 출력
   ||+- POP     DX -+ AX, BX, DX를 복귀
   |+-- POP     BX  |  (PUSH와 의 역순)
   +--- POP     AX -+
        RET
;
PUTHEX: PUSH    AX
        PUSH    DX
        CMP     DL,0AH
        JAE     HEX2
        ADD     DL, '0'
        JMP     HEX3
HEX2:   ADD     DL, 'A'-0AH
HEX3:   MOV     AH,2
        INT     21H
        POP     DX
        POP     AX
        RET

 

CRLF.SUB
CRLF:
    +-- PUSH    AX
    |+- PUSH    DX
    ||  MOV     DL,0DH  ;CR(복귀)을 출력
    ||  MOV     AH,2 -+
    ||  INT     21H  -+
    ||  MOV     DL,0AH
    ||  MOV     AH,2 -+ ;LF(개행)을 출력
    ||  INT     21H  -+
    |+- POP     DX
    +-  POP     AX
        RET

PUSH, POP 명령은 16비트로 된 레지스터에 , 혹은 메모리에 대피, 복귀밖에 하지  못하며
로 , 서브루틴 내의 DL레지스터 밖에 사용하지 않는 경우에도 DX레지스터로서 대피한다.
        PUSH    AX  ; AX의 내용을 BX에 전송
        POP     BX

직접 전송을 허용하지 못하는 세그먼트 간의 값을 전송하는데 유효합니다.
 예)    PUSH    CS
        POP     DS

가장 주의해야 할 점:
하나의 서브루틴 중에서 PUSH 한 것은 그 루틴 내에서 반드시 POP 한다.


7.3 프로그램의 모듈화와 링클 방법
--------------------------------------------------------------------------------
모듈화 된 프로그램을 따로 별도로 어셈블 하고 , LINK에 의해 결합할 수도 있습니다.
+-----------------------------------------------------------------------------+
|메인 프로그램  서브 프로그램             메인 프로그램   서브프로그램         |
|    |                |                          \/       \/                  |
|    +->    MASM    <-+                         MASM      MASM                |
|            \/                                  \/        \/                 |
|      오브젝트 프로그램            오브젝트 프로그램     오브젝트 프로그램   |
|            \/                                       \/                      |
|           LINK                                      LINK                    |
|            \/                                       \/                      |
|     실행 가능 프로그램                         실행가능 파일                 |
|                                                                             |
| 어셈블 단계에서 결합하는 경우               링크 단계에서 결합하는 경우       |
+-----------------------------------------------------------------------------+

오브젝트 단계에서 링크가 가능하기 때문에 , MS-DOS에서 느 공동 형식의 오브젝트  화
일을 작성하는 어셈블러나 컴파일러의 출력을 합쳐서 하나의 실행 가능  프로그램하는 것
이 가능하다.
이 때문에 어셈블러와 C 언어와 의 링크 등이 가능하게 됩니다.
그러나 링크의 기능을 잘 사용하기 위해서는 , 많은 형식사의 약속이나 링커의  동작을 
이해하지 않으면 안 되아서 그 개요를 설명하는 것만 해도 상당한 분량을 차지하게 되니
다.
프로그램의 개발에 대해서는 메인 프로그램을 포함하는 모듈의 소스 프로그램과 서브
루틴으로 이루어지는 소스 프로그램으로 작성합니다.

예제) MODULE1.ASM
문자열(string)  We are the world를  출력한다.
CODE    SEGMENT PUBLIC
        ASSUME  CS:CODE, DS:DATA
;
EXTRN   PUTSTR:NEAR    외부에서 참조하는 서브루틴의 선언
;
START:  MOV     AX, DATA
        MOV     DS, AX
        MOV     BX, OFFSET MSG
        CALL    PUTSTR
        MOV     AH,4CH
        INT     21H
;
CODE    ENDS
;
DATA    SEGMENT PUBLIC
MSG     DB      'We are the world',00H
;                                   |
DATA    ENDS                        +--> 끝내기 위해 삽입된 0이다.
        END     START

PUTSTR.ASM
문자열을 출력하는 서브루틴
CODE    SEGMENT PUBLIC
        ASSUME  CS:CODE
;
PUBLIC  PUSTR    ;다른 모듈에서 참조되는 서브루틴 이름을 PUBLIC 의사 명령으로
;                  선언한다. 즉 다른 데서 PUTSTR을 참조할 것이라고 선언함.
PUTSTR  PROC    NEAR      --+
        PUSH    AX          |
        PUSH    DX          |
        PUSH    SI          |
        MOV     SI,0        |
PUT_NET:                    |
        MOV     DL, [BX+SI]  |
        CMP     DL,0     -+-+-----> 문자 열중 0 이 나타나면 끝낸다.
        JZ      PUT_EXIT -+ |
        MOV     AH,2        |반드시 서브루틴
        INT     21H         | PROC ~   ENDP로
        INC     SI          | 선언한다.
        JMP     PUT_NEXT    |
PUT_EXIT:                   |
        POP     SI          |
        POP     DX          |
        POP     AX          |
        RET                 |
PUTSTR  ENDP      ----------+
;
CODE    ENDS   형식상 하나의 세그먼트에 있는 걸로 한다.
        END


서브루틴 PUTSTR은 , BX에 의해 간접 지정되는 번지를 선두로 한 데이터 열을
문자열로서 출력하고 , 0 이 나타나면 종료하는 것입니다.

PUBLIC이라는 지정은 : 다른 모듈과 링크할 때에 같은 이름의 세그먼트가 있다면
                       그것을 합쳐서 하나로 하라, 라는 지정입니다.
EXTRN(external)    :
  열거된 서브루틴이 외부의 모듈의 모듈 속에 들어있다는 것을 나타내고 있습니다.
어셈블러는 더셈블 단계에서 같은 모듈 내의 서브루틴이나 라벨이 존재하지 않는 경우
에는 Symbol not defined 에러를 발생하는데,   EXTRN  의사 명령에 의해 선언되어 있
게되면 , 그 서브루틴이나 라벨은 다른 모듈에 있어서 링크할 때 결합할 수 있다고  간주돼
어 에러를 발생하지 않고 어셈블을 계속합니다.
라벨 이름이 동일 세그먼트 내에 배치되는 것인 경우에는 ,
       : 에 바로 이어서 NER을 붙이고, 라벨의 속성을 선언하지 않으면 안 됩니다.
만일 동일 세그먼트 내에 존재하지 않을 때에는 ,
        :   FAR을 붙이지 않으면 안 됩니다.

서브루틴 :
 서브루틴에 놓이는 모듈도 하나의 세그먼트로서 형식을 갖추기 위해 그 앞뒤를
SEGMENT ~ ENDS로 둘러쌉니다. 여기에서 세그먼트 선언은 메인 루틴과 같지 않으면 안
됩니다.(동일한 세그먼트 이름이어야 한다)   데이터 세그먼트를 사용하지 않을 때에는
ASSUME 문장으로 DS 선언을 하지 않도록 합니다.

PUBLIC   :
의사 명령으로 PUTSTR이라는 이름의 서브루틴(라벨 이름)이 다른 외부의 모듈에서 참
조 가능하다는  것을 선언하고 있습니다. 이것은 이 루틴 중 어느 라벨 이름 니 외부 참
조가 가능하다는 것을 나타내는 것입니다.
여기서 사용하고 있는 PUBLC 과 세그먼트  선언중에 사용되고 있느 PUBLC과는 다른 것
입니다.    그리고 서브루틴 전후를 PROC ~ ENDP에 의해 둘러싸고, 구조를 명확하게 
합니다. 이전과 마찬가지로 단순히 라벨 이름만을 서브루틴으로 할 수도 있지만,  별로  
추천할 만 하지하지는 않습니다. 그리고 PROC의  다음의 NEAR 은 없어도 상관이  없지
만, 이것도 동일한 세그먼트 내에서 참조되는    라벨이라는 것을 나타내고 있습니다.
이상의 것을 정리하여 프로그램에 필요한 구조를 나타내고면 다음과 같이 됩니다.
(세그먼트 이름을 CODE , 서브루틴 이름을 PUTSTR로 한다.)

+-------------   메인 루틴   -------------------------------------+
|       CODE    SEGMENT PUBLC                                    |
|               ASSUME  CS:CODE.......                           |
|       EXTRN   PUTSTR:NEAR                                      |
|       START:                                                   |
|               +---------+                                      |
|               | 본  문  |                                      |
|               +---------+                                      |
|       CODE    ENDS                                             |
|               +----------------------------------+             |
|               | 만일 있다면 데이터 세그먼트 등   |             |
|               +----------------------------------+             |
|               END     START                                    |
+----------------------------------------------------------------+


+-------   서브루틴   -------------------------+
|       CODE    SEGMENT PUBLC                  |
|               ASSUME  CS:CODE.....           |
|       PUBLC   PUTSTR                         |
|       PUTSTR  PROC    NEAR                   |
|             +---------+                      |
|             | 본  문  |                      |
|             +---------+                      |
|       PUTSTR  ENDP                           |
|       CODE    ENDS                           |
|               END                            |
+----------------------------------------------+

 오브젝트 파일을 링크할 때에는 화일 이름을  " + " 로 연결하여 지정합니다.
반드시 메인 루틴을 포함하는 모듈의 화일 이름을 선두에  지정합니다.

A> LINK MODULE1+PUTSTR,, MODULE1;

이것으로 실행 가능 파일이 완성됩니다.
자기가 모듈을 만들어 나갈 때에는 위에서 설명한   방법에 따라서 모듈을 개발하면 돼
는데, 아른 참고서로부터 인용할 경우에 반드시 위와 같은 대로 되어 있지 않은  경
우가 있습니다. 그와 같은 경우에는 세그먼트  이름을 고쳐 쓰는 방법 등으로  대처해 
주십시오. 그래도 안 되는겨우에는 원인을 여러 가지로 생각할 수가  있지만, 자세하게는
나중에 다루도록 하고 , 할 수 없이  서브루틴 자체를 맞도록 자기가 다시 배열 하여 고
쳐 작성하는 등 시행착오를 해 보십시오.

'어셈블리' 카테고리의 다른 글

어셈 명령어 표형식 정리  (0) 2019.07.09
어셈블리 강좌7  (0) 2019.07.09
어셈블리 강좌5  (0) 2019.07.09
어셈블리 강좌4  (0) 2019.07.09
어셈블리 강좌3  (0) 2019.07.09