鱒身(Masu_mi)のブログ

知った事をメモする場所。

[復習]main にたどり着く前を追ってみた

先日、mainに至るまで追ってみたが幾つか疑問を残していた。

  • mainからのバックトレースで外側が見えないのは何故か?
  • __libc_start_main のdisassembleとobjdumpの結果が異なる

詳細にmainまでの挙動を解説しているページがあった。 読んだり他を調べた中で印象に残った部分を記録する。資料も最後にまとめておく。

ただし依然mainからの復帰後にバックトレースが表示できる理由がわからないまま。

関心ごと

ELFのインタプリタはld-linux.so

man execveを読めってことでした。

実行ファイルが動的リンクされた a.out 実行形式で、共有ライブラリの スタブを含むものだった場合、実行の開始時に Linux の ダイナミックリンカー ld.so(8) が呼び出され、必要な共有ライブラリをメモリーに読み込んでリンクを行う。

実行ファイルがダイナミックリンクされた ELF 実行形式だった場合、 PT_INTERP セグメントに指定されたインタープリターが必要な 共有ライブラリ (shared library) を読み込むのに使用される。 通常、インタープリターは glibc をリンクしたバイナリでは /lib/ld-linux.so.2 である。

__libc_start_main は動的にロードされる

そのため開始前にはアドレスが決まらない。 言及しているエントリがあった。 そのまえに、nmでシンボルを確認しろよって話でした。

$ nm ./a.out  | grep __libc_start_main
                 U __libc_start_main@@GLIBC_2.2.5

main の呼び出し箇所の確認

解説記事と異なりx86_64環境だけど確認してみた。 __libc_start_mainの最初の引数が main へのポインタになってた。ただ pushじゃなくて%rdiを使っている。

(gdb) x/10i $rip
=> 0x400409 <_start+9>: and    $0xfffffffffffffff0,%rsp
   0x40040d <_start+13>:        push   %rax
   0x40040e <_start+14>:        push   %rsp
   0x40040f <_start+15>:        mov    $0x400570,%r8
   0x400416 <_start+22>:        mov    $0x400500,%rcx
   0x40041d <_start+29>:        mov    $0x4004f0,%rdi
   0x400424 <_start+36>:        callq  0x4003e0 <__libc_start_main@plt>
   0x400429 <_start+41>:        hlt
   0x40042a <_start+42>:        xchg   %ax,%ax
   0x40042c <_start+44>:        nopl   0x0(%rax)
(gdb) x/i 0x4004f0
   0x4004f0 <main>:     push   %rbp

main関数から外はバックトレースで見えない

この記事で main に入るときはフレームポインタ(%ebp)をスタックに積むなどの事前処理を行わないためmainの内外が分離されることが書かれてた。

スタックに呼び元の情報が記録されないのだとすると%rbp, %rspがどう修復されるのかわからない。 ちゃんとStack layoutやret命令がどう動くか確認する必要がありそう。

return address of mainmain呼び出し前の%ripに対応しret命令で復帰するので実行に問題はない。 だけどmainの外側に戻って即座にバックトレースが取得できることは解せない。