maeharin; 第3章 画面表示の練習とブートローダ - uchan-nos/os-from-zero GitHub Wiki

輪読会資料

https://docs.google.com/presentation/d/1-e6PZot13EfjeBECQUuUzoimdlQSBK4Z9Tggvds8r_k

ブートローダからカーネルを呼び出す部分

関数呼び出しの規約

System V AMD64 ABIでは、64ビットまでの整数やポインタを引数にする場合、先頭からRDI、RSI、RDX、RCX、R8、R9の順でレジスタを割り当てる

https://refspecs.linuxbase.org/
System V Application Binary Interface x86-64 Architecture Processor Supplement Draft Version
のv0.99

Screenshot from 2022-04-18 13-58-24

関数呼び出し時、引数をレジスタで渡してる様子を確認する

レジスタで渡してるかどうかは、呼び出し側と呼び出される側のバイナリを逆アセンブルすれば確認できる
デバッグ情報つきでコンパイルして、objdumpのオプションに-Sをつけると、C言語のソースコードと機械語命令を対応づけて表示できる

呼び出す側(ブートローダ側)

edk2でデバッグ情報つけてビルドする方法は3つある

  • Situation A: Setup by overriding file "target.txt"
  • Situation B: Use a parameter -b BUILDTARGET when executing building command
  • Situation C: Setup in the DSC file of a platform

https://edk2-docs.gitbook.io/edk-ii-build-specification/12_build_changes_and_customizations/121_building_for_debug

教科書どおりにやっていれば、~/edk2/Conf/target.txtでDEBUGが指定されてるので、buildコマンド叩けばDEBUG情報つけたものが出力されている。 ちなみにデバッグ情報付きのファイルはLoader.elfとは別にLoader.debugというファイルで出力される模様

$ cd ~/edk2
$ objdump -S -d -M intel Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.debug | grep -A 10 -B 3 entry_point

  // #@@range_begin(call_kernel)
  typedef void EntryPointType(UINT64, UINT64);
  EntryPointType* entry_point = (EntryPointType*)entry_addr;
  entry_point(gop->Mode->FrameBufferBase, gop->Mode->FrameBufferSize);
     69d:	48 8b 4d b8          	mov    rcx,QWORD PTR [rbp-0x48]
     6a1:	48 8b 49 18          	mov    rcx,QWORD PTR [rcx+0x18]
     6a5:	48 8b 79 18          	mov    rdi,QWORD PTR [rcx+0x18]
     6a9:	48 8b 71 20          	mov    rsi,QWORD PTR [rcx+0x20]
     6ad:	ff 50 18             	call   QWORD PTR [rax+0x18]
  // #@@range_end(call_kernel)

  Print(L"All done\n");
     6b0:	48 83 ec 20          	sub    rsp,0x20
     6b4:	48 8d 0d 81 17 00 00 	lea    rcx,[rip+0x1781]        # 1e3c <.L.str.34>

確かに、callする直前にrdiとrsiに値をセットしていることが分かる

呼び出される側(カーネル側)

kernel.elf

clang++の-gオプションでデバッグ情報つける 最適化オプションを-O0にして逆アセンブルしてみる(最適化オプション-O2だとrdi, rsiを使っていそうだがそれが引数として使われているのかよくわからなかった)

$ cd ~/workspace/mikanos/kernel/
$ source ~/osbook/devenv/buildenv.sh
$ clang++ $CPPFLAGS  -O0   -Wall   -g   --target=x86_64-elf   -ffreestanding   -mno-red-zone   -fno-exceptions   -fno-rtti   -std=c++17   -c   main.cpp
$ ld.lld   --entry KernelMain   -z norelro   --image-base 0x100000   --static   -o kernel.elf   main.o
objdump -S -d -M intel kernel.elf
セクション .text の逆アセンブル:

0000000000101000 <KernelMain>:
#include <cstdint>

extern "C" void KernelMain(uint64_t frame_buffer_base,+
                           uint64_t frame_buffer_size) {
  101000:	55                   	push   rbp
  101001:	48 89 e5             	mov    rbp,rsp
  101004:	48 83 ec 20          	sub    rsp,0x20
  101008:	48 89 7d f8          	mov    QWORD PTR [rbp-0x8],rdi
  10100c:	48 89 75 f0          	mov    QWORD PTR [rbp-0x10],rsi
  uint8_t* frame_buffer = reinterpret_cast<uint8_t*>(frame_buffer_base);
  101010:	48 8b 75 f8          	mov    rsi,QWORD PTR [rbp-0x8]
  101014:	48 89 75 e8          	mov    QWORD PTR [rbp-0x18],rsi

確かにrdi, rsiから値を取り出していることがわかる

コラム 3.1 レッドゾーン

  • レッドゾーンはスタックポインタを少し超えたスタック領域のこと
  • System V AMD64 ABIではRSPの手前128バイトの領域がレッドゾーンと規定されている
  • この領域は実行中の関数により予約されていて、割り込みハンドラが勝手に変更してはならない、と決められている
  • すなわち、他の関数を呼び出さない関数(leaf function)はRSPの値を調整することなくレッドゾーンを使って良い。通常、スタックを一時領域として使うにはRSPから使いたい大きさだけ数値を引くが、欲しい領域の大きさが128バイト以下であればRSPの値を調整せずにレッドゾーンを利用できる

redzone

例(教科書P81)

g.cpp

int g(int index) {
  int a[16] = {1, 1};
  for (int i = 2; i < 16; ++i) {
    a[i] = a[i - 2] + a[i - 1];
  }
  return a[index];
}


int main() {
  g(10);
}

-mno-red-zoneつけずにコンパイル(つまり、red-zoneを使う)

$ clang++ -O1 -mno-sse -c g.cpp
$ objdump -d -M intel g.o

g.o:     ファイル形式 elf64-x86-64


セクション .text の逆アセンブル:

0000000000000000 <_Z1gi>:
   0:	48 c7 44 24 f0 00 00 	mov    QWORD PTR [rsp-0x10],0x0
   7:	00 00 
   9:	48 c7 44 24 e8 00 00 	mov    QWORD PTR [rsp-0x18],0x0
  10:	00 00 
  12:	48 c7 44 24 e0 00 00 	mov    QWORD PTR [rsp-0x20],0x0
  19:	00 00 
  1b:	48 c7 44 24 d8 00 00 	mov    QWORD PTR [rsp-0x28],0x0
  22:	00 00 
  24:	48 c7 44 24 d0 00 00 	mov    QWORD PTR [rsp-0x30],0x0
  2b:	00 00 
  2d:	48 c7 44 24 c8 00 00 	mov    QWORD PTR [rsp-0x38],0x0
  34:	00 00 
  36:	48 c7 44 24 c0 00 00 	mov    QWORD PTR [rsp-0x40],0x0
  3d:	00 00 
  3f:	48 b8 01 00 00 00 01 	movabs rax,0x100000001
  46:	00 00 00 
  49:	48 89 44 24 b8       	mov    QWORD PTR [rsp-0x48],rax
  4e:	b8 01 00 00 00       	mov    eax,0x1
  53:	b9 02 00 00 00       	mov    ecx,0x2
  58:	0f 1f 84 00 00 00 00 	nop    DWORD PTR [rax+rax*1+0x0]
  5f:	00 
  60:	03 44 8c b0          	add    eax,DWORD PTR [rsp+rcx*4-0x50]
  64:	89 44 8c b8          	mov    DWORD PTR [rsp+rcx*4-0x48],eax
  68:	48 83 c1 01          	add    rcx,0x1
  6c:	48 83 f9 10          	cmp    rcx,0x10
  70:	75 ee                	jne    60 <_Z1gi+0x60>
  72:	48 63 c7             	movsxd rax,edi
  75:	8b 44 84 b8          	mov    eax,DWORD PTR [rsp+rax*4-0x48]
  79:	c3                   	ret    
  7a:	66 0f 1f 44 00 00    	nop    WORD PTR [rax+rax*1+0x0]

↑たしかに rspの値は変えずにrsp-0x10のようにrspより手前の番地にアクセスしてる(つまり、redzoneを使っている)

use_redzone

-mno-red-zoneつけてコンパイル

$ clang++ -O1 -mno-sse -mno-red-zone -c g.cpp
$ objdump -d -M intel g.o

g.o:     ファイル形式 elf64-x86-64


セクション .text の逆アセンブル:

0000000000000000 <_Z1gi>:
   0:	48 83 ec 48          	sub    rsp,0x48
   4:	48 c7 44 24 38 00 00 	mov    QWORD PTR [rsp+0x38],0x0
   b:	00 00 
   d:	48 c7 44 24 30 00 00 	mov    QWORD PTR [rsp+0x30],0x0
  14:	00 00 
  16:	48 c7 44 24 28 00 00 	mov    QWORD PTR [rsp+0x28],0x0
  1d:	00 00 
  1f:	48 c7 44 24 20 00 00 	mov    QWORD PTR [rsp+0x20],0x0
  26:	00 00 
  28:	48 c7 44 24 18 00 00 	mov    QWORD PTR [rsp+0x18],0x0
  2f:	00 00 
  31:	48 c7 44 24 10 00 00 	mov    QWORD PTR [rsp+0x10],0x0
  38:	00 00 
  3a:	48 c7 44 24 08 00 00 	mov    QWORD PTR [rsp+0x8],0x0
  41:	00 00 
  43:	48 b8 01 00 00 00 01 	movabs rax,0x100000001
  4a:	00 00 00 
  4d:	48 89 04 24          	mov    QWORD PTR [rsp],rax
  51:	b8 01 00 00 00       	mov    eax,0x1
  56:	b9 02 00 00 00       	mov    ecx,0x2
  5b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]
  60:	03 44 8c f8          	add    eax,DWORD PTR [rsp+rcx*4-0x8]
  64:	89 04 8c             	mov    DWORD PTR [rsp+rcx*4],eax
  67:	48 83 c1 01          	add    rcx,0x1
  6b:	48 83 f9 10          	cmp    rcx,0x10
  6f:	75 ef                	jne    60 <_Z1gi+0x60>
  71:	48 63 c7             	movsxd rax,edi
  74:	8b 04 84             	mov    eax,DWORD PTR [rsp+rax*4]
  77:	48 83 c4 48          	add    rsp,0x48
  7b:	c3                   	ret    
  7c:	0f 1f 40 00          	nop    DWORD PTR [rax+0x0]

↑たしかに rspの値をsubで手前に更新して、その後rsp+0x10のようにrspより後ろの番地にアクセスしてる(つまり、redzoneを使っていない)

no_use_redzone

コラム3.2 ポインタのキャスト

  • OS作りではポインタが絡むキャストを多用する
  • ポインタとは「指し示す先のアドレスと型を組み合わせたもの」といえる
  • キャスト後のポインタが指すアドレスは同じで、指す先の型が変わるだけ

例(教科書P87)

#include <iostream>
int main() {
  unsigned long long_var = 0x40200000;
  float* float_ptr = (float*)&long_var;
  std::cout << *float_ptr << std::endl; // 2.5

  *float_ptr = 1.0;
  std::cout << std::hex << long_var << std::endl; // 3f800000
  return 0;
}

cast01

cast02

3.7 ポインタ入門(2):ポインタとアセンブリ言語

例(教科書の例だと、関数呼び出し前後の動きが分からないので、より簡素な例にしつつ関数呼び出しの部分から動きを追う)

foo.cpp

void foo() {
  int aa = 1;
  int bb = 2;
  int cc = 3;
}

int main() {
  int a = 1;
  int b = 2;
  foo();
} 
$ clang++ -g -mno-red-zone foo.cpp
$ objdump -S -d -M intel a.out 
0000000000401110 <_Z3foov>:
void foo() {
  401110:	55                   	push   rbp
  401111:	48 89 e5             	mov    rbp,rsp
  401114:	48 83 ec 0c          	sub    rsp,0xc
  int aa = 1;
  401118:	c7 45 fc 01 00 00 00 	mov    DWORD PTR [rbp-0x4],0x1
  int bb = 2;
  40111f:	c7 45 f8 02 00 00 00 	mov    DWORD PTR [rbp-0x8],0x2
  int cc = 3;
  401126:	c7 45 f4 03 00 00 00 	mov    DWORD PTR [rbp-0xc],0x3
}
  40112d:	48 83 c4 0c          	add    rsp,0xc
  401131:	5d                   	pop    rbp
  401132:	c3                   	ret    
  401133:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
  40113a:	00 00 00 
  40113d:	0f 1f 00             	nop    DWORD PTR [rax]

0000000000401140 <main>:

int main() {
  401140:	55                   	push   rbp
  401141:	48 89 e5             	mov    rbp,rsp
  401144:	48 83 ec 10          	sub    rsp,0x10
  int a = 1;
  401148:	c7 45 fc 01 00 00 00 	mov    DWORD PTR [rbp-0x4],0x1
  int b = 2;
  40114f:	c7 45 f8 02 00 00 00 	mov    DWORD PTR [rbp-0x8],0x2
  foo();
  401156:	e8 b5 ff ff ff       	call   401110 <_Z3foov>
  40115b:	31 c0                	xor    eax,eax
}
  40115d:	48 83 c4 10          	add    rsp,0x10
  401161:	5d                   	pop    rbp
  401162:	c3                   	ret    
  401163:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
  40116a:	00 00 00 
  40116d:	0f 1f 00             	nop    DWORD PTR [rax]

図解 https://docs.google.com/spreadsheets/d/1gG41Xx8ncTesHQwiwZVsUEBU5J-hkJBqC1EBcnpocRQ/edit#gid=926203692

⚠️ **GitHub.com Fallback** ⚠️