レジスターの完全な表とドキュメントからの使用方法は次のとおりです [PDF リンク]:
r12
、 r13
、 r14
、 r15
、 rbx
、 rsp
、 rbp
呼び出し先保存レジスタです - 「関数呼び出し間で保持」列に「はい」があります。
実験的アプローチ:GCC コードの逆アセンブル
主に楽しみのためですが、ABI を正しく理解していることを簡単に確認するためでもあります。
インライン アセンブリを使用してすべてのレジスタを上書きして、GCC にそれらの保存と復元を強制してみましょう:
main.c
#include <inttypes.h>
uint64_t inc(uint64_t i) {
__asm__ __volatile__(
""
: "+m" (i)
:
: "rax",
"rbx",
"rcx",
"rdx",
"rsi",
"rdi",
"rbp",
"rsp",
"r8",
"r9",
"r10",
"r11",
"r12",
"r13",
"r14",
"r15",
"ymm0",
"ymm1",
"ymm2",
"ymm3",
"ymm4",
"ymm5",
"ymm6",
"ymm7",
"ymm8",
"ymm9",
"ymm10",
"ymm11",
"ymm12",
"ymm13",
"ymm14",
"ymm15"
);
return i + 1;
}
int main(int argc, char **argv) {
(void)argv;
return inc(argc);
}
GitHub アップストリーム。
コンパイルと逆アセンブル:
gcc -std=gnu99 -O3 -ggdb3 -Wall -Wextra -pedantic -o main.out main.c
objdump -d main.out
分解には以下が含まれます:
00000000000011a0 <inc>:
11a0: 55 push %rbp
11a1: 48 89 e5 mov %rsp,%rbp
11a4: 41 57 push %r15
11a6: 41 56 push %r14
11a8: 41 55 push %r13
11aa: 41 54 push %r12
11ac: 53 push %rbx
11ad: 48 83 ec 08 sub $0x8,%rsp
11b1: 48 89 7d d0 mov %rdi,-0x30(%rbp)
11b5: 48 8b 45 d0 mov -0x30(%rbp),%rax
11b9: 48 8d 65 d8 lea -0x28(%rbp),%rsp
11bd: 5b pop %rbx
11be: 41 5c pop %r12
11c0: 48 83 c0 01 add $0x1,%rax
11c4: 41 5d pop %r13
11c6: 41 5e pop %r14
11c8: 41 5f pop %r15
11ca: 5d pop %rbp
11cb: c3 retq
11cc: 0f 1f 40 00 nopl 0x0(%rax)
そのため、以下がプッシュおよびポップされていることが明確にわかります:
rbx
r12
r13
r14
r15
rbp
仕様から唯一欠けているのは rsp
です 、しかしもちろんスタックが復元されることを期待しています。アセンブリを注意深く読むと、この場合にアセンブリが維持されていることが確認されます:
sub $0x8, %rsp
:%rdi
を保存するためにスタックに 8 バイトを割り当てます%rdi, -0x30(%rbp)
で 、インライン アセンブリ+m
に対して行われます 制約lea -0x28(%rbp), %rsp
%rsp
を復元しますsub
の前に戻る 、つまりmov %rsp, %rbp
の後に 5 ポップ- 6 つのプッシュと 6 つの対応するポップがあります
- 他の命令は
%rsp
に触れていません
Ubuntu 18.10、GCC 8.2.0 でテスト済み。