일반적으로 메모리라고 얘기하는 RAM(Random Access Memory)와는 성격이 다름
CPU가 RAM에 있는 데이터를 액세스(Access)하기 위해서는 물리적으로 먼 길을 돌아가야 하기 때문에 오랜 시간이 걸림
하지만 레지스터는 CPU와 한 몸이기 때문에 고속으로 데이터 처리 가능
어셈블리 명령어의 대부분은 레지스터를 조작하고 그 내용을 검사하는 것들이므로, 레지스터를 확실히 이해해야 명령어 자체도 이해가 가능하다.
1-2. IA-32 레지스터 종류
IA-32는 지원하는 기능도 무척 많고, 그만큼 레지스터의 수도 많다.
IA-32에 존재하는 레지스터들의 종류는 다음과 같다.
Basic program execution registers
x87 FPU registers
MMX registers
XMM registers
Control registers
Memory management registers
Debug registers
Memory type range registers
Machine specific registers
Machine check register
애플리케이션 디버깅의 초급 단계에서는 Basic program execution register에 대해서 알아두어야 한다.
디버깅할 때 가장 많이 보게 될 레지스터이며, 중/고급 단계에서는 추가적으로 Control registers, Memory management registers, Debug registers 등에 대해서도 알아두어야 한다.
1-2-1. Basic program execution registers
Basic program execution registers는 다시 4개의 그룹으로 나눌 수 있다.
General Purpose Registers (32비트 - 8개)
Segment Registers (16비트 - 6개)
Program Status and Control Register (32비트 - 1개)
Instruction Pointer (32비트 - 1개)
# 참고
레지스터 이름에 E(Extend)가 붙은 경우는 예전 16비트 CPU인 IA-16 시절부터 존재하던 16비트 크기의 레지스터들을 32비트 크기로 확장시켰다는 뜻
각 그룹에 대하여 살펴보도록 하자.
1) 범용 레지스터 (General Purpose Registers)
이름처럼 범용적으로 사용되는 레지스터들
IA-32에서 각각의 범용 레지스터들의 크기는 32비트(4바이트)
보통은 상수 / 주소 등을 저장할 때 주로 사용되며, 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 함
어떤 레지스터들은 특수 용도로 사용되기도 함
각 레지스터들은 16비트 하위 호환을 위하여 몇 개의 구획으로 나뉘어진다. (EAX 기준)
EAX : (0 ~ 31) 32비트
AX : (0 ~ 15) EAX의 하위 16비트
AH : (8 ~ 15) AX의 상위 8비트
AL : (0 ~ 7) AX의 하위 8비트
즉 4바이트(32비트)를 다 사용하고 싶을 때는 EAX를 사용하고, 2바이트(16비트)만 사용할 때는 EAX의 하위 16비트 부분인 AX를 사용하면 된다.
AX는 다시 상위 1바이트(8비트)인 AH와 하위 1바이트(8비트)인 AL로 나뉘어진다.
이런 식으로 하나의 32비트 레지스터를 상황에 맞게 8비트, 16비트, 32비트로 알뜰하게 사용 가능하다.
각 레지스터의 이름은 아래와 같다.
EAX : Accumulator for operands and results data
EBX : Pointer to data in the DS segment
ECX : Counter for string and loop operations
EDX : I/O pointer
위 4개의 레지스터들은 주로 산술 연산(ADD, SUB, XOR, OR 등) 명령어에서 상수/변수 값의 저장 용도로 많이 사용된다.
어떤 어셈블리 명령어(MUL, DIV, LODS 등)들은 특정 레지스터를 직접 조작하기도 한다.
(명렁어 실행 뒤에 특정 레지스터들의 값이 변경)
그리고 추가적으로 ECX와 EAX는 특수 용도로 사용되며, ECX는 반복문 명령어(LOOP)에서 반복 카운트(loop count)로 사용된다. (루프를 돌 때마다 ECX를 1씩 감소시킴)
EAX는 일반적으로 함수 리턴 값에 사용되며, 모든 Win32 API 함수들은 리턴 값을 EAX에 저장한 후 리턴한다.
# 참고
Win32 API 함수들은 내부에서 ECX와 EDX를 사용
따라서 이런 API가 호출되면 ECX와 EDX의 값은 변경됨
따라서 ECX와 EDX에 중요한 값이 저장되어 있다면 API 호출 전에 다른 레지스터나 스택에 백업 필요
나머지 범용 레지스터들의 이름은 아래와 같다.
EBP : Pointer to data on the stack (in the SS segment)
ESI : source pointer for string operations
EDI : destination pointer for string operations
ESP : Stack pointer (in the SS segment)
위 4개의 레지스터들은 주로 메모리 주소를 저장하는 포인터로 사용된다.
ESP는 스택 메모리 주소를 가리키며, 어떤 명령어들(PUSH, POP, CALL, RET)은 ESP를 직접 조작하기도 한다.
(스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 ESP를 다른 용도로 사용하지 말아야 한다.)
EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가, 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 하며, 이것을 Stack Frame 기법이라 한다.
ESI와 EDI는 특정 명령어들(LODS, STOS, REP MOVS 등)과 함께 주로 메모리 복사에 사용된다.
2) 세그먼트 레지스터
세그먼트(Segment)란 IA-32의 메모리 관리 모델에서 나오는 용어
IA-32 보호 모드에서 세그먼트란 메모리를 조각내어 각 조각마다 시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법을 말함
세그먼트는 페이징(Paging) 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용
세그먼트 메모리는 Segnet Descriptor Table(SDT)이라고 하는 곳에 기술되어 있는데, 세그먼트 레지스터는 바로 SDT의 index를 가짐
[그림 3]은 보호 모드에서의 세그먼트 메모리 모델을 나타낸다.
세그먼트 레지스터는 총 6개(CS, SS, DS, ES, FS, GS)이며 각각의 크기는 16비트(2바이트)이다.
[그림 3]은 각 세그먼트 레지스터가 가리키는 세그먼트 디스크립터(Segment Descriptor)와 가상 메모리가 조합되어 선형 주소(Linear Address)가 되며, 페이징 기법에 의해 선형 주소가 최종적으로 물리 주소(Physical Address)로 변환 된다.
만약 OS에서 페이징을 사용하지 않는다면 선형 주소는 그대로 물리 주소가 된다.
각 세그먼트 레지스터 이름은 다음과 같다.
CS : Code Segment
SS : Stack Segment
DS : Data Segment
ES : Extra(Data) Segment
FS : Data Segment
GS : Data Segment
이름 그대로 CS는 프로그램의 코드 세그먼트를 나타내며, SS는 스택 세그먼트, DS는 데이터 세그먼트를 나타낸다.
EF, FS, GS 세그먼트는 추가적인 데이터 세그먼트이다.
FS 레지스터는 애플리케이션 디버깅에도 자주 등장하는데 SEH(Structured Ex-ception Handling), TEB(Thread Environment Block), PEB(Process Environment Block) 등의 주소를 계산할 때 사용된다.
3) 프로그램 상태와 컨트롤 레지스터
EFLAGS : Flag Register
플래그(Flag) 레지스터 이름은 EFLAGS이며, 32비트(4바이트) 크기
EFLAGS 레지스터 역시 16비트의 FLAGS 레지스터의 32비트 확장 형태
EFLAGS 레지스터는 [그림 4]와 같이 각각의 비트마다 의미를 가지고 있으며, 각 비트는 1 또는 0의 값을 가진다.
이는 On/Off 혹은 True/False를 의미한다.
일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅된다.
# 참고
Flag는 단어 그대로 깃발이 올라가면 1(On/True), 내려가면 0(Off/False)
flag(ZF, OF, CF)가 중요
이유는 조건 분기 명령어(Jcc)에서 이들 Flag의 값을 확인하고 그에 따라 동작 수행 여부를 결정하기 때문
3개의 플래그 내용은 다음과 같다.
Zero Flag(ZF) : 연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅
Overflow Flag(OF) : 부호 있는 수(signed integer)의 오버플로가 발생했을 때 1로 세팅되며, MSB(Most Significant Bit)가 변경되었을 때 1로 세팅
Carry Flag(CF) : 부호 없는 수(unsigned integer)의 오버플로가 발생했을 때 1로 세팅
4) Instruction Pointer
EIP : Instruction Pointer
CPU가 처리할 명령어의 주소를 나타내는 레지스터
크기는 32비트(4바이트)이며, 16비트의 IP 레지스터의 확장 형태
CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킴
이런 형식으로 계속 명령어를 처리해 나감
범용 레지스터들과 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통하여 간접적으로 변경해야 한다.
EIP를 변경하고 싶을 때는 특정 명령어(JMP, Jcc, CALL, RET)를 사용하거나 인터럽트(Interrupt), 예외(Exception)를 발생시켜야 한다.
AX 또는 DX:AX 내용을 피연산자로 나누고, 몫은 AL 또는 AX에 저장한 다음 나머지는 AH 또는 DX에 저장
IDIV
Integer DIVide (Signed)
부호화된 나눗셈 수행
AAD
ASCII Adjust for Divide
나눗셈 결과 AX 값을 UNPACK 10진수로 보정
CBW
Convert Byte to Word
AL의 바이트 데이터를 부호 비트를 포함해 AX 워드로 확장
CWD
Convert Word to Double word
AX 워드 데이터를 부호를 포함해 DX:AX의 더블 워드로 변환
1-2. 데이터 전송 명령
메모리, 범용 레지스터, 세그먼트 레지스터로 참조되는 주소에 들어 있는 데이터 전송
조건 이동, 스택 접근, 데이터 변환 같은 특정 연산도 수행
MOV (Move)
데이터를 이동할 때 사용
제1피연산자는 데이터 이동의 목적지이고, 제2피연산자는 이동의 대상
제1피연산자는 레지스터와 메모리가 될 수 있고, 제2피연산자는 수치, 레지스터, 메모리 등이 될 수 있음
하지만 메모리에서 메모리로 이동은 불가능
형식
MOV
[제1피연산자],
[제2피연산자]
사용 예
MOV
AX,
[BP+8]
BP의 주소에 8 더한 주소에 있는 데이터 값을 AX에 대입
위 그림에서 BP의 현재 값이 0x10000004라면, BP+8은 0x1000000C가 된다.
그리고 0x1000000C에 있는 값이 1,024이므로 AX에는 1024가 입력된다.
PUSH (Push)
스택에 데이터를 삽입할 때 사용
형식
PUSH
[제1피연산자]
위 그림과 같이 스택은 커지고, 스택 포인터는 데이터 크기만큼 감소한다.
POP (Pop)
스택에서 데이터를 삭제할 때 사용
스택에서 삭제된 명령은 반환 값으로 받아 사용 가능
형식
POP
[제1피연산자]
위 그림과 같이 스택 포인터는 삭제하는 데이터 크기만큼 증가한다.
LEA (Load effective address to register)
데이터 값을 이동할 때 사용하며, MOV와 조금 다름
MOV와 LEA 명령의 공통점은 제1피연산자는 데이터 이동의 목적지지만, 제2피연산자에서 추가 연산을 처리하는 방식이 다름
예로 제2피연산자에 'BP+4'가 있을 때 MOV 명령은 'BP+4'를 주소 값 하나로 처리하지만, LEA 명령은 'BP'만 주소 값으로 인식하고 +4는 BP 주소 값의 추가 연산으로 처리
형식
LEA
[제1피연산자]
[제2피연산자]
사용 예
LEA
AX,
[BP+4]
BP의 현재 값이 0x10000004라면, MOV 명령처럼 BP+4인 0x10000008의 주소 값을 가져오는 것이 아닌 0x10000004의 주소에 있는 값에 4를 더해 AX에 대입
기타 데이터 전송 명령
MOV, PUSH, POP, LEA 외 기타 데이터 전송 명령은 다음과 같다.
명령
설명
XCHG
Exchange Register / Memory with Register
첫 번째 피연산자와 두 번째 피연산자를 바꿈
IN
Input from AL / AX to Fixed port
피연산자로 지시한 포트로 AX에 데이터를 입력
OUT
Output from AL / AX to Fixed port
피연산자가 지시한 포트로 AX의 데이터 출력
XLAT
Translate byte to AL
BX:AL이 지시한 테이블 내용을 AL로 로드
LES
Load Pointer to ES
LEA 명령과 유사한 방식으로 다른 ES 데이터의 주소 내용을 참조할 때 사용
LAHF
Load AH with Flags
플래그 내용을 AH의 특정 비트로 로드
SAHF
Store AH into Flags
AH의 특정 비트를 플래그 레지스터로 전송
PUSHF
Push Flags
플래그 레지스터 내용을 스택에 삽입
POPF
Pop Flags
스택에 저장된 플래그 레지스터 값 삭제
1-3. 논리 명령
연산 부호가 논리 연산을 지정
자리 옮김, 논리합(OR), 논리곱(AND), 기호 변환 등이 있음
AND (And)
대응되는 비트가 둘 다 1일 때만 결과가 1이고, 그 외는 모두 0
형식
AND
[제1피연산자],
[제2피연산자]
사용 예
AND
AX,
10h
AX 값이 0x08h일 때, 이를 2진수로 표현하면 1000이 된다. 0x10h는 1010이므로 명령을 수행한 후 AX 값은 1000이 된다.
OR (Or)
대응되는 비트 중 하나만 1이어도 결과가 1이고, 둘 다 0일 때만 0이 됨
형식
OR
[제1피연산자],
[제2피연산자]
사용 예
OR
AX,
10h
AX 값이 0x08h일 때, 이를 2진수로 표현하면 1000이 된다. 0x10h는 1010이므로 명령을 수행한 후 AX 값은 1010이 된다.
XOR (Exclusive Or)
대응되는 비트 중에서 한 비트가 1이고 다른 비트가 0이면 결과가 1이 됨
비트 두 개가 모두 0 또는 1일 때는 0이 됨
형식
XOR
[제1피연산자],
[제2피연산자]
사용 예
XOR
AX,
10h
AX 값이 0x08h일 때, 이를 2진수로 표현하면 1000이 된다. 0x10h는 1010이므로 명령을 수행한 후 AX 값은 0010이 된다.
NOT (Invert)
피연산자 1의 보수를 구하는 작동 코드로 각 비트를 반전시켜 줌
형식
NOT
[제1피연산자]
사용 예
NOT
AX
AX 값이 0x08h일 때, 이를 2진수로 표현하면 1000이 된다. 명령을 수행한 후 AX 값은 0111이 된다.
TEST (And function to flags, no result)
CMP 명령처럼 데이터의 두 값을 비교할 때 사용
단 CMP처럼 제1피연산자에서 제2피연산자 값을 빼는 과정이 없음
즉, 데이터 변경 없이 단순 비교만 함
형식
TEST
[제1피연산자]
[제2피연산자]
사용 예
TEST
AL
00001001b
AL 레지스터에 비트 0과 비트 3이 둘아 0인지 확인하고 싶다면, 제2피연산자에 비트 0(오른쪽의 첫 번째 비트)과 비트 3(오른쪽에서 네 번째 비트)에 1을 적고 나머지에는 0을 적은 후 TEST 명령을 실행한다. AL의 비트 0과 비트 3이 모두 0이라면 ZF가 세트된다. 비트 0과 비트 3 중 하나라도 1이면 ZF는 클리어된다. AX가 0x08h라면 비트 3이 1이므로 ZF는 클리어된다.
기타 논리 명령
AND, OR, XOR, NOT, TEST 외 기타 논리 명령은 다음과 같다.
명령
설명
SHL / SAL
Shift Left / Shift Arithmetic Left
왼쪽으로 피연산자만큼 자리옮김(이동)
SHR / SAR
Shift Right / Shift Arithmetic Right
오른쪽으로 피연산자만큼 자리옮김(이동)
ROL
Rotate Left
왼쪽으로 피연산자만큼 회전 이동
ROR
Rotate Right
오른쪽으로 피연산자만큼 회전 이동
RCL
Rotate through Carry Left
자리 올림(Carry)을 포함 왼쪽으로 피연산자만큼 회전 이동
RCP
Rotate through Carry Right
자리 올림(Carry)을 포함 오른쪽으로 피연산자만큼 회전 이동
1-4. 스트링 명령
바이트로 구성된 데이터를 메모리에서 가져오거나 메모리로 전송
REP (Repeat)
ADD나 MOVS 같은 작동 코드 앞에 위치
CX가 0이 될 때까지 뒤에 오는 스트링 명령을 반복
형식
REP
작동 코드
-
MOVS (Move String)
바이트나 워드, 더블 워드를 옮기는 명령으로 각각 MOVSB, MOVSW, MOVSD가 있음
CLD (Clear Direction) : 디렉션 플래그를 지움 LEA SI, String_1 : String 1의 주소 값을 SI (Source Index)에 저장 LEA DI, String_2 : String 2의 주소 값을 DI (Destination Index)에 저장 MOV CX, 384 : CX에 384를 저장 REP MOVSB : SI에서 DI까지 CX가 0이 될 때까지 1바이트씩 복사
기타 스트링 명령
REP, MOVS 외 기타 스트링 명령은 다음과 같다.
명령
설명
CMPS
CoMPare String
DS:SI와 ES:DI 내용을 비교한 결과에 따라 플래그 설정
SCAS
SCAn String
AL 또는 AX와 ES:DI가 지시한 메모리 내용을 비교한 결과에 따라 플래그 설정
LODS
LOaD String
SI 내용을 AL 또는 AX로 로드
STOS
STOre String
AL 또는 AX를 ES:DI가 지시하는 메모리에 저장
1-5. 제어 전송 명령
점프 (분기), 조건 점프 (조건 분기), 루프, 호출과 리턴 연산 등으로 프로그램 흐름 제어
JMP (Unconditional Jump)
대표적인 점프 명령
프로그램 실행 주소 또는 라벨로 이동
형식
JMP [제1피연산자]
사용 예
JMP 100h
주소로 직접 지정한 100h번지로 점프
String: MOV CX, 384
...
JMP String
라벨로 JMP를 지정하는 경우
이외에도 JMP 명령은 조건에 따라 다음과 같이 다양하게 사용된다.
명령
점프 조건
설명
JC
Carry Flag Set
CF = 1
CF 값이 1이면 점프
JNC
Not Carry Flag Set
CF = 0
CF 값이 0이면 점프
JE / JZ
Equal / Zero
ZF = 1
결과가 0이면 점프
JA / JNBE
above / Not below Not Equal
CF = 0 and ZF = 0
결과가 크면 점프 (부호화되지 않은 수)
JAE / JNB
above Or Equal / Not below
CF = 0
결과가 크거나 같으면 점프 (부호화되지 않은 수)
JB / JNAE
below / Not above Nor below
CF = 1
결과가 작으면 점프 (부호화되지 않은 수)
JL / JNGE
Less / Not Greater Nor Equal
SF != OF
결과가 작으면 점프 (부호화된 수)
JBE / JNA
below Or Equal / Not above
(CF or ZF) = 1
결과가 작거나 같으면 점프 (부호화되지 않은 수)
JG / JNLE
Greater / Not Less Nor Equal
ZF = 0 and SF = OF
결과가 크면 점프 (부호화되지 않은 수)
JGE / JNL
Greater Or Equal / Not Less
SF = OF
결과가 크거나 같으면 점프 (부호화된 수)
JLE / JNG
Less Or Eqal / Not Greater
ZF = 1 or SF != OF
결과가 작거나 같으면 점프 (부호화되지 않은 수)
JNE / JNZ
Not Equal / Not Zero
ZF = 0
결과가 0이 아니면 점프
JNO
Not Overflow
OF = 0
오버플로가 아니면 점프
JNP / JPO
Not Parity / Parity Odd
PF = 0
PF가 1이면 점프
JNS
Not sign
SF = 0
SF가 1이면 점프
JO
Overflow
OF = 1
오버플로 발생 시 점프
JP / JPE
Parity / Parity Even
PF = 1
PF가 1이면 점프
JS
Sign
SF = 1
SF가 1이면 점프
JCXZ
CX Zero
CX = 0
CX가 0이면 점프
above, below : 부호 없는 두 수의 크기 관계
Greater, Less : 부호 있는 두 수의 크기 관계
CALL (Call)
JMP처럼 함수 호출 시에 사용하며, 제1피연산자에 라벨 지정
하지만 CALL은 리턴 주소로 IP 주소를 백업하여 'PUSH + EIP + JMP'와 의미가 같음
형식
CALL
[제1피연산자]
RET (Return from CALL)
함수에서 호출한 곳으로 돌아갈 때 사용하는 명령
'POP EIP'와 의미가 같음
다음은 CALL과 RET의 예시이며, AX에는 0x08h 값이 저장되었다고 가정한다.
CALL SUBR ADD AX, 10h ... SUBR : INC AX ... RET
위 에에서는 SUBR 함수를 호출하면, SUBR 라벨이 있는 곳에서 RET까지 실행된다.
이때 INC AX가 있으므로 AX는 0x09h가 된다.
RET 명령이 실행되면 CALL 다음 줄인 'ADD AX, 10h'를 실행한다.
따라서 AX는 0x19h가 될 것이다.
LOOP (Loop CX times)
문장들의 블록을 지정된 횟수만큼 반복
CX는 자동으로 카운터로 사용하며, 루프를 반복할 때마다 감소
형식
LOOP
[제1피연산자]
사용 예
MOV AX, 0 MOV CX, 5 L1 : INT INT AX LOOP L1
제1피연산자는 라벨이 되며, 예에서 L1이 CX 숫자만큼 다섯 번 회전하므로 결과적으로 AX는 5가 된다.
INT (Interupt)
인터럽트가 호출되면 CS:IP와 플래그를 스택에 저장하고, 그 인터럽트와 관련한 서브 루틴 실행
제1피연산자는 인터럽트의 번지로 00~FF(256개) 번지가 할당되어 있고, INT 10H와 INT 21H 인터럽트 많이 사용