[libasm] Assembly - 산술 연산

Park Sejin·2021년 4월 3일
0
post-thumbnail

INC 명령어

INC 명령어는 피연산자를 1만큼 증가시키는데 사용한다. 레지스터나 메모리에 위치한 단일 피연산자를 처리한다.

구문
INC 명령어의 구문은 다음과 같다.

INC destination

destination 피연산자는 8비트, 16비트, 32비트가 될 수 있다.

예제

INC EBX	; 32비트 레지스터를 증가시킨다.
INC DL	; 8비트 레지스터를 증가시킨다.
INC [COUNT]	; count 변수를 증가시킨다.

DEC 명령어

DEC 명령어는 피연산자를 1만큼 감소시키는데 사용한다. 레지스터나 메모리에 위치한 단일 피연산자를 처리한다.

구문
DEC 명령어의 구문은 다음과 같다.

DEC destination

destination 피연산자는 8비트, 16비트, 32비트가 될 수 있다.

예제

segment .data
	count dw 0
	value db 15

segment .text
	inc [count]
	dec [value]

	mov ebx, count
	inc word [ebx]

	mov esi, value
	dec byte [esi]

ADD와 SUB 명령어

ADD와 SUB 명령어는 byte, word, doubleword 크기 데이터의 단순한 더하기/빼기 연산을 처리한다. 예를 들어, 8비트 피연산자의 합과 차, 16비트 피연산자의 합과 차, 32비트 피연산자의 합과 차.

구문
ADD와 SUB 명령어는 다음과 같다.

ADD/SUB destination, source

ADD/SUB 명령어는 다음의 피연산자를 받는다.

  • 레지스터에서 레지스터로
  • 메모리에서 레지스터로
  • 레지스터에서 메모리로
  • 레지스터에서 상수 값으로
  • 메모리에서 상수 값으로

하지만, ADD/SUB 명령어는 다른 명령어와 마찬가지로 메모리에서 메모리로 연산은 불가능하다. ADD 또는 SUB 연산은 오버플로우 플래그와 캐리 플래그를 설정(set)하고 클리어(clear)한다.

예제
다음 예제는 두 자리 수를 유저에게 입력 받고 RAX와 RBX 레지스터에 각각 저장한다. 그리고 값을 더하여 메모리에 있는 res에 저장한 후 결과를 출력한다.

❗️   64비트 Intel macOS에서 동작하는 코드이다.

SYS_EXIT	equ 0x2000001
SYS_READ	equ 0x2000003
SYS_WRITE	equ 0x2000004
STDIN		equ 0
STDOUT		equ 1

segment .data
	msg1 db "Enter a digit ", 0xA, 0xD
	len1 equ $- msg1

	msg2 db "Please enter a second digit", 0Xa, 0xD
	len2 equ $- msg2

	msg3 db "The sum is: "
	len3 equ $- msg3

segment .bss
	num1 resb 2
	num2 resb 3
	res  resb 1

section .text
	global _main

_main:
	mov rax, SYS_WRITE
	mov rdi, STDOUT
	mov rsi, msg1
	mov rdx, len1
	syscall

	mov rax, SYS_READ
	mov rdi, STDIN
	mov rsi, num1
	mov rdx, 2
	syscall

	mov rax, SYS_WRITE
	mov rdi, STDOUT
	mov rsi, msg2
	mov rdx, len2
	syscall

	mov rax, SYS_READ
	mov rdi, STDIN
	mov rsi, num2
	mov rdx, 2
	syscall

	mov rax, SYS_WRITE
	mov rdi, STDOUT
	mov rsi, msg3
	mov rdx, len3
	syscall

	; 첫 번째 수를 rax 레지스터에 저장하고 두 번째 수를 rbx에 저장한다.
	; 아스키 코드 '0'을 빼서 십진수 숫자로 변환한다.

	; mov rax, [num1]
	mov r10, num1
	mov rax, [r10]
	sub rax, '0'

	; mov rbx, [num2]
	mov r11, num2
	mov rbx, [r11]
	sub rbx, '0'

	; rax와 rbx를 add한다.
	add rax, rbx
	; '0'을 더해서 십진수 수를 아스키 코드로 변환한다.
	add rax, '0'

	; 메모리 위치의 res 변수에 합을 저장한다.
	; mov [res], rax
	mov r12, res
	mov [r12], rax

	; 합을 출력한다.
	mov rax, SYS_WRITE
	mov rdi, STDOUT
	mov rsi, res
	mov rdx, 1
	syscall

exit:
	mov rax, SYS_EXIT
	mov rdi, 0		; exit 함수의 파라미터에 0을 저장한다. 그렇지 않으면 프로그램 종료 시 exit code를 리턴한다.
	xor rbx, rbx		; xor를 사용하여 이런 방식으로도 레지스터를 0으로 초기화할 수 있다.
	syscall

위의 코드를 컴파일하고 실행하면, 다음의 결과를 얻을 수 있다.

Enter a digit:
3
Please enter a second digit:
4
The sum is:
7

MUL/IMUL 명령어

바이너리 데이터를 곱하기 연산하는 명령어는 두 가지가 있다. MUL(Multiply) 명령어는 부호가 없는 데이터를 다루고, IMUL(Integer Multiply) 명령어는 부호가 있는 데이터를 다룬다. 두 명령어는 Carry flag와 Overflow flag를 변화시킨다.

구문
MUL/IMUL 명령어의 구문은 다음과 같다.

MUL/IMUL multiplier
번호시나리오
1두 개의 바이트가 곱해질 경우
피승수(multiplicant)는 AL 레지스터에 있고, 승수(multiplier)는 메모리나 다른 레지스터에 있다. 곱의 결과는 AX 레지스터에 저장된다. 곱의 상위 8비트는 AH에 저장되고 하위 8비트는 AL에 저장된다.
2두 개의 워드가 곱해질 경우
피승수는 AX 레지스터에 있고, 승수는 메모리나 다른 레지스터에 있다. 예를 들어, MUL DX와 같은 경우, 반드시 승수는 DX에 저장하고, 피승수는 AX에 저장해야 한다.
3두 개의 더블워드가 곱해질 경우
피승수는 EA에 있고, 승수는 메모리나 다른 레지스터에 있다. 곱의 결과는 EDX:EAX 레지스터에 저장된다. 예를 들어, 곱의 상위 32비트는 EDX 레지스터에 저장되고 하위 32비트는 EAX 레지스터에 저장된다.
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH	; DL = -1
MOV AL, 0BEH	; AL = -66
IMUL DL

예제
다음 예제는 3 * 2를 계산한다.

❗️   64비트 Intel macOS에서 동작하는 코드이다.

section .text
	global _main

_main:

	mov al, '3'
	sub al, '0'

	mov bl, '2'
	sub bl, '0'
	mul bl
	add al, '0'

	mov r10, res
	mov [r10], al
	mov rsi, msg
	mov rdx, len
	mov rdi, 1
	mov rax, 0x2000004
	syscall

	mov rsi, res
	mov rdx, 1
	mov rdi, 1
	mov rax, 0x2000004
	syscall

	mov rax, 0x2000001
	mov rdi, 0		; exit 함수의 파라미터에 0을 저장한다. 그렇지 않으면 프로그램 종료 시 exit code를 리턴한다.
	syscall

section .data
	msg db "The result is: ", 0xA, 0xD
	len equ $- msg
segment .bss
	res resb 1

코드를 컴파일하고 실행하면, 다음의 결과를 확인할 수 있다.

The result is:
6

DIV/IDIV 명령어

나누기 연산은 몫과 나머지를 생성한다. 곱하기의 경우, 오버플로우가 발생하지 않는다. 곱의 결과를 두배 길이의 레지스터에 저장하기 때문이다. 하지만, 나누기의 경우, 오버플로우가 발생한다. 프로세서는 오버플로우가 발생할 경우, 인터럽트를 발생시킨다.

DIV 명령어는 부호가 없는 데이터에 사용되고 IDIV는 부호가 있는 데이터에 사용된다.

구문
다음은 DIV/IDIV 명령어의 포맷이다.

DIV/IDIV divisor

피제수(dividend)는 누산기(accumulator)에 위치한다. 8비트, 16비트, 32비트 피연산자와 사용할 수 있다. 나누기 연산은 여섯 개의 상태 플래그 모두에 영향을 준다. 다음 표는 다른 피연산자 크기에 따른 세 가지의 나누기 연산을 설명한다.

번호시나리오
1바이트가 제수(divisor)일 경우
피제수가 AX 레지스터(16비트)에 있다고 가정한다. 나누기 연산 후, 몫은 AL 레지스터에 저장되고 나머지는 AH 레지스터에 저장된다.
2워드가 제수(divisor)일 경우
피제수가 32비트 크기로 DX:AX 레지스터에 있다고 가정한다. 상위 16비트는 DX에 위치하고, 하위 16비트는 AX에 위치한다. 나누기 연산 후, 16비트 몫은 AX 레지스터에 저장되고, 16비트 나머지는 DX 레지스터에 저장된다.
3더블워드가 제수(divisor)일 경우
피제수가 64비트 크기로 EDX:EAX레지스터에 있다고 가정한다. 상위 32비트는 EDX에 위치하고, 하위 32비트는 EAX에 위치한다. 나누기 연산 후, 32비트 몫은 EAX 레지스터에 저장되고, 32비트 나머지는 EDX 레지스터에 저장된다.

예제
다음 예제는 8 / 2를 계산한다. 피제수 8은 16비트 AX 레지스터에 위치하고, 제수 2는 8비트 BL 레지스터에 위치한다.

❗️   64비트 Intel macOS에서 동작하는 코드이다.

section .text
	global _main

_main:
	mov ax, '8'
	sub ax, '0'

	mov bl, '2'
	sub bl, '0'
	div bl
	add ax, '0'

	mov r10, res
	mov [r10], ax
	mov rsi, msg
	mov rdx, len
	mov rdi, 1
	mov rax, 0x2000004
	syscall

	mov rsi, res
	mov rdx, 1
	mov rdi, 1
	mov rax, 0x2000004
	syscall

	mov rax, 0x2000001
	mov rdi, 0		; exit 함수의 파라미터에 0을 저장한다. 그렇지 않으면 프로그램 종료 시 exit code를 리턴한다.
	syscall

section .data
	msg db "The result is: ", 0xA, 0xD
	len equ $- msg

segment .bss
	res resb 1

코드를 컴파일하고 실행하면, 다음의 결과를 확인할 수 있다.

The result is:
4

출처

tutorialspoint.com

0개의 댓글