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にしてデフォルト実装を与えたり、明示的に外部リンケージにすることができます。
出典
- Embedonomicon