Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PE: Support multiple debug directories and VCFeature metadata #403

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
177 changes: 127 additions & 50 deletions src/pe/debug.rs
@@ -1,15 +1,17 @@
use crate::error;
use alloc::vec::Vec;
use scroll::{Pread, Pwrite, SizeWith};

use crate::pe::data_directories;
use crate::pe::options;
use crate::pe::section_table;
use crate::pe::utils;

#[derive(Debug, PartialEq, Copy, Clone, Default)]
#[derive(Debug, PartialEq, Clone, Default)]
pub struct DebugData<'a> {
pub image_debug_directory: ImageDebugDirectory,
pub image_debug_directories: Vec<ImageDebugDirectory>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you know if there is a maximum image debug directories possible? if yes, perhaps we should make this an array and put it back to Copy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub codeview_pdb70_debug_info: Option<CodeviewPDB70DebugInfo<'a>>,
pub vcfeature_info: Option<VCFeatureInfo>,
}

impl<'a> DebugData<'a> {
Expand All @@ -35,21 +37,30 @@ impl<'a> DebugData<'a> {
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
let image_debug_directory =
let image_debug_directories =
ImageDebugDirectory::parse_with_opts(bytes, dd, sections, file_alignment, opts)?;
let codeview_pdb70_debug_info =
CodeviewPDB70DebugInfo::parse_with_opts(bytes, &image_debug_directory, opts)?;
CodeviewPDB70DebugInfo::parse_with_opts(bytes, &image_debug_directories, opts)?;
let vcfeature_info = VCFeatureInfo::parse_with_opts(bytes, &image_debug_directories, opts)?;

Ok(DebugData {
image_debug_directory,
image_debug_directories,
codeview_pdb70_debug_info,
vcfeature_info,
})
}

/// Return this executable's debugging GUID, suitable for matching against a PDB file.
pub fn guid(&self) -> Option<[u8; 16]> {
self.codeview_pdb70_debug_info.map(|pdb70| pdb70.signature)
}

/// Find a specific debug type in the debug data.
pub fn find_type(&self, data_type: u32) -> Option<&ImageDebugDirectory> {
self.image_debug_directories
.iter()
.find(|idd| idd.data_type == data_type)
}
}

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680307(v=vs.85).aspx
Expand All @@ -74,6 +85,7 @@ pub const IMAGE_DEBUG_TYPE_MISC: u32 = 4;
pub const IMAGE_DEBUG_TYPE_EXCEPTION: u32 = 5;
pub const IMAGE_DEBUG_TYPE_FIXUP: u32 = 6;
pub const IMAGE_DEBUG_TYPE_BORLAND: u32 = 9;
pub const IMAGE_DEBUG_TYPE_VC_FEATURE: u32 = 12;

impl ImageDebugDirectory {
#[allow(unused)]
Expand All @@ -82,7 +94,7 @@ impl ImageDebugDirectory {
dd: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
) -> error::Result<Self> {
) -> error::Result<Vec<Self>> {
Self::parse_with_opts(
bytes,
dd,
Expand All @@ -98,16 +110,22 @@ impl ImageDebugDirectory {
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
) -> error::Result<Vec<Self>> {
let rva = dd.virtual_address as usize;
let entries = dd.size as usize / core::mem::size_of::<ImageDebugDirectory>();
let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"Cannot map ImageDebugDirectory rva {:#x} into offset",
rva
))
})?;
let idd: Self = bytes.pread_with(offset, scroll::LE)?;
Ok(idd)
let idd_list = (0..entries)
.map(|i| {
let entry = offset + i * core::mem::size_of::<ImageDebugDirectory>();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice :)

bytes.pread_with(entry, scroll::LE)
})
.collect::<Result<Vec<ImageDebugDirectory>, scroll::Error>>()?;
Ok(idd_list)
}
}

Expand All @@ -127,60 +145,119 @@ pub struct CodeviewPDB70DebugInfo<'a> {
}

impl<'a> CodeviewPDB70DebugInfo<'a> {
pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Option<Self>> {
pub fn parse(bytes: &'a [u8], idd: &Vec<ImageDebugDirectory>) -> error::Result<Option<Self>> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should take a &[ImageDebugDirectory] to be more idiomatic rust

Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
}

pub fn parse_with_opts(
bytes: &'a [u8],
idd: &ImageDebugDirectory,
idd: &Vec<ImageDebugDirectory>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here

opts: &options::ParseOptions,
) -> error::Result<Option<Self>> {
if idd.data_type != IMAGE_DEBUG_TYPE_CODEVIEW {
// not a codeview debug directory
// that's not an error, but it's not a CodeviewPDB70DebugInfo either
return Ok(None);
}
let idd = idd
.iter()
.find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_CODEVIEW);

// ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly
let mut offset: usize = match opts.resolve_rva {
true => idd.pointer_to_raw_data as usize,
false => idd.address_of_raw_data as usize,
};

// calculate how long the eventual filename will be, which doubles as a check of the record size
let filename_length = idd.size_of_data as isize - 24;
if filename_length < 0 {
// the record is too short to be plausible
return Err(error::Error::Malformed(format!(
"ImageDebugDirectory size of data seems wrong: {:?}",
idd.size_of_data
)));
}
let filename_length = filename_length as usize;
if let Some(idd) = idd {
// ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly
let mut offset: usize = match opts.resolve_rva {
true => idd.pointer_to_raw_data as usize,
false => idd.address_of_raw_data as usize,
};

// check the codeview signature
let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
if codeview_signature != CODEVIEW_PDB70_MAGIC {
return Ok(None);
// calculate how long the eventual filename will be, which doubles as a check of the record size
let filename_length = idd.size_of_data as isize - 24;
if filename_length < 0 {
// the record is too short to be plausible
return Err(error::Error::Malformed(format!(
"ImageDebugDirectory size of data seems wrong: {:?}",
idd.size_of_data
)));
}
let filename_length = filename_length as usize;

// check the codeview signature
let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
if codeview_signature != CODEVIEW_PDB70_MAGIC {
return Ok(None);
}

// read the rest
let mut signature: [u8; 16] = [0; 16];
signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?);
let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
if let Some(filename) = bytes.get(offset..offset + filename_length) {
Ok(Some(CodeviewPDB70DebugInfo {
codeview_signature,
signature,
age,
filename,
}))
} else {
Err(error::Error::Malformed(format!(
"ImageDebugDirectory seems corrupted: {:?}",
idd
)))
}
} else {
// CodeView debug info not found
Ok(None)
}
}
}

// read the rest
let mut signature: [u8; 16] = [0; 16];
signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?);
let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
if let Some(filename) = bytes.get(offset..offset + filename_length) {
Ok(Some(CodeviewPDB70DebugInfo {
codeview_signature,
signature,
age,
filename,
/// Represents the `IMAGE_DEBUG_VC_FEATURE_ENTRY` structure
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct VCFeatureInfo {
/// The count of pre-VC++
pub pre_vc_plusplus_count: u32,
/// The count of C and C++
pub c_and_cplusplus_count: u32,
/// The count of guard stack
pub guard_stack_count: u32,
/// The count of SDL
pub sdl_count: u32,
/// The count of guard
pub guard_count: u32,
}

impl<'a> VCFeatureInfo {
pub fn parse(bytes: &'a [u8], idd: &Vec<ImageDebugDirectory>) -> error::Result<Option<Self>> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, &[IMageDebugDirectory]

Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
}

pub fn parse_with_opts(
bytes: &'a [u8],
idd: &Vec<ImageDebugDirectory>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

opts: &options::ParseOptions,
) -> error::Result<Option<Self>> {
let idd = idd
.iter()
.find(|idd| idd.data_type == IMAGE_DEBUG_TYPE_VC_FEATURE);

if let Some(idd) = idd {
let mut offset: usize = match opts.resolve_rva {
true => idd.pointer_to_raw_data as usize,
false => idd.address_of_raw_data as usize,
};

let pre_vc_plusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
let c_and_cplusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
let guard_stack_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
let sdl_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
let guard_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;

Ok(Some(VCFeatureInfo {
pre_vc_plusplus_count,
c_and_cplusplus_count,
guard_stack_count,
sdl_count,
guard_count,
}))
} else {
Err(error::Error::Malformed(format!(
"ImageDebugDirectory seems corrupted: {:?}",
idd
)))
// VC Feature info not found
return Ok(None);
}
}
}
42 changes: 17 additions & 25 deletions src/pe/mod.rs
Expand Up @@ -509,7 +509,7 @@ impl<'a> TE<'a> {

// Parse the debug data. Must adjust offsets before parsing the image_debug_directory
let mut debug_data = debug::DebugData::default();
debug_data.image_debug_directory = debug::ImageDebugDirectory::parse_with_opts(
debug_data.image_debug_directories = debug::ImageDebugDirectory::parse_with_opts(
bytes,
header.debug_dir,
&sections,
Expand All @@ -519,7 +519,7 @@ impl<'a> TE<'a> {
TE::fixup_debug_data(&mut debug_data, rva_offset as u32);
debug_data.codeview_pdb70_debug_info = debug::CodeviewPDB70DebugInfo::parse_with_opts(
bytes,
&debug_data.image_debug_directory,
&debug_data.image_debug_directories,
opts,
)?;

Expand All @@ -533,29 +533,21 @@ impl<'a> TE<'a> {

/// Adjust all addresses in the TE binary debug data.
fn fixup_debug_data(dd: &mut debug::DebugData, rva_offset: u32) {
debug!(
"ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}",
dd.image_debug_directory.address_of_raw_data,
dd.image_debug_directory
.address_of_raw_data
.wrapping_sub(rva_offset),
);
dd.image_debug_directory.address_of_raw_data = dd
.image_debug_directory
.address_of_raw_data
.wrapping_sub(rva_offset);

debug!(
"ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}",
dd.image_debug_directory.pointer_to_raw_data,
dd.image_debug_directory
.pointer_to_raw_data
.wrapping_sub(rva_offset),
);
dd.image_debug_directory.pointer_to_raw_data = dd
.image_debug_directory
.pointer_to_raw_data
.wrapping_sub(rva_offset);
dd.image_debug_directories.iter_mut().for_each(|idd| {
debug!(
"ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}",
idd.address_of_raw_data,
idd.address_of_raw_data.wrapping_sub(rva_offset),
);
idd.address_of_raw_data = idd.address_of_raw_data.wrapping_sub(rva_offset);

debug!(
"ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}",
idd.pointer_to_raw_data,
idd.pointer_to_raw_data.wrapping_sub(rva_offset),
);
idd.pointer_to_raw_data = idd.pointer_to_raw_data.wrapping_sub(rva_offset);
});
}
}

Expand Down
6 changes: 3 additions & 3 deletions tests/te.rs
Expand Up @@ -54,13 +54,13 @@ mod te_tests {
assert_eq!(te.sections[4].pointer_to_relocations, 0);

// Verify the debug directory is correct
assert_eq!(te.debug_data.image_debug_directory.size_of_data, 0xab);
assert_eq!(te.debug_data.image_debug_directories[0].size_of_data, 0xab);
assert_eq!(
te.debug_data.image_debug_directory.address_of_raw_data,
te.debug_data.image_debug_directories[0].address_of_raw_data,
0x3b54
);
assert_eq!(
te.debug_data.image_debug_directory.pointer_to_raw_data,
te.debug_data.image_debug_directories[0].pointer_to_raw_data,
0x3b54
);
let debug_info = te.debug_data.codeview_pdb70_debug_info.unwrap();
Expand Down