이번 과제의 목표는 어셈블리어에 대해 이해하고, 몇가지 간단한 라이브러리들을 어셈블리어로 코딩하는 것이다.
Introduction
An assembly (or assembler) language, often abbreviated asm, is a low-level programming language for a computer, or other programmable device, in which there is a very strong (but often not one-to-one) correspondence between the language and the architecture’s machine code instructions. Each assembly language is specific to a particular computer architecture. In contrast, most high-level programming languages are generally portable across multiple architectures but require interpreting or compiling. Assembly language may also be called symbolic machine code.
어셈블리(assembly) 언어는 컴퓨터 또는 기타 프로그래밍 가능한 장치를 위한 낮은 수준의 프로그래밍 언어로서, 언어와 아키텍처의 기계 코드 명령 사이에 매우 강력한 (종종 일대일이 아닌) 대응 관계가 있다. 각 어셈블리 언어는 특정 컴퓨터 아키텍처에 따라 다릅니다. 대조적으로, 대부분의 고급 프로그래밍 언어는 일반적으로 여러 아키텍처에서 이동 가능하지만 해석 또는 컴파일이 필요하다. 어셈블리어는 심볼 머신 코드라고도 합니다.
Common Instructions
기계어는 실제로 컴퓨터의 CPU가 읽어서 실행할 수 있는 0과 1로 이루어진 명령어의 조합이다. 이러한 각 명령어에 대해 사람이 알아보기 쉬운 니모닉 기호(mnemonic symbol)를 정해 사람이 좀 더 쉽게 컴퓨터의 행동을 제어할 수 있도록 한 것이 어셈블리 언어이다. 기계어와 1대1 대응이 되는 프로그래밍 저급 언어이다.
rm -rf $HOME/.brew && git clone --depth=1 https://github.com/Homebrew/brew $HOME/.brew && export PATH=$HOME/.brew/bin:$PATH && brew update && echo "export PATH=$HOME/.brew/bin:$PATH" >> ~/.zshrc
write error: No space left on device8.01 MiB | 13.94 MiB/s
이런 문구가 뜨면서 설치가 중단이 됐다. 직감적으로 공간이 부족한거같다.. 42 slack tool bot을 통해 i mac 캐시들을 삭제하는 방법을 알아냈다. clean : rm -r ~/Library/Caches/*; rm ~/.zcompdump*; brew cleanup
brew install nasm
빠른 이해를 위해 Hello World를 출력하는 실습을 해보았다.
section .text
global _main
_main :
mov rax, 0x2000004
mov rdi, 1
mov rsi, msg
mov rdx, 12
syscall
mov rax, 0x2000001
mov rdi, 0
syscall
section .data
msg db "Hello World"
mac에선 global _start
함수에 문제가 발생한다. https://stackoverflow.com/questions/35230299/what-is-the-difference-in-using-global-main-and-global-start-in-the-text-secti
어셈블리 프로그램에서 메모리 공간은 세 부분으로 나뉜다고 한다.
section .data
section .text
section .bbs
global _main
: 어셈블리 함수에 _
를 붙이는 이유는 일종의 약속때문이라고 한다, 어셈블리는 기본적으로 모든 코드가 private하기 때문에 global instruction을 이용하여 심볼에 다른 코드가 접근할 수 있도록 해 준다.
msg db "Hello World"
: msg
는 변수명이고 db
는 데이터 타입이다. 그리고 msg
안에 "Hello World"문자열이 입력된다.
(db
: byte (1byte), dw
: word (4byte), dd
: double (8byte))
문법
Opcode Operand1, Operand2 ;주석
: 기본적으로 어셈블리 코드 문법은 위와 같은 format을 따른다.
helloworld.s
-> helloworld.o
: nasm -f macho64 hello.s
-f
의 인자로 macho64
를 안주게 되면 어셈블러가 64비트os 기준으로 동작을 안한다.)ld -lSystem hello.o -o hello
: 오브젝트 파일들을 링크해준다. 어셈블리 파일에서 system call
을 사용했을 경우, 옵션으로 -lSystem
을 주어 system call 라이브러리를 사용한다고 알려야 한다.레지스터
어셈블리 명령어
system call
에 대해 구체적으로 알아보자CPU가 요청을 처리하는 데 필요한 데이터(명령어의 종류, 연산결과, 복귀주소 등)를 일시적으로 저장하는 기억장치이다. CPU 내부에 위치하고 다른 메모리공간보다 데이터에 훨신 빠르게 접근할 수 있지만 비싸다는 단점이 있다.
CPU 내부엔 다양한 레지스터가 있지만 어셈블리어와 직접적으로 관련된 프로그램 실행 관련 레지스터만 다뤄볼 것이다.
상수나 주소를 저장하거나 특수한 목적으로 사용되는 레지스터이다.(가장 많이 쓰임)
시스템 제어용 플래그로 사용되거나, 어셈블리의 조건 처리, 상태 저장 용도로 사용된다. Flag 레지스터의 참, 거짓에 따라 분기하게 된다.
rax 0. 0
이런 식으로 사용된다.rax
에 주소값을 입력할 때, 앞에 2를 붙이는 이유는 1로 시작하는 주소값은 BSD layer이기 때문이다.(추후 자세히 알아볼 예정)rax
레지스터에 사용하고자 하는 함수에 해당하는 주소값을 입력한다.mov rax, 0x2000004
syscall
명령어를 작성한다.S
가 붙으면 부호를 고려한다.cmp BYTE [rdx + rcx], 0
: rdx + rcx
주소부터 1 BYTE 크기 만큼 1 BYTE 크기의 0과 비교해준다.mov WORD [rdx + rcx], 5
: rdx + rcx
주소에 1 BYTE 크기의 숫자 5을 넣어줍니다.rdi
-> rsi
-> rdx
-> %r10
-> %r8
- > %r9
이 부분은 계속 알아보는 중이다.
neg
: 정의 자체는 부호를 변환시켜주는 어셈블리 명령어이다. 하지만 unsigned 형의 값에 적용할 경우, 부호 비트를 고려하지 않아서 부호가 바뀌는 대신 2의 보수 + 1
값으로 바뀐다.ft_strcmp
함수를 어셈블리로 구현할 때, 입력된 두 개의 문자열 s1, s2 중에서 두 번째로 입력된 s2가 클 경우, 음수 값을 출력해야 한다. 하지만 오류가 발생하였다.al
레지스터에 return 값을 넣어서 반환하려고 했지만 음수 값을 입력 하고 반환하면 양수인 2의 보수 + 1
값이 반환 되는 오류를 발견하였다. 이를 통해 해당 레지스터는 부호 비트를 고려하지 않는 것 같다라는 결론을 도출하였다.rax
레지스터는 부호비트를 고려하는 것 같다. mov
어셈블리 명령어를 활용하 rax
레지스터에 -1을 입력하여 반환하였더니 그대로 출력하였다. 추후에 rax
레지스터를 활용하는 방안을 고려해볼 것이다.어셈블리에서도 역시 전역 및 정적 변수를 선언하여 사용할 수 있다.
db
: 1byte, dw
: 2byte, dd
: 4bytevar db 64
var db ?
msg db "Hello World"
system call 함수나 외부함수를 활용할 떄 그에 따른 에러처리를 해주어야 한다. 에러처리를 위해
___error
외부함수를 사용하였다.
carry flag
가 1이 되고, errno를 rax
에 반환한다.jc
어셈블리 명령어는 carry flag
가 1일 경우, jmp
시켜주는 명령어로 에러가 발생 했을 때, err
구문으로 jmp
할 수 있도록 코딩해준다.err
구문엔 errno를 반환해주는 코딩을 할 것이다.push
를 활용하여 반환된 errno
를 스택에 백업시켜둔다.call ___error
외부함수를 실행시켜서 rax
로 에러가 발생한 부분의 주소를 반환받는다.errno
를 받기 위해서 스택에 백업된 errno를 pop
해준 후에 rdx
레지스터에 담는다. 그리고 mov [rax], rdx
코딩을 하여 rax
주소 공간에 errno
를 넣는다.write
함수는 음수 값을 반환해야 하므로 마지막에 rax
레지스터에 -1을 넣어준다.원래는 데이터의 백업이 필요할 때 레지스터에 백업하는 방식을 활용하였으나, 오류가 발생하여 스택백업방식으로 전부 바꿨다. 레지스터 백업방식에 왜 오류가 발생하는지 알기 위해서 어셈블리가 메모리를 어떻게 활용하는지에 대한 이해가 수반되어야할 것 같았다.