Skip to content

Pe

Description

Parsed PE file. Holds the raw bytes plus decoded indices. All three PeOpen* constructors leave the parser as the sole owner of data – see the L / R semantics on the FromMemory / FromMemoryCopy constructors (mirrors VecInsertL / VecInsertR).

Fields

Name Description
data Raw PE bytes as a Buf (owned). Carries its own length and allocator – read via BufLength / BufData / BufAllocator.
machine Decoded IMAGE_FILE_HEADER.Machine.
is_pe32_plus True for PE32+ (64-bit). v1 supports both PE32 and PE32+ headers, but the address-bearing fields are widened to 64 bits even on PE32.
image_base OptionalHeader.ImageBase – the runtime virtual address the loader places the image at when no relocation is required.
size_of_image OptionalHeader.SizeOfImage – total in-memory size (helps bounds-check RVAs).
sections All section headers, in original order.
codeview CodeView debug record, if present.

Usage example (Cross-references)

Usage examples (Cross-references)
    
    #include <Misra/Parsers/Pdb.h>
    #include <Misra/Parsers/Pe.h>
    #include <Misra/Std/Allocator.h>
    #include <Misra/Std/Container/Str/Type.h>
    typedef struct PdbCacheEntry {
        Str  module_path; // owned; cleaned via StrDeinit
        Pe   pe;
        Pdb  pdb;
        bool pe_open;
    #define MISRA_PARSERS_PE_H
    
    #include <Misra/Parsers/Pe/Private.h>
    #include <Misra/Std/Allocator.h>
    #include <Misra/Std/Container/Buf.h>
        PeSections     sections;
        PeCodeViewInfo codeview;
    } Pe;
    
    ///
    /// TAGS: Parser, PE, Memory, Ownership
    ///
    bool PeOpenFromMemory(Pe *out, Buf *in);
    
    ///
    /// TAGS: Parser, PE, Deinit, Lifecycle
    ///
    void PeDeinit(Pe *self);
    
    ///
    /// TAGS: Parser, PE, Address
    ///
    bool PeRvaToOffset(const Pe *self, u32 rva, u64 *out_offset);
    
    #endif // MISRA_PARSERS_PE_H
    //
    // On success populates `out_path` (an owned Str the caller frees).
    static bool find_pdb(const Pe *pe, Zstr pe_path, Str *out_path) {
        const PeCodeViewInfo *cv = PeCodeView(pe);
        if (!cv->present || !cv->pdb_path)
    ///   - LLVM's `Object/COFF.h` for the reader-side conventions
    
    #include <Misra/Parsers/Pe.h>
    #include <Misra/Std.h>
    #include <Misra/Std/Container/Buf.h>
    
    typedef struct PeContext {
        Pe     *out;
        BufIter file;      // bounds for the whole image
        u32     nt_offset; // offset of NT signature
    // MemSets the caller's view. Anything that fails past the snapshot
    // cleans up via PeDeinit -- the buffer never leaks.
    bool PeOpenFromMemory(Pe *out, Buf *in) {
        if (!out || !in || !BufData(in) || !BufAllocator(in)) {
            LOG_FATAL("PeOpenFromMemory: NULL argument (contract violation)");
    
    // R-value form: allocate Buf, copy, hand `&copy` to the L-form.
    bool pe_open_from_memory_copy(Pe *out, const u8 *data, size data_size, Allocator *alloc) {
        if (!out || !data || !alloc) {
            LOG_FATAL("PeOpenFromMemoryCopy: NULL argument (contract violation)");
    }
    
    bool pe_open(Pe *out, Zstr path, Allocator *alloc) {
        if (!out || !path || !alloc) {
            LOG_FATAL("PeOpen: NULL argument (contract violation)");
    }
    
    void PeDeinit(Pe *self) {
        if (!self)
            return;
    }
    
    const PeSection *pe_find_section_zstr(const Pe *self, Zstr name) {
        if (!self || !name)
            return NULL;
    }
    
    const PeSection *pe_find_section_str(const Pe *self, const Str *name) {
        if (!self || !name)
            return NULL;
    }
    
    bool PeRvaToOffset(const Pe *self, u32 rva, u64 *out_offset) {
        if (!self || !out_offset)
            return false;
    
    #include <Misra.h>
    #include <Misra/Parsers/Pe.h>
    #include <Misra/Std/Zstr.h>
    #include <Misra/Std/Allocator/Debug.h>
        build_pe_blob();
    
        Pe   pe;
        bool ok = PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), base);
        if (!ok) {
    
        build_pe_blob();
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), base)) {
            DefaultAllocatorDeinit(&alloc);
        garbage[1] = 'X';
    
        Pe   pe;
        bool ok = !PeOpenFromMemoryCopy(&pe, garbage, sizeof(garbage), base);
    
        build_pe_blob();
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), base)) {
            DefaultAllocatorDeinit(&alloc);
    
        build_pe_blob();
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), base)) {
            DefaultAllocatorDeinit(&alloc);
    static bool pe_rejects(const u8 *bytes, u64 len) {
        DefaultAllocator alloc = DefaultAllocatorInit();
        Pe               pe;
        bool             opened = PeOpenFromMemoryCopy(&pe, bytes, len, ALLOCATOR_OF(&alloc));
        if (opened)
        DefaultAllocator alloc = DefaultAllocatorInit();
        build_pe_blob_m1();
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        // top 4 bytes are all significant.
        wr_u64(&blob[OPT_HDR_OFF + 24], 0x7766554433221100ull);
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
        build_pe32_blob();
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe32_blob();
        wr_u32(&blob[OPT_HDR_OFF + 28], 0x00410000u);
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        // the file but assert via the overrun-reject test below. Here we
        // confirm the normal (well-within-file) blob is accepted.
        Pe   pe;
        bool ok = PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc));
        if (ok)
        // 1896. Set size to 1900 (> 1896) so the real code rejects.
        wr_u16(&bad[FILE_HDR_OFF + 16], 1900);
        Pe   pe;
        bool opened = PeOpenFromMemoryCopy(&pe, bad, sizeof(bad), ALLOCATOR_OF(&alloc));
        if (opened)
        // path just stays empty. opt ends at OPT_HDR_OFF + 240.
        u64  want_len = (u64)OPT_HDR_OFF + OPT_HDR_SIZE_PEPP; // exact fit to EOF
        Pe   pe;
        bool ok = PeOpenFromMemoryCopy(&pe, exact, want_len, ALLOCATOR_OF(&alloc));
        if (ok) {
        build_pe_blob_m1();
        wr_u32(&blob[OPT_HDR_OFF + 108], 7); // NumberOfRvaAndSizes = 7
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        // read slot 6 and FIND the codeview -> present=true. Asserting
        // absent kills that mutation.
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        DefaultAllocator alloc = DefaultAllocatorInit();
        build_pe_blob_m1();
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        // Corrupt the 'R' of RSDS.
        blob[CV_REC_RAW_OFF] = 'N';
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe_blob_m1();
        wr_u32(&blob[DEBUG_RAW_OFF + 12], 9); // Type != CODEVIEW
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        for (u32 i = 0; i < path_region; ++i)
            cv[24 + i] = (u8)('A' + i); // no NUL anywhere in the region
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        cv[27] = 'D';
        cv[28] = '\0'; // terminator is the last in-region byte
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        u8 *cv = &blob[CV_REC_RAW_OFF];
        cv[24] = '\0';                         // empty path, NUL-terminated
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe_blob_m1();
        wr_u32(&blob[DEBUG_RAW_OFF + 16], 24); // one below minimum
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        // 0x420 + sz > 0x800: sz = 0x800 - 0x420 + 4 = 0x3E4.
        wr_u32(&blob[DEBUG_RAW_OFF + 16], (u32)(BLOB_SIZE - CV_REC_RAW_OFF + 4));
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe_blob_fullcover(0x300, 28);
        put_cv_entry_sz(0x300, 0x200, 0x300);
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe_blob_fullcover(0x300, 28);
        put_cv_entry_sz(0x300, 0x500, 0x300);
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        // 73*28 = 2044, dir_offset 0x400 + 2044 = 0xC00 > 0x800.
        wr_u32(&blob[OPT_HDR_OFF + 112 + 6 * 8 + 4], BLOB_SIZE);
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe_blob_fullcover(DIR_RAW, NENT * 28);
        put_cv_entry(DIR_RAW, 0x500); // CodeView in entry 0, record at 0x500
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_pe_blob_fullcover(DIR_RAW, 28);
        put_cv_entry(DIR_RAW, 0x500); // record at 0x500 (in-bounds)
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        cv[27] = 'D';  // no NUL inside [24,28)
        cv[28] = '\0'; // NUL sits exactly AT region_end (one past the region)
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        wr_u32(&d0[24], 0);
        put_cv_entry(0x300 + 28, 0x500); // entry1 (just past declared count)
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        u64 plen = ZstrLen(kPdbPath);
        MemCopy(&cv2[24], kPdbPath, plen + 1);
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 2);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 2);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, total, ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 1);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 2);
    
        Pe   pe;
        bool opened = PeOpenFromMemoryCopy(&pe, blob, total, ALLOCATOR_OF(&alloc));
        if (opened)
        build_blob(blob, secs, 2);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 1);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 1);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, total, ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 1);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 2);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc))) {
            DefaultAllocatorDeinit(&alloc);
        build_blob(blob, secs, 2);
    
        Pe pe;
        if (!PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), base)) {
            DebugAllocatorDeinit(&alloc);
        blob[NT_OFF] = 'Q'; // corrupt the 'P' of "PE\0\0" -> fails after DOS
    
        Pe   pe;
        bool opened = PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc));
        // Real code: rejected AND `out` zeroed by PeDeinit on the fail path.
        }
    
        Pe   pe;
        bool ok = PeOpen(&pe, (Zstr)StrBegin(&path), base);
        if (ok) {
        }
    
        Pe   pe;
        bool opened = PeOpen(&pe, (Zstr)StrBegin(&path), base);
        if (opened)
        build_blob(blob, secs, 1);
    
        Pe   pe;
        bool ok = PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc));
        if (ok)
        blob[1] = 'X'; // 'MZ' -> 'MX'
    
        Pe   pe;
        bool opened = PeOpenFromMemoryCopy(&pe, blob, sizeof(blob), ALLOCATOR_OF(&alloc));
        if (opened)
    
    int main(void) {
        WriteFmt("[INFO] Starting Pe tests\n\n");
    
        TestFunction tests[] = {
        };
    
        return run_test_suite(tests, sizeof(tests) / sizeof(tests[0]), NULL, 0, "Pe");
    }
Last updated on