@@ -312,6 +312,7 @@ impl fmt::Display for Register {
312312/// Unwind operations can be used to reverse the effects of the function prolog and restore register
313313/// values of parent stack frames that have been saved to the stack.
314314#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
315+ #[ non_exhaustive]
315316pub enum UnwindOperation {
316317 /// Push a nonvolatile integer register, decrementing `RSP` by 8.
317318 PushNonVolatile ( Register ) ,
@@ -333,10 +334,45 @@ pub enum UnwindOperation {
333334 /// Save the lower 64 bits of a nonvolatile XMM register on the stack.
334335 SaveXMM ( Register , StackFrameOffset ) ,
335336
336- /// Describes the function epilog.
337+ /// Describes the function epilog location and size (Version 2) .
337338 ///
338- /// This operation has been introduced with unwind info version 2 and is not implemented yet.
339- Epilog ,
339+ /// [UWOP_EPILOG] entries work together to describe where epilogues are located in the function,
340+ /// but not what operations they perform. The actual operations performed during epilogue
341+ /// execution are the reverse of the prolog operations.
342+ ///
343+ /// These entries always appear at the beginning of the [UnwindCode] array, with a minimum of
344+ /// two entries required for alignment purposes, even when only one epilogue exists. The first
345+ /// [UWOP_EPILOG] entry is special as it contains the size of the epilogue in its `offset_low_or_size`
346+ /// field. If bit `0` of its `offset_high_or_flags` field is set, this first entry also describes
347+ /// an epilogue located at the function's end, with `offset_low_or_size` serving dual purpose as
348+ /// both size and offset. When bit `0` is not set, the first entry contains only the size, and
349+ /// subsequent [UWOP_EPILOG] entries provide the epilogue locations.
350+ ///
351+ /// Each subsequent [UWOP_EPILOG] entry describes an additional epilogue location using a 12-bit
352+ /// offset from the function's end address. The offset is formed by combining `offset_low_or_size`
353+ /// (lower 8 bits) with the lower 4 bits of `offset_high_or_flags` (upper 4 bits). The epilogue's
354+ /// starting address is calculated by subtracting this offset from the function's `EndAddress`.
355+ Epilog {
356+ /// For the first [UWOP_EPILOG] entry:
357+ /// * Size of the epilogue in bytes
358+ /// * If `offset_high_or_flags` bit 0 is set, also serves as offset
359+ ///
360+ /// For subsequent entries:
361+ /// * Lower 8 bits of the offset from function end
362+ offset_low_or_size : u8 ,
363+
364+ /// For the first [UWOP_EPILOG] entry:
365+ /// * Bit 0: If set, epilogue is at function end and `offset_low_or_size`
366+ /// is also the offset
367+ /// * Bits 1-3: Reserved/unused
368+ ///
369+ /// For subsequent entries:
370+ /// * Upper 4 bits of the offset from function end (bits 0-3)
371+ ///
372+ /// The complete offset is computed as:
373+ /// `EndAddress` - (`offset_high_or_flags` << 8 | `offset_low_or_size`)
374+ offset_high_or_flags : u8 ,
375+ } ,
340376
341377 /// Save all 128 bits of a nonvolatile XMM register on the stack.
342378 SaveXMM128 ( Register , StackFrameOffset ) ,
@@ -452,22 +488,40 @@ impl<'a> TryFromCtx<'a, UnwindOpContext> for UnwindCode {
452488 UnwindOperation :: SaveNonVolatile ( register, StackFrameOffset :: with_ctx ( offset, ctx) )
453489 }
454490 self :: UWOP_EPILOG => {
455- let data = u32:: from ( bytes. gread_with :: < u16 > ( & mut read, scroll:: LE ) ?) * 16 ;
456491 if ctx. version == 1 {
492+ // Version 1: This was UWOP_SAVE_XMM
493+ let data = u32:: from ( bytes. gread_with :: < u16 > ( & mut read, scroll:: LE ) ?) * 16 ;
457494 let register = Register :: xmm ( operation_info) ;
458495 UnwindOperation :: SaveXMM ( register, StackFrameOffset :: with_ctx ( data, ctx) )
496+ } else if ctx. version == 2 {
497+ // Version 2: UWOP_EPILOG - describes epilogue locations
498+ // See https://github.com/BlancLoup/weekly-geekly.github.io/blob/1cbdf1c6127fcdaeda1f01bcbb006febd17d5a95/articles/322956/index.html
499+ // See https://github.com/Montura/cpp/blob/4393c678ee8e44dd98feb7a198c3983a6108cef8/src/exception_handling/msvc/eh_msvc_cxx_EH_x64.md?plain=1#L119
500+ UnwindOperation :: Epilog {
501+ offset_low_or_size : code_offset,
502+ offset_high_or_flags : operation_info,
503+ }
459504 } else {
460- // TODO: See https://weekly-geekly.github.io/articles/322956/index.html
461- UnwindOperation :: Epilog
505+ let msg = format ! (
506+ "Unwind info version has to be either one of `1` or `2`: {}" ,
507+ ctx. version
508+ ) ;
509+ return Err ( error:: Error :: Malformed ( msg) ) ;
462510 }
463511 }
464512 self :: UWOP_SPARE_CODE => {
465513 let data = bytes. gread_with :: < u32 > ( & mut read, scroll:: LE ) ?;
466514 if ctx. version == 1 {
467515 let register = Register :: xmm ( operation_info) ;
468516 UnwindOperation :: SaveXMM128 ( register, StackFrameOffset :: with_ctx ( data, ctx) )
469- } else {
517+ } else if ctx . version == 2 {
470518 UnwindOperation :: Noop
519+ } else {
520+ let msg = format ! (
521+ "Unwind info version has to be either one of `1` or `2`: {}" ,
522+ ctx. version
523+ ) ;
524+ return Err ( error:: Error :: Malformed ( msg) ) ;
471525 }
472526 }
473527 self :: UWOP_SAVE_XMM128 => {
@@ -1110,6 +1164,118 @@ mod tests {
11101164 // assert_eq!(unwind_info.code_bytes, expected);
11111165 // }
11121166
1167+ #[ test]
1168+ fn test_unwind_codes_one_epilog ( ) {
1169+ // ; Prologue
1170+ // .text:00000001400884E0 000 57 push rdi // Save RDI
1171+ //
1172+ // .text:00000001400884E1 008 8B C2 mov eax, edx
1173+ // .text:00000001400884E3 008 48 8B F9 mov rdi, rcx
1174+ // .text:00000001400884E6 008 49 8B C8 mov rcx, r8
1175+ // .text:00000001400884E9 008 F3 AA rep stosb
1176+ // .text:00000001400884EB 008 49 8B C1 mov rax, r9
1177+ //
1178+ // ; Epilogue
1179+ // .text:00000001400884EE 008 5F pop rdi // Restore RDI
1180+ // .text:00000001400884EF 000 C3 retn
1181+
1182+ const BYTES : & [ u8 ] = & [
1183+ 0x02 , 0x01 , 0x03 , 0x00 , // UNWIND_INFO_HDR <2, 0, 1, 3, 0, 0>
1184+ 0x02 , 0x16 , // UNWIND_CODE <<2, 6, 1>> ; UWOP_EPILOG
1185+ 0x00 , 0x06 , // UNWIND_CODE <<0, 6, 0>> ; UWOP_EPILOG
1186+ 0x01 , 0x70 , // UNWIND_CODE <<1, 0, 7>> ; UWOP_PUSH_NONVOL
1187+ ] ;
1188+
1189+ let unwind_info = UnwindInfo :: parse ( BYTES , 0 ) . unwrap ( ) ;
1190+ let unwind_codes: Vec < UnwindCode > = unwind_info
1191+ . unwind_codes ( )
1192+ . map ( |result| result. expect ( "Unable to parse unwind code" ) )
1193+ . collect ( ) ;
1194+ assert_eq ! ( unwind_codes. len( ) , 3 ) ;
1195+ assert_eq ! (
1196+ unwind_codes,
1197+ [
1198+ UnwindCode {
1199+ code_offset: 2 ,
1200+ operation: UnwindOperation :: Epilog {
1201+ offset_low_or_size: 2 ,
1202+ offset_high_or_flags: 1 ,
1203+ } ,
1204+ } ,
1205+ UnwindCode {
1206+ code_offset: 0 ,
1207+ operation: UnwindOperation :: Epilog {
1208+ offset_low_or_size: 0 ,
1209+ offset_high_or_flags: 0 ,
1210+ } ,
1211+ } ,
1212+ UnwindCode {
1213+ code_offset: 1 ,
1214+ operation: UnwindOperation :: PushNonVolatile ( Register ( 7 ) ) , // RDI
1215+ } ,
1216+ ]
1217+ ) ;
1218+ }
1219+
1220+ #[ test]
1221+ fn test_unwind_codes_two_epilog ( ) {
1222+ // ; Prologue
1223+ // .text:00000001400889A0 000 57 push rdi // Save RDI
1224+ // .text:00000001400889A1 008 56 push rsi // Save RSI
1225+ //
1226+ // .text:00000001400889A2 010 48 8B F9 mov rdi, rcx
1227+ // .text:00000001400889A5 010 48 8B F2 mov rsi, rdx
1228+ // .text:00000001400889A8 010 49 8B C8 mov rcx, r8
1229+ // .text:00000001400889AB 010 F3 A4 rep movsb
1230+ //
1231+ // ; Epilogue
1232+ // .text:00000001400889AD 010 5E pop rsi // Restore RSI
1233+ // .text:00000001400889AE 008 5F pop rdi // Restore RDI
1234+ // .text:00000001400889AF 000 C3 retn
1235+
1236+ const BYTES : & [ u8 ] = & [
1237+ 0x02 , 0x02 , 0x04 , 0x00 , // $xdatasym_5 UNWIND_INFO_HDR <2, 0, 2, 4, 0, 0>
1238+ 0x03 , 0x16 , // UNWIND_CODE <<3, 6, 1>> ; UWOP_EPILOG
1239+ 0x00 , 0x06 , // UNWIND_CODE <<0, 6, 0>> ; UWOP_EPILOG
1240+ 0x02 , 0x60 , // UNWIND_CODE <<2, 0, 6>> ; UWOP_PUSH_NONVOL
1241+ 0x01 , 0x70 , // UNWIND_CODE <<1, 0, 7>> ; UWOP_PUSH_NONVOL
1242+ ] ;
1243+
1244+ let unwind_info = UnwindInfo :: parse ( BYTES , 0 ) . unwrap ( ) ;
1245+ let unwind_codes: Vec < UnwindCode > = unwind_info
1246+ . unwind_codes ( )
1247+ . map ( |result| result. expect ( "Unable to parse unwind code" ) )
1248+ . collect ( ) ;
1249+ assert_eq ! ( unwind_codes. len( ) , 4 ) ;
1250+ assert_eq ! (
1251+ unwind_codes,
1252+ [
1253+ UnwindCode {
1254+ code_offset: 3 ,
1255+ operation: UnwindOperation :: Epilog {
1256+ offset_low_or_size: 3 ,
1257+ offset_high_or_flags: 1 ,
1258+ } ,
1259+ } ,
1260+ UnwindCode {
1261+ code_offset: 0 ,
1262+ operation: UnwindOperation :: Epilog {
1263+ offset_low_or_size: 0 ,
1264+ offset_high_or_flags: 0 ,
1265+ } ,
1266+ } ,
1267+ UnwindCode {
1268+ code_offset: 2 ,
1269+ operation: UnwindOperation :: PushNonVolatile ( Register ( 6 ) ) , // RSI
1270+ } ,
1271+ UnwindCode {
1272+ code_offset: 1 ,
1273+ operation: UnwindOperation :: PushNonVolatile ( Register ( 7 ) ) , // RDI
1274+ } ,
1275+ ]
1276+ ) ;
1277+ }
1278+
11131279 #[ test]
11141280 fn test_iter_unwind_codes ( ) {
11151281 let unwind_info = UnwindInfo {
0 commit comments