Top > Blog > Article > 【Rust+no_std】entrypointが0x0になるときは--entryの値を確認しよう

【Rust+no_std】entrypointが0x0になるときは--entryの値を確認しよう

2023-06-16
2023-06-16

RustでOSをじわじわと作っています。

GitHub - watasuke102/os-proto: my original Operating System (PoC or sandbox)

ところで、最近このような現象が発生しました。

少し前までは普通だったのに、いきなりELFのentrypointが0x0になり、起動不可能になるというものです。流石にOSを起動できないと開発どころではないので、とりあえず調査することにしました。

ちなみに、余談(かどうかもわからない)ですが、以下のような現象も発生していました。全てのシンボルが0始まり(?)・サイズ0になるというものです。

TL;DR

タイトル通りですが、おそらく自作OSでよく使うであろうCustom Targetのjsonやcargoに -C link-arg= で渡すであろう「--entry=XXX」において、存在しないシンボル名を指定するとエントリーポイントが0x0になります。原因はまだ完全に判明していません

環境

  • Arch Linux + linux-zen (6.3.7-zen1-1-zen)
  • rustc 1.72.0-nightly (114fb86ca 2023-06-15)
  • cargo 1.72.0-nightly (0c14026aa 2023-06-14)

調査1. git bisect

バージョン管理システムに感謝しながら2分探索します。以下のコミットが原因だとわかりました。

update: use latest crate · watasuke102/os-proto@6170b21 · GitHub

過去の愚かな自分が生み出した負債こと #![allow(dead_code)] を始めとする数々のallow attributeを排除するというものです。当然ながら、このコミットによって削除されたのは、どこからも呼び出されていない関数・使用されていないuseのみであり、挙動に影響を及ぼすことはない……と思っていました。

調査2. 原因となるファイルを調べる

これは手作業で行いました。といっても、

  1. まず loader/ 下のファイルを全て(一時的に)コミットする
  2. 残った他のファイルを git stash push
  3. 実行して検証、問題が変わらず発生する
  4. loader/ 下の変更はこの問題に関係ないことがわかったため、git resetgit checkout .
  5. git stash pop し、common/ 下のファイルを全てコミット……

という流れで行いました。最終的に main.rs の変更が原因であることがわかりました。

調査3. 原因となる記述を調べる

これも手作業です。とりあえずattributeを消して問題なく動作することを確認し、そこからは地道にuseを消す作業をしていきました。

そしていよいよ原因となる行が判明しました。use kernel::* です。

kernel/lib.rs には過去のプロトタイプで使用していた(現在はどこからも使用されていない)structが入っているだけですが、なぜかこれをuseしないとELFがおかしくなってしまいます。

とりあえず、kernel/lib.rs を削除することでエントリーポイントが正常に設定され、動くようになりました。

調査4. 最小コードで再発するかどうか確かめる

ただ、未だに原因はよくわかっていません。useの有無が出力されるELFに影響を及ぼすというのは直感的には非自明である気がします。

というわけで、ほぼ同じ環境でcargoプロジェクトを作成し、最小のコードでこの現象を再発させることで、何が原因であるかを探ることにしました。「ほぼ同じ環境」ということで、前述のリポジトリにおける kernel/下をまるごとコピーし、src/ 下 は main.rs および lib.rs 以外の全てを削除し、build.rs も消して、lib.rs#![no_std] だけにして、main.rs も最小限のものにしました。内容は以下に示す通りです。

#![no_std]
#![no_main]

use core::panic::PanicInfo;

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

#[no_mangle]
pub extern "sysv64" fn kernel_main() -> ! {
  loop {
  }
}

加えて、Cargo.toml からdependenciesを削除してみたりもしましたが、これはあってもなくても結果は同じでした。

さて、これで確かめてみると、確かに再現します。原因を探っているうち、ふとCustom Targetの指定がおかしいことに気づきました。自作OSのカーネルでは、カーネル用のスタックを確保および設定するためにアセンブリで kernel_entry というラベルを作成し、ここをエントリーポイントとしていました。この再現用プロジェクトには 上記の main.rsと、空の lib.rs しかありません。

前述のリポジトリでは、jsonに以下のような記述がされていました。

  "post-link-args": {
    "ld.lld": ["--entry=kernel_entry", "--image-base=0x1900000", "--static"]
  }

そこで、--entrykernel_main に指定すると、なんとエントリーポイントが正しく設定され、正常に起動が行われました!やった~!

ということで、ひとまず「--entry で存在しないシンボルを指定すると、エントリーポイントが0x0のELFが作成される」ということがわかりました。

わかっていないこと

これで一件落着ということはなく、そもそもの「ほぼ空も同然の lib,rsをuseしないと同様の現象が発生する」という現象の理由はわからないままでした。気付いていないだけでどこかで使っているのであればそもそもビルドできなさそうだし、 lib.rs を消して問題が解決する理由がわかりません。

とりあえず、このような現象が発生したら--entry が正しいかどうかを確かめるのが解決に繋がりそうな手法の1つだということがわかりました。何もわかった気がしませんね。誰かこの現象についてご存知であれば教えていただきたいです。自分でももう少し調べて、わかったことがあれば追記するかもしれません。あったらね……。

icon

わたすけ

高専生 プログラミングでツールを作ったりLinux触ったりゲームしたりしてる
プロフィール詳細はこちら

タグ
TL;DR環境調査1. git bisect調査2. 原因となるファイルを調べる調査3. 原因となる記述を調べる調査4. 最小コードで再発するかどうか確かめるわかっていないこと