X86_64 어셈블리 프로그래밍 기초
슈퍼컴퓨팅인프라센터 2022. 6. 2. 16:08
가. 어셈블리(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의 비교
4. CISD 와 RISC 명령어 비교
라. 어셈블리 코딩 예제
다음과 같은 hello.asm 파일을 작성한다.
빌드를 위한 nasm 어셈블러 설치한다.
(NASM: https://github.com/netwide-assembler/nasm , https://www.nasm.us/ )
컴파일하고, 링크하여 실행파일을 만들고 실행한다.
옵션 -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 을 참고한다.
syscall 은 시스템콜을 호출하는 명령어로, 리눅스에서 사용하는 시스템 콜 번호는 관련 문서(64비트, 32비트, ARM)를 참고한다. 아키텍처에 따라서 시스템 호출 번호가 서로 다름을 알 수 있다.
64비트 리눅스에서 sys_write 시스템콜 정의는 다음과 같다.
시스템콜 sys_exit 의 정의는 다음과 같다.
마. 더 자세한 내용이 궁금하시다면..
아래의 첨부파일을 참고하시기 바랍니다. 본 문서는 X86 64비트 아키텍쳐를 중심으로 일반적인 리눅스 환경에서 어셈블리 프로그래밍을 시작할 수 있는 기초 지식에 대해서 기술하였다. 어셈블리어 코딩, 역어셈블링 방법, C코드와 연동, SSE/AVX 확장 명령, ELF 포맷 등에 대한 기초 지식에 대해서 공부할 수 있다.
X86_64 어셈블리 프로그래밍 기초 2022.6.1. ISBN 978-89-294-1309-5-93560 한국과학기술정보연구원 슈퍼컴퓨팅기술개발센터 김상완, 오광진
본 보고서는 국가과학기술연구회에서 지원한 창의형 융합연구사업(CAP)인 "차세대 초고성능컴퓨터를 위한 이기종 매니코어 하드웨어 시스템 개발" 사업의 결과입니다. 무단전재 및 복사를 금지합니다.
Last updated