web-dev-qa-db-ja.com

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

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

しかし、UNIXとLinuxの両方でのx86-64システム呼び出し規則は何ですか?

127
claws

ここでのトピックの詳細については、 Linuxシステムコールの決定版ガイド


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

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

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

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

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

Linux Assembly Tutorial から次のスニペットを取りましたが、これについては疑わしいです。誰かが例を示すことができれば、それは素晴らしいことです。

6つ以上の引数がある場合、%ebxには引数のリストが保存されているメモリの場所が含まれている必要があります。

例ともう少し読むには、 http://www.int80h.org/bsdasm/#alternate-calling-convention を参照してください。 int 0x80を使用したi386 LinuxのHello Worldの別の例: このHelloWorldアセンブリコードのどの部分が、プログラムをアセンブリで記述する場合に不可欠ですか?

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

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 AMD64Linuxカーネル規約」セクションを参照してください。 i386およびx86-64 System V psABIの最新バージョンは、 ABIメンテナーのリポジトリのこのページからリンクされています にあります。 ( x86 タグwikiも参照して、最新のABIリンクや、x86 asmに関するその他の多くの優れた情報を入手してください。)

このセクションの抜粋は次のとおりです。

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

これは、ABIのLinux固有の付録からのものであり、Linuxの場合でも、参考ではなく参考になります。 (しかし、実際は正確です。)

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

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

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

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

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

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


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

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

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

詳細については、「 System V Application Binary Interface AMD64 Architecture Processor Supplement 」の「3.2関数呼び出しシーケンス」を参照してください。

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

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

したがって、%rdi, %rsi, %rdx, %rcx, %r8 and %r9は、レジスタin orderです。整数/ポインタ(つまりINTEGERクラス)パラメータをアセンブリからlibc関数に渡すために使用されます。 %rdiは、最初のINTEGERパラメーターに使用されます。 2番目は%rsi、3番目は%rdxなどです。次に、call命令を指定する必要があります。 callを実行するとき、スタック(%rsp)は16Bに揃える必要があります。

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

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

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

構造体をレジスタ(rdx:rax戻り時)とメモリ内のどちらにパックするかについてのルールがあります。詳細についてはABIを参照し、コンパイラの出力をチェックして、コードがコンパイラとどのように渡されるか/返されるかについて合意していることを確認してください。


Windows x64関数呼び出し規約 にはx86-64 System Vと複数の重要な違いがあることに注意してください。たとえば、シャドウスペースは、呼び出し元によってmustが予約されます(レッドゾーンの代わりに)、コール保存されたxmm6-xmm15。そして、どの引数がどのレジスタに入るかについての非常に異なるルール。

202
claws

おそらく、x86_64 ABIをお探しですか?

それが正確にあなたが望んでいるものではない場合、代替検索を見つけるためにあなたの好みの検索エンジンで「x86_64 abi」を使用してください。

15

呼び出し規約は、他のプログラムを呼び出したり呼び出したりするときに、レジスターでパラメーターを渡す方法を定義します。そして、これらの規約の最良のソースは、これらのハードウェアごとに定義された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に注意してください):

A good rendering of calling conventions vs registers usage

パフォーマンスは、これらの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

11
Peter Teoh

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はフラグレジスタを意味します。しかし Peter Cordesのコメント これはここでは不要です。
  • memoryは、ポインタがアセンブリに渡され、メモリへのアクセスに使用されることを意味します

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

アセンブリ内のいくつかのsyscallを手動で作成します

それほど科学的ではないが、楽しい:

  • 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

最小限の実行可能なユーザーランドの例を以下に示しました: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grepカーネルコードは、簡単なはずです。