다음을 통해 공유


x64 프롤로그 및 에필로그

스택 공간을 할당하거나, 다른 함수를 호출하거나, 비휘발성 레지스터를 저장하거나, 예외 처리를 사용하는 모든 함수는 해당 함수 테이블 항목에 연결된 해제 데이터에 주소 제한이 설명된 프롤로그가 있어야 합니다. 자세한 내용은 x64 예외 처리를 참조하세요. 프롤로그는 홈 주소에 인수 레지스터를 저장하고(필요한 경우), 스택에 비휘발성 레지스터를 푸시하고, 로컬 및 임시 개체에 대해 스택의 고정 부분을 할당하고, 필요에 따라 프레임 포인터를 설정합니다. 연결된 해제 데이터는 프롤로그의 동작을 설명하고 프롤로그 코드의 효과를 취소하는 데 필요한 정보를 제공해야 합니다.

스택의 고정 할당이 두 페이지를 넘는 경우(즉 4096바이트보다 큰 경우) 스택 할당이 두 페이지 이상의 가상 메모리에 걸쳐 있을 수 있으므로 할당하기 전에 확인해야 합니다. 프롤로그에서 호출할 수 있고 어떤 인수 레지스터도 제거하지 않는 특수 루틴은 이러한 용도로 제공됩니다.

비휘발성 레지스터를 저장하는 기본 방법은 고정 스택 할당 전에 스택으로 이동하는 것입니다. 비휘발성 레지스터를 저장하기 전에 고정 스택 할당을 수행하는 경우 저장된 레지스터 영역을 처리하려면 대개 32비트 치환이 필요합니다. (소문에 따르면, 레지스터의 푸시는 이동만큼 빠르며 푸시 간의 묵시적 종속성에도 불구하고 당분간 그렇게 유지되어야 합니다.) 비휘발성 레지스터는 순서에 따라 저장할 수 있습니다. 그러나 프롤로그에서 비휘발성 레지스터를 처음 사용하는 경우에는 순서에 따라 저장해야 합니다.

프롤로그 코드

일반적인 프롤로그에 대한 코드는 다음과 같을 수 있습니다.

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    sub    RSP, fixed-allocation-size
    lea    R13, 128[RSP]
    ...

이 프롤로그는 인수 레지스터 RCX를 홈 위치에 저장하고, 비휘발성 레지스터 R13-R15를 저장하고, 스택 프레임의 고정 부분을 할당하고, 128바이트를 고정 할당 영역으로 가리키는 프레임 포인터를 설정합니다. 오프셋을 사용하면 1바이트 오프셋으로 더 많은 고정 할당 영역에 주소를 지정할 수 있습니다.

고정 할당 크기가 메모리의 한 페이지이거나 이보다 큰 경우에는 RSP를 수정하기 전에 도우미 함수를 호출해야 합니다. 이 도우미 __chkstk는 할당될 스택 범위를 검색하여 스택이 적절히 확장되게 합니다. 이 경우 이전 프롤로그 예제는 다음과 같습니다.

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    mov    RAX,  fixed-allocation-size
    call   __chkstk
    sub    RSP, RAX
    lea    R13, 128[RSP]
    ...

__chkstk 도우미는 R10, R11 및 조건 코드 이외의 레지스터는 수정하지 않습니다. 특히 RAX를 변경되지 않은 상태로 반환하고 모든 비휘발성 레지스터 및 인수 전달 레지스터를 수정되지 않은 상태로 둡니다.

에필로그 코드

에필로그 코드는 매번 함수로 종료될 때마다 존재합니다. 일반적으로 프롤로그는 하나뿐이지만 에필로그는 여러 개일 수 있습니다. 에필로그 코드는 스택을 고정 할당 크기로 자르고(필요한 경우), 고정 스택 할당을 취소하고, 스택에서 저장된 값을 팝하여 비휘발성 레지스터를 복원하고, 반환합니다.

에필로그 코드는 예외 및 인터럽트를 통해 안정적으로 해제하기 위해 해제 코드에 대한 엄격한 규칙 집합을 따라야 합니다. 이러한 규칙으로 인해 각 에필로그를 설명하는 데 추가 데이터가 없어도 되므로 필요한 해제 데이터의 양이 줄어듭니다. 대신에 해제 코드는 에필로그를 식별하기 위해 코드 스트림을 통한 정방향 스캔으로 에필로그가 실행되고 있음을 확인할 수 있습니다.

함수에서 프레임 포인터를 사용하지 않는 경우 에필로그는 먼저 스택의 고정 부분 할당을 취소해야 합니다. 이어서 비휘발성 레지스터가 팝되고 컨트롤이 호출 함수로 반환됩니다. 예를 들면 다음과 같습니다.

    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

함수에서 프레임 포인터를 사용하는 경우 에필로그를 실행하기 전에 스택을 고정된 할당으로 잘라내야 합니다. 이 작업은 기술적으로 에필로그의 일부가 아닙니다. 예를 들어 다음 에필로그를 사용하여 이전에 사용한 프롤로그를 실행 취소할 수 있습니다.

    lea      RSP, -128[R13]
    ; epilogue proper starts here
    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

실제로 프레임 포인터를 사용하는 경우 두 단계로 RSP를 조정해야 할 유가 없으므로 다음 에필로그가 대신 사용됩니다.

    lea      RSP, fixed-allocation-size - 128[R13]
    pop      R13
    pop      R14
    pop      R15
    ret

이러한 형식은 에필로그에 유일하게 유효한 것입니다. 에필로그는 add RSP,constant 또는 lea RSP,constant[FPReg]에 이어 나오는 일련의 8바이트 레지스터 팝 0개 이상과 return 또는 jmp로 구성되어야 합니다. (에필로그에는 문의 하위 집합 jmp 만 허용됩니다. 하위 집합은 ModRM 모드 필드 값이 00인 ModRM 메모리 참조가 있는 문 클래스 jmp 입니다. ModRM mod 필드 값 01 또는 10과 함께 에필로그에 문을 사용할 jmp 수 없습니다. 허용되는 ModRM 참조에 대한 자세한 내용은 AMD x86-64 아키텍처 프로그래머의 수동 볼륨 3: 범용 및 시스템 지침의 표 A-15를 참조하세요.) 다른 코드는 표시할 수 없습니다. 특히 반환 값 로드를 포함해 에필로그 내에서는 아무 것도 예약할 수 없습니다.

프레임 포인터를 사용하지 않는 경우 에필로그는 add RSP,constant를 사용하여 스택의 고정 부분을 할당 취소해야 합니다. 에필로그는 lea RSP,constant[RSP]를 대신 사용해서는 안 됩니다. 이러한 제한이 있으므로 해제 코드가 에필로그를 검색할 때 인식할 패턴의 수가 더 적습니다.

이러한 규칙을 따르면 에필로그가 현재 실행되고 있는지를 해제 코드가 확인하고 에필로그의 나머지 부분 실행을 시뮬레이션하여 호출하는 함수의 컨텍스트를 다시 만들 수 있습니다.

참고 항목

x64 소프트웨어 규칙