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
レジスタで渡してるかどうかは、呼び出し側と呼び出される側のバイナリを逆アセンブルすれば確認できる
デバッグ情報つきでコンパイルして、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
教科書どおりにやっていれば、~/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から値を取り出していることがわかる
- レッドゾーンはスタックポインタを少し超えたスタック領域のこと
- System V AMD64 ABIではRSPの手前128バイトの領域がレッドゾーンと規定されている
- この領域は実行中の関数により予約されていて、割り込みハンドラが勝手に変更してはならない、と決められている
- すなわち、他の関数を呼び出さない関数(leaf function)はRSPの値を調整することなくレッドゾーンを使って良い。通常、スタックを一時領域として使うにはRSPから使いたい大きさだけ数値を引くが、欲しい領域の大きさが128バイト以下であればRSPの値を調整せずにレッドゾーンを利用できる
例(教科書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を使っている)
-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を使っていない)
- 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;
}
例(教科書の例だと、関数呼び出し前後の動きが分からないので、より簡素な例にしつつ関数呼び出しの部分から動きを追う)
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]