背景(この章は全記事共通)
現在C言語にてOSを開発しています。
どんな機械語が生成されるかを把握するために、色々な検証をしています。
OSのソースは以下です。
投稿時点 : https://github.com/ooe1220/KansoOS/1de6692041b109c82d6824c27cf870ef29ed59cb
最新 : https://github.com/ooe1220/KansoOS
本記事はdocs/notesにある覚書の写しです。
目的
Linux上でC言語をアセンブリへ変換すると、__x86.get_pc_thunkという関数が生成されることがよくあります。
0000118d <main>:
118d: 55 push ebp
118e: 89 e5 mov ebp,esp
1190: e8 10 00 00 00 call 11a5 <__x86.get_pc_thunk.ax>
1195: 05 47 2e 00 00 add eax,0x2e47
119a: 8b 80 2c 00 00 00 mov eax,DWORD PTR [eax+0x2c]
11a0: 83 c0 03 add eax,0x3
11a3: 5d pop ebp
11a4: c3 ret
//gcc -m32 -O0 test.c -o test.out
//objdump -d -M intel test.out
//readelf -S test.out
int global_var = 5;
int main(void)
{
return global_var + 3;
}
__x86.get_pc_thunkの意味
x86にはmov eax, eipという命令がありません。
そこで__x86.get_pc_thunkを呼び出してeip(実行中の命令を指すレジスタ)の値を取得します。
call命令を実行するとCPUは自動的にcall命令が配置されている位置のアドレスをスタックに積んで、指定したアドレスへ跳びます。
実質call add = push 現在アドレス jmp 指定アドレスです。
__x86.get_pc_thunkの中身を見てみるとスタックから返りアドレスを取り出してEAXレジスタに格納しています。
000011a5 <__x86.get_pc_thunk.ax>:
11a5: 8b 04 24 mov eax,DWORD PTR [esp]
11a8: c3 ret
main関数内での作用
広域変数global_varを使用しています。global_varのアドレスはgotに格納されており、この変数を参照するには、現在地からのオフセットで指定します。
GOTのアドレスは以下の通り。
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[22] .got PROGBITS 00003fdc 002fdc 000024 04 WA 0 0 4
main中では1195を取得する為に__x86.get_pc_thunk.axを呼び出しています。
1190: e8 10 00 00 00 call 11a5 <__x86.get_pc_thunk.ax>
1195: 05 47 2e 00 00 add eax,0x2e47
119a: 8b 80 2c 00 00 00 mov eax,DWORD PTR [eax+0x2c]
以下の様に足すとGOTを指すことが出来ます。
0x1195
+0x2e47
--------
0x3fdc
GOTの中身
test@test-fujitsu:~/kaihatsu/ctest$ readelf -S test.out | grep got
[13] .plt.got PROGBITS 00001050 001050 000008 08 AX 0 0 8
[22] .got PROGBITS 00003fdc 002fdc 000024 04 WA 0 0 4 ....
test@test-fujitsu:~/kaihatsu/ctest$ objdump -s -j .got test.out
test.out: 文件格式 elf32-i386
Contents of section .got:
3fdc e43e0000 00000000 00000000 46100000 .>..........F...
3fec 00000000 00000000 00000000 8d110000 ................
3ffc 00000000 ....
test@test-fujitsu:~/kaihatsu/ctest$
mov eax,DWORD PTR [eax+0x2c]はGOTの中身を参照しているのではなく、
GOTを基準アドレスとして global_var の実体位置を計算しています。
そのため、GOT開始アドレス0x3fdcに0x2cを加算すると、global_varが配置されているアドレスになります。
3fdc + 0x2c = 4008
この場所を見てみるとint global_var = 5;の初期値と一致します。
test@test-fujitsu:~/kaihatsu/ctest$ objdump -s --start-address=0x4000 --stop-address=0x4020 test.out
test.out: 文件格式 elf32-i386
Contents of section .data:
4000 00000000 04400000 05000000 .....@......
他のレジスタ向けの関数
今回はEAX向けの関数を使用する機械語が生成されましたが、対象レジスタに応じて以下のような関数も生成されます。
00001090 <__x86.get_pc_thunk.bx>:
1090: 8b 1c 24 mov ebx,DWORD PTR [esp]
1093: c3 ret
00001189 <__x86.get_pc_thunk.dx>:
1189: 8b 14 24 mov edx,DWORD PTR [esp]
118c: c3 ret