Open18

ゼロからのOS自作入門をRustでやってみる

sikinatmsikinatm

4章 ピクセル描画とmake入門

sikinatmsikinatm

frame_bufferの開始アドレスとか、各種設定とか、ブートローダーとkernel両方から参照するものをcommonに切り出す

sikinatmsikinatm

commonをブートローダーから読み込んでビルドすると、こんな感じのエラーが出るように...

error[E0152]: duplicate lang item in crate `core` (which `std` depends on): `sized`
  |
  = note: the lang item is first defined in crate `core` (which `make_mikan_os_boot_loader` depends on)
  = note: first definition in `core` loaded from /IdeaProjects/make-mikan-os/boot-loader/target/x86_64-unknown-uefi/debug/deps/libcore-befb811bf5037789.rlib
  = note: second definition in `core` loaded from /root/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-uefi/lib/libcore-2b7b408b124de8bc.rlib
sikinatmsikinatm

とりあえず、core とはこれ。
https://doc.rust-lang.org/core/

標準ライブラリ(std)の基本部分を提供するRustの低レベルライブラリ。
組み込み環境など、stdが使えない環境(no_std)で利用されることを想定しているらしい。

こいつが重複しているのはなぜ?

sikinatmsikinatm

2つの事実を確認した

  • ブートローダーからカーネルに渡した構造体にメンバ関数を生やし、カーネル側で呼び出すと失敗

  • カーネル側で以下のようなメソッドを呼び出すと失敗

    pub struct PixelColor {
      pub r: u8,
      pub g: u8,
      pub b: u8,
    }
    
    impl PixelColor {
      // これ
      pub fn new(r: u8, g: u8, b: u8) -> Self {
          Self { r, g, b }
      }
    }
    

    なんでだろう。

sikinatmsikinatm

5章 文字表示とコンソールクラス

sikinatmsikinatm
let font = match c {
    'A' => get_font(65),
    _ => &[0; 16],
};

ここで読み出したバイト列が全部0で埋まっているっぽい...
ファイルの中身をみるとちゃんと値入っているが...

sikinatmsikinatm

わからなすぎたのでChatGPTに聞いてみる

回答
まず、この出力から分かるポイントは「ELFの種類が DYN (Position-Independent Executable; PIE) になっている」という点です。
通常、自作OSのカーネルやブートローダを作る場合、リンカスクリプトを使った静的リンクで EXEC (もしくは必要に応じて REL) タイプのELFを作ることが多く、DYN はやや不自然といえます。

なぜ DYN になっていると問題になるのか
DYN (PIE) タイプ のELFは、本来Linuxなどの環境でユーザープロセスとして動的に再配置されることを想定しています。
そのため、DYNAMIC セクションや .dynsym/.dynstr など「動的リンキング用の情報」が含まれ、自作OS向けカーネル としては不要・邪魔になる部分が増えます。
さらに、この出力を見ても .data, .bss, .rodata といったセクションがどのセグメント(LOAD)にも割り当てられていないように見えます。(セクション→セグメント対応に .text と .dynamic/.got のみ出てきている)
結果として .rodata が実行ファイルに含まれていない or ロードされない → include_bytes! したデータを実行時に読もうとすると「全部 0」になってしまう、という症状が起こり得ます。

対策・チェックポイント
1. なぜ DYN タイプになっているかを調べる
Rust で「カーネル用のバイナリ」を作る場合、通常は以下のいずれかで「PIE や動的リンキングを無効化」する必要があります。

カスタムターゲット(JSON)を使う

例: x86_64-unknown-none.json の中で
json
Copy code
{
  "llvm-target": "x86_64-unknown-none",
  "pointer-width": 64,
  "data-layout": "...",
  "arch": "x86_64",
  "os": "none",
  "exe": true,
  "relocation-model": "static",
  "disable-redzone": true,
  "linker-flavor": "ld.lld",
  "panic-strategy": "abort",
  "executables": true,
  "no-compiler-rt": false
}
といった設定をして cargo build --target x86_64-unknown-none.json などでビルドする。
特に "relocation-model": "static", "executables": true あたりが重要
-C relocation-model=static や -Z no-pie を指定

Cargo の RUSTFLAGS などに -C relocation-model=static -Z no-pie を入れる (ただし stable Rust だと -Z no-pie は使えません。Nightly専用)
リンカに対しても -static 相当の指定をする
いずれにせよ、「位置独立実行形式 (PIE) と動的リンキングをオフにして、EXEC タイプを生成」 するのが自作OSでは一般的です。

2. .rodata がどこへ行ったのかを readelf -S で確認
readelf -l ではセグメント(プログラムヘッダ)の情報が出ていますが、さらに

bash
Copy code
readelf -S target/x86_64-unknown-none/debug/make_mikan_os_kernel
でセクションの一覧も確認してください。

.rodata がそもそも存在しない?
.text にマージされている?
あるいは .rodata があってもセグメントに含まれていない?
などを絞り込めます。

3. リンカスクリプトの書き方は正しいか
すでに提示していただいたリンカスクリプト:

ld
Copy code
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kernel_main)

SECTIONS {
  . = 0x100000 + SIZEOF_HEADERS;

  .text : {
    *(.text .text.*)
    *(.data .data.*)
    *(.rodata .rodata.*)
    *(.bss .bss.*)
  }
}
は、一見すると すべてのセクションを一つの領域にまとめています。
ただし「実際にそれがコンパイラの出力とマッチしているか」や「リンカがその通りにマッピングしているか」は別問題です。

もしリンカが「PIE前提」のオプションを付けてしまっている場合、この .ld ファイルの指定が無視されたり、.text セクション以外を別途動的セクションに分割する可能性があります。
カスタムリンカスクリプトを使う場合は、-Clink-arg=-Tlinker.ld のように「明示的にリンカスクリプトを渡す」設定を Cargo 側でしているか確認しましょう。
4. 実際に .rodata がロードされているか
さらにブートローダ側の問題として、ELFのプログラムヘッダをパースし、LOAD セグメントを全部メモリにコピーしているか も重要です。

今回の readelf -l 結果を見ると LOAD セグメントは3個 (offset 0, offset 0x620, offset 0x3560) あり、それぞれ FileSiz や MemSiz が割り当てられています。
しかし「Section to Segment mapping」に .rodata が含まれていない → もし .rodata が別セクションになっていれば、そもそもそこをロードしない可能性が高い。
自作OSの場合は自前ローダーが「.text セクションの分だけコピーしている」「PT_LOAD のうち1つしかコピーしていない」などの実装不備で .rodata を読み落としているケースが多々あります。
結論
今のビルド結果が DYN (PIE) になっている のはおそらく想定外で、これが原因で .rodata が正しくロードされていない可能性大。
EXEC タイプ の「静的にリンクされたELF」になるよう、カスタムターゲットファイル or リンカオプション (-C relocation-model=static, -C link-arg=-static, など) を再設定するのがおすすめ。
readelf -S でセクション、objdump -s -j .rodata で実際のバイナリ内容を確認し、「フォントデータが本当に入っているか」「それがどのセグメントに含まれているか」をもう一度調べてみてください。
ブートローダで ELF をロードするとき、PT_LOAD セグメントをすべて正しくメモリに配置しているか も再確認する必要があります。
以上が「他に知りたい情報ある?」への回答と合わせて、現状“何も描画されない/中身が 0 になる”原因を探るうえで最も重要なポイントです。

要約すると、kernelのビルド結果であるelfファイルが動的リンクになっているのが原因っぽく、静的リンクにすれば治る可能性があるよ、とのこと