Data Transfer Instructions
1. Intro
- CPU 단으로 보내기 전에 컴파일러가 type checking을 해준다.
- CPU는 자기가 할 수 있는 연산이면 뭐든지 다 하기 때문에 프로그래머가 엄격하게 체크해주어야 한다.
2. Operand Types
- 피연산자에는 기본적으로 세 가지 유형이 있다.
- Immediate: 숫자 리터럴 표현
- 레지스터: CPU의 이름이 지정된 레지스터
- 메모리: 메모리 위치를 참조
3. Direct Memory Operands
- 변수 이름은 메모리에서 오프셋을 참조
.data
var1 BYTE 10h
mov al var1
- var1의 사이즈는 1바이트이고 10h를 포함하고 있다.
- 마지막 명령어는 al 레지스터에 값을 복사한다.
- var이 offset 10400h에 위치한다고 가정하면
- 마지막 문장은 A0 00010400의 기계 명령어로 바뀐다.
- bracket을 사용해 메모리를 참조할 수 있다.
- mov a1, [var1]
4. MOV Instruction
소스 연산자에서 대상 피연산자로 데이터를 복사한다.
MOV destination source
대상 피연산자는 값이 변경되지만, 소스 피연산자는 변경되지 않는다.
다음 규칙만 지킨다면 MOV를 매우 유연하게 쓸 수 있다.
- 두 피연산자의 크기가 같아야한다.
- 두 피연산자 모두 메모리 피연산자일 수 없다.
- instruction pointer register는 대상 피연산자가 될 수 없다.
메모리간 데이터 이동
소스 피연산자의 값을 레지스터로 옮겨서, 이후 메모리 피연산자에 값을 할당해야 한다.
.data var1 WORD ? var2 WORD ? .code mov ax, var1 mov var2, ax
정수 상수를 변수나 레지스터에 복사할 때, 상수의 바이트 수를 고려해야 한다.
Overlapping Values
.data oneByte BYTE 78h oneWord WORD 1234h oneDword DWORD 12345678h .code mov eax, 0 ; EAX = 00000000h mov al, oneByte ; EAX = 00000078h mov ax, oneWord ; EAX = 00001234h mov eax, oneDword ; EAX = 12345678h mov ax, 0 ; EAX = 12340000h
5. Zero/sign Extension of Integers
작은 크기의 피연산자에서 큰 크기의 피연산자로 복사
MOV 명령어는 작은 피연산자에서 큰 피연산자로 직접 데이터를 복사할 수 없다.
예를 들어 16비트의 unsigned 변수인 count를 ECX로 이동해야 한다면, EXC를 0으로 설정한 후, count를 CX로 이동시킬 수 있다.
.data count WORD 1 .code mov exc, 0 mov cx, count
만약, -16을 같은 방법으로 적용한다면?!
.data count SWORD -16 ; FFF0h .code mov ecx, 0 mov cx, count ; ECX = 0000FFF0h(+65520)
완전히 다른 수가 ECX에 저장된다.
mov ecx, 0FFFFFFFFh mov cx, count ; ECX = FFFFFFF0h(-16)
다행히도, 인텔은 MOVZX와 MOVSX 명령어를 통해 부호 없는 정수, 부호 있는 정수 모두 처리할 수 있다.
MOVZX Instruction
소스 피연산자의 내용을 대상 피연산자에 복사하고 16비트 또는 32비트로 값을 zero확장한다.
부호 없는 정수와 함께 사용
예시
mov bx, 0A69Bh movzx eax, bx ; EAX = 0000A69Bh movzx edx, bl ; EDX = 0000009Bh movzx cx, bl ; CX = 009Bh ======================================== .data byte1 BYTE 9Bh word1 WORD 0A69Bh .code movzx eax, word1 ; EAX = 0000A69Bh movzx edx, byte1 ; EDX = 0000009Bh movzx cx, byte1 ; CX = 009Bh
MOVSX Instruction
signed integer에서 사용
mov bx, 0A69Bh movsx eax, bx ; EAX = FFFFA69Bh movsx edx, bl ; EDX = FFFFFF9Bh movsx cx, bl ; CX = FF9Bh
6.LAHF and SAHF Instructions
- LAHF: EFLAGS 레지스터의 하위 바이트를 AH 레지스터에 복사한다.
- Sign, Zero, Auxiliary Carry, Parity, Carry
.data
saveflags BYTE ?
.code
lahf ; load flags into AH
mov saveflags, ah ; AH를 변수에 저장
- SAHF: AH 레지스터에 저장된 플래그 값을 EFLAGS 레지스터의 하위 8비트에 저장
mov ah, saveflags ; 플래그를 AH에 로드
sahf ; 플래그 레지스터에 복사
7. XCHG Instruction
- 두 피연산자끼리 교환
- XCHG는 immediate operands를 사용하지 못하는 것을 제외한 MOV와 규칙 똑같다
swap 예시
mov ax, var1
xchg ax, var2
mov var1, ax
8. Direct-Offset Operands
- 명시적인 label 없을 수 있는 메모리 위치에 엑세스 가능
arrayB BYTE 10h, 20h, 30h, 40h, 50h
mov al, arrayB ; AL = 10h
mov al, [arrayB+1] ; AL = 20h
mov al, [arrayB+2] ; AL = 30h
.data
arrayW WORD 100h, 200h, 300h
.code
mov ax, arrayW ; AX = 100h
mov ax, [arrayW + 2] ; AX = 200h
.data
arrayD DWORD 10000h, 20000h
.code
mov eax, arrayD ; EAX = 10000h
mov eax, [arrayD+4] ; EAX = 20000h
Addition and Subtraction
1. INC and DEC Instructions
- INC: 피연산자에 1 더함
- DEC: 피연산자에 1뺌
- INC와 DEC는 Carry flag에 영향을 주지 않는다.
2. ADD Instruction
- 목적지 연산자에 소스 연산자를 더한다. 같은 사이즈이어야 한다.
- 목적지 연산자에 따라서 플래그가 변경될 수 있다.
- 예를 들어, 덧셈 결과가 0이면 Zero flag
3. SUB Instruction
- 목적지 피연산자에서 소스 피연산자를 뺀다.
- 목적지 피연산자에 따라 플래그 변경
4. NEG Instruction (negate)
- 어떤 수의 표현 방법을 2의 보수법을 통해 부호를 반대로 만드는 명령어
5. Implementing Arithmetic Expressions
- Rval = -Xval + (Yval - Zval);
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40
; -Xval
mov eax, Xval
neg eax
; (Yval - Zval)
mov ebx, Yval
sub ebx, Zval
; add terms and store
add eax, ebx
mov rVal, eax
6. Flags Affected by Addition and Subtraction
- 산술 명령어를 실행하고 결과물에 대한 정보를 알고싶다
- 음수인지, 0인지 등
- 목적지 피연산자에 비해 너무 큰 or 작은 값인지
- CPU 상태 플래그 값을 이용해 산술 연산의 결과를 확인한다.
- 상태 플래그들에 대한 개요
- Carry flag: unsigned 정수 오버플로우 나타낸다.
- Overflow flag: signed 정수 오버플로우 나타낸다.
- Zero flag: 연산 결과가 0
- Sign flag: 연산 결과가 음수임을 나타낸다.
- Parity flag: 산술 or 불리언 명령어 실행 직후 대상 연산자의 가장 작은 바이트에서 1비트가 짝수인지 홀수인지
- Auxiliary Carry flag: 대상 연산자의 가장 작은 바이트에서 3번째 비트 자리에서 1비트가 올라갈 때 설정됨
- Unsigned Operations: Zero, Carry and Auxiliary Carry
- Zero flag: 연산결과 0일때 설정
- 덧셈과 Carry flag: 부호 없는 정수를 더할 때, 대상 피연산자의 MSB에서의 캐리 값
- CF = 1은 합이 대상 피연산자의 저장 크기를 초과할 때
- 뺼셈과 Carry flag: 더 작은 부호 없는 정수에서 더 큰 부호 없는 정수를 뺄 때 설정
- Auxiliary Carry Flag (AC): 목적지 피연산자의 3번 비트에서 캐리가 발생하거나 빌림이 발생할 때 설정
- Parity flag: 목적지 피연산자의 가장 작은 바이트에서 1비트가 홀수 개 있을 경우, 이 플래그 설정
- Signed Operations
- Sign Flag: 결과 음수
- Overflow Flag: 부호 있는 정수의 산술 연산의 결과가 오버플로우 or 언더플로우일 때 설정
- 음수 피연산자 두 개를 더했는데 양수 합이 생성되면 오버플로우 발생
- 영수 피연산자 두 개를 더했는데 음수 합이 생성되면 오버플로우 발생
- MSB에서 carry in과 carry out을 XOR하여 참이 나오면 Overflow
- NEG Instruction의 경우 [AL] = -128일때, neg al하면 OF = 1
- CPU는 signed인지 unsigned인지 구분 없이 그냥 산술하므로 프로그래머가 플래그를 적절히 이용하여 결정하여야 한다.
Data-Related Operators and Directives
- 데이터 관련 연산자와 지시어는 실행 가능한 명령어가 아니다.
1. OFFSET Operator
- data label의 offset을 반환한다.
- offset은 데이터 세그먼트의 시작부터 해당 라벨까지의 바이트 거리를 나타낸다.
- 예를 들어, bVal이 offset 00404000에 위치한다고 하면
.data
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal DWORD ?
mov esi, OFFSET bVal ; ESI = 00404000h
mov sei, OFFSET wVal ; ESI = 00404001h
mov sei, OFFSET dVal ; ESI = 00404003h
mov sei, OFFSET dVal2 ; ESI = 00404007h
- 변수의 offset으로 DWORD 변수를 초기화하여 포인터를 만들 수 있다.
.data
bigArray DWORD 500 DPU(?)
pArray DWORD bigArray
mov esi, pArray
- pArray는 bigArray의 시작 부분을 가리킨다.
- 포인터의 값이 ESI에 로드되므로 레지스터는 배열의 시작 부분을 가리킬 수 있다.
2. ALIGN Directive
- 변수를 byte, word, doubleword 경계에 맞춰 정렬한다.
- ALIGN bound
- Bound는 1, 2, 4, 8 또는 16일 수 있다.
- 1: 다음 변수는 1바이트 경계에 맞춰 정렬된다.
- 2: 다음 변수는 짝수 주소에 맞춰 정렬된다.
- 4: 다음 주소는 4의 배수
- 16: 16배수
- 정렬을 맞추기 위해 변수 앞에 빈 바이트를 삽입할 수도 있다.
- CPU는 짝수 주소에 저장된 데이터를 홀수 주소에 저장된 데이터보다 더 빠르게 처리할 수 있다.
- 즉, 메모리를 낭비하더라도 데이터 버스를 빠르게 하기 위함이다.
bVal BYTE ? ; 00404000h
ALIGN 2
wVal WORD ? ; 00404002h
bVal BYTE ? ; 00404004h
ALIGN 4
dVal DWORD ? ; 00404008h
dval2 DWORD ? ; 0040400Ch
3. PTR Operator
- PTR은 표준 어셈블러 데이터 유형과 조합하여 사용해야 한다.
.data
myDouble DWORD 12345678h
.code
mov ax, myDouble
mov ax, WORD PTR myDouble
mov ax, WORD PTR [myDouble+2] ; 1234h
mov bl, BYTE PTR myDouble ; 78h
작은 값들을 큰 대상으로 옮기기
.data wordList WORD 5678h, 1234h\ .code mov eax, DWORD PTR wordList ; EAX = 12345678h
4. TYPE Operator
변수의 크기를 바이트 단위로 반환
.data var1 BYTE ? var2 WORD ? var3 DWORD ? var4 QWORD ?
- TYPE var1 -> 1
- TYPE var2 -> 2
- TYPE var3 -> 4
- TYPE var4 -> 8
5. LENGTHOF Operator
- 배열에서 element의 개수를 세준다. 동일한 줄에서
.data
byte1 BYTE 10, 20, 30
array1 WORD 30 DUP(?), 0, 0
array2 WORD 5 DUP(3 DUP(?))
array3 DWORD 1, 2, 3, 4
digitStr BYTE "12345678", 0
- LENGTHOF byte1 -> 3
- LENGTHOF array1 -> 30 + 2
- LENGTHOF array2 -> 5 * 3
- LENGTHOF array3 -> 4
- LENGTHOF digitStr -> 9
myArray BYTE 10, 20, 30, 40, 50
BYTE 60, 70, 80, 90, 100 ; LENGTHOF myArray 5
myArray1 BYTE 10, 20, 30, 40, 50,
60, 70, 80, 90, 100 ; LENGTHOF myArray 10
6. SIZEOF Operator
- LENGTHOF 와 TYPE의 곱을 반환
.data
intArray WORD 32 DUP(0)
.code
mov eax, SIZEOF intArray ; EAX = 64
7. LABEL Directive
- 이 지시어를 사용하면 공간을 할당하지 않고 크기 속성을 지정하면서 레이블을 삽입할 수 있다.
.data
val16 LABEL WORD
val32 DWORD 12345678h
.code
mov ax, val16 ; AX = 5678h
mov dx, [val16+2] ; DX = 1234h
- val16과 val32는 동일한 곳을 가리킨다.
- LABEL의 일반적인 사용법은 데이터 세그먼트에서 다음으로 선언된 변수의 대체 이름과 크기 속성을 제공
Indirect Addressing
- [var1 + 1]과 같이 상수적인 오프셋을 사용해서 주소 지정하는 것은 비실용적.
- 대신, 레지스터를 포인터로 사용해 레지스터의 값을 조작한다.
1. Indirect Operands
Protected Mode
간접 피연산자는 bracket으로 둘러싸인 32비트 범용 레지스터가 될 수 있다.
.data byteVal BYTE 10h .code mov esi, OFFSET byteVal mov al, [esi] ; AL = 10h
MOV 명령은 간접 피연산자를 소스로 사용하여, ESI의 오프셋을 참조한 바이트를 AL에 넣는다.
대상 피연산자가 간접 주소 지정을 사용하는 경우, 새 값을 레지스터가 가리키는 위치의 메모리에 넣는다.
"mov [esi], bl"의 경우, BL 레지스터의 내용이 ESI가 가리키는 메모리 위치로 복사된다.
Using PTR with Indirect Operands
inc [esi] ; error: 연산자의 사이즈가 정해져야 한다. inc BYTE PTR [esi]
- PTR을 통해 피연산자의 크기를 확인할 수 있다.
2. Arrays
.data
arrayB BYTE 10h, 20h, 30h
.code
mov esi, OFFSET arrayB
mov al, [esi] ; AL = 10h
inc esi
mov al, [esi] ; AL = 20h
inc esi
mov al, [esi] ; AL = 30h
- 16비트 정수 배열을 사용한다면, ESI에 2를 더해주어야 한다.
.data
arrayW WORD 1000h, 2000h, 3000h
.code
mov esi, OFFSET arrayW
mov ax, [esi] ; AX = 1000h
add esi, 2
mov ax, [esi] ; AX = 2000h
add esi, 2
mov ax, [esi] ; AX = 3000h
3. Indexed Operands
인덱스 피연산자는 레지스터에 상수를 더하여 유효한 주소 생성, 따라서 32비트 범용 레지스터 사용
constant[reg], [constant + reg]
- 첫 번째 표기 형식은 변수 이름과 레지스터를 결합
인덱스 피연산자는 배열 처리에 이상적이다.
.data arrayB BYTE 10h, 20h, 30h .code mov esi, 0 mov al, arrayB[esi] ; AL = 10h
- 인덱스 레지스터는 첫 번째 배열 요소에 접근하기 전에 0으로 초기화되어야 한다.
- 마지막 문장은 ESI를 arrayB의 오프셋에 추가한다
- [arrayB + esi] 식으로 생성된 주소가 참조되고 메모리의 바이트가 AL로 복사된다.
Adding Displacements
두 번째 유형의 인덱스 지정은 레지스터와 상수 오프셋 결합
.data arrayW WORD 1000h, 2000h, 3000h .code mov esi, OFFSET arrayW mov ax, [esi] ; AX = 1000h mov ax, [esi + 2] ; AX = 2000h mov ax, [esi + 4] ; AX = 3000h
Scale Factors in Indexed Operands
.data arrayD DWORD 100h, 200h, 300h, 400h .code mov esi, 3 * TYPE arrayD mov eax, arrayD[esi]
일일이 계산하면 귀찮으므로 scale factor라는 방법을 통해 편하게 계산
.data arrayD DWORD 1, 2, 3, 4 .code mov esi, 3 mov eax, arrayD[esi*4] mov esi, 3 mov eax, arrayD[esi*TYPE arrayD]
4. Pointers
- 다른 변수의 주소를 포함하는 변수
- 포인터는 보유하는 주소를 런타임에서 수정할 수 있기 때문에 배열과 데이터 구조를 조작하는 데에 매우 유용한 도구이다.
.data
arrayB byte 10h, 20h, 30h, 40h
ptrB dword arrayB
ptrB는 arrayB의 오프셋을 포함
prtB dword OFFSET arrayB
- 선택적으로 OFFSET 연산자로 선언하여 더 명확하게 할 수 있다
TYPEDEF Operator
TYPEDEF 연산자는 이미 있는 built-in type으로 새로운 user-defined type을 만들어준다.
PBYTE TYPEDEF PTR BYTE .data arrayB BYTE 10h, 20h, 30h, 40h ptr1 PBYTE ? ptr2 PBYTE arrayB
위 선언을 통해 바이트에 대한 포인터인 새 데이터 타입 PBYTE를 생성
JMP and LOOP Instructions
- 어셈블리에서도 conditional한 명령들 제공
- status flag에 따라 현재 명령어 실행할지 다른 명령어 실행할지
- 특정 메모리 주소로 현재의 control을 이동할 수 있다. jump할 수 있다.
- Unconditional Transfer: EIP에 새 주소가 로드된다.
- Conditional Transfer: 특정 플래그에 따라서 분기할지 말지 결정, ECX 값에 따라서도 결정
1. JMP Instruction
JMP destination
- JMP 명령어는 코드 레이블로 식별된 목적지로 무조건적인 이동을 유발
- 목적지의 오프셋이 EIP로 이동되어 새 위치에서 실행이 계속된다.
루프 생성
JMP 명령은 루프 상단의 레이블로 점프하여 루프 생성
top: . . jmp top ; repeat the endless loop
JMP는 무조건적이므로 루프가 끝나는 방법을 찾을 때까지 루프가 무한히 반복
2. LOOP Instruction
ECX 카운터에 따른 루프
이명령어는 일정한 횟수만큼 반복
ECX는 자동으로 카운터로 사용되며 루프가 반복될 때마다 1씩 감소한다.
LOOP destination
- 루프 대상은 현재 위치 카운터로부터 -128바이트에서 +127바이트 이내에 있어야 한다.
동작 방식: ECX에서 1을 빼고 ECX를 0과 비교
ECX가 0이 아니라면, 대상으로 지정된 라벨로 점프
mov ax, 0 mov ecx, 5 L1: inc ax loop L1
- AX=5, ECX=0
Loop 사용 시 흔한 오류
루프 시작 전 ECX를 0으로 초기화
이러한 경우 LOOP 명령은 ECX를 FFFFFFFFh로 감소시키고 루프가 매우 많이 반복됨
CX가 루프 카운터인 경우에는 6만번
루프 내에서 ECX를 수정해야 하는 경우, 루프의 시작 부분에서 변수에 저장 후, LOOP 명령 직전에 복원할 수 있다.
.data count DWORD ? .code mov ecx, 100 top: mov count, ecx . mov ecx, 20 . mov ecx, count loop top
Nested Loops
.data
count DWORD ?
.code
mov ecx, 100
L1:
mov count, ecx
mov ecx, 20
L2:
.
.
loop L2
mov ecx count
loop L1
- 일반적으로, 2레벨 이상으로 중첩된 루프는 작성하기 어렵다.
- 만약 깊은 중첩이 요구된다면, 일부 내부 루프를 서브루틴으로 옮기자.
3. Summing an Integer Array
- 배열의 주소를 인덱스 용도로 사용할 레지스터에 할당
- 루프 카운터를 배열 길이로 초기화
- 합을 누적할 레지스터에 0을 할당
- 루프의 시작을 표시하는 레이블 만든다.
- 루프 몸체에서 합에 배열 요소 하나를 더한다.
- 다음 배열 요소를 가리키도록 한다.
- LOOP 반복
.data
intarray DWORD 10000h, 20000h, 30000h, 40000h
.code
main PROC
mov edi, OFFSET intarray
mov ecx, LENGTHOF intarray
mov eax, 0
L1:
add eax, [edi]
add edi, TYPE intarray
loop L1
invoke ExitProcess, 0
main ENDP
END main
4. Copying a String
.data
source BYTE "This is the source string", 0
target BYTE SIZEOF source DUP(0)
.code
main PROC
mov esi, 0
mov ecx, SIZEOF source
L1:
mov al, source[esi]
mov target[sei], al
inc esi
loop L1
invoke ExitProcess, 0
main ENDP
END main
- 메모리끼리 MOV 불가하므로 레지스터 사용
'CS > System programming' 카테고리의 다른 글
Procedures (0) | 2023.04.09 |
---|---|
Assembly Language Fundamentals (0) | 2023.03.20 |
X86 Processor Architecture (0) | 2023.03.02 |
Basic concept (0) | 2023.02.23 |