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

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

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

나는 OS마다 더 높은 수준의 요구 사항이 다를 수 있음을 이해하지만, 예를 들어 Windows에서 레지스터 매개 변수 전달 순서가 rcx - rdx - r8 - r9 - rest on stack다른 모든 사람들이 rdi - rsi - rdx - rcx - r8 - r9 - rest on stack.

추신 : 이러한 호출 규칙이 일반적으로 어떻게 다른지 알고 있으며 필요한 경우 세부 정보를 찾을 수있는 위치를 알고 있습니다. 내가 알고 싶은 것은 이유 입니다.

편집 : 방법은 wikipedia 항목 과 링크를 참조하십시오 .



답변

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

x86에 대해 기억해야 할 사항 중 하나는 “reg number”인코딩에 대한 레지스터 이름이 명확하지 않다는 것입니다. 명령어 인코딩 측면에서 ( 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.

따라서 반환 값으로 A / C / D (regs 0..2)를 선택하고 처음 두 인수 ( “고전적인”32 비트 __fastcall규칙)를 선택하는 것은 논리적 선택입니다. 64 비트로가는 것에 관해서는 “상위”regs가 주문되고 Microsoft와 UN * X / Linux 둘 다 첫 번째로 R8/ R9를 사용했습니다.

염두에 유지, 마이크로 소프트의 선택 RAX(반환 값) 및 RCX, RDX, R8, R9(인수 [0..3])를 선택하면 이해할 수있는 선택입니다 사 개 인수 레지스터.

AMD64 UN * X ABI가 RDX이전에 선택한 이유를 모르겠습니다 RCX.

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

RISC 아키텍처에서 UN * X는 전통적으로 레지스터에 인수 전달을 수행했습니다. 특히 처음 6 개의 인수 (최소한 PPC, SPARC, MIPS에서 그렇습니다)에 대해. AMD64 (UN * X) ABI 디자이너가 해당 아키텍처에서도 6 개의 레지스터를 사용하기로 선택한 주요 이유 중 하나 일 수 있습니다.

그래서 당신이 원하는 경우 여섯 개 에 인수를 전달하는 레지스터, 그리고 그것을 선택하는 논리이다 RCX, RDX, R8그리고 R9그들 중 네, 당신은 두 개의 다른 어떤을 선택해야합니까?

“높은”regs는 선택하기 위해 추가 명령어 접두사 바이트가 필요하므로 명령어 크기가 더 커지므로 옵션이있는 경우 선택하고 싶지 않을 것입니다. 때문에에 고전 레지스터의 암시 의 의미 RBPRSP이 사용할 수 없으며, RBX전통적으로 겉으로 AMD64 ABI 디자이너가 불필요하게 호환되고 싶지 않았다 UN * X (전역 옵셋 테이블)에 대한 특별 사용이 있습니다.
Ergo, 유일한 선택RSI/ RDI.

따라서 RSI/ RDI를 인수 레지스터로 가져와야한다면 어떤 인수 여야합니까?

그들을 arg[0]만들고 arg[1]몇 가지 장점이 있습니다. cHao의 의견을 참조하십시오.
?SI그리고 ?DI문자열 명령어 소스 / 대상 피연산자이며 cHao가 언급했듯이 인수 레지스터로 사용한다는 것은 AMD64 UN * X 호출 규칙에서 가장 간단한 strcpy()함수가 예를 들어 repz movsb; ret소스 / 대상 이 두 개의 CPU 명령어로만 구성 된다는 것을 의미합니다. 호출자가 주소를 올바른 레지스터에 넣었습니다. 특히 저수준 및 컴파일러 생성 “접착제”코드에 있습니다 (예를 들어 일부 C ++ 힙 할당자는 생성시 개체를 0으로 채우거나 커널에서 힙 페이지를 0으로 채우는 경우).sbrk(), 또는 copy-on-write pagefaults) 막대한 양의 블록 복사 / 채우기를 수행하므로 소스 / 대상 주소 인수를로드하는 두 개 또는 세 개의 CPU 명령을 저장하는 데 자주 사용되는 코드에 유용합니다. “올바른”레지스터.

따라서 UN * X와 Win64는 UN * X가 의도적으로 선택한 RSI/ RDI레지스터 에서 두 개의 추가 인수 RCXRDX, R8및 에서 4 개의 인수를 자연스럽게 선택하는 것에 “앞에 추가”한다는 점에서만 다릅니다 R9.

그 너머 …

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

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

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


답변

Windows가 그들이 한 일을 한 이유 IDK. 추측에 대해서는이 답변의 끝을 참조하십시오. SysV 호출 규칙이 어떻게 결정되었는지 궁금 해서 메일 링리스트 아카이브를 파헤쳐 서 깔끔한 것을 찾았습니다.

AMD 아키텍트가 활발하게 활동하고 있었기 때문에 AMD64 메일 링리스트에서 오래된 쓰레드 중 일부를 읽는 것은 흥미 롭습니다. 예를 들어 레지스터 이름을 선택하는 것은 어려운 부분 중 하나였습니다. AMD 는 원래 8 레지스터 r0-r7의 이름을 바꾸거나UAX .

또한, 커널의 피드백은 원래의 설계했다 확인 된 것들 DEVS syscallswapgs사용할 수를 . 이것이 AMD가 실제 칩을 출시하기 전에 이것을 분류하기 위해 지침업데이트 한 방법 입니다. 2000 년 후반 인텔이 AMD64를 채택하지 않을 것이라는 가정도 흥미 롭습니다.


SysV (Linux) 호출 규칙과 호출자 보존 대 호출자 저장에 대한 레지스터 수에 대한 결정은 2000 년 11 월 Jan Hubicka (gcc 개발자)에 의해 처음에 이루어졌습니다 . 그는 SPEC2000을 컴파일 하고 코드 크기와 명령어 수를 조사했습니다. 그 토론 스레드는이 SO 질문에 대한 답변 및 의견과 동일한 아이디어 중 일부를 중심으로 튀어 나옵니다. 두 번째 스레드에서 그는 현재 시퀀스를 최적이며 최종적으로 제안하여 일부 대안보다 작은 코드를 생성 합니다.

그는 “글로벌”이라는 용어를 사용하여 푸시 / 팝해야하는 호출 보존 레지스터를 의미합니다.

의 선택 rdi, rsi, rdx처음 세 인수에 의해 동기가되었을 때 :

  • memset인수에 대한 다른 C 문자열 함수를 호출하는 함수에서 코드 크기를 약간 절약 합니다 (여기서 gcc는 rep 문자열 작업을 인라인합니까?).
  • rbxREX 접두사 (rbx 및 rbp)없이 액세스 할 수있는 두 개의 호출 보존 된 reg를 갖는 것이 승리이기 때문에 호출 보존됩니다. 어떤 명령에서도 암시 적으로 사용되지 않는 유일한 다른 reg이기 때문에 아마도 선택되었을 것입니다. (rep string, shift count, mul / div outputs / inputs touch all other all).
  • 특별한 목적을 가진 레지스터는 호출 보존되지 않으므로 (이전 포인트 참조) rep 문자열 명령어 나 가변 카운트 시프트를 사용하려는 함수는 함수 인수를 다른 곳으로 이동해야 할 수 있지만 저장할 필요는 없습니다. 발신자의 가치를 회복하십시오.
  • RCX는 EAX와 같은 특수한 목적으로 일반적으로 사용되는 레지스터이므로 시퀀스에서 누락되는 동일한 목적을 가지고 있으므로 시퀀스 초기에 RCX를 피하려고합니다. 또한 syscall에 사용할 수 없으며 가능한 한 함수 호출 시퀀스와 일치하도록 syscall 시퀀스를 만들고 싶습니다.

    (배경 : syscall/ sysret불가피하게 rcx(with rip) 및 r11(with RFLAGS) 파괴 하므로 커널이 실행 rcx되었을 때 원래 있던 내용을 볼 수 없습니다 syscall.)

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


i386 Linux에서 사용하는 SysV 호출 규칙은 Window의 32 비트 __vectorcall에 비해 짜증이납니다. 스택의 모든 것을 전달 edx:eax하고 작은 구조체가 아닌 int64에 대해서만 반환 합니다 . 그것과의 호환성을 유지하기 위해 약간의 노력을 기울인 것은 놀라운 일이 아닙니다. 그렇게하지 않을 이유가 없을 때, 그들은 rbx원래 8 (REX 접두사가 필요하지 않음)에 다른 것을 갖는 것이 좋다고 결정했기 때문에 통화 보존을 유지하는 것과 같은 일 을했습니다.

ABI를 최적으로 만드는 것은 다른 고려 사항보다 장기적으로 훨씬 더 중요합니다. 나는 그들이 꽤 잘했다고 생각합니다. 다른 regs의 다른 필드 대신 레지스터에 압축 된 구조체를 반환하는 것에 대해 완전히 확신하지 못합니다. 필드에서 실제로 작동하지 않고 값별로 전달하는 코드가이 방법으로이기는 것 같지만, 압축을 푸는 추가 작업은 어리석은 것 같습니다. 그것들은 단지보다 더 많은 정수 리턴 레지스터를 가질 수 있었으므로, rdx:rax4 개의 멤버가있는 구조체를 리턴하면 rdi, rsi, rdx, rax 등으로 리턴 될 수 있습니다.

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


나는 Windows ABI 디자이너가 asm을 한 곳에서 다른 곳으로 포팅해야하는 사람들의 이익을 위해 32 비트와 64 비트 사이의 차이를 최소화하는 것을 목표로했을 수도 #ifdef있고, 같은 소스를 더 쉽게 구축 할 수 있도록 일부 ASM에서 몇 s를 사용할 수 있다고 생각합니다. 32 비트 또는 64 비트 버전의 함수.

툴체인의 변경을 최소화하는 것은 불가능 해 보입니다. x86-64 컴파일러에는 레지스터가 무엇에 사용되며 호출 규칙이 무엇인지에 대한 별도의 테이블이 필요합니다. 32 비트와 약간 겹치는 부분은 도구 체인 코드 크기 / 복잡성을 크게 절감 할 수 없습니다.


답변

Microsoft는 IA64 아키텍처에서 Intel과 강력한 파트너 였기 때문에 처음에는 “초기 AMD64 노력에 대해 공식적으로 헌신하지 않았습니다”( Matthew Kerner와 Neil Padgett의 “현대 64 비트 컴퓨팅의 역사” 에서 )를 기억하십시오. 이것이 의미하는 바는 그들이 Unix와 Windows에서 모두 사용하기 위해 ABI에서 GCC 엔지니어들과 함께 일할 수 있었을지라도 그렇게하지 않았을 때 AMD64 노력을 공개적으로 지원한다는 것을 의미하기 때문이라고 생각합니다. 아직 공식적으로 그렇게했습니다 (그리고 아마도 인텔을 화나게했을 것입니다).

게다가 그 당시 마이크로 소프트는 오픈 소스 프로젝트에 대해 전혀 관심이 없었습니다. 확실히 Linux 나 GCC는 아닙니다.

그렇다면 왜 그들은 ABI에 협력했을까요? ABI는 단순히 거의 동시에 그리고 분리되어 설계 되었기 때문에 다르다고 생각합니다.

“현대 64 비트 컴퓨팅의 역사”의 또 다른 인용문 :

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

Weber는 Linux 작업이 AMD64의 성공에 절대적으로 중요하다고 말합니다. AMD가 필요한 경우 다른 회사의 도움 없이도 엔드 투 엔드 시스템을 생산할 수 있었기 때문입니다. 이 가능성은 다른 파트너가 물러나더라도 AMD가 최악의 생존 전략을 가지고 있음을 보장했으며, 결국 다른 파트너는 자신에게 뒤처 질 까봐 두려웠습니다.

이것은 AMD조차도 MS와 Unix 간의 협력이 반드시 가장 중요하다고 생각하지 않았지만 Unix / Linux 지원이 매우 중요하다는 것을 나타냅니다. 타협하거나 협력하도록 한쪽 또는 양쪽을 설득하려는 시도조차도 그들 중 하나를 짜증나게하는 노력이나 위험 (?)의 가치가 없었을까요? 아마도 AMD는 공통 ABI를 제안하는 것조차 단순히 칩이 준비되었을 때 소프트웨어 지원을 준비하는 더 중요한 목표를 지연 시키거나 탈선시킬 수 있다고 생각했을 것입니다.

제 생각에는 추측이지만 ABI가 다른 주된 이유는 MS와 유닉스 / 리눅스 측이 함께 작동하지 않았고 AMD가 그것을 문제로 보지 않은 정치적 이유 때문이라고 생각합니다.


답변

Win32는 ESI 및 EDI를 자체적으로 사용하며 수정하지 않아야합니다 (또는 적어도 API를 호출하기 전에 복원해야 함). 64 비트 코드가 RSI 및 RDI와 동일한 작업을 수행한다고 생각합니다. 따라서 함수 인수를 전달하는 데 사용되지 않는 이유가 설명됩니다.

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


답변