/* pe.pk -- PE/COFF implementation for GNU poke.  */

/* Copyright (C) 2022, 2023, 2024, 2025, 2026 Jose E. Marchesi.  */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

load time; /* For POSIX_Time32 */
load base64;
load coff;

/* Supported architectures/machines.  */

var PE_FILE_MACHINE_UNKNOWN = 0x0UH,
    PE_FILE_MACHINE_AM33 = 0x1d3UH,
    PE_FILE_MACHINE_AMD64 = 0x8664UH,
    PE_FILE_MACHINE_ARM = 0x1c0UH,
    PE_FILE_MACHINE_ARM64 = 0xaa64UH,
    PE_FILE_MACHINE_ARMNT = 0x1c4UH,
    PE_FILE_MACHINE_EBC = 0xebcUH,
    PE_FILE_MACHINE_I386 = 0x14cUH,
    PE_FILE_MACHINE_IA64 = 0x200UH,
    PE_FILE_MACHINE_LOONGARCH32 = 0x6232UH,
    PE_FILE_MACHINE_LOONGARCH64 = 0x6264UH,
    PE_FILE_MACHINE_M32R = 0x9041UH,
    PE_FILE_MACHINE_MIPS16 = 0x266UH,
    PE_FILE_MACHINE_MIPSFPU = 0x366UH,
    PE_FILE_MACHINE_MIPSFPU16 = 0x466UH,
    PE_FILE_MACHINE_POWERPC = 0x1f0UH,
    PE_FILE_MACHINE_POWERPCFP = 0x1f1UH,
    PE_FILE_MACHINE_R4000 = 0x166UH,
    PE_FILE_MACHINE_RISCV32 = 0x5032UH,
    PE_FILE_MACHINE_RISCV64 = 0x5064UH,
    PE_FILE_MACHINE_RISCV128 = 0x5128UH,
    PE_FILE_MACHINE_SH3 = 0x1a2UH,
    PE_FILE_MACHINE_SH3DSP = 0x1a3UH,
    PE_FILE_MACHINE_SH4 = 0x1a6UH,
    PE_FILE_MACHINE_SH5 = 0x1a8UH,
    PE_FILE_MACHINE_THUMB = 0x1c2UH,
    PE_FILE_MACHINE_WCEMIPSV2 = 0x169UH;

var pe_machines =
  [ PE_FILE_MACHINE_UNKNOWN, PE_FILE_MACHINE_AM33,
    PE_FILE_MACHINE_AMD64, PE_FILE_MACHINE_ARM,
    PE_FILE_MACHINE_ARM64, PE_FILE_MACHINE_ARMNT,
    PE_FILE_MACHINE_EBC, PE_FILE_MACHINE_I386,
    PE_FILE_MACHINE_IA64, PE_FILE_MACHINE_LOONGARCH32,
    PE_FILE_MACHINE_LOONGARCH64, PE_FILE_MACHINE_M32R,
    PE_FILE_MACHINE_MIPS16, PE_FILE_MACHINE_MIPSFPU,
    PE_FILE_MACHINE_MIPSFPU16, PE_FILE_MACHINE_POWERPC,
    PE_FILE_MACHINE_POWERPCFP, PE_FILE_MACHINE_R4000,
    PE_FILE_MACHINE_RISCV32, PE_FILE_MACHINE_RISCV64,
    PE_FILE_MACHINE_RISCV128, PE_FILE_MACHINE_SH3,
    PE_FILE_MACHINE_SH3DSP, PE_FILE_MACHINE_SH4,
    PE_FILE_MACHINE_SH5, PE_FILE_MACHINE_THUMB,
    PE_FILE_MACHINE_WCEMIPSV2 ];

/* Currently selected machine type/architecture.  */

var pe_machine = PE_FILE_MACHINE_UNKNOWN;

/* Load architecture-specific stuff.  */

load "pe-amd64.pk";
load "pe-arm.pk";
load "pe-arm64.pk";
load "pe-i386.pk";
load "pe-ia64.pk";
load "pe-m32r.pk";
load "pe-mips.pk";
load "pe-ppc.pk";
load "pe-sh3.pk";

/* PE magic numbers used in the optional COFF header.  */

var PE_MAGIC_PE32 = 0x10bUH,
    PE_MAGIC_PE32P = 0x20bUH; /* PE32+ */

/* DLL characteristics flag bits.  */

var PE_DLL_F_HIGH_ENTROPY_VA = 0x0020UH,
    PE_DLL_F_DYNAMIC_BASE = 0x0040UH,
    PE_DLL_F_FORCE_INTEGRITY = 0x0080UH,
    PE_DLL_F_NX_COMPAT = 0x0100UH,
    PE_DLL_F_NO_ISOLATION = 0x0200UH,
    PE_DLL_F_NO_SEH = 0x0400UH,
    PE_DLL_F_NO_BIND = 0x0800UH,
    PE_DLL_F_APPCONTAINER = 0x1000UH,
    PE_DLL_F_WDM_DRIVER = 0x2000UH,
    PE_DLL_F_GUARD_CF = 0x4000UH,
    PE_DLL_F_TERMINAL_SERVER_AWARE = 0x8000UH;

var pe_dll_characteristics =
  [ PE_DLL_F_HIGH_ENTROPY_VA, PE_DLL_F_DYNAMIC_BASE,
    PE_DLL_F_FORCE_INTEGRITY, PE_DLL_F_NX_COMPAT,
    PE_DLL_F_NO_ISOLATION, PE_DLL_F_NO_SEH,
    PE_DLL_F_NO_BIND, PE_DLL_F_APPCONTAINER,
    PE_DLL_F_WDM_DRIVER, PE_DLL_F_GUARD_CF,
    PE_DLL_F_TERMINAL_SERVER_AWARE ];

/* Section header flags.  */

var PE_IMAGE_SCN_MEM_DISCARDABLE = 0x02000000U,
    PE_IMAGE_SCN_MEM_EXECUTE     = 0x20000000U,
    PE_IMAGE_SCN_MEM_READ        = 0x40000000U,
    PE_IMAGE_SCN_MEM_WRITE       = 0x80000000U;

/* Section characteristics.  */

var PE_IMAGE_SCN_TYPE_NO_PAD = 0x00000008U, /* Reserved.  */
    PE_IMAGE_SCN_CNT_CODE    = 0x00000020U, /* Section contains code.  */
    PE_IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040U, /* Section contains initialized data.  */
    PE_IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080U, /* Section contains uninitialized data.  */
    PE_IMAGE_SCN_LNK_OTHER   = 0x00000100U, /* Reserved.  */
    PE_IMAGE_SCN_LNK_INFO    = 0x00000200U, /* Section contains comments or some other type of information.  */
    PE_IMAGE_SCN_LNK_REMOVE  = 0x00000800U, /* Section contents will not become part of image.  */
    PE_IMAGE_SCN_LNK_COMDAT  = 0x00001000U, /* Section contents comdat.  */
    PE_IMAGE_SCN_MEM_FARDATA = 0x00008000U,
    PE_IMAGE_SCN_MEM_PURGEABLE = 0x00020000U,
    PE_IMAGE_SCN_MEM_16BIT   = 0x00020000U,
    PE_IMAGE_SCN_MEM_LOCKED  = 0x00040000U,
    PE_IMAGE_SCN_MEM_PRELOAD = 0x00080000U;

/* COMDAT selection codes.  */

var PE_IMAGE_COMDAT_SELECT_NODUPLICATES = 1, /* Warn if duplicates.  */
    PE_IMAGE_COMDAT_SELECT_ANY = 2,          /* No warning.  */
    PE_IMAGE_COMDAT_SELECT_SAME_SIZE = 3,    /* Warn if different size.  */
    PE_IMAGE_COMDAT_SELECT_EXACT_MATCH = 4,  /* Warn if different.  */
    PE_IMAGE_COMDAT_SELECT_ASSOCIATIVE = 5;  /* Base on other section.  */

/* Machine numbers.  */

var PE_IMAGE_FILE_MACHINE_UNKNOWN    = 0x0000UH,
    PE_IMAGE_FILE_MACHINE_ALPHA      = 0x0184UH,
    PE_IMAGE_FILE_MACHINE_ALPHA64    = 0x0284UH,
    PE_IMAGE_FILE_MACHINE_AM33       = 0x01d3UH,
    PE_IMAGE_FILE_MACHINE_AMD64      = 0x8664UH,
    PE_IMAGE_FILE_MACHINE_ARM        = 0x01c0UH,
    PE_IMAGE_FILE_MACHINE_ARM64      = 0xaa64UH,
    PE_IMAGE_FILE_MACHINE_AXP64      = PE_IMAGE_FILE_MACHINE_ALPHA64,
    PE_IMAGE_FILE_MACHINE_CEE        = 0xc0eeUH,
    PE_IMAGE_FILE_MACHINE_CEF        = 0x0cefUH,
    PE_IMAGE_FILE_MACHINE_EBC        = 0x0ebcUH,
    PE_IMAGE_FILE_MACHINE_I386       = 0x014cUH,
    PE_IMAGE_FILE_MACHINE_IA64       = 0x0200UH,
    PE_IMAGE_FILE_MACHINE_M32R       = 0x9041UH,
    PE_IMAGE_FILE_MACHINE_M68K       = 0x0268UH,
    PE_IMAGE_FILE_MACHINE_MIPS16     = 0x0266UH,
    PE_IMAGE_FILE_MACHINE_MIPSFPU    = 0x0366UH,
    PE_IMAGE_FILE_MACHINE_MIPSFPU16  = 0x0466UH,
    PE_IMAGE_FILE_MACHINE_POWERPC    = 0x01f0UH,
    PE_IMAGE_FILE_MACHINE_POWERPCFP  = 0x01f1UH,
    PE_IMAGE_FILE_MACHINE_R10000     = 0x0168UH,
    PE_IMAGE_FILE_MACHINE_R3000      = 0x0162UH,
    PE_IMAGE_FILE_MACHINE_R4000      = 0x0166UH,
    PE_IMAGE_FILE_MACHINE_SH3        = 0x01a2UH,
    PE_IMAGE_FILE_MACHINE_SH3DSP     = 0x01a3UH,
    PE_IMAGE_FILE_MACHINE_SH3E       = 0x01a4UH,
    PE_IMAGE_FILE_MACHINE_SH4        = 0x01a6UH,
    PE_IMAGE_FILE_MACHINE_SH5        = 0x01a8UH,
    PE_IMAGE_FILE_MACHINE_THUMB      = 0x01c2UH,
    PE_IMAGE_FILE_MACHINE_TRICORE    = 0x0520UH,
    PE_IMAGE_FILE_MACHINE_WCEMIPSV2  = 0x0169UH,
    PE_IMAGE_FILE_MACHINE_AMD64      = 0x8664UH;

/* Subsystem types.  */

var PE_SUBSYS_UNKNOWN                 = 0UH,
    PE_SUBSYS_NATIVE                  = 1UH,
    PE_SUBSYS_WINDOWS_GUI             = 2UH,
    PE_SUBSYS_WINDOWS_CUI             = 3UH,
    PE_SUBSYS_POSIX_CUI               = 7UH,
    PE_SUBSYS_WINDOWS_CE_GUI          = 9UH,
    PE_SUBSYS_EFI_APPLICATION         = 10UH,
    PE_SUBSYS_EFI_BOOT_SERVICE_DRIVER = 11UH,
    PE_SUBSYS_EFI_RUNTIME_DRIVER      = 12UH,
    PE_SUBSYS_SAL_RUNTIME_DRIVER      = 13UH,
    PE_SUBSYS_XBOX                    = 14UH;

var pe_subsystems =
  [ PE_SUBSYS_UNKNOWN, PE_SUBSYS_NATIVE, PE_SUBSYS_WINDOWS_GUI,
    PE_SUBSYS_WINDOWS_CUI, PE_SUBSYS_POSIX_CUI,
    PE_SUBSYS_WINDOWS_CE_GUI, PE_SUBSYS_EFI_APPLICATION,
    PE_SUBSYS_EFI_BOOT_SERVICE_DRIVER, PE_SUBSYS_EFI_RUNTIME_DRIVER,
    PE_SUBSYS_SAL_RUNTIME_DRIVER, PE_SUBSYS_XBOX ];

/* Characteristics of the PE/COFF image, encoded in the `flags' field
   of the COFF header.  */

var PE_F_RELOCS_STRIPPED = 0x0001UH,
    PE_F_EXECUTABLE_IMAGE = 0x0002UH,
    PE_F_LINE_NUMS_STRIPPED = 0x0004UH,
    PE_F_LOCL_SYMS_STRIPPED = 0x0008UH,
    PE_F_AGGRESSIVE_WS_TRIM = 0x0010UH,
    PE_F_LARGE_ADDRESS_AWARE = 0x0020UH,
    PE_F_BYTES_REVERSED_LO = 0x0080UH;

var pe_characteristics =
  [PE_F_RELOCS_STRIPPED, PE_F_EXECUTABLE_IMAGE,
   PE_F_LINE_NUMS_STRIPPED, PE_F_LOCL_SYMS_STRIPPED,
   PE_F_AGGRESSIVE_WS_TRIM, PE_F_LARGE_ADDRESS_AWARE,
   PE_F_BYTES_REVERSED_LO];

/* PE section flags.  */

var PE_SEC_F_TYPE_NO_PAD = 0x0008U,
    PE_SEC_F_CNT_CODE = 0x0020U,
    PE_SEC_F_CNT_INITIALIZED_DATA = 0x0040U,
    PE_SEC_F_CNT_UNINITIALIZED_DATA = 0x0080U,
    PE_SEC_F_LNK_OTHER = 0x0100U,
    PE_SEC_F_LNK_INFO = 0x0200U,
    PE_SEC_F_LNK_REMOVE = 0x0800U,
    PE_SEC_F_LNK_COMDAT = 0x1000U,
    PE_SEC_F_GPREL = 0x8000U,
    PE_SEC_F_MEM_PURGEABLE = 0x20000U,
    PE_SEC_F_MEM_16BIT = 0x20000U,
    PE_SEC_F_MEM_LOCKED = 0x40000U,
    PE_SEC_F_MEM_PRELOAD = 0x80000U,
    PE_SEC_F_ALIGN_1BYTES = 0x100000U,
    PE_SEC_F_ALIGN_2BYTES = 0x200000U,
    PE_SEC_F_ALIGN_4BYTES = 0x300000U,
    PE_SEC_F_ALIGN_8BYTES = 0x400000U,
    PE_SEC_F_ALIGN_16BYTES = 0x500000U,
    PE_SEC_F_ALIGN_32BYTES = 0x600000U,
    PE_SEC_F_ALIGN_64BYTES = 0x700000U,
    PE_SEC_F_ALIGN_128BYTES = 0x800000U,
    PE_SEC_F_ALIGN_512BYTES = 0xa00000U,
    PE_SEC_F_ALIGN_1024BYTES = 0xb00000U,
    PE_SEC_F_ALIGN_2048BYTES = 0xc00000U,
    PE_SEC_F_ALIGN_4096BYTES = 0xd00000U,
    PE_SEC_F_ALIGN_8192BYTES = 0xe00000U,
    PE_SEC_F_LNK_NRELOC_OVFL = 0x01000000U,
    PE_SEC_F_MEM_DISCARDABLE = 0x02000000U,
    PE_SEC_F_MEM_NOT_CACHED = 0x04000000U,
    PE_SEC_F_MEM_NOT_PAGED = 0x08000000U,
    PE_SEC_F_MEM_SHARED = 0x10000000U,
    PE_SEC_F_MEM_EXECUTE = 0x20000000U,
    PE_SEC_F_MEM_READ = 0x40000000U,
    PE_SEC_F_MEM_WRITE = 0x80000000U;

/* PE names.

   COFF names are sort of ugly (see COFF_Name in coff.pk).
   The PE/COFF variant /ASCII is an atrocity.
   The Microsoft extension to support //BASE64 is unspeakable filth. */

type PE_Name =
  struct
  {
    /* Array containing a null-padded UTF-8 encoded string. If the
       string is exactly 8 characters long, there is no terminating
       null. For longer names, this field contains a slash (/) that is
       followed by an ASCII representation of a decimal number that is
       an offset into the string table.  But if that offset needs more
       than 7 digits, the field contains two slashes (//) followed by
       a left-truncated base64 encoding of a 32-bit unsigned integer
       in big endian, to which one has to prepend "AA", i.e. for
       //XXXXXX the base64 string to decode is "AAXXXXXX".  */
    uint<8>[8] chars;

    /* Offset in the COFF string table for this name.  If the length
       of the name is 8 bytes or shorter, then return 0#B (which is
       not a valid offset in COFF string tables.)  */
    computed offset<uint<32>,B> offset;

    method get_offset = offset<uint<32>,B>:
    {
      if (chars[0] != '/')
        return 0#B;
      if (chars[1] == '/')
        return (base64_decode ("AA" + catos (chars[2:])) as uint<32>)#B;
      else
        return atoi (catos (chars[1:]))#B;
    }

    method set_offset = (offset<uint<32>,B> offset) void:
    {
      /* Note 9999999 is the maximum number that can be encoded
         with seven ASCII characters.  */
      if (offset > 9999999#B)
        {
          // XXX Simplify when array deintegrators become available.
          var m = offset'magnitude;
          stoca ("//" +
                 base64_encode ([0UB, 0UB,
                                 (m .>> 24 & 0xff) as uint<8>,
                                 (m .>> 16 & 0xff) as uint<8>,
                                 (m .>>  8 & 0xff) as uint<8>,
                                 (m & 0xff) as uint<8>])[2:],
                 chars);
        }
      else
        stoca ("/" + format ("%i32d", offset'magnitude), chars);
    }

    method match_p = (string s) int<32>:
    {
      return catos (chars) == s;
    }

    method _print = void:
    {
      print "#<" + catos (chars) + ">";
    }
  };

/* PE section header.  */

type PE_Shdr =
  struct
  {
    PE_Name name;

    offset<uint<32>,B> virtual_size; /* XXX should be zero for objects.  */
    offset<uint<32>,B> virtual_address;
    offset<uint<32>,B> size_of_raw_data;
    offset<uint<32>,B> ptr_to_raw_data;
    offset<uint<32>,B> ptr_to_relocs;
    offset<uint<32>,B> ptr_to_linenos;

    uint<16> num_relocs;
    uint<16> num_linenos;

    uint<32> flags : ((flags & PE_IMAGE_SCN_LNK_INFO) && name.match_p (".drectve")
                      => num_relocs == 0 && num_linenos == 0)
                     && (flags & PE_SEC_F_LNK_NRELOC_OVFL => num_relocs == 0xffff);
  };


/* Supported symbol base types, encoded in the least-significative
   byte of COFF_Sym.e_type.  */

var PE_SYM_TYPE_NULL = 0UB,
    PE_SYM_TYPE_VOID = 1UB,
    PE_SYM_TYPE_CHAR = 2UB,
    PE_SYM_TYPE_SHORT = 3UB,
    PE_SYM_TYPE_INT = 4UB,
    PE_SYM_TYPE_LONG = 5UB,
    PE_SYM_TYPE_FLOAT = 6UB,
    PE_SYM_TYPE_DOUBLE = 7UB,
    PE_SYM_TYPE_STRUCT = 8UB,
    PE_SYM_TYPE_UNION = 9UB,
    PE_SYM_TYPE_ENUM = 10UB,
    PE_SYM_TYPE_MOE = 11UB,
    PE_SYM_TYPE_BYTE = 12UB,
    PE_SYM_TYPE_WORD = 13UB,
    PE_SYM_TYPE_UINT = 14UB,
    PE_SYM_TYPE_DWORD = 15UB;

/* Supported complex sym types, encoded in the most-significative byte
   of COFF_Sym.e_type.  */

var PE_SYM_DTYPE_NULL = 0UB,
    PE_SYM_DTYPE_POINTER = 1UB,
    PE_SYM_DTYPE_FUNCTION = 2UB,
    PE_SYM_DTYPE_ARRAY = 3UB;

/* Symbol storage classes, to be used in COFF_Sym.e_sclass.  */

var PE_SYM_CLASS_END_OF_FUNCTION = 0xFFUB,
    PE_SYM_CLASS_NULL = 0UB,
    PE_SYM_CLASS_AUTOMATIC = 1UB,
    PE_SYM_CLASS_EXTERNAL = 2UB,
    PE_SYM_CLASS_STATIC = 3UB,
    PE_SYM_CLASS_REGISTER = 4UB,
    PE_SYM_CLASS_EXTERNAL_DEF = 5UB,
    PE_SYM_CLASS_LABEL = 6UB,
    PE_SYM_CLASS_UNDEFINED_LABEL = 7UB,
    PE_SYM_CLASS_MEMBER_OF_STRUCT = 8UB,
    PE_SYM_CLASS_ARGUMENT = 9UB,
    PE_SYM_CLASS_STRUCT_TAG = 10UB,
    PE_SYM_CLASS_MEMBER_OF_UNION = 11UB,
    PE_SYM_CLASS_UNION_TAG = 12UB,
    PE_SYM_CLASS_TYPE_DEFINITION = 13UB,
    PE_SYM_CLASS_UNDEFINED_STATIC = 14UB,
    PE_SYM_CLASS_ENUM_TAG = 15UB,
    PE_SYM_CLASS_MEMBER_OF_ENUM = 16UB,
    PE_SYM_CLASS_REGISTER_PARAM = 17UB,
    PE_SYM_CLASS_BIT_FIELD = 18UB,
    PE_SYM_CLASS_BLOCK = 100UB,
    PE_SYM_CLASS_FUNCTION = 101UB,
    PE_SYM_CLASS_END_OF_STRUCT = 102UB,
    PE_SYM_CLASS_FILE = 103UB,
    PE_SYM_CLASS_SECTION = 104UB,
    PE_SYM_CLASS_WEAK_EXTERNAL = 105UB,
    PE_SYM_CLASS_CLR_TOKEN = 107UB;

/* PE auxiliary symbol records.  These follow certain kind of regular
   symbol records and must be 18 bytes long.  */

type PE_Func_Def =
  struct
  {
    uint<32> tag_index;
    offset<uint<32>,B> total_size;
    offset<uint<32>,B> ptr_to_linenos;
    offset<uint<32>,B> ptr_to_next_func;
    uint<16>;
  };

assert (1#PE_Func_Def == 18#B);

type PE_Begin_Function =
  struct
  {
    uint<32>;
    uint<16> lineno : lineno > 0;
    uint<48>;
    offset<uint<32>,B> ptr_to_next_function;
    uint<16>;
  };

assert (1#PE_Begin_Function == 18#B);

type PE_End_Function =
  struct
  {
    uint<32>;
    uint<16> lineno : lineno > 0;
    uint<48>;
    uint<32>;
    uint<16>;
  };

assert (1#PE_End_Function == 18#B);

/* XXX I made these values up - jemarch  */
var PE_WEAK_EXTERN_SEARCH_NOLIBRARY = 0x1U,
    PE_WEAK_EXTERN_SEARCH_LIBRARY = 0x2U,
    PE_WEAK_EXTERN_SEARCH_ALIAS = 0x4U;

type PE_Weak_External =
  struct
  {
    uint<32> tag_index;
    uint<32> flags;
    uint<64>;
    uint<16>;
  };

assert (1#PE_Weak_External == 18#B);

var PE_COMDAT_SELECT_NODUPLICATES = 1UB,
    PE_COMDAT_SELECT_ANY = 2UB,
    PE_COMDAT_SELECT_SAME_SIZE = 3UB,
    PE_COMDAT_SELECT_EXACT_MATCH = 4UB,
    PE_COMDAT_SELECT_ASSOCIATIVE = 5UB,
    PE_COMDAT_SELECT_LARGEST = 6UB;

type PE_Section_Definition =
  struct
  {
    offset<uint<32>,B> size; /* Same as PE_Shdr.size_of_raw_data  */
    uint<16> num_of_relocs;
    uint<16> num_of_linenos;
    uint<32> checksum;
    uint<16> number;
    uint<8> selection : selection in [0UB, /* When no COMDAT section.  */
                                      PE_COMDAT_SELECT_NODUPLICATES,
                                      PE_COMDAT_SELECT_ANY,
                                      PE_COMDAT_SELECT_SAME_SIZE,
                                      PE_COMDAT_SELECT_EXACT_MATCH,
                                      PE_COMDAT_SELECT_ASSOCIATIVE,
                                      PE_COMDAT_SELECT_LARGEST];
    uint<24> unused;
  };

assert (1#PE_Section_Definition == 18#B);

var PE_AUX_SYMBOL_TYPE_TOKEN_DEF = 1UB;

type PE_CLR_Token =
  struct
  {
    uint<8> aux_type == PE_AUX_SYMBOL_TYPE_TOKEN_DEF;
    uint<8> rsv1 : rsv1 == 0;
    uint<32> symbol_table_index;
    uint<64> rsv2 : rsv2 == 0;
    uint<32> rsv3 : rsv3 == 0;
  };

assert (1#PE_CLR_Token == 18#B);

/* PE symbol records.

   Each regular COFF symbol may be followed by an auxiliary "aux
   symbol".  Note that the number of symbols in the symbol table
   counts both regular and aux symbols, so when mapping an array of
   PE_Sym structures, please do it by size.  */

type PE_Sym =
  struct
  {
    COFF_Sym sym;
    union
    {
      PE_Func_Def function_definition : sym.e_sclass == PE_SYM_CLASS_EXTERNAL
                                        && sym.e_type.msb == PE_SYM_DTYPE_FUNCTION
                                        && sym.e_scnum > 0;
      PE_Begin_Function bf            : sym.e_type.msb == PE_SYM_DTYPE_FUNCTION
                                        && catos (sym.e_name.name) == ".bf";
      PE_End_Function ef              : sym.e_type.msb == PE_SYM_DTYPE_FUNCTION
                                        && catos (sym.e_name.name) == ".ef";
      PE_Weak_External weak_external  : sym.e_sclass == PE_SYM_CLASS_EXTERNAL
                                        && sym.e_scnum == COFF_N_UNDEF
                                        && sym.e_value == 0;
      /* We assume here that any symbol with static storage and whose
         name starts with a dot character '.' is a section name.  */
      PE_Section_Definition section_definition
                                      : sym.e_sclass == PE_SYM_CLASS_STATIC
                                        && (sym.e_name.name ?! E_elem
                                            || sym.e_name.name[0] == '.');
      PE_CLR_Token clr_token          : sym.e_sclass == PE_SYM_CLASS_CLR_TOKEN;
      uint<8>[18] file_name           : sym.e_sclass == PE_SYM_CLASS_FILE;
      struct {} empty;
    } aux_sym;
  };

/* PE attribute certificates.  */

var PE_WIN_CERT_REVISION_1_0 = 0x0100UH,
    PE_WIN_CERT_REVISION_2_0 = 0x0200UH;

var PE_WIN_CERT_TYPE_X509 = 0x0001UH,
    PE_WIN_CERT_TYPE_PKCS_SIGNED_DATA = 0x0002UH,
    PE_WIN_CERT_TYPE_RESERVED_1 = 0x0003UH,
    PE_WIN_CERT_TYPE_TS_STACK_SIGNED = 0x0004UH;

type PE_Attribute_Certificate =
  struct
  {
    offset<uint<32>,B> length;
    uint<16> revision;
    uint<16> cert_type;

    /* Note: we use internal padding here so we can map arrays of
       attribute certificates by total size.  */
    uint<8>[0] certificate_begin;
    uint<8>[0] certificate_end @ length + alignto (length, 8#B);
  };

/* PE delay import descriptors.  */

type PE_Delay_Import =
  struct
  {
    uint<32> attributes == 0;
    offset<uint<32>,B> name;
    offset<uint<32>,B> module_handle;
    offset<uint<32>,B> delay_import_address_table;
    offset<uint<32>,B> delay_import_name_table;
    offset<uint<32>,B> bound_delay_import_table;
    offset<uint<32>,B> unload_delay_import_table;
    POSIX_Time32 timestamp;
  };

/* PE debug directory.

   Each entry in the debug directory describes a form of debug
   information present in the PE file.  */

var PE_DEBUG_TYPE_UNKNOWN               = 0U,
    PE_DEBUG_TYPE_COFF                  = 1U,
    PE_DEBUG_TYPE_CODEVIEW              = 2U,
    PE_DEBUG_TYPE_FPO                   = 3U,
    PE_DEBUG_TYPE_MISC                  = 4U,
    PE_DEBUG_TYPE_EXCEPTION             = 5U,
    PE_DEBUG_TYPE_FIXUP                 = 6U,
    PE_DEBUG_TYPE_OMAP_TO_SRC           = 7U,
    PE_DEBUG_TYPE_OMAP_FROM_SRC         = 8U,
    PE_DEBUG_TYPE_BORLAND               = 9U,
    PE_DEBUG_TYPE_RESERVED10            = 10U,
    PE_DEBUG_TYPE_CLSID                 = 11U,
    PE_DEBUG_TYPE_REPRO                 = 16U,
    PE_DEBUG_TYPE_EX_DLLCHARACTERISTICS = 20U;

type PE_Debug_Directory_Entry =
  struct
  {
    /* Reserved.  */
    uint<32> flags == 0;

    /* Date and time that the debug data was created.  */
    POSIX_Time32 timestamp;

    /* Version number of the debug data format.  */
    uint<16> major_version;
    uint<16> minor_version;

    /* Format of debugging information.  */
    uint<32> debug_type : debug_type in [ PE_DEBUG_TYPE_UNKNOWN,
                                          PE_DEBUG_TYPE_COFF,
                                          PE_DEBUG_TYPE_CODEVIEW,
                                          PE_DEBUG_TYPE_FPO,
                                          PE_DEBUG_TYPE_MISC,
                                          PE_DEBUG_TYPE_EXCEPTION,
                                          PE_DEBUG_TYPE_FIXUP,
                                          PE_DEBUG_TYPE_OMAP_TO_SRC,
                                          PE_DEBUG_TYPE_OMAP_FROM_SRC,
                                          PE_DEBUG_TYPE_BORLAND,
                                          PE_DEBUG_TYPE_CLSID,
                                          PE_DEBUG_TYPE_REPRO,
                                          PE_DEBUG_TYPE_EX_DLLCHARACTERISTICS ];

    /* Size of the debug data (not including the debug directory.) */
    offset<uint<32>,B> size_of_data;

    /* The address of the debug data when loaded, relative to the
       image base.  */
    offset<uint<32>,B> address_of_raw_data;

    /* File offset to the debug data described by this entry.  */
    offset<uint<32>,B> ptr_to_raw_data;
 };

/* PE Load Configuration Layout.  */

var PE_GUARD_CF_INSTRUMENTED = 0x00000100U,
    PE_GUARD_CFW_INSTRUMENTED = 0x00000200U,
    PE_GUARD_CF_FUNCTION_TABLE_PRESENT = 0x00000400U,
    PE_GUARD_SECURITY_COOKIE_UNUSED = 0x00000800U,
    PE_GUARD_PROTECT_DELAYLOAD_IAT = 0x00001000U,
    PE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION = 0x00002000U,
    PE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT = 0x00004000U,
    PE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION = 0x00008000U,
    PE_GUARD_CF_LONGJUMP_TABLE_PRESENT = 0x00010000U,
    PE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK = 0xF0000000U;

type PE_Load_Configuration_Layout =
  struct
  {
    uint<32> flags == 0;
    POSIX_Time32 timestamp;
    uint<16> major_version;
    uint<16> minor_version;
    uint<32> global_flags_clear;
    uint<32> global_flags_set;
    uint<32> critical_section_default_timeout;
    offset<uint<32>,B> de_commit_free_block_threshold;
    offset<uint<32>,B> de_commit_total_free_threshold;
    offset<uint<32>,B> lock_prefix_table
      if pe_machine == PE_FILE_MACHINE_I386;
    offset<uint<32>,B> maximum_allocation_size;
    offset<uint<32>,B> virtual_memory_threshold;
    uint<32> process_affinity_mask;
    uint<32> process_heap_flags;
    uint<16> csd_version;
    uint<16> reserved == 0;
    uint<32> edit_list;
    offset<uint<32>,B> security_cookie;
    offset<uint<32>,B> se_handler_table
      if pe_machine == PE_FILE_MACHINE_I386;
    uint<32> se_handler_count
      if pe_machine == PE_FILE_MACHINE_I386;
    offset<uint<32>,B> guard_cf_check_function_ptr;
    offset<uint<32>,B> guard_cf_dispatch_function_ptr;
    offset<uint<32>,B> guard_cf_function_table;
    uint<32> guard_cf_function_count;
    uint<32> guard_flags;
    uint<8>[12] code_integrity;
    offset<uint<32>,B> guard_addr_taken_iat_entry_table;
    uint<32> guard_addr_taken_iat_entry_count;
    offset<uint<32>,B> guard_long_jump_target_table;
    uint<32> guard_long_jump_target_count;
  };

/* Entry for the PE Import Directory Table.  */

type PE_Import_Directory_Entry =
  struct
  {
    offset<uint<32>,B> import_lookup_table_offset;
    POSIX_Time32 timestamp;
    uint<32> forwarder_chain;
    offset<uint<32>,B> dll_name_offset;
    offset<uint<32>,B> import_address_table_offset;
  };

/* Import Lookup Table entry.

   The 32-bit variant is used in PE32.
   The 64-bit variant is used in PE32+.  */

type PE_Import_Lookup_Table_Entry =
  union uint<32>
  {
    struct uint<32>
    {
      uint<1> import_by_ordinal_flag : import_by_ordinal_flag == 1;
      uint<15> zero : zero == 0;
      uint<16> ordinal_number;
    } by_ordinal;

    struct uint<32>
    {
      uint<1> import_by_ordinal_flag;
      offset<uint<31>,B> name_table_offset;
    } by_name;
  };

type PE_Import_Lookup_Table_Entry_64 =
  union uint<64>
  {
    struct uint<64>
    {
      uint<1> import_by_ordinal_flag : import_by_ordinal_flag == 1;
      uint<47> zero == 0;
      uint<16> ordinal_number;
    } by_ordinal;

    struct uint<64>
    {
      uint<1> import_by_ordinal_flag : import_by_ordinal_flag == 0;
      uint<32> zero == 0;
      offset<uint<31>,B> hint_name_table_offset;
    } by_name;
  };

/* Hint/Name Table Entry.  */

type PE_Hint_Name_Table_Entry =
  struct
  {
    uint<16> hint;
    string name;
    if (OFFSET % 2#B != 0#B)
      uint<8> pad;
  };

/* Function Table Entry.

   These live in the .pdata section and are used for exception
   handling.  There are three variants, which are used depending
   on the target architecture.  */

type PE_Function_Table_Entry_A =
  struct
  {
    offset<uint<32>,B> begin_addr;
    offset<uint<32>,B> end_addr;
    offset<uint<32>,B> exception_handler;
    offset<uint<32>,B> handler_data;
    offset<uint<32>,B> prolog_end_address;
  };

type PE_Function_Table_Entry_B =
  struct
  {
    offset<uint<32>,B> begin_addr;
    uint<8> prolog_length;
    uint<22> function_length;
    uint<1> i32bit_flag;
    uint<1> exception_flag;
  };

type PE_Function_Table_Entry_C =
  struct
  {
    offset<uint<32>,B> begin_addr;
    offset<uint<32>,B> end_addr;
    offset<uint<32>,B> unwind_info;
  };

type PE_Function_Table_Entry =
  union
  {
    PE_Function_Table_Entry_A a : pe_machine in [PE_FILE_MACHINE_MIPS16,
                                                 PE_FILE_MACHINE_MIPSFPU,
                                                 PE_FILE_MACHINE_MIPSFPU16];
    PE_Function_Table_Entry_B b : pe_machine in [PE_FILE_MACHINE_ARM,
                                                 PE_FILE_MACHINE_THUMB,
                                                 PE_FILE_MACHINE_POWERPC,
                                                 PE_FILE_MACHINE_POWERPCFP,
                                                 PE_FILE_MACHINE_SH3,
                                                 PE_FILE_MACHINE_SH3DSP,
                                                 PE_FILE_MACHINE_SH4,
                                                 PE_FILE_MACHINE_SH5];
    PE_Function_Table_Entry_C c : pe_machine in [PE_FILE_MACHINE_I386,
                                                 PE_FILE_MACHINE_IA64];
  };

/* Entry for the PE Export Directory Table.

   This contains information on how to locate all the others export
   tables in the file.  */

type PE_Export_Directory_Entry =
  struct
  {
    uint<32> flags == 0;
    POSIX_Time32 timestamp;
    uint<16> major_version;
    uint<16> minor_version;
    offset<uint<32>,B> dll_name_offset;
    uint<32> ordinal_base;
    uint<32> num_address_table_entries;
    uint<32> num_name_pointer_table_entries;
    /* The offsets below are all relative to the beginning of
       the PE file.  */
    offset<uint<32>,B> export_address_table_offset;
    offset<uint<32>,B> name_pointer_table_offset;
    offset<uint<32>,B> ordinal_table_offset;
  };

/* PE relocations.  */

type PE_Reloc =
  union
  {
    PE_ARM64_Reloc aarch64 : pe_machine in [PE_IMAGE_FILE_MACHINE_ARM64];
    struct
      {
        offset<uint<32>,B> r_vaddr;
        uint<32> r_symndx;
        uint<16> r_type;
      } pe;
  };

/* PE Base relocations.  */

var PE_R_BASED_ABSOLUTE = 0x00UN,
    PE_R_BASED_HIGH = 0x01UN,
    PE_R_BASED_LOW = 0x02UN,
    PE_R_BASED_HIGHLOW = 0x03UN,
    PE_R_BASED_HIGHADJ = 0x04UN,
    PE_R_BASED_DIR64 = 0x0aUN;

type PE_Base_Reloc =
  struct uint<16>
  {
    uint<4> reloc_type;
    uint<12> offset;
  };

type PE_Base_Reloc_Block =
  struct
  {
    offset<uint<32>,B> page_rva;
    offset<uint<32>,B> block_size : block_size >= OFFSET;
    PE_Base_Reloc[block_size - OFFSET] relocs;
  };

/* PE TLS Directory.  */

type PE_TLS_Directory =
  struct
  {
    offset<uint<32>,B> raw_data_start_va;
    offset<uint<32>,B> raw_data_end_va;
    offset<uint<32>,B> address_of_index;
    offset<uint<32>,B> address_of_callbacks;
    offset<uint<32>,B> size_of_zero_fill;
    struct uint<32>
    {
      uint<9>;
      uint<4> alignment_info
        : alignment_info as uint<32> in [ PE_SEC_F_ALIGN_1BYTES,
                                          PE_SEC_F_ALIGN_2BYTES,
                                          PE_SEC_F_ALIGN_4BYTES,
                                          PE_SEC_F_ALIGN_8BYTES,
                                          PE_SEC_F_ALIGN_16BYTES,
                                          PE_SEC_F_ALIGN_32BYTES,
                                          PE_SEC_F_ALIGN_64BYTES,
                                          PE_SEC_F_ALIGN_128BYTES,
                                          PE_SEC_F_ALIGN_512BYTES,
                                          PE_SEC_F_ALIGN_1024BYTES,
                                          PE_SEC_F_ALIGN_2048BYTES,
                                          PE_SEC_F_ALIGN_4096BYTES,
                                          PE_SEC_F_ALIGN_8192BYTES ];
      uint<19>;
    } flags;
  };

type PE_TLS_Directory_64 =
  struct
  {
    offset<uint<64>,B> raw_data_start_va;
    offset<uint<64>,B> raw_data_end_va;
    offset<uint<64>,B> address_of_index;
    offset<uint<64>,B> address_of_callbacks;
    offset<uint<32>,B> size_of_zero_fill;
    struct uint<32>
    {
      uint<9>;
      uint<4> alignment_info
        : alignment_info as uint<32> in [ PE_SEC_F_ALIGN_1BYTES,
                                          PE_SEC_F_ALIGN_2BYTES,
                                          PE_SEC_F_ALIGN_4BYTES,
                                          PE_SEC_F_ALIGN_8BYTES,
                                          PE_SEC_F_ALIGN_16BYTES,
                                          PE_SEC_F_ALIGN_32BYTES,
                                          PE_SEC_F_ALIGN_64BYTES,
                                          PE_SEC_F_ALIGN_128BYTES,
                                          PE_SEC_F_ALIGN_512BYTES,
                                          PE_SEC_F_ALIGN_1024BYTES,
                                          PE_SEC_F_ALIGN_2048BYTES,
                                          PE_SEC_F_ALIGN_4096BYTES,
                                          PE_SEC_F_ALIGN_8192BYTES ];
      uint<19>;
    } flags;
  };

/* PE data directory descriptor.  */

type PE_Data_Directory =
  struct
  {
    offset<uint<32>,B> rva;
    offset<uint<32>,B> size;
  };

/* Entries in the optional header data_directories array.  */

var PE_DD_EXPORT_TABLE = 0,
    PE_DD_IMPORT_TABLE = 1,
    PE_DD_RESOURCE_TABLE = 2,
    PE_DD_EXCEPTION_TABLE = 3,
    PE_DD_CERTIFICATE_TABLE = 4,
    PE_DD_BASE_RELOCATION_TABLE = 5,
    PE_DD_DEBUG = 6,
    PE_DD_ARCHITECTURE = 7,
    PE_DD_GLOBAL_PTR = 8,
    PE_DD_TLS_TABLE = 9,
    PE_DD_LOAD_CONFIG_TABLE = 10,
    PE_DD_BOUND_IMPORT = 9,
    PE_DD_IAT = 10,
    PE_DD_DELAY_IMPORT_DESCRIPTOR = 11,
    PE_DD_CLR_RUNTIME_HEADER = 12,
    PE_DD_RESERVED = 13;

/* PE optional header.  */

type PE_Opt_Hdr =
  struct
  {
    COFF_Opt_Hdr coff_opt_hdr = COFF_Opt_Hdr { magic = PE_MAGIC_PE32 }
                              : coff_opt_hdr.magic in [PE_MAGIC_PE32,
                                                       PE_MAGIC_PE32P];

    var pe32p = coff_opt_hdr.magic == PE_MAGIC_PE32P;

    if (!pe32p)
      offset<uint<32>,B> data_start;

    /* Preferred address of the first byte of image when loaded into
       memory.  */
    union
    {
      offset<uint<64>,B> a64 : pe32p && a64 % 64#B == 0#B;
      offset<uint<32>,B> a32 : (a32 & 0xffff#B) == 0#B;
    } image_base;

    /* Alignment of sections when they are loaded into memory.  */
    offset<uint<32>,B> section_alignment;

    fun is_power_of_two = (offset<uint<32>,B> o) int<32>:
    {
      return o != 0#B && (o & (o - 1#B)) == 0#B;
    }

    /* Alignment factor that is used to align the raw data of sections
       in the image file.  */
    offset<uint<32>,B> file_alignment : is_power_of_two (file_alignment)
                                        && file_alignment <= section_alignment
                                        && file_alignment >= 512#B
                                        && file_alignment <= 64#KB
                                      = 512#B;

    /* Version numbers for operating system, images etc.  */
    uint<16> major_os_version;
    uint<16> minor_os_version;
    uint<16> major_image_version;
    uint<16> minor_image_version;
    uint<16> major_subsystem_version;
    uint<16> minor_subsystem_version;
    uint<32> win32_version_value;

    /* The size of the image, including all headers, as the image is
       loaded in memory.  */
    offset<uint<32>,B> size_of_image
                       : size_of_image % section_alignment == 0#B;

    /* The combined size of an MS-DOS stub, PE header, and section
       headers rounded up to a multiple of FileAlignment.  */
    offset<uint<32>,B> size_of_headers
                       : size_of_headers % file_alignment == 0#B;

    /* The image file checksum.  */
    uint<32> checksum;

    /* The subsystem that is required to run this image.  */
    uint<16> subsystem : subsystem in pe_subsystems;

    /* DLL characteristics.  */
    uint<16> dll_characteristics;

    /* The size of the stack to reserve.  */
    union
    {
      offset<uint<64>,B> s64 : pe32p;
      offset<uint<32>,B> s32;
    } size_of_stack_reserve;

    /* The size of the stack to commit.  */
    union
    {
      offset<uint<64>,B> s64 : pe32p;
      offset<uint<32>,B> s32;
    } size_of_stack_commit;

    /* The size of the local heap space to reserve.  */
    union
    {
      offset<uint<64>,B> s64 : pe32p;
      offset<uint<32>,B> s32;
    } size_of_heap_reserve;

    /* The size of the local heap space to commit.  */
    union
    {
      offset<uint<64>,B> s64 : pe32p;
      offset<uint<32>,B> s32;
    } size_of_heap_commit;

    /* Loader flags.  This is reserved and should be zero.
       However, sometimes it is not zero.  */
    uint<32> loader_flags;

    /* Number of data-directory entries in the remainder of the
       optional header.  */
    uint<32> num_of_rva_and_sizes;

    /* Data directories.  */
    PE_Data_Directory[num_of_rva_and_sizes] data_directories
      : (num_of_rva_and_sizes > PE_DD_ARCHITECTURE
          => (data_directories[PE_DD_ARCHITECTURE].rva == 0#B
              && data_directories[PE_DD_ARCHITECTURE].size == 0#B))
        && (num_of_rva_and_sizes > PE_DD_GLOBAL_PTR
            => data_directories[PE_DD_GLOBAL_PTR].size == 0#B)
        && (num_of_rva_and_sizes > PE_DD_RESERVED
             => (data_directories[PE_DD_RESERVED].rva == 0#B
                 && data_directories[PE_DD_RESERVED].size == 0#B));
  };

/* PE file header.  */

type PE_File_Hdr =
  struct
  {
    fun set_machine = (int<32> mach) int<32>: { pe_machine = mach; return 1; }

    uint<16>           machine = pe_machines[0] : machine in pe_machines
                                                  && set_machine (machine);
    uint<16>           nscns;  /* Number of sections.  */
    POSIX_Time32       timdat; /* Time and date stamp.  */
    offset<uint<32>,B> symptr; /* File pointer to symtab.  */
    uint<32>           nsyms;  /* Number of symtab entries.  */
    offset<uint<16>,B> opthdr; /* Size of the optional header. */
    uint<16>           flags;  /* File flags.  */

  };

/* PE file.  */

type PE_File =
  struct
  {
    offset<uint<32>,B> pe_offset @ 0x3C#B;
    uint<8>[4] signature == ['P','E','\0','\0'] @ pe_offset;

    PE_File_Hdr hdr;
    var opt_hdr_offset = OFFSET;
    if (hdr.opthdr > 0#B)
      PE_Opt_Hdr opt_hdr;

    /* Note that the actual size of opt_hdr may not actually match
       hdr.opthdr.  The Microsoft spec warns about this: "Make sure
       to use the size of the optional header as specified in the
       file header.  So we have to use a label here.  */
    if (hdr.nscns > 0)
      PE_Shdr[hdr.nscns] sections @ opt_hdr_offset + hdr.opthdr;
    if (hdr.nsyms > 0)
      /* See PE_Sym above as for why we are mapping by size.  */
      PE_Sym[hdr.nsyms * 18#B] symbols @ hdr.symptr;
    COFF_Strtab strings;
  };
