i386 및 x86-64에서 UNIX 및 Linux 시스템 호출에 대한 호출 규칙은 무엇입니까


147

다음 링크는 UNIX (BSD 플레이버) 및 Linux 모두에 대한 x86-32 시스템 호출 규칙을 설명합니다.

그러나 UNIX와 Linux 모두에서 x86-64 시스템 호출 규칙은 무엇입니까?


유닉스 호출 규칙에는 "표준"이 없습니다. 리눅스의 경우 확실하지만 Solaris, OpenBSD, Linux 및 Minix는 적어도 약간 다른 호출 규칙을 가지고 있으며 모두 유닉스임을 확신합니다.
Earlz

2
이는 사실이 아닙니다. 대부분의 머신 유형에 사용할 수있는 UNIX ABI 세트가 있으므로 C 컴파일러가 상호 운용성을 달성 할 수 있습니다. C ++ 컴파일러에는 더 큰 문제가 있습니다.
Jonathan Leffler

1
둘 다 맞습니다. FreeBSD & Linux를 찾고 있습니다.
발톱

답변에 시스템 호출에 대해 유지되는 레지스터에 대한 정보가 포함되어 있다면 감사하겠습니다. 물론 스택 포인터는 (__NR_clone 호출에서 제어 된 방식으로 변경되지 않는 한) 다른 것입니까?
Albert van der Horst

@ AlbertvanderHorst : 예, 방금 위키 답변을 32 비트에 대한 세부 정보로 업데이트했습니다. 64 비트는 이미 정확했습니다 : rcx와 r11은 sysret작동 방식으로 인해 파괴 되고 rax는 반환 값으로 대체됩니다. 다른 모든 레지스터는 amd64에 유지됩니다.
Peter Cordes

답변:


230

여기에있는 주제에 대한 추가 정보 : 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 Assembly Tutorial 에서 다음 스 니펫을 가져 갔지만 이것에 대해서는 의문입니다. 어느 누구도 예를 보여줄 수 있다면 좋을 것입니다.

인수가 6 개 이상인 경우 인수 %ebx목록이 저장된 메모리 위치를 포함해야합니다. 그러나 인수가 6 개 이상인 syscall을 사용할 가능성이 없으므로 걱정하지 마십시오.

예제와 약간의 추가 정보는 http://www.int80h.org/bsdasm/#alternate-calling-convention참조하십시오 . 다음을 사용하는 i386 Linux 용 Hello World의 또 다른 예 int 0x80: Linux 시스템 호출을 사용하는 어셈블리 언어의 세계?

32 비트 시스템 호출을하는 더 빠른 방법이 있습니다 : using sysenter. 커널은 메모리의 페이지를 모든 프로세스 (vDSO)에 매핑합니다. sysenter댄스 의 사용자 공간 쪽에서 는 커널과 협력하여 반환 주소를 찾을 수 있습니다. 레지스터 맵핑에 대한 Arg는와 동일합니다 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 응용 프로그램 바이너리 인터페이스 AMD64 아키텍처 프로세서 부록의 "A.2 AMD64 Linux 커널 규칙" 섹션을 참조하십시오 . 최신 버전의 i386 및 x86-64 시스템 V psABI는 이 페이지에서 ABI 관리자의 저장소에 링크되어 있습니다 . (또한 참조 x86 asm에 대한 최신 ABI 링크 및 기타 유용한 정보를 제공하는 태그 위키

이 섹션의 스 니펫은 다음과 같습니다.

  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. syscall에서 복귀하면 레지스터 % rax에 시스템 호출 결과가 포함됩니다. -4095와 -1 사이의 값은 오류를 나타냅니다 -errno.
  6. INTEGER 클래스 또는 MEMORY 클래스의 값만 커널로 전달됩니다.

이것은 Linux 관련 부록에서 ABI에 대한 것임을 기억하십시오. 심지어 Linux의 경우 표준이 아닙니다. (그러나 실제로는 정확합니다.)

이 32 비트 int $0x80ABI 64 비트 코드에서 사용할 수 있지만 권장되지는 않습니다. 64 비트 코드에서 32 비트 int 0x80 Linux ABI를 사용하면 어떻게됩니까? 여전히 입력을 32 비트로 자르므로 포인터에 적합하지 않으며 r8-r11은 0입니다.

사용자 인터페이스 : 함수 호출

x86-32 함수 호출 규칙 :

x86-32에서 매개 변수는 스택에 전달되었습니다. 모든 매개 변수가 완료되고 call명령이 실행될 때까지 마지막 매개 변수가 스택에 먼저 푸시 되었습니다. 이는 어셈블리에서 Linux의 C 라이브러리 (libc) 함수를 호출하는 데 사용됩니다.

(리눅스에서 사용)는 i386 시스템 V ABI의 현대 버전은 16 바이트 정렬이 필요한 %esp사전이 call시스템 V ABI는 항상 요구 한 - 64 등을. 수신자는이를 가정하고 정렬되지 않은 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 매개 변수에 사용됩니다. 두 번째는 % rsi, 세 번째는 % rdx입니다. 그런 다음 call교육을 받아야합니다. 스택 ( %rsp)은 call실행될 때 16B로 정렬되어야합니다 .

INTEGER 매개 변수가 6 개보다 많으면 7 번째 INTEGER 매개 변수 이상이 스택에 전달됩니다. (x86-32와 동일한 발신자 팝업)

처음 8 개의 부동 소수점 인수는 스택에서 나중에 % xmm0-7에 전달됩니다. 통화 보존 벡터 레지스터가 없습니다. FP와 정수 인수가 혼합 된 함수는 총 레지스터 인수를 8 개 이상 가질 수 있습니다.

가변 함수 ( printf :)는 항상 %alFP 레지스터 인수 수를 필요로합니다 .

언제 구조체를 레지스터 (패키지 rdx:rax) 대 메모리에 패킹 할 것인지에 대한 규칙이 있습니다 . 자세한 내용은 ABI를 참조하고 컴파일러 출력을 확인하여 코드가 무언가를 전달 / 반환하는 방법에 대해 컴파일러와 일치하는지 확인하십시오.


주의 에서 Windows x64 함수 호출 규칙이 그림자 공간 같은 x86-64에 시스템 V에서 여러 유의 한 차이를 가지고 있어야합니다 (대신 레드 존의) 호출에 의해 예약 및 통화 보존 xmm6-xmm15을. 그리고 arg가 어떤 레지스터로 들어가는 지에 대해 매우 다른 규칙.


1
리눅스 32에서 "ax bx cd dx si di bp를 제외한 모든 레지스터는 보존된다". 나는 전혀 생각할 수 없다 ...
Albert van der Horst

amd64에서 매개 변수가 6 개 이상이고 스택에 전달되면 호출, 호출자 또는 호출 수신자 후 스택 정리를 담당하는 사람은 누구입니까?
Nicolás

1
@ Nicolás : 호출자가 스택을 정리합니다. 함수 호출 규칙에 대한 자세한 내용으로 답변을 업데이트했습니다.
Peter Cordes

1
int 0x8064 비트 코드에서 Linux ABI 를 사용 하는 경우 정확하게 발생합니다 : stackoverflow.com/questions/46087730/… . r8-r11은 0이며 32 비트 프로세스에서 실행될 때와 동일하게 작동합니다. 그 Q & A에서 포인터가 잘 리거나 작동하지 않는 것을 보여주는 예제가 있습니다. 또한 커널 소스를 조사하여 왜 그렇게 작동하는지 보여줍니다.
Peter Cordes

1
@EvanCarroll : 코드 조각 (인용 텍스트) 주어진 링크에 있습니다 리눅스 조립 튜토리얼 특히 섹션 4.3 리눅스 시스템 호출
마이클 페치

14

아마도 x86_64 ABI를 찾고 계십니까?

그게 정확하지 않은 경우 선호하는 검색 엔진에서 'x86_64 abi'를 사용하여 대체 참조를 찾으십시오.


5
실제로 시스템 호출 규칙 만 원합니다. ESP UNIX (FreeBSD의)에 대한
발톱

3
@claws : 시스템 호출 규칙은 ABI의 일부입니다.
Jonathan Leffler

1
네. 나는 각각의 OS의 커널 개발 irc에 가서 그것에 대해 물었다. 그들은 나에게 소스를 조사하고 알아 내라고 말했습니다. 물건을 문서화하지 않으면 어떻게 개발을 시작할 수 있는지 이해할 수 없습니까? 그래서 다른 사람들이 나머지 세부 사항을 작성하기를 희망하면서 수집 한 정보에서 답변을 추가했습니다.
발톱

@JonathanLeffler 링크가 현재 작동하지 않는 것 같습니다. 링크를 방문하는 데 문제가있는 경우 업데이트 할 수 있습니까?
Ajay Brahmakshatriya

@AjayBrahmakshatriya : 고마워요; Wayback Machine 레코드에 대한 링크를 추가했습니다. 전체 x86-64.org 웹 사이트는 데이터로 응답하지 않았습니다.
Jonathan Leffler

11

호출 규칙은 다른 프로그램에서 호출하거나 호출 할 때 레지스터에서 매개 변수가 전달되는 방법을 정의합니다. 그리고 이러한 컨벤션의 가장 좋은 소스는 각 하드웨어에 대해 정의 된 ABI 표준 형식입니다. 쉬운 컴파일을 위해 사용자 공간과 커널 프로그램에서도 동일한 ABI를 사용합니다. Linux / Freebsd는 x86-64의 동일한 ABI와 32 비트의 다른 세트를 따릅니다. 그러나 Windows 용 x86-64 ABI는 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의 이유 중 하나입니다 (예 : 메모리 스택에 저장하는 대신 레지스터를 통해 매개 변수 전달)

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


요점 외에. 이 질문의 포스터는 리눅스에서 64 비트 syscall 호출 규칙이 일반적인 ABI 변환과 동일하다면 요구하지 않습니다.
Albert van der Horst

6

리눅스 커널 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.
 */

arch / x86 / entry / entry_32.S의 32 비트 :

/*
 * 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 시스템 콜을 정의하고 다음 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;                       \
})

나는 꽤 자기 설명 적이라고 느낍니다. 이 디자인 된 것 같습니다 어떻게 참고가 정확히 일반 시스템 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

나는에서 최소한의 실행 가능한 유저 랜드의 예를 보여 주었다 : /reverseengineering/16917/arm64-syscalls-table/18834#18834 여기 TODO 그렙 커널 코드, 쉽게해야합니다.


1
"cc"소지품은 불필요 : 리눅스 시스템 콜이 저장 / 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.