i386およびx86-64でのUNIXおよびLinuxシステム呼び出しの呼び出し規則は何ですか


147

以下のリンクは、UNIX(BSDフレーバー)とLinuxの両方のx86-32システムコール規則を説明しています。

しかし、UNIXとLinuxの両方でのx86-64システムコール規則は何ですか?


Unixの呼び出し規約には「標準」はありません。Linuxの場合は確かですが、Solaris、OpenBSD、Linux、Minixの呼び出し規約は少なくともわずかに異なり、すべてUNIXであると思います。
Earlz 2010年

2
それは完全に真実ではありません-ほとんどのマシンタイプで利用可能なUNIX ABIのセットがあり、Cコンパイラが相互運用性を実現できるようにします。C ++コンパイラにはさらに大きな問題があります。
ジョナサンレフラー

1
どちらも正しいです。FreeBSDとLinuxを探しています。

回答に、システムコール間で保持されるレジスタに関する情報が含まれていれば幸いです。もちろん、スタックポインタは(__NR_clone呼び出しで制御された方法で変更されない限り)ですが、他のものはありますか?
Albert van der Horst

@AlbertvanderHorst:はい、32ビットの詳細でWikiの回答を更新しました。64ビットはすでに正確でした。rcxとr11はsysret、raxが戻り値に置き換えられるとともに、動作方法により破棄されます。他のすべてのレジスタはamd64で保持されます。
Peter Cordes、2016

回答:


230

ここのトピックのさらなる読み物:Linuxシステムコールの決定的なガイド


LinuxでGNU Assembler(gas)を使用してこれらを検証しました。

カーネルインターフェイス

x86-32別名i386 Linuxシステムコール規約:

x86-32では、Linuxシステムコールのパラメーターはレジスターを使用して渡されます。%eaxsyscall_numberの場合。%ebx、%ecx、%edx、%esi、%edi、%ebpは、6つのパラメーターをシステムコールに渡すために使用されます。

戻り値はです%eax。他のすべてのレジスタ(EFLAGSを含む)は全体で保持されint $0x80ます。

Linuxアセンブリチュートリアルから次のスニペットを取得しましたが、これについて疑問があります。誰かが例を示すことができれば、それは素晴らしいでしょう。

引数が6つ以上ある場合は %ebx、引数のリストが格納されるメモリ位置を含める必要があります。ただし、引数が6つ以上あるシステムコールを使用することはほとんどないため、これについては心配しないでください。

例ともう少し読むには、http://www.int80h.org/bsdasm/#alternate-calling-conventionを参照してください。i386 Linuxを使用したHello Worldの別の例int 0x80Hello、アセンブリ言語でのLinuxシステムコールを使用したHello World

32ビットシステムコールを行うためのより高速な方法がありsysenterます。カーネルは、メモリのページをすべてのプロセス(vDSO)にマップします。sysenterダンスのユーザー空間側は、戻りアドレスを見つけるためにカーネルと連携する必要があります。Arg to registerマッピングはと同じですint $0x80。通常はsysenter直接使用するのではなく、vDSOを呼び出す必要があります。(vDSOへのリンクと呼び出しに関する情報、および、およびシステムコールで実行するその他のすべての詳細については、Linuxシステムコールの確定ガイドを参照してくださいsysenter。)

x86-32 [Free | Open | Net | DragonFly] BSD UNIXシステムコール規約:

パラメータはスタックに渡されます。パラメータ(最後に最初にプッシュされたパラメータ)をスタックにプッシュします。次に、追加の32ビットのダミーデータをプッシュします(実際にはダミーデータではありません。詳細については、次のリンクを参照してください)。その後、システムコールの指示を出します。int $0x80

http://www.int80h.org/bsdasm/#default-calling-convention


x86-64 Linuxシステムコール規則:

x86-64 Mac OS Xは似ていますが異なります。TODO:* BSDの機能を確認してください。

System V Application Binary Interface AMD64 Architecture Processor Supplementの「A.2 AMD64 Linuxカーネル規約」のセクションを参照してください。i386およびx86-64 System V psABIの最新バージョンは、このページからリンクされたABIメンテナのリポジトリにあります。(も参照してください タグ付けされたwikiに最新のABIリンクと、x86 asmに関する他の多くの優れた機能があります。)

このセクションのスニペットは次のとおりです。

  1. ユーザーレベルのアプリケーションは、シーケンス%rdi、%rsi、%rdx、%rcx、%r8および%r9を渡すための整数レジスタとして使用します。カーネルインターフェイスは、%rdi、%rsi、%rdx、%r10、%r8、および%r9を使用します。
  2. システムコールは、syscall命令を介して行われます。このクローバーは%rcxと%r11、および%raxの戻り値を破棄しますが、他のレジスターは保持されます。
  3. syscallの数は、レジスタ%raxで渡す必要があります。
  4. システムコールは6つの引数に制限されており、引数はスタックに直接渡されません。
  5. syscallから戻ると、レジスタ%raxにはシステムコールの結果が含まれています。-4095から-1までの範囲の値はエラーを示します-errno
  6. クラスINTEGERまたはクラスMEMORYの値のみがカーネルに渡されます。

これはLinux固有の付録からABIへの引用であることを覚えておいてください。Linuxの場合でも、情報提供は規範的ではありません。(しかし、実際には正確です。)

この32ビットint $0x80ABI 64ビットコードで使用できます(ただし、強くはお勧めしません)。 32ビットint 0x80 Linux ABIを64ビットコードで使用するとどうなりますか? それでも入力は32ビットに切り捨てられるため、ポインターには適しておらず、r8〜r11はゼロになります。

ユーザーインターフェイス:関数呼び出し

x86-32関数呼び出し規約:

x86-32では、パラメーターがスタックに渡されました。最後のパラメーターは、すべてのパラメーターが完了するまでスタックに最初にプッシュされ、その後、call命令が実行されました。これは、LinuxでアセンブリからCライブラリ(libc)関数を呼び出すために使用されます。

最新バージョンのi386 System V ABI(Linuxで使用)では、x86-64 System V ABIが常に必要であったように、の%esp前に16バイトのアライメントがcall必要です。呼び出し先は、非整列でフォールトするSSE 16バイトのロード/ストアを想定して使用できます。しかし、歴史的には、Linuxは4バイトのスタックアライメントしか必要としないため、8バイトdoubleなどでも、自然にアライメントされたスペースを予約するために余分な作業が必要でした。

他の一部の最新の32ビットシステムでは、4バイトを超えるスタックアライメントを必要としません。


x86-64 System Vユーザー空間関数呼び出し規約:

x86-64 System Vは、argsをレジスターに渡します。これは、i386 System Vのスタック引数規則よりも効率的です。これは、レイテンシーと、argsをメモリ(キャッシュ)に保存し、呼び出し先に再度ロードする追加の指示を回避します。これは、より多くのレジスターが利用可能であり、レイテンシと順序外の実行が問題となる最新の高性能CPUに適しているため、うまく機能します。(i386 ABIは非常に古いものです)。

この新しいメカニズムでは:まず、パラメーターがクラスに分割されます。各パラメーターのクラスは、呼び出された関数に渡される方法を決定します。

詳細については、「System VアプリケーションバイナリインターフェイスAMD64アーキテクチャプロセッササプリメント」の「3.2関数呼び出しシーケンス」を参照してください。

引数が分類されると、レジスターは(左から右に)割り当てられ、次のように渡されます。

  1. クラスがMEMORYの場合、引数をスタックに渡します。
  2. クラスがINTEGERの場合、シーケンス%rdi、%rsi、%rdx、%rcx、%r8および%r9の次に使用可能なレジスタが使用されます

そう%rdi, %rsi, %rdx, %rcx, %r8 and %r9登録されているためには、アセンブリから任意のlibc関数の整数/ポインタ(すなわち、INTEGERクラス)パラメータを渡すために使用されます。%rdiは最初のINTEGERパラメータに使用されます。2番目の%rsi、3番目の%rdxなど。次に、call指示を与える必要があります。スタック(%rsp)は、call実行時に16Bに整列する必要があります。

INTEGERパラメーターが6つを超える場合、7番目以降のINTEGERパラメーターがスタックに渡されます。(呼び出し元のポップ、x86-32と同じ)

最初の8つの浮動小数点引数は、後でスタックで%xmm0-7に渡されます。コール保存のベクトルレジスタはありません。(FP引数と整数引数が混在する関数は、合計8つを超えるレジスタ引数を持つことができます。)

可変個関数(などprintf)には、常に%al= FPレジスタ引数の数が必要です。

構造体をレジスタにパックするタイミング(rdx:rax復帰時)とメモリ内のルールがあります。詳細についてはABIを参照し、コンパイラーの出力をチェックして、コードがコンパイラーと一致することを確認してください。


注意呼び出し規約のWindows x64の機能はシャドウ空間と同様に、x86-64のシステムVから複数の有意差を持っている必要があります(代わりに赤ゾーンの)呼び出し側によって予約され、コール保存xmm6-XMM15。そして、どのargがどのレジスタに入るかについての非常に異なるルール。


1
Linux 32では、「ax bx cd dx si di bpを除くすべてのレジスタが保持されます」。私は何も考えられません...
アルバートファンデルホルスト

amd64では、6つを超えるパラメーターがあり、それらがスタックで渡される場合、呼び出し、呼び出し元、または呼び出し先の後にスタックをクリーンアップする責任は誰にありますか?
ニコラス2016年

1
@Nicolás:呼び出し元がスタックをクリーンアップします。関数呼び出し規約の詳細を追加して、回答を更新しました。
Peter Cordes 2017

1
Linuxのint 0x80ABIを64ビットコードで使用する場合、次のようになります:stackoverflow.com/questions/46087730/…。r8-r11をゼロにし、32ビットプロセスで実行する場合とまったく同じように機能します。そのQ&Aには、それが機能するか、ポインタの切り捨てで失敗する例があります。また、カーネルソースを掘り下げて、なぜそのように動作するのかを示しました。
Peter Cordes 2017

1
@EvanCarroll:スニペット(引用されたテキストは)与えられたリンクであるLinuxの組立チュートリアルセクションで、特に4.3のLinuxシステムコール
マイケル・ペッチ

14

おそらく、あなたはx86_64 ABIを探していますか?

それが正確ではない場合は、優先検索エンジンで「x86_64 abi」を使用して、代替参照を検索してください。


5
実際には、システムコール規則だけが必要です。esp for UNIX(FreeBSD)

3
@claws:システムコール規約はABIの一部です。
ジョナサンレフラー

1
ええ。私は、個々のOSのカーネル開発ircに行って、それらについて尋ねました。彼らは私に情報源を調べて理解するように言った。文書化せずにどうやって開発を開始できるのか理解できません。それで、私が収集した情報からの回答を追加しました。他の人が残りの詳細を記入してくれることを期待しています。

@JonathanLefflerリンクは現在機能していないようです。リンクにアクセスしても問題が発生する場合は、更新していただけますか?
Ajay Brahmakshatriya 2018年

@AjayBrahmakshatriya:頭を上げてくれてありがとう。Wayback Machineレコードへのリンクを追加しました。x86-64.org Webサイト全体がデータで応答しませんでした。
ジョナサンレフラー

11

呼び出し規約は、他のプログラムを呼び出すとき、または他のプログラムから呼び出されているときに、パラメーターがレジスターで渡される方法を定義します。そして、これらの慣習の最良の情報源は、これらのハードウェアごとに定義されたABI標準の形式です。コンパイルを簡単にするために、同じABIがユーザー空間とカーネルプログラムでも使用されます。Linux / Freebsdは、x86-64用の同じABIと32ビット用の別のセットに従います。ただし、x86-64 ABI for WindowsはLinux / FreeBSDとは異なります。そして一般的に、ABIはシステムコールと通常の「関数呼び出し」を区別しません。つまり、これはx86_64呼び出し規約の特定の例であり、Linuxユーザースペースとカーネルの両方で同じです:http : //eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 /(パラメータのシーケンスa、b、c、d、e、fに注意してください):

呼び出し規約とレジスター使用法の適切なレンダリング

パフォーマンスは、これらのABIの理由の1つです(たとえば、メモリスタックに保存するのではなく、レジスタを介してパラメーターを渡す)。

ARMにはさまざまなABIがあります。

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html

https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/iPhoneOSABIReference.pdf

ARM64規約:

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf

Linux on PowerPCの場合:

http://refspecs.freestandards.org/elf/elfspec_ppc.pdf

http://www.0x04.net/doc/elf/psABI-ppc64.pdf

組み込み用にはPPC EABIがあります。

http://www.freescale.com/files/32bit/doc/app_note/PPCEABI.pdf

このドキュメントは、さまざまな規則すべての概要です。

http://www.agner.org/optimize/calling_conventions.pdf


全く別のポイント。質問の投稿者は、一般的なABI変換と同じである場合、Linuxでの64ビットsyscall呼び出し規約を要求しません。
Albert van der Horst

6

Linuxカーネル5.0ソースコメント

私はx86の詳細が下arch/x86にあり、syscallのものが下にあることを知っていましたarch/x86/entry。したがって、git grep rdiそのディレクトリをざっと見るとarch / x86 / entry / entry_64.Sに移動します。

/*
 * 64-bit SYSCALL instruction entry. Up to 6 arguments in registers.
 *
 * This is the only entry point used for 64-bit system calls.  The
 * hardware interface is reasonably well designed and the register to
 * argument mapping Linux uses fits well with the registers that are
 * available when SYSCALL is used.
 *
 * SYSCALL instructions can be found inlined in libc implementations as
 * well as some other programs and libraries.  There are also a handful
 * of SYSCALL instructions in the vDSO used, for example, as a
 * clock_gettimeofday fallback.
 *
 * 64-bit SYSCALL saves rip to rcx, clears rflags.RF, then saves rflags to r11,
 * then loads new ss, cs, and rip from previously programmed MSRs.
 * rflags gets masked by a value from another MSR (so CLD and CLAC
 * are not needed). SYSCALL does not save anything on the stack
 * and does not change rsp.
 *
 * Registers on entry:
 * rax  system call number
 * rcx  return address
 * r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
 * rdi  arg0
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 (needs to be moved to rcx to conform to C ABI)
 * r8   arg4
 * r9   arg5
 * (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
 *
 * Only called from user space.
 *
 * When user can change pt_regs->foo always force IRET. That is because
 * it deals with uncanonical addresses better. SYSRET has trouble
 * with them due to bugs in both AMD and Intel CPUs.
 */

32ビットの場合は、arch / x86 / entry / entry_32.Sにあります。

/*
 * 32-bit SYSENTER entry.
 *
 * 32-bit system calls through the vDSO's __kernel_vsyscall enter here
 * if X86_FEATURE_SEP is available.  This is the preferred system call
 * entry on 32-bit systems.
 *
 * The SYSENTER instruction, in principle, should *only* occur in the
 * vDSO.  In practice, a small number of Android devices were shipped
 * with a copy of Bionic that inlined a SYSENTER instruction.  This
 * never happened in any of Google's Bionic versions -- it only happened
 * in a narrow range of Intel-provided versions.
 *
 * SYSENTER loads SS, ESP, CS, and EIP from previously programmed MSRs.
 * IF and VM in RFLAGS are cleared (IOW: interrupts are off).
 * SYSENTER does not save anything on the stack,
 * and does not save old EIP (!!!), ESP, or EFLAGS.
 *
 * To avoid losing track of EFLAGS.VM (and thus potentially corrupting
 * user and/or vm86 state), we explicitly disable the SYSENTER
 * instruction in vm86 mode by reprogramming the MSRs.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  user stack
 * 0(%ebp) arg6
 */

glibc 2.29 Linux x86_64システムコールの実装

次に、主要なlibc実装を調べて、それらが何をしているかを見てみましょう。

この回答を書いているときに、現在使用しているglibcを調べるよりも優れている点は何ですか?:-)

glibc 2.29はx86_64のsyscallsを定義しsysdeps/unix/sysv/linux/x86_64/sysdep.h、それにはいくつかの興味深いコードが含まれています。例:

/* The Linux/x86-64 kernel expects the system call parameters in
   registers according to the following table:

    syscall number  rax
    arg 1       rdi
    arg 2       rsi
    arg 3       rdx
    arg 4       r10
    arg 5       r8
    arg 6       r9

    The Linux kernel uses and destroys internally these registers:
    return address from
    syscall     rcx
    eflags from syscall r11

    Normal function call, including calls to the system call stub
    functions in the libc, get the first six parameters passed in
    registers and the seventh parameter and later on the stack.  The
    register use is as follows:

     system call number in the DO_CALL macro
     arg 1      rdi
     arg 2      rsi
     arg 3      rdx
     arg 4      rcx
     arg 5      r8
     arg 6      r9

    We have to take care that the stack is aligned to 16 bytes.  When
    called the stack is not aligned since the return address has just
    been pushed.


    Syscalls of more than 6 arguments are not supported.  */

そして:

/* Registers clobbered by syscall.  */
# define REGISTERS_CLOBBERED_BY_SYSCALL "cc", "r11", "cx"

#undef internal_syscall6
#define internal_syscall6(number, err, arg1, arg2, arg3, arg4, arg5, arg6) \
({                                  \
    unsigned long int resultvar;                    \
    TYPEFY (arg6, __arg6) = ARGIFY (arg6);              \
    TYPEFY (arg5, __arg5) = ARGIFY (arg5);              \
    TYPEFY (arg4, __arg4) = ARGIFY (arg4);              \
    TYPEFY (arg3, __arg3) = ARGIFY (arg3);              \
    TYPEFY (arg2, __arg2) = ARGIFY (arg2);              \
    TYPEFY (arg1, __arg1) = ARGIFY (arg1);              \
    register TYPEFY (arg6, _a6) asm ("r9") = __arg6;            \
    register TYPEFY (arg5, _a5) asm ("r8") = __arg5;            \
    register TYPEFY (arg4, _a4) asm ("r10") = __arg4;           \
    register TYPEFY (arg3, _a3) asm ("rdx") = __arg3;           \
    register TYPEFY (arg2, _a2) asm ("rsi") = __arg2;           \
    register TYPEFY (arg1, _a1) asm ("rdi") = __arg1;           \
    asm volatile (                          \
    "syscall\n\t"                           \
    : "=a" (resultvar)                          \
    : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4),     \
      "r" (_a5), "r" (_a6)                      \
    : "memory", REGISTERS_CLOBBERED_BY_SYSCALL);            \
    (long int) resultvar;                       \
})

私はかなり自明だと思います。これが通常のSystem V AMD64 ABI関数の呼び出し規約に正確に一致するように設計されているように見えることに注意してください:https : //en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

盗賊の簡単な注意:

  • ccフラグレジスタを意味します。しかしピーター・コーデスは、これはここでは不要であるとコメントしています。
  • memory ポインタがアセンブリで渡され、メモリへのアクセスに使用される可能性があることを意味します

ゼロからの明示的な最小実行可能例については、この回答を参照してください:インラインアセンブリでsysenterを介してシステムコールを呼び出す方法?

アセンブリでいくつかのシステムコールを手動で作成する

あまり科学的ではありませんが、楽しいです:

  • x86_64.S

    .text
    .global _start
    _start:
    asm_main_after_prologue:
        /* write */
        mov $1, %rax    /* syscall number */
        mov $1, %rdi    /* stdout */
        mov $msg, %rsi  /* buffer */
        mov $len, %rdx  /* len */
        syscall
    
        /* exit */
        mov $60, %rax   /* syscall number */
        mov $0, %rdi    /* exit status */
        syscall
    msg:
        .ascii "hello\n"
    len = . - msg
    

    GitHubアップストリーム

aarch64

私が最小限の実行可能なユーザランドの例を示しました:/reverseengineering/16917/arm64-syscalls-table/18834#18834ここにTODO grepのカーネルコード、簡単にする必要があります。


1
"cc"クロバーは不要です:LinuxのシステムコールはRFLAGSを保存/復元(syscall/ sysret命令がR11を使用して、カーネルが保存されたR11を変更しません/経由以外のRFLAGSことをやるptraceので、それは今までに重要ということはないデバッガシステムコール。)"cc"クロバーがありますGNU C Extended asmのx86 / x86-64では暗黙的であるため、除外しないと何も得られません。
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.