어셈블리어에 대해서 알아보자

개발하는 곰댕이·2021년 7월 10일
0

Assembly

목록 보기
1/1

1. 어셈블리어란?

어셈블리어는 기계어와 1:1대응이 되는 컴퓨터 프로그래밍 저급 언어 입니다.
기계어는 사람 기준으로 컴퓨터가 바로 읽을 수 있다는 점을 제외하고는 장점이 없는 언어이기 때문에 어셈블리가 탄생했습니다.
이런 저급 언어는 배우기 어렵고 유지보수가 어렵다는 이유 때문에 잘 사용되지는 않고 있습니다.
하지만 임베비드 시스템, 커널 프로그래밍, 컴퓨터 보안 등에서는 어셈블리를 알아야 하고 디버깅이 불가능한 라이브 서비스 같은 경우에는 어느정도 디버깅을 할 수 있게 해 주고, 어셈블리에 대해서 공부하면서 컴퓨터의 전반적인 구조에 대해서 알 수있기 때문에 배울 가치는 많이 있습니다.

2. 어셈블리 구조

어셈블리어 문법에는 intel과 AT&T가 있습니다. 주로 사용되는 문법은 intel이기 때문에 이번 글에서도 intel문법에 따라서 정리해 보도록 하겠습니다.

2.1 Section

어셈블리 프로그램은 세 가지의 section으로 구성되어 있습니다.
각 섹션은 메모리의 구조를 알고 있다면 이해하기 쉬울 것 같습니다.

1. section.data

  • 초기값이 있는 전역 변수, 혹은 스태틱 변수를 선언하는 공간입니다.
  • 상수, 파일 이름, 버퍼 사이즈 등을 여기에 선언할 수 있습니다.

2. section.text

  • 실행할 코드를 작성하는 공간입니다.

3. section.bbs

  • 추가적으로 변수를 선언할 때 사용하는 공간입니다.

2.2 어셈블리어의 명령어 수행 방식

어셈블리어의 명령어 방식은 다음과 같습니다.

명령어피연산자_1피연산자_2
MOVRAX1

이런 식으로 맨 앞에 명령어가 오고 그 다음 레지스터가 오게 됩니다.
자세한 내용은 뒤에 정리 할 레지스터에서 알아봅시다.

3. 레지스터

레지스터는 CPU내부에서 처리할 명령어나 연산의 중간 값 등을 일시적으로 기억하는 임시 기억장소입니다. 컴퓨터의 프로세서 내에서 자료를 보관하는 아주 빠른 기억장소라고 생각하면 되며 일반적으로 현재 계산을 수행중인 값을 저장하는데 사용됩니다. 레지스터는 메모리 계층의 최상위에 위치하면서 가장 빠른 속도로 접근이 가능하게끔 설계되어있는 메모리입니다.

3.1 범용 레지스터

64Bit32Bit16Bit상위 8Bit하위 8Bit용도
RAXEAXAXAHAL사칙연산 등 산술 연산에 자동으로 사용되며, 함수의 반환 값을 처리할 때도 사용됩니다.
그리고 syscall을 통해서 함수를 호출할 때 함수의 호출번호를 이 곳에 넣어주면 해당 함수가 호출됩니다.
RBXEBXBXBHBL메모리 주소를 저장하는 용도로 사용됩니다.
RCXECXCXCHCL반복(Loop)에서 반복 Count 역할을 수행합니다.
그리고 syscall을 호출했던 사용자프로그램의 return 주소를 가집니다.
RDXEDXDXDHDLEAX를 보조하는 역할을 합니다. 예를 들어 나누기를 진행할 경우 몫은 EAX에 나머지는 EDX에 저장됩니다.

3.2 인덱스 레지스터

64Bit32Bit16Bit용도
RSIESISI복사나 비교를 할 경우 출발지 주소를 저장하는 레지스터입니다.
RDIEDIDI복사나 비교를 할 경우 목적지 주소를 저장하는 레지스터입니다.

3.3 포인터 레지스터

64Bit32Bit16Bit용도
RIPEIPIP다음에 실행할 명령어의 주소를 가지고 있는 레지스터입니다.
현재 실행하고 있는 명령어가 종료되면 이 레지스터에 있는 명령어를 실행하게 됩니다.
RSPESPSPStack Pointer의 가장 최근에 저장된 공간의 주소를 저장하는 레지스터입니다.
RBPEBPBPStack Pointer의 기준점(바닥 부분)을 저장하는 레지스터입니다.

3.4 플래그 레지스터

시스템 제어용 혹은 비교, 조건문 처리 용도로 사용되는 레지스터입니다.
연산 결과에 따라서 0 혹은 1의 값을 가집니다.

  • CF (Carry Flag)
    부호 없는 수 끼리 연산 결과가 자리올림/자리내림이 발생할 때나 unsigned int 값을 벗어날 때 참이 됩니다.

  • OF (Overflow Flag)
    부호 있는 수 끼리연산 결과가 용량을 초과하였을 경우 참이 됩니다.

  • SF (Sign Flag)
    연산 결과 최상위 비트가 1인 경우 참이 됩니다.

  • ZF (Zero Flag)
    연산 결과가 0이라면 참이됩니다.

  • AF (Auximiliary-carry Flag)
    16비트 연산시 자리올림/자리내림이 발생할 때 참이됩니다.

  • PF (Parity Flag)
    연산 결과가 짝수면 참이 됩니다.

  • DF( Direction Flag)
    문자열 조작에서 참일 경우 주소 레지스터 값이 자동으로 감소하고, 거짓일 경우 자동으로 증가합니다.

  • IF (Interrupt Fla )
    이 플래그가 참일 경우에만 인터럽트 요구를 받아들인다. 일반적으로 관리자모드 에서만 값을 변경 할 수 있습니다.

  • TF (Trap Flag)
    참일 경우 한 명령이 실행할 때마다 인터럽트가 발생하고 디버깅에 사용됩니다.

4 함수 호출 규약 (Calling Convention)

  • 함수 호출 시 매개변수는 순서대로 rdi, rsi, rdx, rcx, r8, r9 를 통해 넘어오거나 넘겨집니다.
    이 때 매개변수의 갯수와 관계없이 이 레지스터들에 값이 들어가 있다면 오류가 날 수 있으니 주의해야 합니다.
  • 반환값은 항상 rax에 저장됩니다.
  • 호출 시 스택은 16바이트로 정렬되어야 합니다.
    • 함수 호출 시 다시 돌아가기 위한 주소값으로 8바이트가 쌓이므로, 함수 호출 전에 16바이트 단위로 정렬해줍니다.
    • sub rsp, 8의 형태로 직접 rsp 값을 옮겨주거나 push rdx의 형태로 데이터를 쌓아 스택 정렬을 맞출 수 있습니다.

5. 명령어

자주 쓰이는 명령어 정리

명령어예제설명분류
pushpush eaxeax의 값을 스택에 저장합니다.스택 조작
poppop eax스택 가장 상위에 있는 값을 꺼내서 eax에 저장합니다.스택 조작
movmov eax, ebx메모리나 레지스터의 값을 옮길때 사용합니다.
두 레지스터의 크기가 동일해야 하고
포인터끼리의 연산은 안됩니다.(mov [rdi + rax][rsi + rax])
데이터 이동
movzxmovzx rax, almov와 같은 역할이지만 al을 rax와 같은 크기로 확장시켜 줍니다.데이터 이동
inclnc eaxeax의 값을 1증가시킵니다. (++)데이터 조작
decdec eaxeax의 값을 1감소시킵니다. (--)데이터 조작
addadd eax, ebx레지스터나 메모리의 값을 덧셈할때 쓰입니다.논리, 연산
subsub eax, ebx레지스터나 메모리의 값을 뺄셈할때 쓰입니다.논리, 연산
callcall proc프로시저를 호출합니다.프로시저
retret호출했던 바로 다음 지점으로 이동합니다.프로시저
cmpcmp eax, ebx레지스터와 레지스터의 값을 비교합니다.
내부적으로 두 값을 빼서 결과를 도출합니다.
결과가 같다면 ZF=0, 음수라면 SF=1 양수라면 ZF, SF =0입니다.
비교
jmpjmp proc특정한 곳으로 분기합니다.분기
jcjc _err캐리플래그가 1일때 분기합니다.분기
jeje exit보통 cmp와 같이 사용하며 비교한 결과가 같으면 분기합니다.분기

[Assembly] 어셈블리어 명령어 총정리

6. 어셈블리 자료형(Data type)

어셈블리에도 자료형이 있습니다.
어셈블리에서는 레지스터의 크기를 지정해줘야 할 때가 있습니다.
예를 들어서 cmp byte [rdi + rax], 0와 같은 부분에서 대괄호를 사용한 부분은 C언어에서 *의 역할을 합니다. 역참조를 해서 값을 변경하는 방식이죠.
이렇듯 포인터를 사용하게 되거나, 변수를 선언하는 등 디버거가 해당 값의 크기를 알기 힘들 때 꼭 사용해야 합니다.

자료형의 종류는 다음과 같습니다.

자료형크기C 자료형과 비교
BYTE1바이트char과 동일
WORD2바이트short
DWORD4바이트int
QWORD8바이트double

7. Syscall Table

syscall함수를 통해서 함수를 불러올 때 rax에 해당 함수의 테이블 값을 넣어서 함수를 호출해야 합니다.
자주 사용되는 Syscall Table은 다음과 같습니다.

함수 명테이블 번호
sys_exit1
sys_fork2
sys-read3
sys_write4
sys_open5
sys-close6

각 테이블 번호는 이렇게 나눠지는데 Mac에서는 system call이 각 클래스별로 나눠져 있습니다.
그래서 syscall을 사용할 때 상위비트는 해당 클래스의 번호가 붙어야 합니다.
참고로 read, write는 #define SYSCALL_CLASS_UNIX 2로써 상위 비트가 2가 됩니다.
그렇기 때문에 read를 사용한다면 mov rax 3이 아니라 mov rax 0x2000003이 됩니다.
참고
macOS BSD System Calls

참고

어셈블리어 비교문과 반복문
[어셈블리어] ___error 함수로 에러 처리하기
어셈블리어 기초 - 2 [메모리]
[Assembly] 어셈블리어 기초 사용법 & 예제 총정리
[libasm] 어셈블리 프로그램 구조와 x64 레지스터 이해하기
[42] libasm 과제에 필요한 어셈블리어 기초 총정리
[H/W] 여러가지 CPU 레지스터의 종류와 기능
[Assembly] 한 번에 시작하는 x64 어셈블리어 코드 작성을 위한 기본 지식
입문자를 위한 어셈블리어 기초

0개의 댓글