GNU/Linux >> Linux の 問題 >  >> Linux

アセンブリ (yasm) コードから 64 ビット Linux で C 標準ライブラリ関数を呼び出せない

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 の構文が同じであると仮定します)。


Linux
  1. C++ で C 関数を呼び出す方法、C で C++ 関数を呼び出す方法 (C と C++ の混合)

  2. LinuxでPythonからWine dllを呼び出す方法は?

  3. exit() がプロセスの終了に失敗することはありますか?

  1. LinuxからEXEバージョンを読み取るCライブラリ?

  2. Linux カーネル モジュール内からユーザー空間関数を呼び出す

  3. C++ コードから C 関数を呼び出す

  1. 共有ライブラリ (.so) は、ローディング プログラムに実装されている関数をどのように呼び出すことができますか?

  2. Linux で関数ブロックからファイルに出力をリダイレクトする

  3. Linuxからジェンキンスを完全に削除するにはどうすればよいですか