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
次のコマンドで実行します。
。/メイン
プログラムを実行すると、セグメンテーション違反が発生します。 gdbを使用すると、printf
not foundと表示されます。動作しない「.extern printf」を試しました。誰かがprintf
を呼び出す前にスタックポインタを保存し、[〜#〜] ret [〜#〜]の前に復元することを提案しましたが、どうすればよいですか?
このコードにはいくつかの問題があります。 Linuxで使用される AMD64 System V ABI 呼び出し規約には、いくつかの要件があります。 [〜#〜] call [〜#〜] の直前に、スタックが少なくとも16バイト(または32バイト)整列されている必要があります。
入力引数領域の終わりは、16(__m256がスタックに渡された場合は32)バイト境界に揃えられます。
[〜#〜] c [〜#〜] ランタイムがmain
関数を呼び出した後、リターンポインタが [〜#〜] call [〜#〜] 。 16バイト境界に再調整するには、単に Push any汎用レジスターをスタックにスタックして [〜#〜] pop [〜#〜] 最後にオフにします。
呼び出し規約では、 [〜#〜] al [〜#〜] に、可変引数関数に使用されるベクトルレジスタの数が含まれている必要があります。
%alは、可変数の引数を必要とする関数に渡されるベクトル引数の数を示すために使用されます
printf
は可変引数関数なので、 [〜#〜] al [〜#〜] を設定する必要があります。この場合、ベクトルレジスタにパラメータを渡さないため、 [〜#〜] al [〜#〜] を0に設定できます。
$ formatポインターが既にアドレスである場合も、それを逆参照します。だからこれは間違っています:
mov $format, %rbx
mov (%rbx), %rdi
これは、フォーマットのアドレスを受け取り、それを [〜#〜] rbx [〜#〜] に配置します。次に、 [〜#〜] rbx [〜#〜] でそのアドレスの8バイトを取得し、 [〜#〜] rdi [〜に配置します#〜] 。 [〜#〜] rdi [〜#〜] は、文字自体ではなく、文字列への pointer である必要があります。 2つの行は次のように置き換えることができます。
lea format(%rip), %rdi
これはRIP相対アドレス指定を使用します。
また、 [〜#〜] nul [〜#〜] 文字列を終了する必要があります。 .ascii
を使用する代わりに、x86プラットフォームでは.asciz
を使用できます。
プログラムの作業バージョンは次のようになります。
# 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から、呼び出し規約には特定のレジスターの保持を尊重するために作成する関数も必要であることを認識しておく必要があります。レジスターのリストとそれらを保存する必要があるかどうかは次のとおりです。
Preserved across Register 列でYes
と表示されているレジスタは、関数全体で確実に保持される必要があるレジスタです。関数main
は他の [〜#〜] c [〜#〜] 関数と同じです。
読み取り専用であることがわかっている文字列/データがある場合は、.rodata
ではなく.section .rodata
を使用して.data
セクションに配置できます。
64ビットモードの場合:32ビットレジスタであるデスティネーションオペランドがある場合、CPUはレジスタを64ビットレジスタ全体にゼロ拡張します。これにより、命令エンコーディングのバイトを節約できます。
実行可能ファイルが位置に依存しないコードとしてコンパイルされている可能性があります。次のようなエラーが表示される場合があります。
共有オブジェクトを作成するとき、シンボル `printf @@ GLIBC_2.2.5 'に対するR_X86_64_PC32の再配置は使用できません。 -fPICで再コンパイル
これを修正するには、次のように外部関数printf
を呼び出す必要があります。
call printf@plt
Procedure Linkage Table(PLT) を介して外部ライブラリ関数を呼び出します。
同等のcファイルから生成されたアセンブリコードを確認できます。
test.cでgcc -o - -S -fno-asynchronous-unwind-tables test.c
を実行しています
#include <stdio.h>
int main() {
return printf("%d\n", 1);
}
これはアセンブリコードを出力します。
.file "test.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
popq %rbp
ret
.size main, .-main
.ident "GCC: (GNU) 6.1.1 20160602"
.section .note.GNU-stack,"",@progbits
これにより、printfを呼び出して変更できるアセンブリコードのサンプルが提供されます。
コードと比較して、次の2つを変更する必要があります。
mov $format, %rdi
で実行できますmov $0, %eax
を追加する必要がありますこれらの変更を適用すると、次のようになります。
.data
format: .ascii "%d\n"
.text
.global main
main:
mov $format, %rdi
mov $1, %rsi
mov $0, %eax
call printf
ret
そして、それを実行して印刷します:
1