Rust で #![no_std] の "Hello, World!" をやってみる

## 環境
Linux Mint 20.1 Ulyssa
rustc 1.53.0 nightly-x86_64-unknown-linux-gnu

## 動機
[低レイヤーガール](https://www.youtube.com/channel/UCMsstGhINXdpMqo9tsElCMQ)という YouTube チャンネルにてアセンブリで遊んでいるのを見まして、Rust の `asm!`マクロでアセンブリを書いてみようということです。アセンブリは初心者なので、まずは "Hello, World!" で入門してみます。あと、こちらの「[no_stdのRust on LinuxでHello, world!する](https://qiita.com/0yoyoyo/items/9a7c96e1250b50c6b339)」という記事を参考にしました。この記事では参考記事とは違う部分だけ書きます。

## エントリポイント
リンカに`-nostartfiles`というオプションを渡して、`_start`関数内にアセンブリを書いていきます。

```toml:.cargo/config.toml
[build]
rustflags = ["-Clink-arg=-nostartfiles"]
```

```rust:main.rs
#![no_std]
#![no_main]

#[no_mangle]
fn _start() {
    // ここにアセンブリを書いていく！
}

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}
```
Segmentation fault は発生しますが、とりあえず実行できるバイナリができました。

## アセンブリを書く
[#1: FizzBuzzをアセンブリ言語で書きたい！](https://www.youtube.com/watch?v=HFzk0fKDm_w)を参考に "Hello, World!" のアセンブリを書いてみました。

```rust:main.rs
#[no_mangle]
fn _start() {
    unsafe {
        asm!(
            "mov rdx, 14",
            "lea rsi, [string]",
            "mov rdi, 1",
            "mov rax, 1",
            "syscall",
            "mov rax, 60",
            "syscall",
        "string:",
            ".ascii \"Hello, World!\n\"",
        );
    }
}
```

ところがこれはリンカが`relocation R_X86_64_32S against '.text._start' can not be used when making a PIE object; recompile with -fPIE`というエラーを吐いてビルドに失敗しました。`PIE`とはなんぞや？

Wiki より引用
>位置独立コード（いちどくりつコード、英: position-independent code、PIC）または位置独立実行形式（いちどくりつじっこうけいしき、英: position-independent executable、PIE）とは、主記憶装置内のどこに置かれても絶対アドレスに関わらず正しく実行できる機械語の列である。

データ位置の絶対アドレス、相対アドレス的なものかな？（よくわからん…）とにかくリンカオプションに`-pie`が付けてられてるのが原因っぽいのでこれを無効にする必要があります。

そのためにはどうやら`-Crelocation-model=dynamic-no-pic`というオプションを rustc に渡せばいいようです。

```.cargo/config.toml
[build]
rustflags = ["-Clink-arg=-nostartfiles", "-Crelocation-model=dynamic-no-pic"]
```

これで無事にビルド・実行できました。やったぜ:relaxed:

ちなみに文字列のアドレスを rip 相対`lea rsi, [rip + string]`にしてやれば、`-Crelocation-model=dynamic-no-pic` は不要でした。

## rustc にアセンブリを吐かせる
rustc にアセンブリを吐かせるには以下のようにします。（intel 記法の場合）

```
cargo rustc --release -- --emit asm -Cllvm-args=-x86-asm-syntax=intel
```

`.cargo/config.toml`に alias として設定しておくと楽ちんだと思います。下のように書いておくと`cargo asm`コマンドでアセンブリが出力されます。

```toml:.cargo/config.toml
[alias]
asm = "rustc --release -- --emit asm -Cllvm-args=-x86-asm-syntax=intel"
```

アセンブリは`target/release/deps/*.s`に出力されます。内容を覗いてみると`#APP`と`#NOAPP`の間に`asm!`マクロ内の記述がそのまま出力されていました。

```
_start:
        sub     rsp, 8
        #APP

        mov     rdx, 14
        lea     rsi, [string]
        mov     rdi, 1
        mov     rax, 1
        syscall
        mov     rax, 60
        syscall
string:
        .ascii  "Hello, World!\n"

        #NO_APP
        pop     rax
        ret
```

## 成果物

```toml:Cargo.toml
[package]
name = "hello_from_asm"
version = "0.1.0"
authors = ["benki"]
edition = "2018"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
```

```toml:.cargo/config.toml
[alias]
asm = "rustc --release -- --emit asm -Cllvm-args=-x86-asm-syntax=intel"

[build]
rustflags = ["-Clink-arg=-nostartfiles", "-Crelocation-model=dynamic-no-pic"]
```

```rust:main.rs
#![no_std]
#![no_main]
#![feature(asm)]

#[no_mangle]
fn _start() {
    unsafe {
        asm!(
            "mov rdx, 14",
            "lea rsi, [string]",
            "mov rdi, 1",
            "mov rax, 1",
            "syscall",
            "mov rax, 60",
            "syscall",
        "string:",
            ".ascii \"Hello, World!\n\"",
        );
    }
}

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}
```