1. ホーム
  2. linux

[解決済み] GNUアセンブラを使ってx86_64でprintfを呼び出す

2022-02-02 23:17:28

質問

GNUアセンブラで使用するために、AT&T構文でプログラムを書きました。

            .data
format:   .ascii "%d\n"  

            .text
            .global main  
main:
            mov    $format, %rbx
            mov    (%rbx), %rdi
            mov    $1, %rsi
            call     printf
            ret

私は GCC でアセンブルとリンクを行います。

gcc -o main main.s

このコマンドで実行しています。

./main

プログラムを実行するとセグメンテーションフォールトが発生します。gdbを使うと、次のように表示されます。 printf が見つかりません。.extern printf"を試しましたが、うまくいきません。ある人が、以下を呼び出す前にスタックポインタを保存するべきだと言いました。 printf の前にリストアし RET どうすればいいのでしょうか?

どのように解決するのですか?

このコードには、いくつかの問題があります。その AMD64 システム V ABI Linuxで使用されている呼び出し規約は、いくつかのことを要求しています。それは CALL スタックが少なくとも16バイト(または32バイト)アラインであること。

入力引数領域の終端は、16(_m256の場合は32)にアライメントされていなければならない。 バイトのバウンダリで渡される。

の後に C ランタイムは main によってリターンポインタがスタック上に配置されたため、スタックの位置が8つずれています。 CALL . 16 バイトの境界線に再調整するためには、単純に PUSH 任意の 汎用レジスタをスタック上に配置し POP を最後に削除します。

また、呼び出し規約では AL には、可変長引数関数に使用されるベクターレジスタの数が含まれます。

al は、可変数の引数を必要とする関数に渡されるベクトル引数の数を示すために使用されます。

printf は可変引数関数なので AL を設定する必要があります。この場合、ベクターレジスターでパラメータを渡さないので AL を0に設定します。

また、$formatポインタがすでにアドレスであるにもかかわらず、そのポインタを再参照しています。だからこれは間違っている。

mov  $format, %rbx
mov  (%rbx), %rdi 

これは、formatのアドレスを取り、それを RBX . そして、そのアドレスの8バイトを RBX に配置し RDI . RDI である必要があります。 ポインタ を、文字そのものではなく、文字列に変換します。この2行は、次のように置き換えることができる。

lea  format(%rip), %rdi

これはRIP Relative Addressingを使用しています。

また NUL は文字列を終了させます。を使うよりも、むしろ .ascii を使用することができます。 .asciz をx86プラットフォームで使用することができます。

あなたのプログラムの動作バージョンは次のようになります。

# global data  #
    .data
format: .asciz "%d\n"
.text
    .global main
main:
  push %rbx
  lea  format(%rip), %rdi
  mov  $1, %esi           # Writing to ESI zero extends to RSI.
  xor %eax, %eax          # Zeroing EAX is efficient way to clear AL.
  call printf
  pop %rbx
  ret


その他の推奨事項/提案

また、64ビット版LinuxのABIでは、呼び出しの規約で、特定のレジスタの保存を尊重するよう記述された関数があることも知っておく必要があります。レジスタの一覧と、それらが保存されるべきかどうかは、以下の通りです。

というレジスタは Yes の中に をまたいで保存される 関数呼び出し の列は、関数全体で保存されていることを確認する必要があります。関数 main は、他の C 関数を使用します。


読み取り専用であることが分かっている文字列やデータについては .rodata セクションで .section .rodata よりも .data


64ビットモードの場合:32ビットレジスタである目的オペランドがある場合、CPUは64ビットレジスタ全体にわたってレジスタをゼロ拡張します。これにより、命令エンコーディングのバイト数を節約することができます。


実行ファイルが位置独立コードとしてコンパイルされている可能性があります。以下のようなエラーが表示されることがあります。

<ブロッククオート

再配置 R_X86_64_PC32 シンボル `printf@@GLIBC_2.2.5' に対して、共有オブジェクトを作成するときに使用することができません; -fPIC で再コンパイルしてください。

これを解決するには、外部関数 printf このように

call printf@plt 

を経由して外部ライブラリ関数を呼び出します。 プロシージャ・リンケージ・テーブル(PLT)