Pure-MoonBit Windows PE32/PE32+ reader: headers, IAT, dependencies, resources, LoadConfig and security metadata, with precise errors instead of panics.
| Input | moonar 0.6.1 | moonpe |
|---|---|---|
| kernel32.dll | AR001 at byte 0: expected ar archive magic | PE64, 8 sections, 104 import libraries, 3 delay-loaded, 1697 exports |
| demo.a | 0 118560 "bcheck.o" | bad signature at 0x0: expected the 'MZ' DOS signature |
| Area | Status |
|---|---|
| DOS header (MZ, e_lfanew) and PE\0\0 | ✅ |
| COFF header and every standard optional-header field, PE32 and PE32+ | ✅ |
| All 16 data directories, named and mapped to file offsets | ✅ |
| Section table (name, sizes, addresses, permissions) | ✅ |
| RVA → file offset, rejecting the zero-filled tail | ✅ |
| Imports by name and by ordinal | ✅ |
| Static and delay-loaded IAT slots with library, raw value and matching import | ✅ |
| Delay-load imports, RVA and VA descriptor forms | ✅ |
| Exports, including ordinal-only entries | ✅ |
| Debug directory, CodeView RSDS/NB10, PDB path and age | ✅ |
| Base relocations, counted by fixup type | ✅ |
| Section entropy and a release/triage check with a verdict | ✅ |
| TLS directory and callback count | ✅ |
| CLR header and .NET runtime version | ✅ |
moon add Rz-coder8848/moonpeimport { "Rz-coder8848/moonpe" @pe }let pe = @pe.Pe::parse(bytes)
pe.bits() // 64
pe.machine() // "AMD64"
pe.subsystem() // "windows-console"
pe.optional.entry_point // 0x2c5d0
pe.optional.image_base // 0x180000000
for library in pe.imports() {
println(library.name)
}
for slot in pe.iat() {
println(slot.library + " " + slot.slot_rva.to_string())
}
for library in pe.delay_imports() { // the dependencies a naive reader misses
println("delay: " + library.name)
}
for entry in pe.debug_entries() {
println(entry.kind_name)
}
for resource in pe.resources() {
println(resource.language.to_string())
}
match pe.version_info() {
Some(info) => println(info.file_version)
None => ()
}moon run cmd/moonpe --target js -- info C:/Windows/System32/kernel32.dllfile: C:/Windows/System32/kernel32.dll
format: PE64
machine: AMD64
subsystem: windows-console
image: base 0x180000000
size 0xcb000
entry: 0x2c5d0
sections: 8
imports: 104 libraries
delay: 3 libraries
exports: 1697 symbols
pdb: kernel32.pdb
version: Microsoft® Windows® Operating System 10.0.26100.9444$ moon run cmd/moonpe --target js -- resources C:/Windows/System32/kernel32.dll
TYPE NAME LANG RVA/SIZE
MUI 1 0x409 0xc9458/200
VERSION 1 0x409 0xc90b0/932
$ moon run cmd/moonpe --target js -- relocations C:/Windows/System32/kernel32.dll
PAGE ENTRIES DIR64 HIGHLOW OTHER
0x88000 218 217 0 0
0x89000 222 222 0 0
...
The check deliberately treats those imports as a *capability*, not a verdict:
`kernel32.dll` imports most of the list and still comes out `ok`. It only
escalates when the imports appear together with writable-executable code or a
high-entropy section.
## Design notes
**Bounds-checked reads carry the offset.** `Reader::need` turns every overrun
into `OutOfBounds { offset, needed, available }`. The same applies to a bad
`e_lfanew`, a bogus optional-header magic, or a section table that declares more
entries than the file can hold.
**RVA mapping is not `rva + image_base`.** A section's `VirtualSize` and
`RawSize` differ; the bytes between them exist in memory (zero-filled by the
loader) but not in the file. `rva_to_offset` returns `None` for that tail rather
than reading whatever follows the section on disk.
**Ordinals and delay-load are preserved.** Real DLLs import and export symbols
by ordinal, and many ship a large delay-load table: `shell32.dll` has 79 static
imports and **138 delay-loaded** ones. A reader that only walks slot 1 reports
less than half of that file's dependencies while still claiming success.
**IAT values are exposed as raw bits, not guessed addresses.** An unbound
static IAT may still contain the lookup-table RVA or ordinal bit pattern, while
a bound or delay-loaded image may contain a full virtual address. `Pe::iat`
therefore returns the slot RVA, raw pointer-sized value, matching symbol and a
`delayed` flag instead of assuming every slot is already resolved.
**32-bit fields are read through `Int64` and range-checked.** The same source
must behave identically when compiled for a 32-bit `Int` target and a 64-bit
one; the reader rejects address-like values above 2 GiB instead of letting them
wrap differently on each platform. Pure constants such as the `VS_FIXEDFILEINFO`
signature go through a separate raw read, because they are not addresses.
## Testing
Thirty-five tests, all offline: hand-built PE32 and PE32+ images with every
optional-header field, imports and IAT slots, delay imports (both descriptor
forms), exports, debug records, relocations, a resource tree and `VERSIONINFO`;
plus malformed inputs and a test that truncates a valid image at every possible
length and requires a clean error every time.
```sh
moon test --target wasm-gc| File | Result |
|---|---|
| System32\kernel32.dll | PE64, 8 sections, 104 imports, 1292 IAT slots (18 delay), 1697 exports |
| SysWOW64\kernel32.dll | PE32, 6 sections, 102 imports, 1283 IAT slots (18 delay), 1669 exports |
| System32\shell32.dll | PE64, 79 imports, 138 delay, 843 exports |
| System32\notepad.exe | PE64, 50 imports, 6 delay, 0 exports |
| Reference | Used for | License |
|---|---|---|
| PE format specification | header, section, import, export, resource layouts | Microsoft documentation |
| goblin → pe module | behaviour cross-check | MIT |
| pefile | behaviour cross-check | MIT |
| xmyd0915/moonar | adjacent archive library; relationship documented above | Apache-2.0 |
pub(all) suberror PeError {
OutOfBounds(Int, Int, Int)
BadMagic(String, Int)
Malformed(String, Int)
Unsupported(String)
}pub(all) struct ClrInfo {
runtime_version : String
metadata_size : Int
flags : Int
entry_point_token : Int
} derive(Debug)pub(all) struct CoffHeader {
machine : Int
number_of_sections : Int
timestamp : Int
characteristics : Int
} derive(Debug)pub(all) struct DebugEntry {
kind : Int
kind_name : String
timestamp : Int
size : Int
address : Int
pdb_signature : String?
pdb_age : Int?
pdb_path : String?
} derive(Debug)pub(all) struct DelayImportLibrary {
name : String
entries : Array[ImportEntry]
rva_based : Bool
iat_rva : Int?
} derive(Debug)pub(all) struct IatEntry {
library : String
delayed : Bool
slot_rva : Int
raw_value : Int64
symbol : ImportEntry
} derive(Debug)pub(all) struct LoadConfig {
size : Int
timestamp : Int
global_flags_clear : Int
global_flags_set : Int
critical_section_timeout : Int
process_heap_flags : Int
csd_version : Int
dependent_load_flags : Int
security_cookie : Int64
seh_handler_table : Int64
seh_handler_count : Int64
guard_cf_check : Int64
guard_cf_dispatch : Int64
guard_cf_function_table : Int64
guard_cf_function_count : Int64
guard_flags : Int
code_integrity_flags : Int
code_integrity_catalog : Int
} derive(Debug)pub(all) struct OptionalHeader {
kind : PeKind
major_linker_version : Int
minor_linker_version : Int
size_of_code : Int
size_of_initialized_data : Int
size_of_uninitialized_data : Int
entry_point : Int
base_of_code : Int
base_of_data : Int?
image_base : Int64
section_alignment : Int
file_alignment : Int
major_operating_system_version : Int
minor_operating_system_version : Int
major_image_version : Int
minor_image_version : Int
major_subsystem_version : Int
minor_subsystem_version : Int
win32_version_value : Int
size_of_image : Int
size_of_headers : Int
checksum : Int
subsystem : Int
dll_characteristics : Int
size_of_stack_reserve : Int64
size_of_stack_commit : Int64
size_of_heap_reserve : Int64
size_of_heap_commit : Int64
loader_flags : Int
number_of_rva_and_sizes : Int
directories : Array[DataDirectory]
} derive(Debug)pub(all) struct Pe {
bytes : Bytes
coff : CoffHeader
optional : OptionalHeader
sections : Array[Section]
}pub(all) struct RelocationBlock {
page_rva : Int
entries : Int
absolute : Int
high : Int
low : Int
highlow : Int
dir64 : Int
other : Int
} derive(Debug)pub(all) struct RelocationSummary {
blocks : Array[RelocationBlock]
total_entries : Int
absolute : Int
high : Int
low : Int
highlow : Int
dir64 : Int
other : Int
} derive(Debug)pub(all) struct ResolvedDirectory {
index : Int
name : String
virtual_address : Int
size : Int
file_offset : Int?
} derive(Debug)pub(all) struct ResourceEntry {
type_id : Int?
type_name : String?
name_id : Int?
name_name : String?
language : Int
rva : Int
size : Int
code_page : Int
} derive(Debug)pub(all) struct Section {
name : String
virtual_size : Int
virtual_address : Int
raw_size : Int
raw_pointer : Int
characteristics : Int
} derive(Debug)pub(all) struct VersionInfo {
file_version : String
product_version : String
company_name : String?
product_name : String?
file_description : String?
original_filename : String?
} derive(Debug)fn debug_type_name(kind : Int) -> Stringfn entropy_range(bytes : Bytes, start : Int, end : Int) -> Doublefn has_flag(flags : Int, mask : Int) -> Boolfn hex(value : Int) -> Stringfn hex64(value : Int64) -> Stringfn hex_upper(value : Int) -> Stringfn relocation_type_name(kind : Int) -> Stringfn resource_type_name(id : Int) -> StringInstall
Download zipPure-MoonBit Windows PE32/PE32+ reader: headers, IAT, dependencies, resources, LoadConfig and security metadata, with precise errors instead of panics.