2016-05-01 9 views
2

私は、単純な関数をコンパイルして出力を見ることによってアセンブリを学ぼうとしています。gccが関数呼び出しのPLTを参照しないのはなぜですか?

他のライブラリの関数を呼び出す方法を見ています。ここでは他の場所で定義された関数を呼び出すおもちゃのC関数があります:

void give_me_a_ptr(void*); 

void foo() { 
    give_me_a_ptr("foo"); 
} 

ここではgccによって生成されたアセンブリです:

$ gcc -Wall -Wextra -g -O0 -c call_func.c 
$ objdump -d call_func.o 

call_func.o:  file format elf64-x86-64 

Disassembly of section .text: 

0000000000000000 <foo>: 
    0: 55      push %rbp 
    1: 48 89 e5    mov %rsp,%rbp 
    4: bf 00 00 00 00   mov $0x0,%edi 
    9: e8 00 00 00 00   callq e <foo+0xe> 
    e: 90      nop 
    f: 5d      pop %rbp 
    10: c3      retq 

私はcall <[email protected]>のようなものを期待していました。 give_me_a_ptrがどこに定義されているかを知る前に、これが相対位置にジャンプしているのはなぜですか?

私はまたmov $0, %ediに困惑しています。これはnullポインタを渡しているようです - 確かにmov $address_of_string, %rdiはここで正しいでしょうか?

+0

再配置エントリを表示するには、 'objdump -dr'を使用します。 – Jester

+1

FYI、 'clang -S'は、あなたのmov 0x0/callqのペアの代わりにこれを出します:' leaq L_.str(%rip)、%rdi; callq _give_me_a_ptr'です。 –

+2

PLTについては、PICをコンパイルする場合にのみ使用され、 '-fPIC'フラグを使用します。 – Jester

答えて

6

あなたはsymbol-interpositionを構築していないが(-fPICの副作用を)有効になって、そのcall宛先アドレスが潜在的に静的に同じ実行可能ファイルにリンクされている別のオブジェクトファイル内のアドレスへのリンク時に解決することができます。 (例えば、gcc foo.o bar.o)。

シンボルが唯一あなたが(gcc foo.o -lbar)へのリンク動的しているライブラリで発見された場合は、callはサポートにPLTを通じてindirectedする必要があります。

は、今これはトリッキーな部分である:without -fPIC or -fPIE、GCCはまだ直接関数を呼び出しASM発する:

int puts(const char*);   // puts exists in libc, so we can link this example 
void call_puts(void) { puts("foo"); } 

    # gcc 5.3 -O3 (without -fPIC) 
    movl $.LC0, %edi  # absolute 32bit addressing: slightly smaller code, because static data is known to be in the low 2GB, in the default "small" code model 
    jmp  puts    # tail-call optimization. Same as call puts/ret, except for stack alignment 

をしかし、あなたがリンクされたバイナリを見れば: (this Godbolt compiler explorer linkに、「バイナリ」ボタンをクリックしてくださいリンクの際

# disassembled linker output 
    mov $0x400654,%edi 
    jmpq 400490 <[email protected]> 

gcc -S ASM出力と objdump -dr分解の間を切り替えるには、 putsへの呼び出しは、「魔法」を通じて間接に交換しました [email protected]、および [email protected]定義がリンクされた実行可能ファイルに存在します。

これはどのように動作するのか分かりませんが、リンク時に共有ライブラリにリンクしています。重要なことは、関数プロトタイプを共有ライブラリにあるものとしてマークするためにヘッダファイルに何も必要としないことです。 putsを自分で宣言するのと同じように、<stdio.h>を含めると同じ結果になります。 (これは非常にお勧めしません。それが唯一のヘッダに宣言で正しく動作するCの実装のために、おそらく法的だしかし、Linux上で動作するように起こる。。)


位置に依存しない実行ファイルをコンパイルする場合(-fPIEで)、リンクされたバイナリは、-fPICとまったく同じように、PLTを介してputsにジャンプします。しかし、コンパイラのアセンブラ出力が異なる(上記godboltリンク上でそれを自分で試してください):

call_puts: # compiled with -fPIE 
    leaq .LC0(%rip), %rdi  # RIP-relative addressing for static data 
    jmp  [email protected] 

関数への呼び出しのためのPLTによるコンパイラ軍の間接それがために定義を参照することはできません。なぜか分からない。 PIEモードでは、共有ライブラリではなく実行可能ファイルのコードをコンパイルしています。リンカは、複数のオブジェクトファイルを、実行可能ファイルに定義されている関数間の直接呼び出しで、位置に依存しない実行可能ファイルにリンクできなければなりません。私はLinux(私のデスクトップとゴッドボルト)でテストしています。OS Xではなく、gcc -fPIEがデフォルトであると仮定しています。これはIDKとは異なる設定になっている可能性があります。 -fPIC代わりの-fPIE


は、物事はさらに悪化している。でも、PLTを通過する必要があり、同じコンパイル単位内で定義されたグローバル関数の呼び出し、symbol interpositionをサポートします。 (例えばLD_PRELOAD=intercept_some_functions.so ./a.out-fPIC-fPIE

違いはPIEは、同じコンパイル単位で機能のためのシンボルの介在をとることができないが、PICはないことを主としています。 OS Xは、位置に依存しない実行可能ファイルと共有ライブラリを必要としますが、ライブラリのコードを作成するときとコンパイラが実行可能ファイルのコードを作るときとで異なることがあります。

このGodbolt exampleには、PICモードとPIEモードに関するものを示すいくつかの機能があります。 call_puts()は、PICモードの別の関数にインラインで挿入することはできず、PIEのみをインライン化することはできません。

も参照してください。Shared object in Linux without symbol interposition, -fno-semantic-interposition error。あなたがアドレスが移転に基づいて、リンク時にリンカによって置き換えられます0をプレースホルダている.oから解体出力を見ているmov $0, %edi

によって困惑


ELFオブジェクトファイル内の情報だから@Leandrosはobjdump -rを提案した。

同様に、callマシンコード内の相対変位は、リンカーがまだ入力していないため、すべてゼロです。

-1

私はまだこのリンクプロセスを自分自身で研究していますが、私自身の言葉で何かを書き直したいと思っていました。 PLT関連のユーザ関数呼び出しは、実行が開始されるまでにすべてが適切なコードで詰め込まれるとは限りません。そのようにすると、実行の開始時に多くの時間がかかることがあります。 PLTによって計装された関数呼び出しのすべてが使用されるわけではありません。だから、 'lazy binding'メソッドの下では、最初に 'user'関数がPLTコードを介して呼び出されます。まず、PLTの 'binding function'にジャンプします。バインディング関数が出て、 'user'関数(GOTから考える)の正しいアドレスを見つけて、 'user'関数を指すコードでPLTエントリ(バインディング関数を指す)を置き換えます。その後、ユーザ関数が呼び出されるたびに、 'lazy'バインディング関数は呼び出されません。代わりに 'user'関数が呼び出されます。これは、最初の紅潮でPLTエントリが奇妙に見える理由かもしれません。それはバインディング関数を指しており、 'user'関数には向いていません。

+0

遅延バインディングは、PLTエントリへの最初の呼び出し後に発生し、コンパイラが 'call puts'または' call puts @ PLT'を使用するかどうかに影響しません。また、PLTを変更するのではなく、GOTを変更します。 PLTは読み取り専用であり、 'puts @ PLT:jmp [puts @ GOTPLT]'の形で間接JMP命令を使用します。共有ライブラリがバインドされた後、遅延バインディングコードを呼び出すコードでGOTの 'puts @ GOTPLT'エントリは、共有ライブラリの' puts'をポイントします。 –

関連する問題