Skip to main content

goblin/pe/
debug.rs

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