5

私のLinuxプログラムでは、アドレスを取得し、に配置された命令が共有ライブラリからロードされた特定の関数を呼び出しているaddrかどうかをチェックする関数が必要です。つまり、でのようなものがあるかどうかを確認する必要があります。callqaddrfunccallq func@PLTaddr

では、Linuxでは、命令funcから関数の実際のアドレスに到達する方法callq func@PLTは?

4

1 に答える 1

11

動的リンカーが実際のロードアドレスを解決した後、実行時にのみそれについて知ることができます。
警告: 以下は少し深い魔法です...

何が起こっているかを説明するには、デバッガーを使用します。

#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_mainputs

ELF の観点からすると、それがどのように正確に行われるかは、インタープリター(別名、動的リンカーの実装) の詳細次第です。

于 2013-02-26T10:52:06.480 に答える