tviewをLLVM 10でビルドするとページフォルトが発生する
uchan-nos opened this issue · 3 comments
MikanOSのビルドはLLVM 7(llvm-7, lld-7, clang-7)で動作確認しているが,
ふとした思いでtviewアプリをLLVM 10でビルドしたらカーネル側でページフォルトが起きることが分かった。
カーネルをLLVM 7,tviewをLLVM 10でビルドし,QEMUで起動させ,tview jpn.txt
を実行するとページフォルトが発生する。例外時の RIP は 0x14efb4 であった。その周辺の逆アセンブル結果は次。
14efac: 4c 8b 6e 70 mov r13,QWORD PTR [rsi+0x70]
14efb0: 4c 8b 76 78 mov r14,QWORD PTR [rsi+0x78]
14efb4: 48 0f c3 47 40 movnti QWORD PTR [rdi+0x40],rax
14efb9: 4c 0f c3 47 48 movnti QWORD PTR [rdi+0x48],r8
14efbe: 4c 0f c3 4f 50 movnti QWORD PTR [rdi+0x50],r9
例外が起きたのは movnti 命令。例外が起きたときのスクリーンショットは下図。
ld.lld-10, clang-10, clang++-10 を使ってビルドすると PF が発生する。
clang/clang++ のバージョンは固定で ld.lld のみ ld.lld-7 を使うようにすると正常に動くようになる。
とりあえず lld-7 と lld-10 でリンクした場合のマップファイルの違いを見てみる。まず注目するのは .text 領域の開始アドレスが lld-7 のときは 0xffff80000000b000,lld-10 になると 32 バイトだけ後方にずれ,0xffff80000000b020 になること。
lld-7 でリンクした場合のマップファイル(.text 開始位置付近):
ffff800000009f28 ffff800000009f28 3c 1 /home/uchan/osbook/devenv/x86_64-elf/lib/libc++abi.a(cxa_noexception.cpp.o):(.eh_frame+0x50)
ffff800000009f68 ffff800000009f68 a4 1 /home/uchan/osbook/devenv/x86_64-elf/lib/libc++abi.a(cxa_noexception.cpp.o):(.eh_frame+0x8c)
ffff80000000b000 ffff80000000b000 2f598 16 .text
ffff80000000b000 ffff80000000b000 b57 16 tview.o:(.text)
ffff80000000b000 ffff80000000b000 ef 1 MapFile(char const*)
ffff80000000b0f0 ffff80000000b0f0 14d 1 OpenTextWindow(int, int, char const*)
lld-10 でリンクした場合のマップファイル(.text 開始位置付近):
ffff800000009f28 ffff800000009f28 3c 1 /home/uchan/osbook/devenv/x86_64-elf/lib/libc++abi.a(cxa_noexception.cpp.o):(.eh_frame+0x50)
ffff800000009f68 ffff800000009f68 a4 1 /home/uchan/osbook/devenv/x86_64-elf/lib/libc++abi.a(cxa_noexception.cpp.o):(.eh_frame+0x8c)
ffff80000000b020 ffff80000000b020 2f598 16 .text
ffff80000000b020 ffff80000000b020 b57 16 tview.o:(.text)
ffff80000000b020 ffff80000000b020 ef 1 MapFile(char const*)
ffff80000000b110 ffff80000000b110 14d 1 OpenTextWindow(int, int, char const*)
.text の開始アドレスが 32 バイトだけ後ろになるため,ELF の LOAD セグメントの開始アドレスもずれるようだ。
readelf -l アプリバイナリ
とするとプログラムヘッダを一覧できる。その差分を見てみる。
$ diff -y <(readelf -l tview.lld7) <(readelf -l tview.lld10)
Elf file type is EXEC (Executable file) Elf file type is EXEC (Executable file)
Entry point 0xffff80000000b880 | Entry point 0xffff80000000b8a0
There are 5 program headers, starting at offset 64 There are 5 program headers, starting at offset 64
Program Headers: Program Headers:
Type Offset VirtAddr PhysAd Type Offset VirtAddr PhysAd
FileSiz MemSiz Flags FileSiz MemSiz Flags
PHDR 0x0000000000000040 0xffff800000000040 0xffff PHDR 0x0000000000000040 0xffff800000000040 0xffff
0x0000000000000118 0x0000000000000118 R 0x0000000000000118 0x0000000000000118 R
LOAD 0x0000000000000000 0xffff800000000000 0xffff LOAD 0x0000000000000000 0xffff800000000000 0xffff
0x000000000000a014 0x000000000000a014 R 0x000000000000a014 0x000000000000a014 R
LOAD 0x000000000000b000 0xffff80000000b000 0xffff | LOAD 0x000000000000a020 0xffff80000000b020 0xffff
0x000000000002f598 0x000000000002f598 R E 0x000000000002f598 0x000000000002f598 R E
LOAD 0x000000000003b000 0xffff80000003b000 0xffff | LOAD 0x00000000000395c0 0xffff80000003b5c0 0xffff
0x0000000000003e78 0x0000000000003f10 RW | 0x0000000000003e78 0x0000000000003f0c RW
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000
0x0000000000000000 0x0000000000000000 RW 0x0000000000000000 0x0000000000000000 RW
Section to Segment mapping: Section to Segment mapping:
Segment Sections... Segment Sections...
00 00
01 .rodata .eh_frame 01 .rodata .eh_frame
02 .text 02 .text
03 .data .data.rel.ro .got .bss 03 .data .data.rel.ro .got .bss
04 04
Read + Exec の LOAD セグメント(.text 領域が含まれるセグメント)の仮想アドレスが lld-10 では 0xffff80000000b020 になっていることが分かった。仮想アドレスが 4KiB 境界にないことは想定しておらず,そのへんでバグが発生している可能性がある。
lld-10 で 0x20 だけ後ろにずれる理由を推測する。
注目するのは Read Exec の LOAD セグメントのオフセットが lld-7 は 0xb000 で lld-10 は 0xa020 になっていること。
lld-7 は 4KiB 境界にそろっているが,lld-10 は前のセグメントに詰めて配置される。
おそらくファイル内に余計な空白を作らないためだろう。
ファイル内オフセットが,そのままメモリ上のオフセットに影響している。
ページ(4KiB)単位でコピーができるようにとの配慮だろうか。