Skip to content

Commit d4244cc

Browse files
authored
PE: Add parser for C_SCOPE_TABLE (#477)
* PE: Add parser for `C_SCOPE_TABLE` * Make an internal `offset` in `ScopeTableIterator` * make `ScopeTableIterator` infallible
1 parent eb80629 commit d4244cc

File tree

1 file changed

+190
-1
lines changed

1 file changed

+190
-1
lines changed

src/pe/exception.rs

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use core::fmt;
4444
use core::iter::FusedIterator;
4545

4646
use scroll::ctx::TryFromCtx;
47-
use scroll::{self, Pread, Pwrite};
47+
use scroll::{self, Pread, Pwrite, SizeWith};
4848

4949
use crate::error;
5050

@@ -96,6 +96,68 @@ const RUNTIME_FUNCTION_SIZE: usize = 12;
9696
/// Size of unwind code slots. Codes take 1 - 3 slots.
9797
const 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

633709
impl 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

Comments
 (0)