gcc はデフォルトで PIE 実行可能ファイルをビルドしています (x86-64 Linux では 32 ビット絶対アドレスは許可されなくなりましたか?)。
理由はわかりませんが、そうすると、リンカーは call puts
を自動的に解決しません。 call [email protected]
まで .まだ puts
あります PLT エントリが生成されましたが、call
実行時に、動的リンカーは puts
を解決しようとします その名前の libc シンボルに直接移動し、call rel32
を修正します .しかし、シンボルは +-2^31 以上離れているため、R_X86_64_PC32
のオーバーフローに関する警告が表示されます。 移転。ターゲット アドレスの下位 32 ビットは正しいですが、上位ビットは正しくありません。 (したがって、あなたの call
悪いアドレスにジャンプします)。
gcc -no-pie -fno-pie call-lib.c libcall.o
でビルドすると、あなたのコードが機能します . -no-pie
重要な部分です。これはリンカー オプションです。 YASM コマンドを変更する必要はありません。
従来の位置依存の実行可能ファイルを作成する場合、リンカーは puts
を 呼び出しターゲットのシンボルを [email protected]
に 動的実行可能ファイルをリンクしているため (libc を gcc -static -fno-pie
で静的にリンクするのではなく) 、この場合は call
直接行ける libc 関数へ)
とにかく、これが gcc が call [email protected]
を発行する理由です (GAS 構文) -fpie
でコンパイルする場合 (デスクトップのデフォルトですが、https://godbolt.org/ のデフォルトではありません)、ただ call puts
-fno-pie
でコンパイルした場合 .
ここで @plt の意味を参照してください。 PLT についての詳細と、数年前の Linux の動的ライブラリの残念な状態について。 (現代の gcc -fno-plt
そのブログ投稿のアイデアの 1 つに似ています。)
ところで、より正確で具体的なプロトタイプを使用すると、gcc は foo
を呼び出す前に EAX をゼロにすることを回避できます。 :
extern void foo();
C では extern void foo(...);
を意味します
extern void foo(void);
として宣言できます 、これが ()
です C++ で意味します。 C++ では、引数を未指定のままにする関数宣言は許可されていません。
asm の改善
message
を入れることもできます section .rodata
で (読み取り専用データ、テキスト セグメントの一部としてリンクされています)。
スタック フレームは必要ありません。呼び出しの前にスタックを 16 に揃えるためのものだけです。ダミーの push rax
やります。
または、puts
をテールコールすることもできます ジャンプ この関数への入り口と同じスタック位置で、それを呼び出す代わりにそれを呼び出します。これは、PIE の有無にかかわらず機能します。 call
を置き換えるだけです jmp
で 、RSP があなた自身の返送先住所を指している限り。
PIE 実行可能ファイル (または共有ライブラリ) を作成する場合、2 つのオプションがあります
call puts wrt ..plt
- PLT を通じて明示的に呼び出します。call [rel puts wrt ..got]
- gcc の-fno-plt
のように、明示的に GOT エントリを介して間接呼び出しを行います コード生成のスタイル。 (RIP 相対アドレッシング モードを使用して GOT に到達するため、rel
キーワード)
WRT =に関して。 NASM マニュアル文書 wrt ..plt
、セクション 7.9.3:特殊記号と WRT も参照してください。
通常は default rel
を使用します ファイルの先頭にあるので、実際に call [puts wrt ..got]
を使用できます それでもRIP相対アドレッシングモードを取得します。 PIE または PIC コードでは 32 ビット絶対アドレッシング モードを使用できません。
call [puts wrt ..got]
動的リンクがGOTに格納した関数ポインタを使用して、メモリ間接呼び出しにアセンブルします。 (遅延動的リンクではなく、事前バインディングです。)
NASM ドキュメント ..got
セクション 9.2.3 で変数のアドレスを取得するため。 (他の) ライブラリの関数は同じです。オフセットはリンク時の定数ではなく、32 ビットに収まらない可能性があるため、直接呼び出すのではなく、GOT からポインターを取得します。
YASM は call [puts wrt ..GOTPCREL]
も受け入れます 、AT&T 構文のように call *[email protected](%rip)
、しかし NASM はそうではありません。
; don't use BITS 64. You *want* an error if you try to assemble this into a 32-bit .o
default rel ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional
section .rodata ; .rodata is best for constants, not .data
message:
db 'foo() called', 0
section .text
global foo
foo:
sub rsp, 8 ; align the stack by 16
; PIE with PLT
lea rdi, [rel message] ; needed for PIE
call puts WRT ..plt ; tailcall puts
;or
; PIE with -fno-plt style code, skips the PLT indirection
lea rdi, [rel message]
call [rel puts wrt ..got]
;or
; non-PIE
mov edi, message ; more efficient, but only works in non-PIE / non-PIC
call puts ; linker will rewrite it into call [email protected]
add rsp,8 ; remove the padding
ret
位置に依存する mov edi, message
を使用できます RIP 相対 LEA の代わりに。コードサイズが小さく、ほとんどの CPU でより多くの実行ポートで実行できます。
非 PIE 実行可能ファイルでは、 call puts
も使用できます。 または jmp puts
より効率的な no-plt スタイルの動的リンクが必要でない限り、リンカにそれを整理させます。しかし、libc を静的にリンクすることを選択した場合、これが libc 関数への直接の jmp を取得する唯一の方法だと思います。
(非 PIE の静的リンクの可能性は理由だと思います ld
非 PIE 用に PLT スタブを自動的に生成する意思がありますが、PIE または共有ライブラリ用には生成しません。 ELF 共有オブジェクトをリンクするときに何を意味するかを言う必要があります。)
call puts
を使用した場合 PIE (call rel32
)、位置に依存しない puts
の実装を静的にリンクした場合にのみ機能します。 つまり、実行時に (通常のダイナミック リンカー メカニズムによって) ランダムなアドレスにロードされる 1 つの実行可能ファイルでしたが、単純に libc.so.6
への依存関係はありませんでした。
0xe8
オペコードの後には、分岐先を計算するために PC (その時点までに次の命令に進んでいる) に適用される符号付きオフセットが続きます。したがって、objdump
分岐先を 0x671
と解釈しています .
YASM は、そのオフセットに再配置を設定した可能性が高いため、ゼロをレンダリングしています。これは、ローダーに puts
の正しいオフセットを入力するように要求する方法です。 ロード中。再配置の計算中にローダーでオーバーフローが発生しました。これは、puts
を示している可能性があります。 は、32 ビットの符号付きオフセットで表現できるよりも、呼び出しからさらにオフセットされています。したがって、ローダーはこの命令を修正できず、クラッシュが発生します。
66c: e8 00 00 00 00
未入力のアドレスを示します。再配置テーブルを見ると、0x66d
に再配置が表示されます。 .アセンブラがアドレス/オフセットに再配置をすべてゼロとして設定することは珍しくありません。
このページは、YASM に WRT
があることを示唆しています .got
の使用を制御できるディレクティブ 、 .plt
など
NASM ドキュメントの S9.2.5 によると、 CALL puts WRT ..plt
を使用できるようです (YASM の構文が同じであると仮定します)。