x64에서 구조화된 예외 처리 및 C++ 예외 처리 코딩 규칙 및 동작에 대한 개요입니다. 예외 처리에 대한 일반적인 내용은 Microsoft C++의 예외 처리를 참조하세요.
예외 처리 및 디버거 지원을 위한 데이터 해제
예외를 처리할 때 비휘발성 레지스터를 복구하기 위해 비리프 함수에 정적 데이터를 이용해 주석이 달립니다. 일반적으로 "함수 해제 정보"라고 하는 이 데이터는 임의의 명령에서 함수를 제대로 해제하는 방법을 설명합니다. 이 데이터는 pdata 또는 프로시저 데이터로 저장됩니다. 이 데이터는 xdata, 즉 예외 처리 데이터를 참조합니다.
함수 언와인드 정보는 이어서 설명하는 여러 데이터 구조로 구성됩니다.
Intel APX (고급 성능 확장)를 지원하는 언와인드 정보는 Unwind V3 Preview Specification을 참조하세요.
구조체 RUNTIME_FUNCTION
테이블 기반 예외 처리에는 스택 공간을 할당하거나 다른 함수(예: 리프가 아닌 함수)를 호출하는 모든 함수에 대한 테이블 항목이 필요합니다. 함수 테이블 항목의 형식은 다음과 같습니다.
| 크기 | 값 |
|---|---|
| ULONG | 함수 시작 주소 |
| ULONG | 함수 끝 주소 |
| ULONG | 해제 정보 주소 |
RUNTIME_FUNCTION 구조체는 메모리에서 DWORD 정렬되어야 합니다. 모든 주소는 이미지를 기준으로 합니다. 즉, 함수 테이블 항목을 포함하는 이미지의 시작 주소에서 32비트 오프셋입니다. 이러한 항목은 정렬되어 PE32+ 이미지의 .pdata 섹션에 배치됩니다. 동적으로 생성된 함수 [JIT 컴파일러]의 경우 이러한 함수를 지원하는 런타임은 이 정보를 사용 RtlInstallFunctionTableCallback 하거나 RtlAddFunctionTable 운영 체제에 제공해야 합니다. 이렇게 하지 않으면 프로세스의 신뢰할 수 없는 예외 처리 및 디버깅이 발생합니다.
구조체 UNWIND_INFO
해제 데이터 정보 구조는 함수가 스택 포인터에 미치는 영향과 비휘발성 레지스터가 스택에 저장되는 위치를 기록합니다.
| 크기 | 값 |
|---|---|
| UBYTE: 3 | 버전 |
| UBYTE: 5 | 플래그 |
| UBYTE | 프롤로그 크기 |
| UBYTE | 해제 코드 개수 |
| UBYTE: 4 | 프레임 레지스터 |
| UBYTE: 4 | 프레임 레지스터 오프셋(스케일 조정된) |
| USHORT * 엔 | 언와인드 코드 배열 |
| 변수 | 다음 형식 (1) 또는 (2) 중 하나를 사용할 수 있음 |
(1) 예외 처리기
| 크기 | 값 |
|---|---|
| ULONG | 예외 처리기의 주소 |
| 변수 | 언어별 처리기 데이터(선택 사항) |
(2) 체인 언와인드 정보
| 크기 | 값 |
|---|---|
| ULONG | 함수 시작 주소 |
| ULONG | 함수 끝 주소 |
| ULONG | 해제 정보 주소 |
UNWIND_INFO 구조체는 메모리에서 DWORD 정렬되어야 합니다. 각 필드의 의미는 다음과 같습니다.
버전
해제 데이터의 버전 번호(현재 1)입니다.
깃발
현재 세 개의 플래그가 정의되어 있습니다.
깃발 설명 UNW_FLAG_EHANDLER함수에는 운영 체제에서 예외 상태를 검사하고 잠재적으로 처리하기 위해 호출하는 예외 처리기가 있습니다. C __try절과 같은 언어 기능은 이러한 처리기를 등록합니다.UNW_FLAG_UHANDLER함수에는 운영 체제가 스택을 언와인드할 때 호출하는 종료 핸들러가 있습니다. 이 처리기는 예외 안전 코드에서 함수에 의해 할당된 리소스를 해제할 수 있습니다. 로컬 C++ 개체 소멸자 및 C __finally절과 같은 언어 기능은 이러한 종료 처리기를 등록합니다.UNW_FLAG_CHAININFO이 해제 정보 구조는 프로시저의 기본 구조가 아닙니다. 대신 체인된 언와인드 정보 항목은 이전 RUNTIME_FUNCTION항목의 내용입니다. 자세한 내용은 연결된 해제 정보 구조체를 참조하세요. 이 플래그가 설정되면UNW_FLAG_EHANDLER및UNW_FLAG_UHANDLER플래그를 해제해야 합니다. 또한 프레임 레지스터 및 고정 스택 할당 필드의 값은 기본 해제 정보의 값과 같아야 합니다.프롤로그 크기
함수 프롤로그의 길이(바이트)입니다.
해제 코드 개수
해제 코드 배열의 슬롯 수입니다. 같은
UWOP_SAVE_NONVOL일부 해제 코드는 배열에 둘 이상의 슬롯이 필요합니다.프레임 레지스터
0이 아닌 경우 함수는 FP(프레임 포인터)를 사용하고 이 필드는 노드의 작업 정보 필드에 대해 동일한 인코딩을 사용하여 프레임 포인터로 사용되는 비휘발성 레지스터의
UNWIND_CODE수입니다.프레임 레지스터 오프셋(스케일링됨)
이 필드는
RSP레지스터 값과 선택한 FP(프레임 포인터) 레지스터 값 사이의 스케일된 오프셋입니다. 선택한 FP 레지스터는 + 16 * 이 숫자로RSP설정되므로 0에서 240까지의 오프셋을 사용할 수 있습니다. 이 오프셋은 FP 레지스터를 동적 스택 프레임에 대한 로컬 스택 할당의 중간에 가리키므로 더 짧은 지침을 통해 코드 밀도를 높일 수 있습니다. (즉, 더 많은 명령이 부호 있는 8비트 오프셋 형식을 사용할 수 있음)해제 코드 배열
비휘발성 레지스터 및
RSP에 대한 프롤로그의 효과를 설명하는 항목의 배열입니다. 개별 항목의 의미는 언와인드 작업 코드 섹션을 참조하세요. 적절한 데이터 정렬을 유지하기 위해 이 배열에는 항상 짝수의 항목이 포함되며 최종 항목은 사용되지 않을 수 있습니다. 이 경우 배열은 해제 코드 필드 수에 지정된 것보다 더 깁니다.예외 처리기의 주소
플래그가 명확하고 플래그
UNW_FLAG_CHAININFOUNW_FLAG_EHANDLER중 하나이거나UNW_FLAG_UHANDLER설정된 경우 함수의 언어별 예외 또는 종료 처리기에 대한 이미지 상대 포인터입니다.언어별 처리기 데이터
함수의 언어별 예외 처리기 데이터입니다. 이 데이터의 형식은 지정되지 않고 사용 중인 특정 예외 처리기에 의해 전적으로 결정됩니다.
연결된 해제 정보
플래그
UNW_FLAG_CHAININFO가 설정된 경우UNWIND_INFO구조체는UWORD세 개로 끝납니다. 이러한UWORD는 체인 언와인드의 기능에 대한RUNTIME_FUNCTION정보를 나타냅니다.
구조체 UNWIND_CODE
해제 코드 배열을 사용하여 비휘발성 레지스터와 RSP에 영향을 주는 프롤로그의 작업 순서를 기록합니다. 각 코드 항목의 형식은 다음과 같습니다.
| 크기 | 값 |
|---|---|
| UBYTE | 프롤로그의 오프셋 |
| UBYTE: 4 | 언와인드 연산 코드 |
| UBYTE: 4 | 작업 정보 |
배열은 프롤로그에서 오프셋의 내림차순으로 정렬됩니다.
프롤로그의 오프셋
프롤로그의 시작부터 이 작업을 수행하는 명령의 끝까지의 오프셋에 1을 더한 값입니다(즉, 다음 명령의 시작 오프셋).
언와인드 연산 코드
특정 연산 코드는 로컬 스택 프레임에 있는 값에 대한 부호 없는 오프셋을 필요로 합니다. 이 오프셋은 시작, 즉 고정 스택 할당의 가장 아래 주소에서 온 것입니다.
UNWIND_INFO의 프레임 레지스터 필드가 0인 경우, 이 오프셋은 RSP부터입니다. 프레임 레지스터 필드가 0이 아닌 경우, 이 오프셋은 FP 레지스터가 설정된 시점에 RSP가 위치해 있던 지점을 기준으로 합니다. 이는 FP 레지스터에서 FP 레지스터 오프셋(UNWIND_INFO의 스케일된 프레임 레지스터 오프셋 * 16)을 뺀 값과 같습니다. FP 레지스터가 사용되는 경우, 프롤로그에서 FP 레지스터가 설정된 후에만 오프셋을 사용하는 해제 코드를 사용할 수 있습니다.
UWOP_SAVE_XMM128 및 UWOP_SAVE_XMM128_FAR을 제외한 모든 opcode에 대해 오프셋은 항상 8의 배수입니다. 왜냐하면 모든 대상 스택 값은 8바이트 경계에 저장되기 때문입니다(스택 자체는 항상 16바이트로 정렬됨). 짧은 오프셋(512K 미만)을 사용하는 작업 코드의 경우 이 코드의 노드에서 마지막 USHORT 은 오프셋을 8로 나눈 값을 보유합니다. 긴 오프셋(512K <= 오프셋 < 4GB)을 사용하는 작업 코드의 경우 이 코드의 마지막 두 USHORT 노드는 오프셋(little-endian 형식)을 유지합니다.
opcode UWOP_SAVE_XMM128 및 UWOP_SAVE_XMM128_FAR의 경우, 모든 128비트 XMM 연산은 16바이트 경계에 정렬된 메모리에서 수행되어야 하므로 오프셋은 항상 16의 배수입니다. 따라서 배율 16은 1M 미만의 오프셋을 허용하는 UWOP_SAVE_XMM128에 사용됩니다.
해제 연산 코드는 다음 값 중 하나입니다.
UWOP_PUSH_NONVOL(0) 1 노드8씩 감소하여 비휘발성 정수 레지스터를 푸시합니다
RSP. 작업 정보는 레지스터의 번호입니다.UWOP_PUSH_NONVOL에필로그에 대한 제약 조건으로 인해 해제 코드가 프롤로그에는 처음에 표시되고, 이에 상응하여 해제 코드 배열에서는 마지막에 표시되어야 합니다. 이 상대 순서 지정은UWOP_PUSH_MACHFRAME을 제외한 다른 모든 해제 코드에 적용됩니다.UWOP_ALLOC_LARGE(1) 2 또는 3 노드스택에서 큰 크기의 영역을 할당합니다. 두 가지 형태가 있습니다. 작업 정보가 0인 경우 할당의 크기를 8로 나눈 값이 다음 슬롯에 기록되어 최대 512K-8까지 할당할 수 있습니다. 작업 정보가 1이면, 조정되지 않은 할당 크기는 리틀-엔디언 형식으로 다음 두 슬롯에 기록되며, 이로써 최대 4GB에서 8바이트를 뺀 값까지 할당할 수 있습니다.
UWOP_ALLOC_SMALL(2) 1 노드스택에서 작은 크기의 영역을 할당합니다. 할당 크기는 작업 정보 필드 * 8 + 8이며 8에서 128 바이트까지의 할당을 허용합니다.
스택 할당에 대한 해제 코드는 항상 가능한 한 가장 짧은 인코딩을 사용해야 합니다.
할당 크기 해제 코드 8~128바이트 UWOP_ALLOC_SMALL136~512K-8바이트 UWOP_ALLOC_LARGE, 작업 정보 = 0512K ~ 4G-8바이트 UWOP_ALLOC_LARGE, 작업 정보 = 1UWOP_SET_FPREG(3) 1 노드레지스터를 현재
RSP의 특정 오프셋 값으로 설정하여 프레임 포인터 레지스터를 설정합니다. 오프셋은 * 16의 프레임 레지스터 오프셋(배율) 필드UNWIND_INFO와 같으므로 0에서 240까지의 오프셋을 허용합니다. 오프셋을 사용하면 고정 스택 할당의 중간을 가리키는 프레임 포인터를 설정할 수 있습니다. 이로써 더 많은 액세스를 통해 짧은 명령 형식을 사용하게 되어 코드 밀도에 도움이 됩니다. 작업 정보 필드는 예약되어 있으므로 사용할 수 없습니다.UWOP_SAVE_NONVOL(4) 2 노드PUSH 대신 MOV를 사용하여 스택에 비휘발성 정수 레지스터를 저장합니다. 이 코드는 주로 shrink-wrapping에 사용되며, 여기서 비휘발성 레지스터가 스택에서 이전에 할당된 위치에 저장됩니다. 작업 정보는 레지스터의 번호입니다. 참고 사항에 설명된 대로, 8배로 조정된 스택 오프셋은 다음 언와인드 작업 코드 슬롯에 기록됩니다.
UWOP_SAVE_NONVOL_FAR(5) 3 노드PUSH 대신 MOV를 사용하여 긴 오프셋을 가진 스택에 비휘발성 정수 레지스터를 저장합니다. 이 코드는 주로 shrink-wrapping에 사용되며, 여기서 비휘발성 레지스터가 스택에서 이전에 할당된 위치에 저장됩니다. 작업 정보는 레지스터의 번호입니다. 위의 참고 사항에 설명된 대로, 조정되지 않은 스택 오프셋은 다음 두 개의 언바인드 연산 코드 슬롯에 기록됩니다.
UWOP_SAVE_XMM128(8) 2 노드스택에 비휘발성
XMM레지스터의 128비트를 모두 저장합니다. 작업 정보는 레지스터의 번호입니다. 16으로 비율 크기 조정된 스택 오프셋은 다음 슬롯에 기록됩니다.UWOP_SAVE_XMM128_FAR(9) 3 노드긴 오프셋을 사용하여 스택에 비휘발성
XMM레지스터의 128비트를 모두 저장합니다. 작업 정보는 레지스터의 번호입니다. 스케일링되지 않은 스택 오프셋은 다음 두 슬롯에 기록됩니다.UWOP_PUSH_MACHFRAME(10) 1 노드컴퓨터 프레임을 푸시합니다. 이 해제 코드는 하드웨어 인터럽트 또는 예외의 영향을 기록합니다. 두 가지 형식이 있습니다. 값이 0이면 하드웨어가 스택에서 다음과 같은 프레임을 푸시했음을 나타냅니다.
위치 값 RSP+32SSRSP+24오래 된 RSPRSP+16EFLAGSRSP+8CSRSPRIP값이 1이면 하드웨어가 스택에서 다음과 같은 프레임을 푸시했음을 나타냅니다.
위치 값 RSP+40SSRSP+32오래 된 RSPRSP+24EFLAGSRSP+16CSRSP+8RIPRSP오류 코드 이 해제 코드는 실제로 실행되지 않는 더미 프롤로그에 항상 표시되지만, 대신 중단 루틴의 실제 진입점 앞에 표시되며 컴퓨터 프레임의 푸시를 시뮬레이션하는 위치를 제공하기 위해서만 존재합니다.
UWOP_PUSH_MACHFRAME은 해당 시뮬레이션을 기록하여 컴퓨터가 개념적으로 해당 작업을 수행했음을 표시합니다.RIP스택 맨 위에서 반환 주소를 꺼내 Temp에 저장누르기
SS이전으로 푸시
RSP누르기
EFLAGS누르기
CS푸시 Temp
오류 코드 푸시(op 정보가 1인 경우)
시뮬레이션된
UWOP_PUSH_MACHFRAME연산은RSP를 40(op info가 0인 경우) 또는 48(op info가 1인 경우)만큼 감소시킵니다.
작업 정보
작업 정보 비트의 의미는 연산 코드에 따라 달라집니다. 범용(정수) 레지스터를 인코딩하려면 다음 매핑이 사용됩니다.
| 비트 | 등록 |
|---|---|
| 0 | RAX |
| 1 | RCX |
| 2 | RDX |
| 3 | RBX |
| 4 | RSP |
| 5 | RBP |
| 6 | RSI |
| 7 | RDI |
| 8~15 |
R8 - R15 |
연결된 해제 정보 구조체
플래그가 UNW_FLAG_CHAININFO 설정된 경우 해제 정보 구조는 보조 구조이고 공유 예외 처리기/체인 정보 주소 필드에는 기본 해제 정보가 포함됩니다. 이 샘플 코드는 unwindInfo가 UNW_FLAG_CHAININFO 플래그가 설정된 구조체라고 가정하고 주 해제 정보를 가져옵니다.
PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);
연결된 정보는 두 가지 상황에서 유용합니다. 첫째, 연속되지 않은 코드 세그먼트에 사용할 수 있습니다. 연결된 정보를 사용하면 기본 해제 정보에서 해제 코드 배열을 복제할 필요가 없으므로 필요한 해제 정보의 크기를 줄일 수 있습니다.
또한 연결된 정보를 사용하여 휘발성 레지스터 저장을 그룹화할 수 있습니다. 컴파일러는 함수 항목 프롤로그 외부에 있을 때까지 일부 휘발성 레지스터 저장을 지연할 수 있습니다. 그룹화된 코드 이전의 함수 부분에 대한 기본 언와인드 정보를 두고, 프롤로그 크기를 0이 아닌 값으로 한 체인된 정보를 설정하여 이를 기록할 수 있습니다. 여기서 체인된 정보의 언와인드 코드는 비휘발성 레지스터 저장을 반영합니다. 이 경우 해제 코드는 모두 .의 UWOP_SAVE_NONVOL인스턴스입니다.
PUSH를 사용해 비휘발성 레지스터를 저장하거나, 추가 고정 스택 할당을 사용해 RSP 레지스터를 수정하는 그룹은 지원되지 않습니다.
UNW_FLAG_CHAININFO이 설정된 UNWIND_INFO 항목은 UNW_FLAG_CHAININFO도 설정된 UNWIND_INFO 항목이 있는 RUNTIME_FUNCTION 항목을 포함할 수 있으며, 이를 다중 축소 래핑이라고도 합니다. 결국 체인된 언와인드 정보 포인터는 UNW_FLAG_CHAININFO가 클리어된 UNWIND_INFO 항목에 도달합니다. 이 항목은 실제 프로시저 진입점을 가리키는 기본 UNWIND_INFO 항목입니다.
언와인드 프로시저
해제 코드 배열은 내림차순으로 정렬됩니다. 예외가 발생하면 운영 체제는 컨텍스트 레코드에 전체 컨텍스트를 저장합니다. 그런 다음 예외 디스패치 논리가 호출되어 예외 처리기를 찾기 위해 다음과 같은 단계를 반복적으로 실행합니다.
컨텍스트 레코드에 저장된 현재
RIP를 사용하여 현재 함수를 설명하는RUNTIME_FUNCTION테이블 항목(또는 연결된UNWIND_INFO항목의 경우 함수 부분)을 검색합니다.검색에서 함수 테이블 항목을 찾을 수 없는 경우 코드는 리프 함수의 일부로 간주되며
RSP반환 포인터의 주소를 직접 지정합니다. [RSP]의 반환 포인터는 업데이트된 컨텍스트에 저장되고 시뮬레이션된RSP포인터는 8씩 증가하며 1단계는 반복됩니다.검색에서 함수 테이블 항목을
RIP찾는 경우 예외 처리기가 다룰 수 있는 코드에서 a) 에필로그, b) 프롤로그 또는 c)의 세 지역 내에 있을 수 있습니다.사례 a) 에필로그 내에 있는 경우
RIP컨트롤이 함수를 종료합니다. 이 함수에 대해 이 예외와 연결된 예외 처리기는 있을 수 없습니다. 에필로그의 효과는 호출자 함수의 컨텍스트를 계속 계산해야 합니다.RIP이 에필로그에 속하는지 확인하기 위해RIP부터의 코드 스트림을 검사합니다. 해당 코드 스트림이 합법적인 에필로그의 후행 부분과 일치하는 경우 에필로그에 있습니다. 에필로그의 나머지 부분은 시뮬레이션되며 각 명령이 처리될 때 컨텍스트 레코드가 업데이트됩니다. 이 처리 후 1단계가 반복됩니다.- 사례 b) 프롤로그 내에 있는 경우
RIP컨트롤이 함수에 들어가지 않았습니다. 이 함수에 대해 이 예외와 연결된 예외 처리기는 있을 수 없습니다. 프롤로그의 효과를 실행 취소하여 호출자 함수의 컨텍스트를 계산해야 합니다. 함수 시작 지점에서RIP까지의 거리가 언와인드 정보에 인코딩되어 있는 프롤로그 크기보다 작거나 같으면RIP는 프롤로그 내에 있습니다. 언와인더는 함수 시작점 기준RIP의 오프셋보다 작거나 같은 오프셋을 가진 첫 번째 항목을 찾기 위해 언와인드 코드 배열을 순방향으로 스캔한 다음, 언와인드 코드 배열에 남아 있는 모든 항목의 효과를 되돌립니다. 그런 다음 1단계를 반복합니다.
- 사례 b) 프롤로그 내에 있는 경우
사례 c) 프롤로그 또는 에필로그 내에 있지 않고 함수에 예외 처리기(
RIP설정됨)가 있는 경우UNW_FLAG_EHANDLER언어별 처리기가 호출됩니다. 처리기는 해당 데이터를 스캔하고 필터 함수를 적절하게 호출합니다. 언어별 처리기는 예외가 처리되었음 또는 검색이 계속됨을 반환합니다. 또한 역추적을 직접 시작할 수 있습니다.
언어별 처리기가 처리된 상태를 반환하는 경우 원래 컨텍스트 레코드를 사용하여 실행을 계속합니다.
언어별 처리기가 없거나 처리기가 "계속 검색" 상태를 반환하는 경우 컨텍스트 레코드를 호출자의 상태로 해제해야 합니다. 언와인더는 언와인드 코드 배열의 각 요소가 미치는 효과를 되돌립니다. 그런 다음 1단계를 반복합니다.
체인드 언와인드 정보를 포함하는 경우에도 이 기본 단계를 계속 따릅니다. 유일한 차이점은 해제 코드 배열을 탐색하여 프롤로그의 효과를 해제하는 동안 프로세스가 배열의 끝에 도달하면 부모 해제 정보에 연결되고 이 배열에 있는 전체 해제 코드 배열을 안내한다는 것입니다. 이 연결은 UNW_CHAINED_INFO 플래그가 없는 언와인드 정보에 도달할 때까지 계속되며, 그런 다음 해당 언와인드 코드 배열 순회를 마칩니다.
해제 데이터의 가장 작은 집합은 8바이트입니다. 이러한 집합은 128바이트 이하의 스택만 할당하고 하나의 비휘발성 레지스터를 저장할 수 있는 함수를 나타냅니다. 또한 해제 코드 없이 길이가 0인 프롤로그에 대한 연결된 해제 정보 구조의 크기이기도 합니다.
언어별 처리기
UNWIND_INFO 구조체는 UNW_FLAG_EHANDLER 또는 UNW_FLAG_UHANDLER 플래그가 설정된 경우 언어별 핸들러의 상대 주소를 제공합니다. 이전 섹션에서 설명한 대로 예외 처리기 또는 해제 프로세스에 대한 검색은 언어별 처리기를 호출합니다. 처리기는 다음 프로토타입을 사용합니다.
typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN ULONG64 EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
ExceptionRecord는 표준 Win64 정의를 포함하는 예외 레코드에 대한 포인터를 제공합니다.
EstablisherFrame은 이 함수에 대한 고정 스택 할당의 기본 주소입니다.
ContextRecord는 예외가 발생한 시점의 예외 컨텍스트(예외 처리기의 경우) 또는 현재 “해제” 컨텍스트(종료 처리기의 경우)를 가리킵니다.
DispatcherContext는 이 함수에 대한 디스패처 컨텍스트를 가리킵니다. 다음과 같은 정의를 갖습니다.
typedef struct _DISPATCHER_CONTEXT {
ULONG64 ControlPc;
ULONG64 ImageBase;
PRUNTIME_FUNCTION FunctionEntry;
ULONG64 EstablisherFrame;
ULONG64 TargetIp;
PCONTEXT ContextRecord;
PEXCEPTION_ROUTINE LanguageHandler;
PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
ControlPc 는 이 함수 내의 RIP 값입니다. 이 값은 예외 주소이거나 제어가 설정 함수를 떠난 주소입니다.
RIP은(는) 제어가 이 함수 내부의 어떤 보호 구문 안에 있는지 확인하는 데 사용됩니다. 예를 들어 __try/__except 또는 __try/__finally에 대한 __try 블록 내부인지 확인합니다.
ImageBase 는 이 함수를 포함하는 모듈의 이미지 베이스(로드 주소)입니다. 함수 엔트리와 언와인드 정보에 사용되는 32비트 오프셋은 최종 주소를 얻기 위해 ImageBase에 더해야 합니다.
FunctionEntry는 이 함수의 함수 및 언와인드 정보에 대한 이미지 베이스 상대 주소를 포함하는 RUNTIME_FUNCTION 함수 항목에 대한 포인터를 제공합니다.
EstablisherFrame은 이 함수에 대한 고정 스택 할당의 기본 주소입니다.
TargetIp는 언와인드가 이어서 진행될 주소를 지정하는 선택적 명령어 주소를 제공합니다. EstablisherFrame이 지정되지 않은 경우 이 주소는 무시됩니다.
ContextRecord는 시스템 예외 디스패치/해제 코드에서 사용할 예외 컨텍스트를 가리킵니다.
LanguageHandler는 호출되는 언어별 언어 처리기 루틴을 가리킵니다.
HandlerData는 이 함수에 대한 언어별 처리기 데이터를 가리킵니다.
MASM 언와인드 도우미
적절한 어셈블리 루틴을 작성하려면 실제 어셈블리 지침과 함께 의사 연산 집합을 사용합니다. 이러한 의사 연산은 적절한 .pdata 및 .xdata을 생성합니다. 또한 가장 일반적인 용도로 이러한 의사 연산의 사용을 간소화하는 매크로 집합을 사용합니다.
원시 의사연산
| 의사 작업 | 설명 |
|---|---|
| PROC FRAME [:ehandler] | MASM이 함수의 구조적 예외 처리 언와인드 동작을 위해 .pdata에 함수 테이블 항목을, .xdata에 언와인드 정보를 생성하도록 합니다.
ehandler가 있는 경우 이 proc은 언어별 처리기로 .xdata에 입력됩니다.FRAME 특성을 사용하는 경우 뒤이어 .ENDPROLOG 지시문을 사용하십시오. 함수가 리프 함수( 함수 형식에 정의된 대로)인 경우 이러한 의사 연산의 나머지 부분과 마찬가지로 FRAME 특성은 필요하지 않습니다. |
| .PUSHREG 레지스터 | 프롤로그의 현재 오프셋을 사용하여 지정된 레지스터 번호에 대한 UWOP_PUSH_NONVOL 언와인드 코드 항목을 생성합니다.비휘발성 정수 레지스터에서만 사용합니다. 휘발성 레지스터를 푸시하는 경우에는 대신 .ALLOCSTACK 8을 사용하십시오. |
| . SETFRAME 레지스터, 오프셋 | 지정된 레지스터와 오프셋을 사용하여 되감기 정보에서 프레임 레지스터 필드와 오프셋을 설정합니다. 오프셋은 16의 배수여야 하며 240보다 작거나 같아야 합니다. 또한 이 지시문은 현재 프롤로그 오프셋을 사용하여 지정된 레지스터에 대한 UWOP_SET_FPREG 언와인드 코드 항목을 생성합니다. |
| . ALLOCSTACK 크기 | 프롤로그의 현재 오프셋에 대해 지정된 크기의 UWOP_ALLOC_SMALL 또는 UWOP_ALLOC_LARGE를 생성합니다.크기 피연산자는 8의 배수여야 합니다. |
| . SAVEREG 레지스터, 오프셋 | 현재 프롤로그 오프셋을 사용하여 지정된 레지스터와 오프셋에 대해 UWOP_SAVE_NONVOL 또는 UWOP_SAVE_NONVOL_FAR 언와인드 코드 항목을 생성합니다. MASM은 가장 효율적인 인코딩을 선택합니다.‘오프셋’은 양수여야 하며 8의 배수여야 합니다. 오프셋은 프로시저 프레임의 기준점을 기준으로 하며, 이 기준점은 일반적으로 RSP에 있고, 프레임 포인터를 사용하는 경우에는 스케일되지 않은 프레임 포인터입니다. |
| . SAVEXMM128 레지스터, 오프셋 | 현재 프롤로그 오프셋을 사용하여 지정된 XMM 레지스터와 오프셋에 대해 UWOP_SAVE_XMM128 또는 UWOP_SAVE_XMM128_FAR 언와인드 코드 항목을 생성합니다. MASM은 가장 효율적인 인코딩을 선택합니다.‘오프셋’은 양수여야 하며 16의 배수여야 합니다. 오프셋은 프로시저 프레임의 기준점을 기준으로 하며, 이 기준점은 일반적으로 RSP에 있고, 프레임 포인터를 사용하는 경우에는 스케일링되지 않은 프레임 포인터를 기준으로 합니다. |
| .PUSHFRAME [코드] |
UWOP_PUSH_MACHFRAME 해제 코드 항목을 생성합니다. 선택적 코드를 지정하는 경우 해제 코드 항목은 1의 한정자를 가져옵니다. 그러지 않으면 한정자 0이 지정됩니다. |
| . ENDPROLOG | 프롤로그 선언의 끝을 신호로 보냅니다. 함수의 처음 255바이트에서 발생해야 합니다. |
대부분의 opcode를 적절하게 사용하는 샘플 함수 프롤로그는 다음과 같습니다.
sample PROC FRAME
db 048h; emit a REX prefix, to enable hot-patching
push rbp
.pushreg rbp
sub rsp, 040h
.allocstack 040h
lea rbp, [rsp+020h]
.setframe rbp, 020h
movdqa [rbp], xmm7
.savexmm128 xmm7, 020h ;the offset is from the base of the frame
;not the scaled offset of the frame
mov [rbp+018h], rsi
.savereg rsi, 038h
mov [rsp+010h], rdi
.savereg rdi, 010h ; you can still use RSP as the base of the frame
; or any other register you choose
.endprolog
; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer
sub rsp, 060h
; we can unwind from the next AV because of the frame pointer
mov rax, 0
mov rax, [rax] ; AV!
; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5
movdqa xmm7, [rbp]
mov rsi, [rbp+018h]
mov rdi, [rbp-010h]
; Here's the official epilog
lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
pop rbp
ret
sample ENDP
에필로그 예제에 대한 자세한 내용은 x64 프롤로그 및 에필로그의 에필로그 코드를 참조하세요.
MASM 매크로
원시 의사 연산의 사용을 간소화하려면 ksamd64.inc에 정의된 매크로 집합을 사용하세요. 이러한 매크로는 일반적인 프로시저 프롤로그 및 에필로그를 만드는 데 도움이 됩니다.
| 매크로 | 설명 |
|---|---|
| alloc_stack(n) - 스택 할당 함수(n) |
sub rsp, n를 사용하여 n 바이트의 스택 프레임을 할당하고 적절한 언와인드 정보(.allocstack n)를 생성한다. |
| save_reg reg, loc | 비휘발성 레지스터 reg를 스택의 RSP 오프셋 loc에 저장하고, 적절한 언와인드 정보(.savereg reg, loc)를 생성합니다. |
| push_reg reg | 스택에 비휘발성 레지스터 reg를 푸시하고, 적절한 언와인드 정보(.pushreg reg)를 생성한다. |
| rex_push_reg reg | 2바이트 푸시를 사용하여 스택에 비휘발성 레지스터를 저장하고 적절한 해제 정보(.pushreg reg)를 내보낸다. 푸시가 함수의 첫 번째 명령인 경우 이 매크로를 사용하여 함수가 핫 패치 가능하도록 합니다. |
| save_xmm128 reg, loc | 비휘발성 XMM 레지스터 reg를 스택의 RSP 오프셋 loc에 저장하고 적절한 언와인드 정보(.savexmm128 reg, loc)를 생성합니다. |
| set_frame reg, 오프셋 | 프레임 레지스터 reg를 RSP + offset(mov 또는 lea를 사용하여)으로 설정하고, 적절한 언와인드 정보(.set_frame reg, offset)를 출력합니다. |
| push_eflags (플래그 푸시) |
pushfq 명령을 사용하여 EFLAGS를 푸시하고, 적절한 언와인드 정보(.alloc_stack 8)를 내보낸다. |
다음은 매크로를 적절하게 사용하는 샘플 함수 프롤로그입니다.
sampleFrame struct
Fill dq ?; fill to 8 mod 16
SavedRdi dq ?; Saved Register RDI
SavedRsi dq ?; Saved Register RSI
sampleFrame ends
sample2 PROC FRAME
alloc_stack(sizeof sampleFrame)
save_reg rdi, sampleFrame.SavedRdi
save_reg rsi, sampleFrame.SavedRsi
.end_prolog
; function body
mov rsi, sampleFrame.SavedRsi[rsp]
mov rdi, sampleFrame.SavedRdi[rsp]
; Here's the official epilog
add rsp, (sizeof sampleFrame)
ret
sample2 ENDP
C의 데이터 정의 풀기
다음은 언와인드 데이터에 대한 C 설명입니다.
typedef enum _UNWIND_OP_CODES {
UWOP_PUSH_NONVOL = 0, /* info == register number */
UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */
UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */
UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */
UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;
typedef unsigned char UBYTE;
typedef union _UNWIND_CODE {
struct {
UBYTE CodeOffset;
UBYTE UnwindOp : 4;
UBYTE OpInfo : 4;
};
USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
#define UNW_FLAG_EHANDLER 0x01
#define UNW_FLAG_UHANDLER 0x02
#define UNW_FLAG_CHAININFO 0x04
typedef struct _UNWIND_INFO {
UBYTE Version : 3;
UBYTE Flags : 5;
UBYTE SizeOfProlog;
UBYTE CountOfCodes;
UBYTE FrameRegister : 4;
UBYTE FrameOffset : 4;
UNWIND_CODE UnwindCode[1];
/* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
* union {
* OPTIONAL ULONG ExceptionHandler;
* OPTIONAL ULONG FunctionEntry;
* };
* OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;
typedef struct _RUNTIME_FUNCTION {
ULONG BeginAddress;
ULONG EndAddress;
ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
#define GetUnwindCodeEntry(info, index) \
((info)->UnwindCode[index])
#define GetLanguageSpecificDataPtr(info) \
((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))
#define GetExceptionHandler(base, info) \
((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))
#define GetChainedFunctionEntry(base, info) \
((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))
#define GetExceptionDataPtr(info) \
((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))