---
description: 어셈블리어로 프로그램을 짜려면 먼저 함수 호출 규약을 알아야 한다. syscall 함수의 사용법과 에러 처리 방법도 함게 알아보자.
---

# \[libasm] strlen 함수를 어셈블리어로 짠다면

## 1. strlen 함수를 어셈블리어로 짠다면?

```c
size_t        ft_strlen(const char *s)
{
    size_t    i;

    i = 0;
    while (s[i] != '\0')
        i++;
    return (i);
}
```

인자로 받은 문자열의 길이를 구하는 ft\_strlen 함수이다. 시작주소부터 +1씩 증가하다가 널문자(0)를 만나면 반복문을 탈출하고 구한 길이를 반환한다. 얘를 어셈블리어로 변환해보자!

> 어셈블리 프로그램을 짜려면 먼저 **함수 호출 규약**을 알아야 한다. 더 자세한 내용은 [여기](https://kkamagui.tistory.com/811) 참고.

1. **매개변수로 받은 s의 주소는 `rdi` 레지스터에 들어온다.**
2. **`rdi` 레지스터의 값은  `BYTE [rdi]`로 참조한다.**
3. **`rax`를 통해 리턴 값을 반환한다.**

위 레지스터를 활용해 어셈블리어로 코드를 짜면 아래와 같다.

![](https://github.com/hidaehyunlee/Fork-my-brain/tree/4f7d19f4487fe8d450e22a9b6451bbed865ce125/images/4329c5a9-35a6-477b-a5d1-fd0506cdcee8-image.png)

#### 어셈블리 파일 내부 함수에 \_(언더바)를 붙이는 이유

일종의 약속(코딩 컨벤션)으로, 언더바를 붙이지 않으면 C에서 어셈블리어로 작성한 함수를 사용할 수 없다. [출처](https://heeyamsec.tistory.com/21)

어셈블리에서는 기본적으로 모든 코드가 private이다. 이때 다른 모듈이 해당 코드에 접근할 수 있게 하기 위해서 global instruction을 이용하여 심볼에 다른 코드가 접근할 수 있도록 해 준다. 이렇게 명시하지 않는다면 링커에서 아무런 심볼을 찾을 수 없다는 오류가 발생한다. [출처](https://velog.io/@jbae/nasmhelloworld)

그 외에 section, label, 명령어, 레지스터가 낯설다면 이전 글 참고!

## 2. 어셈블리 프로그램 실행하기

undefined

42에서는 Assembler로 [nasm](https://www.nasm.us)을 쓴다. 만약 nasm 설치가 안되어 있다면 `brew install nasm`

### 2.1. 컴파일

```
$ nasm -f macho64 ft_strlen.s
```

### 2.2. 링킹

```
$ ld -lSystem ft_strlen.o
```

### 2.3. 실행

```
$ ./a.out
```

## 3. 시스템 콜(syscall)

syscall 명령어를 통해, 시스템 상에 미리 선언되어 있는 함수를 어셈블리 프로그램에서도 사용할 수 있다. 아래 예제는 **`write()`함수를 syscall하는 예제**이다.

시스템 콜 함수는 고유의 번호(0x2000004)를 갖고 있으며, OS마다 조금씩 다르다. Linux는 [여기](https://filippo.io/linux-syscall-table/) 참고, MacOS는 [여기](https://github.com/opensource-apple/xnu/blob/master/bsd/kern/syscalls.master) 참고.

```
_main:
    mov rax, 0x2000004 ;시스템콜 함수를 write로 변경
    syscall ;시스템콜(write) 호출
```

* 이 때 syscall 할 함수의 번호를 `rax`에 미리 넣어줘야한다.
* syscall 함수의 반환값 또한 `rax`에 저장된다.
* 자주 사용하는 syscall numbers (mac)
  * **0x2000001** - exit()
  * **0x2000002** -&#x20;
  * **0x2000003** - read()
  * **0x2000004** - write()
  * **0x2000005** - open()
  * **0x2000006** - close()

### 3.1. \_\_\_error 함수를 이용한 에러 처리

만약 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
```

#### 참고

* [\_\_\_error 함수로 에러 처리하기](https://yechoi.tistory.com/17)
* [https://yeosong1.github.io/libasm#errno-설정](https://yeosong1.github.io/libasm#errno-%EC%84%A4%EC%A0%95)
* [errno에 대한 설명](https://www.notion.so/Libasm-f4869fe5de17402b9054a7ca06bfc79c)
