호출규약 Windows64가 x86-64의 다른 모든 OS와는 다른 호출 규칙을 사용하는 이유는 무엇입니까?




x64 호출규약 (4)

AMD에는 x86-64에서 사용할 호출 규칙을 설명하는 ABI 사양이 있습니다. x86-64 자체 호출 규칙이있는 Windows를 제외하고 모든 OS가이를 따릅니다. 왜?

이 차이에 대한 기술적, 역사적 또는 정치적 이유를 아는 사람이 있습니까? 아니면 순수하게 NIHsyndrome의 문제입니까?

나는 다른 OS가 더 높은 레벨의 것들에 대한 다른 요구를 가질 수 있다는 것을 이해한다. 그러나 그것은 예를 들어 Windows에서의 레지스터 전달 패러미터가 rcx - rdx - r8 - r9 - rest on stackrdi - rsi - rdx - rcx - r8 - r9 - rest on stack .

추신 : 나는이 전화 협약이 일반적으로 다른 점을 알고 있으며 필요한 경우 세부 정보를 어디에서 찾을 수 있는지 알고 있습니다. 내가 알고 싶은 것은 이유 입니다.

편집 : 방법에 대해서는 위키피디아 항목 과 링크를 참조하십시오.


Windows가 왜 그들이 한 일을했는지 ​​IDK. 추측을 위해이 대답의 끝을보십시오. SysV 콜링 컨벤션이 어떻게 결정되었는지 궁금 해서 메일 링리스트 아카이브를 파헤 치고 멋진 것들을 발견했습니다.

AMD 아키텍트가 활발히 활동 중이므로 AMD64 메일 링리스트에있는 오래된 스레드를 읽는 것이 흥미 롭습니다. 예를 들어 레지스터 이름을 선택하는 것은 어려운 부분 중 하나였습니다. AMD 는 원래 8 개의 레지스터 r0-r7의 이름을 바꾸거나 UAX 와 같은 새 레지스터를 호출하는 것을 고려했습니다.

또한 커널 개발자들로부터 피드백을 swapgs syscallswapgs 의 원래 디자인을 사용할 수 없게 만든다. 이것이 AMD가 실제 칩을 출시하기 전에 이것을 분류 하는 지침업데이트 한 방법 입니다. 또한 2000 년 말에 인텔이 AMD64를 채택하지 않을 것이라는 가정이 흥미 롭습니다.

SysV (Linux) 호출 규칙과 발신자 저장 대 전화 번호 저장 유지 여부 결정은 Jan Hubicka (gcc 개발자)가 2000 년 11 월에 처음 작성했습니다 . 그는 SPEC2000을 컴파일 하여 코드 크기 및 지침 수를 검토했습니다. 이 토론 스레드는이 SO 질문에 대한 답변과 의견과 동일한 아이디어를 주위에 반송합니다. 두 번째 스레드에서 그는 현재 시퀀스를 최적의 것으로 제안하고, 최종적으로 최종적으로 일부 대안보다 작은 코드를 생성했습니다 .

그는 "전역"이라는 용어를 사용하여 통화 보존 레지스터를 의미합니다. 사용되는 경우 푸시 / 팝되어야합니다.

처음 세 args로 rdi , rsi , rdi 의 선택에 의해 동기 부여했다 :

  • memset 또는 args에 다른 C 문자열 함수를 호출하는 함수에서 사소한 코드 크기를 절약합니다 (gcc는 rep 문자열 연산을 인라인합니다).
  • rbx 는 REX 접두어 (rbx 및 rbp)없이 액세스 할 수있는 두 개의 통화 보존 된 rbx 갖는 것이이기 때문에 통화 보존됩니다. 아마 그것은 어떤 명령에 의해서도 묵시적으로 사용되지 않는 다른 reg이기 때문에 아마도 선택 될 것입니다. (rep 문자열, 시프트 수 및 mul / div 출력 / 입력은 다른 모든 것을 만집니다).
  • 특별한 목적을 가진 레지스터는 아무 것도 (prev point를 보아라) call-preserved되지 않는다. 그래서 rep 문자열 명령이나 가변 카운트 쉬프트를 사용하고자하는 함수는 다른 곳으로 함수 args를 옮겨야 만 할 것이다. 호출자의 값을 복원하십시오.
  • 시퀀스의 초기에 RCX를 피하려고합니다. EAX와 같이 특수 목적으로 일반적으로 사용되는 레지스터이기 때문에 순서 상 누락 된 동일한 목적을 갖습니다. 또한 syscall에는 사용할 수 없으므로 함수 호출 시퀀스를 가능한 한 많이 일치시키는 syscall 시퀀스를 만들고 싶습니다.

    (배경 : syscall / sysret 필연적으로 rcx ( rip 과 함께)와 r11 ( RFLAGS 와 함께)을 파괴하기 때문에 커널은 syscallrcx 때 원래 rcx 에 있던 것을 볼 수 없다.)

커널 시스템 콜 ABI는 rcx 대신 r10 제외하고 함수 호출 ABI와 일치하도록 선택되었으므로 mmap(2) 와 같은 libc 래퍼 함수는 mov %rcx, %r10 / mov $0x9, %eax / syscall 있습니다.

i386 Linux에서 사용되는 SysV 호출 규칙은 Window의 32 비트 __vectorcall과 비교해 낫습니다. 그것은 스택에 모든 것을 전달하고 작은 구조체가 아니라 intx의 edx:eax 에서만 반환됩니다 . 그것과의 호환성을 유지하기위한 노력이 조금도 놀랍지 않습니다. 할 수있는 이유가 없을 때 그들은 원래의 8 개의 REX 접두사가 필요 없다는 결정을 rbx 때문에 rbx call-preserved로 유지하는 것과 같은 일을했습니다.

ABI를 최적으로 만드는 것은 다른 어떤 고려 사항보다 장기적으로 중요합니다. 나는 그들이 꽤 잘한 것 같아. 난 다른 regs에 다른 필드 대신 레지스터에 포장 structs 반환에 대해 완전히 확신 해요. 필드에서 실제로 작동하지 않고 값으로 전달하는 코드는이 방법으로 이깁니다. 그러나 언 패킹의 추가 작업은 어리석은 것처럼 보입니다. 그들은 더 많은 정수 리턴 레지스터를 가질 수 있었을 것입니다, 단지 rdx:rax 보다 더 많은 것입니다, 그래서 4 명의 멤버들과 함께 구조체를 반환하면 rdi, rsi, rdx, rax 등에서 그들을 반환 할 수 있습니다.

그들은 SSE2가 정수에서 작동 할 수 있기 때문에 vector regs에 정수를 전달하는 것을 고려했습니다. 다행히도 그들은 그렇게하지 않았습니다. 정수는 포인터 오프셋으로 자주 사용되며 스택 메모리로의 왕복은 매우 저렴 합니다. 또한 SSE2 명령어는 정수 명령어보다 많은 코드 바이트를 사용합니다.

필자는 Windows ABI 설계자가 하나의 ASM에서 다른 ASM으로 포트를 옮겨야하는 사람들을 위해 32 비트와 64 비트 간의 차이를 최소화하려고했거나 동일한 ASM에서 몇 가지 #ifdef 사용하여 동일한 소스가 더 많은 것을 할 수 있다고 생각했습니다. 함수의 32 또는 64 비트 버전을 쉽게 빌드 할 수 있습니다.

툴체인의 변경을 최소화하는 것은 쉬운 일이 아닙니다. x86-64 컴파일러에는 무엇을 위해 어떤 레지스터가 사용되고, 호출 규칙이 무엇인지에 대한 별도의 테이블이 필요합니다. 32 비트가 약간 겹치면 툴체인 코드 크기 / 복잡성이 크게 줄어들지 않습니다.


x64에서 네 개의 인수 레지스터 선택 - UN * X / Win64와 공통

x86에 대해 염두에 두어야 할 사항 중 하나는 "reg 번호"인코딩에 대한 레지스터 이름이 분명하지 않다는 것입니다. 명령 인코딩 ( MOD R / M 바이트, http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm )의 관점에서 레지스터 번호 0 ... 7은 - 순서대로 - ?AX , ?CX , ?DX , ?BX , ?SP , ?BP , ?SI , ?DI .

따라서 반환 값과 처음 두 인수 ( "고전적인"32 비트 __fastcall 규칙)에 대해 A / C / D (regs 0..2)를 선택하는 것이 논리적 인 선택입니다. 64 비트에 관한 한 "더 높은"regs가 주문되었으며, Microsoft와 UN * X / Linux는 R8 / R9 를 처음으로 사용했습니다.

Microsoft가 RAX (반환 값) 및 RCX , RDX , R8 , R9 (arg [0..3])를 선택하는 것은 인수에 대해 네 개의 레지스터를 선택하는 경우 이해할 수있는 선택입니다.

왜 AMD64 UN * X ABI가 RCX 선택하기 전에 RDX 를 선택했는지 모르겠습니다.

x64에서 6 개의 인수 레지스터 선택 - UN * X 특정

RISC 아키텍처의 UN * X는 전통적으로 레지스터에서 전달하는 인수를 수행했습니다. 특히 처음 6 개의 인수 (PPC, SPARC, MIPS 등)에서 인수를 전달했습니다. AMD64 (UN * X) ABI 설계자가 아키텍처에서 여섯 개의 레지스터를 사용하기로 선택한 주된 이유 중 하나가 될 수 있습니다.

6 개의 레지스터가 인수를 전달하기를 원한다면 RCX , RDX , R8R9 중 4 개를 선택하는 것이 합리적입니다. 나머지 두 개는 선택해야합니까?

"상위"레지스터는 명령어를 선택하기 위해 추가 명령어 접두사 바이트가 필요하므로 명령어 크기가 더 커지므로 옵션이있는 경우이를 선택하지 않아도됩니다. 클래식 레지스터 중 RBPRSP암시적인 의미로 인해 이들은 사용할 수 없으며 RBX 전통적으로 UN * X (글로벌 오프셋 테이블)에서 특수한 용도로 사용됩니다. AMD64 ABI 설계자는 불필요하게 호환되지 않을 것 같지 않습니다. 와.
Ergo, 유일한 선택RSI / RDI 였습니다.

RSI / RDI 를 인수 레지스터로 가져 가야한다면 어떤 인수를 사용해야합니까?

arg[0]arg[1] 을 만드는 것은 몇 가지 장점이 있습니다. cHao의 의견을 참조하십시오.
?SI?DI 는 문자열 명령 소스 / 대상 피연산자이며 cHao에서 언급 한 것처럼 인수 레지스터로 사용하면 AMD64 UN * X 호출 규칙에서 가능한 가장 단순한 strcpy() 함수는 두 개의 CPU 명령 repz movsb; ret 소스 / 타겟 어드레스가 발신자에 의해 올바른 레지스터에 저장 되었기 때문에 repz movsb; ret 된다. 특히 저수준 및 컴파일러에서 생성 된 "glue"코드 (예 : 일부 C ++ 힙 할당자가 생성시 객체를 0으로 채우거나 sbrk() 에서 커널이 0을 채우는 힙 페이지라고 생각하거나 copy-on -write pagefaults)에 막대한 양의 블럭 복사 / 채우기가 있으므로이 소스 / 대상 주소 인수를 "올바른"레지스터에로드하는 2 ~ 3 개의 CPU 명령어를 저장하는 데 자주 사용되는 코드에 유용합니다.

따라서 유엔 * X와 Win64는 유엔 * X가 RCX , RDX , R8R9 의 4 가지 논점 중 RCX 으로 선택된 RSI / RDI 레지스터에서 두 가지 추가적인 주장을 "앞에"추가한다는 점에서 차이가 있습니다.

그 너머 ...

UN * X와 Windows x64 ABI에는 특정 레지스터에 인수를 매핑하는 것보다 더 많은 차이점이 있습니다. Win64에 대한 개요는 다음을 확인하십시오.

http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx

Win64와 AMD64 UN * X는 또한 stackspace가 사용되는 방식이 현저히 다릅니다. 예를 들어 Win64에서 args 0 ... 3이 레지스터로 전달 되더라도 호출자 함수 인수에 대해 stackspace를 할당 해야합니다 . 반면에 UN * X에서는 리프 함수 (다른 함수를 호출하지 않는 함수)는 스택 공간을 128 바이트 이상 필요로하지 않으면 스택 공간을 할당 할 필요가 없습니다 (예, 소유하고 사용할 수 있음). 커널 코드를 제외하고는 스택을 할당하지 않고서도 일정량의 스택을 만들 수 있습니다. 멋진 버그의 근원). 이 모든 것들이 특별한 최적화 선택 사항이며, 그 이유에 대한 대부분의 설명은 원래의 포스터의 위키 피 디아 참조가 가리키는 전체 ABI 참조에서 설명됩니다.


Microsoft는 IA64 아키텍처에서 Intel과 강력한 파트너 관계 였기 때문에 처음에는 "초기 AMD64 노력에 대해 공식적으로 약속하지 않았습니다"(Matthew Kerner 및 Neil Padgett의 "현대 64 비트 컴퓨팅의 역사" 에서) 것을 기억하십시오. 나는 이것이 ABI의 GCC 엔지니어들과 유닉스와 윈도우즈 모두에서 사용하도록 개방되어 있었다고해도 AMD64 노력을 공개적으로지지한다는 의미로 그렇게하지 않았을 것이라고 생각했다. 아직 공식적으로 그렇게 (그리고 아마도 인텔을 화나게 할 것입니다).

그 당시에도 마이크로 소프트는 오픈 소스 프로젝트와 친하게 지내는 데 전혀 열중하지 않았습니다. 물론 Linux 나 GCC는 아닙니다.

그렇다면 왜 그들은 ABI에 협력했을까요? 나는 ABI가 다소 다르다는 것을 단순히 추측 할 수 있습니다. 왜냐하면 ABI는 다소간 같은 시간에 고립되어 설계 되었기 때문입니다.

"현대 64 비트 컴퓨팅의 역사"에서 인용 한 또 다른 인용문 :

마이크로 소프트의 협업과 병행하여 AMD는 오픈 소스 커뮤니티와 협력하여 칩을 준비했습니다. AMD는 코드 소서리 (Code Sorcery)와 수세 (SuSE)와 툴 체인 작업을 위해 계약을 맺었습니다 (Red Hat은 이미 IA64 툴 체인 포트에서 인텔과 계약을 맺고있었습니다). Russell은 SuSE가 C 및 FORTRAN 컴파일러를 생성했으며 Code Sorcery가 Pascal 컴파일러를 생성했다고 설명했습니다. 웨버는 회사가 리눅스 커뮤니티를 통해 리눅스 포트를 준비하고 있다고 설명했다. 이 노력은 매우 중요했습니다. Microsoft가 AMD64 Windows 작업에 지속적으로 투자 할 수있는 인센티브로 작용했으며 당시에 중요한 OS가 된 Linux가 출시되면 사용할 수있게되었습니다.

Weber는 AMD가 AMD64의 성공에 절대적으로 결정적인 역할을했다고 말하면서 AMD는 필요하다면 다른 회사의 지원 없이도 종단 간 시스템을 생산할 수있게되었습니다. 이 가능성은 AMD가 다른 파트너가 철회 했음에도 불구하고 최악의 상황에서 생존 전략을 취할 수있게 해주었습니다.

이것은 심지어 AMD도 MS와 유닉스 사이에서 협력이 필연적으로 가장 중요하다고 느끼지는 않았지만 Unix / Linux를 지원하는 것은 매우 중요하다는 것을 나타냅니다. 어쩌면 한쪽이나 양쪽 모두가 타협하거나 협조하도록 설득하려고 노력하는 것조차도 그 중 하나를 짜증나게하는 노력이나 위험 (?)에 가치가 있지 않습니까? 아마도 AMD는 일반적인 ABI를 제안하는 것조차도 칩이 준비되었을 때 소프트웨어 지원을 준비하는 것보다 더 중요한 목표를 지연 시키거나 탈선시킬 수 있다고 생각했습니다.

추측하지만 내 생각에 ABI가 다른 주된 이유는 MS와 유닉스 / 리눅스 측이 함께 작동하지 않는 정치적 이유 였고 AMD는이를 문제로 보지 않았다.


Win32는 ESI와 EDI를 위해 자체적으로 사용되며 수정되지 않도록해야합니다 (또는 최소한 API 호출 전에 복원해야 함). 나는 64 비트 코드가 RSI와 RDI에서 똑같지 만, 함수 인수를 전달하는 데 익숙하지 않은 이유를 설명 할 것이라고 생각한다.

RCX와 RDX가 왜 바뀌 었는지 말할 수 없었습니다.





calling-convention