Pure-MoonBit reader for Windows PE/COFF images: headers, section table, imports and exports, with precise byte-offset 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 optional header, 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 | ✅ |
| 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 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.
**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-one tests, all offline: hand-built PE32 and PE32+ images with sections,
imports, 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, 3 delay, 1697 exports |
| SysWOW64\kernel32.dll | PE32, 6 sections, 102 imports, 3 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
} 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
entry_point : Int
image_base : Int64
section_alignment : Int
file_alignment : Int
size_of_image : Int
size_of_headers : Int
subsystem : Int
dll_characteristics : 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 reader for Windows PE/COFF images: headers, section table, imports and exports, with precise byte-offset errors instead of panics.