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


環境

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

動機

低レイヤーガールという YouTube チャンネルにてアセンブリで遊んでいるのを見まして、Rust の asm!マクロでアセンブリを書いてみようということです。アセンブリは初心者なので、まずは "Hello, World!" で入門してみます。あと、こちらの「no_stdのRust on LinuxでHello, world!する」という記事を参考にしました。この記事では参考記事とは違う部分だけ書きます。

エントリポイント

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

.cargo/config.toml
[build]
rustflags = ["-Clink-arg=-nostartfiles"]
main.rs
#![no_std]
#![no_main]

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

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

Segmentation fault は発生しますが、とりあえず実行できるバイナリができました。

アセンブリを書く

#1: FizzBuzzをアセンブリ言語で書きたい!を参考に "Hello, World!" のアセンブリを書いてみました。

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"]

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

ちなみに文字列のアドレスを 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コマンドでアセンブリが出力されます。

.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

成果物

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

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
.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"]
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 {}
}