11

この質問の適切な件名が何であるかはわかりませんが、ここで説明します。

コードのクリティカルセクションのコードの局所性/コンパクト性を強制するためにR_X86_64_JUMP_SLOT、呼び出し時に直接「ジャンプスロット」(ELF再配置)を介して外部(動的にロードされた)ライブラリの関数を呼び出す方法を探していますサイト-リンカーが通常PLT/GOTに配置するものですが、これらは呼び出しサイトにインライン化されます。

次のように呼び出しをエミュレートすると、次のようになります。

#include <stdio.h>
int main(int argc, char **argv)
{
        asm ("push $1f\n\t"
             "jmp *0f\n\t"
             "0: .quad %P0\n"
             "1:\n\t"
             : : "i"(printf), "D"("Hello, World!\n"));
        return 0;
}

64ビットワード用のスペースを確保するために、呼び出し自体が機能します(これは、特定のABIルールに違反するため、幸運な偶然の一致についてのコメントはありません。これらはすべて、この質問の対象ではありません。

私の場合、他の方法で回避/対処するために、この例を簡潔にしようとしています)。

次のアセンブリを作成します。

0000000000000000 <メイン>:
0:bf 00 00 00 00 mov $ 0x0、%edi
1:R_X86_64_32 .rodata.str1.1
5:68 00 00 00 00 pushq $ 0x0
6:R_X86_64_32 .text + 0x19
a:ff 24 25 00 00 00 00 jmpq * 0x0
d:R_X86_64_32S .text + 0x11
..。
11:R_X86_64_64 printf
19:31 c0 xor%eax、%eax
1b:c3 retq

しかし(printfイミディエートとして使用しているため、私は推測します...?)ここでのターゲットアドレスはまだPLTフックのアドレスです-同じR_X86_64_64再配置です。libcに対するオブジェクトファイルを実際の実行可能ファイルにリンクすると、次のようになります。

0000000000400428 <printf @ plt>:
  400428:ff 25 92 04 10 00 jmpq * 1049746(%rip)#5008c0 <_GLOBAL_OFFSET_TABLE_ + 0x20>
[...]
0000000000400500 <メイン>:
  400500:bf 0c 06 40 00 mov $ 0x40060c、%edi
  400505:68 19 05 40 00 pushq $ 0x400519
  40050a:ff 24 25 11 05 40 00 jmpq * 0x400511
  400511:[.quad 400428]
  400519:31 c0 xorl%eax、%eax
  40051b:c3 retq
[...]
動的再配置記録
オフセットタイプ値
[...]
00000000005008c0 R_X86_64_JUMP_SLOT printf

つまり、これでも2段階のリダイレクトが行われ、最初に実行がPLTフックに転送され、次にライブラリのエントリポイントにジャンプします。

コンパイラ/アセンブラ/リンカに、この例ではアドレスのジャンプスロットターゲットを「インライン化」するように指示する方法はあります0x400511か?

つまり、「ローカル」(プログラムのリンク時にldR_X86_64_64relocを「リモート」(プログラムのロード時にld.so)に置き換えますR_X86_64_JUMP_SLOT(コードのこのセクションでは非遅延ロードを強制します)?たぶんリンカーマップファイルはこれを可能にするかもしれません-もしそうなら、どのように?

編集:
これを明確にするために、問題は、動的にリンクされた実行可能ファイル/動的ライブラリでのみ使用可能な外部関数でこれを実現する方法についてです。はい、それは本当の静的リンクがこれをより簡単な方法で解決しますが、:

  • 静的ライブラリが通常ベンダーから出荷されていないシステム(Solarisなど)があります
  • ソースコードまたは静的バージョンとして利用できないライブラリがあります

したがって、静的リンクはここでは役に立ちません:(

Edit2:
一部のアーキテクチャ(SPARC、特に、GNUでのSPARCの再配置に関するセクションを手動で参照)では、GNUは修飾子を使用してリンカーの特定のタイプの再配置参照をインプレースで作成できることがわかりました。引用されたSPARCは%gdop(symbolname)、アセンブラに「ここにその再配置を作成する」という指示をリンカーに送信させるために使用します。Itanium上のIntelのアセンブラは、同じ種類のものの@fptr(symbol) リンク再配置演算子を知っています( Itanium psABIのセクション4も参照してください)。しかし、同等のメカニズム(コード内の特定の位置に特定のリンカー再配置タイプを発行するようにアセンブラーに指示するもの)がx86_64に存在しますか?

また、GNUアセンブラには、.relocこの目的で使用されると思われるディレクティブがあることもわかりました。それでも、私が試してみると:

#include <stdio.h>
int main(int argc, char **argv)
{
        asm ("push %%rax\n\t"
             "lea 1f(%%rip), %%rax\n\t"
             "xchg %%rax, (%rsp)\n\t"
             "jmp *0f\n\t"
             ".reloc 0f, R_X86_64_JUMP_SLOT, printf\n\t"
             "0: .quad 0\n"
             "1:\n\t"
             : : "D"("Hello, World!\n"));
        return 0;
}

リンカからエラーが発生します(注意してください7 == R_X86_64_JUMP_SLOT):

エラー:/tmp/cc6BUEZh.o:オブジェクトファイル内の予期しない再配置7
アセンブラは、次のようなオブジェクトファイルを作成しますreadelf
オフセット0x5e8の再配置セクション'.rela.text.startup'には、次の2つのエントリが含まれています。
オフセット情報タイプシンボルの値シンボルの名前+加数
0000000000000001 000000050000000a R_X86_64_32 0000000000000000 .rodata.str1.1 + 0
0000000000000017 0000000b00000007 R_X86_64_JUMP_SLOT 0000000000000000 printf + 0

これは私が欲しいものです-しかし、リンカーはそれを取りません。
リンカR_X86_64_64、上記の代わりに使用するだけで受け入れます。これを行うと、最初の場合と同じ種類のバイナリが作成されます... printf@plt「解決された」バイナリではなく、にリダイレクトされます。

4

3 に答える 3

3

.text呼び出しをインライン化するには、動的にロードされた共有ライブラリ内の関数の最終アドレスとなるコード()の再配置が必要になります。GNU / Linux用のGNUツールチェーンを使用するx86_64には、そのような再配置は存在しません(また、最新の静的リンカーでは許可されません)。したがって、必要に応じて呼び出し全体をインライン化することはできません。

最も近いのは、GOTを介した直接呼び出しです(PLTを回避します)。

    .section    .rodata
.LC0:
    .string "Hello, World!\n"
    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $.LC0, %eax
    movq    %rax, %rdi
    call    *printf@GOTPCREL(%rip)
    nop
    popq    %rbp
    ret
    .size   main, .-main

これR_X86_64_GLOB_DATにより、上記のシーケンスで使用されるGOTのprintfに対する再配置が生成されます。一般に、コンパイラはプロローグとエピローグで任意の数の呼び出し元が保存したレジスタを使用する可能性があるため、Cコードを回避する必要があります。これにより、asm関数呼び出しの周りにそのようなすべてのレジスタを保存および復元する必要があります。そうしないと、後で使用するためにこれらのレジスタが破損するリスクがあります。ラッパー関数で。したがって、純粋なアセンブリでラッパーを作成する方が簡単です。

もう1つのオプションは、コンパイル-Wl,-z,now -Wl,-z,relro時にPLTおよびPLT関連のGOTエントリが起動時に解決され、コードの局所性とコンパクトさが向上することです。フルRELROを使用すると、PLTでコードを実行し、GOTでデータにアクセスするだけで済みます。これは、論理コアのキャッシュ階層のどこかにすでに存在するはずの2つのことです。完全なRELROでニーズを満たすのに十分な場合は、ラッパーは必要なく、セキュリティ上の利点が追加されます。

最良のオプションは、実際に静的リンクまたはLTOが利用可能な場合です。

于 2016-05-25T17:54:54.397 に答える
3

その後、この最適化はGCCに実装されました。-fno-pltオプションnoplt関数属性で有効にできます:

位置に依存しないコードでの外部関数呼び出しにPLTを使用しないでください。代わりに、GOTから呼び出しサイトの呼び出し先アドレスをロードして分岐します。これにより、PLTスタブが排除され、GOTの負荷が最適化されるため、コードがより効率的になります。PLTスタブが特定のレジスタ内のGOTポインタを期待する、32ビットx86などのアーキテクチャでは、これにより、コンパイラにレジスタ割り当ての自由度が高まります。遅延結合にはPLTを使用する必要があります。すべての-fno-plt外部シンボルはロード時に解決されます。

あるいは、function属性nopltを使用して、特定の外部関数に対するPLTを介した呼び出しを回避することもできます。

位置に依存するコードでは、いくつかのターゲットは、PLTを使用せずにGOTを使用するようにマークされた関数への呼び出しも変換します。

于 2020-04-18T16:08:04.663 に答える
-1

実行可能ファイルを静的にリンクできます。最後のリンクコマンドに追加-staticするだけで、間接ジャンプはすべて直接呼び出しに置き換えられます。

于 2012-06-01T11:54:47.773 に答える