# x86\_64 어셈블리 프로그래밍 기초

## 가. 어셈블리(Assembly) 언어란?

* 어셈블리어란 사용자가 이해하기 어려운 기계어 대신에 명령 기능을 쉽게 연상할 수 있는 기호를 기계어와 대응시켜 코드화한 언어입니다.
* 어셈블리어로 작성한 프로그램은 어셈블러를 통해 오브젝트 코드(기계어)로 변환하는 과정(어셈블)을 거쳐야 합니다.

## 나. 어셈블리 언어를 배워야 하는 이유

* 어셈블리 언어를 배워야 하는 이유는 다음과 같습니다:\
  ※ 참고: x86-64 Assembly Language Programming with Ubuntu (Jorgensen)
* 하드웨어 구조에 대한 이해를 돕습니다. 기본적인 명령어, 레지스터, 메모리 접근방식, 하드웨어 인터페이싱에 대해 더 잘 이해할 수 있습니다.
* 툴 체인에 대한 이해: 컴파일러, 어셈블러, 링커, 로더, 디버거와 같은 도구의 세부사항을 이해할 수 있습니다
* 알고리즘 향상: 저수준 프로그램의 작성을 통해 프로그래머는 세부 사항에 더 많은 생각을 하게 됩니다.
* 기능/절차에 대한 이해 : 함수/프로시저 호출이 작동하는 방식을 이해하게 되고, 스텍 기반의 인수, 보존 레지스터 및 동적 로컬 변수에 대한 개념을 이해할 수 있습니다.
* 컴파일러의 범위 이해: 컴파일러가 컴퓨터 아키텍처와 관련하여 하는 일과 하지 않는 일에 대해 이해할 수 있습니다.
* 공유메모리, 인터럽트, 스레드 처리, 경쟁 조건과 같은 개념에 대해 이해 할 수 있습니다.

## 다. CISC vs RISC

### **1. CISC (Complex Instruction Set Computer)**

* 복잡한 명령어 집합을 갖는 CPU 아키텍처
* 명령어가 복잡하기 때문에 명령어를 해석하는데 상대적으로 긴 시간이 필요하며 해석에 필요한 회로도 복잡합니다.
* 연산의 대상은 레지스터, 메모리, 또는 상수(immediate value)의 조합으로 다양합니다.
* 피연산자(operand)의 개수는 제한이 없으나, 2개\~3개 지정하는 경우가 많습니다.
* 복잡한 명령어 처리를 위해 마이크로프로그램 방식을 채택하는 경우가 많습니다. 복잡한 명령을 다시 단순한 명령어(micro-instruction)로 나누어 명령어 파이프라인에서 처리합니다.

### **2. RISC (Reduced Instruction Set Computer)**

* 명령어 개수를 줄여 하드웨어 구조를 단순하게 만드는 방식
* CISC에서 지원하는 명령은 많지만, 그 중에서 실제로 자주 사용되는 명령어는 몇 개 되지 않는 다는 사실을 바탕으로 명령어 집합을 구성합니다.
* CISC에서 줄어든 제어 로직을 대신하여 레지스터와 캐시를 증가시켜 파이프라이닝(piplining) 기법 등을 적용하여 수행 속도가 전체적으로 향상됩니다.

### 3.  CISC 와 RISC의 비교

| **구분**   | **CISC**           | **RISC**          |
| -------- | ------------------ | ----------------- |
| 하드웨어 복잡도 | 복잡한 하드웨어 구조        | 단순한 하드웨어 구조       |
| 명령어 개수   | 많은 명령어             | 최소 명령어            |
| 명령어 길이   | 다양한 길이             | 고정된 길이            |
| 실행 싸이클   | 복잡한 명령은 여러 클럭이 필요함 | 단순한 명령을 단일 클럭에 실행 |
| 메모리 참조   | 대부분의 명령이 메모리를 참조함  | 소수의 명령만 메모리를 참조함  |
| 명령어 실행   | 마이크로 프로그램이 명령을 실행  | 하드웨어가 직접 명령을 실행   |
| 어드레싱 모드  | 다양한 어드레싱 모드        | 단순한 어드레싱 모드       |
| 레지스터수    | 비교적 소수의 레지스터       | 비교적 레지스터 수가 많음    |
| 복잡도      | 마이크로 프로그램이 복잡      | 컴파일러 제작이 복잡함      |
| 파이프라이닝   | 파이프라인을 적용하기 힘듦     | 파이프라이닝이 쉬움        |
| 마이크로프로세서 | X86                | ARM, SPARC, MIPS  |

### 4. CISD 와 RISC 명령어 비교

| 예시 기능                   | CISC 방식 어셈블리어                                | RISC 방식 어셈블리어                                                                     |
| ----------------------- | -------------------------------------------- | --------------------------------------------------------------------------------- |
| <p>덧셈<br>(메모리 어드레싱)</p> | ADD Ma, Mb                                   | <p>LOAD Ra, Ma<br>LOAD Rb, Mb<br>ADD Ra, Rb<br>STORE Ma, Ra</p>                   |
| 곱셈                      | <p>mov ax, 10<br>mov bx, 5<br>mul bx, ax</p> | <p>mov ax, 0<br>mov bx, 10<br>mov cx, 5<br>begin:<br>add ax, bx<br>loop begin</p> |

## 라. 어셈블리 코딩 예제

다음과 같은 hello.asm 파일을 작성합니다.

```
section .data
        text db "Hello, World!",10
section .text
        global _start
_start: mov rax, 1
        mov rdi, 1
        mov rsi, text
        mov rdx, 15    ; length of message
        syscall
        mov rax, 60
        mov rdi, 0
        syscall
```

빌드를 위한 nasm 어셈블러 설치합니다.

(NASM: <https://github.com/netwide-assembler/nasm> , <https://www.nasm.us/> )

```shell-session
$ sudo apt-get install nasm
```

컴파일하고, 링크하여 실행파일을 만들고 실행합니다.

```shell-session
$ nasm -f elf64   -o hello.o  -l hello.lst  hello.asm 
$ ld hello.o  -o hello
$ ./hello 
Hello, World!
```

* 옵션 -f 는 출력 파일의 형식으로 elf64는 리눅스를 위한 x86\_64 포맷을 의미합니다. nasm에서 지원하는 출력파일 형식은 nasm -hf 명령을 참고합니다.

* 옵션 -l 은 소스 코드와 매크로 처리 결과와 생성된 코드를 비교할 수 있도록 출력합니다.

* 코드 설명
  * data 섹션(.data)은 프로그램에서 초기화된 정적 변수를 위한 공간으로, 글로벌 변수와 정적 로컬 변수를 위한 공간입니다. 이 섹션의 크기는 런타임에서 변경되지 않습니다. data 섹션은 읽기와 쓰기가 가능하나, 읽기 전용을 위한 .rodata 섹션이 존재합니다.
  * text 섹션(section .text)는 코드를 위한 영역으로 읽기만 가능합니다.
  * \_start 레이블은 프로그램의 엔트리 포인트입니다. 이것은 디폴트 값으로 엔트리 포인트를 바꾸고 싶을 경우 ld -e foo 라고 할 수 있습니다.
  * x86\_64 아키텍처의 레지스터는 다음과 같습니다. 자세한 것은 Intel 64 and IA-2 Architectures Software Developer's Manual 을 참고합니다.

![X86\_64 프로세서 내부 레지스터 구조](https://1495676336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1EblQxc8JRzZstYpkdOb%2Fuploads%2Fgit-blob-56b8f5bd4192eed4a165cbafe05f26d5d2932102%2F0Xmdn0h1lkHC4wMQmXbF61.png?alt=media)

* syscall 은 시스템콜을 호출하는 명령어로, 리눅스에서 사용하는 시스템 콜 번호는 관련 문서(64비트, 32비트, ARM)를 참고 바랍니다. 아키텍처에 따라서 시스템 호출 번호가 서로 다름을 알 수 있습니다.
* 64비트 리눅스에서 sys\_write 시스템콜 정의는 다음과 같습니다.

| 레지스터 | 값    | 설명                       |
| ---- | ---- | ------------------------ |
| %rax | 1    | sys\_write               |
| %rdi | 1    | unsigned int fd (표준출력:1) |
| %rsi | text | const char \*buf         |
| %rdx | 15   | size\_t count            |

* 시스템콜 sys\_exit 의 정의는 다음과 같습니다.

| 레지스터 | 값  | 설명              |
| ---- | -- | --------------- |
| %rax | 60 | sys\_exit       |
| %rdi | 0  | int error\_code |

## 마. 더 자세한 내용이 궁금하시다면..

아래의 첨부파일을 참고하시기 바랍니다.\
본 문서는 X86 64비트 아키텍쳐를 중심으로 일반적인 리눅스 환경에서 어셈블리 프로그래밍을 시작할 수 있는 기초 지식에 대해서 기술하였습니다. 어셈블리어 코딩, 역어셈블링 방법, C코드와 연동, SSE/AVX 확장 명령, ELF 포맷 등에 대한 기초 지식에 대해서 공부할 수 있습니다.

X86\_64 어셈블리 프로그래밍 기초\
2022.6.1.\
ISBN 978-89-294-1309-5-93560\
한국과학기술정보연구원 슈퍼컴퓨팅기술개발센터 김상완, 오광진

{% file src="<https://1495676336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1EblQxc8JRzZstYpkdOb%2Fuploads%2Fgit-blob-e21b904165c9125add20d40617c6c23b103d2625%2F%5B%EA%B8%B0%EC%88%A0%EB%AC%B8%EC%84%9C%5D%20X86_64%20%EC%96%B4%EC%85%88%EB%B8%94%EB%A6%AC%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%20%EA%B8%B0%EC%B4%88.pdf?alt=media>" %}

{% hint style="danger" %}
본 보고서는 국가과학기술연구회에서 지원한 창의형 융합연구사업(CAP)인 "차세대 초고성능컴퓨터를 위한 이기종 매니코어 하드웨어 시스템 개발" 사업의 결과입니다. 무단전재 및 복사를 금지합니다.
{% endhint %}
