main にたどり着く前を追ってみた
結婚式準備の気分転換とgdb
の練習を兼ねてmain
にたどり着くまでの動きを追った。
GCCでは以下の関数を設定するとmain
に入る前に実行される。
|
|
Linux環境のバイナリはELF形式が一般的で、ELFでは_start
がデフォルトのエントリポイントになる。
今回の整理
だいたい下の事がわかったが、main の中でバックトレースを確認しても_start
などが表示されない理由がわからなかった。
- GCCを使うとELFのデフォルトエントリポイントは
_start
_start
は/usr/lib64/crt1.o
由来- OSXはELFファイルでなく
_start
がリンク時に衝突する事はない - GCC拡張の
constructor
は__libc_csu_init()
から呼ばれる - mainは
__libc_start_main()
から呼ばれる - ELFのエントリポイントは変更可能だが後処理の自作が必要になる
一応main 外部がバックトレースで表示されない理由は下が考えられる。
- スタック積み上げ動作が異なり呼び元の情報が不完全になる
- gdbが気を効かせてmain以降を遡らない
とりあえず切り分けるには手作業でESP, EBPを追いmainから__libc_csu_init
,_start
に辿り着けるか確認すれば良い。
そしてgdbの優しさの疑いが高まった場合に、ソースコードを読みに行けば解決しそう。
ヘタレとしては面倒くさくなりそうな気がする。
サンプルを準備する
|
|
|
|
_startがリンクされる事を確認する
バイナリに含まれる事を確認する。
|
|
_start は /usr/lib64/crt1.o 由来
以下の関数を定義して衝突させる。
|
|
以下の様に衝突させて衝突先オブジェクトファイルを確認した。ちなみにOSXでは衝突しない。
|
|
constructorは__libc_csu_init から呼ばれる
以下の様に呼び元を確認できる。
|
|
main は __libc_start_main の中で呼ばれる
main
を読んでいたのは__libc_start_main
の以下にあった。$rax
がmain
である事は確認した。
下の箇所のcall
でmain
が実行されるのは確認できたが、main
の中からバックトレースで_start
が見えない事が疑問として残った。jmp
じゃない。
(gdb) disassemble __libc_start_main
//...
0x00007ffff7a3baaf <+175>: jne 0x7ffff7a3bb03 <__libc_start_main+259>
0x00007ffff7a3bab1 <+177>: mov %fs:0x300,%rax
0x00007ffff7a3baba <+186>: mov %rax,0x68(%rsp)
0x00007ffff7a3babf <+191>: mov %fs:0x2f8,%rax
0x00007ffff7a3bac8 <+200>: mov %rax,0x70(%rsp)
0x00007ffff7a3bacd <+205>: lea 0x20(%rsp),%rax
0x00007ffff7a3bad2 <+210>: mov %rax,%fs:0x300
0x00007ffff7a3badb <+219>: mov 0x3983be(%rip),%rax # 0x7ffff7dd3ea0
0x00007ffff7a3bae2 <+226>: mov 0x8(%rsp),%rsi
0x00007ffff7a3bae7 <+231>: mov 0x14(%rsp),%edi
0x00007ffff7a3baeb <+235>: mov (%rax),%rdx
0x00007ffff7a3baee <+238>: mov 0x18(%rsp),%rax
=> 0x00007ffff7a3baf3 <+243>: callq * %rax
0x00007ffff7a3baf5 <+245>: mov %eax,%edi
また__libc_start_main
は動的に作られるかロード時に再配置されておりobjdump
と異なる命令列がメモリ上には存在した。
シンボルがPLT
上に居るので関係していそう。
|
|
エントリポイントを素朴に変更してもセグフォる
以下の様にエントリポイントを変更しても後処理が上手くいかないのかmain
実行後にセグメンテーションフォルトを起こす。
ちなみに_start
を経由せずmain
に入る事はgdb
で確認できる。
|
|