@@ -44,7 +44,7 @@ use core::fmt;
4444use core:: iter:: FusedIterator ;
4545
4646use scroll:: ctx:: TryFromCtx ;
47- use scroll:: { self , Pread , Pwrite } ;
47+ use scroll:: { self , Pread , Pwrite , SizeWith } ;
4848
4949use crate :: error;
5050
@@ -96,6 +96,68 @@ const RUNTIME_FUNCTION_SIZE: usize = 12;
9696/// Size of unwind code slots. Codes take 1 - 3 slots.
9797const UNWIND_CODE_SIZE : usize = 2 ;
9898
99+ /// Represents a single entry in a Windows PE exception handling scope table `C_SCOPE_TABLE_ENTRY`.
100+ ///
101+ /// Each entry defines a protected range of code and its associated exception handler.
102+ /// These entries are typically found in the scope table associated with `UNWIND_INFO`
103+ /// structures in Windows x64 exception handling.
104+ #[ derive( Debug , Copy , Clone , Default , PartialEq , Hash , Pread , Pwrite , SizeWith ) ]
105+ #[ repr( C ) ]
106+ pub struct ScopeTableEntry {
107+ /// The starting RVA (relative virtual address) of the protected code region.
108+ ///
109+ /// This marks the beginning of a `try` block.
110+ pub begin : u32 ,
111+
112+ /// The ending RVA (exclusive) of the protected code region.
113+ ///
114+ /// This marks the end of the `try` block.
115+ pub end : u32 ,
116+
117+ /// The RVA of the exception handler function.
118+ ///
119+ /// e.g., be invoked when an exception occurs in the associated code range.
120+ pub handler : u32 ,
121+
122+ /// The RVA of the continuation target after the handler is executed.
123+ ///
124+ /// This is used for control transfer (e.g., continuation blocks, to resume execution after `finally`).
125+ pub target : u32 ,
126+ }
127+
128+ /// Iterator over [ScopeTableEntry] entries in `C_SCOPE_TABLE`.
129+ #[ derive( Debug ) ]
130+ pub struct ScopeTableIterator < ' a > {
131+ data : & ' a [ u8 ] ,
132+ offset : usize ,
133+ }
134+
135+ impl Iterator for ScopeTableIterator < ' _ > {
136+ type Item = ScopeTableEntry ;
137+
138+ fn next ( & mut self ) -> Option < Self :: Item > {
139+ if self . offset >= self . data . len ( ) {
140+ return None ;
141+ }
142+
143+ // It is guaranteed that .expect here is really a unreachable.
144+ // See: that we do `num_entries * core::mem::size_of::<ScopeTableEntry>() as u32;`
145+ Some (
146+ self . data
147+ . gread_with ( & mut self . offset , scroll:: LE )
148+ . expect ( "Scope table is not aligned" ) ,
149+ )
150+ }
151+
152+ fn size_hint ( & self ) -> ( usize , Option < usize > ) {
153+ let len = self . data . len ( ) / core:: mem:: size_of :: < ScopeTableEntry > ( ) ;
154+ ( len, Some ( len) )
155+ }
156+ }
157+
158+ impl FusedIterator for ScopeTableIterator < ' _ > { }
159+ impl ExactSizeIterator for ScopeTableIterator < ' _ > { }
160+
99161/// An unwind entry for a range of a function.
100162///
101163/// Unwind information for this function can be loaded with [`ExceptionData::get_unwind_info`].
@@ -628,6 +690,20 @@ impl<'a> UnwindInfo<'a> {
628690 } ,
629691 }
630692 }
693+
694+ /// Returns an iterator over C scope table entries in this unwind info.
695+ ///
696+ /// If this unwind info has no [UnwindHandler::ExceptionHandler], this will always return `None`.
697+ pub fn c_scope_table_entries ( & self ) -> Option < ScopeTableIterator < ' a > > {
698+ let UnwindHandler :: ExceptionHandler ( _, data) = self . handler ? else {
699+ return None ;
700+ } ;
701+ let mut offset = 0 ;
702+ let num_entries = data. gread_with :: < u32 > ( & mut offset, scroll:: LE ) . ok ( ) ?;
703+ let table_size = num_entries * core:: mem:: size_of :: < ScopeTableEntry > ( ) as u32 ;
704+ let data = data. pread_with :: < & [ u8 ] > ( offset, table_size as usize ) . ok ( ) ?;
705+ Some ( ScopeTableIterator { data, offset : 0 } )
706+ }
631707}
632708
633709impl fmt:: Debug for UnwindInfo < ' _ > {
@@ -1059,4 +1135,117 @@ mod tests {
10591135
10601136 assert_eq ! ( unwind_codes[ 0 ] , expected) ;
10611137 }
1138+
1139+ #[ rustfmt:: skip]
1140+ const UNWIND_INFO_C_SCOPE_TABLE : & [ u8 ] = & [
1141+ // UNWIND_INFO_HDR
1142+ 0x09 , 0x0F , 0x06 , 0x00 ,
1143+
1144+ // UNWIND_CODEs
1145+ 0x0F , 0x64 , // UWOP_SAVE_NONVOL (Offset=6, Reg=0x0F)
1146+ 0x09 , 0x00 ,
1147+ 0x0F , 0x34 , // UWOP_SAVE_NONVOL (Offset=3, Reg=0x0F)
1148+ 0x08 , 0x00 ,
1149+ 0x0F , 0x52 , // UWOP_ALLOC_SMALL (Size = (2 * 8) + 8 = 24 bytes)
1150+ 0x0B , 0x70 , // UWOP_PUSH_NONVOL (Reg=0x0B)
1151+
1152+ // Exception handler RVA
1153+ 0xC0 , 0x1F , 0x00 , 0x00 , // __C_specific_handler
1154+
1155+ // Scope count
1156+ 0x02 , 0x00 , 0x00 , 0x00 , // Scope table count = 2
1157+
1158+ // First C_SCOPE_TABLE entry
1159+ 0x01 , 0x15 , 0x00 , 0x00 , // BeginAddress = 0x00001501
1160+ 0x06 , 0x16 , 0x00 , 0x00 , // EndAddress = 0x00001606
1161+ 0x76 , 0x1F , 0x00 , 0x00 , // HandlerAddress = 0x00001F76
1162+ 0x06 , 0x16 , 0x00 , 0x00 , // JumpTarget = 0x00001606
1163+
1164+ // Second C_SCOPE_TABLE entry
1165+ 0x3A , 0x16 , 0x00 , 0x00 , // BeginAddress = 0x0000163A
1166+ 0x4C , 0x16 , 0x00 , 0x00 , // EndAddress = 0x0000164C
1167+ 0x76 , 0x1F , 0x00 , 0x00 , // HandlerAddress = 0x00001F76
1168+ 0x06 , 0x16 , 0x00 , 0x00 , // JumpTarget = 0x00001606
1169+ ] ;
1170+
1171+ #[ rustfmt:: skip]
1172+ const UNWIND_INFO_C_SCOPE_TABLE_INVALID : & [ u8 ] = & [
1173+ // UNWIND_INFO_HDR
1174+ 0x09 , 0x0F , 0x06 , 0x00 ,
1175+
1176+ // UNWIND_CODEs
1177+ 0x0F , 0x64 , // UWOP_SAVE_NONVOL (Offset=6, Reg=0x0F)
1178+ 0x09 , 0x00 ,
1179+ 0x0F , 0x34 , // UWOP_SAVE_NONVOL (Offset=3, Reg=0x0F)
1180+ 0x08 , 0x00 ,
1181+ 0x0F , 0x52 , // UWOP_ALLOC_SMALL (Size = (2 * 8) + 8 = 24 bytes)
1182+ 0x0B , 0x70 , // UWOP_PUSH_NONVOL (Reg=0x0B)
1183+
1184+ // Exception handler RVA
1185+ 0xC0 , 0x1F , 0x00 , 0x00 , // __C_specific_handler
1186+
1187+ // Scope count
1188+ 0x02 , 0x00 , 0x00 , 0x00 , // Scope table count = 2
1189+
1190+ // First C_SCOPE_TABLE entry
1191+ 0x01 , 0x15 , 0x00 , 0x00 , // BeginAddress = 0x00001501
1192+ 0x06 , 0x16 , 0x00 , 0x00 , // EndAddress = 0x00001606
1193+ 0x76 , 0x1F , 0x00 , 0x00 , // HandlerAddress = 0x00001F76
1194+ 0x06 , 0x16 , 0x00 , 0x00 , // JumpTarget = 0x00001606
1195+
1196+ // Second C_SCOPE_TABLE entry
1197+ 0x3A , 0x16 , 0x00 , 0x00 , // BeginAddress = 0x0000163A
1198+ 0x4C , 0x16 , 0x00 , 0x00 , // EndAddress = 0x0000164C
1199+ 0x76 , 0x1F , 0x00 , 0x00 , // HandlerAddress = 0x00001F76
1200+ 0x06 , // JumpTarget = 0x??????06
1201+ ] ;
1202+
1203+ #[ test]
1204+ fn parse_c_scope_table ( ) {
1205+ let unwind_info = UnwindInfo :: parse ( UNWIND_INFO_C_SCOPE_TABLE , 0 )
1206+ . expect ( "Failed to parse unwind info with C scope table" ) ;
1207+ let entries = unwind_info
1208+ . c_scope_table_entries ( )
1209+ . expect ( "C scope table should present" ) ;
1210+ let entries = entries. collect :: < Vec < _ > > ( ) ;
1211+ assert_eq ! ( entries. len( ) , 2 ) ;
1212+ assert_eq ! (
1213+ entries[ 0 ] ,
1214+ ScopeTableEntry {
1215+ begin: 0x00001501 ,
1216+ end: 0x00001606 ,
1217+ handler: 0x00001F76 ,
1218+ target: 0x00001606 ,
1219+ }
1220+ ) ;
1221+ assert_eq ! (
1222+ entries[ 1 ] ,
1223+ ScopeTableEntry {
1224+ begin: 0x0000163A ,
1225+ end: 0x0000164C ,
1226+ handler: 0x00001F76 ,
1227+ target: 0x00001606 ,
1228+ }
1229+ ) ;
1230+ }
1231+
1232+ #[ test]
1233+ #[ should_panic( expected = "C scope table should present" ) ]
1234+ fn malformed_scope_table_is_not_allowed ( ) {
1235+ let unwind_info = UnwindInfo :: parse ( UNWIND_INFO_C_SCOPE_TABLE_INVALID , 0 )
1236+ . expect ( "Failed to parse unwind info with C scope table" ) ;
1237+ unwind_info
1238+ . c_scope_table_entries ( )
1239+ . expect ( "C scope table should present" ) ;
1240+ }
1241+
1242+ #[ test]
1243+ #[ should_panic( expected = "Scope table is not aligned" ) ]
1244+ fn unaligned_scope_table_is_not_allowed ( ) {
1245+ let it = ScopeTableIterator {
1246+ data : & [ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ] ,
1247+ offset : 0 ,
1248+ } ;
1249+ let _ = it. collect :: < Vec < _ > > ( ) ;
1250+ }
10621251}
0 commit comments