64ビットLinuxはデフォルトでスモールメモリモデルを使用します。これにより、すべてのコードと静的データが2GBのアドレス制限を下回ります。これにより、32ビットの絶対アドレスを使用できるようになります。古いバージョンのgccは、相対アドレス計算のための追加の命令を保存するために、静的配列に32ビットの絶対アドレスを使用します。ただし、これは機能しなくなりました。アセンブリで32ビットの絶対アドレスを作成しようとすると、「共有オブジェクトの作成時に「.data」に対する再配置R_X86_64_32Sは使用できません。-fPICで再コンパイルしてください」というリンカーエラーが発生します。もちろん、このエラーメッセージは誤解を招くものです。私が共有オブジェクトを作成しておらず、-fPICが役に立たないためです。これまでにわかったことは、gccバージョン4.8.5では静的配列に32ビットの絶対アドレスを使用しますが、gccバージョン6.3.0では使用しません。バージョン5もおそらくそうではありません。 binutils 2.24のリンカーは32ビットの絶対アドレスを許可しますが、verson 2.28は許可しません。
この変更の結果、古いライブラリを再コンパイルする必要があり、レガシーアセンブリコードが破損します。
今私は尋ねたいと思います:この変更はいつ行われたのですか?どこかに文書化されていますか?また、32ビットの絶対アドレスを受け入れるリンカーオプションはありますか?
あなたのディストリビューションは_--enable-default-pie
_でgccを設定したので、デフォルトで位置に依存しない実行可能ファイルを作成しています(実行可能ファイルとライブラリのASLRを許可しています)。最近では、ほとんどのディストリビューションがそれを行っています。
あなたは実際にが共有オブジェクトを作成しています:PIE実行可能ファイルは、エントリポイントを持つ共有オブジェクトを使用する一種のハックです。ダイナミックリンカーはすでにこれをサポートしており、ASLRはセキュリティに優れているため、実行可能ファイルにASLRを実装する最も簡単な方法でした。
ELF共有オブジェクトでは、32ビットの絶対再配置は許可されていません。これにより、低2GiB(符号拡張された32ビットアドレスの場合)の外部にロードされなくなります。 64ビットの絶対アドレスを使用できますが、一般的には、命令の一部としてではなく、ジャンプテーブルまたはその他の静的データに対してのみ必要です。1
エラーメッセージの_recompile with -fPIC
_の部分は、手書きのasmでは偽です。 _gcc -c
_でコンパイルしてから_gcc -shared -o foo.so *.o
_でリンクしようとする人のために書かれています。gccで_-fPIE
_はではありませんデフォルト。手書きのasmをリンクすると、多くの人がこのエラーに遭遇するため、エラーメッセージはおそらく変わるはずです。
欠点がない単純なケースでは、常にRIP相対アドレッシングを使用してください。以下の脚注1および 構文のこの回答 も参照してください。有害ではなくコードサイズに実際に役立つ場合のみ、32ビット絶対アドレス指定の使用を検討してください。例えばNASM _default rel
_ファイルの先頭。
AT&T foo(%rip)
またはGASでは_.intel_syntax noprefix
_ _[rip + foo]
_を使用します。
_gcc -fno-pie -no-pie
_を使用して、これをオーバーライドして以前の動作に戻します。 _-no-pie
_はリンカーオプション、 _-fno-pie
_はコード生成オプション 。 _-fno-pie
_のみの場合、gccは_mov eax, offset .LC0
_のように、まだ有効な_-pie
_とリンクしないコードを作成します。
(clangデフォルトでPIEを有効にすることもできます:_clang -fno-pie -nopie
_を使用します。A 2017年7月のパッチ 作成_-no-pie
_ an _-nopie
_のエイリアス。gccとの互換性がありますが、clang4.0.1にはありません。)
_-no-pie
_のみ(ただし、_-fpie
_)コンパイラ生成コード(CまたはC++ソースから)は必要以上に少し遅く、大きくなりますにリンクされますASLRの恩恵を受けない位置依存の実行可能ファイル。 "パフォーマンスが低下すると、PIEが多すぎます" SPEC CPU2006でx86-64の平均速度が3%低下すると報告(IDKの紙のコピーがないので上にあったハードウェア:/)。しかし、32ビットコードでは、平均の速度低下は10%、最悪の場合は25%です(SPEC CPU2006)。
Agnerが質問で説明しているように、PIE実行可能ファイルのペナルティは、静的配列のインデックス付けなどの場合にほとんどです。静的アドレスを32ビットイミディエイトとして、または_[disp32 + index*4]
_アドレッシングモードの一部として使用すると、命令とレジスタが保存されます。アドレスをレジスターに取得するためのRIP相対LEA。また、静的アドレスをレジスタに取得するための7バイト_mov r32, imm32
_ではなく5バイト_lea r64, [rel symbol]
_は、文字列リテラルまたは他の静的データのアドレスを関数に渡すのに適しています。
_-fPIE
_は、GOTを介してグローバルにアクセスする必要がある共有ライブラリの_-fPIC
_とは異なり、グローバル変数/関数のシンボル挿入がないと想定しています(これは、static
を使用するもう1つの理由です)グローバルではなくファイルスコープに制限できる変数の場合)。 Linuxの動的ライブラリの申し訳ありません を参照してください。
したがって、_-fPIE
_は、64ビットコードの_-fPIC
_よりも悪くはありませんが、それでもRIP相対アドレッシングが利用できないため、32ビットの場合は不良です。 Godboltコンパイラエクスプローラのいくつかの例 を参照してください。平均すると、_-fPIE
_は64ビットコードでのパフォーマンス/コードサイズの低下が非常に小さくなります。特定のループの最悪のケースは、ほんの数%です。ただし、32ビットのPIEの方がはるかに悪い場合があります。
これらの_-f
_ code-genオプションは、リンクするだけの場合、または_.S
_の手書きのasmをアセンブルする場合に違いはありません。 _gcc -fno-pie -no-pie -O3 main.c nasm_output.o
_は、両方のオプションが必要な場合です。
GCCがこのように構成されている場合、_gcc -v |& grep -o -e '[^ ]*pie'
_は_--enable-default-pie
_を出力します。この構成オプションのサポートは、gccに early 2015 で追加されました。 Ubuntuは16.10でそれを有効にし、Debianはgcc _6.2.0-7
_でほぼ同時に有効にしました(カーネルビルドエラーの原因: https://lkml.org/lkml/2016/10/21/904 )。
関連: PIEとして圧縮x86カーネルをビルド も、変更されたデフォルトの影響を受けました。
なぜLinuxは実行可能コードセグメントのアドレスをランダム化しないのですか? なぜそれが以前にデフォルトでなかったのか、または有効になる前に古いUbuntuのいくつかのパッケージでのみ有効にされたのかについての古い質問です全面的に。
ld
自体はデフォルトを変更しなかったであることに注意してください。それはまだ正常に動作します(少なくともbinutils 2.28を備えたArch Linuxでは)。変更点は、明示的に_-pie
_または_-static
_を使用しない限り、gcc
はデフォルトで_-no-pie
_をリンカーオプションとして渡すことです。
NASMソースファイルでは、_a32 mov eax, [abs buf]
_を使用して絶対アドレスを取得しました。 (私は小さな絶対アドレスをエンコードする6バイトの方法(address-size + mov eax、moffs:_67 a1 40 f1 60 00
_)がIntel CPUでLCPストールを持っているかどうかをテストしていました。 そうします 。)
_nasm -felf64 -Worphan-labels -g -Fdwarf testloop.asm &&
ld -o testloop testloop.o # works: static executable
gcc -v -nostdlib testloop.o # doesn't work
...
..../collect2 ... -pie ...
/usr/bin/ld: testloop.o: relocation R_X86_64_32 against `.bss' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status
gcc -v -no-pie -nostdlib testloop.o # works
gcc -v -static -nostdlib testloop.o # also works: -static implies -no-pie
_
GCCは_-static-pie
_を使用して「静的PIE」を作成することもできます。動的ライブラリやELFインタープリターによってASLRされません。 _-static -pie
_と同じではありません。これらは互いに競合します(静的な非PIEを取得します) 変更される可能性があります 。
関連: libcあり/なしの静的/動的実行可能ファイルの構築、__start
_またはmain
の定義。
file
およびreadelf
は、PIEはELF実行可能ファイルではなく「共有オブジェクト」であると述べています。 ELFタイプEXECをPIEにすることはできません。
_$ gcc -fno-pie -no-pie -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB executable, ...
$ gcc -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB shared object, ...
## Or with a more recent version of file:
a.out: ELF 64-bit LSB pie executable, ...
_
gcc _-static-pie
_は、現在のfile
とともに_LSB pie executable
_、_dynamically linked
_として表示されます。 ELFタイプのDYNですが、readelf
は_.interp
_を表示せず、ldd
は静的にリンクされていることを示します。 GDB starti
および_/proc/maps
_は、ELFインタープリターではなく、その__start
_の先頭から実行が開始されることを確認します。
これも質問されています: Linuxバイナリが位置独立コードとしてコンパイルされたかどうかをテストする方法?
準関連(ただし、実際にはそうではありません):別の最近のgcc機能は_gcc -fno-plt
_です。最後に、共有ライブラリへの呼び出しは、PLTトランポリンなしで、単に_call [rip + symbol@GOTPCREL]
_(AT&T call *puts@GOTPCREL(%rip)
)にすることができます。
これのNASMバージョンは_call [rel puts wrt ..got]
_です
_call puts wrt ..plt
_の代替として。 64ビットLinuxでアセンブリ(yasm)コードからC標準ライブラリ関数を呼び出せない を参照してください。これはPIEまたは非PIEで機能し、リンカーがPLTスタブを作成するのを回避します。
一部のディストリビューションはそれを有効にし始めました。また、書き込み可能な+実行可能なメモリページを必要としないため、コードインジェクションに対するセキュリティに優れています。これは、多くの共有ライブラリ呼び出しを行うプログラム(たとえば、 x86-64 _clang -O2 -g
_のコンパイルtramp3dは、どのハードウェアでも41.6秒から36.8秒になります パッチ作成者がテストしたもの 。 (clangは、共有ライブラリー呼び出しの最悪のシナリオであり、小さなLLVMライブラリー関数を何度も呼び出します。)
遅延動的リンクの代わりに事前バインディングが必要なので、すぐに終了する大きなプログラムの場合は遅くなります。 (例:_clang --version
_または_hello.c
_のコンパイル)。この速度低下は、どうやらプレリンクで軽減できる可能性があります。
ただし、これによって共有ライブラリのPICコードの外部変数のGOTオーバーヘッドが削除されるわけではありません。 (上記のgodboltリンクを参照してください)。
脚注1
Linux ELF共有オブジェクトでは、実際には64ビットの絶対アドレスが許可されており、異なるアドレス(ASLRおよび共有ライブラリ)でのロードを可能にする text relocations が指定されています。これにより、ランタイム初期化子なしで_section .rodata
_または_static const int *foo = &bar;
_にジャンプテーブルを作成できます。
したがって、_mov rdi, qword msg
_は機能します(10バイトのNASM/YASM構文 _mov r64, imm64
_ 、別名AT&T構文movabs
、64ビットの即時を使用できる唯一の命令)。しかし、それは_lea rdi, [rel msg]
_よりも大きく、通常は低速です。これは、_-pie
_を無効にしない場合に使用する必要があります。 Agner Fogのmicroarch pdf によると、64ビットイミディエイトはSandybridgeファミリCPUのuopキャッシュからフェッチするのに時間がかかります。 (はい、この質問をした人と同じ人です:)
すべての_default rel
_アドレッシングモードで指定する代わりに、NASMの_[rel symbol]
_を使用できます。 32ビットの絶対アドレス指定を回避するための詳細については、 Mach-O 64ビット形式は32ビットの絶対アドレスをサポートしていません。NASMは配列にアクセスしています も参照してください。 OS Xは32ビットのアドレスをまったく使用できないため、RIP相対アドレス指定も最善の方法です。
位置依存コード(_-no-pie
_)では、レジスタにアドレスが必要な場合は_mov edi, msg
_を使用する必要があります。 5バイトの_mov r32, imm32
_は、RIP相対LEAよりもさらに小さく、より多くの実行ポートで実行できます。