다음 링크는 UNIX (BSD 플레이버) 및 Linux 모두에 대한 x86-32 시스템 호출 규칙을 설명합니다.
그러나 UNIX와 Linux 모두에서 x86-64 시스템 호출 규칙은 무엇입니까?
sysret
작동 방식으로 인해 파괴 되고 rax는 반환 값으로 대체됩니다. 다른 모든 레지스터는 amd64에 유지됩니다.
다음 링크는 UNIX (BSD 플레이버) 및 Linux 모두에 대한 x86-32 시스템 호출 규칙을 설명합니다.
그러나 UNIX와 Linux 모두에서 x86-64 시스템 호출 규칙은 무엇입니까?
sysret
작동 방식으로 인해 파괴 되고 rax는 반환 값으로 대체됩니다. 다른 모든 레지스터는 amd64에 유지됩니다.
답변:
여기에있는 주제에 대한 추가 정보 : Linux 시스템 호출에 대한 결정적인 안내서
나는 리눅스에서 GNU Assembler (gas)를 사용하여 이것을 확인했다.
x86-32 일명 i386 Linux 시스템 호출 규칙 :
x86-32에서 Linux 시스템 호출에 대한 매개 변수는 레지스터를 사용하여 전달됩니다. %eax
syscall_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 Mac OS X은 비슷하지만 다릅니다 . TODO : * BSD의 기능을 확인하십시오.
System V 응용 프로그램 바이너리 인터페이스 AMD64 아키텍처 프로세서 부록의 "A.2 AMD64 Linux 커널 규칙" 섹션을 참조하십시오 . 최신 버전의 i386 및 x86-64 시스템 V psABI는 이 페이지에서 ABI 관리자의 저장소에 링크되어 있습니다 . (또한 참조x86 x86 asm에 대한 최신 ABI 링크 및 기타 유용한 정보를 제공하는 태그 위키
이 섹션의 스 니펫은 다음과 같습니다.
- 사용자 수준 응용 프로그램은 시퀀스 % rdi, % rsi, % rdx, % rcx, % r8 및 % r9를 전달하기 위해 정수 레지스터로 사용합니다. 커널 인터페이스는 % rdi, % rsi, % rdx, % r10, % r8 및 % r9를 사용합니다.
- 시스템 호출은
syscall
명령어 를 통해 수행됩니다 . 이 clobbers % rcx 및 % r11 및 % rax 반환 값이지만 다른 레지스터는 유지됩니다.- syscall 수는 레지스터 % rax에 전달되어야합니다.
- 시스템 호출은 6 개의 인수로 제한되며 스택에 직접 전달되는 인수는 없습니다.
- syscall에서 복귀하면 레지스터 % rax에 시스템 호출 결과가 포함됩니다. -4095와 -1 사이의 값은 오류를 나타냅니다
-errno
.- INTEGER 클래스 또는 MEMORY 클래스의 값만 커널로 전달됩니다.
이것은 Linux 관련 부록에서 ABI에 대한 것임을 기억하십시오. 심지어 Linux의 경우 표준이 아닙니다. (그러나 실제로는 정확합니다.)
이 32 비트 int $0x80
ABI 는 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는 레지스터에서 args를 전달합니다. 이는 i386 System V의 스택 args 규칙보다 효율적입니다. 대기 시간과 인수를 메모리 (캐시)에 저장 한 다음 수신자에게 다시로드하는 추가 명령을 피합니다. 이것은 사용 가능한 레지스터가 더 많기 때문에 잘 작동하며 대기 시간과 순서가 잘못된 실행이 필요한 최신 고성능 CPU에 더 좋습니다. (i386 ABI는 매우 오래되었습니다).
이 새로운 메커니즘에서 : 먼저 매개 변수는 클래스로 나뉩니다. 각 매개 변수의 클래스는 호출 된 함수에 전달되는 방식을 결정합니다.
자세한 내용은 System V 응용 프로그램 바이너리 인터페이스 AMD64 아키텍처 프로세서 부록의 "3.2 함수 호출 시퀀스"를 참조하십시오 .
인수가 분류되면 레지스터는 다음과 같이 전달하기 위해 왼쪽에서 오른쪽으로 할당됩니다.
- 클래스가 MEMORY 인 경우 스택에서 인수를 전달하십시오.
- 클래스가 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
:)는 항상 %al
FP 레지스터 인수 수를 필요로합니다 .
언제 구조체를 레지스터 (패키지 rdx:rax
) 대 메모리에 패킹 할 것인지에 대한 규칙이 있습니다 . 자세한 내용은 ABI를 참조하고 컴파일러 출력을 확인하여 코드가 무언가를 전달 / 반환하는 방법에 대해 컴파일러와 일치하는지 확인하십시오.
주의 에서 Windows x64 함수 호출 규칙이 그림자 공간 같은 x86-64에 시스템 V에서 여러 유의 한 차이를 가지고 있어야합니다 (대신 레드 존의) 호출에 의해 예약 및 통화 보존 xmm6-xmm15을. 그리고 arg가 어떤 레지스터로 들어가는 지에 대해 매우 다른 규칙.
int 0x80
64 비트 코드에서 Linux ABI 를 사용 하는 경우 정확하게 발생합니다 : stackoverflow.com/questions/46087730/… . r8-r11은 0이며 32 비트 프로세스에서 실행될 때와 동일하게 작동합니다. 그 Q & A에서 포인터가 잘 리거나 작동하지 않는 것을 보여주는 예제가 있습니다. 또한 커널 소스를 조사하여 왜 그렇게 작동하는지 보여줍니다.
아마도 x86_64 ABI를 찾고 계십니까?
그게 정확하지 않은 경우 선호하는 검색 엔진에서 'x86_64 abi'를 사용하여 대체 참조를 찾으십시오.
호출 규칙은 다른 프로그램에서 호출하거나 호출 할 때 레지스터에서 매개 변수가 전달되는 방법을 정의합니다. 그리고 이러한 컨벤션의 가장 좋은 소스는 각 하드웨어에 대해 정의 된 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
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
이 문서는 다른 모든 규칙에 대한 좋은 개요입니다.
리눅스 커널 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
aarch64
나는에서 최소한의 실행 가능한 유저 랜드의 예를 보여 주었다 : /reverseengineering/16917/arm64-syscalls-table/18834#18834 여기 TODO 그렙 커널 코드, 쉽게해야합니다.
"cc"
소지품은 불필요 : 리눅스 시스템 콜이 저장 / RFLAGS을 복원합니다 ( syscall
/ sysret
지침 R11을 사용하고 커널이 저장 R11을 수정하지 않는 것은 / 경유 이외의 RFLAGS 그렇게 ptrace
디버거 시스템 호출.)이 때문에 지금까지, 중요하지 않는 것이 "cc"
소지품은 GNU C Extended asm에서 x86 / x86-64에 암시 적으로 적용되므로 아무 것도 얻을 수 없습니다.