In high-level languages, compilers perform strict type checking
On the other hand, assemblers let you do just about anything you want, as long as the processor’s instruction set can do what you ask
Introduction
고급 프로그래밍 언어에서는 컴파일러가 엄격한 타입 검사를 수행하여 변수와 데이터의 불일치와 같은 가능한 오류를 방지합니다. 반면, 어셈블러는 프로세서의 명령어 세트가 원하는 작업을 수행할 수 있는 한, 원하는 작업을 수행할 수 있습니다. 어셈블리 언어는 데이터 저장 및 기계 특정 세부 사항에 주의를 기울이도록 강요합니다.
x86 instruction formats
Instructions can have zero, one, two, or three operands
There are three basic types of operands
Operand | Description |
---|---|
reg8 | 8-bit general purpose register: AH, AL, BH, BL, CF, CL, DH, DL |
reg16 | 16-bit general purpose register: AX, BX, CX, DX, SI, DI, SP, BP |
reg32 | 32-bit general purpose register: EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP |
reg | Any general-purpose register |
sreg | 16-bit segment register: CS, DS, SS, ES, FD, GS |
imm | 8-, 16-, or 32-bit immediate value |
imm8 | 8-bit immediate byte value |
imm16 | 16-bit immediate word value |
imm32 | 32-bit immediate doubleword value |
reg/mem8 | 8-bit operand, which can be an 8-bit general register or memory byte |
reg/mem16 | 16-bit operand, which can be a 16-bit general register or memory word |
reg/mem32 | 16-bit operand, which can be a 16-bit general register or memory doubleword |
mem | An 8-,16- or 32-bit memory operand |
2.Operand Types
x86 명령어 형식에는 0, 1, 2, 3개의 피연산자가 있을 수 있습니다. 피연산자의 기본 유형은 다음과 같습니다.
이러한 피연산자들은 다양한 크기의 레지스터와 메모리 영역을 사용하여 값을 저장하거나 전달할 수 있습니다. 표에서 볼 수 있듯이, 각각의 피연산자는 다양한 크기(8비트, 16비트, 32비트)와 유형(reg, sreg, imm, reg/mem 등)을 가질 수 있습니다.
예를 들어, 레지스터 피연산자는 8비트, 16비트, 32비트 크기의 일반 목적 레지스터를 사용할 수 있으며, 이들은 각각 AH, AL, BH, BL 등과 같은 이름으로 지정됩니다.
즉, x86 어셈블리 언어에서는 다양한 크기와 유형의 피연산자를 사용하여 데이터를 저장하고 전송하는 데 사용되는 명령어 형식을 제공합니다. 이를 통해 프로그래머는 레지스터와 메모리를 효율적으로 사용하여 복잡한 연산을 수행할 수 있습니다.
Variable names are references to offsets within the data segment
• E.g.) .data, var1 BYTE 10h, mov a1 var1
3.Direct Memory Operands
변수 이름은 데이터 세그먼트 내의 오프셋에 대한 참조입니다. 예를 들어, 변수 var1의 크기 속성은 바이트이고 10진수 값이 있습니다. 마지막 명령어는 이 값을 AL 레지스터로 복사합니다. var1이 오프셋 10400h에 위치한다면, 마지막 명령어는 다음 기계 명령어로 조립됩니다: A0 00010400. 첫 번째 바이트는 연산 코드(opcode)이고 나머지 부분은 var1의 32비트 16진수 주소입니다. 대괄호를 사용하여 직접 피연산자를 선택적으로 사용할 수 있습니다.
Copy data from a source operand to a destination operand
Basic format: MOV destination, source
Memory to Memory
4.MOV Instruction
MOV 명령어는 데이터를 원본 피연산자에서 목적 피연산자로 복사합니다. 기본 형식은 다음과 같습니다: MOV destination, source. 목적 피연산자의 내용이 변경되지만 원본 피연산자는 변경되지 않습니다. 데이터의 오른쪽에서 왼쪽으로 이동하는 것은 C++ 또는 Java의 할당문과 유사합니다.
MOV는 다음 규칙을 지키는 한 피연산자 사용에 매우 유연합니다:
Memory to Memory
단일 MOV 명령어를 사용하여 데이터를 한 메모리 위치에서 다른 메모리 위치로 직접 이동할 수 없습니다. 대신 원본 피연산자의 값을 레지스터로 이동한 다음 메모리 피연산자에 값을 할당해야 합니다. 정수 상수에 필요한 최소 바이트 수를 고려하여 변수 또는 레지스터로 복사해야 합니다.
Overlapping Values
오른쪽 코드 예제는 동일한 32비트 레지스터를 다양한 크기의 데이터로 수정하는 방법을 보여줍니다. oneWord가 AX로 이동하면 AL의 기존 값이 덮어쓰여집니다. oneDword가 EAX로 이동하면 AX가 덮어쓰여집니다. 마지막으로 0이 AX로 이동하면 EAX의 하위 절반을 덮어
Copying Smaller Values to Larger Ones
.data
signedVal SWORD -16 ; FFF0h (-16)
.code
mov ecx,0
mov cx,signedVal ;ECX = 0000FFF0h (+65,520)
mov ecx,0FFFFFFFFh
mov cx,signedVal ;ECX = FFFFFFF0h (-16)
MOVZX Instruction (move with zero-extend)
MOVZX reg32,reg/mem8
MOVZX reg32,reg/mem16
MOVZX reg16,reg/mem8
.data
byteVal BYTE 10001111b
.code
movzx ax,byteVal ;AX = 000000001000111b
mov bx, 0A69Bh
movzx eax, bx ; EAX = 0000A69Bh
movzx edx, b1 ; EDX = 0000009Bh
movzx cx, b1 ; CX = 009Bh
.data
byte1 BYTE 9Bh
word1 WORD 0A69Bh
.code
movzx eax,word1 ; EAX = 0000A69Bh
movzx edx,byte1 ; EDX = 0000009Bh
movzx cx,byte ; CX = 009Bh
MOVSX Instruction (move with sign-extend)
MOVSX reg32,reg/mem8
MOVSX reg32,reg/mem16
MOVSX reg16,reg/mem8
.data
byteVal BYTE 10001111b
.code
movsx ax,byteVal ; AX = 1111111110001111b
mov bx,0A69Bh
movsx eax,bx ; EAX = FFFFA69Bh
movsx edx,bl ; EDX = FFFFFF9Bh
movsx cx,bl ; CX = FF9Bh
이 섹션에서는 정수의 제로/부호 확장에 대해 설명하고 있습니다.
Copying Smaller Values to Larger Ones
MOV 명령어는 작은 피연산자에서 큰 피연산자로 데이터를 직접 복사할 수 없습니다. 예를 들어, 부호 없는 16비트 정수 count를 32비트 정수 ECX로 옮기려면 ECX를 0으로 설정한 다음 count를 CX로 이동할 수 있습니다.
그러나 동일한 접근 방식을 사용하여 값이 -16인 부호 있는 정수를 복사하면 ECX의 값(65,520)은 -16과 완전히 다릅니다. 부호 확장을 사용하여 출처 피연산자의 최상위 비트(1)를 목적 피연산자인 ECX의 상위 16비트로 채울 수 있습니다. 다행히도 인텔은 부호 없는 정수와 부호 있는 정수를 처리하는 MOVZX와 MOVSX 명령어를 제공합니다.
MOVZX Instruction (move with zero-extend)
MOVZX 명령어는 소스 피연산자의 내용을 대상 피연산자로 복사하고 값을 16비트 또는 32비트로 확장하여 0으로 채웁니다. 이 명령어는 부호 없는 정수에만 사용됩니다. 세 가지 변형이 있습니다.
MOVZX reg32,reg/mem8
MOVZX reg32,reg/mem16
MOVZX reg16,reg/mem8
첫 번째 피연산자(레지스터)는 대상이고 두 번째 피연산자는 소스입니다. 소스 피연산자는 상수가 될 수 없습니다.
.data
byteVal BYTE 10001111b
.code
movzx ax,byteVal ;AX = 0000000010001111b
위 예제에서 byteVal의 값을 ax 레지스터로 옮기고 나머지 비트를 0으로 채웁니다.
다음 예제들은 모든 피연산자에 레지스터를 사용하여 가능한 모든 크기 변형을 보여줍니다:
mov bx, 0A69Bh
movzx eax, bx ; EAX = 0000A69Bh
movzx edx, b1 ; EDX = 0000009Bh
movzx cx, b1 ; 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
이 예제들에서 볼 수 있듯이, MOVZX 명령어는 작은 크기의 값을 더 큰 레지스터로 옮길 때 상위 비트를 0으로 채워줍니다. 이렇게 함으로써 부호 없는 정수를 올바르게 확장할 수 있습니다.
MOVSX Instruction (move with sign-extend) 은 소스 피연산자의 내용을 목적지 피연산자로 복사하고 값을 16 또는 32비트로 부호 확장합니다. 이 명령은 부호 있는 정수에만 사용됩니다. MOVSX는 다음 세 가지 변형이 있습니다:
MOVSX reg32,reg/mem8
MOVSX reg32,reg/mem16
MOVSX reg16,reg/mem8
피연산자는 작은 피연산자의 최상위 비트를 취하여 목적지 피연산자의 확장된 비트에 반복(복제)하여 부호 확장됩니다. 예를 들면:
.data
byteVal BYTE 10001111b
.code
movsx ax,byteVal ; AX = 1111111110001111b
다음 예제는 모든 피연산자에 레지스터를 사용하여 가능한 모든 크기 변형을 보여줍니다:
mov bx,0A69Bh
movsx eax,bx ; EAX = FFFFA69Bh
movsx edx,b1 ; EDX = FFFFFF9Bh
movsx cx,b1 ; CX = FF9Bh
이러한 예제들에서 볼 수 있듯이, MOVSX 명령어는 작은 크기의 값을 더 큰 레지스터로 옮길 때 부호를 확장하여 부호 있는 정수를 올바르게 처리할 수 있습니다.
The LAHF (load status flags into AH) instruction
.data
saveflags BYTE ?
.code
lafh ; load flags into AH
mov saveflags,ah ; save them in a variable
The SAHF (store AH into status flags) instruction
mov ah,saveflags ; load saved flags into AH
sahf ; copy into Flags register
LAHF (load status flags into AH) 명령은 EFLAGS 레지스터의 하위 바이트를 AH 레지스터로 복사합니다. 다음 플래그들이 복사됩니다: Sign, Zero, Auxiliary Carry, Parity, and Carry. 이 명령어를 사용하면 변수에 플래그의 사본을 쉽게 저장할 수 있습니다.
.data
saveflags BYTE ?
.code
lafh ; load flags into AH
mov saveflags,ah ; save them in a variable
SAHF (store AH into status flags) 명령은 AH 레지스터의 내용을 EFLAGS (또는 RFLAGS) 레지스터의 하위 바이트로 복사합니다.
mov ah,saveflags ; load saved flags into AH
sahf ; copy into Flags register
이렇게 LAHF와 SAHF 명령어를 사용하여 플래그 레지스터의 상태를 저장하고 복원할 수 있습니다. 이 기능은 특정 연산이 완료된 후 플래그 레지스터를 이전 상태로 되돌려 놓아야 할 때 유용합니다.
Exchange the contents of two operands
There are three variants:
XCHG reg, reg
XCHG reg, mem
XCHG mem, reg
The rules for operands are the same as those for the MOV instruction
xchg ax,bx ; exchange 16-bit regs
xchg ah,al ; exchange 8-bit regs
xchg var1,bx ; exchange 16-bit mem op with BX
xchg eax,ebx ; exchange 32-bit regs
To exchange two memory operands, use a register as a temporary container and combine MOV with XCHG
mov ax,val1
xchg ax,val2
mov val1,ax
XCHG 명령어는 두 피연산자의 내용을 교환합니다. 다음 세 가지 변형이 있습니다:
XCHG reg, reg
XCHG reg, mem
XCHG mem, reg
피연산자에 대한 규칙은 MOV 명령어와 동일합니다. 단, XCHG는 즉시 피연산자를 허용하지 않습니다.
xchg ax,bx ; exchange 16-bit regs
xchg ah,a1 ; exchange 8-bit regs
xchg var1,bx ; exchange 16-bit mem op with BX
xchg eax,ebx ; exchange 32-bit regs
두 메모리 피연산자를 교환하려면 레지스터를 임시 컨테이너로 사용하고 MOV와 XCHG를 결합합니다.
mov ax,val1
xchg ax,val2
mov val1,ax
You can add a displacement to the name of a variable, creating a direct-offset operand
This lets you access memory locations that may not have explicit labels
arrayB BYTE 10h,20h,30h,40h,50h
mov al, arrayB ; AL = 10h
mov al, [arrayB+1] ; AL = 20h
mov al, [arrayB+2] ; AL = 30h
mov al, array[B+20] ; AL = ??
Word and Doubleword Arrays
.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
변수 이름에 이동 값을 추가하여 직접 오프셋 피연산자를 만들 수 있습니다. 이를 통해 명시적 레이블이 없는 메모리 위치에 액세스할 수 있습니다.
arrayB BYTE 10h,20h,30h,40h,50h
mov a1, arrayB ; AL = 10h
arrayB의 오프셋에 1을 추가하여 배열의 두 번째 바이트에 액세스할 수 있습니다.
mov a1, [arrayB+1] ; AL = 20h
mov a1, [arrayB+2] ; AL = 30h
오프셋에 상수를 더함으로써 변수의 오프셋과 효과적인 주소를 생성하는 표현식이 만들어집니다. 이 표현식을 괄호로 묶어 주소에서 메모리 내용을 참조하는 것이라는 것을 명확하게 합니다. 괄호는 MASM에서 필수적이지는 않지만, 명확성을 위해 사용하는 것이 좋습니다.
MASM에는 효과적인 주소에 대한 범위 검사가 내장되어 있지 않습니다.
mov a1, array[B+20] ; AL = ??
예를 들어, arrayB가 5 바이트를 저장하고 있다면, 이 명령어는 배열 외부의 메모리 바이트를 검색합니다. 결과적으로 이는 논리적인 버그가 될 수 있으므로 배열 참조를 확인할 때 주의해야 합니다.
단어와 더블워드 배열에서는 16비트 워드의 배열에서 각 배열 요소의 오프셋이 이전 요소보다 2바이트 더 멀리 있습니다.
.data
arrayW WORD 100h,200h,300h
.code
mov ax,arrayW ; AX = 100h
mov ax,[arrayW+2] ; AX = 200h
비슷하게, 더블워드 배열의 두 번째 요소는 첫 번째 요소보다 4 바이트 더 멀리 있습니다.
.data
arrayD DWORD 10000h,20000h
.code
mov eax,arrayD ; EAX = 10000h
mov eax,[arrayD+4] ; EAX = 20000h
직접 오프셋 피연산자를 사용하면 명시적인 레이블이 없는 메모리 위치에 액세스할 수 있습니다. 더블워드 배열이나 워드 배열에서 각 요소의 오프셋은 이전 요소보다 일정한 바이트 수만큼 더 멀리 있습니다. 이를 통해 배열의 요소를 쉽게 참조하고 값을 불러오거나 저장할 수 있습니다. 하지만 효과적인 주소에 대한 범위 검사가 없기 때문에 배열 참조를 확인할 때 주의를 기울여야 합니다. 그렇지 않으면 논리적인 버그가 발생할 수 있습니다.
; Data Transfer Examples (Moves.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
val1 WORD 1000h
val2 WORD 2000h
arrayB aBYTE 10h,20h,30h,40h,50h
arrayW WORD 100h,200h,300h
arrayD DWORD 10000h,20000h
.code
main PROC
; Demonstrating MOVZX instruction:
mov bx,0A69Bh
movzx eax,bx ; EAX = 0000A69Bh
movzx edx,bl ; EDX = 0000009Bh
movzx cx,bl ; CX = 009Bh
; Demonstrating MOVSX instruction:
mov bx,0A69Bh
movsx eax,bx ; EAX = FFFFA69Bh
movsx edx,bl ; EDX = FFFFFF9Bh
mov bl,7Bh
movsx cx,bl ; CX = 007Bh
; Memory-to-memory exchange:
mov ax,val1 ; AX = 1000h
xchg ax,val2 ; AX = 2000h, val2 = 1000h
mov val1,ax ; val1 = 2000h
; Direct-Offset Addressing (byte array):
mov al,arrayB ; AL = 10h
mov al,[arrayB+1] ; AL = 20h
mov al,[arrayB+2] ; AL = 30h
; Direct-Offset Addressing (word array):
mov ax, arrayW ; AX = 100h
mov ax,[arrayW+2] ; AX = 200h
; Direct-Offset Addressing (Doubleword array):
mov eax,arrayD ; EAX = 10000h
mov eax,[arrayD+4] ; EAX = 20000h
mov eax,[arrayD+4] ; EAX = 20000h
INVOKE ExitProcess, 0
main ENDP
END main
Displaying CPU Flags in the Visual Studio Debugger
FlagName | Overflow | Direction | Interrput | Sign | Zero | Aux Carry | Parity | Carry |
---|---|---|---|---|---|---|---|---|
Symbol | OV | UP | EI | PL | ZR | AC | PE | CY |
예제 프로그램 (Moves.asm)은 데이터 전송 관련 명령어들을 활용하여 다양한 데이터 이동 및 변환 작업을 수행하는 코드입니다. 이 프로그램은 주로 MOVZX, MOVSX, XCHG, 그리고 Direct-Offset addressing 기법들을 보여주고 있습니다.
그리고 이 프로그램에서는 바이트 배열, 워드 배열, 그리고 더블워드 배열을 사용하여 Direct-Offset addressing 기법을 시연합니다.
Visual Studio 디버거에서 CPU 플래그를 표시하는 방법에 대해 설명합니다. 디버깅 세션 중에 CPU 상태 플래그를 표시하려면, 디버그 메뉴에서 Windows를 선택한 다음, Windows 메뉴에서 레지스터를 선택하세요. 레지스터 창에서 마우스 오른쪽 버튼을 클릭하고 드롭다운 목록에서 플래그를 선택합니다. 이 메뉴 옵션을 보려면 프로그램을 디버깅 중이어야 합니다.
레지스터 창에서 사용되는 플래그 심볼들을 아래 표에서 확인할 수 있습니다.
각 플래그는 0 (지워짐) 또는 1 (설정)의 값을 가집니다.
INC reg/mem
DEC reg/mem
The INC (increment) instruction adds 1 to a register or memory operand
and DEC (decrement) instruction subtracts 1 from a register or memory operand
.data
myWord WORD 1000h
.code
inc myWord ; myWord = 1001h
mov bx,myWord
dec bx ; BX = 1000h
Flags
INC와 DEC 명령어
INC reg/mem
DEC reg/mem
INC (증가) 명령어는 레지스터 또는 메모리 피연산자에 1을 더하고, DEC (감소) 명령어는 레지스터 또는 메모리 피연산자에서 1을 뺍니다.
플래그
Add a source operand to a destination operand of the same size
ADD dest,source
.data
var1 DWORD 10000h
var2 DWORD 20000h
.code
mov eax,var1 ; EAX = 10000h
add eax,var2 ; EAX = 30000H
Flags
2.ADD 명령어
동일한 크기의 피연산자인 소스 피연산자를 대상 피연산자에 더합니다.
플래그
Subtract a source operand from a destination operand
SUB dest,source
.data
var1 DWORD 30000h
var2 DWORD 10000h
.code
mov eax,var1 ; EAX = 30000h
sub eax,var2 ; EAX = 20000h
Flags
3.SUB 명령어
소스 피연산자를 대상 피연산자에서 뺍니다.
Reverses the sign of a number by converting the number to its two’s complement
NEG reg
NEG mem
Flags
4.NEG 명령어 (부정)
숫자의 부호를 바꾸기 위해 숫자를 2의 보수로 변환합니다.
플래그
Now, you can simulate what a C++ compiler might do when a statement such as this:
Rval = -Xval + (Yval - Zval);
Suppose that the following signed 32-bit variables will be used
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40
First, we negate a copy of Xval and store it in a register
Then Yval is copied to a register and Zval is subtracted
Finally, the two terms (in EAX and EBX) are added
; first term: -Xval
mov eax,Xval
neg eax ; EAX = -26
; second term: (Yval - Zval)
mov ebx,Yval
sub ebx,Zval ; EBX = -10
; add the terms and store;
add eax,ebx
mov Rval,eax ; -36
5.산술 표현식 구현
이제 다음과 같은 문장이 C++ 컴파일러에서 어떻게 처리되는지 시뮬레이션할 수 있습니다:
다음과 같은 부호 있는 32비트 변수가 사용된다고 가정합니다.
먼저 Xval의 복사본을 부정하고 레지스터에 저장합니다.
그런 다음 Yval을 레지스터에 복사하고 Zval을 뺍니다.
마지막으로 두 항 (EAX 및 EBX)을 더합니다.
When executing arithmetic instructions, we often want to know something about the result
We use the values of CPU status flags
A quick overview of the status flags
Unsigned Operations: Zero, Carry, and Auxiliary Carry
mov ecx,1
sub ecx,1 ; ECX = 0, ZF =1
mov eax,0FFFFFFFFh
inc eax ; EAX = 0, ZF = 1
inc eax ; EAX = 1, ZF = 0
dec eax ; EAX = 0, ZF = 1
mov al,0FFh
add al,1 ; AL = 00, CF = 1
mov ax,00FFg
add ax,1 ; AX = 0100h, CF = 0
mov ax,0FFFFg
add ax,1 ; AX = 0000, CF = 1
Unsigned Operations: Zero, Carry, and Auxiliary Carry
mov al,1
sub al,2 ; AL = FFh, CF = 1
mov al,0Fh
add al,1 ; AC = 1
mov al,10001100b
add al,00000001b ; AL = 10001110, PF = 1
sub al,10000000b ; AL = 00001110, PF = 0
Signed Operations: Sign and Overflow Flags
Sign Flag
mov eax,4
sub eax,5 ; EAX = -1, SF = 1
mov bl,1 ; BL = 01h
sub bl,2 ; BL = FFh (-1), SF = 1
Overflow Flag
mov al, +127
add al,1 ; OF = 1
mov al,-128
sub al,1 ; OF = 1
The Addition Test
How the Hardware Detects Overflow
NEG Instruction
mov al,-128 ; AL = 10000000b
neg al ; AL = 10000000b, OF = 1
mov al,+127 ; AL = 01111111b
neg al ; AL = 10000001b, OF = 0
The CPU does NOT know whether an arithmetic operation is signed or unsigned!
6.덧셈과 뺄셈에 의해 영향을 받는 플래그
산술 명령어를 실행할 때 결과에 대해 알고 싶은 것이 종종 있습니다.
CPU 상태 플래그의 값을 사용합니다.
상태 플래그의 간단한 개요입니다.
부호 없는 연산: 제로, 캐리 및 보조 캐리
부호 있는 연산: 부호 및 오버플로 플래그
; Addition and Subtraction (AddSubTest.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCode:dword
.data
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40
.code
main PROC
; INC and DEC
mov ax,1000h
inc ax ; 1001h
dec ax ; 1000h
; Expression: Rval = -Xval + (Yval - Zval)
mov eax,Xval
neg eax ; -26
mov ebx,Yval
sub ebx,Zval ; -10
add eax,ebx
mov Rval,eax ; -36
; Zero flag example:
mov cx,1
sub cx,1 ; ZF = 1
mov ax,0FFFFh
inc ax ; ZF = 1
; Sign flag example:
mov cx,0
sub cx,1 ; SF = 1
mov ax,7FFFg
add ax,2 ; SF = 1
; Carry flag example:
mov al,0FFh
add al,1 ; CF = 1, AL = 00
; Overflow flag example:
mov al,+127
add al,1 ; OF = 1
mov al,-128
sub al,1 ; OF = 1
INVOKE ExitProcess,0
main ENDP
END main
데이터 관련 연산자와 지시문
.data
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal2 DWORD ?
mov esi,OFFSET bVal ; ESI = 00404000h
mov esi,OFFSET wVAL ; ESI = 00404001h
mov esi,OFFSET dVal ; ESI = 00404003h
mov esi,OFFSET dVal2 ; ESI = 00404007h
.data
myArray WORD 1,2,3,4,5
.code
mov esi,OFFSET myArray + 4
.data
bigArray DWORD 500 DUP (?)
pArray DWORD bigArray
mov esi.pArray
1.OFFSET 연산자
ALIGN bound
bVal BYTE ? ; 00404000h
ALIGN 2
wVal WORD ? ; 00404002h
bVal2 BYTE ? ; 00404004h
ALIGN 4
dVal DWORD ? ; 00404008h
dVal2 DWORD ? ; 0040400Ch
2.ALIGN 지시문
bVal BYTE ? ; 00404000h
ALIGN 2
wVal WORD ? ; 00404002h
bVal2 BYTE ? ; 00404004h
ALIGN 4
dVal DWORD ? ; 00404008h
dVal2 DWORD ? ; 0040400Ch
.data
myDouble DWORD 12345678h
.code
mov ax,myDouble ;error
mov ax, WORD PTR myDouble
.data
myDouble DWORD 12345678h
.code
mov ax,myDouble ; error
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
3.PTR Operator
.data
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?
Expression | value |
---|---|
TYPE var 1 | 1 |
TYPE var 2 | 2 |
TYPE var 3 | 4 |
TYPE var 4 | 8 |
4.TYPE Operator
– Count the number of elements in an array, defined by the values appearing on the same line as its label
.data
intArray WORD 32 DUP(0)
.code
mov eax,SIZEOF intArray ; EAX = 64
6.SIZEOF Operator
.data
val16 LABEL WORD
val32 DWORD 12345678h
.code
mov ax,val16 ; AX = 5678h
mov dx,[val16+2] ; DX = 1234h
val16 is an alias for the same storage location as val32.
The LABEL directive itself allocates no storage
.data
LongValue LABEL DWORD
val1 WORD 5678h
val2 WORD 1234h
.code
mov eax,LongValue ; EAX = 12345678h
Sometimes we need to construct a larger integer from two smaller integers.
7.LABEL Directive
Indirect addressing
Indirect Addressing
간접 주소 지정은 배열 처리에 대한 주소 지정 방식입니다. 상수 오프셋을 사용하여 배열 요소를 직접 주소 지정하는 것이 비효율적이기 때문에 간접 주소 지정이 필요합니다. 이 방식에서는 레지스터를 포인터로 사용하고 레지스터의 값을 조작합니다. 피연산자가 간접 주소 지정을 사용하면 간접 피연산자라고 합니다.
Protected Mode
.data
byteVal BYTE 10h
.code
mov esi,OFFSET byteVal
mov al,[esi] ; AL = 10h
mov [esi],bl
Using PTR with Indirect Operands
inc [esi] ; error: operand must have size
inc BYTE PTR [esi]
1.Indirect Operands
보호 모드(Protected Mode)에서 간접 피연산자는 대괄호로 둘러싸인 32비트 범용 레지스터가 될 수 있습니다. 레지스터는 데이터의 주소를 포함한다고 가정합니다.
MOV 명령어에서 간접 피연산자를 소스로 사용하면 ESI의 오프셋이 참조 해제되고 바이트가 AL로 이동합니다. 대상 피연산자가 간접 주소 지정을 사용하면 레지스터가 가리키는 위치에 새 값이 메모리에 배치됩니다.
BL 레지스터의 내용이 ESI가 주소 지정하는 메모리 위치에 복사됩니다.
간접 피연산자를 사용할 때 명령어의 맥락에서 피연산자의 크기를 명확하게 파악할 수 없는 경우 PTR 연산자를 사용하여 피연산자 크기를 확인할 수 있습니다.
Indirect operands are ideal tools for stepping through 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
.data
arrayW WORD 1000h,2000h,3000h
.code
mov esi,OFFSET arrayW
mov al,[esi] ; AL = 1000h
inc esi,2
mov al,[esi] ; AL = 2000h
inc esi,2
mov al,[esi] ; AL = 3000h
Example: Adding 32-Bit Integers
.data
arrayD DWORD 10000h,20000h,30000h
.code
mov esi,OFFSET arrayD
mov eax,[esi] ; first number
inc esi,4
mov eax,[esi] ; second number
inc esi,4
mov eax,[esi] ; third number
2.Arrays
간접 피연산자는 배열을 순회하는 데 이상적인 도구입니다.
16비트 정수 배열을 사용하는 경우, 각 연속 배열 요소의 주소를 지정하기 위해 ESI에 2를 더합니다.
예시: 32비트 정수 더하기
위 예제에서는 ESI 레지스터를 사용하여 배열 arrayD의 오프셋을 가져옵니다. 그리고 각 배열 요소를 더하고 그 결과를 EAX 레지스터에 저장합니다. ESI 레지스터에 4를 더하여 각 배열 요소에 접근할 수 있습니다. 이 예제에서는 세 개의 32비트 정수를 더하는 방법을 보여줍니다. EAX 레지스터에는 최종 합계 값이 저장됩니다.
An indexed operand adds a constant to a register to generate an effective address
Any of the 32-bit general-purpose registers may be used as index registers
The first notational form combines the name of a variable with a register
arrayB[esi] | [arrayB + esi] |
arrayD[ebx] | [arrayD + ebx] |
Indexed operands are ideally suited to array processing
.data
arrayB BYTE 10h,20h,30h
.code
mov esi,0
mov al,arrayB[esi]
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
Using 16-Bit Registers
mov al,arrayB[si]
mov ax,arrayW[di]
mov eax,arrayD[bx]
Scale Factors in Indexed Operands
.data
arrayD DWORD 100h, 200h, 300h, 400h
.code
mov esi,3 * TYPE arrayD ; offset of arrayD[3]
mov eax,arrayD[esi] ; EAX = 400h
.data
arrayD DWORD 1,2,3,4
.code
mov esi,3 ; subscript
mov eax,arrayD[esi*4] ; EAX = 4
mov esi,3 ; subscript
mov eax,arrayD[esi*TYPE arrayD] ;EAX
3.인덱스 피연산자(Indexed Operands)
이러한 스케일 요소를 사용하면 배열 인덱스에 따라 오프셋을 보다 효율적으로 계산할 수 있습니다. 첨자(subscript)와 배열 요소의 크기를 곱하여 오프셋을 생성하므로 배열 요소에 더 쉽게 접근할 수 있습니다. 이 기능은 배열 처리를 더 간단하게 만들어 주며, 컴파일러가 생성한 코드를 최적화하는 데 도움이 됩니다.
.data
arrayB byte 10h,20h,30h,40h
prtB dword arrayB
ptrB dword OFFSET arrayB
arrayB BYTE 10h,20h,30h,40h
arrayW WORD 1000h,2000h,3000h
ptrB DWORD arrayB
ptrW DWORD arrayW
ptrB DWORD OFFSET arrayB
ptrW DWORD OFFSET arrayW
PBYTE TYPEDEF PTR BYTE
.data
arrayB BYTE 10h,20h,30h,40h
ptr1 PBYTE ? ;uninitialized
ptr2 PBYTE arrayB ; points to an array
TITLE Pointers (Pointers.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCode:dword
;Create user-defined types
PBYTE TYPEDEF PTR BYTE ; pointer to bytes
PBYTE TYPEDEF PTR WORD ; pointer to words
PBYTE TYPEDEF PTR DWORD ; pointer to doublewords
.data
arrayB BYTE 10h,20h,30h
arrayW WORD 1,2,3
arrayD DWORD 4,5,6
;Create sine pointer variables
ptr1 PBYTE arrayB
ptr2 PWORD arrayW
ptr3 PDWORD arrayD
.code
main PROC
; Use the pointers to access data.
mov esi,ptr1
mov al,[esi] ; 10h
mov esi,ptr2
mov ax,[esi] ; 1
mov esi,ptr3
mov al,[esi] ; 4
invoke ExitProcess,0
main ENDP
END main
4.포인터
이 예제 프로그램은 다음과 같은 작업을 수행합니다:
이 프로그램은 포인터를 사용하여 각 배열의 첫 번째 요소에 접근하고 값을 읽습니다. 이를 확장하여 포인터를 사용해 배열 내의 다른 요소에도 접근할 수 있습니다.
JMP 및 LOOP 명령어
JMP destination
top:
.
.
jmp top ;repeat the endless loop
1.JMP 명령어
top:
.
.
jmp top ;repeat the endless loop
– Loop According to ECX Counter
– This instruction repeats a block of statements a specific number of times
– ECX is automatically used as a counter and is decremented each time the loop repeats
LOOP destination
mov ax,0
mov ecx,5
L1:
inc ax
loop L1
error A2075: jump destination too far : by 14 byte(s)
.data
count DWORD ?
.code
mov ecx,100 ;set loop count
top:
mov count,ecx ; save the count
.
mov ecx,20 ; modify ECX
.
mov ecx,count ;restore loop count
loop top
.data
count DWORD ?
.code
mov ecx, 100 ; set outer loop count
L1:
mov count,ecx ; save outer loop count
mov ecx,20 ; set inner loop count
L2:
.
.
loop L2 ; repeat the inner loop
mov ecx,count ; restore outer loop count
loop L1 ; repeat the outer loop
2.LOOP 명령어
3.Visual Studio 디버거에서 배열 표시하기
디버깅 세션에서 배열의 내용을 표시하는 방법입니다.
Copying a String (CopyStr.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCodeL:dword
.data
intarray DWORD 10000h,20000h,30000h,40000h
.code
main PROC
mov edi,OFFSET intarray ; 1: EDI = address of intarray
mov ecx,LENGTHOF intarray ; 2: initialize loop counter
mov eax,0 ; 3: sum = 0
L1: ; 4: mark beginning of loop
add eax,[edi] ; 5: add an integer
add edi,TYPE intarray ; 6: repeat untill ECX = 0
loop L1
invoke ExitProcess,0
main ENDP
END main
4.정수 배열 합하기
1. 배열의 주소를 인덱싱된 피연산자로 사용할 레지스터에 할당합니다.
2. 루프 카운터를 배열의 길이로 초기화합니다.
3. 합계를 누적하는 레지스터에 0을 할당합니다.
4. 루프 시작 부분에 레이블을 만듭니다.
5. 루프 본문에서 배열 요소를 합계에 더합니다.
6. 다음 배열 요소를 가리키도록 합니다.
7. 루프를 반복하려면 LOOP 명령어를 사용합니다.
An assembly language program using a loop that copies a string, represented as an array of bytes with a null terminator value
Copying a String (CopyStr.asm)
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCodeL:dword
.data
source BYTE "This is the source string",0
target BYTE SIZEOF source DUP(0)
.code
main PROC
mov esi,0 ; index register
mov ecx,SIZEOF source ; loop counter
L1:
mov al,sources[esi] ; get a character from source
mov target[esi], al ; store it in the target
inc esi ; move to next character
loop L1 ; repeat for entire string
invoke ExitProcess,0
main ENDP
END main
5.문자열 복사하기
위의 예제는 다음과 같이 작동합니다.
루프가 완료되면, source 문자열의 모든 문자가 target 문자열로 복사됩니다. 프로그램은 ExitProcess를 호출하여 종료합니다.
이 예제에서는 MOV 명령어가 두 개의 메모리 피연산자를 가질 수 없으므로, 각 문자를 source 문자열에서 AL로 이동한 다음, AL에서 target 문자열로 이동합니다.