[libasm] 어셈블리 프로그램 구조와 x64 레지스터 이해하기
어셈블리어 소개 및 문법 구조, 그리고 어셈블리에서 다루는 64비트 범용 레지스터 종류를 정리했다.
Last updated
어셈블리어 소개 및 문법 구조, 그리고 어셈블리에서 다루는 64비트 범용 레지스터 종류를 정리했다.
Last updated
"기계어와 일대일 대응이 되는 컴퓨터 프로그래밍의 저급 언어" - 위키백과
어셈블리어는, 0과 1로만 이루어져 있는 기계어에 MOV, ADD와 같은 명령어를 각각 대응시킨, 프로그래밍의 저급 언어이다. 컴퓨터 구조(CPU)마다 기계어가 다르기 때문에 이에 대응하는 어셈블리어도 각각 달라지게 된다.
이러한 단점을 개선하기 위해 만들어진 것이 C언어와 같은 고급 언어로, CPU에 종속적이지 않으면서도, 저급 언어처럼 메모리에 직접 접근할 수 있다는 장점이 있다.
고급 언어의 경우에는 프로그램을 실행하기까지 위와 같은 과정을 거쳐야 한다. 하지만 어셈블리어 파일은 이미 .s확장자를 가지고 있기 때문에, 바로 어셈블러를 거쳐 오브젝트 파일을 생성할 수 있다.
파일의 크기가 작고 동작 속도가 빠른 특징 덕분에 요즘은 초소형 임베디드 시스템에서 많이 사용된다.
어셈블리 프로그램은 아래 세 개의 section으로 구성되어 있다.
section.data
초기값이 있는 전역 변수, 혹은 스태틱 변수를 선언하는 공간
상수, 파일 이름, 버퍼 사이즈 등을 여기에 선언할 수 있다.
section.text
실행할 코드를 작성하는 공간
section.bbs
추가적으로 변수를 선언할 때 사용하는 공간
어셈블리어는 정해진 표준이 없고, CPU에 따라 여러 종류의 문법이 존재한다. libasm과제에서는 x64 Intel 문법을 사용하며 포맷은 아래와 같다.
opcode
는 명령어, operand
는 인자값이다.
operand2가 source인자이고, operand1이 Destination인자이다.
어셈블리 프로그램은 operand로서 레지스터를 다룬다.
레지스터에 대한 설명은 아래 3번에서.
숫자는 1,2,3, ... 그대로 표기한다.
특정 레지스터의 메모리 주소를 참조할 때는 대괄호([]
)를 사용한다.
Offset: EAX 레지스터에서 +4 만큼 떨어진 메모리 주소를 표기할 때는 [EAX + 4]
와 같이 표현한다.
세미콜론으로 주석을 처리한다.
레지스터는 CPU가 요청을 처리하는 데 필요한 데이터(명령어의 종류, 연산결과, 복귀주소 등)를 일시적으로 저장하는 기억장치이다. CPU에서 사용하는 변수라고 생각하면 레지스터를 조금 쉽게 이해할 수 있다고 한다.
RAX (Accumulator) : 더하기, 빼기 등 산술/논리 연산을 수행하며 함수의 return값이 저장된다.
시스템콜 함수를 사용하려면 RAX에 함수의 syscall 번호를 넣어준다.
RBX (Base) : 메모리 주소를 저장하기 위한 용도로 사용된다.
RCX (Count) : 반복문에서 카운터로 사용되는 레지스터. 고급언어 for문의 i
와 같은 역할이지만, 다만 ECX는 미리 반복 값을 정해두고 명령어를 사용할 때마다 값이 하나씩 줄어든다는 점이 다르다.
syscall을 호출했던 사용자프로그램의 return 주소를 가진다.
RDX (Data) : 다른 레지스터를 서포트하는 여분의 레지스터. 큰 수의 곱셈이나 나눗셈 연산에서 EAX와 함께 사용된다.
RSI (Source Index) : 데이터를 복사할 때 src데이터, 즉 복사할 데이터의 주소가 저장된다.
RDI (Destination Index) : 데이터를 복사할 때 복사된 dest데이터의 주소가 저장된다.
RSP (Stack Point) : 스택프레임에서 스택의 끝 지점 주소(현재 스택 주소)가 저장된다.
즉, 데이터가 계속 쌓일 때 스택의 가장 높은 곳을 가리킨다.
push, pop 명령을 통해 RSP 값이 위아래로 8바이트씩 이동하면서 스택프레임의 크기를 변경하게 된다.
RBP (Base Point) : 함수가 호출되면 스택프레임이 형성 되는데 이 스택스레임의 시작 지점 주소(스택 복귀 주소)가 저장된다.
RFLAGS register : 시스템 제어 용도 혹은 어셈블리에서는 비교/조건문 처리 용도로 사용되는 상태 레지스터이다. 연산 결과에 따라 64비트 레지스터의 각 비트에 0(clear, reset) 혹은 1(set)로 표시해 처리 결과를 저장한다.
CF (Carry Flag) : 부호 없는 수 끼리 연산 결과가 자리올림/자리내림이 발생할 때 1, unsigned int 값을 벗어날 때 1
OF (Overflow Flag) : 부호 있는 수 끼리연산 결과가 용량을 초과하였을 경우 1
SF (Sign Flag) : 연산 결과 최상위 비트가 1인 경우 1
ZF (Zero Flag) : 연산 결과가 0이면 1
AF (Auximiliary-carry Flag) : 16비트 연산시 자리올림/자리내림이 발생할 때 1
PF (Parity Flag) : 연산 결과가 짝수면 1, 홀수면 0
DF( Direction Flag)
IF (Interrupt Fla )
TF (Trap Flag)
이 외에도, 64비트에서는 R8 ~ R15까지 8개의 레지스터가 추가로 사용되며, 하나의 레지스터는 아래 그림처럼 크기에 따라 적절히 쪼개 사용할 수 있다. RAX(64 bits) - EAX(32 bits, Extended AX) - AX(16 bits) - AL(8 bits) - AH(8 bits)
Operand(레지스터)를 알아봤으니, 레지스터를 조작할 수 있는 Opcode(명령어)를 다음 글 [libasm] 어셈블리 명령어(opcode) 정리에서 알아보자.