size_t ft_strlen(const char *s)
{
size_t i;
i = 0;
while (s[i] != '\0')
i++;
return (i);
}
인자로 받은 문자열의 길이를 구하는 ft_strlen 함수이다. 시작주소부터 +1씩 증가하다가 널문자(0)를 만나면 반복문을 탈출하고 구한 길이를 반환한다. 얘를 어셈블리어로 변환해보자!
어셈블리 프로그램을 짜려면 먼저 함수 호출 규약을 알아야 한다. 더 자세한 내용은 여기 참고.
rdi
레지스터에 들어온다.rdi
레지스터의 값은 BYTE [rdi]
로 참조한다.rax
를 통해 리턴 값을 반환한다.위 레지스터를 활용해 어셈블리어로 코드를 짜면 아래와 같다.
일종의 약속(코딩 컨벤션)으로, 언더바를 붙이지 않으면 C에서 어셈블리어로 작성한 함수를 사용할 수 없다. 출처
어셈블리에서는 기본적으로 모든 코드가 private이다. 이때 다른 모듈이 해당 코드에 접근할 수 있게 하기 위해서 global instruction을 이용하여 심볼에 다른 코드가 접근할 수 있도록 해 준다. 이렇게 명시하지 않는다면 링커에서 아무런 심볼을 찾을 수 없다는 오류가 발생한다. 출처
그 외에 section, label, 명령어, 레지스터가 낯설다면 이전 글 참고!
42에서는 Assembler로 nasm을 쓴다. 만약 nasm 설치가 안되어 있다면 brew install nasm
$ nasm -f macho64 ft_strlen.s
$ ld -lSystem ft_strlen.o
$ ./a.out
syscall 명령어를 통해, 시스템 상에 미리 선언되어 있는 함수를 어셈블리 프로그램에서도 사용할 수 있다. 아래 예제는 write()
함수를 syscall하는 예제이다.
시스템 콜 함수는 고유의 번호(0x2000004)를 갖고 있으며, OS마다 조금씩 다르다. Linux는 여기 참고, MacOS는 여기 참고.
_main:
mov rax, 0x2000004 ;시스템콜 함수를 write로 변경
syscall ;시스템콜(write) 호출
rax
에 미리 넣어줘야한다.rax
에 저장된다.만약 syscall 후 에러가 발생했다면 이는 ___error
함수를 이용해 처리해야한다.
syscall 함수는 오류가 있을 경우 rax
에 작은 음수(-1 ~ -4095)를 반환하며 동시에 carry flag가 참이 된다. 따라서 jc
(carry flag가 1일 때 점프)를 활용하면 에러 처리 구문(err)으로 넘어갈 수 있다.
rax
에 담긴 음수의 정체를 이해하는 것이 중요하다. 이 리턴값은, C언어에서 에러에 대한 정보를 나타내는 정보인 errno(쉽게 말해 미리 정의된 에러 번호)의 음수 값이다. 즉, syscall 후 에러 발생 시 rax에는 -errno(-4095 ~ -1)가 반환된다.
- errno 가 궁금하다면 man 2 errno
어셈블리로 작성한 _err 구문이 올바르게 errno를 출력할 수 있도록 하기 위해서는 rax
에 음수 리턴값, [rax]
에 errno에 해당하는 숫자가 들어갈 수 있도록 작성해야한다.
extern ___error
jc _err
ret
_err:
push rax
call ___error
pop rdx
mov [rax], rdx
mov rax, -1
ret