kdnakt blog

hello there.

Rust dojo第85回を開催した

第85回です。前回はこちら。

kdnakt.hatenablog.com

[第85回の様子]

2023/07/19に第85回を開催した。

内容としてはRust By Example 日本語版22. 安全でない操作の「22.1. Inline assembly」のSymbol operands and ABI clobbersに取り組んだ。
参加者は自分を入れて6人。

[学んだこと]

  • 22.1. Inline assembly: Symbol operands and ABI clobbers
  • asm!マクロは、出力に指定されていないレジスタアセンブリコードによって中身が変わらないと想定している
  • clobber_abi()引数を利用すると、コンパイラが自動的に呼び出し規約を補完してくれる
    • ABIで保管されないレジスタはclobbered(壊れた)扱いになる
  • サンプルコードは次のようになる
use std::arch::asm;

extern "C" fn foo(arg: i32) -> i32 {
    println!("arg = {}", arg);
    arg * 2
}

fn call_foo(arg: i32) -> i32 {
    unsafe {
        let result;
        asm!(
            "call {}",
            // 呼び出す関数のポインタ
            in(reg) foo,
            // RDIレジスタに最初の引数
            in("rdi") arg,
            // 戻り値はRAXレジスタ
            out("rax") result,
            // C言語の呼び出し規約で保護されないレジスタをclobberedとしてマーク
            clobber_abi("C"),
        );
        result
    }
}

fn main() {
    let result = call_foo(10);
    println!("result = {}", result);
}

// 出力は以下の通り
arg = 10
result = 20
  • 他のレジスタの値が変更されているはずなので、unsafeブロックの前後で確認してみることに
  • 以下のようなコードを書いて実行してみた
fn call_foo(arg: i32) -> i32 {
    unsafe {
        let reg: u64;
        asm!(
            "mov rbx, rcx",
            out("rcx") reg,
        );
        println!("rbx before asm! = {}", reg);
    }
    let result
    unsafe {;
        asm!(
            "call {}",
            in(reg) foo,
            in("rdi") arg,
            out("rax") result,
            clobber_abi("C"),
        );
    }
    unsafe {
        let reg: u64;
        asm!(
            "mov rbx, rcx",
            out("rcx") reg,
        );
        println!("rbx after asm! = {}", reg);
    }
    result
}

// 出力は以下の通り
rbx before asm! = 140722307190784
arg = 10
rbx after asm! = 20
result = 20
  • 上記はデバッグビルドでの実行結果だった。
  • clobber_abi("C")の行をコメントアウトしても結果は変わらなかった。この例ではあまり意味がないのかも?
  • リリースビルドにしたら実行結果が変わるかな?と言うことで実験してみたら、以下のような出力になった
rbx before asm! = 140736894476288
arg = 10
rbx after asm! = 0
result = 0
  • 結果が変わっているのは、リリースビルドの最適化によって何かが変わっているっぽい
  • 動作確認のために、最後のリターンの直前で以下のようにprintln!を挟んだらパニックが発生した。
    unsafe {
        let reg: u64;
        asm!(
            "mov rbx, rcx",
            out("rcx") reg,
        );
        println!("rbx after asm! = {}", reg);
    }
    // この行を追加
    println!("result={}", result);
    result

// 出力は以下の通り
   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.36s
     Running `target/debug/playground`
thread 'main' panicked at 'invalid args', /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/core/src/fmt/mod.rs:309:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
rbx before asm! = 140728984006656
arg = 10

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。
アセンブリ周り触るコードでprintlnすると壊れるやつが多くて怖い...。

今週もプルリクエストはおやすみ。