다음을 통해 공유


ARM 예외 처리

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

ARM 예외 처리

Windows on ARM에서는 해제 코드를 사용하여 SEH(structured exception handling) 중의 스택 해제를 제어합니다.해제 코드는 실행 가능 이미지의 .xdata 섹션에 저장되는 바이트 시퀀스로,함수 프롤로그 및 에필로그 코드의 작동을 요약하여 설명하므로 호출자 스택 프레임 해제를 준비하기 위해 적용된 함수 프롤로그를 실행 취소할 수 있습니다.

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

Assumptions

Windows on ARM의 실행 가능 이미지는 PE(이식 가능한 실행 파일) 형식을 사용합니다.자세한 내용은 Microsoft PE 및 COFF 사양을 참조하세요.예외 처리 정보는 이미지의 .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 블록에 대한 포인터 또는 정식 함수 해제 시퀀스를 설명하는 압축된 단어를 포함하는 두 번째 단어가 그 뒤에 붙습니다.

단어 오프셋

비트

용도

0

0-31

Function Start RVA는 함수 시작 부분의 32비트 RVA입니다.함수에 Thumb 코드가 포함되어 있으면 이 주소의 하위 비트를 설정해야 합니다.

1

0-1

Flag는 두 번째 .pdata 단어의 나머지 30비트를 해석하는 방법을 나타내는 2비트 필드입니다.Flag가 0이면 나머지 비트는 Exception Information RVA가 됩니다(하위 2비트는 암시적으로 0이 됨).Flag가 0이 아니면 나머지 비트는 Packed Unwind Data 구조체가 됩니다.

1

2-31

Exception Information RVA 또는 Packed Unwind Data

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

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

압축된 해제 데이터

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

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

단어 오프셋

비트

용도

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이면 LR을 저장/복원하지 않고 1이면 LR을 저장/복원합니다.

1

21

C는 함수가 빠른 스택 워크를 위한 프레임 체인을 설정하는 추가 명령을 포함하는지(1) 아니면 포함하지 않는지(0)를 나타내는 1비트 플래그입니다.이 비트가 설정되어 있으면 저장된 정수 비휘발성 레지스터 목록에 r11이 암시적으로 추가됩니다.C 플래그를 사용하는 경우에는 아래 제한을 참조하세요.

1

22-31

Stack Adjust는 이 함수에 대해 할당된 스택 바이트 수를 4로 나눈 값을 나타내는 10비트 필드입니다.그러나 0x000-0x3F3 사이의 값만 직접 인코딩할 수 있습니다.4044바이트보다 많은 스택을 할당하는 함수는 전체 .xdata 레코드를 사용해야 합니다.Stack Adjust 필드의 값이 0x3F4 이상인 경우 하위 4개 비트에는 특수한 의미가 있습니다.

  • 비트 0-1은 스택 조정(1-4)의 단어 수에서 1을 뺀 값을 나타냅니다.

  • 프롤로그에서 이 조정을 푸시 작업에 결합한 경우 비트 2는 1로 설정됩니다.

  • 에필로그에서 이 조정을 표시 작업에 결합한 경우 비트 3은 1로 설정됩니다.

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

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

    • 프레임 체인에 r11과 LR이 모두 필요하므로 L 플래그도 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 및 (L==0 및 R==1 및 PF==0)

16

mov r11,sp

C0-CF/FB

3b

C==1 및 (L==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 필드에 따라 저장되는 레지스터가 요약되어 있습니다.모든 경우에 N은 Reg + 4, E는 Reg + 8, S는 (~Stack Adjust) 및 3과 같습니다.

C

L

R

PF

푸시되는 정수 레지스터

푸시되는 VFP 레지스터

0

0

0

0

r4-rN

없음

0

0

0

1

rS-rN

없음

0

0

1

0

없음

d8-dE

0

0

1

1

rS-r3

d8-dE

0

1

0

0

r4-rN, LR

없음

0

1

0

1

rS-rN, LR

없음

0

1

1

0

LR

d8-dE

0

1

1

1

rS-r3, LR

d8-dE

1

0

0

0

r4-rN, r11

없음

1

0

0

1

rS-rN, r11

없음

1

0

1

0

r11

d8-dE

1

0

1

1

rS-r3, r11

d8-dE

1

1

0

0

r4-rN, r11, LR

없음

1

1

0

1

rS-rN, r11, LR

없음

1

1

1

0

r11, LR

d8-dE

1

1

1

1

rS-r3, r11, LR

d8-dE

정식 함수의 에필로그도 비슷한 형식을 따르지만 반대 방향이며 몇 가지 옵션이 추가로 사용됩니다.에필로그의 길이는 최대 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) 또는 R==0 또는 EF==1

16/32

pop   {registers}

9a

H==1 및 L==0

16

add   sp,sp,#0x10

9b

H==1 및 L==1

32

ldr   pc,[sp],#0x14

10a

Ret==1

16

bx    reg

10b

Ret==2

32

b     address

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

명령 7과 8은 프롤로그와 같은 논리를 사용하여 스택에서 복원되는 레지스터를 결정합니다. 단, 이 명령에서는 두 가지 사항이 변경됩니다. 첫째로, PF 대신 EF가 사용됩니다. 둘째로 Ret = 0이면 LR이 레지스터 목록의 PC로 바뀌고 에필로그가 즉시 종료됩니다.

H가 설정되어 있으면 명령 9a 또는 9b가 포함됩니다.L이 0이면 LR이 스택에 없음을 나타내기 위해 명령 9a가 사용됩니다.이 경우 스택은 수동으로 조정되며 명시적 반환을 지정하려면 Ret가 1 또는 2여야 합니다.L이 1이면 에필로그가 조기에 종료됨을 나타내고 스택을 반환하는 동시에 조정하기 위해 명령 9b가 사용됩니다.

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

.xdata 레코드

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

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

    단어

    비트

    용도

    0

    0-17

    Function Length는 함수의 전체 길이(바이트 단위)를 2로 나눈 결과를 제공하는 18비트 필드입니다.함수가 512KB보다 크면 여러 .pdata 및 .xdata 레코드를 사용하여 함수를 설명해야 합니다.자세한 내용은 이 문서의 큰 함수 섹션을 참조하세요.

    0

    18-19

    Vers는 나머지 xdata의 버전을 설명하는 2비트 필드입니다.현재는 버전 0만 정의되어 있으며 값 1~3은 예약됩니다.

    0

    20

    X은 예외 데이터가 있는지(1) 아니면 없는지(0)를 나타내는 1비트 필드입니다.

    0

    21

    E는 나중에 추가 범위 단어를 다시 사용해야 하도록 지정(0)하는 대신 단일 에필로그를 설명하는 정보를 헤더에 압축(0)함을 나타내는 1비트 필드입니다.

    0

    22

    F는 이 레코드가 함수 조각(1)을 설명하는지 아니면 전체 함수(0)를 설명하는지를 나타내는 1비트 필드입니다.코드 조각은 프롤로그가 없으며 모든 프롤로그 처리를 무시해야 함을 암시합니다.

    0

    23-27

    Epilogue Count는 E 비트의 상태에 따라 두 가지 의미가 될 수 있는 5비트 필드입니다.

    • E가 0이면 이 필드는 섹션 3에서 설명한 예외 범위의 총 수입니다.함수에 범위가 32개 이상 포함되는 경우에는 이 필드와 Code Words 필드를 모두 0으로 설정하여 확장 단어가 필요함을 나타내야 합니다.

    • E가 1이면 이 필드는 에필로그만을 설명하는 첫 번째 해제 코드의 인덱스를 지정합니다.

    0

    28-31

    Code Words는 섹션 4의 모든 해제 코드를 포함하는 데 필요한 32비트 단어의 수를 지정하는 4비트 필드입니다.해제 코드 64바이트 이상에 대해 단어가 16개 이상 필요한 경우에는 이 필드와 Epilogue Count 필드를 모두 0으로 설정하여 확장 단어가 필요함을 나타내야 합니다.

    1

    0-15

    Extended Epilogue Count는 매우 많은 에필로그를 인코딩하기 위한 추가 공간을 제공하는 16비트 필드입니다.첫 번째 헤더 단어의 Epilogue Count 및 Code Words 필드가 모두 0으로 설정된 경우에만 이 필드가 들어 있는 확장 단어가 포함됩니다.

    1

    16-23

    Extended Code Words는 매우 많은 해제 코드 단어를 인코딩하기 위한 추가 공간을 제공하는 8비트 필드입니다.첫 번째 헤더 단어의 Epilogue Count 및 Code Words 필드가 모두 0으로 설정된 경우에만 이 필드가 들어 있는 확장 단어가 포함됩니다.

    1

    24-31

    예약됨

  2. 헤더에서 E 비트가 0으로 설정된 경우 예외 데이터 다음에는 에필로그 범위에 대한 정보 목록이 있습니다. 이러한 범위는 단어에 하나로 압축되어 작은 시작 오프셋부터 차례로 저장됩니다.각 범위에 포함되는 필드는 다음과 같습니다.

    비트

    용도

    0-17

    Epilogue Start Offset는 함수 시작 위치를 기준으로 하는 에필로그 오프셋(바이트 단위)을 2로 나눈 결과를 설명하는 18비트 필드입니다.

    18-19

    Res는 이후 확장을 위해 예약되는 2비트 필드입니다.해당 값은 0이어야 합니다.

    20-23

    Condition은 에필로그가 실행되는 조건을 제공하는 4비트 필드입니다.무조건 에필로그의 경우에는 이 필드를 "항상"을 나타내는 0xE로 설정해야 합니다.에필로그는 완전 조건부 또는 완전 무조건이어야 하며 Thumb-2 모드에서는 에필로그가 IT opcode 다음의 첫 번째 명령으로 시작됩니다.

    24-31

    Epilogue Start Index는 이 에필로그를 설명하는 첫 번째 해제 코드의 바이트 인덱스를 나타내는 8비트 필드입니다.

  3. 에필로그 범위 목록 다음에는 해제 코드를 포함하는 바이트 배열이 옵니다. 이 문서의 해제 코드 섹션에서 해제 코드에 대해 자세히 설명합니다.이 배열은 가장 가까운 전체 단어 경계 끝에 채워집니다.바이트는 little endian 모드에서 직접 가져올 수 있도록 쉽게 little endian 순서로 저장됩니다.

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

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

ULONG ComputeXdataSize(PULONG *Xdata)
{
    ULONG EpilogueScopes;
    ULONG Size;
    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;
    }
    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

위의 테이블에는 해제 코드 Code에 포함된 각 바이트의 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 헤더의 F 플래그를 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:

축소 래핑된 함수는 보통 일반 프롤로그에서 추가 레지스터 저장을 위한 공간을 미리 할당한 다음 push 대신 str 또는 stm을 사용하여 레지스터 저장을 수행합니다.그러면 모든 스택 포인터 조작이 함수의 원래 프롤로그에 유지됩니다.

축소 래핑된 예제 함수는 주석에 A, B, C로 표시된 3개 영역으로 분리해야 합니다.첫 번째 영역인 A에는 함수 시작 부분부터 추가 비휘발성 저장 끝부분까지가 포함됩니다.이 조각이 프롤로그는 포함하지만 에필로그는 포함하지 않는 것을 설명하려면 .pdata 또는 .xdata 레코드를 생성해야 합니다.

가운데의 B 영역은 프롤로그와 에필로그가 없는 조각을 설명하는 자체 .pdata 또는 .xdata 레코드를 포함합니다.그러나 이 영역은 함수 본문으로 간주되므로 이 영역의 해제 코드도 있어야 합니다.해당 코드는 영역 A 프롤로그에 저장된 원래 레지스터와 영역 B가 시작되기 전에 저장된 추가 레지스터가 모두 단일 작업 시퀀스에서 생성된 것처럼 표시하는 복합 프롤로그를 설명해야 합니다.

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

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

마지막 B 영역은 프롤로그는 없지만 에필로그는 있는 조각을 설명하는 자체 .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의 일부분으로 간주됩니다. 영역 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=0 및 Reg = 1(r4-r5의 푸시/표시를 나타냄)

    • 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=0 및 Reg = 3(r4-r7의 푸시/표시를 나타냄)

    • 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 = 0x04(총 4개의 에필로그 범위를 나타냄)

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

  • 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 = 0x01(해제 코드의 32비트 단어 하나를 나타냄)

  • 단어 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 = 0x02(해제 코드의 32비트 단어 두 개를 나타냄)

  • 해제 코드(단어 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바이트 스택 조정을 나타냄)

참고 항목

참조

일반적인 Visual C++ ARM 마이그레이션 문제

개념

ARM ABI 규칙 개요