This post is intended to supplement MSDN's ARM64 exception handling information. To start, here's a fleshed-out version of a table listing the available unwind codes:

NameEncodingProlog InstructionEpilog InstructionUnwind Effect
alloc_s000iiiiisub sp, sp, i*16add sp, sp, i*16Emulate epilog instruction
alloc_m11000iiiiiiiiiiisub sp, sp, i*16add sp, sp, i*16Emulate epilog instruction
alloc_l11100000i:u24sub sp, sp, i*16add sp, sp, i*16Emulate epilog instruction
add_fp11100010i:u8add fp, sp, i*8 (NB: not 16)sub sp, fp, i*8 (NB: not 16)Emulate epilog instruction
set_fp11100001mov fp, spmov sp, fpEmulate epilog instruction
pac_sign_lr11111100pacibsp (sign lr using sp)autibsp (authenticate lr using sp)Emulate autibsp or xpaclri
save_fplr01iiiiiistp fp, lr, [sp+i*8]ldp fp, lr, [sp+i*8]Emulate epilog instruction
save_lrpair1101011nnniiiiistp x(19+2*n), lr, [sp+i*8]ldp x(19+2*i), lr, [sp+i*8]Emulate epilog instruction
save_fplr_x10iiiiiistp fp, lr, [sp-(i+1)*8]!ldp fp, lr, [sp], (i+1)*8Emulate epilog instruction
save_regp110010nnnniiiiiistp x(19+n), x(20+n), [sp+i*8]ldp x(19+n), x(20+n), [sp+i*8]Emulate epilog instruction
save_regp_x110011nnnniiiiiistp x(19+n), x(20+n), [sp-(i+1)*8]!ldp x(19+n), x(20+n), [sp], (i+1)*8Emulate epilog instruction
save_r19r20_x001iiiiistp x19, x20, [sp-i*8]! (NB: not i+1)ldp x19, x20, [sp], i*8 (NB: not i+1)Emulate epilog instruction
save_next11100110stp similar to previous non-lr stp ldp similar to next non-lr ldp Extend next effect by two
save_reg110100nnnniiiiiistr x(19+n), [sp+i*8]ldr x(19+n), [sp+i*8]Emulate epilog instruction
save_reg_x1101010nnnniiiiistr x(19+n), [sp-(i+1)*8]!ldr x(19+n), [sp], (i+1)*8Emulate epilog instruction
save_fregp1101100nnniiiiiistp d(8+n), d(9+n), [sp+i*8]ldp d(8+n), d(9+n), [sp+i*8]Emulate epilog instruction
save_fregp_x1101101nnniiiiiistp d(8+n), d(9+n), [sp-(i+1)*8]!ldp d(8+n), d(9+n), [sp], (i+1)*8Emulate epilog instruction
save_freg1101110nnniiiiiistr d(8+n), [sp+i*8]ldr d(8+n), [sp+i*8]Emulate epilog instruction
save_freg_x11011110nnniiiiistr d(8+n), [sp-(i+1)*8]!ldr d(8+n), [sp], (i+1)*8Emulate epilog instruction
save_any_reg11100111x:u16 *One str or stp to stack *One ldr or ldp from stack *Varies *
custom11101xxxNo instructionNo instructionBespoke
reserved11110xxxNo instructionNo instructionUnwind fails
reserved11111000x:u8Any one instructionAny one instructionNo effect (yet)
reserved11111001x:u16Any one instructionAny one instructionNo effect (yet)
reserved11111010x:u24Any one instructionAny one instructionNo effect (yet)
reserved11111011x:u32Any one instructionAny one instructionNo effect (yet)
reserved11111101Any one instructionAny one instructionNo effect (yet)
reserved11111110Any one instructionAny one instructionNo effect (yet)
reserved11111111Any one instructionAny one instructionNo effect (yet)
nop11100011Any one instructionAny one instructionNo effect
end_c11100101No instruction, start of prolog Any one instruction, then end of epilog No effect
end11100100No instruction, start of prologret (or tailcall b), then end of epilogUnwind complete

(*) save_any_reg was added for Arm64EC. The 16-bit payload is made from a number of fields, packed as rpxnnnnnmmiiiiii. This gives rise to many different variants:

NameEncodingProlog InstructionEpilog InstructionUnwind Effect
save_any_reg11100111000nnnnn00iiiiiistr x(0+n), [sp+i*8]ldr x(0+n), [sp+i*8]Emulate epilog instruction
save_any_reg11100111001nnnnn00iiiiiistr x(0+n), [sp-(i+1)*16]!ldr x(0+n), [sp], (i+1)*16Emulate epilog instruction
save_any_reg11100111010nnnnn00iiiiiistp x(0+n), x(1+n), [sp+i*16]ldp x(0+n), x(1+n), [sp+i*16]Emulate epilog instruction
save_any_reg11100111011nnnnn00iiiiiistp x(0+n), x(1+n), [sp-(i+1)*16]!ldp x(0+n), x(1+n), [sp], (i+1)*16Emulate epilog instruction
save_any_reg11100111000nnnnn01iiiiiistr d(0+n), [sp+i*8]ldr d(0+n), [sp+i*8]Emulate epilog instruction
save_any_reg11100111001nnnnn01iiiiiistr d(0+n), [sp-(i+1)*16]!ldr d(0+n), [sp], (i+1)*16Emulate epilog instruction
save_any_reg11100111010nnnnn01iiiiiistp d(0+n), d(1+n), [sp+i*16]ldp d(0+n), d(1+n), [sp+i*16]Emulate epilog instruction
save_any_reg11100111011nnnnn01iiiiiistp d(0+n), d(1+n), [sp-(i+1)*16]!ldp d(0+n), d(1+n), [sp], (i+1)*16Emulate epilog instruction
save_any_reg11100111000nnnnn10iiiiiistr q(0+n), [sp+i*16] (NB: not 8)ldr q(0+n), [sp+i*16] (NB: not 8)Emulate epilog instruction
save_any_reg11100111001nnnnn10iiiiiistr q(0+n), [sp-(i+1)*16]!ldr q(0+n), [sp], (i+1)*16Emulate epilog instruction
save_any_reg11100111010nnnnn10iiiiiistp q(0+n), q(1+n), [sp+i*16]ldp q(0+n), q(1+n), [sp+i*16]Emulate epilog instruction
save_any_reg11100111011nnnnn10iiiiiistp q(0+n), q(1+n), [sp-(i+1)*16]!ldp q(0+n), q(1+n), [sp], (i+1)*16Emulate epilog instruction
reserved111001111xxxxxxxxxxxxxxxAny one instructionAny one instructionUnwind fails
reserved11100111xxxxxxxx11xxxxxxAny one instructionAny one instructionUnwind fails

() The save_next unwind code requires further explanation. It causes the next unwind code to load four registers rather than two. It can be stacked; a sequence of N save_next unwind codes causes the next unwind code thereafter to load (N+1)*2 registers. Said next unwind code must be one of save_regp / save_regp_x / save_r19r20_x / save_fregp / save_fregp_x (notably, this excludes pair instructions with lr in their name). As examples:

The MSDN documentation suggests that a sufficiently long sequence of save_next codes can overflow from x registers to d registers, but best not to try this; stop at x31 or d15.

() In a prolog, end_c corresponds to no instruction, and also causes subsequent codes to correspond to no instruction. In an epilog, end_c corresponds to ret (or b, or any other one instruction that has no unwind effect), and causes subsequent codes to correspond to no instruction. In either case, subsequent codes (up to the first end) are still executed during unwind.

Moving on, each .xdata record describes a contiguous region of machine instructions. There is typically a 1:1 correspondence between regions and functions, though this needn't be true. A region contains:

If any of the above limits would be exceeded, then the region needs to be artifically split into multiple smaller regions, such that no limit is exceeded. The MSDN documentation suggests that split boundaries should be chosen as to not be in the middle of an epilog (this suggestion would not be required if end_c was treated as "No instruction, end of epilog", but alas it is treated as "Any one instruction, then end of epilog").

The length of the prolog is not explicitly given; it is calculated by analysing the unwind codes strictly preceding the first end or end_c and counting how many of them correspond to one instruction. Said unwind codes, once reversed, should correspond 1:1 with instructions in the prolog (nop codes can be used to make things line up, if there are instructions in the prolog which do not need an unwind effect). If unwinding from within the prolog, we calculate how many instructions we are away from the end of the prolog, and skip that many unwind codes from the start of the list. To indicate a lack of prolog, the first unwind code should be end_c (or end if no unwind effects are required by the main body of the region). Note that unwind effects after end_c are still executed if unwinding from within the prolog (though there's a Wine bug here).

The length of each epilog is not explicitly given; it is calculated by analysing the unwind codes starting at the epilog-specific offset and continuing up to and including the first end or end_c thereafter. Said unwind codes should correspond 1:1 with instructions in the epilog (nop codes can be used to make things line up, if there are instructions in the epilog which do not need an unwind effect). If unwinding from within an epilog, we calculate how many instructions we are away from the start of the epilog, and skip that many unwind codes from the start of the list. Note that unwind effects after end_c are still executed if unwinding from within an epilog (though again Wine bug).

If the E bit is set in the .xdata header, then the end of the epilog is equal to the end of the region (the start of the epilog is found by calculating the length of the epilog and subtracting this from the end). If the E bit is not set, then the start of each epilog is explicitly specified.

If unwinding from a PC not within a prolog or epilog, unwind codes are executed until the first end is reached. Note that these codes are shared with those describing the prolog; this is not normally a problem, but if it is, the prolog can be split off to a separate region consisting solely of prolog, leaving no prolog in the original region.

If the X bit is set in the .xdata header, and PC is not within a prolog or epilog, then the specified function is called during exception handler search and during exception unwinding. See __C_specific_handler for the prototype of this function, and see LuaJIT for a concrete example.