Assembly Lang - 산술 연산, 비트 연산
어셈블리언어에서 사용되는 산술 연산자와 비트 연산자에 대해 학습한 내용입니다.
어셈블리 언어 - 산술, 비트 연산자
문법
→ 어셈블리 언어에도 다른 프로그래밍 언어와 같이 문법이 존재합니다. 물론 다른 프로그래밍 언어보다 매우 단순하며 한 줄에 하나의 연산만 수행할 수 있다는 특징이 있습니다.
기본 구문
;label/address mnemonic operand ;comment
00A92DF9 mov eax,dword ptr [0AD4194] ;moves the dword value at 0AD4194 to
-
레이블(label)
→ 레이블은 명령어 코드 위치를 정의하는 데 사용됩니다. 일반적으로 메모리에 코드가 배치될 주소를 정확히 알지 못하는 상황에서 어셈블리 코드를 개발할 때 사용됩니다.
-
주소(address)
→ 해당 명령어가 위치해 있는 메모리 상 주소를 나타냅니다.
-
니모닉(mnemonic)
→ 니모닉은 MOV, ADD, SUB와 같이 사람이 읽을 수 있는 형상의 명령어를 뜻합니다. 연산자를 나타냅니다. 이러한 니모닉은 opcode라고 불리는 한 바이트 명령어 또는 여러 바이트 명령어로 표현됩니다.
-
피연산자(operand)
→ 말 그대로 연산의 대상이 되는 피연산자 입니다. 명령어의 인자라고 할 수 있는 피연산자는 보통 목적지와 출발지로 읽습니다. 보기와 같은 명령어에서 dword ptr [0AD4194], 즉, 0AD4194에 위치한 데이터를 eax 레지스터로 옮긴 것입니다. 그러므로 eax는 목적지, 0AD4194는 출발지가 됩니다.
-
주석(comment)
→ 해당 라인에 대한 부연 설명을 적을 수 있는 주석 기능 입니다. 일반적으로 우리는 프로그램의 모든 명령어 라인에 주석을 추가할 수 있습니다. 어셈블리 언어에서 주석은 세미콜론(;)으로 표시됩니다.
명령어
명령어 종류
-
데이터 복사 및 액세스 명령어
→ MOV, LEA, MOVB 등
-
산술 명령어
→ ADD, SUB, MUL, DIV 등
-
이진 논리 명령어
→ XOR, NOT, SUR, ROL 등
-
흐름 제어
→ JMP, CAL, CMP, INT 등
데이터 복사 및 액세스 명령어
(1) MOV
→ MOV 연산자는 데이터를 이동시키는 역할을 수행합니다.
-
MOV의 피연산자
→ MOV는 레지스터, 상수, 특정 주소를 피연산자로 둘 수 있습니다. 하지만 상수로의 데이터 이동은 불가능합니다.
;예시 MOV AL, BYTE PTR[00000071] ; AL에 00000071번지의 데이터를 복사합니다. MOV EAX, EDX ; EDX 레지스터에 들어있는 데이터를 EAX로 복사합니다. MOV EAX, 0xAABBCCDD ; 앞서 보여드렸듯이 AABBCCDD의 16진수 상수를 EAX로 복사합니다.
(2) LEA
→ LEA 연산자는 Load Effect Address의 약자로 주소를 가져오는 데 사용됩니다. MOV 연산이 지정된 주소에서 값을 읽는 데 사용된다면 LEA는 주소 자체를 가져온다고 생각하시면 됩니다. 코드로 보여드리겠습니다.
MOV EAX, DWORD PTR [00000060] ; 00000060 번지에 있는 값을 EAX 레지스터로 가져옵니다.
LEA EAX, DWORD PTR [00000060] ; 60h를 EAX 레지스터로 가져옵니다.
이러한 LEA 연산자는 주소 계산을 훨씬 용이하게 해준다는 이점이 있습니다. C언어에서의 & 역할을 해준다고 생각하시면 편합니다. []은 주소라는 의미이다.
(3) LEA 연산의 이로운 예
; using MUL and ADD
mov ecx, 1111h
mov ebx, 2222h
mov eax, 2 ; eax = 2
mul ecx ; eax = 2222h
add eax, ebx ; eax = 4444h
add eax, 1 ; eax = 4445h
; using LEA
mov ecx, 1111h
mov ebx, 2222h
lea eax, [ecx*2+ebx+1] ; eax = 4445h
산술 연산
→ 여기서 저희가 알고가야 할 것은 x86 기반은 CISC를 기반으로 하기 때문에 여러 산술 연산자와 같은 명령어는 그 뒤에 더 저수준의 연산 집합을 가지고 있습니다. 산술 연산은 작업 중에 충족해야 할 상태를 플래그의 도움을 받아 참조하여 작동합니다.
덧셈과 뺄셈
주로 사용되는 플래그
→ OF, SF, CF가 주로 사용됩니다.
- OF → 연산 시 Overflow 발생 여부를 체크합니다. ( 발생 시 = 1 )
- SF → 연산 시 Sign bit의 상태를 체크합니다. ( 음수일 경우 = 1 )
- CF → 연산 시 CARRY가 발생했는지 체크합니다. ( 발생 시 = 1 )
ADD
→ ADD 연산자는 말 그대로 덧셈을 수행하는 어셈블리 연산자입니다.
mov eax, 1111h ; eax에 0x1111을 넣는다.
add eax, 2222h ; eax += 2222h 와 동일한 명령어이다.
결과: EAX → 0x00003333
SUB
→ SUB 연산자는 뺄셈에 사용되는 어셈블리 연산자이다.
mov eax, 1111h ; eax에 0x1111을 넣어준다.
sub eax, 111h ; eax -= 111h와 같은 명령어이다.
결과: EAX → 0x00001000
++와 - -
→ 어셈블리 언어에서도 하나의 피연산자만으로 가능한 덧셈 뺄셈 연산이 있습니다. C언어에서의 전위, 후위 연산자와 동일한 수행을 해줍니다. 하지만, 전위와 후위처럼 앞에 오거나 뒤에 오거나 할 것 없이 해당 명령어에서 피연산자의 값에 1을 더해주거나 빼줍니다.
INC
→ 단순히 피연산자의 값에 1을 추가해주는 연산자입니다.
mov eax, 0xffffffff ; eax에 0xffffffff 값을 넣어줍니다.
inc eax ; eax에 +1을 추가해줍니다.
결과: EAX → 0
DEC
→ 단순히 피연산자의 값에 1을 감소시키는 연산자입니다.
mov eax, 0 ; eax에 0x00000000 값을 넣어줍니다.
dec eax ; eax에 1을 감소시킵니다.
결과: EAX → 0xffffffff
곱셈과 나눗셈
MUL
→ 어셈블리 언어에서 곱셈을 담당하는 연산자입니다. 결과값이 레지스터의 용량을 초과할 경우 해당 값들은 EDX 레지스터를 빌려 저장됩니다.
mov eax, 0x80000000 ; eax에 0x80000000 값을 넣습니다.
mov ecx, 2 ; ecx에 곱해줄 값을 넣습니다. 2
mul ecx ; 기본적으로 eax에 있는 값과 피연산자를 곱해줍니다.
결과: EAX → 0x00000000 EDX → 0x00000001 CF → 1 OF → 1
IMUL
→ 다른 레지스터나 주소에 대한 피연산자를 허용하는 곱셈입니다.
DIV
→ 어셈블리 언어에서 나눗셈을 담당하는 연산자입니다. 나누기 작업 후 몫이 AL, AX 또는 EAX에 저장되며, 나머지는 AH, DX 또는 EDX에 저장됩니다.
IDIV
→ IMUL과 마찬가지로 다른 레지스터나 주소에 대한 피연산자를 허용하는 나눗셈입니다.
그 외 연산자들
NEG
→ 2의 보수를 구하는 연산자입니다.
neg eax
neg dword ptr [00403000]
MOVSX
→ 부호를 포함, BYTE → WORD, WORD → DWORD로 이동시킵니다. 피연산자를 수용하기 때문에 CBW, CWDE, CWD 보다 유연한 연산자입니다.
mov bx, 0xffff ; bx에 ffffh 값을 넣습니다.
movsx eax, bx ; eax로 확장합니다.
결과: EAX → 0xffffffff (-1)
CBW
→ MOVSX와 유사, 부호를 포함해 BYTE를 WORD로 변환하는데 AL과 AX만 무조건 영향을 받습니다. 그러므로 피연산자가 존재하지 않는 연산자입니다. 즉, 바이트 크기인 AL이 워드 크기 AX로 확장되는 것이며 이러한 변환은 ‘- >’ 기호를 이용합니다. AL - > AX는 저장된 값을 손상시키지 않고 1byte의 숫자를 2byte로 확장한다는 것을 뜻 합니다.
CWDE
→ WORD를 DWORD로 변환합니다. AX - > EAX
CWD
→ WORD를 DWORD로 변환하는 점에서 CWDE와 같지만 CWD는 AX - > DX:AX 에 영향을 미칩니다.
비트 대수
→ 일반적으로 암호학의 암호화와 복호화에 사용됩니다.
NOT
→ 비트 반전을 수행합니다.
AND
→ 비교되는 bit가 둘 다 1인 경우 bit를 1로 설정합니다. 그렇지 않은 경우 0으로 설정합니다.
OR
→ 비교되는 bit가 한 쪽만 1이더라도 bit를 1로 설정합니다. 둘 다 0이면 0으로 설정합니다.
XOR
→ 두 bit가 같은 경우 1이든 0이든 0으로 설정합니다. 두 bit가 다른 경우 1로 설정합니다. 주로 레지스터 초기화에 많이 사용됩니다.
xor eax, eax ; eax를 0으로 초기화
SHL/SAL
→ bit를 왼쪽으로 이동(shift) 시킵니다.
SHR/SAR
→ bit를 오른쪽으로 이동시킵니다.
ROL
→ bit를 왼쪽으로 회전(rotate)시킵니다.
ROR
→ bit를 오른쪽으로 회전시킵니다.