다음을 통해 공유


ARM 예외 처리

WINDOWS on ARM은 비동기 하드웨어 생성 예외 및 동기 소프트웨어 생성 예외에 대해 동일한 구조적 예외 처리 메커니즘을 사용합니다. 언어별 예외 처리기가 언어 도우미 함수를 사용하여 Windows의 구조적 예외 처리를 기반으로 작성됩니다. 이 문서에서는 WINDOWS on ARM의 예외 처리와 Microsoft ARM 어셈블러와 MSVC 컴파일러가 생성하는 언어 도우미에 대해 설명합니다.

ARM 예외 처리

Windows on ARM에서는 해제 코드를 사용하여 구조적 예외 처리(SEH) 중의 스택 해제를 제어합니다. 해제 코드는 실행 가능 이미지의 .xdata 섹션에 저장되는 바이트 시퀀스로, 이러한 코드는 함수 프롤로그 및 에필로그 코드의 작업을 추상적인 방식으로 설명합니다. 처리기는 호출자의 스택 프레임을 해제할 때 함수 프롤로그의 효과를 실행 취소하는 데 사용합니다.

ARM EABI(포함된 애플리케이션 이진 인터페이스)는 해제 코드를 사용하는 예외 해제 모델을 지정합니다. 모델은 Windows에서 SEH 해제에 충분하지 않습니다. 프로세서가 함수의 프롤로그 또는 에필로그 중간에 있는 비동기 사례를 처리해야 합니다. 또한 Windows에서는 해제 제어를 함수 수준 해제와 언어별 범위 해제로 구분하는데, 이는 ARM EABI에서 통합됩니다. 따라서 Windows on ARM에서는 해제 데이터 및 프로시저에 대해 추가 세부 정보를 지정합니다.

가정

Windows on ARM의 실행 가능 이미지는 PE(이식 가능한 실행 파일) 형식을 사용합니다. 자세한 내용은 PE 형식을 참조하세요. 예외 처리 정보는 이미지의 .pdata.xdata 섹션에 저장됩니다.

예외 처리 메커니즘은 Windows on ARM용 ABI를 따르는 코드에 대해 다음과 같은 특정 사항을 가정합니다.

  • 함수 본문 내에서 예외가 발생하면 처리기는 프롤로그 작업을 실행 취소하거나 에필로그 작업을 앞으로 수행할 수 있습니다. 두 작업에서 모두 같은 결과가 생성됩니다.

  • 프롤로그와 에필로그는 서로 미러링되는 경향이 있으므로 이 기능을 사용하여 해제를 설명하는 데 필요한 메타데이터의 크기를 줄일 수 있습니다.

  • 함수는 비교적 크기가 작은 경우가 많습니다. 여러 최적화는 데이터의 효율적인 압축을 위해 이 관찰을 사용합니다.

  • 에필로그에 대해 조건을 적용하는 경우 에필로그의 각 명령에 조건이 동일하게 적용됩니다.

  • 프롤로그가 SP(스택 포인터)를 다른 레지스터에 저장하는 경우 해당 레지스터는 함수 전체에서 변경되지 기본 변경되지 않아야 하므로 언제든지 원래 SP가 복구될 수 있습니다.

  • SP를 다른 레지스터에 저장하는 경우가 아니면 모든 SP 조작은 반드시 프롤로그 및 에필로그 내에서만 수행해야 합니다.

  • 스택 프레임을 해제하려면 다음 작업을 수행해야 합니다.

    • 4바이트 증분 단위로 r13(SP)을 조정합니다.

    • 하나 이상의 정수 레지스터를 표시합니다.

    • 하나 이상의 VFP(가상 부동 소수점) 레지스터를 표시합니다.

    • 임의의 레지스터 값을 r13(SP)에 복사합니다.

    • 간단한 감소 후 작업을 사용하여 스택에서 SP를 로드합니다.

    • 적절하게 정의된 몇 가지 프레임 유형 중 하나를 구문 분석합니다.

.pdata 레코드

PE 형식 이미지의 .pdata 레코드는 모든 스택 조작 함수를 설명하는 고정 길이 항목의 정렬된 배열입니다. 리프 함수(다른 함수를 호출하지 않는 함수)는 스택을 조작하지 않을 때 레코드가 필요하지 .pdata 않습니다. 즉, 로컬 스토리지가 필요하지 않으며 비휘발성 레지스터를 저장하거나 복원하지 않아도 됩니다. 공간을 절약하려면 이러한 함수의 레코드를 .pdata 섹션에서 생략할 수 있습니다. 이러한 함수 중 하나에서 해제 작업을 수행하는 경우 LR(링크 레지스터)의 반환 주소를 PC(프로그램 카운터)에 복사하여 호출자로 이동하는 작업만 가능합니다.

ARM의 모든 .pdata 레코드 길이는 8바이트입니다. 일반적인 레코드 형식에서는 아래 테이블에 나와 있는 것처럼 처음 32비트 단어에 함수 시작의 RVA(상대 가상 주소)가 배치되고 가변 길이 .xdata 블록에 대한 포인터 또는 정식 함수 해제 시퀀스를 설명하는 압축된 단어를 포함하는 두 번째 단어가 그 뒤에 붙습니다.

단어 오프셋 Bits 목적
0 0-31 Function Start RVA는 함수 시작 부분의 32비트 RVA입니다. 함수에 Thumb 코드가 포함되어 있으면 이 주소의 하위 비트를 설정해야 합니다.
1 0-1 Flag는 두 번째 .pdata 단어의 나머지 30비트를 해석하는 방법을 나타내는 2비트 필드입니다. Flag가 0이면 나머지 비트는 ‘예외 정보 RVA’가 됩니다(하위 2비트는 암시적으로 0이 됨). Flag가 0이 아니면 나머지 비트는 ‘압축된 해제 데이터’ 구조체가 됩니다.
1 2-31 예외 정보 RVA 또는 압축된 해제 데이터입니다.

‘예외 정보 RVA’는 .xdata 섹션에 저장되는 가변 길이 예외 정보 구조체의 주소입니다. 이 데이터는 4비트 단위로 정렬되어야 합니다.

압축된 해제 데이터는 함수에서 해제하기 위해 수행해야 하는 작업의 압축된 설명으로, 정규형을 취합니다. 이 경우에는 .xdata 레코드가 필요하지 않습니다.

압축된 해제 데이터

프롤로그 및 에필로그가 아래에서 설명하는 정규형을 따르는 함수의 경우에는 압축된 해제 데이터를 사용할 수 있습니다. 레코드의 필요성을 .xdata 없애고 해제 데이터를 제공하는 데 필요한 공간을 크게 줄입니다. 정식 프롤로그 및 에필로그는 예외 처리기가 필요하지 않은 간단한 함수의 일반적인 요구 사항을 충족하도록 설계되었으며 표준 순서로 설정 및 해체 작업을 수행합니다.

아래 테이블에는 압축된 해제 데이터를 포함하는 .pdata 레코드의 형식이 나와 있습니다.

단어 오프셋 Bits 목적
0 0-31 Function Start RVA는 함수 시작 부분의 32비트 RVA입니다. 함수에 Thumb 코드가 포함되어 있으면 이 주소의 하위 비트를 설정해야 합니다.
1 0-1 Flag는 의미가 다음과 같은 2비트 필드입니다.

- 00 = 사용하지 않은 압축된 해제 데이터이며, 나머지 비트는 .xdata 레코드를 가리킵니다.
- 01 = 압축된 해제 데이터.
- 10 = 함수에 프롤로그가 없는 것으로 가정하는 경우의 압축된 해제 데이터입니다. 이러한 비트는 함수 시작 부분에 인접하지 않은 함수 조각을 설명하는 데 유용합니다.
- 11 = 예약되어 있습니다.
1 2-12 Function Length는 전체 함수의 길이(바이트 단위)를 2로 나눈 결과를 제공하는 11비트 필드입니다. 함수가 4,000바이트보다 크면 전체 .xdata 레코드를 대신 사용해야 합니다.
1 13-14 Ret는 함수 반환 방식을 나타내는 2비트 필드입니다.

- 00 = pop {pc}를 통해 반환됩니다. 이 경우 L 플래그 비트를 1로 설정해야 합니다.
- 01 = 16비트 분기를 사용하여 반환합니다.
- 10 = 32비트 분기를 사용하여 반환합니다.
- 11 = 에필로그가 전혀 없습니다. 프롤로그만 포함할 수 있으며 에필로그는 다른 위치에 있는 인접하지 않은 함수 조각을 설명하는 데 유용합니다.
1 15 H는 함수가 정수 매개 변수 레지스터(r0-r3)를 함수 시작 부분에서 푸시하는 방식으로 “호밍”하고 16바이트 스택을 반환 전에 할당 취소하는지 여부를 나타내는 1비트 플래그입니다. (0 = 주택 등록을 하지 않습니다., 1 = 주택 등록.)
1 16-18 Reg는 마지막으로 저장한 비휘발성 레지스터의 인덱스를 나타내는 3비트 필드입니다. R 비트가 0이면 정수 레지스터만 저장되며 해당 레지스터의 범위가 r4-rN이라고 가정합니다. 여기서 N은 4 + Reg입니다. R 비트가 1이면 부동 소수점 레지스터만 저장되며 해당 레지스터의 범위가 d8-dN이라고 가정합니다. 여기서 N은 8 + Reg입니다. R = 1이고 Reg = 7인 특수 조합은 저장된 레지스터가 없음을 나타냅니다.
1 19 R은 저장된 비휘발성 레지스터가 정수 레지스터인지(0), 아니면 부동 소수점 레지스터인지(1)를 나타내는 1비트 플래그입니다. R은 1로, Reg 필드는 7로 설정되어 있으면 아무런 비휘발성 레지스터도 푸시되지 않은 것입니다.
1 20 L은 함수가 Reg 필드로 표시되는 다른 레지스터와 함께 LR을 저장/복원하는지 여부를 나타내는 1비트 플래그입니다. (0 = 저장/복원하지 않음, 1 = 저장/복원)
1 21 C는 함수가 빠른 스택 워크를 위한 프레임 체인을 설정하는 추가 명령을 포함하는지(1), 아니면 포함하지 않는지(0)를 나타내는 1비트 플래그입니다. 이 비트가 설정되어 있으면 저장된 정수 비휘발성 레지스터 목록에 r11이 암시적으로 추가됩니다. C 플래그를 사용하는 경우에는 아래 제한을 참조하세요.
1 22-31 Stack Adjust는 이 함수에 대해 할당된 스택 바이트 수를 4로 나눈 값을 나타내는 10비트 필드입니다. 그러나 0x000-0x3F3 사이의 값만 직접 인코딩할 수 있습니다. 4,044바이트보다 많은 스택을 할당하는 함수는 전체 .xdata 레코드를 사용해야 합니다. Stack Adjust 필드의 값이 0x3F4 이상인 경우 하위 4개 비트에는 특수한 의미가 있습니다.

- 비트 0-1은 스택 조정(1-4)의 단어 수에서 1을 뺀 값을 나타냅니다.
- 프롤로그에서 이 조정을 푸시 작업에 결합한 경우 비트 2는 1로 설정됩니다.
- 에필로그에서 이 조정을 Pop 작업에 결합한 경우 비트 3은 1로 설정됩니다.

위의 인코딩에서는 중복 항목이 포함될 수 있으므로 다음 제한이 적용됩니다.

  • C 플래그가 1로 설정된 경우

    • 프레임 연결에는 L r11과 LR이 모두 필요하기 때문에 플래그도 1로 설정해야 합니다.

    • Reg에서 설명하는 레지스터 세트에 r11을 포함해서는 안 됩니다. 즉, r4-r11을 푸시하는 경우 C 플래그가 r11을 암시적으로 설명하므로 Reg는 r4-r10만 설명해야 합니다.

  • Ret 필드가 0으로 설정된 경우에는 L 플래그를 1로 설정해야 합니다.

이러한 제한을 위반하면 지원되지 않는 시퀀스가 생성됩니다.

아래에서는 설명을 위해 두 의사 플래그가 Stack Adjust에서 파생됩니다.

  • PF(“프롤로그 접기”)는 Stack Adjust가 0x3F4 이상임을 나타내며 비트 2가 설정됩니다.

  • EF(“에필로그 접기”)는 Stack Adjust가 0x3F4 이상임을 나타내며 비트 3이 설정됩니다.

정식 함수의 프롤로그는 명령을 5개까지 포함할 수 있습니다. 여기서 3a와 3b는 함께 사용할 수 없습니다.

지침 opcode가 있는 것으로 간주하는 경우 크기 Opcode 해제 코드
1 H==1 16 push {r0-r3} 04
2 C==1 또는 L==1 또는 R==0 또는 PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3a C==1 및 (R==1 및 PF==0) 16 mov r11,sp FB
3b C==1 및 (R==0 또는 PF==1) 32 add r11,sp,#xx FC
4 R==1 및 Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 및 PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

H 비트가 1로 설정된 경우 명령 1이 항상 포함됩니다.

프레임 연결을 설정하려는 경우 C 비트가 설정되어 있으면 명령 3a 또는 3b가 포함됩니다. 이 명령은 r11 및 LR 이외의 레지스터를 푸시하지 않는 경우 16비트 mov이고 그렇지 않으면 32비트 add입니다.

접히지 않은 조정을 지정하는 경우에는 명령 5가 명시적 스택 조정이 됩니다.

명령 2와 4는 푸시가 필요한지 여부에 따라 설정됩니다. 아래 테이블에는 C, L, R, PF 필드에 따라 저장되는 레지스터가 요약되어 있습니다. 모든 경우에 NReg + 4, EReg + 8, S는 (~Stack Adjust) 및 3과 같습니다.

C L R PF 푸시되는 정수 레지스터 푸시되는 VFP 레지스터
0 0 0 0 r4 - r*N* 없음
0 0 0 1 r*S* - r*N* 없음
0 0 1 0 없음 d8 - d*E*
0 0 1 1 r*S* - r3 d8 - d*E*
0 1 0 0 r4 - r**N, LR 없음
0 1 0 1 r*S* - r**N, LR 없음
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (잘못된 인코딩) (잘못된 인코딩)
1 0 0 1 (잘못된 인코딩) (잘못된 인코딩)
1 0 1 0 (잘못된 인코딩) (잘못된 인코딩)
1 0 1 1 (잘못된 인코딩) (잘못된 인코딩)
1 1 0 0 r4 - r**N, r11, LR 없음
1 1 0 1 r*S* - r**N, r11, LR 없음
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

정식 함수의 에필로그도 비슷한 형식을 따르지만 반대 방향이며 몇 가지 옵션이 추가로 사용됩니다. 에필로그의 길이는 최대 5개의 명령이며 해당 형식은 프롤로그 형식에 따라 엄격하게 규정됩니다.

지침 opcode가 있는 것으로 간주하는 경우 크기 Opcode
6 Stack Adjust!=0 및 EF==0 16/32 add sp,sp,#xx
7 R==1 및 Reg!=7 32 vpop {d8-dE}
8 C==1 또는 (L==1 및 (H==0 또는 Ret !=0)) 또는 R==0 또는 EF==1 16/32 pop {registers}
9a H==1 및 (L==0 또는 Ret!=0) 16 add sp,sp,#0x10
9b H==1 및 L==1 및 Ret==0 32 ldr pc,[sp],#0x14
10a Ret==1 16 bx reg
10b Ret==2 32 b address

접히지 않은 조정을 지정하는 경우에는 명령 6이 명시적 스택 조정이 됩니다. PFEF는 서로 독립적이므로 명령 5와 6 중 하나만 포함할 수도 있습니다.

지침 7과 8은 프롤로그와 동일한 논리를 사용하여 스택에서 복원되는 레지스터를 결정하지만, 다음 세 가지 변경 내용으로는 첫 번째 EF 변경 내용이 사용됩니다. 둘째, = 0과 H = 0이면 Ret LR이 레지스터 목록의 PFPC로 바뀌고 에필로그는 즉시 종료됩니다. 세 번째 변경 Ret 내용은 = 0 및 H = 1입니다. 그런 다음 LR이 레지스터 목록에서 생략되고 명령 9b에 의해 팝됩니다.

H가 설정되어 있으면 명령 9a 또는 9b가 포함됩니다. 명령 9a는 0이 아닌 경우 Ret 사용되며, 이는 10a 또는 10b의 존재도 의미합니다. L=1이면 명령 8의 일부로 LR이 튀어 나왔습니다. 명령 9b는 1이고 Ret 0이면 에필로그의 초기 끝을 나타내고 스택을 동시에 반환하고 조정하는 데 사용됩니다L.

에필로그가 아직 종료되지 않은 경우 값에 Ret따라 16비트 또는 32비트 분기를 나타내는 명령 10a 또는 10b가 있습니다.

.xdata 레코드

압축된 해제 형식만으로는 함수 해제를 설명하는 데 부족한 경우에는 가변 길이 .xdata 레코드를 만들어야 합니다. 이 레코드의 주소는 .pdata 레코드의 두 번째 단어에 저장됩니다. .xdata의 형식은 다음의 4개 섹션을 포함하는 압축된 가변 길이 단어 세트입니다.

  1. .xdata 구조의 전체 크기를 설명하고 주요 함수 데이터를 제공하는 단어 1~2개로 구성된 헤더입니다. 두 번째 단어는 에필로그 개수코드 단어 필드가 모두 0으로 설정된 경우에만 존재합니다. 해당 필드에 대한 설명이 아래 테이블에 나와 있습니다.

    Word Bits 목적
    0 0-17 Function Length는 함수의 전체 길이(바이트 단위)를 2로 나눈 결과를 제공하는 18비트 필드입니다. 함수가 512KB보다 크면 여러 .pdata.xdata 레코드를 사용하여 함수를 설명해야 합니다. 자세한 내용은 이 문서의 큰 함수 섹션을 참조하세요.
    0 18-19 ‘버전’은 나머지 .xdata의 버전을 설명하는 2비트 필드입니다. 현재는 버전 0만 정의되어 있으며 값 1~3은 예약됩니다.
    0 20 X는 예외 데이터가 있는지(1), 아니면 없는지(0)를 나타내는 1비트 필드입니다.
    0 21 E는 나중에 추가 범위 단어를 다시 사용해야 하도록 지정(0)하는 대신 단일 에필로그를 설명하는 정보를 헤더에 압축(1)함을 나타내는 1비트 필드입니다.
    0 22 F는 이 레코드가 함수 조각(1)을 설명하는지, 아니면 전체 함수(0)를 설명하는지를 나타내는 1비트 필드입니다. 조각은 프롤로그가 없고 모든 프롤로그 처리를 무시해야 한다는 것을 의미합니다.
    0 23-27 ‘에필로그 수’는 E 비트의 상태에 따라 두 가지 의미가 될 수 있는 5비트 필드입니다.

    - 0이면 E 이 필드는 섹션 2에 설명된 에필로그 범위의 총 개수입니다. 함수에 범위가 32개 이상 포함되는 경우에는 이 필드와 코드 단어 필드를 모두 0으로 설정하여 확장명 단어가 필요함을 나타내야 합니다.
    E가 1이면 이 필드는 에필로그만을 설명하는 첫 번째 해제 코드의 인덱스를 지정합니다.
    0 28-31 코드 단어는 섹션 4의 모든 해제 코드를 포함하는 데 필요한 32비트 단어의 수를 지정하는 4비트 필드입니다. 해제 코드 64바이트 이상에 대해 단어가 16개 이상 필요한 경우에는 이 필드와 에필로그 수 필드를 모두 0으로 설정하여 확장명 단어가 필요함을 나타내야 합니다.
    1 0-15 확장된 에필로그 수는 매우 많은 에필로그를 인코딩하기 위한 추가 공간을 제공하는 16비트 필드입니다. 첫 번째 헤더 단어의 에필로그 수코드 단어 필드가 모두 0으로 설정된 경우에만 이 필드가 들어 있는 확장명 단어가 포함됩니다.
    1 16-23 확장 코드 단어 는 비정상적으로 많은 수의 해제 코드 단어를 인코딩하기 위한 더 많은 공간을 제공하는 8비트 필드입니다. 첫 번째 헤더 단어의 에필로그 수코드 단어 필드가 모두 0으로 설정된 경우에만 이 필드가 들어 있는 확장명 단어가 포함됩니다.
    1 24-31 예약됨
  2. 헤더에서 E 비트가 0으로 설정된 경우 예외 데이터 다음에는 에필로그 범위에 대한 정보 목록이 있습니다. 이러한 범위는 단어에 하나로 압축되어 작은 시작 오프셋부터 차례로 저장됩니다. 각 범위에 포함되는 필드는 다음과 같습니다.

    Bits 목적
    0-17 에필로그 시작 오프셋은 함수 시작 위치를 기준으로 하는 에필로그 오프셋(바이트 단위)을 2로 나눈 결과를 설명하는 18비트 필드입니다.
    18-19 Res는 이후 확장을 위해 예약되는 2비트 필드입니다. 해당 값은 0이어야 합니다.
    20-23 조건은 에필로그가 실행되는 조건을 제공하는 4비트 필드입니다. 무조건 에필로그의 경우에는 이 필드를 "항상"을 나타내는 0xE로 설정해야 합니다. 에필로그는 완전 조건부 또는 완전 무조건이어야 하며 Thumb-2 모드에서는 에필로그가 IT opcode 다음의 첫 번째 명령으로 시작됩니다.
    24-31 에필로그 시작 인덱스는 이 에필로그를 설명하는 첫 번째 해제 코드의 바이트 인덱스를 나타내는 8비트 필드입니다.
  3. 에필로그 범위 목록 다음에는 해제 코드를 포함하는 바이트 배열이 옵니다. 이 문서의 해제 코드 섹션에서 해제 코드에 대해 자세히 설명합니다. 이 배열은 가장 가까운 전체 단어 경계 끝에 채워집니다. 바이트는 little endian 모드에서 직접 가져올 수 있도록 쉽게 little endian 순서로 저장됩니다.

  4. 헤더의 X 필드가 1이면 해제 코드 바이트 뒤에 예외 처리기 정보가 옵니다. 이 정보는 예외 처리기의 주소 바로 다음에 예외 처리기에 필요한 가변 길이 데이터의 양이 붙는 형식의 예외 처리기 RVA 하나로 구성됩니다.

.xdata 레코드는 처음 8바이트(그다음의 가변 크기 예외 데이터의 길이는 포함되지 않음)를 가져와 레코드의 전체 길이를 계산할 수 있도록 설계됩니다. 이 코드 조각은 레코드 크기를 계산합니다.

ULONG ComputeXdataSize(PULONG Xdata)
{
    ULONG Size;
    ULONG EpilogueScopes;
    ULONG UnwindWords;

    if ((Xdata[0] >> 23) != 0) {
        Size = 4;
        EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
        UnwindWords = (Xdata[0] >> 28) & 0x0f;
    } else {
        Size = 8;
        EpilogueScopes = Xdata[1] & 0xffff;
        UnwindWords = (Xdata[1] >> 16) & 0xff;
    }

    if (!(Xdata[0] & (1 << 21))) {
        Size += 4 * EpilogueScopes;
    }

    Size += 4 * UnwindWords;

    if (Xdata[0] & (1 << 20)) {
        Size += 4;  // Exception handler RVA
    }

    return Size;
}

프롤로그와 각 에필로그에 해제 코드로의 인덱스가 있지만 테이블은 프롤로그와 에필로그 간에 공유됩니다. 모두 동일한 해제 코드를 공유할 수 있는 것은 드문 일이 아닙니다. 이러한 경우를 위해 컴파일러 작성기를 최적화하는 것이 좋습니다. 지정할 수 있는 최대 인덱스는 255이므로 특정 함수에 대해 사용할 수 있는 총 해제 코드 수가 제한되기 때문입니다.

해제 코드

해제 코드 배열은 작업을 실행 취소해야 하는 순서대로 프롤로그의 결과를 실행 취소하는 방법을 정확히 설명하는 명령 시퀀스 풀입니다. 해제 코드는 바이트 문자열로 인코딩된 미니 명령 집합입니다. 실행이 완료되면 호출 함수에 대한 반환 주소는 LR 레지스터에 포함되며 모든 비휘발성 레지스터는 함수 호출 시의 값으로 복원됩니다.

예외가 함수 본문 내에서만 발생하며 프롤로그 또는 에필로그 내에서는 발생하지 않음이 보장된다면 해제 시퀀스는 하나만 있으면 됩니다. 그러나 Windows 해제 모델에서는 부분적으로 실행된 프롤로그 또는 에필로그 내에서 해제할 수 있는 기능이 필요합니다. 이 요구 사항을 충족하기 위해 프롤로그와 에필로그의 각 관련 opcode로의 명확한 일대일 매핑을 사용하도록 해제 코드가 정교하게 설계되었습니다. 여기에는 다음과 같은 의미가 있습니다.

  • 해제 코드 수를 계산하여 프롤로그 및 에필로그의 길이를 컴퓨팅할 수 있습니다. 또한 16비트 및 32비트 opcode에 대한 고유 매핑이 사용되므로 가변 길이 Thumb-2 명령으로도 해당 길이를 계산할 수 있습니다.

  • 에필로그 범위 시작 위치 뒷부분의 명령 수를 계산하면 해당 해제 코드 수를 건너뛰고 나머지 시퀀스를 실행하여 에필로그가 수행 중이었던 부분적으로 실행된 해제를 완료할 수 있습니다.

  • 프롤로그 종료 위치 앞부분의 명령 수를 계산하면 해당 해제 코드 수를 건너뛰고 나머지 시퀀스를 실행하여 실행이 완료된 프롤로그 부분만 실행을 취소할 수 있습니다.

다음 테이블에는 해제 코드에서 opcode로의 매핑이 나와 있습니다. 가장 일반적인 코드는 1바이트이고 2, 3, 4바이트가 필요한 코드도 가끔 있습니다. 각 코드는 가장 중요한 바이트부터 차례로 저장됩니다. 해제 코드 구조는 ARM EABI에서 설명하는 인코딩과는 다릅니다. 이러한 해제 코드는 부분적으로 실행된 프롤로그 및 에필로그 해제를 허용하기 위해 프롤로그 및 에필로그의 opcode에 대한 일대일 매핑을 포함하도록 설계되기 때문입니다.

바이트 1 바이트 2 바이트 3 바이트 4 Opsize 설명
00-7F 16 add sp,sp,#X

여기서 X는 (코드 및 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

여기서 코드 & 0x1FFF에 해당 비트가 설정된 경우 코드 & 0x2000과 r0-r12가 팝되면 LR이 팝됩니다.
C0-CF 16 mov sp,rX

여기서 X는 코드 & 0x0F입니다.
D0-D7 16 pop {r4-rX,lr}

여기서 X는 (코드 & 0x03) + 4이며, 코드 & 0x04인 경우에는 LR이 팝됩니다.
D8-DF 32 pop {r4-rX,lr}

여기서 X는 (코드 & 0x03) + 8이며, 코드 & 0x04인 경우에는 LR이 팝됩니다.
E0-E7 32 vpop {d8-dX}

여기서 X는 (코드 & 0x07) + 8입니다.
E8-EB 00-FF 32 addw sp,sp,#X

여기서 X는 (코드 및 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

여기서 코드 & 0x00FF에 해당 비트가 설정된 경우 코드 & 0x0100과 r0-r7이 팝되면 LR이 팝됩니다.
EE 00-0F 16 Microsoft 전용
EE 10-FF 16 사용 가능
EF 00-0F 32 ldr lr,[sp],#X

여기서 X는 (코드 및 0x000F) * 4
EF 10-FF 32 사용 가능
F0-F4 - 사용 가능
F5 00-FF 32 vpop {dS-dE}

여기서 S는 (코드 및 0x00F0) >> 4이고 E는 코드이고 0x000F
F6 00-FF 32 vpop {dS-dE}

여기서 S는 ((코드 및 0x00F0) >> 4) + 16이고 E는 (코드 및 0x000F) + 16
F7 00-FF 00-FF 16 add sp,sp,#X

여기서 X는 (코드 및 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

여기서 X는 (코드 및 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

여기서 X는 (코드 및 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

여기서 X는 (코드 및 0x00FFFFFF) * 4
FB 16 nop(16비트)
FC 32 nop(32비트)
FD 16 에필로그의 종료 + 16비트 nop
FE 32 에필로그의 종료 + 32비트 nop
FF - end

위 테이블에는 해제 코드 코드에 포함된 각 바이트의 16진수 값 범위와 opcode 크기 Opsize 및 해당하는 원래 명령 해석이 나와 있습니다. 빈 셀은 짧은 해제 코드를 나타냅니다. 여러 바이트를 포함하는 큰 값이 있는 명령에서는 가장 중요한 비트가 먼저 저장됩니다. Opsize 필드는 각 Thumb-2 작업과 연관된 암시적 opcode 크기를 표시합니다. 테이블에서는 인코딩이 서로 다른 명확한 중복 항목을 사용하여 각 opcode 크기를 구분합니다.

해제 코드는 코드의 첫 번째 바이트가 코드의 총 바이트 크기와 명령 스트림의 해당 opcode 크기를 모두 설명하도록 설계됩니다. 프롤로그나 에필로그의 크기를 컴퓨팅하려면 시퀀스 시작 부분부터 종료 부분까지 해제 코드 워크를 진행한 다음 조회 테이블 또는 비슷한 방법을 사용하여 해당 opcode의 길이를 확인합니다.

해제 코드 0xFD 및 0xFE는 일반 종료 코드 0xFF와 동일하지만 에필로그(16비트 또는 32비트)의 경우 추가 nop opcode 하나를 더 고려합니다. 프롤로그의 경우에는 0xFD, 0xFE 및 0xFF 코드가 정확하게 동일합니다. 이는 동일한 프롤로그 명령이 없는 일반 에필로그 종료인 bx lr 또는 b <tailcall-target>을 설명해 줍니다. 이로 인해 프롤로그와 에필로그 간에 해제 시퀀스를 공유할 수 있는 가능성이 높아집니다.

대부분의 경우에는 프롤로그와 에필로그에 같은 해제 코드 집합을 사용할 수 있습니다. 그러나 부분적으로 실행된 프롤로그 및 에필로그 해제를 처리하려면 순서나 동작이 서로 다른 여러 해제 코드 시퀀스를 사용해야 할 수 있습니다. 따라서 각 에필로그는 실행을 시작할 위치를 표시하는 고유 인덱스를 해제 배열에 포함합니다.

부분 프롤로그 및 에필로그 해제

가장 일반적인 해제 사례는 프롤로그 및 모든 에필로그 범위를 벗어난 함수 본문에서 예외가 발생하는 경우입니다. 이 경우에는 해제기가 인덱스 0부터 해제 배열의 코드를 실행하고 종료 opcode가 검색될 때까지 계속됩니다.

프롤로그 또는 에필로그를 실행하는 중에 예외가 발생하면 스택 프레임은 일부분만 생성되며 해제기는 수행된 작업을 올바르게 실행 취소하기 위해 정확하게 확인해야 합니다.

예를 들어 다음과 같은 프롤로그 및 에필로그 시퀀스를 고려해 보겠습니다.

0000:   push  {r0-r3}         ; 0x04
0002:   push  {r4-r9, lr}     ; 0xdd
0006:   mov   r7, sp          ; 0xc7
...
0140:   mov   sp, r7          ; 0xc7
0142:   pop   {r4-r9, lr}     ; 0xdd
0146:   add   sp, sp, #16     ; 0x04
0148:   bx    lr

각 opcode 옆에는 이 작업을 설명하는 적절한 해제 코드가 있습니다. 프롤로그에 대한 해제 코드 순서는 에필로그에 대한 해제 코드의 미러 이미지이며 최종 명령은 계산에 포함되지 않습니다. 이러한 사례가 일반적이기 때문에 프롤로그에 대한 해제 코드는 항상 프롤로그 실행 순서의 역순으로 저장된다고 가정합니다. 일반적으로 사용되는 해제 코드 집합은 다음과 같습니다.

0xc7, 0xdd, 0x04, 0xfd

0xFD 코드는 에필로그가 프롤로그보다 긴 단일 16비트 명령임을 의미하는 시퀀스 종료용 특수 코드입니다. 따라서 해제 코드 공유 가능성이 높아집니다.

위의 예에서는 프롤로그와 에필로그 사이의 함수 본문을 실행하는 동안 예외가 발생하면 에필로그 코드 내의 오프셋 0에서 에필로그와 함께 해제가 시작됩니다. 예에서 여기에 해당하는 오프셋은 0x140입니다. 정리가 수행되지 않았으므로 해제기는 전체 해제 시퀀스를 실행합니다. 대신 에필로그 코드 시작으로부터 명령 하나 후에 예외가 발생하면 해제기가 첫 번째 해제 코드를 건너뛰어 해제를 정상적으로 수행할 수 있습니다. opcode 및 해제 코드 간의 일대일 매핑이 지정되므로 에필로그의 명령 n에서 해제를 수행하는 경우 해제기는 처음 n개 해제 코드를 건너뛰어야 합니다.

이와 비슷한 논리가 프롤로그에서도 역방향으로 작동합니다. 프롤로그의 오프셋 0에서 해제하는 경우에는 코드를 실행하지 않아도 됩니다. 명령 하나에서 해제하는 경우 프롤로그 해제 코드는 역순으로 저장되기 때문에 해제 시퀀스가 맨 끝의 해제 코드 하나를 시작해야 합니다. 일반적으로는 프롤로그의 명령 n에서 해제하는 경우 코드 목록 끝에서 n번째 해제 코드에서 해제 실행이 시작되어야 합니다.

프롤로그 및 에필로그 해제 코드가 항상 정확히 일치하지는 않습니다. 이 경우 해제 코드 배열은 여러 코드 시퀀스를 포함해야 할 수 있습니다. 코드 처리를 시작할 오프셋을 결정하려면 다음 논리를 사용합니다.

  1. 함수 본문 내에서 해제하는 경우 인덱스 0에서 해제 코드 실행을 시작하고 종료 opcode에 도달할 때까지 계속합니다.

  2. 에필로그 내에서 해제하는 경우 에필로그 범위에서 제공되는 에필로그별 시작 인덱스를 사용합니다. 에필로그 시작 위치에서 PC까지의 거리(바이트 수)를 계산하고, 이미 실행된 모든 명령의 수만큼 해제 코드를 정방향으로 건너뛴 다음 해당 시점에서부터 해제 시퀀스를 실행합니다.

  3. 프롤로그 내에서 해제하는 경우 해제 코드의 인덱스 0부터 시작합니다. 시퀀스에서 프롤로그 코드의 길이를 계산하고, 프롤로그 종료 위치에서 PC까지의 거리(바이트 수)를 계산합니다. 실행되지 않은 모든 명령의 수만큼 해제 코드를 정방향으로 건너뛴 다음 해당 시점에서부터 해제 시퀀스를 실행합니다.

프롤로그의 해제 코드는 항상 배열의 첫 번째 코드여야 합니다. 또한 본문 내에서 해제하는 일반적인 경우 해제하는 데 사용되는 코드이기도 합니다. 에필로그별 코드 시퀀스는 프롤로그 코드 시퀀스 바로 뒤에 와야 합니다.

함수 조각

코드를 최적화하려는 경우 함수를 여러 분리된 부분으로 분할하면 유용할 수 있습니다. 이 작업을 수행할 때는 각 함수 조각에 고유한 별도의 .pdata 레코드가 필요하며 .xdata 레코드도 필요할 수 있습니다.

함수 프롤로그가 함수 시작 부분에 있으며 분할할 수 없다고 가정하는 경우 4가지 함수 조각이 사용될 수 있습니다.

  • 프롤로그만(모든 에필로그는 다른 조각에 포함됨)

  • 프롤로그 및 하나 이상의 에필로그; 다른 조각에 더 많은 에필로그가 있습니다.

  • 프롤로그 또는 에필로그 없음(프롤로그와 하나 이상의 에필로그가 다른 조각에 포함됨)

  • 에필로그만 해당; 프롤로그 및 다른 조각에 더 많은 에필로그가 있을 수 있습니다.

첫 번째 경우에는 프롤로그만 설명해야 합니다. 이렇게 하려면 압축 .pdata 형식에서 프롤로그를 정상적으로 설명하고 에필로그가 없음을 나타내는 Ret 값 3을 지정하면 됩니다. 전체 .xdata 형식에서는 인덱스 0에서 프롤로그 해제 코드를 일반적인 방식으로 제공하고 에필로그 수를 0으로 지정하여 이 작업을 수행할 수 있습니다.

두 번째 사례는 일반 함수와 같습니다. 조각에 에필로그가 하나뿐이고 조각 끝에 에필로그가 있으면 압축 .pdata 레코드를 사용할 수 있습니다. 그렇지 않은 경우에는 전체 .xdata 레코드를 사용해야 합니다. 에필로그 시작에 대해 지정되는 오프셋은 함수의 원래 시작 위치가 아닌 조각 시작 위치를 기준으로 합니다.

세 번째와 네 번째 사례는 프롤로그를 포함하지 않는다는 점 외에는 각각 첫 번째와 두 번째 사례의 변형입니다. 이러한 상황에서는 에필로그가 시작되기 전에 코드가 있다고 가정하고 일반적으로 프롤로그의 효과를 실행 취소하여 해제되는 함수 본문의 일부로 간주됩니다. 따라서 이러한 사례는 의사 프롤로그를 사용하여 인코딩해야 합니다. 의사 프롤로그는 본문 내에서 해제하는 방법을 설명하며 조각 시작 위치에서 부분 해제를 수행할지 여부를 결정할 때는 길이 0으로 처리됩니다. 또한 이 의사 프롤로그는 에필로그와 동일한 작업을 수행하는 경우가 많으므로 에필로그와 같은 해제 코드를 사용하여 설명할 수도 있습니다.

세 번째와 네 번째 사례에서는 압축 .pdata 레코드의 Flag 필드를 2로 설정하거나 .xdata 헤더의 플래그를 1로 설정하여 의사 프롤로그 유무를 지정합니다. 두 경우 모두 부분 프롤로그 해제 확인은 무시되며 에필로그 이외의 모든 해제는 전체 해제로 간주됩니다.

큰 함수

조각을 사용하면 .xdata 헤더의 비트 필드에 의해 적용되는 512KB 제한보다 큰 함수를 설명할 수 있습니다. 더 큰 함수를 설명하려면 512KB보다 작은 조각으로 나누기만 하면 됩니다. 에필로그를 여러 조각으로 분할하지 않도록 각 조각을 조정해야 합니다.

함수의 첫 번째 조각만 프롤로그를 포함합니다. 다른 모든 조각은 프롤로그가 없는 것으로 표시됩니다. 에필로그 수에 따라 각 조각은 에필로그를 포함하지 않을 수도 있고 포함할 수도 있습니다. 조각의 각 에필로그 범위는 함수 시작 위치가 아닌 조각 시작 위치를 기준으로 시작 오프셋을 지정합니다.

조각에 프롤로그와 에필로그가 없어도 함수 본문 내에서 해제할 방법을 설명하는 자체 .pdata가 필요하며 .xdata도 필요할 수 있습니다.

축소 래핑

함수 조각의 더 복잡한 특수 사례를 축소 래핑이라고 합니다. 함수의 시작에서 함수의 뒷부분으로 레지스터 저장을 지연시키는 기술입니다. 등록 저장이 필요하지 않은 간단한 사례에 최적화됩니다. 이 사례에는 스택 공간을 할당하지만 최소 레지스터 집합을 저장하는 외부 지역과 다른 레지스터를 저장하고 복원하는 내부 영역의 두 부분이 있습니다.

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatiles
    sub    sp, sp, #0x100    ; A: allocate all stack space up front
    ...                      ; A:
    add    r0, sp, #0xE4     ; A: prepare to do the inner save
    stm    r0, {r5-r11}      ; A: save remaining non-volatiles
    ...                      ; B:
    add    r0, sp, #0xE4     ; B: prepare to do the inner restore
    ldm    r0, {r5-r11}      ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C:

축소 래핑된 함수는 일반적으로 일반 프롤로그에서 추가 레지스터 저장을 위한 공간을 미리 할당한 다음, 레지스터를 사용 str 하거나 stm 대신 push저장해야 합니다. 이 작업은 함수의 원래 프롤로그에서 모든 스택 포인터 조작을 유지합니다.

축소 래핑된 함수 예제는 주석에 표시된 세 개의 영역으로 ABC 나뉘어야 합니다. 첫 번째 A 영역은 추가 비휘발성 저장의 끝을 통해 함수의 시작을 다룹니다. 이 조각이 프롤로그는 포함하지만 에필로그는 포함하지 않는 것을 설명하려면 .pdata 또는 .xdata 레코드를 생성해야 합니다.

중간 B 영역은 프롤로그와 에필로그가 없는 조각을 설명하는 자체 .pdata 또는 .xdata 레코드를 가져옵니다. 그러나 이 영역은 함수 본문으로 간주되므로 이 영역의 해제 코드도 있어야 합니다. 코드는 지역 프롤로그에 저장된 원래 레지스터와 지역을 A 입력 B하기 전에 저장된 추가 레지스터를 모두 나타내는 복합 프롤로그를 설명해야 합니다.

지역에 대해 설명된 복합 프롤로그는 지역 B 프롤로그와 저장된 추가 레지스터를 모두 A 설명해야 하므로 지역에 B 대한 레지스터 저장은 "내부 프롤로그"로 간주될 수 없습니다. 조각 B 에 프롤로그가 있는 경우 해제 코드는 해당 프롤로그의 크기를 의미하며, 추가 레지스터만 저장하는 opcode와 일대일로 매핑되는 방식으로 복합 프롤로그를 설명할 방법이 없습니다.

추가 레지스터 저장은 완료될 때까지 복합 프롤로그가 스택의 상태를 정확하게 설명하지 않기 때문에 영역 A의 일부로 간주되어야 합니다.

마지막 C 지역은 프롤로그가 없지만 에필로그가 있는 조각을 설명하는 자체 .pdata 또는 .xdata 레코드를 가져옵니다.

지역을 B 입력하기 전에 수행된 스택 조작을 하나의 명령으로 줄일 수 있는 경우에도 다른 방법이 작동할 수 있습니다.

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatile registers
    sub    sp, sp, #0xE0     ; A: allocate minimal stack space up front
    ...                      ; A:
    push   {r4-r9}           ; A: save remaining non-volatiles
    ...                      ; B:
    pop    {r4-r9}           ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C: restore non-volatile registers

핵심 인사이트는 각 명령 경계에서 스택이 해당 지역의 해제 코드와 완전히 일치한다는 것입니다. 이 예제의 내부 푸시 전에 해제가 발생하면 영역 A의 일부로 간주됩니다. 지역 A 프롤로그만 해제됩니다. 내부 푸시 후에 해제가 발생하면 프롤로그가 없는 영역 B의 일부로 간주됩니다. 그러나 영역의 내부 푸시와 원래 프롤로그를 모두 설명하는 해제 코드가 있습니다 A. 내부 팝에 대해 유사한 논리가 유지됩니다.

인코딩 최적화

해제 코드의 풍요로움과 압축되고 확장된 형태의 데이터를 사용할 수 있는 기능은 공간을 더 줄이기 위해 인코딩을 최적화할 수 있는 많은 기회를 제공합니다. 이러한 기술을 적극적으로 사용하면 해제 코드를 사용해 함수와 조각을 설명하는 과정의 순 오버헤드를 최소화할 수 있습니다.

가장 중요한 최적화 아이디어: 컴파일러 관점에서 논리 프롤로그 및 에필로그 경계를 해제하기 위해 프롤로그 및 에필로그 경계를 혼동하지 마세요. 효율성을 높이기 위해 해제 경계를 축소하여 범위를 더 좁힐 수 있습니다. 예를 들어 프롤로그는 스택 설정 후 확인 검사 수행하는 코드를 포함할 수 있습니다. 그러나 모든 스택 조작이 완료되면 추가 작업을 인코딩할 필요가 없으며 해제 프롤로그에서 제거할 수 있습니다.

함수 길이에도 이와 동일한 규칙이 적용됩니다. 함수에 에필로그를 따르는 데이터(예: 리터럴 풀)가 있는 경우 함수 길이의 일부로 포함해서는 안 됩니다. 함수를 함수의 일부인 코드로만 축소하면 에필로그가 맨 끝에 있고 압축 .pdata 레코드를 사용할 수 있을 가능성이 훨씬 큽니다.

프롤로그에서는 스택 포인터가 다른 레지스터에 저장되고 나면 보통 추가 opcode를 기록할 필요가 없습니다. 함수를 해제하기 위해 가장 먼저 수행되는 작업은 저장된 레지스터에서 SP를 복구하는 것입니다. 추가 작업은 해제에 영향을 주지 않습니다.

단일 명령 에필로그는 범위 또는 해제 코드로 인코딩할 필요가 없습니다. 해당 명령이 실행되기 전에 해제가 발생하는 경우 함수 본문 내에서의 것으로 가정해도 안전합니다. 프롤로그 해제 코드를 실행하는 것만으로도 충분합니다. 단일 명령이 실행된 후 해제가 발생하는 경우 정의에 따라 다른 지역에서 발생합니다.

이와 같은 이유로 인해 다중 명령 에필로그에서는 에필로그의 첫 번째 명령을 인코딩하지 않아도 됩니다. 명령이 실행되기 전에 해제가 수행되는 경우 전체 프롤로그 해제만 수행하면 되기 때문입니다. 해당 명령 이후에 해제가 발생하는 경우 이후 작업만 고려해야 합니다.

해제 코드는 적극적으로 다시 사용해야 합니다. 각 에필로그 범위의 인덱스는 해제 코드 배열의 임의의 시작점을 가리킵니다. 이전 시퀀스의 시작을 가리킬 필요는 없습니다. 중간을 가리킬 수 있습니다. 가장 좋은 방법은 해제 코드 시퀀스를 생성하는 것입니다. 그런 다음, 이미 인코딩된 시퀀스 풀에서 정확한 바이트 일치를 검색합니다. 완전히 일치하는 항목을 코드 다시 사용 시작 지점으로 사용하는 것입니다.

단일 명령 에필로그를 무시한 후 남은 에필로그가 없으면 압축 .pdata 형식을 사용하는 것이 좋습니다. 에필로그가 없는 경우에는 해당 형식을 사용하는 것이 훨씬 효율적입니다.

예제

이 예에서 이미지 기준은 0x00400000에 있습니다.

예제 1: 리프 함수, 로컬 항목 없음

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x000535F8(= 0x004535F8-0x00400000)
  • 단어 1

    • Flag = 1(정식 프롤로그 및 에필로그 형식을 나타냄)

    • Function Length = 0x31(= 0x62/2)

    • Ret = 1(16비트 분기 반환을 나타냄)

    • H = 0(매개 변수가 호밍되지 않음을 나타냄)

    • R = r4-r5의 푸시/팝을 나타내는 0 및 Reg = 1

    • L = 0(LR 저장/복원이 수행되지 않음을 나타냄)

    • C = 0(프레임 연결이 없음을 나타냄)

    • Stack Adjust = 0(스택 조정이 없음을 나타냄)

예제 2: 로컬 할당이 포함된 중첩 함수

Prologue:
  004533AC: B5F0      push        {r4-r7, lr}
  004533AE: B083      sub         sp, sp, #0xC
Epilogue:
  00453412: B003      add         sp, sp, #0xC
  00453414: BDF0      pop         {r4-r7, pc}

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x000533AC(= 0x004533AC -0x00400000)
  • 단어 1

    • Flag = 1(정식 프롤로그 및 에필로그 형식을 나타냄)

    • Function Length = 0x35(= 0x6A/2)

    • Ret = 0(pop {pc} 반환을 나타냄)

    • H = 0(매개 변수가 호밍되지 않음을 나타냄)

    • R = r4-r7의 푸시/팝을 나타내는 0 및 Reg = 3

    • L = 1(LR이 저장/복원되었음을 나타냄)

    • C = 0(프레임 연결이 없음을 나타냄)

    • Stack Adjust = 3(= 0x0C/4)

예제 3: 중첩 variadic 함수

Prologue:
  00453988: B40F      push        {r0-r3}
  0045398A: B570      push        {r4-r6, lr}
Epilogue:
  004539D4: E8BD 4070 pop         {r4-r6}
  004539D8: F85D FB14 ldr         pc, [sp], #0x14

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x00053988(= 0x00453988-0x00400000)
  • 단어 1

    • Flag = 1(정식 프롤로그 및 에필로그 형식을 나타냄)

    • Function Length = 0x2A(= 0x54/2)

    • Ret = 0(pop {pc} 스타일 반환(이 경우 ldr pc,[sp],#0x14 반환)을 나타냄)

    • H = 1(매개 변수가 호밍되었음을 나타냄)

    • R = 0 및 Reg = 2, r4-r6의 푸시/팝을 나타냅니다.

    • L = 1(LR이 저장/복원되었음을 나타냄)

    • C = 0(프레임 연결이 없음을 나타냄)

    • Stack Adjust = 0(스택 조정이 없음을 나타냄)

예제 4: 여러 에필로그가 포함된 함수

Prologue:
  004592F4: E92D 47F0 stmdb       sp!, {r4-r10, lr}
  004592F8: B086      sub         sp, sp, #0x18
Epilogues:
  00459316: B006      add         sp, sp, #0x18
  00459318: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  0045943E: B006      add         sp, sp, #0x18
  00459440: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  004595D4: B006      add         sp, sp, #0x18
  004595D6: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459606: B006      add         sp, sp, #0x18
  00459608: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459636: F028 FF0F bl          KeBugCheckEx     ; end of function

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x000592F4(= 0x004592F4-0x00400000)
  • 단어 1

    • Flag = 0(에필로그가 여러 개인 경우 필요한 .xdata 레코드가 있음을 나타냄)

    • .xdata address - 0x00400000

.xdata(가변, 6개 단어):

  • 단어 0

    • Function Length = 0x0001A3(= 0x000346/2)

    • Vers = 0으로, 첫 번째 버전을 나타냅니다..xdata

    • X = 0, 예외 데이터 없음 표시

    • E = 0(에필로그 범위 목록을 나타냄)

    • F = 프롤로그를 포함하여 전체 함수 설명을 나타내는 0

    • Epilogue Count = 총 4개의 에필로그 범위를 나타내는 0x04

    • Code Words = 해제 코드의 32비트 단어 하나를 나타내는 0x01

  • 4개 위치에서 4개 에필로그 범위를 설명하는 단어 1-4. 각 범위는 오프셋 0x00에서 프롤로그와 공유되는 공통 해제 코드 집합을 포함하며, 조건 0x0E(항상)를 지정하는 무조건 범위입니다.

  • 단어 5에서 시작되는 해제 코드(프롤로그/에필로그 간에 공유됨):

    • 해제 코드 0 = 0x06: sp += (6 << 2)

    • 해제 코드 1 = 0xDE: pop {r4-r10, lr}

    • 해제 코드 2 = 0xFF: end

예제 5: 동적 스택과 내부 에필로그가 포함된 함수

Prologue:
  00485A20: B40F      push        {r0-r3}
  00485A22: E92D 41F0 stmdb       sp!, {r4-r8, lr}
  00485A26: 466E      mov         r6, sp
  00485A28: 0934      lsrs        r4, r6, #4
  00485A2A: 0124      lsls        r4, r4, #4
  00485A2C: 46A5      mov         sp, r4
  00485A2E: F2AD 2D90 subw        sp, sp, #0x290
Epilogue:
  00485BAC: 46B5      mov         sp, r6
  00485BAE: E8BD 41F0 ldm         sp!, {r4-r8, lr}
  00485BB2: B004      add         sp, sp, #0x10
  00485BB4: 4770      bx          lr
  ...
  00485E2A: F7FF BE7D b           #0x485B28    ; end of function

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x00085A20(= 0x00485A20-0x00400000)
  • 단어 1

    • Flag = 0(에필로그가 여러 개인 경우 필요한 .xdata 레코드가 있음을 나타냄)

    • .xdata address - 0x00400000

.xdata(가변, 3개 단어):

  • 단어 0

    • Function Length = 0x0001A3(= 0x000346/2)

    • Vers = 0으로, 첫 번째 버전을 나타냅니다..xdata

    • X = 0, 예외 데이터 없음 표시

    • E = 0(에필로그 범위 목록을 나타냄)

    • F = 프롤로그를 포함하여 전체 함수 설명을 나타내는 0

    • Epilogue Count = 0x001, 총 에필로그 범위 1개

    • Code Words = 해제 코드의 32비트 단어 하나를 나타내는 0x01

  • 단어 1: 해제 코드 인덱스 0x00에서 시작되며 조건이 0x0E(항상)인 오프셋 0xC6(= 0x18C/2)의 에필로그 범위

  • 단어 2에서 시작되는 해제 코드(프롤로그/에필로그 간에 공유됨):

    • 해제 코드 0 = 0xC6: sp = r6

    • 해제 코드 1 = 0xDC: pop {r4-r8, lr}

    • 해제 코드 2 = 0x04: sp += (4 << 2)

    • 해제 코드 3 = 0xFD: end(에필로그의 16비트 명령으로 계산됨)

예제 6: 예외 처리기가 있는 함수

Prologue:
  00488C1C: 0059 A7ED dc.w  0x0059A7ED
  00488C20: 005A 8ED0 dc.w  0x005A8ED0
FunctionStart:
  00488C24: B590      push        {r4, r7, lr}
  00488C26: B085      sub         sp, sp, #0x14
  00488C28: 466F      mov         r7, sp
Epilogue:
  00488C6C: 46BD      mov         sp, r7
  00488C6E: B005      add         sp, sp, #0x14
  00488C70: BD90      pop         {r4, r7, pc}

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x00088C24(= 0x00488C24-0x00400000)
  • 단어 1

    • Flag = 0(에필로그가 여러 개인 경우 필요한 .xdata 레코드가 있음을 나타냄)

    • .xdata address - 0x00400000

.xdata(가변, 5개 단어):

  • 단어 0

    • Function Length =0x000027(= 0x00004E/2)

    • Vers = 0으로, 첫 번째 버전을 나타냅니다..xdata

    • X = 1, 예외 데이터 표시

    • E = 1(단일 에필로그가 있음을 나타냄)

    • F = 프롤로그를 포함하여 전체 함수 설명을 나타내는 0

    • Epilogue Count = 0x00 에필로그 해제 코드가 오프셋 0x00 시작됨을 나타냅니다.

    • Code Words = 해제 코드의 32비트 단어 2개를 나타내는 0x02

  • 해제 코드(단어 1에서 시작):

    • 해제 코드 0 = 0xC7: sp = r7

    • 해제 코드 1 = 0x05: sp += (5 << 2)

    • 해제 코드 2 = 0xED/0x90: pop {r4, r7, lr}

    • 해제 코드 4 = 0xFF: end

  • 단어 3은 예외 처리기 = 0x0019A7ED(= 0x0059A7ED - 0x00400000)를 지정합니다.

  • 단어 4부터는 인라인 처리된 예외 데이터입니다.

예제 7: 작은 함수

Function:
  00488C72: B500      push        {lr}
  00488C74: B081      sub         sp, sp, #4
  00488C76: 3F20      subs        r7, #0x20
  00488C78: F117 0308 adds        r3, r7, #8
  00488C7C: 1D3A      adds        r2, r7, #4
  00488C7E: 1C39      adds        r1, r7, #0
  00488C80: F7FF FFAC bl          target
  00488C84: B001      add         sp, sp, #4
  00488C86: BD00      pop         {pc}

.pdata (고정, 2단어):

  • 단어 0

    • Function Start RVA = 0x00088C72(= 0x00488C72-0x00400000)
  • 단어 1

    • Flag = 1(정식 프롤로그 및 에필로그 형식을 나타냄)

    • Function Length = 0x0B(= 0x16/2)

    • Ret = 0(pop {pc} 반환을 나타냄)

    • H = 0(매개 변수가 호밍되지 않음을 나타냄)

    • R = 0 및 Reg = 7로, 레지스터가 저장/복원되지 않았음을 나타냅니다.

    • L = 1(LR이 저장/복원되었음을 나타냄)

    • C = 0(프레임 연결이 없음을 나타냄)

    • Stack Adjust = 1(1x4바이트 스택 조정을 나타냄)

참고 항목

ARM ABI 규칙 개요
일반적인 Visual C++ ARM 마이그레이션 문제