Rustで自作OSをしているときのデバッグ例 - syscall 命令と仲良くなりたい!後編

この記事は自作OS Advent Calendar 2022の18日目の記事です。前回のあらすじはこちら:

hikalium.hatenablog.jp

例外を吐いてるのはどこのどいつだぁ?

さて、前回の状況をおさらいすると、

  • 0x200fd0の命令がアドレス0x2000000000にアクセスしようとしてページフォルト(0xe)を発生させた。
  • ページフォルトを処理している最中に一般保護例外(0xd)が発生し、一般保護例外を処理中にもさらに一般保護例外が発生することで、順次スタックが食い潰されていった。
  • 例外ハンドラのスタックが枯渇し、0x3de4bed8にアクセスしようとした段階でページフォルトが発生した。これにより、ダブルフォルトが発生した。
  • ダブルフォルトハンドラ内で再度ページフォルトが発生した結果、トリプルフォルトとなってリセットがかかった。

というわけで、まずは最初のきっかけである 0x200fd0とかにある命令がどこのどいつかを特定することにしましょう。

まず、アプリのロード状況を確認します。OSのログを見ると、

Executable found: Elf { name: hello, data: @0x000000003e2d9000 } 
This ELF seems to be executable!
phdr_start: 64, phdr_entry_size: 56, num_of_phdr_entry: 5
type  : (rwx) = (101)
offset: 0x0000000000000000
vaddr : 0x0000000000200000
fsize : 0x0000000000001B64
vsize : 0x0000000000001B64
align : 0x0000000000001000

type  : (rwx) = (110)
offset: 0x0000000000001B68
vaddr : 0x0000000000202B68
fsize : 0x00000000000001C8
vsize : 0x00000000000001C8
align : 0x0000000000001000

load_region_start = 0x0000000000200000
load_region_end = 0x0000000000203000
load_region @ 0x000000003d233000
dst_range: 0x0000000000000000 - 0x0000000000001B64
dst_range: 0x0000000000002B68 - 0x0000000000002D30
run the code!
entry_point = 0x000000003d2332f0

というふうになっています。これはどういうことかというと…図示したほうがはやそうなので、お絵描きしました。

現在のアプリケーションのロードの様子

現在は手抜きをするために、アプリケーションとOSのメモリ空間を分けていないので、アプリの希望通りのメモリレイアウトではなく、相対位置を維持したまま、異なるアドレスにCodeとDataの両方をロードしているのでした。

さて、例外を発生させたメモリアドレス(0x200fd0)は、このロードされたコード(0x3d233000-0x3d234b64)の中…にありませんね…あれ?

…と思って状況をよくよく確認すると、どうもこのアドレスは、アプリがもともと予期していた、コード領域がロードされるべきアドレス範囲(0x200000-0x201b64)の中にあるようです…つまり、アプリケーションが正しく再配置可能ではなかった、もしくはローダーが仕事をサボっているせいで、正しく再配置できなかった、のいずれかの可能性であると考えられます。ちょっとアプリのバイナリのほうを確認してみましょう。

$ `brew --prefix binutils`/bin/objdump -d -C target/x86_64-unknown-elf/release/hello | grep -A 3 200fd0
0000000000200fd0 <<core::fmt::Arguments as core::fmt::Display>::fmt>:
  200fd0:       48 83 ec 38             sub    $0x38,%rsp
  200fd4:       48 8b 06                mov    (%rsi),%rax
  200fd7:       48 8b 76 08             mov    0x8(%rsi),%rsi
  200fdb:       48 8b 4f 28             mov    0x28(%rdi),%rcx

objdumpに-Cオプションをつけると、シンボルをデマングル(人間が読みやすい形式に変換)してくれるみたいで、Rustのシンボル名にも対応してるみたいです。ありがとうbinutils!

というわけでですね、この200fd0は、明らかにDisplay::fmtを呼び出しているコードで利用されていて、我々のprintln!のコードパスで通っていそうな雰囲気がひしひしと伝わってきます。

さて、これを呼び出している側のコードはどうなっているのでしょうか?rip相対のcallではこのようなことは起こり得ないので、レジスタ間接でのcallがDisplay::fmtへ制御を移そうとしているはずです。なので、レジスタ間接callを使っている人々をリストアップしましょう。

$ `brew --prefix binutils`/bin/objdump -d -C target/x86_64-unknown-elf/release/hello | grep -E -e 'call.*\*' -e ' <.*>:$' | grep 'call.*\*' -B 1
0000000000200860 <<core::panic::panic_info::PanicInfo as core::fmt::Display>::fmt>:
  200886:       41 ff 56 18             call   *0x18(%r14)
  2008bf:       ff 50 18                call   *0x18(%rax)
--
0000000000201030 <core::fmt::write>:
  2010b7:       ff 50 18                call   *0x18(%rax)
  201177:       ff 54 08 08             call   *0x8(%rax,%rcx,1)
  2011d8:       ff 50 18                call   *0x18(%rax)
  2011e9:       42 ff 54 35 08          call   *0x8(%rbp,%r14,1)
  20122f:       ff 51 18                call   *0x18(%rcx)
0000000000201250 <core::fmt::Formatter::pad_integral>:
  2013f1:       ff 55 20                call   *0x20(%rbp)
  20150a:       41 ff 55 20             call   *0x20(%r13)
  201557:       ff 55 18                call   *0x18(%rbp)
  201582:       41 ff 55 18             call   *0x18(%r13)
  2015aa:       41 ff 55 20             call   *0x20(%r13)
00000000002015d0 <core::fmt::Formatter::pad_integral::write_prefix>:
  2015f1:       41 ff 54 24 20          call   *0x20(%r12)
0000000000201630 <core::fmt::Formatter::pad>:
  2018ea:       41 ff 56 20             call   *0x20(%r14)
  20190d:       41 ff 56 18             call   *0x18(%r14)
  20193f:       41 ff 56 20             call   *0x20(%r14)

まあ…たぶんcore::fmt::writeですよね…きっとそうだ…

`brew --prefix binutils`/bin/objdump -d -C target/x86_64-unknown-elf/release/hello | grep '<core::fmt::write>:' -A 155 | grep -z -E -e 'call' --color='always'

qemu/target/i386/cpu.h

$ cat qemu_debug.log | grep hikalium | head -n 3
hikalium: raise_interrupt2: is_int == 0 intno = 14 eip=0x000000000000200FD0 error_code = 0x000000000000000000
hikalium: rsp: 00000000003E286BE0
hikalium: ret_to: 00000000003D2341EE
$ cat com2.log | grep -a -e entry_point
entry_point = 0x000000003d2332f0
$ make objdump_hello | grep '<entry>:'
00000000002002f0 <entry>:
$ ./scripts/debug/inspect_app_crash.sh 
0000000000201030 <core::fmt::write>:
  2011e9:       42 ff 54 35 08          call   *0x8(%rbp,%r14,1)
  2011ee:       84 c0                   test   %al,%al

.data.rel.roがあやしい!

https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/specialsections.html

Chromium Docs - Native Relocations

Relative relocations and RELR | MaskRay

$ `brew --prefix binutils`/bin/objcopy -O binary --only-section=.data.rel.ro generated/bin/hello data.bin
[00:34:16]
[local] hikalium@t05.z01.hikalium.com: ~/repo/wasabi  
$ hexdump -C data.bin 
00000000  58 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |X. .............|
00000010  58 01 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |X. .............|
00000020  58 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |X. .............|
00000030  e0 04 20 00 00 00 00 00  08 00 00 00 00 00 00 00  |. .............|
00000040  08 00 00 00 00 00 00 00  20 06 20 00 00 00 00 00  |........ . .....|
00000050  f0 04 20 00 00 00 00 00  c0 05 20 00 00 00 00 00  |. ...... .....|
00000060  60 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |`. .............|
00000070  60 01 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |`. .............|
00000080  61 01 20 00 00 00 00 00  08 00 00 00 00 00 00 00  |a. .............|
00000090  60 01 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |`. .............|
000000a0  e0 04 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
000000b0  01 00 00 00 00 00 00 00  70 07 20 00 00 00 00 00  |........p. .....|
000000c0  b0 03 20 00 00 00 00 00  80 04 20 00 00 00 00 00  |. ....... .....|
000000d0  e0 04 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
000000e0  01 00 00 00 00 00 00 00  70 19 20 00 00 00 00 00  |........p. .....|
000000f0  94 01 20 00 00 00 00 00  13 00 00 00 00 00 00 00  |.. .............|
00000100  37 00 00 00 05 00 00 00  94 01 20 00 00 00 00 00  |7......... .....|
00000110  13 00 00 00 00 00 00 00  53 00 00 00 05 00 00 00  |........S.......|
00000120  db 01 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |. .............|
00000130  dc 01 20 00 00 00 00 00  03 00 00 00 00 00 00 00  |. .............|
00000140  a8 02 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
00000150  a8 02 20 00 00 00 00 00  01 00 00 00 00 00 00 00  |. .............|
*
00000170  40 1a 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |@. .............|
00000180  01 00 00 00 00 00 00 00  50 08 20 00 00 00 00 00  |........P. .....|
00000190  b8 02 20 00 00 00 00 00  00 00 00 00 00 00 00 00  |. .............|
000001a0  b8 02 20 00 00 00 00 00  02 00 00 00 00 00 00 00  |. .............|
000001b0

https://uclibc.org/docs/elf-64-gen.pdf

Hardening ELF binaries using Relocation Read-Only (RELRO)

https://maskray.me/blog/2021-08-29-all-about-global-offset-table

Airs – Ian Lance Taylor » Linker relro

cnlelema.github.io

時間切れ

というわけで追いかけていたら.data.rel.roの沼にはまったので今日はここまで。明日もやるかも。おやすみなさい…。