To handle an exception, Windows must be able to unwind the stack: to step back from
the function that failed to the one that called it, and so on, restoring the registers
each function saved. 64-bit code keeps no chain of handler records on the stack.
Instead the image describes every function that uses the stack, or saves registers, or
has a handler, in a table: the exception table, at data directory 3 and in the
.pdata section, sorted by function address. Each entry points at the
function’s unwind information, which says what its prolog did. The form depends on the
machine: x64 and Itanium use one, ARM64 another.
Function table entries
x64 and Itanium use RUNTIME_FUNCTION, 12 bytes:
| Offset | Size | Type | Field | Meaning |
|---|---|---|---|---|
| 0 | 4 | u32 | BeginAddress | The RVA of the function’s start. |
| 4 | 4 | u32 | EndAddress | The RVA just past its end. |
| 8 | 4 | u32 | UnwindData | The RVA of its unwind information (named UnwindInfoAddress for Itanium). |
0000CE00 10 10 00 00 30 10 00 00 68 B7 00 00 30 10 00 00
0000CE10 E6 11 00 00 74 B7 00 00
0x10000 (file 0xCE00): the function from 0x1010 to 0x1030, with unwind information at 0xB768; then 0x1030 to 0x11E6, at 0xB774. The table holds 239 functions.ARM64 uses an 8-byte entry:
| Offset | Size | Type | Field | Meaning |
|---|---|---|---|---|
| 0 | 4 | u32 | BeginAddress | The RVA of the function’s start. |
| 4 | 4 | u32 | UnwindData | The low 2 bits are the Flag. Flag 0: the rest is the RVA of an .xdata record. Flag 1 or 2: the word itself is packed unwind data, for a function with a standard prolog and epilog. Flag 3: a link to another .pdata record. |
The explorer shows the table for x64, ARM64 and Itanium, and decodes the unwind information for x64 and ARM64. An ARM64EC image, whose header says x64, is read as x64.
Unwind information
x64: UNWIND_INFO, at the RVA the entry gives, on a 4-byte
boundary, in .rdata or .xdata:
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 1 | Version and Flags | The version in the low 3 bits: 1, or 2 with epilog codes. The flags in the high 5 bits: 1 EHANDLER (an exception handler follows), 2 UHANDLER (a termination handler follows), 4 CHAININFO (a chained entry follows). |
| 1 | 1 | SizeOfProlog | The length of the prolog in bytes. |
| 2 | 1 | CountOfCodes | The number of 2-byte unwind code slots. |
| 3 | 1 | FrameRegister and FrameOffset | The low 4 bits: the frame pointer register, 0 for none. The high 4 bits: its offset from RSP, times 16. |
| 4 | 2 each | Unwind codes | CountOfCodes slots, then one padding slot if the count is odd. |
| – | 4 and more | Handler | With EHANDLER or UHANDLER: the RVA of the language-specific handler, then the handler’s own data, of a length only the handler knows. |
| – | 12 | Chained entry | With CHAININFO instead: a RUNTIME_FUNCTION for the function this one continues, whose unwind information applies after this one’s. |
0000A960 01 08 04 00 08 F0 06 E0
0000A970 04 C0 02 50
0xB768 (file 0xA968) of the 64-bit glfw3.dll: version 1, no flags, an 8-byte prolog, 4 codes, no frame register; then at offset 8 in the prolog a push of R15, at 6 of R14, at 4 of R12 and at 2 of RBPThe explorer groups the unwind records into one “Unwind information” part, 127 records in the example, and shows the bytes between them, the handlers’ data, as “Handler data” without decoding them. Microsoft also documents a version 3 for the APX registers, which the explorer does not decode.
ARM64: an .xdata record starts with a 32-bit header:
| Bits | Field | Meaning |
|---|---|---|
| 0–17 | Function Length | The function’s length, in 4-byte units. |
| 18–19 | Vers | The version, 0. |
| 20 | X | 1 when an exception handler’s RVA follows the codes. |
| 21 | E | 1 when there is one epilog, described in the header itself. |
| 22–26 | Epilog Count | The number of epilog scopes, or with E, the index of the first unwind code of the one epilog. |
| 27–31 | Code Words | The number of 4-byte words of unwind codes. |
When Epilog Count and Code Words are both 0, a second word follows, with an Extended Epilog Count (16 bits) and Extended Code Words (8 bits). Then come the epilog scopes, 4 bytes each unless E is set; the unwind codes; and, with X, the handler’s RVA and its data. The explorer shows the header and the rest as bytes; Microsoft’s ARM64 exception handling page lists the unwind code encodings.
x64 unwind codes
Each slot is 2 bytes: the offset in the prolog just after the instruction it describes (1 byte), then the operation (low 4 bits of the second byte) and its information (high 4 bits). The codes are listed in reverse order of the prolog, so that unwinding reads them first to last. Some operations take one or two more slots.
| Operation | Name | Slots | What the prolog did |
|---|---|---|---|
| 0 | UWOP_PUSH_NONVOL | 1 | Pushed the register that the information gives. |
| 1 | UWOP_ALLOC_LARGE | 2 or 3 | Allocated stack space. Information 0: the next slot is the size ÷ 8. Information 1: the next two slots are the size. |
| 2 | UWOP_ALLOC_SMALL | 1 | Allocated information × 8 + 8 bytes, from 8 to 128. |
| 3 | UWOP_SET_FPREG | 1 | Set the frame register to RSP plus FrameOffset × 16. |
| 4 | UWOP_SAVE_NONVOL | 2 | Saved the register with a mov, at RSP plus the next slot × 8. |
| 5 | UWOP_SAVE_NONVOL_FAR | 3 | The same, with a 32-bit offset in the next two slots. |
| 6 | UWOP_EPILOG | – | In version 2: describes an epilog. Not in Microsoft’s list; the name is LLVM’s and Wine’s. |
| 7 | – | – | Not defined. |
| 8 | UWOP_SAVE_XMM128 | 2 | Saved the XMM register with a movaps, at RSP plus the next slot × 16. |
| 9 | UWOP_SAVE_XMM128_FAR | 3 | The same, with a 32-bit offset in the next two slots. |
| 10 | UWOP_PUSH_MACHFRAME | 1 | A machine frame was pushed, by a hardware interrupt or exception; information 1 when an error code was pushed too. |
Registers by number: 0 RAX, 1 RCX, 2 RDX, 3 RBX, 4 RSP, 5 RBP, 6 RSI, 7 RDI, and 8 to
15 R8 to R15. In the example, 08 F0 is offset 8, operation 0
(PUSH_NONVOL), register 15 (R15). The instructions themselves are described in the
x86 instruction reference.
Other entry forms
The exception table has other forms on 32-bit machines: 8-byte entries on Windows CE (ARM, SH3, SH4) and 32-bit ARM, and 20-byte entries on MIPS, Alpha and PowerPC. They belong to 32-bit machines and were not seen in the PE32+ files examined for this site, whose machines were x64 and ARM64. For a machine whose form it does not know, the explorer shows the exception table as bytes.