3-4. リンカ

ベアメタルプログラミングを行う上で、リンカは避けられない要素です。ここでは、Rustでシンボルセクションを扱う方法について、説明します。

シンボル名とセクション配置

C++と同様に、デフォルトではRustのシンボルは、コンパイラによってマングルされます。コンパイラが生成したシンボル名は、コンパイラのバージョンごとに異なる可能性があります。そこで、次のアトリビュートを使用して、シンボル名やセクション配置を制御します。

  • #[export_name = "foo"]は、関数や変数のシンボル名をfooに設定します。
  • #[no_mangle]は、関数名や変数名をマングルせず、そのままシンボル名として使用します。
  • #[link_section = ".bar"]は、対象のシンボルを、.barというセクションに配置します。

#[no_mangle]は、基本的に、#[export_name = <item-name>]のシンタックスシュガーです。このことから、#[no_mangle][#link_section]との組み合わせで、任意のシンボルを特定のセクションに配置できます。

次のコードは、ARM Cortex-Mシリーズのリセットベクタを指定セクションに配置する例です。Reset関数の関数ポインタを、RESET_VECTORというシンボルで、.vector_table.reset_vectorセクションに配置します。もちろん、このセクションはリンカスクリプトで定義されている必要があります。

#[link_section = ".vector_table.reset_vector"]
#[no_mangle]
pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;

補足:上のコードでexternを使用している理由は、Cortex-MシリーズのハードウェアがリセットハンドラにC ABIを要求するためです。

リンカスクリプトのシンボルを参照

Rustからリンカスクリプトのシンボルを参照することも可能です。これは、.bssセクションのゼロクリアや、.dataセクションの初期化に利用できます。

次のように、リンカスクリプトでセクションの開始位置と終了位置にシンボルを作成します。

SECTIONS
{
  .bss :
  {
    _sbss = .;
    *(.bss .bss.*);
    _ebss = .;
  } > RAM

  .data : AT(ADDR(.rodata) + SIZEOF(.rodata))
  {
    _sdata = .;
    *(.data .data.*);
    _edata = .;
  } > RAM

  _sidata = LOADADDR(.data);

これらのリンカスクリプトで作成したシンボルは、Rustで次のように利用できます。これは、.bssセクションのゼロクリアと、.dataセクションの初期化を行うコードの例です。

use core::ptr;

#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
    // RAMの初期化
    extern "C" {
        static mut _sbss: u8;
        static mut _ebss: u8;

        static mut _sdata: u8;
        static mut _edata: u8;
        static _sidata: u8;
    }

    let count = &_ebss as *const u8 as usize - &_sbss as *const u8 as usize;
    ptr::write_bytes(&mut _sbss as *mut u8, 0, count);

    let count = &_edata as *const u8 as usize - &_sdata as *const u8 as usize;
    ptr::copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count);

    loop {}
}

リンカスクリプトで作成したシンボルをu8の変数として、そのアドレスを利用します。

extern

externは、Rustのキーワードで、外部とのインタフェースに使用されます。外部クレートとの接続にも使われますが、組込みでの重要な利用方法はFFI (Foreign function interfaces) です。

他言語の変数や関数を利用する場合、下記の通りexternブロック内でインタフェースを宣言します。

    extern "C" {
        static mut _sbss: u8;
        // ...
    }

逆に、他言語からRustのコードを呼ぶ場合は、次のように関数シグネチャを宣言します。

#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
    // ...
}

FFIだけでなく、どこか外部にあるRustコードを宣言することも可能です。

extern "Rust" {
    fn main() -> !;
}

コラム〜RustのABIは安定化していない!?〜

意外に思うかもしれませんが、RustのABIは定義されていません。4年前からこのissueで議論が続けられています。

そのため、Rustで安定したABIを提供するためには、extern "C"を用いてC言語のABIを使用しなければなりません。

/// C ABI
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
    /* ... */
}

linkageアトリビュート

linkageアトリビュートは、まだunstableの状態です。linkage featureのissueで議論が続いています。このアトリビュートは、シンボルのリンケージを制御するものです。例えば、特定のシンボルをweakにしてデフォルト実装を与えたり、明示的に外部リンケージにすることができます。

出典