私のLinuxプログラムでは、アドレスを取得し、に配置された命令が共有ライブラリからロードされた特定の関数を呼び出しているaddr
かどうかをチェックする関数が必要です。つまり、でのようなものがあるかどうかを確認する必要があります。callq
addr
func
callq func@PLT
addr
では、Linuxでは、命令func
から関数の実際のアドレスに到達する方法callq func@PLT
は?
私のLinuxプログラムでは、アドレスを取得し、に配置された命令が共有ライブラリからロードされた特定の関数を呼び出しているaddr
かどうかをチェックする関数が必要です。つまり、でのようなものがあるかどうかを確認する必要があります。callq
addr
func
callq func@PLT
addr
では、Linuxでは、命令func
から関数の実際のアドレスに到達する方法callq func@PLT
は?
動的リンカーが実際のロードアドレスを解決した後、実行時にのみそれについて知ることができます。
警告: 以下は少し深い魔法です...
何が起こっているかを説明するには、デバッガーを使用します。
#include <stdio.h>
int main(int argc, char **argv) { printf("Hello, World!\n"); return 0; }
コンパイルします ( gcc -O8 ...
)。objdump -d
バイナリショーで(耐えられないプレーン文字列printf()
に置き換えられることの最適化...):puts()
セクション .init の分解: [ ... ] セクション .plt の分解: 0000000000400408 < __libc_start_main@plt-0x10 >: 400408 : ff 35 a2 04 10 00 pushq 1049762(%rip) # 5008b0 <_GLOBAL_OFFSET_TABLE_+0x8> > 40040e: ff 25 a4 04 10 00 jmpq *1049764(%rip) # 5008b8 <_GLOBAL_OFFSET_TABLE_+0x10> [ ... ] 0000000000400428 <puts@plt> : 400428: ff 25 9a 04 10 00 jmpq *1049754(%rip) # 5008c8 <_GLOBAL_OFFSET_TABLE_+0x20> 40042e: 68 01 00 00 00 プッシュq $0x1 400433: e9 d0 ff ff ff jmpq 400408 <_init+0x18> [ ... ] 0000000000400500 <メイン> : 400500: 48 83 ec 08 サブ $0x8、%rsp 400504: bf 0c 06 40 00 mov $0x40060c,%edi 400509: e8 1a ff ff ff callq 400428 <puts@plt> 40050e: 31 c0 xor %eax,%eax 400510: 48 83 c4 08 追加 $0x8、%rsp 400514: c3 retq
にロードしgdb
ます。それで:
$ gdb ./tcc GNU gdb Red Hat Linux (6.3.0.0-0.30.1rh) [ ... ] (gdb) x/3i 0x400428 0x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32> 0x40042e: pushq $0x1 0x400433: jmpq 0x400408 (gdb) x/gx 0x5008c8 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>: 0x000000000040042e
jmpq
この値は、最初の;の直後の命令を指していることに注意してください。これは、puts@plt
スロットが最初の呼び出しで次のように「フォールスルー」することを意味します。
(gdb) x/3i 0x400408 0x400408: pushq 1049762(%rip) # 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8> 0x40040e: jmpq *1049764(%rip) # 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16> 0x400414: いいえ (gdb) x/gx 0x5008b0 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8>: 0x0000000000000000 (gdb) x/gx 0x5008b8 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16>: 0x0000000000000000
関数のアドレスと引数はまだ初期化されていません。
プログラムロード直後、実行前の状態です。実行を開始します。
(gdb) ブレークメイン 0x400500 のブレークポイント 1 (gdb) 実行 開始プログラム: tcc (デバッグ シンボルが見つかりません) (デバッグ シンボルが見つかりません) メインのブレークポイント 1、0x0000000000400500 () (gdb) x/i 0x400428 0x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32> (gdb) x/gx 0x5008c8 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>: 0x000000000040042e
したがって、これはまだ変更されていませんが、ターゲット(初期化のGOT
内容libc
) が異なります。
(gdb) x/gx 0x5008b0 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8>: 0x0000002a9566b9a8 (gdb) x/gx 0x5008b8 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16>: 0x0000002a955609f0 (gdb) disas 0x0000002a955609f0 関数 _dl_runtime_resolve のアセンブラー コードのダンプ: 0x0000002a955609f0 <_dl_runtime_resolve+0> : サブ $0x38,%rsp [ ... ]
つまり、プログラムのロード時に、動的リンカはinit
最初に " " 部分を解決します。GOT
動的リンクコードにリダイレクトするポインターで参照を 置き換えます。
したがって、最初に参照を介して外部からバイナリへの関数を呼び出すと.plt
、リンカーに再びジャンプします。それをさせてから、その後プログラムを調べてください - 状態が再び変化しました:
(gdb) ブレーク *0x0000000000400514 0x400514 のブレークポイント 2 (gdb) 続行 つづく。 こんにちは世界! ブレークポイント 2、メイン () の 0x0000000000400514 (gdb) x/i 0x400428 0x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32> (gdb) x/gx 0x5008c8 0x5008c8 : 0x0000002a956c8870 (gdb) disas 0x0000002a956c8870関数puts のアセンブラー コードのダンプ: 0x0000002a956c8870 <puts+0>: mov %rbx,0xffffffffffffffe0(%rsp) [ ... ]
だから今すぐリダイレクトがありますlibc
- へのPLT
参照はputs()
最終的に解決されました.
実際の関数ロード アドレスを挿入するリンカへの指示(これは_dl_runtime_resolve
、ELF バイナリの特別なセクションから来ています。
$ readelf -a tcc [ ... ] プログラム ヘッダー: タイプ オフセット VirtAddr PhysAddr FileSiz MemSiz フラグの整列 [ ... ] INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 [プログラムインタープリタの要求: /lib64/ld-linux-x86-64.so.2] [ ... ] オフセット 0x700 の動的セクションには、21 のエントリが含まれます。 タグタイプの名前/値 0x0000000000000001 (必要) 共有ライブラリ: [libc.so.6] [ ... ] オフセット 0x3c0 の再配置セクション '.rela.plt' には 2 つのエントリが含まれています。 オフセット情報タイプ記号。値の記号。名前 + 追加 0000005008c0 000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0 0000005008c8 000200000007 R_X86_64_JUMP_SLO 0000000000000000 プット + 0
上記以外にも ELF には多くの機能がありますが、これらの 3 つの部分は、カーネルのバイナリ形式ハンドラーに、「この ELF バイナリには、最初にロード/初期化する必要があるインタープリター」(動的リンカー) があり、必要 libc.so.6
であり、オフセットがあることを伝えます。プログラムの書き込み可能データ セクション内の0x5008c0
およびは、動的リンクのステップが実際に実行されるときに、 および のロード アドレスにそれぞれ置き換えられる必要0x5008c8
があります。__libc_start_main
puts
ELF の観点からすると、それがどのように正確に行われるかは、インタープリター(別名、動的リンカーの実装) の詳細次第です。