어셈블리어란?
어셈블리어란 C나 JAVA 와 같은 사용자와 가까운 고급언어보다 기꼐어와 더 가까운 언어이다. 즉, Low Langague라는 말이다.
기계어와 명령어가 1:1로 대응하고, 기계어는 CPU 종류에 따라 달라지기 때문에 호환성이 없는 언어이다.
주로 임베디드나 커널 프로그래밍 등에 쓰인다.
특징은 다음과 같다.
- 동일한 종류의 프로레서만 실행된다.
- 프로세서에 대한 사전지식이 필요한다.
- 메모리나 입출력장치, 레지스터 등의 구성요소를 직접 다룰 수 있다.
- 컴퓨터에서 실행하는 과정 등을 이해하기 쉽다.
마지막 특징이 매우 중요하다.
나는 C++를 배우기 이전에 앞서 컴퓨터에서 언어가 어떻게 실행되는 과정을 미리 알아보고 C++의 동작을 연결해보기 위해 먼저 어셈블리어에 대해 다룰 예정이다.
SASM?
SASM은 어셈블어의 통합 환경이라고 할 수 있다. 어셈블리어로 작성 할 수 있게 편집기의 역할을 하면서도 작성된 어셈블리어를 exe 파일로 추출할 수 있다. 즉, SASM은 어셈블리어의 번역기 라고 말할 수 있다.
Register(레지스터)
SASM으로 어셈블리어를 실습하기 전 알아야 할 기본 지식이 있다. 바로 레지스터이다. 앞에서도 말했다싶이 어셈블리어는 컴퓨터 구성요소를 직접 다룰 수 있다.
컴퓨터 구조는 CPU, 메모리, 하드디스크 등이 있고 CPU는 ALU를 기본으로 여러 레지스터를 가지고 있다. ALU에서 계산한 값을 잠시 저장해야 하는데 왜 레지스터를 사용한지 의문이 들 수 있다. 메모리나 하드디스크에 저장하면 안되는 것인가?
결론은 안된다. 이다. 그 이유는 CPU와 메모리, 하드디스크 간의 물리적인 거리가 멀기 때문이다. 거리가 멀기 때문에 서로 데이터를 전달하는 시간이 많이 걸리기 때문에 사용하지 않는다. 그에 비해 레지스터는 CPU 안에 있기 때문에 사용이 용이하다.
메모리는 저장한 데이터가 컴퓨터의 전원이 종료되면 그대로 사라지는 휘발성이다. 반면 하드디스크는 사라지지 않는다. 우리가 게임을 다운받았을 때, 게임은 하드디스크에 저장되고 실행하면 메모리에 올라가게된다.
우리가 사용하는 요즘 컴퓨터들의 레지스터들은 대부분 64 bit 즉 8 byte를 사용한다. 8 byte를 강조하는 이유는 byte에 따라 뒷 부분의 명령어가 달라지기 때문이다. 우리는 A,B,C,D 레지스터를 사용한다.
SASM 실습
레지스터에 데이터를 저장하고 이동하는 코드를 작성해보겠다. 앞에서 말했듯이 레지스터는 64 bit, 8 byte를 사용한다. 하지만 우리가 데이터를 저장할 때, 8 byte를 모두 사용하지 않을 수 있다. 따라서 어셈블리어에는 다양한 레지스터와 관련된 명령어가 존재한다.
이 그림이 레지스터 명령어를 한번에 정리해준다. 형식은 O{레지스터 번호}O를 따른다. 즉, 내가 8 byte를 전부 사용하고 A 레지스터를 사용하고 싶다면 rax가 되는것이다. 만약 똑같은 상황에서 B 레지스터를 사용한다고 하면 rbx가 되는것이다. 이와같은 원리로 eax는 4 byte, ax 는 2 byte, ah와 al는 각 1 byte 이지만 bit의 위치가 다르다.
그래서 한번 여러 데이터들을 저장하고 옮기는 코드를 작성해보겠다.
%include "io64.inc"
section .text
global CMAIN
main:
mov rbp, rsp
; for correct debugging
; 8 bit = 1 byte
; 16 bit = 2 byte = 1 word
; 32 bit = 4 byte = 2 word = 1 dword(double - word)
; 64 bit = 8 byte = 4 word = 1 qword(quad - wrod)
; mov
mov eax, 0x1234
mov rbx, 0x12345678
mov cl, 0xff
mov al, 0x00
mov rax, rdx
xor rax, rax
ret
먼저 mov eax, 0x1234라는 코드가 있다. mov는 오른쪽에 있는 값을 왼쪽으로 이동하라는 어셈블리어 명령어이다. 그리고 오른쪽에 있는 0x1234에서 0x는 16진수를 뜻한다. 즉, 이 코드는 eax(A 레지스터의 4 byte)에 0x1234라는 값을 저장하겠다는 의미이다.
나머지 코드 역시 일맥상통하다. mov rax, rdx는 rdx의 값을 rax값에 저장하겠다는 의미이다.
밑에는 디버깅으로 결과를 분석한 것이다.
mov c1, 0xff까지 실행한 결과이다. rax에는 0x1234, rbx는 0x12345678, rcx는 0xff로 잘 저장된 것을 확인 할 수 있다.
중요한 점은 바로 다음이다. (eax를 해서 0~ 31번 비트까지 0x1234가 저장되었고 32~63번 비트가 0으로 되어있기 때문에 그대로 0x1234이다.)
mov al, 0x00을 실행한 결과이다. 앞서 설명한 대로 라면, rax에는 0x00이 저장되어 있어야한다. 하지만 0x1200이 저장되어있다. 그 이유는 먼저 A레지스터에는 기존에 0x1234라는 값을 가지고 있었다. 이것을 비트로 표현하면 다음과 같다.
우리가 명령어로 al를 사용했다. al은 1 byte 중 0~7번 비트에 저장하겠다는 의미다. 즉 0~7 번 비트에 0x00을 저장하라는 의미이다.
그림과 같이 보면 왜 0x1200이 저장되었는지 단숨에 확인할 수 있다.
다음 글에서는 메모리, 문자와 엔디안 등을 다룰 예정이다.
'C++ > 기초' 카테고리의 다른 글
[C++] 데이터 가지고 놀기 - 1 (정수, 불리언, 부동소수점, 문자열) (1) | 2023.11.22 |
---|---|
[Assemble] SASM - 5 (1) | 2023.11.21 |
[Assemble] SASM - 4 (0) | 2023.11.21 |
[Assemble] SASM - 3 (1) | 2023.11.21 |
[Assemble] SASM - 2 (0) | 2023.11.21 |