들어가며
이 글은 42seoul의 libasm 과제를 해결하기 위한 자료로 작성되었습니다.
참고문서(ryukim) https://www.notion.so/Libasm-3c94bbc7df234499b012f6ae82b84dc2
작동 환경 : masOS(Catalina) 10.15.5
목차
nasm 설치
Hello World 출력해보기
Mendatory Part
Bonus Part
nasm 설치
- MacOS 에는 기본적으로
nasm
이 설치되어 있지 않다. 따라서 우선nasm
을 설치해줘야 한다. - MacOS에서
nasm
의 설치는nasm
공식 홈페이지에서 압축파일을 직접 다운받아 local에 설치하는 방법과Homebrew
를 이용하여 설치하는 방법 두 가지가 있다.
1.1 공식 사이트에서 파일을 다운받아 설치하기
- NASM(https://www.nasm.us/) 웹사이트에서 최신버전을 확인한다.
- nasm-x.xx.xx-tar.gz 형태의 최신 버전을 받아서 압축을 해제한다.
- nasm-x.xx.xx 폴더에서 ./configure 명령어를 입력한다. 해당 명령어는 Makefile을 이용하여 적절한 C 컴파일러를 찾아주는 명령어이다.
make
명령어를 입력하여nasm
을 build 해준다.make install
명령어를 입력하여nasm
을 설치한 후, man page를 확인하여 설치된 것을 확인한다.
1.2 Homebrew를 이용하여 설치하기
- 우선
ruby -e "$(curl -fsSL [https://raw.githubusercontent.com/Homebrew/install/master/install](https://raw.githubusercontent.com/Homebrew/install/master/install))" < /dev/null 2> /dev/null
명령어로Homebrew
를 설치한다. - 그 다음
brew install nasm
으로nasm
을 설치한다.- permission 오류가 발생할 경우, chown 명령어로 해당 경로에 권한을 주어 설치될 수 있도록 한다. (참고)
nasm -ver
명령어를 이용하여nasm
의 버전을 확인할 수 있다.
- MacOS 에는 기본적으로
Hello World 출력해보기
hello_world.s 파일을 만들고 다음과 같이 입력한다.
global start section .text start: mov rax, 0x02000004 mov rdi, 1 mov rsi, message mov rdx, 13 syscall mov rax, 0x02000001 xor rdi, rdi syscall section .data message: db "Hello, World", 10
nasm 을 이용하여 object 파일을 생성해준다.
nasm -f macho64 hello_world.s
linker를 이용하여 실행할 수 있는 파일로 만들어준다.
ld -macosx_version_min 10.7.0 -o hello_world hello_world.o
hello_world 를 실행해보자.
./hello_world
global _main 변수로 linking할 경우 ld -lSystem flag를 사용해야 linking 가능하다.
Mandatory Part
int ft_strlen(char const *str); int ft_strcmp(char const *s1, char const *s2); char *ft_strcpy(char *dst, char const *src); ssize_t ft_write(int fd, void const *buf, size_t nbyte); ssize_t ft_read(int fd, void *buf, size_t nbyte); char *ft_strdup(char const *s1);
- ft_strlen
- 64bit macOS에서 parameter는 rdi, rsi, rdx, rcx, r8, r9 register를 통해서 전달되며, 그 이상의 parameter는 stack을 통해 전달된다. 따라서, char const *str은 rdi에 전달되어 있다. 관련 자료는 calling convention을 찾아보자. 참고자료
- rax에 저장되어 있는 값은 함수가 끝났을 때 return하는 값이기 때문에 count index를 rax로 사용하였다.
- strlen 함수는 문자열의 길이를 반환하는 함수이므로, str의 주소를 참조하여 해당 문자가 null인지 아닌지 판단하여 값을 반환하게 된다.
- assembly에서 값을 비교할 때는 register의 크기에 주의해야 한다. 문자 하나의 data size는 1byte이기 때문에 null을 판단할 때에도 byte 만큼만 비교해야 한다.
- ft_strcmp
- assembly에서는 두 operand를 비교할 때, 둘 중 하나의 operand에만 추가 연산이 가능하다. 예를 들어, a와 b를 index 만큼 이동한 위치를 비교한다고 가정할 때, a + index, b + index 와 같은 연산이 불가능하다는 이야기이다.
- 따라서 위와 같은 연산을 할 경우에는 다른 register에 값을 옮긴 후 옮겨진 값을 이용하여 연산해야 한다.
- libft에서 고려하지 않았던 확장 아스키코드와의 비교도 가능하기 때문에 unsigned char의 범위 안에 있는 문자는 모두 비교할 수 있어야 한다.
- 문자의 차이값을 구하는 과정에서 특정 register의 flag에 주의하여야 한다. overflow가 발생했을 경우에 어떻게 처리해야 할지 잘 생각해봐야 한다.
- ft_strcpy
- ft_strcmp와 마찬가지로 하나의 register를 temp로 사용하여 src에 있는 값을 복사한 후 복사된 값이 null인지를 체크하여 마지막에 *dest를 return 해주면 된다.
- 이 함수를 작성하면서 왜 parameter에서 dest가 먼저 전달되는 지 알 수 있었다. rdi와 rsi의 관계가 dest와 src의 관계와 일치한다.
- ft_write
- syscall에 대해 이해하고 있다면 함수를 작성하는 것 자체는 어렵지 않다. 그러나, error처리에 대한 부분을 간과하기 쉽다.
- syscall 후에 error가 발생했다면, 자동적으로 error number를 rax에 return해준다.
- error가 발생했을 때, 적절한 return value와 errno를 반환해야 하기 때문에 먼저 ___error를 불러오는 방법부터 알아야 한다.
- ___error 함수를 호출했을 때, return되는 값은 errno의 주소값이다.
- errno가 제대로 설정되었는지 확인하려면 standard 함수에서의 errno와 비교해보면 된다. 다만 standard 함수에서 errno 출력 후, ft_write 함수를 불러오기 전에 errno를 다시 초기화 해주어야 하는 것을 잊지 말자.
- https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master syscall 목록
- ft_read
- syscall 해야 하는 코드만 다를 뿐, ft_write와 거의 동일하다.
- ft_strdup
- ft_strlen함수로 strlen을 구한 후 + 1만큼을 malloc하여 ft_strcpy를 이용하여 복사해주면 쉽게 해결할 수 있다.
- 다른 함수를 호출할 때, caller, callee calling convention에 유의해야 한다.
- ft_strlen
Bonus Part
typedef struct s_list { void *data; struct s_list *next; } t_list; int ft_atoi_base(char const *str, char const *base); void ft_list_push_front(t_list **begin_list, void *data); int ft_list_size(t_list *begin_list); void ft_list_sort(t_list **begin_list,int (*cmp)()); void ft_list_remove_if(t_list **begin_list, void *data_ref, int (*cmp)(), void (*free_fct)(void *));
- ft_atoi_base
- ft_atoi_base는 piscine때 했었던 함수와 동일하게 작성하면 된다.
- str에 있는 숫자를 base에 맞는 진법으로 변환하여 출력하는 문제이다.
- str에서 처리해야 할 문자 순서는 whitespace→sign→number순이다.
- 나머지 규칙은 piscine의 subject를 보고 해당 규칙에 맞게 작성하면 된다.
- ft_list_push_front
- assembly에서 list를 구현하기 위해서는 우선 linked list가 어떠한 구조로 이루어져 있는지 이해할 필요가 있다.
- 구조체에 선언된 data type의 size만큼 메모리를 차지하기 때문에 구조체의 메모리 주소로부터 + datasize만큼 이동한다면 해당 data의 주소를 얻을 수 있다.
- linked list에서 list의 위치를 조작할 경우에 반드시 이전 list의 주소를 잃어버리지 않도록 기록해두어야 한다.
- ft_list_size
- ft_list_size함수는 ft_strlen에서 배열 대신에 list로 바뀌었을 뿐이다.
- list→next 가 null 인지 확인한다.
- ft_list_sort
- ft_list_sort 함수는 piscine 때의 함수와 같은 함수를 assembly로 작성하는 것이다.
- list를 정렬할 때, 맨 첫 부분, 중간 부분, 마지막 부분으로 나누어서 swap을 진행하였다.
- swap algorithm은 bubble sort를 사용하였다.
- 정 안되겠을 땐, C로 작성한 후에 옮겨보자.
- ft_list_remove_if
- ft_list_remove_if 함수는 list에서 data와 일치하는 값을 제거 하는 함수이다.
- 값을 제거한 후 list를 다시 연결해주어야 하기 때문에 제거되는 위치를 고려해주어야 한다.
- 비교할 datasize에 유의하기
- ft_atoi_base
유용한 링크
https://cs.lmu.edu/~ray/notes/nasmtutorial/ NASM Tutorial
https://www.tutorialspoint.com/assembly_programming/index.htm Assembly tutorial
https://waintman.tistory.com/53 linker 에서 _main 함수가 없다고 에러가 발생하는 원인
https://medium.com/@thisura1998/hello-world-assembly-program-on-macos-mojave-d5d65f0ce7c6 hello world 출력해보기
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/x64-architecture 64비트 아키텍쳐에서의 register size
https://blog.naver.com/c3dygks/221637596485 함수 호출 규약(calling convention)
https://dhdhfl.tistory.com/142 64bit 환경 함수 호출 규약
https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-and-user-space-f 64bit calling convention
https://github.com/gurugio/book_assembly_8086_ko/blob/master/README.md 어셈기초
https://duddnr0615k.tistory.com/263 레지스터 기초 지식
https://tyeolrik.github.io/assembly/2017/02/25/Assembly-Command.html 어셈블리 명령어 정리
https://blog.notepads.kr/98 cmp flag
https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master syscall
https://blog.naver.com/seriousboy_/70044054702 caller, callee calling convention