2010년 1월 7일 목요일

5장 - 이진 세상으로 떠나는 깃발 - Flag

이진 세상으로 떠나는 깃발 - Flag

지금 이 순간에도 엄청난 양의 이진수가 땅속으로 또는 하늘위로 전송되고 있을 것이다. 실세계와 다르게 컴퓨터가 다룰 수 있는 이진수의 갯수는
한정되어있다. 반면 사람은 종이와 연필만 있다면 (?) 얼마든지 큰 수라도 연산을 할 수 있을 것이다. 컴퓨터도 마찬가지다 얼마든지 큰 메모리만
있다면 사람보다 더 큰 양의 데이터를 처리할 수 있다. 우리는 그 거대한 데이터 중에서 컴퓨터에서 정수로 인정하는 한정된 수를 다루려한다.

보통 고급 언어에서는 정수를 integer 형식으로 몇 바이트며 어디까지가 영역이다고 미리 정해두고 쓰는데, 어셈에선 정하기 나름이다. 강제적인
규칙이 없다. 왠만한 정수는 db로 바이트 단위 (이는 가장 작은 메모리 액세스 단위이다)로 정해주면 만사 해결된다. 이제 정해준 정수에 연산할
때 add/sub/mul/div 등의 사칙 연산 CPU 명령을 사용하면 왠만한 정수 연산은 문제없이 수행한다. 다만 곱셈과 나눗셈이 약간의 주의를 요하는데,
이는 일상에서 보통 사람들도 곱셈과 나눗셈 보단 덧셈과 뺄셈을 더 잘할 것이다. 마찬가지로, 연산 속도를 빠른 순으로 나열한다면, 덧셈, 뺄셈,
곱셈, 나눗셈이다.

우리는 이 사칙 연산을 수행할 때 그 결과에 민감하게 반응하고, CPU는 플랙에 반영한다. 그러므로 연산할 때 항상 플랙을 염두에 두고 연산하는
습관을 들일 필요가 있다. 이번 장은 플랙의 내가 아는 모든 것을 설명한다. 플랙은 16비트 (64비트 환경에선 64비트, 32비트 환경에선 32비트)
레지스터이다. 즉, 범용 레지스터와 같은 크기의 일종의 레지스터이지만, CPU가 가지고 노는 레지스터이다. 물론 어셈블리 프로그래머는 CPU 명령을
사용해 가지고 논다. 플랙의 역할이 무엇이냐고 정의하는 어셈블리 책은 많이 있다. 나는 "특정 연산 수행후 대상 연산자에 결과를 반영하는"역할이라고
하겠다. 즉, 소스 연산자와는 아무 관련이 없다 (나는 이를 소스 불변의 법칙이라고 언급했다. 플랙 레지스터에도 소스 불변의 법칙은 적용된다).
예로, add ax, bx라는 명령이 있다고 하자. 이는 ax에 bx를 더하며, ax를 대상 연산자 (destination operand)라고 하고, bx를 소스 연산자 (source
operand)라고 한다. 이 연산이 수행되면, CPU는 대상 연산자의 결과에 따라 플랙 레지스터를 특정 값으로 세팅한다.

CPU 명령어 레퍼런스에 플랙 레지스터의 해부학적 설명이 잘 나와있다. 이중 우리가 산술 연산시 신경써야 할 것은 6개 밖에 안된다.

- CF : 연산 결과의 자리 올림 (carry)이나 자리 내림 (borrow)시 on/off로 토글된다.
- OF : 연산 결과 담을 수 없는 이진 량이거나 담기에 부족한 량일 경우 토글된다.
- AF : 연산 결과 반 바이트 자리 올림이 일어나거나 자리 내림이 일어날 경우 토글된다.
- ZF : 연산 결과가 0일 경우 토글된다.
- SF : 연산 결과 부호 (음수/양수)가 뒤집어질 경우 토글된다.
- PF : 연산 결과가 비트의 1의 갯수가 홀수에서 짝수 또는 그 반대로 뒤집어질 경우 토글된다.

나머지는 별로 중요치 않으며 필요할 경우 그때 그때 설명하기로하자. ZF를 제외하고 이 플랙 대부분이 앞 자리 비트 (MSB)에 민감하게 반응함을
눈치챘는가 ? AF는 반바이트 (1니블)의 맨 앞 비트가 변경될 경우 토글되므로 이 또한 마찬가지이며 앞장에서 다룬 BCD 연산에서 특히 영향력을 행사한다.

이 시점에서 중요한 4대 산술 명령인 add/sub/mul/div 등의 사칙 연산 명령을 간략히 다뤄야겠다. 각 명령의 기본 포맷은 이렇다. 확장 포맷은 생략한다.

add d, s
sub d, s
mul s
div s

- d: destination operand / s: source operand

mul과 div은 하나의 연산자를 요구하며 이는 dest/src 아무렇게나 칭해도 무관하다. 명칭이 중요한게 아니라 하나의 연산자만 필요한다는게 중요하다.
즉, mul과 div은 "무엇에" 무엇을 곱하라 또는 "무엇을" 무엇으로 나눠라는 명령인데, 여기서 ""에 해당하는 대상은 mul/div으로 정해주지 않고
다른 mov 등의 명령으로 정해준다. 나중에 살펴보기로 하고 먼저 add부터 살펴보자.

mov ax, 1234h
add ax, 5678h

이렇게 연산을 할 경우 ax = 68AC로 갱신된다. add 연산에 사용된 상수 5678h를 immediate 상수라고 하며 그 상수가 명령에 함께 인코딩된다. 우리는
임시값 또는 즉시값 또는 상수 등으로 표현하기로 하자. 이 경우 PF가 토글된다. 이는 비트의 갯수가 홀수 (odd)나 짝수 (even)으로의 변경이 일어났다는
뜻이며, 값 자체가 짝수/홀수인가와는 아무 관련없다.

1234h = 0001 0010 0011 0100 (비트 1의 갯수는 5개이다)
5678h = 0101 0110 0111 1000 (비트 1의 갯수는 8개이다)
68ACh = 0110 1000 1010 1100 (비트 1의 갯수는 7개이다)

대상 연산자의 결과를 반영한다고 했는데, 틀렸다는 것인가 ? 아직까지는 단정할 수 없다. 플랙의 역할을 가장 잘 설명하는 명령은 쉬프트 명령이다.
예로, 다음처럼 디버그로 입력하고 트레이싱해보면 이렇다.

mov ax, 0FFFFh        ; 비트로 =         1111 1111 1111 1111
shr ax, 1             ; 이 명령 수행후,  0111 1111 1111 1111 , CF on, PF on, OF on
shr ax, 1             ; 이 명령 수행후,  0011 1111 1111 1111 , CF 그대로, PF 그대로, OF 반전
shr ax, 1             ; 이 명령 수행후,  0001 1111 1111 1111 , CF 그대로, PF 그대로, OF 그대로
shr ax, 1             ; 이 명령 수행후,  0000 1111 1111 1111 , CF 그대로, PF 그대로, OF 그대로

여기서 잘 살펴보면, 비트 1이 하나씩 떨어져나가 총 1의 갯수가 홀짝으로 변경되지만, PF 는 처음에만 반응하고 나머지 명령엔 그대로 유지하고 있다.
CF나 OF도 마찬가지다. 그러므로 다음처럼 정리를 해야할 필요가 있다. 디버그는 플랙의 변경사항을 알지만 이를 명시적으로 보여주지 않는다.

위의 1234h가 68ACh로 변경되었지만, 이미 비트 1의 갯수가 5-6-7로 두번 변경되었다고 할 수 있다. 그러므로 유저 입장에서는 디버그로 플랙 변경
사항을 제대로 알 수 없다. 처음에만 보여주는 것은 대부분의 디버거가 가진 문제점 중의 하나다. 그렇다고 디버거가 이를 눈치 못 채는 것은 아니다.
다음은 Iczelion's Win32 ASM Tutorial의 2장에 있는 예제를 조금 수정한 것이다.

.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib kernel32.lib
include \masm32\include\user32.inc
includelib user32.lib

.data
MsgCaption      db "Iczelion's tutorial no.2",0
MsgBoxText      db "Win32 Assembly is Great!",0

.code
start:
    invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK
 
    mov eax, 0FFFFFFFFh       ; eax에 32개의 비트 1을 넣고

L1:
  shr eax, 1

L2:
  shr eax, 1
  jp L2                     ; 비트 1의 갯수가 홀짝으로 변경되면 L2로 분기

L3:
  add eax, 1

    invoke ExitProcess,NULL
end start

이를 컴파일해서 올리 디버거로 트레이싱해보면, 무한 루프에 걸린 것처럼 보일 것이다. jp라는 명령이 PF 플랙을 테스트하여 그 결과에 따라 점프하는
명령인데, 이 경우 shr로 비트를 한번씩 쉬프트 할때마다 비트가 하나씩 떨어져나가 1의 갯수가 변경됨을 알기 때문에, L2에 모든 비트가 0이 될때까지
루프를 반복적으로 돌게된다. 올리 디버거로 테스트해보니 약 23번의 루프를 돌았다. 왜인지는 모르겠다. 하지만, 올리 디버거가 플랙 변경 사항을
감지하고 있음은 분명하다는 뜻이다. 다만 첫번째 변경 사항만 보여줄 뿐이다. 디버그도 마찬가지며 대부분의 디버거가 이와 비슷하다. 플랙에 민감하자는
뜻에서 뜬금없이 32비트 코드를 들어 당황할지 모르지만, 사실 32비트 프로그래밍이 더 쉽다. 함수와의 통신만 잘 해결해주면 되기 때문이다.

위의 코드에서 L3로 넘어가는 부분은 EAX가 7F일 때였다. 그 결과 L3가 실행되어 eax에 1을 더해주면 80h가 된다. al 값만 살펴보자.

al    0111 1111
add eax, 1
al    1000 0000

이 경우 add 명령을 실행하면 CF가 토글되며, AF가 토글되었다. CF은 사칙 연산, 특히 덧셈에서 아주 중요한 플랙인데, 초과일 경우 토글된다고
생각하면 된다. 이 경우 7F라는 바이트 레벨 절대치가 가장 큰 음수에서 80h (바이트 레벨에서 80h는 0으로 간주)로 변경되어 음수 레벨을 초과했다는
뜻인지 아니면 OS 레벨에서 발생한 것인지 확실치 않고, AF는 반 바이트 (1니블) 단위의 캐리 (보조 캐리, auxiliary carry)가 일어나서 토글되었다.

우리는 캐리 플랙의 작동 방식이 애매하므로 debug를 실행시켜 a 100으로 다음처럼 어셈블하여 트레이싱해보자.

-a 100

mov ax, 3F        ; ax = 003Fh
add ax, 1         ; ax = 0040h,     AF = 1 (AC)
add ax, 3F        ; ax = 007Fh
add ax, 1         ; ax = 0080h,     AF = 1 (AC)
add ax, 3F        ; ax = 00BFh,
add ax, 1         ; ax = 00C0h,     AF = 1 (AC), PE = 1
add ax, 3F        ; ax = 00FFh,
add ax, 1         ; ax = 0100h,     AF = 1
nop
nop
-t
-t (NOP까지 반복)

이를 트레이싱해보니 CF는 한번도 토글되지 않음을 알 수 있었다. 즉, 7Fh에서 80h로 변경되어도 CF는 아무 영향이 없다는 뜻인데, 그럼 왜 올리
디버그는 7F에서 80h로 넘어갈 때 CF를 토글하는가 ? W32Dasm으로 트레이싱해도 같은 결과가 나왔으니 debug의 문제란 소린가 ? 현 시점에서는
알 수 없으므로 간략히 테스트 코드를 만들어봤다.

;============ CF tester ==================

.model small
.stack 100h
.data

.code
main proc
  mov ax, @data
  mov ds, ax

L1:

  xor ax, ax
  mov al, 7Fh
  add al, 1                     ; 이 명령은 캐리를 발생하지 않았다.

  jc L3                         ; 그러므로 이 명령은 무시된다.

L2:
  xor ax, ax
  mov ax, 7FFh
  add ax, 1                     ; 이는 캐리가 발생하지 않으므로 jc는 무시되었다.

  jc L4

L3:                             ; 그러므로 이 코드는 실행된다.
  xor ax, ax
  mov ax, 7FFFh
  add al, 1                     ; 이는 캐리를 발생시켰다.

  jc L5                         ; 그러므로 L5로 분기한다.

L4:
  xor ax, ax
  mov ax, 0FFFFh
  add ax, 1

  jc L1

L5:
  xor ax, ax
  mov ax, 0FFFFh
  add ax, 1                     ; CF = 1

Exit:
  mov ah, 0
  int 16h                       ; 키보드 인터럽트로 잠시 지연 (wait for key-press)

  mov ax, 4C00h
  int 21h

main endp
end main

이 코드를 유심히 보면, add 명령 뒤에 dest 연산자를 al로 해줄때와 ax로 해줄때에 따라 다르게 반응함을 알 수 있을 것이다. L1에서 add는
7F에 1을 더해도 al의 범위 (00~FFh)를 넘지 않으므로 캐리가 발생하지 않으며, L2에서는 ax의 범위를 넘지 않으므로 역시 캐리가 발생하지
않고, L3에서는 al의 범위를 초과하므로 캐리가 발생하며, L5에서는 ax의 범위를 넘으므로 캐리가 발생한다. 즉, 우리가 처음 가설에
"dest 연산자"의 결과에 따라 토글된다는 것이 증명되었다. 위의 32비트 코드에서 왜 CF가 토글되었는지는 여전히 미지수다.

그러므로 이 시점에서 CPU 매뉴얼을 참조해야겠다.

Carry flag - Set if an arithmetic operation generates a carry or a borrow out of the most-significant bit of the result; cleared
otherwise. This flag indicates an overflow condition for unsigned-integer arithmetic. It is also used in multiple-precision
arithmetic.

- 결과의 MSb가 캐리(자리 올림)나 바로우(자리 내림)가 일어나면 CF가 세팅되고, 그렇지 않으면 (즉, MSb가 자리 변동 없으면)
클리어된다. 이 플랙은 부호무시 (unsigned) 산술 연산에서 오버플로 상태를 지시하며, 복정밀 산술 연산에서도 사용된다.
(E)Flag 레지스터의 0번 비트

보다시피 dest 연산자에 반응한다는 얘기는 없다. 어셈블리 프로그래머는 CPU 매뉴얼에 없는 내용은 직접 코드로 테스트해서라도 작동 방식을
알아야한다. 그런 이유에서 매니아적인 언어라고 할 수 있다. 이왕 CPU 매뉴얼 참조한 김에 다른 것들도 참조하자. 국어사전을 무시한 쌩영어 번역을
즐기다보니 독자가 무슨 소린지 모를 수 있을지 모르지만 그래도 다소 친숙하게 느껴질 것이다. (나는 CPU 매뉴얼을 나 개인 보관용으로 상당 부분
번역해봤지만, 국어 사전으로 설명 안되는 부분이나 우리말로 바꾸기조차 애매한 단어가 상당히 많았다. 특히 복합어)

PF (bit 2)Parity flag - Set if the least-significant byte of the result contains an even number of 1 bits; cleared otherwise.

PF - (2번 비트) - 결과물이 LSB 바이트가 가진 1이 짝수 갯수이면 세트되고 그외엔 (즉, LSB 1이 홀수 갯수이면) 클리어된다.

AF (bit 4)Adjust flag - Set if an arithmetic operation generates a carry or a borrow out of bit 3 of the result; cleared otherwise.
This flag is used in binary-coded decimal (BCD) arithmetic.

AF - (4번 비트) - (예전 CPU 명령은 Auxiliary carry flag 이었는데, 언제부언지 Adjust flag으로 바뀌어 있었다.) 만약, 결과물에서
비트 3 (1000 에서 앞의 1이 3번 비트다)이 캐리되어 떨어져나가거나 바로우되어 껴들어오는 산술 연산에서 세트되고, 그렇지 않으면 (즉,
3번 비트가 캐리가 없으면) 클리어된다. 이 플랙은 BCD 산술 연산에서 사용된다.

ZF (bit 6)Zero flag - Set if the result is zero; cleared otherwise.

ZF - (6번 비트) 결과가 0이면 세트되고, 그외엔 클리어된다.

SF (bit 7)Sign flag - Set equal to the most-significant bit of the result, which is the sign bit of a signed integer. (0 indicates
a positive value and 1 indicates a negative value.)

SF (7번 비트) - 결과의 MSb와 같게 세팅된다. 이는 부호를 따지는 정수 (음수)연산에서 사인 비트가 된다. (0은 양수를 1은 음수를 의미한다)

OF (bit 11)Overflow flag - Set if the integer result is too large a positive number or too small a negative number (excluding the sign-bit)
to fit in the destination operand; cleared otherwise. This flag indicates an overflow condition for signed-integer (two’s
complement) arithmetic.

OF (11번 비트) - 정수 결과물이 최대 양수보다 더크거나 최소 양수보다 더 작을 경우 세트된다 (다만, 사인 비트는 제외한다). 그래서 대상 연산자에
맞춘다. 그 외엔 (최대값보다 크지 않거나 최소값보다 작지 않으면) 클리어된다. 이 플랙은 부호 중시 정수 (음수) 연산 (2의 보수 연산)에서 오버플로가
발생함을 의미한다.

이 암호같은 텍스트를 유심히 읽은 눈썰미가 있는 독자라면 왜 위의 32비트 코드에서 패러티 플랙이 세트되었는지 이해할 것이다. 우리가 쓰는 JP
명령은 JPE와 같은 말이며 이는 패러티 플랙을 테스트하여 분기를 하는 명령이다. CPU 매뉴얼 내용에서 설명하듯이 LSB (LSB는 마지박 바이트, LSb는
마지막 비트)가 PF 플랙을 세팅할 것인지를 결정한다. 그러므로 dword/word와는 별 관련없지만 byte 레벨에선 PF 플랙을 테스트할 수 있다는 뜻이다.

다른 플랙도 테스팅해보자. 옷을 만드는 사람은 옷을 만들기 전에 손님의 치수를 재듯이 어셈블리 프로그래머는 프로그래밍하는 중간 중간 플랙을
테스트해야 할 경우가 발생한다. 이중 몇가지만 예로 들어보자. 위의 32비트 코드에서 불확실했던 패러티 플랙을 테스트하자.

위의 32비트 코드에서 쉬프트를 하다보면, 7Fh (0111 1111b)가 되고 LSB의 패러티 플랙이 JP (=JPE)에서 토글되어 (JPO/JPE 모두 PF 플랙을 토글한다),
무한 루프를 빠져나왔다는 소리다. 즉, 1이 8개에서 7개가 되었다는 뜻이며, 디버거가 멍청하지 않았다는 소리다. 우리는 16비트 코드로 이를 다시
테스트해보자.

;============ odd parity tester ==================

.model small
.stack 100h
.data
pmsg db "Partity flag set"

.code
main proc
  mov ax, @data
  mov ds, ax
 
  mov ah, 0
  mov al, 00001111b           ; 테스트 값 (1비트 4개)
  mov cx, -1                  ; 루프 카운터 (-1 = 무한 루프)
 
L1:
  shl al, 1
  jpo L2
  loop L1
 
L2:
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof pmsg
  lea dx, pmsg
  int 21h
  jmp Exit

Exit:
  mov ah, 0
  int 16h                       ; 키보드 인터럽트로 잠시 지연 (wait for key-press)

  mov ax, 4C00h
  int 21h

main endp
end main


보다시피 ah는 ax의 상위 바이트이므로 패러티와는 관련없으므로 0으로 세팅해줬다. 반면, al은 4개의 비트를 1로 설정해주어 짝수 패러티인 상태로
이를 시작하였다. 그러다가 쉬프트를 하여 홀수 패러티가 발생하면 jpo 명령에 의해 루프를 빠져나온다. 물론 비트 3개를 1로 해주고 JP로 테스트해도
되며, 디버그 (debug.exe)로 트레이싱하니 F0h (11110000b)에서 E0 (11100000b)로 변경되는 순간 패러티 플랙이 토글되었다. 그 결과 무한 루프를
빠져나오며 출력 스트링이 찍혀졌다.

이제 홀수와 짝수를 판정내려보자. 홀수인지 짝수인지 어떻게 구분하는가 ? 여러가지 방법이 있지만, 간단한 한가지만 예를 들어보자.
모든 2진수는 2의 특정 배수의 합으로 이루어진다. 그러므로 제일 우측 비트 (즉, LSb)가 1이면 홀수이다. 이는 ror 명령으로 우측으로
1비트 회전시켜보면 판정난다. 코딩하면 대략 이렇다.

;============ odd/even tester ==================

.model small
.stack 100h
.data
isOdd db "Yes, it's a odd"
isEven db "Yes, it's a even"

.code
main proc
  mov ax, @data
  mov ds, ax

L1:
  mov ax, 1FFFh
  ror ax, 1
  jc L2
 
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isEven
  lea dx, isEven
  int 21h
  rol ax, 1
  jmp Exit
 
L2:
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isOdd
  lea dx, isOdd
  int 21h

Exit:
  mov ah, 0
  int 16h                       ; 키보드 인터럽트로 잠시 지연 (wait for key-press)

  mov ax, 4C00h
  int 21h

main endp
end main

반대로 어떤수가 음수인지 양수인지를 테스트한다면, 모든 음수는 사인 비트 (MSb)가 1이다. 그러므로 rol 명령으로 제일 왼쪽 비트 (MSb)만 1비트
왼쪽으로 회전해보면 판정난다. 다만 음/양을 판정하기앞서 먼저 그 데이터의 단위 (type)가 무엇인지를 알아야한다. 예로, 바이트 레벨로 FFh는
워드 레벨에선 00FF가 되어 양수가 될 수 있다. 즉, 기혼/미혼의 판정 기준이 결혼반지라면 음/양 판정 기준은 젤 왼쪽 비트 (사인 비트)이다.

;============ positive/negative tester ==================

.model small
.stack 100h
.data
isPos db "Positive"
isNeg db "Negative"

.code
main proc
  mov ax, @data
  mov ds, ax

L1:
  mov ax, 1FFFh
  rol ax, 1
  jc L2
 
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isPos
  lea dx, isPos
  int 21h
  ror ax, 1
  jmp Exit
 
L2:
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isNeg
  lea dx, isNeg
  int 21h

Exit:
  mov ah, 0
  int 16h                       ; 키보드 인터럽트로 잠시 지연 (wait for key-press)

  mov ax, 4C00h
  int 21h

main endp
end main

어떤 수가 0인지 아닌지는 어떻게 판정하는가 ? 0은 두종류가 있으며 8비트를 예로 들면 0과 80이다 0은 부호-무시 정수일 경우 0이고, 80은 부호-인정
정수일 경우 0으로 간주한다. 그러므로 signed와 unsigned 인지를 먼저 확인해야 하며 이는 유저 레벨에서 결정한다. 일단 결정되고 나면 나머지는
쉽게 판정된다. unsigned일 경우 80h를 무시하고 0이 아닌 80h 숫자로 인정하고, signed일 경우는 80h와 0을 동시에 같게 해줘야 한다. 상위 비트만
1로 and 마스킹하여 제로 플랙이 세팅되면 음수로 인정한다. 백마디 말보다 코딩/디버깅 한번 해보는게 쉬울 것이다.

;============ zero/non-zero tester ==================

.model small
.stack 100h
.data
Prompt db "Type 0 (Unsigned), or 1 (Signed)"
isZero db "Zero"
isNotZero db "Not Zero"

.code
main proc
  mov ax, @data
  mov ds, ax
 
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof Prompt
  lea dx, prompt
  int 21h
 
  mov ah, 0
  int 16h
 
  cmp al, '0'                 ; Unsigned ?
  jne L2                      ; NO

L1:                           ; Yes
  mov ax, 8000h
  test ax, 1000000000000000b  ; = 8000h, 즉 사인비트가 켜졌는가 ?
  jz L2
 
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isNotZero
  lea dx, isNotZero
  int 21h
  jmp Exit
 
L2:
  rol ax, 1                     ; 음수일 경우 0으로 강제 설정
  jc L3                         ; 샘플용 분기 처리
 
L3:
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isZero
  lea dx, isZero
  int 21h

Exit:
  mov ah, 0
  int 16h                       ; 키보드 인터럽트로 잠시 지연 (wait for key-press)

  mov ax, 4C00h
  int 21h

main endp
end main



이외에도 특정 데이터를 저장할 때 특정 데이터 타입 (type)을 초과할 경우가 발생할 것인지를 테스트해야할 경우도 있다. 덩치는 작은
사람이 큰 옷을 사는 것으로 비유하면 쉽게 이해될 것이다. 예로, al을 넘는 값을 al에 어거지로 저장한다고 하면, 입력하려는 값이
ah에도 저장되는 지를 테스트하면 간단히 해결된다.

;============ type overflow - match/mismatch tester ==================

.model small
.stack 100h
.data
ismatched db "Good"
isoverflow db "Too small to save"

.code
main proc
  mov ax, @data
  mov ds, ax
 
  mov ax, 1234h                 ; 테스트 값 (바이트 레벨에선 오버플로임)
  cmp ah, 0
  ja L1

  mov ah, 40h
  mov bx, 1
  mov cx, sizeof ismatched
  lea dx, ismatched
  int 21h
  jmp Exit
  
L1:
  mov ah, 40h
  mov bx, 1
  mov cx, sizeof isoverflow
  lea dx, isoverflow
  int 21h
 

Exit:
  mov ah, 0
  int 16h                       ; 키보드 인터럽트로 잠시 지연 (wait for key-press)

  mov ax, 4C00h
  int 21h

main endp
end main

몇가지 대표적인 경우만 예로 들었지만, 이외에도 플랙 테스트를 해야하는 경우가 많으며 에러 처리 루틴이 대부분 로우레벨에서 보면 플랙 테스트로
이뤄진다고 할 수 있다. 보다시피 명시적인 플랙 테스트는 하지 않지만 jxx 명령이 모두 암시적으로 플랙을 테스트하는 명령이다. 플랙을 잘 테스트하여
유저의 고민거리를 덜어주기도 하지만, 이를 게을리하여 프로그램을 삭제당하기도 한다.

댓글 없음:

댓글 쓰기

블로그 보관함