데이터 전송, 주소 지정과 산술연산

개요

4장 : 어셈블리 언어를 사용하는데에 있어 데이터 저장과 기계에 고유한 세부 내용을 소개한다.




데이터 전송 명령어


피연산자 유형

피연산자는 말 그대로 연산을 진행하고 저장되는 즉시값, 레지스터, 메모리를 말하는데 0개부터 3개까지 가질 수 있다.

다음은 피연산자의 종류와 형태이다.



MOV 명령어

MOV 명령어는 소스 피연산자에서 목적지 피연산자로 데이터를 복사한다.

기본적인 형태는 MOV destination, source 이며 사용하기 위해서는 5가지 규칙이 필요하다.

  • 두 피연산자는 같은 크기여야한다.
  • 두 피연산자가 모두 메모리 피연산자일 수는 없다.
  • CS, EIP, IP는 목적지 피연산자일 수 없다.
  • 즉시값이 세그먼트 레지스터에 이동될 수 없다.
  • 메모리에서 메모리로 이동하려면 레지스터를 한번 거쳐서 가야한다.



MOVZX/MOVSX 명령어

mov명령어는 크기가 작은 피연산자에서 큰 피연산자로 직접 복사할 수 없다.

그렇기 때문에 소스 피연산자의 최상위비트를 사용해서 목적지 피연산자의 상위 16비트를 채우는 제로확장 혹은 부호확장을 이용한다.

다음의 그림을 보면 쉽게 이해할 수 있을것이다.

  • MOVZX명령어는 소스 피연산자를 목적지 피연산자로 복사하고 값을 16비트 또는 32비트로 제로확장한다. 이 명령어는 부호없는 정수에만 사용된다.



  • MOVSX명령어는 소스 피연산자를 목적지 피연산자로 복사하고 값을 16비트 또는 32비트로 부호확장한다. 이 명령어는 부호있는 정수에만 사용된다.

사용할 수 있는 형식은 다음 3가지가 있다.

  • MOVZX/MOVSX reg32, reg/mem8
  • MOVZX/MOVSX reg32, reg/mem16
  • MOVZX/MOVSX reg16, reg/mem8



LAHF/SAHF 명령어

LAHF(load status flags into AH)명령어는 EFLAGS 레지스터의 하위 바이트를 AH로 복사한다.

SAHF(load status flags into AH)명령어는 AH를 EFLAGS 레지스터의 하위 바이트로 복사한다.

복사되는 플래그 레지스터는 Sign, Zero, Carry, 보조 Carry, Parity 플래그가 복사된다.



XCHG 명령어

XCHG명령어는 두 피연산자의 내용을 서로 교환한다.



직접 오프셋 피연산자

단어는 어려워보이지만 실제는 간단한 내용이다.

변수의 이름에 변위를 더하여 직접 오프셋 피연산자를 만들 수 있다는 규칙이다.

예를 들어 arrayB BYTE 10h, 20h, 30h의 배열이 있다.

mov al, arrayB를 수행하면 al = 10h 이다. mov al, [arrayB + 1]를 수행하면 al = 20h 이다. mov al, [arrayB + 2]를 수행하면 al = 30h 이다.

대괄호안의 수식은 상수를 변수의 오프셋에 더하여 유효 주소(effective address)라고 하는 것을 계산한다.

대괄호는 필수사항은 아니다. 위의 예시는 바이트 배열이기 때문에 주소를 1씩 증가한다. 만약 워드배열이면 2씩, 더블워드 배열이면 4씩 증가한다.



덧셈과 뺄셈

산술은 어셈블리 언어에서 매우 큰 주제이다. 우선은 기본적인 덧셈과 뺄셈을 살펴보고 곱셈과 나눗셈은 이후 7장에서 소개한다.

CPU는 부호있는 연산인지 부호없는 연산인지를 모른다. 그러므로 프로그래머가 결정해서 해당하는 연산을 진행해주어야한다.

CPU는 산술 연산 후에 어떤 플래그가 관련이 되는지와 관계없이 부울 함수 규칙을 사용하여 모든 상태 플래그를 설정한다.



상태 플래그

산술 연산을 처리할 때는 상태 플래그값이 설정된다. 그렇다면 상태플래그의 종류와 역할을 살펴보겠다.

  • Carry Flag : 부호없는 정수 오버플로를 나타낸다. 부호 없는 연산에서 목적지 피연산자가 저장할 수 있는 크기를 넘을 때에 Carry플래그는 1로 설정된다.
  • Overflow Flag : 부호있는 정수 오버플로를 나타낸다. 부호 없는 연산에서 목적지 피연산자가 저장할 수 있는 크기를 넘을 때에 Overflow플래그는 1로 설정된다.
  • Zero Flag : 연산의 결과가 0인 것을 나타낸다.
  • Sign Flag : 연산의 결과가 음수인 것을 나타낸다. 목적지 피연산자의 최상위 비트가 1이면 Sign 플래그는 1로 설정된다.
  • Parity Flag : 산술논리 연산을 수행한 직후에 목적지 피연산자의 최하위 바이트에 1인비트의 개수가 짝수인지 판별한다.
  • 보조 Carry Flag : 목적지 피연산자의 최하위 바이트의 비트 3에서 1이 캐리로 발생했을 때 1로 설정된다.



INC/DEC 명령어

INC(increment)와 DEC(decrement) 명령어는 각각 단일 피연산자에서 1을 더하고, 1을 뺀다.

플래그 레지스터에서 Overflow, Sign, Zero, 보조 carry, Parity 플래그는 목적지 피연산자의 값에 따라서 변하지만 Carry 플래그에는 영향을 주지 않는다.



ADD/SUB 명령어

ADD 명령어는 소스 피연산자를 같은 크기의 목적지 피연산자에 더한다.

ADD destination, source의 구문을 가지고 있으며 모든 상태플래그는 목적지 피연산자의 값에 따라 변한다.

SUB 명령어는 목적지 피연산자에서 소스 피연산자를 뺀다.

SUB destination, source의 구문을 가지고 있으며 모든 상태플래그는 목적지 피연산자의 값에 따라 변한다.

CPU가 할 수 있는 산술 연산은 덧셈밖에없다. CPU는 보수를 이용해서 부호를 바꾸어 더하는 것으로 뺄셈을 처리한다.



NEG 명령어

NEG(negate)명령어는 숫자를 2의 보수로 변환하여 숫자의 부호를 바꾸는 명령어이다.

NEG reg 혹은 NEG mem의 구문으로 사용할 수 있고 모든 상태플래그는 목적지 피연산자에 따라 영향을 받는다.




데이터 관련 연산자와 디렉티브


연산자와 디렉티브는 실행 가능한 명령어가 아니다. 단지 어셈블러가 이들을 해석하여 실행할 뿐이다.


OFFSET 연산자

OFFSET 연산자는 데이터 레이블의 오프셋을 반환한다. 즉 데이터 세그먼트의 시작부터 변수의 위치까지의 거리를 알려준다.



ALIGN 디렉티브

ALIGN 디렉티브는 변수를 바이트, 워드, 더블워드, 또는 문단의 경계에 정렬한다.

CPU는 홀수 주소에 저장된 데이터보다 짝수 주소에 저장된 데이터를 더 빠르게 처리할 수 있기 때문에 정렬한다.

ALIGN bound의 구문을 가지며 bound에는 1,2,4,16이 올 수 있다.



PTR 연산자

피연산자의 선언된 크기를 바꾸어 사용하기 위해서 PTR 연산자를 사용할 수 있다.

변수를 선언할 때 사용된 크기와 다른 크기를 사용하여 변수에 접근하고자 할 때 사용한다.

그렇기 때문에 PTR 연산자는 어셈블러 자료형(BYTE, SBYTE, WORD, SWORD, DWORD, SDWORD, FWORD, QWORD, TBYTE)랑 사용해야한다.

예를 들어,

1
2
3
4
.data
wordList WORD 5678h, 1234h
.code
mov eax, DWORD PTR wordList

의 결과는 eax의 하반부에 wordList의 첫 번째 워드가 복사되고 두 번째 워드는 상반부로 복사되어 eax = 12345678h가된다.



TYPE 연산자

TYPE 연산자는 변수의 단일 원소의 크기를 바이트 단위로 반환할 때 사용한다.



LENGTHOF 연산자

LENGTHOF연산자는 레이블과 같은 줄에 있는 값들로 정의되는 배열에 있는 원소의 개수를 반환한다.

자료형이 무엇이든간에 무조건 배열안의 개수만 고려한다. 만약 배열을 여러줄에 걸쳐서 선언한다면 첫 번째 줄 데이터의 개수만 반환한다.



SIZEOF 연산자

SIZEOF 연산자는 LENGTHOF와 TYPE을 곱한 값을 반환한다.



LABEL 디렉티브

LABEL 디렉티브는 저장공간을 할당하지 않으면서 레이블을 넣고 그것에 크기 속성을 준다.

LABEL의 용도는 데이터 세그먼트에서 다음에 선언된 변수에 대해서 다른 이름과 크기 속성을 제공하는 것이다.




간접 주소지정


배열 원소들의 주소를 지정하는 데 레지스터를 포인터로 사용하고 이 레지스터의 값을 조작하는 것을 간접 주소지정이라고 한다.



간접 피연산자

피연산자가 간접 주소지정을 사용하여 값이 저장되면 간접 피연산자라고 한다.

레지스터를 간접 피연산자로 사용하기 전에 레지스터를 항상 초기화 하고 사용해야 한다.

배열을 처리할 때 사용한다.


보호모드

보호 모드에서 간접 피연산자는 대괄호를 둘러싸인 임의의 32비트 범용 레지스터일 수 있다.


실제 주소 모드

실제 주소 모드에서는 16비트 레지스터에 변수의 오프셋을 저장한다.

사용 가능한 레지스터는 SI, DI, BX, BP이다.




JMP와 LOOP 명령어


JMP 명령어

JMP 명령어는 코드 레이블로 표시되는 목적지로 무조건 이동시킨다.

jmp destination와 같은 구문을 가진다.

LOOP 명령어

LOOP 명령어는 ECX를 카운터로 사용하여 루프를 실행할 때마다 0이 될 때까지 감소시킨다.

1
2
3
mov ecx, cnt
L1:
loop L1