Skip to main content

goblin_experimental/pe/
debug.rs

1use core::iter::FusedIterator;
2
3use crate::error;
4use log::debug;
5use scroll::{Pread, Pwrite, SizeWith};
6
7use crate::pe::data_directories;
8use crate::pe::options;
9use crate::pe::section_table;
10use crate::pe::utils;
11
12/// Size of [`ImageDebugDirectory`]
13pub const IMAGE_DEBUG_DIRECTORY_SIZE: usize = 0x1C;
14
15/// Iterator over debug directory entries in [`DebugData`].
16#[derive(Debug, Copy, Clone)]
17pub struct ImageDebugDirectoryIterator<'a> {
18    /// Raw data reference that scoped to the next element if appropriate
19    data: &'a [u8],
20    /// Fixup RVA offset used for TE fixups
21    ///
22    /// - **When zero**: no fixup is performed
23    /// - **When non-zero**: fixup is performed
24    rva_offset: u32,
25}
26
27impl Iterator for ImageDebugDirectoryIterator<'_> {
28    type Item = error::Result<ImageDebugDirectory>;
29
30    fn next(&mut self) -> Option<Self::Item> {
31        if self.data.is_empty() {
32            return None;
33        }
34
35        Some(
36            match self.data.pread_with::<ImageDebugDirectory>(0, scroll::LE) {
37                Ok(func) => {
38                    self.data = &self.data[IMAGE_DEBUG_DIRECTORY_SIZE..];
39
40                    // Adjust all addresses in the TE binary debug data if fixup is specified
41                    let idd = ImageDebugDirectory {
42                        address_of_raw_data: func.address_of_raw_data.wrapping_sub(self.rva_offset),
43                        pointer_to_raw_data: func.pointer_to_raw_data.wrapping_sub(self.rva_offset),
44                        ..func
45                    };
46
47                    debug!(
48                        "ImageDebugDirectory address of raw data fixed up from: 0x{:X} to 0x{:X}",
49                        idd.address_of_raw_data.wrapping_add(self.rva_offset),
50                        idd.address_of_raw_data,
51                    );
52
53                    debug!(
54                        "ImageDebugDirectory pointer to raw data fixed up from: 0x{:X} to 0x{:X}",
55                        idd.pointer_to_raw_data.wrapping_add(self.rva_offset),
56                        idd.pointer_to_raw_data,
57                    );
58
59                    Ok(idd)
60                }
61                Err(error) => {
62                    self.data = &[];
63                    Err(error.into())
64                }
65            },
66        )
67    }
68
69    fn size_hint(&self) -> (usize, Option<usize>) {
70        let len = self.data.len() / IMAGE_DEBUG_DIRECTORY_SIZE;
71        (len, Some(len))
72    }
73}
74
75impl FusedIterator for ImageDebugDirectoryIterator<'_> {}
76impl ExactSizeIterator for ImageDebugDirectoryIterator<'_> {}
77
78impl<'a> ImageDebugDirectoryIterator<'a> {
79    /// Find a specific debug type in the debug data.
80    pub fn find_type(&self, data_type: u32) -> Option<ImageDebugDirectory> {
81        self.filter_map(Result::ok)
82            .find(|idd| idd.data_type == data_type)
83    }
84}
85
86/// Represents debug data extracted from a PE (Portable Executable) or TE (Terse Executable) file.
87#[derive(Debug, PartialEq, Clone, Default)]
88pub struct DebugData<'a> {
89    /// Raw data covering bytes of entire [`ImageDebugDirectory`]
90    data: &'a [u8],
91    /// Fixup RVA offset used for TE fixups
92    ///
93    /// - **When zero**: no fixup is performed
94    /// - **When non-zero**: fixup is performed
95    rva_offset: u32,
96    /// Parsed CodeView PDB 7.0 (RSDS) debug information, if available.
97    ///
98    /// CodeView PDB 7.0 contains a GUID, an age value, and the path to the PDB file.
99    /// This is commonly used in modern PDB files.
100    ///
101    /// [`IMAGE_DEBUG_TYPE_CODEVIEW`]
102    pub codeview_pdb70_debug_info: Option<CodeviewPDB70DebugInfo<'a>>,
103    /// Parsed CodeView PDB 2.0 (NB10) debug information, if available.
104    ///
105    /// CodeView PDB 2.0 includes a signature, an age value, and the path to the PDB file.
106    /// It is used in older PDB formats.
107    ///
108    /// [`IMAGE_DEBUG_TYPE_CODEVIEW`]
109    pub codeview_pdb20_debug_info: Option<CodeviewPDB20DebugInfo<'a>>,
110    /// Visual C++ feature data, if available.
111    ///
112    /// This includes information about specific features or optimizations enabled
113    /// in Visual C++ builds.
114    ///
115    /// [`IMAGE_DEBUG_TYPE_VC_FEATURE`]
116    pub vcfeature_info: Option<VCFeatureInfo>,
117    /// Extended DLL characteristics information, if available.
118    ///
119    /// This data includes extended properties of the DLL that may affect
120    /// how the operating system handles the DLL, such as security features.
121    ///
122    /// [`IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS`]
123    pub ex_dll_characteristics_info: Option<ExDllCharacteristicsInfo>,
124    /// Reproducible build (Repro) information, if available.
125    ///
126    /// - **MSVC builds**: Contains a 32-byte hash stored directly in the raw data.
127    /// - **Clang builds**: Uses the [`ImageDebugDirectory::time_date_stamp`] as a hash,
128    ///   with no dedicated raw data.
129    ///
130    /// [`IMAGE_DEBUG_TYPE_REPRO`]
131    pub repro_info: Option<ReproInfo<'a>>,
132    /// Profile-guided optimization (POGO aka PGO) data, if available.
133    ///
134    /// This data provides information relevant to Profile-Guided Optimization
135    /// (POGO) processes, including function and data block optimizations.
136    ///
137    /// [`IMAGE_DEBUG_TYPE_POGO`]
138    ///
139    /// Reference: <https://devblogs.microsoft.com/cppblog/pogo>
140    pub pogo_info: Option<POGOInfo<'a>>,
141}
142
143impl<'a> DebugData<'a> {
144    pub fn parse(
145        bytes: &'a [u8],
146        dd: data_directories::DataDirectory,
147        sections: &[section_table::SectionTable],
148        file_alignment: u32,
149    ) -> error::Result<Self> {
150        Self::parse_with_opts(
151            bytes,
152            dd,
153            sections,
154            file_alignment,
155            &options::ParseOptions::default(),
156        )
157    }
158
159    pub fn parse_with_opts(
160        bytes: &'a [u8],
161        dd: data_directories::DataDirectory,
162        sections: &[section_table::SectionTable],
163        file_alignment: u32,
164        opts: &options::ParseOptions,
165    ) -> error::Result<Self> {
166        Self::parse_with_opts_and_fixup(bytes, dd, sections, file_alignment, opts, 0)
167    }
168
169    pub fn parse_with_opts_and_fixup(
170        bytes: &'a [u8],
171        dd: data_directories::DataDirectory,
172        sections: &[section_table::SectionTable],
173        file_alignment: u32,
174        opts: &options::ParseOptions,
175        rva_offset: u32,
176    ) -> error::Result<Self> {
177        let offset =
178            utils::find_offset(dd.virtual_address as usize, sections, file_alignment, opts)
179                .ok_or_else(|| {
180                    error::Error::Malformed(format!(
181                        "Cannot map ImageDebugDirectory rva {:#x} into offset",
182                        dd.virtual_address
183                    ))
184                })?;
185
186        // Ensure that the offset and size do not exceed the length of the bytes slice
187        if offset + dd.size as usize > bytes.len() {
188            return Err(error::Error::Malformed(format!(
189                "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}",
190                offset, dd.size, bytes.len()
191            )));
192        }
193        let data = &bytes[offset..offset + dd.size as usize];
194        let it = ImageDebugDirectoryIterator { data, rva_offset };
195
196        let mut codeview_pdb70_debug_info = None;
197        let mut codeview_pdb20_debug_info = None;
198        let mut vcfeature_info = None;
199        let mut ex_dll_characteristics_info = None;
200        let mut repro_info = None;
201        let mut pogo_info = None;
202
203        if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_CODEVIEW) {
204            codeview_pdb70_debug_info = CodeviewPDB70DebugInfo::parse_with_opts(bytes, idd, opts)?;
205            codeview_pdb20_debug_info = CodeviewPDB20DebugInfo::parse_with_opts(bytes, idd, opts)?;
206        }
207        if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_VC_FEATURE) {
208            vcfeature_info = Some(VCFeatureInfo::parse_with_opts(bytes, idd, opts)?);
209        }
210        if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS) {
211            ex_dll_characteristics_info =
212                Some(ExDllCharacteristicsInfo::parse_with_opts(bytes, idd, opts)?);
213        }
214        if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_REPRO) {
215            repro_info = Some(ReproInfo::parse_with_opts(bytes, idd, opts)?);
216        }
217        if let Some(idd) = &it.find_type(IMAGE_DEBUG_TYPE_POGO) {
218            pogo_info = POGOInfo::parse_with_opts(bytes, idd, opts)?;
219        }
220
221        Ok(DebugData {
222            data,
223            rva_offset,
224            codeview_pdb70_debug_info,
225            codeview_pdb20_debug_info,
226            vcfeature_info,
227            ex_dll_characteristics_info,
228            repro_info,
229            pogo_info,
230        })
231    }
232
233    /// Return this executable's debugging GUID, suitable for matching against a PDB file.
234    pub fn guid(&self) -> Option<[u8; 16]> {
235        self.codeview_pdb70_debug_info.map(|pdb70| pdb70.signature)
236    }
237
238    /// Find a specific debug type in the debug data.
239    pub fn find_type(&self, data_type: u32) -> Option<ImageDebugDirectory> {
240        self.entries().find_type(data_type)
241    }
242
243    /// Returns iterator for [`ImageDebugDirectory`]
244    pub fn entries(&self) -> ImageDebugDirectoryIterator<'a> {
245        ImageDebugDirectoryIterator {
246            data: &self.data,
247            rva_offset: self.rva_offset,
248        }
249    }
250}
251
252/// Represents the IMAGE_DEBUG_DIRECTORY structure in a Portable Executable (PE) file.
253///
254/// This structure holds information about the debug data in a PE file. It is used
255/// to locate debug information such as PDB files or other types of debugging data.
256/// The fields correspond to the Windows-specific IMAGE_DEBUG_DIRECTORY structure.
257///
258/// For more details, see the [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#image-debug_directory).
259///
260/// <https://msdn.microsoft.com/en-us/library/windows/desktop/ms680307(v=vs.85).aspx>
261#[repr(C)]
262#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
263pub struct ImageDebugDirectory {
264    /// The characteristics of the debug data, reserved for future use.
265    pub characteristics: u32,
266    /// The time and date when the debug data was created, represented as a Unix timestamp.
267    pub time_date_stamp: u32,
268    /// The major version number of the debug data format.
269    pub major_version: u16,
270    /// The minor version number of the debug data format.
271    pub minor_version: u16,
272    /// The type of debug data, such as codeview or coff.
273    pub data_type: u32,
274    /// The size of the debug data in bytes.
275    pub size_of_data: u32,
276    /// The address of the debug data when loaded into memory.
277    pub address_of_raw_data: u32,
278    /// The file pointer to the debug data within the PE file.
279    pub pointer_to_raw_data: u32,
280}
281
282/// Represents an unknown debug data type.
283pub const IMAGE_DEBUG_TYPE_UNKNOWN: u32 = 0;
284/// Represents COFF (Common Object File Format) debug information.
285pub const IMAGE_DEBUG_TYPE_COFF: u32 = 1;
286/// Represents CodeView debug information, often used for PDB (Program Database) files.
287pub const IMAGE_DEBUG_TYPE_CODEVIEW: u32 = 2;
288/// Represents FPO (Frame Pointer Omission) information.
289pub const IMAGE_DEBUG_TYPE_FPO: u32 = 3;
290/// Represents miscellaneous debug information.
291pub const IMAGE_DEBUG_TYPE_MISC: u32 = 4;
292/// Represents exception handling information.
293pub const IMAGE_DEBUG_TYPE_EXCEPTION: u32 = 5;
294/// Represents fixup information, used for relocation.
295pub const IMAGE_DEBUG_TYPE_FIXUP: u32 = 6;
296/// Represents OMAP (Optimized Map) information from source to compiled addresses.
297pub const IMAGE_DEBUG_TYPE_OMAP_TO_SRC: u32 = 7;
298/// Represents OMAP information from compiled addresses to source.
299pub const IMAGE_DEBUG_TYPE_OMAP_FROM_SRC: u32 = 8;
300/// Represents Borland-specific debug information.
301pub const IMAGE_DEBUG_TYPE_BORLAND: u32 = 9;
302/// Reserved debug data type (value 10).
303pub const IMAGE_DEBUG_TYPE_RESERVED10: u32 = 10;
304/// Represents BBT (Basic Block Transfer) information, an alias for reserved type 10.
305pub const IMAGE_DEBUG_TYPE_BBT: u32 = IMAGE_DEBUG_TYPE_RESERVED10;
306/// Represents a CLSID (Class ID) associated with the debug data.
307pub const IMAGE_DEBUG_TYPE_CLSID: u32 = 11;
308/// Represents Visual C++ feature data.
309pub const IMAGE_DEBUG_TYPE_VC_FEATURE: u32 = 12;
310/// Represents POGO (Profile Guided Optimization) information.
311pub const IMAGE_DEBUG_TYPE_POGO: u32 = 13;
312/// Represents ILTCG (Incremental Link Time Code Generation) optimization data.
313pub const IMAGE_DEBUG_TYPE_ILTCG: u32 = 14;
314/// Represents MPX (Memory Protection Extensions) related debug information.
315pub const IMAGE_DEBUG_TYPE_MPX: u32 = 15;
316/// Represents repro information, typically used for reproducible builds.
317pub const IMAGE_DEBUG_TYPE_REPRO: u32 = 16;
318/// Represents an embedded Portable PDB, a .NET-specific debug information format.
319pub const IMAGE_DEBUG_TYPE_EMBEDDEDPORTABLEPDB: u32 = 17;
320/// Represents SPGO (Static Profile Guided Optimization) information.
321pub const IMAGE_DEBUG_TYPE_SPGO: u32 = 18;
322/// Represents a checksum for the PDB file.
323pub const IMAGE_DEBUG_TYPE_PDBCHECKSUM: u32 = 19;
324/// Represents extended DLL characteristics for debugging.
325pub const IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS: u32 = 20;
326/// Represents a performance map for profiling.
327pub const IMAGE_DEBUG_TYPE_PERFMAP: u32 = 21;
328
329/// Magic number for CodeView PDB 7.0 signature (`'SDSR'`).
330pub const CODEVIEW_PDB70_MAGIC: u32 = 0x5344_5352;
331/// Magic number for CodeView PDB 2.0 signature (`'01BN'`).
332pub const CODEVIEW_PDB20_MAGIC: u32 = 0x3031_424e;
333/// Magic number for CodeView CV 5.0 signature (`'11BN'`).
334pub const CODEVIEW_CV50_MAGIC: u32 = 0x3131_424e;
335/// Magic number for CodeView CV 4.1 signature (`'90BN'`).
336pub const CODEVIEW_CV41_MAGIC: u32 = 0x3930_424e;
337
338// http://llvm.org/doxygen/CVDebugRecord_8h_source.html
339#[repr(C)]
340#[derive(Debug, PartialEq, Copy, Clone, Default)]
341pub struct CodeviewPDB70DebugInfo<'a> {
342    pub codeview_signature: u32,
343    pub signature: [u8; 16],
344    pub age: u32,
345    pub filename: &'a [u8],
346}
347
348impl<'a> CodeviewPDB70DebugInfo<'a> {
349    pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Option<Self>> {
350        Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
351    }
352
353    pub fn parse_with_opts(
354        bytes: &'a [u8],
355        idd: &ImageDebugDirectory,
356        opts: &options::ParseOptions,
357    ) -> error::Result<Option<Self>> {
358        // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly
359        let mut offset: usize = match opts.resolve_rva {
360            true => idd.pointer_to_raw_data as usize,
361            false => idd.address_of_raw_data as usize,
362        };
363
364        // calculate how long the eventual filename will be, which doubles as a check of the record size
365        let filename_length = idd.size_of_data as isize - 24;
366        if filename_length < 0 {
367            // the record is too short to be plausible
368            return Err(error::Error::Malformed(format!(
369                "ImageDebugDirectory size of data seems wrong: {:?}",
370                idd.size_of_data
371            )));
372        }
373        let filename_length = filename_length as usize;
374
375        // check the codeview signature
376        let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
377        if codeview_signature != CODEVIEW_PDB70_MAGIC {
378            return Ok(None);
379        }
380
381        // read the rest
382        let mut signature: [u8; 16] = [0; 16];
383        signature.copy_from_slice(bytes.gread_with(&mut offset, 16)?);
384        let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
385        if let Some(filename) = bytes.get(offset..offset + filename_length) {
386            Ok(Some(CodeviewPDB70DebugInfo {
387                codeview_signature,
388                signature,
389                age,
390                filename,
391            }))
392        } else {
393            Err(error::Error::Malformed(format!(
394                "ImageDebugDirectory seems corrupted: {:?}",
395                idd
396            )))
397        }
398    }
399}
400
401/// Represents the `IMAGE_DEBUG_VC_FEATURE_ENTRY` structure
402#[repr(C)]
403#[derive(Debug, PartialEq, Copy, Clone, Default)]
404pub struct VCFeatureInfo {
405    /// The count of pre-VC++
406    pub pre_vc_plusplus_count: u32,
407    /// The count of C and C++
408    pub c_and_cplusplus_count: u32,
409    /// The count of guard stack
410    pub guard_stack_count: u32,
411    /// The count of SDL
412    pub sdl_count: u32,
413    /// The count of guard
414    pub guard_count: u32,
415}
416
417impl<'a> VCFeatureInfo {
418    pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Self> {
419        Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
420    }
421
422    pub fn parse_with_opts(
423        bytes: &'a [u8],
424        idd: &ImageDebugDirectory,
425        opts: &options::ParseOptions,
426    ) -> error::Result<Self> {
427        let mut offset: usize = match opts.resolve_rva {
428            true => idd.pointer_to_raw_data as usize,
429            false => idd.address_of_raw_data as usize,
430        };
431
432        let pre_vc_plusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
433        let c_and_cplusplus_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
434        let guard_stack_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
435        let sdl_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
436        let guard_count: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
437
438        Ok(VCFeatureInfo {
439            pre_vc_plusplus_count,
440            c_and_cplusplus_count,
441            guard_stack_count,
442            sdl_count,
443            guard_count,
444        })
445    }
446}
447
448// http://llvm.org/doxygen/CVDebugRecord_8h_source.html
449#[repr(C)]
450#[derive(Debug, PartialEq, Copy, Clone, Default)]
451pub struct CodeviewPDB20DebugInfo<'a> {
452    pub codeview_signature: u32,
453    pub codeview_offset: u32,
454    pub signature: u32,
455    pub age: u32,
456    pub filename: &'a [u8],
457}
458
459impl<'a> CodeviewPDB20DebugInfo<'a> {
460    pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Option<Self>> {
461        Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
462    }
463
464    pub fn parse_with_opts(
465        bytes: &'a [u8],
466        idd: &ImageDebugDirectory,
467        opts: &options::ParseOptions,
468    ) -> error::Result<Option<Self>> {
469        // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly
470        let mut offset: usize = match opts.resolve_rva {
471            true => idd.pointer_to_raw_data as usize,
472            false => idd.address_of_raw_data as usize,
473        };
474
475        // calculate how long the eventual filename will be, which doubles as a check of the record size
476        let filename_length = idd.size_of_data as isize - 16;
477        if filename_length < 0 {
478            // the record is too short to be plausible
479            return Err(error::Error::Malformed(format!(
480                "ImageDebugDirectory size of data seems wrong: {:?}",
481                idd.size_of_data
482            )));
483        }
484        let filename_length = filename_length as usize;
485
486        // check the codeview signature
487        let codeview_signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
488        if codeview_signature != CODEVIEW_PDB20_MAGIC {
489            return Ok(None);
490        }
491        let codeview_offset: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
492
493        // read the rest
494        let signature: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
495        let age: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
496        if let Some(filename) = bytes.get(offset..offset + filename_length) {
497            Ok(Some(CodeviewPDB20DebugInfo {
498                codeview_signature,
499                codeview_offset,
500                signature,
501                age,
502                filename,
503            }))
504        } else {
505            Err(error::Error::Malformed(format!(
506                "ImageDebugDirectory seems corrupted: {:?}",
507                idd
508            )))
509        }
510    }
511}
512
513/// Represents the reproducible build (Repro) information extracted from a PE (Portable Executable) file.
514///
515/// The Repro information differs based on the compiler used to build the executable:
516/// - For MSVC (Microsoft Visual C++), the Repro information is written directly into the raw data as a 32-byte hash.
517/// - For Clang/(correctly, LLD linker), there is no dedicated raw data for the Repro information. Instead, the [`ImageDebugDirectory::time_date_stamp`]
518///   field functions as a hash, providing a unique identifier for the reproducible build.
519#[derive(Debug, PartialEq, Copy, Clone)]
520pub enum ReproInfo<'a> {
521    /// Represents a hash stored in the [`ImageDebugDirectory::time_date_stamp`] field.
522    ///
523    /// This variant is used primarily for executables built with Clang/LLD, where the
524    /// [`ImageDebugDirectory::time_date_stamp`] acts as the Repro hash.
525    TimeDateStamp(u32),
526    /// Represents a buffer containing the 32-byte Repro hash.
527    ///
528    /// This variant is used for MSVC-built executables, where the Repro hash is directly
529    /// stored as raw data in the debug directory.
530    Buffer {
531        /// The length of the buffer containing the Repro data. For MSVC, this is typically 32 bytes long.
532        length: u32,
533        /// A reference to the buffer containing the Repro hash data.
534        buffer: &'a [u8],
535    },
536}
537
538impl<'a> ReproInfo<'a> {
539    pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Self> {
540        Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
541    }
542
543    pub fn parse_with_opts(
544        bytes: &'a [u8],
545        idd: &ImageDebugDirectory,
546        opts: &options::ParseOptions,
547    ) -> error::Result<Self> {
548        let mut offset: usize = match opts.resolve_rva {
549            true => idd.pointer_to_raw_data as usize,
550            false => idd.address_of_raw_data as usize,
551        };
552
553        // Clang(LLD) produces no data, uses timestamp field instead
554        // MSVC(link.exe) produces 32-byte data
555        if idd.size_of_data > 0 {
556            let length: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
557            if let Some(buffer) = bytes.get(offset..offset + length as usize) {
558                Ok(Self::Buffer { length, buffer })
559            } else {
560                Err(error::Error::Malformed(format!(
561                    "ImageDebugDirectory seems corrupted: {:?}",
562                    idd
563                )))
564            }
565        } else {
566            Ok(Self::TimeDateStamp(idd.time_date_stamp))
567        }
568    }
569}
570
571/// Represents extended DLL characteristics information.
572///
573/// This structure holds additional characteristics of a DLL that may influence
574/// how the operating system loads or manages the DLL, especially in terms of
575/// security features and optimizations. These characteristics can include
576/// settings related to Intel CET (Control-flow Enforcement Technology) and other
577/// security-relevant attributes.
578#[repr(C)]
579#[derive(Debug, PartialEq, Copy, Clone, Default)]
580pub struct ExDllCharacteristicsInfo {
581    /// The extended characteristics of the DLL.
582    ///
583    /// This field is a bitmask of flags that define various security and performance
584    /// properties of the DLL. The specific flags are defined by the PE format specification.
585    ///
586    /// This field contains one or more bitflags of:
587    ///
588    /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT`]
589    /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE`]
590    /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE`]
591    /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY`]
592    /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1`]
593    /// - [`IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2`]
594    /// - [`IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT`]
595    /// - [`IMAGE_DLLCHARACTERISTICS_EX_HOTPATCH_COMPATIBLE`]
596    pub characteristics_ex: u32,
597}
598
599/// Indicates that Control Flow Enforcement Technology (CET) is enabled for the DLL,
600/// enhancing security via control-flow integrity.
601pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT: u32 = 0x1;
602/// Indicates that CET is enforced in strict mode, increasing security measures against
603/// control-flow attacks but may impact compatibility.
604pub const IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE: u32 = 0x2;
605/// Indicates that relaxed mode for Context IP Validation under CET is allowed,
606/// providing a balance between security and performance.
607pub const IMAGE_DLLCHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE: u32 = 0x4;
608/// Indicates that the use of dynamic APIs is restricted to processes only,
609/// enhancing security by limiting external API calls under CET.
610pub const IMAGE_DLLCHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY: u32 = 0x8;
611/// Reserved for future.
612pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_1: u32 = 0x10;
613/// Reserved for future.
614pub const IMAGE_DLLCHARACTERISTICS_EX_CET_RESERVED_2: u32 = 0x20;
615/// Indicates that the DLL is compatible with Forward Control Flow Integrity (CFI).
616///
617/// This flag signifies that the DLL is designed to support forward CFI, a security
618/// feature that helps prevent certain types of control flow attacks by ensuring
619/// that control flow transfers occur only to valid targets.
620pub const IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT: u32 = 0x40;
621/// Indicates that the DLL is hotpatch-compatible.
622///
623/// This flag indicates that the DLL can be modified while in use (hotpatching),
624/// allowing updates or fixes to be applied without needing to restart the application
625/// or service that is using the DLL. This can be useful for maintaining uptime and
626/// applying critical patches in a live environment.
627pub const IMAGE_DLLCHARACTERISTICS_EX_HOTPATCH_COMPATIBLE: u32 = 0x80;
628
629impl<'a> ExDllCharacteristicsInfo {
630    pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Self> {
631        Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
632    }
633
634    pub fn parse_with_opts(
635        bytes: &'a [u8],
636        idd: &ImageDebugDirectory,
637        opts: &options::ParseOptions,
638    ) -> error::Result<Self> {
639        // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly
640        let mut offset: usize = match opts.resolve_rva {
641            true => idd.pointer_to_raw_data as usize,
642            false => idd.address_of_raw_data as usize,
643        };
644
645        let characteristics_ex: u32 = bytes.gread_with(&mut offset, scroll::LE)?;
646
647        Ok(ExDllCharacteristicsInfo { characteristics_ex })
648    }
649}
650
651/// Represents the POGO info structure, which provides information
652/// about Profile-Guided Optimization (POGO aka PGO) data within a PE file.
653///
654/// PGO is a compiler optimization technique that uses data collected from program
655/// execution to optimize code layout and improve runtime performance. This structure
656/// contains details such as the relative virtual address (RVA), size, and the associated
657/// name of the function or data block for which PGO data is provided.
658///
659/// <https://devblogs.microsoft.com/cppblog/pogo>
660#[repr(C)]
661#[derive(Debug, PartialEq, Copy, Clone, Default)]
662pub struct POGOInfo<'a> {
663    /// The signature of POGO debug directory entry is always first 4-bytes
664    ///
665    /// Either one of:
666    ///
667    /// - [`IMAGE_DEBUG_POGO_SIGNATURE_LTCG`]
668    /// - [`IMAGE_DEBUG_POGO_SIGNATURE_PGU`]
669    pub signature: u32,
670    /// Raw bytes of POGO debug entry, without first 4-bytes signatures field
671    pub data: &'a [u8],
672}
673
674/// Represents the `IMAGE_DEBUG_POGO_ENTRY` structure
675#[derive(Debug, PartialEq, Copy, Clone, Default)]
676pub struct POGOInfoEntry<'a> {
677    /// The relative virtual address (RVA) of the PGO data.
678    pub rva: u32,
679    /// The size of the PGO data block.
680    pub size: u32,
681    /// The name of the function or data block associated with the PGO data as a byte slice.
682    ///
683    /// This may contain a null-terminated string that represents the function or block
684    /// name for which PGO optimization data is provided.
685    pub name: &'a [u8],
686}
687
688/// Iterator over POGO entries in [`POGOInfo`].
689#[derive(Debug, Copy, Clone)]
690pub struct POGOEntryIterator<'a> {
691    /// The raw data of [`POGOInfo::data`] without the signature field
692    data: &'a [u8],
693}
694
695/// Indicates the PGO signature for Link-Time Code Generation (LTCG) (`u32` hex representation of `'LTCG'`).
696///
697/// This constant is used in the `IMAGE_DEBUG_DIRECTORY` to identify sections of
698/// PGO data generated specifically for LTCG optimizations.
699pub const IMAGE_DEBUG_POGO_SIGNATURE_LTCG: u32 = 0x4C544347;
700/// Indicates the PGO signature for Profile-Guided Optimization (PGO) updates (PGU) (`u32` hex representation of `'PGU\0'`).
701///
702/// This constant is used to signify sections of PGO data associated with PGO updates,
703/// which are incremental optimizations based on profiling data collected over time.
704pub const IMAGE_DEBUG_POGO_SIGNATURE_PGU: u32 = 0x50475500;
705/// Size of [`IMAGE_DEBUG_POGO_SIGNATURE_LTCG`] or [`IMAGE_DEBUG_POGO_SIGNATURE_PGU`]
706pub const POGO_SIGNATURE_SIZE: usize = core::mem::size_of::<u32>();
707
708impl<'a> POGOInfo<'a> {
709    pub fn parse(bytes: &'a [u8], idd: &ImageDebugDirectory) -> error::Result<Option<Self>> {
710        Self::parse_with_opts(bytes, idd, &options::ParseOptions::default())
711    }
712
713    pub fn parse_with_opts(
714        bytes: &'a [u8],
715        idd: &ImageDebugDirectory,
716        opts: &options::ParseOptions,
717    ) -> error::Result<Option<Self>> {
718        // ImageDebugDirectory.pointer_to_raw_data stores a raw offset -- not a virtual offset -- which we can use directly
719        let mut offset: usize = match opts.resolve_rva {
720            true => idd.pointer_to_raw_data as usize,
721            false => idd.address_of_raw_data as usize,
722        };
723
724        let signature = bytes.gread_with::<u32>(&mut offset, scroll::LE)?;
725        if signature != IMAGE_DEBUG_POGO_SIGNATURE_LTCG
726            && signature != IMAGE_DEBUG_POGO_SIGNATURE_PGU
727        {
728            // This is not something we support
729            return Ok(None);
730        }
731
732        if offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE > bytes.len() {
733            return Err(error::Error::Malformed(format!(
734                    "ImageDebugDirectory offset {:#x} and size {:#x} exceeds the bounds of the bytes size {:#x}",
735                    offset, idd.size_of_data, bytes.len()
736                )));
737        }
738        let data = &bytes[offset..offset + idd.size_of_data as usize - POGO_SIGNATURE_SIZE];
739        Ok(Some(POGOInfo { signature, data }))
740    }
741
742    /// Returns iterator for [`POGOInfoEntry`]
743    pub fn entries(&self) -> POGOEntryIterator<'a> {
744        POGOEntryIterator { data: &self.data }
745    }
746}
747
748impl<'a> Iterator for POGOEntryIterator<'a> {
749    type Item = error::Result<POGOInfoEntry<'a>>;
750
751    fn next(&mut self) -> Option<Self::Item> {
752        if self.data.is_empty() {
753            return None;
754        }
755
756        let mut offset = 0;
757        let rva = match self.data.gread_with::<u32>(&mut offset, scroll::LE) {
758            Ok(rva) => rva,
759            Err(error) => return Some(Err(error.into())),
760        };
761        let size = match self.data.gread_with::<u32>(&mut offset, scroll::LE) {
762            Ok(size) => size,
763            Err(error) => return Some(Err(error.into())),
764        };
765
766        if offset >= self.data.len() {
767            return Some(Err(error::Error::Malformed(format!(
768                "Offset {:#x} is too big for containing name field of POGO entry (rva {:#x} and size {:#X})",
769                offset,rva, size
770            ))));
771        }
772        let name = match self.data[offset..].iter().position(|&b| b == 0) {
773            Some(pos) => {
774                if offset + pos as usize >= self.data.len() {
775                    return Some(Err(error::Error::Malformed(format!(
776                        "Null-terminator for POGO entry (rva {:#x} and size {:#X}) found but exceeds iterator buffer",
777                        rva, size
778                    ))));
779                }
780                let name = &self.data[offset..offset + pos + 1];
781                offset = offset + pos + 1;
782                // Align to the next u32 boundary
783                offset = (offset + 3) & !3; // Round up to the nearest multiple of 4
784                name
785            }
786            None => {
787                return Some(Err(error::Error::Malformed(format!(
788                    "Cannot find null-terimnator for POGO entry (rva {:#x} and size {:#X})",
789                    rva, size
790                ))
791                .into()));
792            }
793        };
794
795        self.data = &self.data[offset..];
796        Some(Ok(POGOInfoEntry { rva, size, name }))
797    }
798}
799
800impl FusedIterator for POGOEntryIterator<'_> {}
801
802#[cfg(test)]
803mod tests {
804    use super::{
805        ExDllCharacteristicsInfo, ImageDebugDirectory, POGOInfoEntry, ReproInfo, VCFeatureInfo,
806        CODEVIEW_PDB70_MAGIC, IMAGE_DEBUG_POGO_SIGNATURE_LTCG, IMAGE_DEBUG_TYPE_CODEVIEW,
807        IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, IMAGE_DEBUG_TYPE_ILTCG, IMAGE_DEBUG_TYPE_POGO,
808        IMAGE_DEBUG_TYPE_REPRO, IMAGE_DEBUG_TYPE_VC_FEATURE,
809        IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE,
810        POGO_SIGNATURE_SIZE,
811    };
812
813    const NO_DEBUG_DIRECTORIES_BIN: &[u8] =
814        include_bytes!("../../tests/bins/pe/no_debug_directories.exe.bin");
815    const DEBUG_DIRECTORIES_TEST_MSVC_BIN: &[u8] =
816        include_bytes!("../../tests/bins/pe/debug_directories-msvc.exe.bin");
817    const DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN: &[u8] =
818        include_bytes!("../../tests/bins/pe/debug_directories-clang_lld.exe.bin");
819
820    fn ffi_to_string(bytes: &[u8]) -> String {
821        unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(bytes) }
822            .to_string_lossy()
823            .to_string()
824    }
825
826    #[test]
827    fn parse_no_debug_directories() {
828        let binary =
829            crate::pe::PE::parse(NO_DEBUG_DIRECTORIES_BIN).expect("Unable to parse binary");
830        assert_eq!(binary.debug_data.is_none(), true);
831    }
832
833    #[test]
834    fn parse_debug_entries_iterator() {
835        let binary =
836            crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary");
837        assert_eq!(binary.debug_data.is_some(), true);
838        let debug_data = binary.debug_data.unwrap();
839        let entries = debug_data.entries().collect::<Result<Vec<_>, _>>();
840        assert_eq!(entries.is_ok(), true);
841        let entries = entries.unwrap();
842        let entries_expect = vec![
843            ImageDebugDirectory {
844                characteristics: 0x0,
845                time_date_stamp: 0x80AC7661,
846                major_version: 0x0,
847                minor_version: 0x0,
848                data_type: IMAGE_DEBUG_TYPE_CODEVIEW,
849                size_of_data: 0x38,
850                address_of_raw_data: 0x20c0,
851                pointer_to_raw_data: 0x4c0,
852            },
853            ImageDebugDirectory {
854                characteristics: 0x0,
855                time_date_stamp: 0x80AC7661,
856                major_version: 0x0,
857                minor_version: 0x0,
858                data_type: IMAGE_DEBUG_TYPE_VC_FEATURE,
859                size_of_data: 0x14,
860                address_of_raw_data: 0x20f8,
861                pointer_to_raw_data: 0x4f8,
862            },
863            ImageDebugDirectory {
864                characteristics: 0x0,
865                time_date_stamp: 0x80AC7661,
866                major_version: 0x0,
867                minor_version: 0x0,
868                data_type: IMAGE_DEBUG_TYPE_POGO,
869                size_of_data: 0x58,
870                address_of_raw_data: 0x210c,
871                pointer_to_raw_data: 0x50c,
872            },
873            ImageDebugDirectory {
874                characteristics: 0x0,
875                time_date_stamp: 0x80AC7661,
876                major_version: 0x0,
877                minor_version: 0x0,
878                data_type: IMAGE_DEBUG_TYPE_ILTCG,
879                size_of_data: 0x0,
880                address_of_raw_data: 0x0,
881                pointer_to_raw_data: 0x0,
882            },
883            ImageDebugDirectory {
884                characteristics: 0x0,
885                time_date_stamp: 0x80AC7661,
886                major_version: 0x0,
887                minor_version: 0x0,
888                data_type: IMAGE_DEBUG_TYPE_REPRO,
889                size_of_data: 0x24,
890                address_of_raw_data: 0x2164,
891                pointer_to_raw_data: 0x564,
892            },
893            ImageDebugDirectory {
894                characteristics: 0x0,
895                time_date_stamp: 0x80AC7661,
896                major_version: 0x0,
897                minor_version: 0x0,
898                data_type: IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,
899                size_of_data: 0x4,
900                address_of_raw_data: 0x2188,
901                pointer_to_raw_data: 0x588,
902            },
903        ];
904        assert_eq!(entries, entries_expect);
905    }
906
907    #[test]
908    fn parse_debug_codeview_pdb70_msvc() {
909        let binary =
910            crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary");
911        assert_eq!(binary.debug_data.is_some(), true);
912        let debug_data = binary.debug_data.unwrap();
913        assert_eq!(debug_data.codeview_pdb70_debug_info.is_some(), true);
914        let codeview_pdb70_debug_info = debug_data.codeview_pdb70_debug_info.unwrap();
915        let filename = ffi_to_string(codeview_pdb70_debug_info.filename);
916        assert_eq!(filename, String::from("THIS-IS-BINARY-FOR-GOBLIN-TESTS"));
917        assert_eq!(codeview_pdb70_debug_info.age, 3);
918        assert_eq!(
919            codeview_pdb70_debug_info.codeview_signature,
920            CODEVIEW_PDB70_MAGIC
921        );
922        assert_eq!(
923            codeview_pdb70_debug_info.signature,
924            [
925                0x1F, 0x4F, 0x58, 0x9C, 0x3C, 0xEA, 0x00, 0x83, 0x3F, 0x57, 0x00, 0xCC, 0x36, 0xA7,
926                0x84, 0xDF,
927            ]
928        );
929    }
930
931    #[test]
932    fn parse_debug_codeview_pdb70_clang() {
933        let binary = crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN)
934            .expect("Unable to parse binary");
935        assert_eq!(binary.debug_data.is_some(), true);
936        let debug_data = binary.debug_data.unwrap();
937        assert_eq!(debug_data.codeview_pdb70_debug_info.is_some(), true);
938        let codeview_pdb70_debug_info = debug_data.codeview_pdb70_debug_info.unwrap();
939        let filename = ffi_to_string(codeview_pdb70_debug_info.filename);
940        assert_eq!(filename, String::from("THIS-IS-BINARY-FOR-GOBLIN-TESTS"));
941        assert_eq!(codeview_pdb70_debug_info.age, 1);
942        assert_eq!(
943            codeview_pdb70_debug_info.codeview_signature,
944            CODEVIEW_PDB70_MAGIC
945        );
946        assert_eq!(
947            codeview_pdb70_debug_info.signature,
948            [
949                0xC8, 0xBA, 0xF6, 0xAB, 0xB2, 0x98, 0xD1, 0x9E, 0x4C, 0x4C, 0x44, 0x20, 0x50, 0x44,
950                0x42, 0x2E,
951            ]
952        );
953    }
954
955    #[test]
956    fn parse_debug_vcfeature() {
957        let binary =
958            crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary");
959        assert_eq!(binary.debug_data.is_some(), true);
960        let debug_data = binary.debug_data.unwrap();
961        assert_eq!(debug_data.vcfeature_info.is_some(), true);
962        let vcfeature_info = debug_data.vcfeature_info.unwrap();
963        let vcfeature_info_expect = VCFeatureInfo {
964            pre_vc_plusplus_count: 0,
965            c_and_cplusplus_count: 1,
966            guard_stack_count: 0,
967            sdl_count: 0,
968            guard_count: 0,
969        };
970        assert_eq!(vcfeature_info, vcfeature_info_expect);
971    }
972
973    #[test]
974    fn parse_debug_repro_msvc() {
975        let binary =
976            crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary");
977        assert_eq!(binary.debug_data.is_some(), true);
978        let debug_data = binary.debug_data.unwrap();
979        assert_eq!(debug_data.repro_info.is_some(), true);
980        let repro_info = debug_data.repro_info.unwrap();
981        let repro_info_expect = ReproInfo::Buffer {
982            length: 32,
983            buffer: &[
984                0x1F, 0x4F, 0x58, 0x9C, 0x3C, 0xEA, 0x00, 0x83, 0x3F, 0x57, 0x00, 0xCC, 0x36, 0xA7,
985                0x84, 0xDF, 0xF7, 0x7C, 0x70, 0xE0, 0xEF, 0x7A, 0xBA, 0x08, 0xD0, 0xA6, 0x8B, 0x7F,
986                0x61, 0x76, 0xAC, 0x80,
987            ],
988        };
989        assert_eq!(repro_info, repro_info_expect);
990    }
991
992    #[test]
993    fn parse_debug_repro_clang_lld() {
994        let binary = crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_CLANG_LLD_BIN)
995            .expect("Unable to parse binary");
996        assert_eq!(binary.debug_data.is_some(), true);
997        let debug_data = binary.debug_data.unwrap();
998        assert_eq!(debug_data.repro_info.is_some(), true);
999        let repro_info = debug_data.repro_info.unwrap();
1000        let repro_info_expect = ReproInfo::TimeDateStamp(0xDB2F3908);
1001        assert_eq!(repro_info, repro_info_expect);
1002    }
1003
1004    #[test]
1005    fn parse_debug_exdllcharacteristics() {
1006        let binary =
1007            crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary");
1008        assert_eq!(binary.debug_data.is_some(), true);
1009        let debug_data = binary.debug_data.unwrap();
1010        assert_eq!(debug_data.ex_dll_characteristics_info.is_some(), true);
1011        let ex_dll_characteristics_info = debug_data.ex_dll_characteristics_info.unwrap();
1012        let ex_dll_characteristics_info_expect = ExDllCharacteristicsInfo {
1013            characteristics_ex: IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT
1014                | IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE,
1015        };
1016        assert_eq!(
1017            ex_dll_characteristics_info,
1018            ex_dll_characteristics_info_expect
1019        );
1020    }
1021
1022    #[test]
1023    fn parse_debug_pogo() {
1024        let binary =
1025            crate::pe::PE::parse(DEBUG_DIRECTORIES_TEST_MSVC_BIN).expect("Unable to parse binary");
1026        assert_eq!(binary.debug_data.is_some(), true);
1027        let debug_data = binary.debug_data.unwrap();
1028        assert_eq!(debug_data.pogo_info.is_some(), true);
1029        let pogo_info = debug_data.pogo_info.unwrap();
1030        assert_eq!(pogo_info.signature, IMAGE_DEBUG_POGO_SIGNATURE_LTCG);
1031        assert_eq!(pogo_info.data.len(), 88 - POGO_SIGNATURE_SIZE);
1032        let entries = pogo_info.entries().collect::<Result<Vec<_>, _>>().unwrap();
1033        let entries_expect = vec![
1034            POGOInfoEntry {
1035                rva: 0x1000,
1036                size: 0x3,
1037                name: b".text$mn\0",
1038            },
1039            POGOInfoEntry {
1040                rva: 0x2000,
1041                size: 0xA8,
1042                name: b".rdata\0",
1043            },
1044            POGOInfoEntry {
1045                rva: 0x20A8,
1046                size: 0x18,
1047                name: b".rdata$voltmd\0",
1048            },
1049            POGOInfoEntry {
1050                rva: 0x20C0,
1051                size: 0xCC,
1052                name: b".rdata$zzzdbg\0",
1053            },
1054        ];
1055        assert_eq!(entries, entries_expect);
1056    }
1057}