Change how unwind information is stored on Windows (#4314)

* Change how unwind information is stored on Windows

Unwind information on Windows is stored in two separate locations. The
first location is the unwind information itself which corresponds to
`UNWIND_INFO`. The second location is a list of `RUNTIME_INFO`
structures which point to function bodes and `UNWIND_INFO` structures.

Currently in Wasmtime the `UNWIND_INFO` structures are stored just after
functions themselves with a somewhat cryptic comment indicating that
Windows prefers this (I'm unsure as to the provenance of this comment).
The `RUNTIME_INFO` data is then stored in a separate section which has
the custom name of `_wasmtime_winx64_unwind`.

After my recent foray into trying to debug windows-2022 bad unwind
information again I realized though that Windows actually has official
sections for these two unwind information items. The `.xdata` section is
used to store the `UNWIND_INFO` structures and the `.pdata` section
stores the `RUNTIME_INFO` list. To try to be somewhat idiomatic and
perhaps one day even hook into standard Windows debugging tools I went
ahead and refactored how our unwind information is stored to match this.

Perhaps the main benefit of this is that it reduces the size of the
read/execute section of the binary. Previously the unwind information
was executable since it was stored in the `.text` section, but
unnecessarily so. Now it's in a read-only section which is in theory a
small amount of hardening.

Otherwise though I don't think this will really help all that much to
hook up in to standard debugging tools like `objdump` because it's all
still stored in an ELF file rather than a COFF file.

* Review comments
This commit is contained in:
Alex Crichton
2022-06-28 10:40:04 -05:00
committed by GitHub
parent fc38f39bd2
commit 66b829b1bf
2 changed files with 79 additions and 42 deletions

View File

@@ -109,9 +109,7 @@ impl<'a> ModuleTextBuilder<'a> {
});
if let Some(info) = &func.unwind_info {
self.unwind_info.push(off, body_len, info, |data, align| {
self.text.append(false, data, Some(align))
});
self.unwind_info.push(off, body_len, info);
}
for r in func.relocations.iter() {
@@ -222,7 +220,8 @@ impl<'a> ModuleTextBuilder<'a> {
/// text section.
#[derive(Default)]
struct UnwindInfoBuilder<'a> {
windows_unwind_info: Vec<RUNTIME_FUNCTION>,
windows_xdata: Vec<u8>,
windows_pdata: Vec<RUNTIME_FUNCTION>,
systemv_unwind_info: Vec<(u64, &'a systemv::UnwindInfo)>,
}
@@ -245,37 +244,47 @@ impl<'a> UnwindInfoBuilder<'a> {
/// the text section itself, and the function's size is specified by
/// `function_len`.
///
/// The `info` should come from Cranelift itself and this function may
/// append more data to the text section in which case the `append_data`
/// callback will be invoked. The `append_data` callback receives the data
/// to append to the text section as well as the alignment it needs to be
/// written at. The return value of `append_data` should be the offset
/// within the text section for where the data was written.
fn push(
&mut self,
function_offset: u64,
function_len: u64,
info: &'a UnwindInfo,
append_data: impl FnOnce(&[u8], u32) -> u64,
) {
/// The `info` should come from Cranelift. and is handled here depending on
/// its flavor.
fn push(&mut self, function_offset: u64, function_len: u64, info: &'a UnwindInfo) {
match info {
// Windows unwind information is preferred to come after the code
// itself. The information is appended here just after the function,
// aligned to 4-bytes as required by Windows.
// Windows unwind information is stored in two locations:
//
// The location of the unwind info, and the function it describes,
// is then recorded in an unwind info table to get embedded into the
// object at the end of compilation.
// * First is the actual unwinding information which is stored
// in the `.xdata` section. This is where `info`'s emitted
// information will go into.
// * Second are pointers to connect all this unwind information,
// stored in the `.pdata` section. The `.pdata` section is an
// array of `RUNTIME_FUNCTION` structures.
//
// Due to how these will be loaded at runtime the `.pdata` isn't
// actually assembled byte-wise here. Instead that's deferred to
// happen later during `write_windows_unwind_info` which will apply
// a further offset to `unwind_address`.
UnwindInfo::WindowsX64(info) => {
// Windows prefers Unwind info after the code -- writing it here.
let unwind_size = info.emit_size();
let mut unwind_info = vec![0; unwind_size];
info.emit(&mut unwind_info);
let unwind_off = append_data(&unwind_info, 4);
self.windows_unwind_info.push(RUNTIME_FUNCTION {
// `.xdata` entries are always 4-byte aligned
//
// FIXME: in theory we could "intern" the `unwind_info` value
// here within the `.xdata` section. Most of our unwind
// information for functions is probably pretty similar in which
// case the `.xdata` could be quite small and `.pdata` could
// have multiple functions point to the same unwinding
// information.
while self.windows_xdata.len() % 4 != 0 {
self.windows_xdata.push(0x00);
}
let unwind_address = self.windows_xdata.len();
self.windows_xdata.extend_from_slice(&unwind_info);
// Record a `RUNTIME_FUNCTION` which this will point to.
self.windows_pdata.push(RUNTIME_FUNCTION {
begin: u32::try_from(function_offset).unwrap(),
end: u32::try_from(function_offset + function_len).unwrap(),
unwind_address: u32::try_from(unwind_off).unwrap(),
unwind_address: u32::try_from(unwind_address).unwrap(),
});
}
@@ -303,15 +312,16 @@ impl<'a> UnwindInfoBuilder<'a> {
let text_section_size =
obj.append_section_data(text_section, &[], isa.code_section_alignment());
if self.windows_unwind_info.len() > 0 {
if self.windows_xdata.len() > 0 {
assert!(self.systemv_unwind_info.len() == 0);
// The `.xdata` section must come first to be just-after the `.text`
// section for the reasons documented in `write_windows_unwind_info`
// below.
let segment = obj.segment_name(StandardSegment::Data).to_vec();
let section_id = obj.add_section(
segment,
b"_wasmtime_winx64_unwind".to_vec(),
SectionKind::ReadOnlyData,
);
self.write_windows_unwind_info(obj, section_id);
let xdata_id = obj.add_section(segment, b".xdata".to_vec(), SectionKind::ReadOnlyData);
let segment = obj.segment_name(StandardSegment::Data).to_vec();
let pdata_id = obj.add_section(segment, b".pdata".to_vec(), SectionKind::ReadOnlyData);
self.write_windows_unwind_info(obj, xdata_id, pdata_id, text_section_size);
}
if self.systemv_unwind_info.len() > 0 {
@@ -331,19 +341,46 @@ impl<'a> UnwindInfoBuilder<'a> {
/// anything on the other end of loading a module from a precompiled object.
///
/// Support for reading this is in `crates/jit/src/unwind/winx64.rs`.
fn write_windows_unwind_info(&self, obj: &mut Object<'_>, section_id: SectionId) {
fn write_windows_unwind_info(
&self,
obj: &mut Object<'_>,
xdata_id: SectionId,
pdata_id: SectionId,
text_section_size: u64,
) {
// Currently the binary format supported here only supports
// little-endian for x86_64, or at least that's all where it's tested.
// This may need updates for other platforms.
assert_eq!(obj.architecture(), Architecture::X86_64);
let mut unwind_info = Vec::with_capacity(self.windows_unwind_info.len() * 3 * 4);
for info in self.windows_unwind_info.iter() {
unwind_info.extend_from_slice(&info.begin.to_le_bytes());
unwind_info.extend_from_slice(&info.end.to_le_bytes());
unwind_info.extend_from_slice(&info.unwind_address.to_le_bytes());
// Append the `.xdata` section, or the actual unwinding information
// codes and such which were built as we found unwind information for
// functions.
obj.append_section_data(xdata_id, &self.windows_xdata, 4);
// Next append the `.pdata` section, or the array of `RUNTIME_FUNCTION`
// structures stored in the binary.
//
// This memory will be passed at runtime to `RtlAddFunctionTable` which
// takes a "base address" and the entries within `RUNTIME_FUNCTION` are
// all relative to this base address. The base address we pass is the
// address of the text section itself so all the pointers here must be
// text-section-relative. The `begin` and `end` fields for the function
// it describes are already text-section-relative, but the
// `unwind_address` field needs to be updated here since the value
// stored right now is `xdata`-section-relative. We know that the
// `xdata` section follows the `.text` section so the
// `text_section_size` is added in to calculate the final
// `.text`-section-relative address of the unwind information.
let mut pdata = Vec::with_capacity(self.windows_pdata.len() * 3 * 4);
for info in self.windows_pdata.iter() {
pdata.extend_from_slice(&info.begin.to_le_bytes());
pdata.extend_from_slice(&info.end.to_le_bytes());
let address = text_section_size + u64::from(info.unwind_address);
let address = u32::try_from(address).unwrap();
pdata.extend_from_slice(&address.to_le_bytes());
}
obj.append_section_data(section_id, &unwind_info, 4);
obj.append_section_data(pdata_id, &pdata, 4);
}
/// This function appends a nonstandard section to the object which is only

View File

@@ -33,7 +33,7 @@ impl UnwindRegistration {
}
pub fn section_name() -> &'static str {
"_wasmtime_winx64_unwind"
".pdata"
}
}