kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 277327a487828145938a8eeca9946f9f28d76256
parent 745214299916153bb7dcb7c0f446c55d330d7efc
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 14:50:22 -0700

obj: COFF/PE image reader + format-specific raw-fields escape hatch

Open linked PE executables/DLLs through kit_obj_open like ELF/Mach-O.
read_coff dispatches the DOS 'MZ' magic to a new read_coff_image
(coff/read_image.c) that fills the neutral ObjImage -- one segment per PE
section, exports -> dynsyms + soname, imports -> deps + undefined dynsyms,
base relocs -> arch RELATIVE dynrelocs -- plus the full section/symbol
view. detect_pe routes PE images to KIT_OBJ_COFF/Windows (kit_detect_target
previously had no KIT_BIN_PE case). Shared RVA / bounded-string /
Characteristics->SecKind helpers are factored into coff/read_util.{c,h},
used by the .obj, DSO, and image readers.

Add a generic image-level escape hatch (KitObjImageRaw +
kit_obj_image_rawiter_*) for format-specific values outside the neutral
model, with producers for PE (data directories + subsystem + dllchars),
ELF (raw DT_*), and Mach-O (load commands). obj_image_add_dep now
deep-copies the imports[] array into image-heap memory.

objdump: delete the hand-rolled pe_parse_image walker and the KIT_BIN_PE
fallback branch; -p/-f render PE through the neutral image API + the
dependency iterator, and -h/-s/-t/-T/-d/-r/-R work for free.

Tests: non-gated pe-image-read round-trip links a PE in memory (x64 + aa64)
and reads it back, asserting the image view + the raw escape hatch;
committed pe-image.exe objdump golden; gated 04-pe-sections updated to the
neutral -h header.

Closes out doc/plan/IMAGE_INSPECT.md.

Diffstat:
Mdoc/OBJ.md | 37+++++++++++++++++++++++++++----------
Ddoc/plan/IMAGE_INSPECT.md | 174-------------------------------------------------------------------------------
Mdoc/plan/README.md | 1-
Mdriver/cmd/objdump.c | 555++++++++++++++++++-------------------------------------------------------------
Minclude/kit/object.h | 27+++++++++++++++++++++++++++
Mmk/test.mk | 12+++++++++++-
Msrc/api/object_detect.c | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/api/object_file.c | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/obj/coff/read.c | 66+++++++++++++++++++-----------------------------------------------
Msrc/obj/coff/read_dso.c | 66+++++++++---------------------------------------------------------
Asrc/obj/coff/read_image.c | 384+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/obj/coff/read_util.c | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/obj/coff/read_util.h | 36++++++++++++++++++++++++++++++++++++
Msrc/obj/elf/read.c | 10++++++++++
Msrc/obj/macho/read.c | 10++++++++++
Msrc/obj/obj.c | 48+++++++++++++++++++++++++++++++++++++++++++++---
Msrc/obj/obj.h | 36++++++++++++++++++++++++++++++++++--
Atest/coff/pe-image-read.c | 415+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/objdump/x86_64-windows/cases/04-pe-sections.sh | 12++++++------
Atest/objdump/x86_64-windows/cases/pe-image.exe | 0
Atest/objdump/x86_64-windows/cases/pe-image.expected | 48++++++++++++++++++++++++++++++++++++++++++++++++
Atest/objdump/x86_64-windows/cases/pe-image.sh | 14++++++++++++++
22 files changed, 1400 insertions(+), 753 deletions(-)

diff --git a/doc/OBJ.md b/doc/OBJ.md @@ -70,7 +70,7 @@ The five tables and what they model: ### Format pass-through without leaking format knowledge Generic tables stay neutral, but `.o` round-tripping needs to preserve bits the -canonical model doesn't name. Three escape hatches handle this without +canonical model doesn't name. A few targeted escape hatches handle this without polluting the core: - Per-section `ext_type` / `ext_flags` (raw `sh_type`/`sh_flags`) re-emit @@ -78,6 +78,10 @@ polluting the core: (`SHT_LLVM_ADDRSIG`, `SHT_ARM_ATTRIBUTES`, `SHF_EXCLUDE`, ...). - Per-symbol `flags` carry format attribute bits (today Mach-O `n_desc`). - Builder-level fields: ELF `e_flags`, the COFF short-import DLL annotation. + - Per-image raw fields (`ObjImageRaw`, `kit_obj_image_rawiter_*`): a flat + `(tag, value, extra)` list for linked-image values outside the neutral model + — PE data directories / subsystem / dllchars, ELF raw `DT_*`, Mach-O load + commands (see "The linked-image dimension"). - `obj_ext_set`/`obj_ext_get` attach one opaque payload per `ObjExtKind` (today the Wasm module model and Wasm import descriptors); the builder owns the payload's lifetime via a registered free function. @@ -258,13 +262,17 @@ dimension the section/symbol tables can't model — load segments, an entry point, image base, interpreter, soname, dependencies, rpaths, dynamic symbols, and dynamic relocations. The `ObjImage` (defined in `obj.c`, hung off the builder, released by `obj_free`) holds this *common denominator* across formats. -Readers call `obj_image_ensure(ob, OBJ_KIND_EXEC|DYN)` and the appenders; the -section/symbol view stays populated where the format still carries it, so a -non-stripped ELF exec presents both views and a table-stripped image presents -only segments. The public API mirrors the relocatable iterators: -`kit_obj_kind`, image-info scalars, and segment/dep/rpath/dynsym/dynreloc -iterators. `OBJ_KIND_CORE` is reserved — detected and rejected cleanly, not -parsed. +All three native formats fill it: ELF (`ET_EXEC`/`ET_DYN`), Mach-O +(`MH_EXECUTE`/`MH_DYLIB`), and COFF/PE (executables / DLLs). Readers call +`obj_image_ensure(ob, OBJ_KIND_EXEC|DYN)` and the appenders; the section/symbol +view stays populated where the format still carries it, so a non-stripped ELF +exec presents both views and a table-stripped image presents only segments. The +public API mirrors the relocatable iterators: `kit_obj_kind`, image-info +scalars, and segment/dep/rpath/dynsym/dynreloc iterators, plus a raw-fields +iterator (`kit_obj_image_rawiter_*`) — the image-level escape hatch for +format-specific values the neutral model doesn't name (PE data directories / +subsystem / dllchars, ELF raw `DT_*`, Mach-O load commands). `OBJ_KIND_CORE` is +reserved — detected and rejected cleanly, not parsed. ## Per-format notes @@ -311,8 +319,17 @@ mingw/llvm-mingw UCRT, not MSVC. `read.c`/`emit.c` round-trip relocatable PE/COFF: sections with `Characteristics`, symbols with auxiliary records, COMDAT groups and SELECTANY dedup, weak externals + mingw alias fallback, commons, long section names via the string table, and per-arch relocations. -`read_dso.c` walks raw PE DLL export directories (and forwarder ENT entries, -surfaced as defined symbols so the OS loader chases the chain at runtime). +`read_image.c` (`read_coff_image`, dispatched from `read.c` on the DOS `MZ` +magic) is the peer of `read_elf_image`/`read_macho_image`: it parses a linked +`.exe`/`.dll` into the `ObjImage` — one segment per PE section, exports → +dynamic symbols + soname, imports → deps (with imported-name lists) + undefined +dynamic symbols, base relocations → `RELATIVE` dynamic relocs — plus a full +section/symbol view, and the raw data-directory / subsystem / dllchars fields +through the image escape hatch. `read_util.c` holds the RVA→offset, bounded +string, and `Characteristics`→`SecKind` helpers shared by the `.obj`, DSO, and +image readers. `read_dso.c` walks raw PE DLL export directories (and forwarder +ENT entries, surfaced as defined symbols so the OS loader chases the chain at +runtime). `archive.c` implements the registry's archive-ingestion hooks: it classifies import-library members, routing Microsoft short-import records (`Sig1=0, Sig2=0xFFFF`) through `read_coff` to synthesize the imported symbols diff --git a/doc/plan/IMAGE_INSPECT.md b/doc/plan/IMAGE_INSPECT.md @@ -1,174 +0,0 @@ -# Image Inspection (planned work) - -kit can read relocatable objects through `kit_obj_open`, and it has been -extended to also inspect *linked images* -- executables and shared objects -- -across the same neutral API. ELF and Mach-O reading have landed; the -remaining work is the COFF/PE image reader plus a handful of follow-ups. This -doc captures the goal, what is already baseline, and what is left, so the PE -work and any later refinements parallelize against a settled contract. The -matching design lives in [../OBJ.md](../OBJ.md); see also -[LINKER.md](LINKER.md) for the linker side and [../DBG.md](../DBG.md) / -[../DWARF.md](../DWARF.md) for the debug-info flow that rides on it. - -## Goal - -One `kit_obj_open` call inspects any of: a relocatable object, an -executable, or a shared object, for ELF, Mach-O, and COFF/PE. Sections and -symbols keep working where the format still carries them; linked images -additionally expose segments, an entry point and image base, an interpreter / -SONAME, library dependencies and rpaths, and a dynamic symbol/relocation -table. `objdump` and the inherited tools (nm, size, addr2line) operate on -images the same way they operate on objects, with no per-format -special-casing in the driver. - -## Why this is a real extension, not a flag - -The original reader was relocatable-object-shaped. `kit_obj_open` -> -`kit_detect_target` -> `impl->read`, and the ELF backend rejected anything -but `ET_REL`. There were DSO readers (`read_elf_dso`, `read_coff_dso`, a -Mach-O dylib stub) but they were wired only into the *linker's* input path, -not the public `impl->read` / `kit_obj_open` surface; `ET_EXEC` had no -reader at all. The in-memory model (`ObjBuilder`) was section / symbol / -reloc oriented with no notion of a **segment** (PT_LOAD), the **dynamic -table** (DT_NEEDED / SONAME / RPATH), an **entry point**, **image base**, -**imports**, or **data directories** -- which is exactly what image -inspection is about. The fix is a new image dimension on the model plus -neutral iterators, not a flag. - -## Baseline (done) - -The neutral API and internal model are in place, and ELF + Mach-O image -reading work end to end: - -- **Neutral API** (`include/kit/object.h`): `KitObjKind` + - `kit_obj_kind`; `KitObjImageInfo` + `kit_obj_image_info` (entry, image - base, interp, soname); segment iterator (`kit_obj_segiter_*` over - `KitObjSegInfo` with `KIT_SEG_R/W/X`); dependency iterator - (`kit_obj_depiter_*`, carrying imported names for PE/Mach-O); rpath - iterator (`kit_obj_rpathiter_*`); dynamic symbols and relocations reusing - the `KitObjSymIter` / `KitObjRelocIter` shapes via - `kit_obj_dynsymiter_new` / `kit_obj_dynreliter_new`. -- **Internal model** (`src/obj/obj.h`, `src/obj/obj.c`): an `ObjImage` hung - off `ObjBuilder`, NULL on pure relocatables. Readers call - `obj_image_ensure(ob, OBJ_KIND_*)` then setters/appenders for entry, base, - interp, soname, segments, deps, rpaths, dynsyms, and dynrelocs; - `obj_free` releases it. -- **Glue** (`src/api/object_file.c`): maps `ObjImage` to the public - iterators; relocatable inputs report `KIT_OBJ_KIND_REL` with empty image - iterators and the section/symbol path unchanged. -- **ELF reader** (`src/obj/elf/read.c`): `read_elf` accepts `ET_EXEC` / - `ET_DYN`, sharing one path -- the old `e_type != ET_REL` guard is now a kind - switch. `read_elf_image` walks program headers for segments + PT_INTERP + - image base, and parses `.dynamic` for DT_NEEDED / DT_SONAME / DT_RPATH / - DT_RUNPATH plus the dynsym/dynstr/reloc pointers. A zeroed section-header - table is accepted for images (empty section view, segment view carries the - load picture). -- **Mach-O reader** (`src/obj/macho/read.c`): accepts `MH_EXECUTE` / - `MH_DYLIB`; `read_macho_image` re-walks load commands for segments - (`LC_SEGMENT_64`, `__TEXT` base, VM_PROT->OBJ_SEG perms), interp - (`LC_LOAD_DYLINKER`), soname (`LC_ID_DYLIB`), deps (`LC_LOAD_DYLIB` and - weak/reexport variants), rpaths (`LC_RPATH`), entry (`LC_MAIN` / - `LC_UNIXTHREAD`), dynamic symbols from the external `LC_SYMTAB` nlist - entries, and `LC_DYLD_CHAINED_FIXUPS` binds/rebases - (`DYLD_CHAINED_PTR_64`). Classic `LC_DYLD_INFO` and the exports trie are - intentionally not read; non-64-bit chained pointer formats are skipped - leniently. -- **objdump** (`driver/cmd/objdump.c`): grew `-p` / `--private-headers` - (program/dynamic headers, format-neutral via the image API), `-T` / - `--dynamic-syms`, and `-R` / `--dynamic-reloc`; `-f` reports the image type - flags and real entry point; `-h` / `-t` / `-d` work on executables. `-d` - falls back to disassembling X-perm `PT_LOAD` segments by vaddr when the - section walk is empty (stripped images), with no ELF special-casing. -- **Inherited tools**: nm, size, addr2line open images via `kit_obj_open`. - nm grew `-D` (`.dynsym`); `KitObjSecInfo.addr` carries the load vaddr (0 - for relocatables) so SysV `size -A` reports real layout. (strings is - intentionally format-agnostic -- it scans raw bytes and does not call - `kit_obj_open`.) -- **Debug-info retention in the linker**: `.debug_*` sections are carried - through to linked images as file-only sections with relocations resolved in - place, so `addr2line` / `dbg` resolve `file:line` on kit-linked - executables (single- and multi-input, ELF and Mach-O). See - [../LINK.md](../LINK.md) and [../DWARF.md](../DWARF.md). - -## Remaining work - -### COFF/PE image reader (primary gap) - -PE is the one format whose linked images do not yet open through -`kit_obj_open`. The COFF backend's `read` does not populate `ObjImage`, and -`read_coff_dso` is still wired only into the linker. As a result `objdump` -keeps a hand-rolled `pe_parse_image` raw-byte walker -(`driver/cmd/objdump.c:392`) behind a `KIT_BIN_PE` special-case -(`driver/cmd/objdump.c:1831`) that serves `-f` / `-h` / `-p` and soft-errors -`-t` / `-d` / `-r` / `-s`. The plan: - -- Give the COFF backend a real image reader: DOS / NT headers, optional - header (entry point + image base + subsystem), data directories, the - section table, the import and export directories, and the base-relocation - table. Reuse / fold in `read_coff_dso`'s machinery so EXEC and DLL share one - path the way ELF EXEC/DYN do. -- Populate `ObjImage`: segments from sections + image base, deps from the - import directory (each DLL's imported names go into the per-dep imports - list), exports from the export directory into the dynamic symbol table, and - base relocations into the dynamic relocation view. -- Extend objdump `-p` to render the PE optional header + data directories, - `-T` for exports, `-R` for base relocations, all through the neutral image - API. -- **Delete** `pe_parse_image` and collapse the `KIT_BIN_PE` branch in - `driver_objdump` into the normal `dump_obj` path once PE images open via - `kit_obj_open`. - -### Escape hatch for format-specific raw fields - -Some inspection needs format-specific values that do not fit the neutral -model: raw DT_* tag values, raw Mach-O load commands, PE data-directory -entries. Surface these through a per-format escape hatch in the spirit of the -existing `kit_obj_section_format_flags`, keeping the neutral API clean -rather than widening it per format. - -### Mach-O classic-format breadth (deferred) - -The Mach-O reader deliberately supports only the modern fixup path -(`LC_DYLD_CHAINED_FIXUPS`, with the symbol table as the authoritative -dynamic-symbol source) and `LC_DYLD_EXPORTS_TRIE`. Classic `LC_DYLD_INFO` -opcode/trie reading and the exports trie remain out of scope; reading older -dylibs is a separate, lower-priority effort. Revisit only if a real input -demands it. - -## Out of scope - -- **Core files** (`ET_CORE`, Mach-O `MH_CORE`): `KIT_OBJ_KIND_CORE` stays - defined but unimplemented; detect and reject cleanly. Note / register-state - parsing is a separate feature. -- **Synthesizing pseudo-sections from segments** on stripped ELF: matches GNU - `objdump` / `llvm-objdump`, which are section-header-driven and report "no - sections" when the table is absent. The segment view (and `-d` over X-perm - segments) covers the disassembly case. - -## Design notes carried forward - -- **One open call, two views.** `kit_obj_open` detects kind (reusing - `kit_detect_target` + `e_type` / `filetype` / PE characteristics) and - routes to the backend, which fills `ObjBuilder` (sections/symbols where - present) and, for EXEC/DYN, `ObjImage`. Tools that already use - `kit_obj_open` inherit image support for free. -- **Segment is the load-layout unit.** `{ vaddr, vsize, file_off, file_size, - perms, align, name }`, populated from PT_LOAD / LC_SEGMENT_64 / PE sections. - Sections continue to map through the existing `ObjBuilder` view where the - format retains them; the segment view carries the load picture when section - headers are absent. -- **Dynamic syms/relocs reuse object shapes.** The dynamic symbol and - relocation iterators reuse `KitObjSymInfo` / `KitObjReloc` rather than - introducing parallel types, so consumers written for objects work on - images. - -## Test strategy - -The compiler links its own ELF / Mach-O / PE images, so tests round-trip: -link a small program, open it via `kit_obj_open`, and assert -kind/entry/segments/deps/dynsyms against what the linker emitted, cross-checked -against host `readelf` / `objdump` in smoke tests where available. ELF and -Mach-O goldens live under `test/objdump/`; PE corpora land under -`test/{coff,pe}/` with the reader. Dynamic NEEDED/SONAME/dynsym paths fully -exercise once `-shared` / dynamic linking emit populated tables; the -empty-table rendering is already covered. See [../TESTING.md](../TESTING.md). diff --git a/doc/plan/README.md b/doc/plan/README.md @@ -17,7 +17,6 @@ shrinks to whatever remains open. | [ARCH.md](ARCH.md) | Remaining native-backend completeness for x64/rv64 relative to the aa64 reference, and per-call cost follow-ups. | [../ARCH.md](../ARCH.md) | | [BOOTSTRAP.md](BOOTSTRAP.md) | The 3-stage self-build reproducibility goal and the open `-O1` issues blocking it. | [../BUILD.md](../BUILD.md) | | [windows.md](windows.md) | Self-hosting kit on Windows: cross-built `kit.exe` runs `cc` + JIT on aarch64-windows; the open self-host miscompile crash, JIT printf, x64 parity, compile-on-VM lane, and the Windows 3-stage bootstrap. | [../WINDOWS.md](../WINDOWS.md) | -| [IMAGE_INSPECT.md](IMAGE_INSPECT.md) | Extending object inspection to executables and shared libraries. COFF/PE image reader is the main remaining gap. | [../OBJ.md](../OBJ.md) | | [BUILD.md](BUILD.md) | A new content-addressed build coordinator (Bazel/Nix-style incremental builds layered on the CAS) — storage state machine, caching algorithm, recipe protocol. Distinct from `../BUILD.md` (kit's own Makefile build). | — (new subsystem) | | [BUILD_COMMANDS.md](BUILD_COMMANDS.md) | The kit-native `build-exe`/`build-lib`/`build-obj` verbs that replace `compile`: polyglot, in-memory compile+link with `--group` flag scoping and full link-flag control. Distinct from `BUILD.md` (the CAS coordinator). | [../DRIVER.md](../DRIVER.md) | | [LLGEN_IMPORT.md](LLGEN_IMPORT.md) | Importing the standalone LL(1)/Pratt parser and lexer generator into libkit, including public API renames, file moves, build gates, and a `kit llgen` command. | — | diff --git a/driver/cmd/objdump.c b/driver/cmd/objdump.c @@ -129,38 +129,17 @@ void driver_help_objdump(void) { "usage\n"))); } -/* ---- PE/COFF private-header walker (used by `-p`) ---- +/* ---- PE/COFF display helpers ---- * - * The objdump driver currently relies on the high-level KitObjFile - * interface for section/symbol/disasm output. For PE images that hides - * a lot of useful structure: the optional header, data directories, - * and per-DLL import lists. The walker below operates on the raw input - * bytes so we can print this view without piping the data through - * libkit. It does the strict minimum needed for a `-p` style dump - * and bails out on malformed offsets — diagnostic, not security-grade. - * - * RVA-to-file resolution: each section header records VirtualAddress - * (RVA) and PointerToRawData (file offset). A target RVA lands inside - * a section iff RVA in [VA, VA + VirtualSize). The file offset of the - * RVA inside the section's raw bytes is PointerToRawData + (RVA - VA). - * `pe_rva_to_file` returns -1 when no section covers the RVA. */ -#define PE_DOS_E_LFANEW_OFFSET 60u -#define PE_FILE_HEADER_SIZE 20u -#define PE_OPT_HDR64_MAGIC 0x020Bu -#define PE_NUM_DATA_DIRS 16u -#define PE_DATA_DIRECTORY_SIZE 8u -#define PE_SECTION_HEADER_SIZE 40u -#define PE_DIR_EXPORT 0u + * PE executables / DLLs now open through kit_obj_open like every other + * format, so the image data (optional-header scalars, data directories, + * imports) arrive through the neutral image API — kit_obj_image_info, the + * raw-fields iterator (KitObjImageRaw), and the dependency iterator. The + * helpers below only map PE numeric constants to the symbolic names + * objdump prints (data-directory index, subsystem). */ + +/* Data-directory index (IMAGE_DIRECTORY_ENTRY_IMPORT). */ #define PE_DIR_IMPORT 1u -#define PE_DIR_RESOURCE 2u -#define PE_DIR_EXCEPTION 3u -#define PE_DIR_BASERELOC 5u -#define PE_DIR_DEBUG 6u -#define PE_DIR_TLS 9u -#define PE_DIR_IAT 12u -#define PE_IMPORT_DESCRIPTOR_SIZE 20u -#define PE_THUNK_SIZE 8u -#define PE_ORDINAL_FLAG64 0x8000000000000000ull /* COFF-specific Characteristics bits we surface as tags. Kept in sync * with src/obj/coff.h's IMAGE_SCN_* values; objdump only needs the @@ -174,16 +153,6 @@ void driver_help_objdump(void) { static int j_match(const ObjdumpOpts* o, KitSlice name); -static uint16_t pe_rd_u16(const uint8_t* p) { - return (uint16_t)(p[0] | ((uint32_t)p[1] << 8)); -} -static uint32_t pe_rd_u32(const uint8_t* p) { - return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | - ((uint32_t)p[3] << 24); -} -static uint64_t pe_rd_u64(const uint8_t* p) { - return (uint64_t)pe_rd_u32(p) | ((uint64_t)pe_rd_u32(p + 4) << 32); -} /* Names match the IMAGE_DIRECTORY_ENTRY_* index. Keep aligned with the * order in coff.h to avoid drift. */ @@ -257,351 +226,6 @@ static const char* pe_subsystem_name(uint16_t s) { } } -/* Find file offset for an RVA by scanning the section headers. Returns - * -1 if the RVA is outside every section's covered range. */ -static long pe_rva_to_file(const uint8_t* buf, size_t buf_len, size_t sec_off, - uint16_t nsec, uint32_t rva) { - uint16_t i; - for (i = 0; i < nsec; ++i) { - size_t sh = sec_off + (size_t)i * PE_SECTION_HEADER_SIZE; - uint32_t va; - uint32_t vsize; - uint32_t raw_off; - uint32_t raw_size; - if (sh + PE_SECTION_HEADER_SIZE > buf_len) return -1; - vsize = pe_rd_u32(buf + sh + 8); - va = pe_rd_u32(buf + sh + 12); - raw_size = pe_rd_u32(buf + sh + 16); - raw_off = pe_rd_u32(buf + sh + 20); - /* VirtualSize is sometimes 0 in object files; fall back to raw size. */ - if (vsize == 0) vsize = raw_size; - if (rva >= va && rva < va + vsize) { - uint32_t delta = rva - va; - if (delta >= raw_size) return -1; - return (long)(raw_off + delta); - } - } - return -1; -} - -/* Read a NUL-terminated ASCII string starting at `off`, capped to - * 256 bytes. Writes a copy into `dst` (size `dstcap`) and returns - * 0 on success, 1 if the offset is out of bounds. */ -static int pe_read_cstr(const uint8_t* buf, size_t buf_len, size_t off, - char* dst, size_t dstcap) { - size_t i; - if (off >= buf_len) { - if (dstcap) dst[0] = '\0'; - return 1; - } - for (i = 0; i + 1 < dstcap && off + i < buf_len && buf[off + i]; ++i) { - dst[i] = (char)buf[off + i]; - } - dst[i] = '\0'; - return 0; -} - -static void pe_dump_imports(const uint8_t* buf, size_t buf_len, size_t sec_off, - uint16_t nsec, uint32_t import_rva, - uint32_t import_size) { - long desc_off; - uint32_t consumed; - (void)import_size; - desc_off = pe_rva_to_file(buf, buf_len, sec_off, nsec, import_rva); - if (desc_off < 0) { - driver_printf(" (import directory RVA not covered by any section)\n"); - return; - } - driver_printf("\nThe Import Tables:\n"); - for (consumed = 0;; consumed += PE_IMPORT_DESCRIPTOR_SIZE) { - size_t off = (size_t)desc_off + consumed; - uint32_t ilt_rva; - uint32_t name_rva; - uint32_t iat_rva; - char dll[256]; - long thunk_off; - uint32_t i; - if (off + PE_IMPORT_DESCRIPTOR_SIZE > buf_len) break; - ilt_rva = pe_rd_u32(buf + off + 0); - name_rva = pe_rd_u32(buf + off + 12); - iat_rva = pe_rd_u32(buf + off + 16); - /* All-zero descriptor terminates the chain. */ - if (ilt_rva == 0 && name_rva == 0 && iat_rva == 0) break; - { - long name_off = pe_rva_to_file(buf, buf_len, sec_off, nsec, name_rva); - if (name_off < 0 || - pe_read_cstr(buf, buf_len, (size_t)name_off, dll, sizeof dll) != 0) { - dll[0] = '\0'; - } - } - driver_printf(" DLL Name: %.*s\n", - KIT_SLICE_ARG(kit_slice_cstr(dll[0] ? dll : "(unreadable)"))); - driver_printf(" ILT RVA: 0x%x IAT RVA: 0x%x\n", ilt_rva, iat_rva); - /* Prefer walking the original first thunk (ILT) for names. Some - * mingw-emitted images zero the ILT and only ship the IAT; fall - * back to the IAT in that case. */ - { - uint32_t walk_rva = ilt_rva ? ilt_rva : iat_rva; - thunk_off = - walk_rva ? pe_rva_to_file(buf, buf_len, sec_off, nsec, walk_rva) : -1; - } - if (thunk_off < 0) continue; - for (i = 0;; ++i) { - size_t toff = (size_t)thunk_off + (size_t)i * PE_THUNK_SIZE; - uint64_t t; - if (toff + PE_THUNK_SIZE > buf_len) break; - t = pe_rd_u64(buf + toff); - if (t == 0) break; - if (t & PE_ORDINAL_FLAG64) { - driver_printf(" Ordinal: %u\n", (unsigned)(t & 0xFFFFu)); - } else { - long hint_off = - pe_rva_to_file(buf, buf_len, sec_off, nsec, (uint32_t)t); - char name[256]; - if (hint_off < 0 || pe_read_cstr(buf, buf_len, (size_t)hint_off + 2u, - name, sizeof name) != 0) { - continue; - } - driver_printf(" Name: %.*s\n", - KIT_SLICE_ARG(kit_slice_cstr(name))); - } - } - } - driver_printf("\n"); -} - -/* Parsed view of a PE32+ image's headers. Populated by pe_parse_image; - * callers check `valid` before reading other fields. Avoids the prior - * pattern of every PE-walker re-validating the DOS/PE/optional header - * triplet from scratch. */ -typedef struct PeImage { - int valid; - uint16_t machine; - uint16_t file_chars; - uint16_t nsec; - uint16_t opt_magic; - uint16_t subsystem; - uint16_t dllchars; - uint64_t image_base; - uint32_t entry_rva; - size_t sec_off; - size_t dir_off; -} PeImage; - -static int pe_parse_image(const uint8_t* buf, size_t buf_len, PeImage* out) { - uint32_t e_lfanew; - size_t coff_off; - size_t opt_off; - uint16_t opt_size; - - out->valid = 0; - if (buf_len < PE_DOS_E_LFANEW_OFFSET + 4u) return 0; - if (pe_rd_u16(buf) != 0x5A4Du) return 0; - e_lfanew = pe_rd_u32(buf + PE_DOS_E_LFANEW_OFFSET); - if ((size_t)e_lfanew + 4u + PE_FILE_HEADER_SIZE > buf_len) return 0; - if (pe_rd_u32(buf + e_lfanew) != 0x00004550u) return 0; - coff_off = (size_t)e_lfanew + 4u; - out->machine = pe_rd_u16(buf + coff_off + 0); - out->nsec = pe_rd_u16(buf + coff_off + 2); - out->file_chars = pe_rd_u16(buf + coff_off + 18); - opt_size = pe_rd_u16(buf + coff_off + 16); - opt_off = coff_off + PE_FILE_HEADER_SIZE; - if (opt_size == 0 || opt_off + opt_size > buf_len) return 0; - out->opt_magic = pe_rd_u16(buf + opt_off); - if (out->opt_magic != PE_OPT_HDR64_MAGIC) { - /* PE32 (0x10B) is out of scope but the caller may still want to - * report what it found, so we return a "valid header, unsupported - * subset" view rather than failing. */ - out->valid = 1; - return 1; - } - out->entry_rva = pe_rd_u32(buf + opt_off + 16); - out->image_base = pe_rd_u64(buf + opt_off + 24); - out->subsystem = pe_rd_u16(buf + opt_off + 68); - out->dllchars = pe_rd_u16(buf + opt_off + 70); - out->sec_off = opt_off + opt_size; - out->dir_off = opt_off + 112u; - out->valid = 1; - return 1; -} - -static const char* pe_machine_name(uint16_t m) { - switch (m) { - case 0x8664u: - return "x86_64 (AMD64)"; - case 0xAA64u: - return "aarch64 (ARM64)"; - case 0xA641u: - return "aarch64 (ARM64EC)"; - case 0x014Cu: - return "i386"; - case 0x01C0u: - return "arm"; - case 0x01C4u: - return "armnt"; - case 0x0200u: - return "ia64"; - case 0x5064u: - return "riscv64"; - default: - return "unknown"; - } -} - -/* PE-image `-f`: architecture, image base, entry point, subsystem. - * Counterpart to dump_file_header for inputs that kit_obj_open can't - * parse yet (PE executables / DLLs vs .obj). */ -static void dump_pe_file_header(const char* label, const PeImage* pe) { - driver_printf("%.*s:\tfile format pei-%.*s\n\n", - KIT_SLICE_ARG(kit_slice_cstr(label)), - KIT_SLICE_ARG(kit_slice_cstr(pe_machine_name(pe->machine)))); - driver_printf("architecture: %.*s, flags 0x%04x\n", - KIT_SLICE_ARG(kit_slice_cstr(pe_machine_name(pe->machine))), - (unsigned)pe->file_chars); - if (pe->opt_magic == PE_OPT_HDR64_MAGIC) { - driver_printf("start address 0x%016llx\n", - (unsigned long long)(pe->image_base + pe->entry_rva)); - driver_printf( - "image base: 0x%llx, entry rva: 0x%x, subsystem: %u (%.*s)\n\n", - (unsigned long long)pe->image_base, pe->entry_rva, - (unsigned)pe->subsystem, - KIT_SLICE_ARG(kit_slice_cstr(pe_subsystem_name(pe->subsystem)))); - } else { - driver_printf( - "PE32 (magic 0x%x) — only PE32+ inspection is implemented\n\n", - (unsigned)pe->opt_magic); - } -} - -/* Decode IMAGE_SECTION_HEADER.Characteristics into the GNU objdump tag - * style used for COFF .obj inputs. Used by both dump_sections (via - * render_sec_flags) and the PE section walker. */ -static void render_pe_sec_flags(uint32_t ch, char* buf, size_t cap) { - size_t n = 0; - const char* tags[16]; - int nt = 0; - int i; - /* Bit layout shared with render_sec_flags; PE images don't carry - * BSS / TLS-by-name detection so we go straight from raw flags to - * tags. */ - if (ch & 0x00000020u) tags[nt++] = "CODE"; - if (ch & 0x00000040u) tags[nt++] = "DATA"; - if (ch & 0x00000080u) tags[nt++] = "BSS"; - if (ch & OBJDUMP_IMAGE_SCN_LNK_INFO) tags[nt++] = "LINK_INFO"; - if (ch & OBJDUMP_IMAGE_SCN_LNK_REMOVE) tags[nt++] = "LINK_REMOVE"; - if (ch & OBJDUMP_IMAGE_SCN_LNK_COMDAT) tags[nt++] = "LINK_ONCE"; - if (ch & OBJDUMP_IMAGE_SCN_GPREL) tags[nt++] = "GPREL"; - if (ch & OBJDUMP_IMAGE_SCN_MEM_DISCARDABLE) tags[nt++] = "DISCARDABLE"; - if (ch & OBJDUMP_IMAGE_SCN_MEM_SHARED) tags[nt++] = "SHARED"; - if (ch & 0x20000000u) tags[nt++] = "EXEC"; - if (ch & 0x40000000u) tags[nt++] = "READ"; - if (ch & 0x80000000u) tags[nt++] = "WRITE"; - for (i = 0; i < nt && n + 1 < cap; ++i) { - const char* t = tags[i]; - if (i > 0 && n + 1 < cap) buf[n++] = ','; - while (*t && n + 1 < cap) buf[n++] = *t++; - } - buf[n] = '\0'; -} - -/* PE-image `-h`: walks IMAGE_SECTION_HEADER table directly from raw - * bytes (kit_obj_open doesn't yet parse PE executables). Output - * shape mirrors dump_sections for .obj inputs so postprocessing - * grep-fu doesn't care which path produced the line. */ -static void dump_pe_sections(const char* label, const PeImage* pe, - const uint8_t* buf, size_t buf_len, - const ObjdumpOpts* opts) { - uint16_t i; - char flagbuf[160]; - char name[9]; - driver_printf("%.*s:\tSections (PE image):\n", - KIT_SLICE_ARG(kit_slice_cstr(label))); - driver_printf( - "Idx Name VMA Size " - "FileOff Align Flags\n"); - for (i = 0; i < pe->nsec; ++i) { - size_t sh = pe->sec_off + (size_t)i * PE_SECTION_HEADER_SIZE; - uint32_t vsize; - uint32_t va; - uint32_t raw_size; - uint32_t raw_off; - uint32_t ch; - uint32_t align_field; - unsigned align_log2; - int j; - if (sh + PE_SECTION_HEADER_SIZE > buf_len) break; - for (j = 0; j < 8; ++j) name[j] = (char)buf[sh + (size_t)j]; - name[8] = '\0'; - vsize = pe_rd_u32(buf + sh + 8); - va = pe_rd_u32(buf + sh + 12); - raw_size = pe_rd_u32(buf + sh + 16); - raw_off = pe_rd_u32(buf + sh + 20); - ch = pe_rd_u32(buf + sh + 36); - align_field = (ch >> 20) & 0xFu; - align_log2 = align_field ? (align_field - 1u) : 0u; - if (!j_match(opts, kit_slice_cstr(name))) continue; - render_pe_sec_flags(ch, flagbuf, sizeof(flagbuf)); - driver_printf("%3u %-16s %016llx %08x %08x 2**%-2u %.*s\n", (unsigned)i, - name, (unsigned long long)(pe->image_base + va), - vsize ? vsize : raw_size, raw_off, align_log2, - KIT_SLICE_ARG(kit_slice_cstr(flagbuf))); - driver_printf( - " " - "Characteristics: 0x%08x\n", - ch); - } - driver_printf("\n"); -} - -/* Walk a PE image (DOS → "PE\0\0" → COFF file header → optional header - * → data directories) and print the highlights GNU objdump's `-p` - * shows. Bails silently on any header that doesn't validate — leaves - * the basic dump_obj() output untouched. */ -static void dump_pe_private(const char* label, const uint8_t* buf, - size_t buf_len) { - PeImage pe; - uint32_t import_rva = 0; - uint32_t import_size = 0; - uint32_t i; - if (!pe_parse_image(buf, buf_len, &pe) || !pe.valid) return; - if (pe.opt_magic != PE_OPT_HDR64_MAGIC) { - driver_printf("%.*s:\tPE optional header magic 0x%x (PE32) — skipping\n", - KIT_SLICE_ARG(kit_slice_cstr(label)), (unsigned)pe.opt_magic); - return; - } - - driver_printf("\n%.*s:\tPE32+ private headers\n", - KIT_SLICE_ARG(kit_slice_cstr(label))); - driver_printf(" Magic: 0x%x (PE32+)\n", pe.opt_magic); - driver_printf(" Machine: 0x%04x (%.*s)\n", (unsigned)pe.machine, - KIT_SLICE_ARG(kit_slice_cstr(pe_machine_name(pe.machine)))); - driver_printf(" Characteristics: 0x%04x\n", (unsigned)pe.file_chars); - driver_printf(" ImageBase: 0x%llx\n", - (unsigned long long)pe.image_base); - driver_printf(" AddressOfEntryPoint: 0x%x\n", pe.entry_rva); - driver_printf(" Subsystem: %u (%.*s)\n", (unsigned)pe.subsystem, - KIT_SLICE_ARG(kit_slice_cstr(pe_subsystem_name(pe.subsystem)))); - driver_printf(" DllCharacteristics: 0x%04x\n", (unsigned)pe.dllchars); - driver_printf(" NumberOfSections: %u\n", (unsigned)pe.nsec); - - if (pe.dir_off + PE_NUM_DATA_DIRS * PE_DATA_DIRECTORY_SIZE > buf_len) return; - driver_printf("\nData Directories:\n"); - driver_printf(" Idx Name RVA Size\n"); - for (i = 0; i < PE_NUM_DATA_DIRS; ++i) { - uint32_t rva = pe_rd_u32(buf + pe.dir_off + i * PE_DATA_DIRECTORY_SIZE); - uint32_t sz = pe_rd_u32(buf + pe.dir_off + i * PE_DATA_DIRECTORY_SIZE + 4); - if (rva == 0 && sz == 0) continue; - driver_printf(" %2u %-14s 0x%08x 0x%08x\n", i, pe_dir_name(i), rva, sz); - if (i == PE_DIR_IMPORT) { - import_rva = rva; - import_size = sz; - } - } - - if (import_rva && import_size) { - pe_dump_imports(buf, buf_len, pe.sec_off, pe.nsec, import_rva, import_size); - } -} /* Render objdump's "file format" spelling: the obj layer's canonical bare * name (elf/coff/macho/wasm) plus the bitwidth suffix this tool presents. @@ -638,6 +262,91 @@ static const char* arch_str(KitArchKind arch) { return "unknown"; } +/* Collected PE optional-header escape-hatch view, gathered from the neutral + * raw-fields iterator (KitObjImageRaw): the 16 data directories plus the + * Subsystem / DllCharacteristics scalars. Returns 0 if the image carries no + * raw fields (i.e. not a PE image). */ +typedef struct PeRaw { + uint16_t subsystem; + uint16_t dllchars; + uint32_t dir_rva[16]; + uint32_t dir_size[16]; +} PeRaw; + +static int pe_collect_raw(KitObjFile* f, PeRaw* out) { + KitObjImageRawIter* it = NULL; + KitObjImageRaw r; + memset(out, 0, sizeof *out); + if (kit_obj_image_rawiter_new(f, &it) != KIT_OK) return 0; + while (kit_obj_image_rawiter_next(it, &r) == KIT_ITER_ITEM) { + if (r.tag < 16) { + out->dir_rva[r.tag] = (uint32_t)r.value; + out->dir_size[r.tag] = (uint32_t)r.extra; + } else if (r.tag == KIT_OBJ_RAW_PE_SUBSYSTEM) { + out->subsystem = (uint16_t)r.value; + } else if (r.tag == KIT_OBJ_RAW_PE_DLLCHARS) { + out->dllchars = (uint16_t)r.value; + } + } + kit_obj_image_rawiter_free(it); + return 1; +} + +/* PE-image `-p`: the GNU objdump "PE32+ private headers" view — optional + * header highlights, data directories, and the import tables — rendered + * entirely from the neutral image API (kit_obj_image_info + the raw-fields + * iterator + the dependency iterator). */ +static void dump_pe_private(KitObjFile* f, const char* label) { + PeRaw raw; + KitObjImageInfo info; + KitTargetSpec target = kit_obj_target(f); + KitObjDepIter* dit = NULL; + KitObjDepInfo dep; + uint32_t i; + int have_imports = 0; + if (!pe_collect_raw(f, &raw)) return; + if (kit_obj_image_info(f, &info) != KIT_OK) return; + + driver_printf("\n%.*s:\tPE32+ private headers\n", + KIT_SLICE_ARG(kit_slice_cstr(label))); + driver_printf(" Magic: 0x20b (PE32+)\n"); + driver_printf(" Machine: %.*s\n", + KIT_SLICE_ARG(kit_slice_cstr(arch_str(target.arch)))); + driver_printf(" ImageBase: 0x%llx\n", + (unsigned long long)info.image_base); + driver_printf( + " AddressOfEntryPoint: 0x%llx\n", + (unsigned long long)(info.entry > info.image_base + ? info.entry - info.image_base + : 0)); + driver_printf(" Subsystem: %u (%.*s)\n", (unsigned)raw.subsystem, + KIT_SLICE_ARG(kit_slice_cstr(pe_subsystem_name(raw.subsystem)))); + driver_printf(" DllCharacteristics: 0x%04x\n", (unsigned)raw.dllchars); + + driver_printf("\nData Directories:\n"); + driver_printf(" Idx Name RVA Size\n"); + for (i = 0; i < 16; ++i) { + if (raw.dir_rva[i] == 0 && raw.dir_size[i] == 0) continue; + driver_printf(" %2u %-14s 0x%08x 0x%08x\n", i, pe_dir_name(i), + raw.dir_rva[i], raw.dir_size[i]); + } + + if (kit_obj_depiter_new(f, &dit) == KIT_OK) { + while (kit_obj_depiter_next(dit, &dep) == KIT_ITER_ITEM) { + uint32_t k; + if (!have_imports) { + driver_printf("\nThe Import Tables:\n"); + have_imports = 1; + } + driver_printf(" DLL Name: %.*s\n", KIT_SLICE_ARG(dep.name)); + for (k = 0; k < dep.nimports; ++k) + driver_printf(" Name: %.*s\n", KIT_SLICE_ARG(dep.imports[k])); + } + kit_obj_depiter_free(dit); + } + driver_printf("\n"); +} + static char sym_bind_char(KitSymBind b) { switch (b) { case KIT_SB_LOCAL: @@ -1066,10 +775,11 @@ static void dump_disasm(const KitDisasmContext* dctx, KitObjFile* f, } /* `-f`: GNU objdump-style file header summary. Object files have no - * meaningful entry point so start address is always 0; PE images are - * handled separately by dump_pe_private. The flags line summarizes - * whether the input has symbols and relocations so it's clear at a - * glance whether further -t / -r work is going to be productive. */ + * meaningful entry point so start address is always 0. For a PE image we + * also surface the Windows subsystem (via the raw-fields escape hatch). The + * flags line summarizes whether the input has symbols and relocations so + * it's clear at a glance whether further -t / -r work is going to be + * productive. */ static void dump_file_header(KitObjFile* f, const char* label) { KitTargetSpec target = kit_obj_target(f); KitObjFmt fmt = kit_obj_fmt(f); @@ -1128,6 +838,13 @@ static void dump_file_header(KitObjFile* f, const char* label) { fmt_str(fmt, target.ptr_size, fmt_buf, sizeof fmt_buf))), nsec, nsym); } + if (fmt == KIT_OBJ_COFF && kind != KIT_OBJ_KIND_REL) { + PeRaw raw; + if (pe_collect_raw(f, &raw)) + driver_printf( + "subsystem: %u (%.*s)\n\n", (unsigned)raw.subsystem, + KIT_SLICE_ARG(kit_slice_cstr(pe_subsystem_name(raw.subsystem)))); + } (void)label; } @@ -1524,11 +1241,12 @@ static unsigned u32_log2(uint32_t v) { return n; } -/* Private/program headers (-p): the linked-image view — entry point, load - * segments, and dynamic dependencies. Format-neutral across ELF / Mach-O / - * (eventually) PE via the kit_obj image API. Relocatable objects have no - * image and report so. */ -static void dump_private(KitObjFile* f) { +/* Private/program headers (-p): the linked-image view. PE images get the + * GNU objdump "PE32+ private headers" rendering (optional header + data + * directories + import tables); ELF / Mach-O get the entry/segments/dynamic + * view. Both are driven by the neutral kit_obj image API. Relocatable + * objects have no image and report so. */ +static void dump_private(KitObjFile* f, const char* label) { KitObjImageInfo info; KitObjSegIter* sit = NULL; KitObjDepIter* dit = NULL; @@ -1536,6 +1254,11 @@ static void dump_private(KitObjFile* f) { KitObjDepInfo dep; int have_info; + if (kit_obj_fmt(f) == KIT_OBJ_COFF && kit_obj_kind(f) != KIT_OBJ_KIND_REL) { + dump_pe_private(f, label); + return; + } + if (kit_obj_kind(f) == KIT_OBJ_KIND_REL) { driver_printf( "Private headers:\n" @@ -1615,7 +1338,7 @@ static void dump_obj(const KitContext* ctx, const KitDisasmContext* dctx, if (opts->h) dump_groups(f, opts); if (opts->t) dump_symbols(f, opts, 0); if (opts->T) dump_symbols(f, opts, 1); - if (opts->p) dump_private(f); + if (opts->p) dump_private(f, label); if (opts->s) dump_hex(f, opts); if (opts->d || opts->D) dump_disasm(dctx, f, opts, image); if (opts->r) dump_relocs(f, opts); @@ -1907,45 +1630,15 @@ int driver_objdump(int argc, char** argv) { case KIT_BIN_MACHO: case KIT_BIN_WASM: { KitObjFile* f = NULL; - /* PE executables aren't yet readable via kit_obj_open (the - * obj reader is .obj-shaped only). For PE inputs we serve -f / - * -h / -p by walking the raw image bytes; -t / -d / -r / -s - * still need an ObjFile and are skipped with a soft error so - * the other ops don't get swallowed. */ + /* PE executables / DLLs open through kit_obj_open like every other + * format (read_coff dispatches the DOS 'MZ' magic to the image + * reader); the whole dump flows through the neutral dump_obj path. */ if (kit_obj_open(&ctx, kit_slice_cstr(a), &input, &f) != KIT_OK) { - if (bin == KIT_BIN_PE) { - PeImage pe; - int parsed = pe_parse_image(input.data, input.len, &pe) && pe.valid; - int handled = 0; - if (parsed && opts.f) { - dump_pe_file_header(a, &pe); - handled = 1; - } - if (parsed && opts.h && pe.opt_magic == PE_OPT_HDR64_MAGIC) { - dump_pe_sections(a, &pe, input.data, input.len, &opts); - handled = 1; - } - if (opts.p) { - dump_pe_private(a, input.data, input.len); - handled = 1; - } - if (!handled) { - driver_errf(OBJDUMP_TOOL, - "%.*s: PE images support only -f / -h / -p; " - "use -p for image details", - KIT_SLICE_ARG(kit_slice_cstr(a))); - rc = 1; - } - } else { - driver_errf(OBJDUMP_TOOL, "failed to parse: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(a))); - rc = 1; - } + driver_errf(OBJDUMP_TOOL, "failed to parse: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(a))); + rc = 1; } else { dump_obj(&ctx, dctx_p, a, f, &opts, &input); - if (opts.p && bin == KIT_BIN_PE) { - dump_pe_private(a, input.data, input.len); - } kit_obj_free(f); } break; diff --git a/include/kit/object.h b/include/kit/object.h @@ -404,6 +404,33 @@ KIT_API KitStatus kit_obj_dynsymiter_new(KitObjFile*, KitObjSymIter** out); * kit_obj_reliter_next / _free. Empty on relocatable objects. */ KIT_API KitStatus kit_obj_dynreliter_new(KitObjFile*, KitObjRelocIter** out); +/* Raw, format-specific image fields that don't fit the neutral model. One + * neutral signature with per-format tag semantics, in the spirit of + * kit_obj_section_format_flags: + * PE : data directories tag = 0..15 (index), value = RVA, extra = size + * subsystem tag = KIT_OBJ_RAW_PE_SUBSYSTEM, value = u16 + * dllcharacteristics tag = KIT_OBJ_RAW_PE_DLLCHARS, value = u16 + * ELF : .dynamic entries tag = d_tag, value = d_val, extra = 0 + * Mach-O: load commands tag = cmd, value = file offset, extra = cmdsize + * Reserved tags use the high range (>= 0x80000000) so they never collide + * with a PE directory index or an ELF DT_* tag. _new returns KIT_NOT_FOUND on + * a relocatable object (no image); an image with no raw fields yields + * KIT_ITER_END immediately. */ +typedef struct KitObjImageRaw { + uint32_t tag; + uint64_t value; + uint64_t extra; +} KitObjImageRaw; + +#define KIT_OBJ_RAW_PE_SUBSYSTEM 0x80000000u +#define KIT_OBJ_RAW_PE_DLLCHARS 0x80000001u + +typedef struct KitObjImageRawIter KitObjImageRawIter; +KIT_API KitStatus kit_obj_image_rawiter_new(KitObjFile*, KitObjImageRawIter** out); +KIT_API KitIterResult kit_obj_image_rawiter_next(KitObjImageRawIter*, + KitObjImageRaw* out); +KIT_API void kit_obj_image_rawiter_free(KitObjImageRawIter*); + /* Roundtrip: open an object via kit_obj_open, then hand its underlying * builder back. The builder is the same one the reader populated; it is * already finalized, so callers may inspect it (e.g. iterate sections via diff --git a/mk/test.mk b/mk/test.mk @@ -659,6 +659,7 @@ COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive +COFF_IMAGE_READ_BIN = build/test/pe-image-read LLVM_MINGW_SYSROOT_X64_MARKER = build/llvm-mingw/20260602/ucrt/x86_64-w64-mingw32/PROVENANCE LLVM_MINGW_SYSROOT_AARCH64_MARKER = build/llvm-mingw/20260602/ucrt/aarch64-w64-mingw32/PROVENANCE JIT_RUNNER = build/test/jit-runner @@ -715,6 +716,14 @@ $(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_OBJS) @mkdir -p $(dir $@) $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_OBJS) -o $@ +# PE32+ linked-image reader round-trip (test/coff/pe-image-read.c). Links a +# tiny PIE .exe in memory (import + base reloc), re-opens it via kit_obj_open, +# and asserts the neutral image view + raw escape hatch. Needs no external +# toolchain — runs on every host. +$(COFF_IMAGE_READ_BIN): test/coff/pe-image-read.c $(LIB_OBJS) + @mkdir -p $(dir $@) + $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-image-read.c $(LIB_OBJS) -o $@ + $(LLVM_MINGW_SYSROOT_X64_MARKER): scripts/llvm_mingw_sysroot.sh @bash scripts/llvm_mingw_sysroot.sh prepare x64 @@ -750,11 +759,12 @@ test-elf: lib bin-soft $(ROUNDTRIP_BIN) # PE/COFF round-trip harness plus optional hosted Windows smoke. The # UCRT smoke self-skips when llvm-mingw is not installed. -test-coff: lib bin rt-aarch64-windows $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN) +test-coff: lib bin rt-aarch64-windows $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN) $(COFF_IMAGE_READ_BIN) $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN) + $(COFF_IMAGE_READ_BIN) bash test/coff/windows-ucrt-hosted-smoke.sh bash test/coff/windows-system-dlls-smoke.sh diff --git a/src/api/object_detect.c b/src/api/object_detect.c @@ -178,34 +178,67 @@ static KitStatus detect_elf(const u8* d, size_t len, KitTargetSpec* out) { return KIT_OK; } -static KitStatus detect_coff(const u8* d, size_t len, KitTargetSpec* out) { - u16 machine; - KitArchKind arch; - const ObjFormatImpl* fmt; - const ObjCoffArchOps* ops; - if (len < 2) return KIT_MALFORMED; - machine = (u16)d[0] | ((u16)d[1] << 8); - - /* Resolve the arch through the COFF format's machine reverse map (which - * also aliases ARM64EC -> ARM64). The registry models only the - * link/codegen arches (AMD64 / ARM64); the legacy ABI-classifiable COFF - * machines it does not carry are mapped explicitly to preserve detection - * for objects classified as COFF by kit_detect_fmt. */ - fmt = obj_format_lookup(KIT_OBJ_COFF); - ops = (fmt && fmt->coff_machine) ? fmt->coff_machine(machine) : NULL; +/* Resolve a COFF Machine number to a KitArchKind through the registry's + * coff_machine reverse map (which aliases ARM64EC -> ARM64). The registry + * models only the link/codegen arches (AMD64 / ARM64); the legacy + * ABI-classifiable machines it does not carry are mapped explicitly to + * preserve detection. Returns 1 and writes *out on success; 0 if the + * machine is unsupported. Shared by detect_coff (.obj) and detect_pe + * (linked image). */ +static int coff_machine_to_arch(u16 machine, KitArchKind* out) { + const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF); + const ObjCoffArchOps* ops = + (fmt && fmt->coff_machine) ? fmt->coff_machine(machine) : NULL; if (ops) { - arch = ops->arch; + *out = ops->arch; } else if (machine == 0x014Cu) { /* IMAGE_FILE_MACHINE_I386 */ - arch = KIT_ARCH_X86_32; + *out = KIT_ARCH_X86_32; } else if (machine == 0x01C4u) { /* IMAGE_FILE_MACHINE_ARMNT */ - arch = KIT_ARCH_ARM_32; + *out = KIT_ARCH_ARM_32; } else if (machine == 0x5032u) { /* IMAGE_FILE_MACHINE_RISCV32 */ - arch = KIT_ARCH_RV32; + *out = KIT_ARCH_RV32; } else if (machine == 0x5064u) { /* IMAGE_FILE_MACHINE_RISCV64 */ - arch = KIT_ARCH_RV64; + *out = KIT_ARCH_RV64; } else { - return KIT_UNSUPPORTED; + return 0; } + return 1; +} + +static KitStatus detect_coff(const u8* d, size_t len, KitTargetSpec* out) { + u16 machine; + KitArchKind arch; + if (len < 2) return KIT_MALFORMED; + machine = (u16)d[0] | ((u16)d[1] << 8); + if (!coff_machine_to_arch(machine, &arch)) return KIT_UNSUPPORTED; + detect_target_defaults(out); + out->obj = KIT_OBJ_COFF; + out->os = KIT_OS_WINDOWS; + detect_set_ptr(out, arch); + return KIT_OK; +} + +/* PE image (DOS 'MZ' stub + "PE\0\0" signature). Unlike a bare .obj, the + * COFF Machine word lives in the file header at e_lfanew+4, not at offset 0 + * (the DOS stub), so this can't reuse detect_coff's offset-0 read. Routes a + * well-formed image to KIT_OBJ_COFF / KIT_OS_WINDOWS; read_coff then + * dispatches the 'MZ' magic to read_coff_image. A 'MZ' prefix with no valid + * PE signature (a DOS-only stub) is rejected as malformed. */ +static KitStatus detect_pe(const u8* d, size_t len, KitTargetSpec* out) { + u32 e_lfanew, pe_sig; + u16 machine; + KitArchKind arch; + if (len < 64) return KIT_MALFORMED; /* DOS header */ + if (!(d[0] == 'M' && d[1] == 'Z')) return KIT_MALFORMED; + e_lfanew = (u32)d[60] | ((u32)d[61] << 8) | ((u32)d[62] << 16) | + ((u32)d[63] << 24); + /* Need the 4-byte PE signature + the 20-byte IMAGE_FILE_HEADER. */ + if ((u64)e_lfanew + 4u + 20u > (u64)len) return KIT_MALFORMED; + pe_sig = (u32)d[e_lfanew] | ((u32)d[e_lfanew + 1] << 8) | + ((u32)d[e_lfanew + 2] << 16) | ((u32)d[e_lfanew + 3] << 24); + if (pe_sig != 0x00004550u) return KIT_MALFORMED; /* "PE\0\0" */ + machine = (u16)d[e_lfanew + 4] | ((u16)d[e_lfanew + 5] << 8); + if (!coff_machine_to_arch(machine, &arch)) return KIT_UNSUPPORTED; detect_target_defaults(out); out->obj = KIT_OBJ_COFF; out->os = KIT_OS_WINDOWS; @@ -282,6 +315,8 @@ KitStatus kit_detect_target(const uint8_t* data, size_t len, return detect_elf(data, len, out); #endif #if KIT_OBJ_COFF_ENABLED + case KIT_BIN_PE: + return detect_pe(data, len, out); case KIT_BIN_COFF: return detect_coff(data, len, out); #endif diff --git a/src/api/object_file.c b/src/api/object_file.c @@ -774,3 +774,44 @@ void kit_obj_rpathiter_free(KitObjRpathIter* it) { h = it->file->ctx->heap; h->free(h, it, sizeof(*it)); } + +struct KitObjImageRawIter { + KitObjFile* file; + u32 idx; +}; + +KitStatus kit_obj_image_rawiter_new(KitObjFile* f, KitObjImageRawIter** out) { + Heap* h; + KitObjImageRawIter* it; + if (!f || !out) return KIT_INVALID; + if (!obj_image(f->ob)) return KIT_NOT_FOUND; /* relocatable: no image */ + h = f->ctx->heap; + it = (KitObjImageRawIter*)h->alloc(h, sizeof(*it), + _Alignof(KitObjImageRawIter)); + if (!it) return KIT_NOMEM; + it->file = f; + it->idx = 0; + *out = it; + return KIT_OK; +} + +KitIterResult kit_obj_image_rawiter_next(KitObjImageRawIter* it, + KitObjImageRaw* out) { + const ObjImage* im; + const ObjImageRaw* r; + if (!it || !out) return KIT_ITER_ERROR; + im = obj_image(it->file->ob); + if (it->idx >= obj_image_nraws(im)) return KIT_ITER_END; + r = obj_image_raw(im, it->idx++); + out->tag = r->tag; + out->value = r->value; + out->extra = r->extra; + return KIT_ITER_ITEM; +} + +void kit_obj_image_rawiter_free(KitObjImageRawIter* it) { + Heap* h; + if (!it) return; + h = it->file->ctx->heap; + h->free(h, it, sizeof(*it)); +} diff --git a/src/obj/coff/read.c b/src/obj/coff/read.c @@ -7,12 +7,12 @@ * section-definition aux records. * * Scope: IMAGE_FILE_MACHINE_AMD64 and IMAGE_FILE_MACHINE_ARM64. PE - * executables (with a non-zero SizeOfOptionalHeader) are rejected — a - * future read_coff_pe would handle those. Microsoft "short import" - * records (Sig1=0, Sig2=0xFFFF) found inside .lib archive members are - * detected at entry and dispatched to read_coff_short_import, which - * synthesizes a DSO-shaped ObjBuilder annotated with the providing - * DLL name via obj_set_coff_import_dll. */ + * *images* (executables / DLLs, beginning with the DOS 'MZ' stub) are + * detected at entry and dispatched to read_coff_image (read_image.c). + * Microsoft "short import" records (Sig1=0, Sig2=0xFFFF) found inside + * .lib archive members are likewise detected at entry and dispatched to + * read_coff_short_import, which synthesizes a DSO-shaped ObjBuilder + * annotated with the providing DLL name via obj_set_coff_import_dll. */ #include <string.h> @@ -21,6 +21,7 @@ #include "core/pool.h" #include "core/slice.h" #include "obj/coff/coff.h" +#include "obj/coff/read_util.h" #include "obj/format.h" /* ---- section-header scratch ---- */ @@ -87,44 +88,9 @@ static void resolve_section_name(const char raw[8], const u8* strtab, *len_out = n; } -/* ---- characteristics -> SecKind / SecFlag / SecSem ---- */ - -static u16 coff_sec_kind(const char* name, u32 nlen, u32 ch) { - if (ch & IMAGE_SCN_CNT_UNINITIALIZED_DATA) return SEC_BSS; - if (ch & IMAGE_SCN_CNT_CODE) return SEC_TEXT; - if (ch & IMAGE_SCN_MEM_EXECUTE) return SEC_TEXT; - if (nlen >= 7 && memcmp(name, ".debug_", 7) == 0) return SEC_DEBUG; - /* The MS toolchain spells DWARF section names with a leading ".debug$" - * (CodeView) — keep ELF-style ".debug_" detection but also treat the - * MS form as debug. */ - if (nlen >= 7 && memcmp(name, ".debug$", 7) == 0) return SEC_DEBUG; - if (ch & IMAGE_SCN_CNT_INITIALIZED_DATA) { - if (ch & IMAGE_SCN_MEM_WRITE) return SEC_DATA; - return SEC_RODATA; - } - return SEC_OTHER; -} - -static u16 coff_sec_flags(const char* name, u32 nlen, u32 ch) { - u16 f = 0; - if (ch & IMAGE_SCN_MEM_READ) f |= SF_ALLOC; - if (ch & IMAGE_SCN_MEM_EXECUTE) f |= SF_EXEC; - if (ch & IMAGE_SCN_MEM_WRITE) f |= SF_WRITE; - if (ch & IMAGE_SCN_LNK_COMDAT) f |= SF_GROUP; - /* TLS sections in PE are spelled ".tls$<suffix>" (e.g. ".tls$", ".tls$ZZZ"). - * There is no characteristics bit for TLS — detection is name-based. */ - if (nlen >= 5 && memcmp(name, ".tls$", 5) == 0) f |= SF_TLS; - if (nlen == 4 && memcmp(name, ".tls", 4) == 0) f |= SF_TLS; - return f; -} - -/* Bits 20..23 of Characteristics encode alignment as (log2(align)+1). - * 0 means "default"; we collapse to align=1 for round-trip purposes. */ -static u32 coff_sec_align(u32 ch) { - u32 n = (ch & IMAGE_SCN_ALIGN_MASK) >> 20; - if (n == 0) return 1; - return 1u << (n - 1u); -} +/* characteristics -> SecKind / SecFlag / alignment live in read_util.c + * (coff_sec_kind / coff_sec_flags / coff_sec_align), shared with the + * image reader. */ /* ---- symbol-name resolution ---- */ @@ -347,6 +313,14 @@ ObjBuilder* read_coff(Compiler* c, const char* name, const u8* data, return read_coff_short_import(c, name, data, len); } + /* PE image? A linked .exe/.dll begins with the DOS 'MZ' stub, not a bare + * IMAGE_FILE_HEADER — dispatch to the image reader, which walks the + * DOS -> PE-sig -> file/optional headers. (Placed before the offset-0 + * machine read below, which assumes a bare header, and before the + * optional-header rejection.) */ + if (len >= 2 && coff_rd_u16(data + 0) == IMAGE_DOS_SIGNATURE) + return read_coff_image(c, name, data, len); + u16 machine = coff_rd_u16(data + 0); u16 nsections = coff_rd_u16(data + 2); /* data + 4: TimeDateStamp (4 bytes, ignored). */ @@ -614,9 +588,7 @@ ObjBuilder* read_coff(Compiler* c, const char* name, const u8* data, (int)sec_num); } - /* WEAK_EXTERNAL primary: aux record carries TagIndex + Characteristics. - * kit's model has SB_WEAK; the fallback symbol is link-time - * resolution by name and we drop the explicit index. */ + /* WEAK_EXTERNAL primary: aux record carries TagIndex + Characteristics. */ if (sclass == IMAGE_SYM_CLASS_WEAK_EXTERNAL) bind = SB_WEAK; Sym sn = diff --git a/src/obj/coff/read_dso.c b/src/obj/coff/read_dso.c @@ -26,55 +26,7 @@ #include "core/pool.h" #include "core/slice.h" #include "obj/coff/coff.h" - -/* ---- RVA -> file offset ---- - * Walks the section table once per call. Returns 1 on success and - * fills *off_out; returns 0 if the RVA falls outside every section's - * [VirtualAddress, VirtualAddress + max(VirtualSize, SizeOfRawData)) - * range or the resulting file offset would exceed `len`. */ -static int rva_to_offset(const u8* shdrs, u16 nsec, u32 rva, size_t len, - u64* off_out) { - for (u16 i = 0; i < nsec; ++i) { - const u8* sh = shdrs + (u64)i * COFF_SECTION_HEADER_SIZE; - u32 vsize = coff_rd_u32(sh + 8); - u32 vaddr = coff_rd_u32(sh + 12); - u32 raw_size = coff_rd_u32(sh + 16); - u32 raw_ptr = coff_rd_u32(sh + 20); - /* Some linkers leave VirtualSize == 0 in objects; use raw_size as - * a fallback so we still resolve RVAs in well-formed images. */ - u32 span = vsize ? vsize : raw_size; - if (rva >= vaddr && rva < vaddr + span) { - u64 delta = (u64)(rva - vaddr); - if (delta >= raw_size) return 0; /* RVA past on-disk data */ - u64 off = (u64)raw_ptr + delta; - if (off >= len) return 0; - *off_out = off; - return 1; - } - } - return 0; -} - -/* Read a NUL-terminated string starting at `off`, bounded by `len`. - * Returns the string length (excluding NUL); writes the pointer to - * *out. Returns 0 if off is out of range or the string is not - * terminated within the file. */ -static u32 read_cstr(const u8* data, size_t len, u64 off, const char** out) { - if (off >= len) { - *out = ""; - return 0; - } - const char* s = (const char*)(data + off); - u64 max = (u64)len - off; - u64 n = 0; - while (n < max && s[n] != '\0') ++n; - if (n == max) { - *out = ""; - return 0; - } /* unterminated */ - *out = s; - return (u32)n; -} +#include "obj/coff/read_util.h" ObjBuilder* read_coff_dso(Compiler* c, const char* name, const u8* data, size_t len, Sym* soname_out) { @@ -152,7 +104,7 @@ ObjBuilder* read_coff_dso(Compiler* c, const char* name, const u8* data, } u64 exp_off; - if (!rva_to_offset(shdrs, nsec, export_rva, len, &exp_off)) + if (!coff_rva_to_offset(shdrs, nsec, export_rva, len, &exp_off)) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: export directory RVA 0x%x out of range", export_rva); @@ -172,11 +124,11 @@ ObjBuilder* read_coff_dso(Compiler* c, const char* name, const u8* data, /* ---- DLL name (soname) ---- */ if (name_rva) { u64 name_off; - if (!rva_to_offset(shdrs, nsec, name_rva, len, &name_off)) + if (!coff_rva_to_offset(shdrs, nsec, name_rva, len, &name_off)) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: DLL name RVA 0x%x out of range", name_rva); const char* dll_name; - u32 nlen = read_cstr(data, len, name_off, &dll_name); + u32 nlen = coff_read_cstr(data, len, name_off, &dll_name); if (nlen && soname_out) *soname_out = pool_intern_slice(c->global, (Slice){.s = dll_name, .len = nlen}); @@ -185,13 +137,13 @@ ObjBuilder* read_coff_dso(Compiler* c, const char* name, const u8* data, /* ---- resolve EAT / ENT / ordinal table once ---- */ u64 eat_off = 0, ent_off = 0, ord_off = 0; if (num_names) { - if (!rva_to_offset(shdrs, nsec, eat_rva, len, &eat_off)) + if (!coff_rva_to_offset(shdrs, nsec, eat_rva, len, &eat_off)) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: EAT RVA 0x%x out of range", eat_rva); - if (!rva_to_offset(shdrs, nsec, ent_rva, len, &ent_off)) + if (!coff_rva_to_offset(shdrs, nsec, ent_rva, len, &ent_off)) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: ENT RVA 0x%x out of range", ent_rva); - if (!rva_to_offset(shdrs, nsec, ord_rva, len, &ord_off)) + if (!coff_rva_to_offset(shdrs, nsec, ord_rva, len, &ord_off)) compiler_panic(c, SRCLOC_NONE, "read_coff_dso: ordinal table RVA 0x%x out of range", ord_rva); @@ -217,9 +169,9 @@ ObjBuilder* read_coff_dso(Compiler* c, const char* name, const u8* data, (void)func_rva; /* see comment above re: forwarders */ u64 name_off; - if (!rva_to_offset(shdrs, nsec, nrva, len, &name_off)) continue; + if (!coff_rva_to_offset(shdrs, nsec, nrva, len, &name_off)) continue; const char* nm; - u32 nlen = read_cstr(data, len, name_off, &nm); + u32 nlen = coff_read_cstr(data, len, name_off, &nm); if (!nlen) continue; Sym sn = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}); diff --git a/src/obj/coff/read_image.c b/src/obj/coff/read_image.c @@ -0,0 +1,384 @@ +/* PE32+ linked-image reader. Peer of read_elf_image / read_macho_image: + * parses a linked Windows executable (.exe) or DLL (.dll) into the neutral + * ObjImage view that kit_obj_open / objdump consume — segments, entry point, + * image base, dependencies + imports, dynamic symbols (exports + imports), + * and dynamic relocations (base relocs). A full section/symbol view is + * populated through the ObjBuilder Section table as well, so -h / -s / -d + * work the same way they do for ELF / Mach-O images. + * + * Dispatched from read_coff on the DOS 'MZ' magic (read.c). Handles both + * subkinds: IMAGE_FILE_DLL clear -> OBJ_KIND_EXEC, set -> OBJ_KIND_DYN. + * + * Leniency: truncated *core* headers (DOS / PE sig / file / optional / + * section table) panic -> the kit_obj_open setjmp turns that into + * KIT_MALFORMED. Malformed *sub-tables* (export / import / base-reloc + * directories) are skipped, yielding a partial-but-useful inspection view, + * matching read_elf_image / read_macho_image. */ + +#include <string.h> + +#include <kit/cg.h> +#include <kit/object.h> /* KIT_OBJ_RAW_PE_* reserved tags */ + +#include "core/arena.h" +#include "core/heap.h" +#include "core/pool.h" +#include "core/slice.h" +#include "obj/coff/coff.h" +#include "obj/coff/read_util.h" +#include "obj/format.h" + +static Sym intern(Compiler* c, const char* s, u32 n) { + return n ? pool_intern_slice(c->global, (Slice){.s = s, .len = n}) : 0; +} + +/* ---- exports -> dynsyms + soname ---- + * Mirrors read_coff_dso's export-directory walk, but emits ObjImageSym + * entries (defined, value = ImageBase + func RVA) and sets the DLL's own + * Name as the image soname. Lenient: any out-of-range sub-table aborts the + * export view rather than panicking. */ +static void read_pe_exports(Compiler* c, ObjImage* im, const u8* data, + size_t len, const u8* shdrs, u16 nsec, + const u8* data_dir, u32 num_dirs, u64 image_base) { + if ((u32)IMAGE_DIRECTORY_ENTRY_EXPORT >= num_dirs) return; + const u8* dd = + data_dir + IMAGE_DIRECTORY_ENTRY_EXPORT * COFF_DATA_DIRECTORY_SIZE; + u32 export_rva = coff_rd_u32(dd); + u32 export_size = coff_rd_u32(dd + 4); + if (!export_rva || !export_size) return; + + u64 exp_off; + if (!coff_rva_to_offset(shdrs, nsec, export_rva, len, &exp_off)) return; + if (exp_off + COFF_EXPORT_DIR_SIZE > len) return; + const u8* ed = data + exp_off; + u32 name_rva = coff_rd_u32(ed + 12); + u32 num_funcs = coff_rd_u32(ed + 20); + u32 num_names = coff_rd_u32(ed + 24); + u32 eat_rva = coff_rd_u32(ed + 28); + u32 ent_rva = coff_rd_u32(ed + 32); + u32 ord_rva = coff_rd_u32(ed + 36); + + /* soname = the DLL's own Name (DT_SONAME / LC_ID_DYLIB analogue). */ + if (name_rva) { + u64 noff; + if (coff_rva_to_offset(shdrs, nsec, name_rva, len, &noff)) { + const char* dn; + u32 dl = coff_read_cstr(data, len, noff, &dn); + if (dl) obj_image_set_soname(im, intern(c, dn, dl)); + } + } + + if (!num_names) return; + u64 eat_off, ent_off, ord_off; + if (!coff_rva_to_offset(shdrs, nsec, eat_rva, len, &eat_off)) return; + if (!coff_rva_to_offset(shdrs, nsec, ent_rva, len, &ent_off)) return; + if (!coff_rva_to_offset(shdrs, nsec, ord_rva, len, &ord_off)) return; + if (ent_off + (u64)num_names * 4u > len || + ord_off + (u64)num_names * 2u > len) + return; + if (eat_off + (u64)num_funcs * 4u > len) return; + + for (u32 i = 0; i < num_names; ++i) { + u32 nrva = coff_rd_u32(data + ent_off + (u64)i * 4u); + u16 ord = coff_rd_u16(data + ord_off + (u64)i * 2u); + if (ord >= num_funcs) continue; /* malformed; skip */ + u32 func_rva = coff_rd_u32(data + eat_off + (u64)ord * 4u); + u64 noff; + if (!coff_rva_to_offset(shdrs, nsec, nrva, len, &noff)) continue; + const char* en; + u32 el = coff_read_cstr(data, len, noff, &en); + if (!el) continue; + + ObjImageSym ds; + memset(&ds, 0, sizeof ds); + ds.name = intern(c, en, el); + ds.bind = SB_GLOBAL; + ds.kind = SK_FUNC; /* forwarders point at the export-dir string; still SK_FUNC */ + ds.section = OBJ_SEC_NONE; + ds.value = image_base + func_rva; + obj_image_add_dynsym(im, &ds); + } +} + +/* ---- imports -> deps + undefined dynsyms ---- + * Walks the import directory descriptors. Each provider DLL becomes one + * ObjImageDep carrying its imported-name list; every by-name import also + * lands as an undefined ObjImageSym so -T lists imports like ELF .dynsym. + * By-ordinal imports are not named in v1 and are skipped. */ +static void read_pe_imports(Compiler* c, ObjImage* im, const u8* data, + size_t len, const u8* shdrs, u16 nsec, + const u8* data_dir, u32 num_dirs) { + if ((u32)IMAGE_DIRECTORY_ENTRY_IMPORT >= num_dirs) return; + const u8* dd = + data_dir + IMAGE_DIRECTORY_ENTRY_IMPORT * COFF_DATA_DIRECTORY_SIZE; + u32 imp_rva = coff_rd_u32(dd); + if (!imp_rva) return; + u64 imp_off; + if (!coff_rva_to_offset(shdrs, nsec, imp_rva, len, &imp_off)) return; + + for (u32 d = 0;; ++d) { + u64 desc = imp_off + (u64)d * COFF_IMPORT_DESCRIPTOR_SIZE; + if (desc + COFF_IMPORT_DESCRIPTOR_SIZE > len) break; + u32 oft = coff_rd_u32(data + desc + 0); /* OriginalFirstThunk (ILT) */ + u32 dll_name_rva = coff_rd_u32(data + desc + 12); + u32 ft = coff_rd_u32(data + desc + 16); /* FirstThunk (IAT) */ + if (oft == 0 && dll_name_rva == 0 && ft == 0) break; /* null terminator */ + if (dll_name_rva == 0) continue; + + u64 noff; + if (!coff_rva_to_offset(shdrs, nsec, dll_name_rva, len, &noff)) continue; + const char* dll; + u32 dll_len = coff_read_cstr(data, len, noff, &dll); + if (!dll_len) continue; + Sym dep_name = intern(c, dll, dll_len); + + /* Prefer the ILT (OriginalFirstThunk); fall back to the IAT when the + * image was bound and the ILT is absent. */ + u32 thunk_rva = oft ? oft : ft; + Sym* imports = NULL; + u32 nimports = 0, cap = 0; + u64 toff; + if (thunk_rva && coff_rva_to_offset(shdrs, nsec, thunk_rva, len, &toff)) { + for (u32 t = 0;; ++t) { + u64 te = toff + (u64)t * COFF_THUNK_DATA64_SIZE; + if (te + COFF_THUNK_DATA64_SIZE > len) break; + u64 thunk = coff_rd_u64(data + te); + if (thunk == 0) break; /* null-terminated table */ + if (thunk & IMAGE_ORDINAL_FLAG64) continue; /* by-ordinal: skip (v1) */ + u32 ibn_rva = (u32)(thunk & 0x7fffffffu); + u64 hoff; + if (!coff_rva_to_offset(shdrs, nsec, ibn_rva, len, &hoff)) continue; + /* IMAGE_IMPORT_BY_NAME: u16 Hint, then NUL-terminated name. */ + const char* inm; + u32 il = coff_read_cstr(data, len, hoff + 2u, &inm); + if (!il) continue; + Sym isym = intern(c, inm, il); + + if (nimports == cap) { + u32 ncap = cap ? cap * 2u : 8u; + Sym* grown = arena_array(c->scratch, Sym, ncap); + if (nimports) memcpy(grown, imports, sizeof(Sym) * nimports); + imports = grown; + cap = ncap; + } + imports[nimports++] = isym; + + ObjImageSym us; + memset(&us, 0, sizeof us); + us.name = isym; + us.bind = SB_GLOBAL; + us.kind = SK_NOTYPE; /* PE import descriptors don't distinguish func/data */ + us.section = OBJ_SEC_NONE; + obj_image_add_dynsym(im, &us); + } + } + + ObjImageDep dep; + dep.name = dep_name; + dep.imports = imports; /* transient scratch; add_dep deep-copies */ + dep.nimports = nimports; + obj_image_add_dep(im, &dep); + } +} + +/* ---- base relocations -> dynrelocs ---- + * Walks the .reloc base-relocation blocks. Each DIR64/HIGHLOW fixup is a + * symbol-less load-bias adjustment, mapped to the arch's RELATIVE kind. + * ABSOLUTE entries are block padding and skipped. */ +static void read_pe_basereloc(ObjImage* im, const u8* data, size_t len, + const u8* shdrs, u16 nsec, const u8* data_dir, + u32 num_dirs, u64 image_base, + RelocKind relative_kind) { + if ((u32)IMAGE_DIRECTORY_ENTRY_BASERELOC >= num_dirs) return; + const u8* dd = + data_dir + IMAGE_DIRECTORY_ENTRY_BASERELOC * COFF_DATA_DIRECTORY_SIZE; + u32 rel_rva = coff_rd_u32(dd); + u32 rel_size = coff_rd_u32(dd + 4); + if (!rel_rva || !rel_size) return; + u64 rel_off; + if (!coff_rva_to_offset(shdrs, nsec, rel_rva, len, &rel_off)) return; + u64 end = rel_off + rel_size; + if (end > len) end = len; + + u64 pos = rel_off; + while (pos + COFF_BASE_RELOCATION_SIZE <= end) { + u32 page_rva = coff_rd_u32(data + pos + 0); + u32 block_size = coff_rd_u32(data + pos + 4); + if (block_size < COFF_BASE_RELOCATION_SIZE) break; /* malformed */ + if (pos + block_size > end) block_size = (u32)(end - pos); + u32 nent = (block_size - COFF_BASE_RELOCATION_SIZE) / 2u; + for (u32 e = 0; e < nent; ++e) { + u16 ent = coff_rd_u16(data + pos + COFF_BASE_RELOCATION_SIZE + (u64)e * 2u); + u32 type = (u32)ent >> 12; + u32 off = (u32)ent & 0x0fffu; + if (type == IMAGE_REL_BASED_ABSOLUTE) continue; /* padding */ + if (type != IMAGE_REL_BASED_DIR64 && type != IMAGE_REL_BASED_HIGHLOW) + continue; + ObjImageReloc dr; + memset(&dr, 0, sizeof dr); + dr.section = OBJ_SEC_NONE; /* offset is a vaddr */ + dr.offset = image_base + page_rva + off; + dr.kind = relative_kind; + obj_image_add_dynreloc(im, &dr); + } + pos += block_size; + } +} + +ObjBuilder* read_coff_image(Compiler* c, const char* name, const u8* data, + size_t len) { + (void)name; + + /* ---- DOS header + PE signature (truncation panics) ---- */ + if (len < COFF_DOS_HEADER_SIZE) + compiler_panic(c, SRCLOC_NONE, + "read_coff_image: input shorter than DOS header"); + if (coff_rd_u16(data + 0) != IMAGE_DOS_SIGNATURE) + compiler_panic(c, SRCLOC_NONE, "read_coff_image: bad DOS magic"); + u32 e_lfanew = coff_rd_u32(data + 60); + u64 nt_end = + (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + COFF_OPT_HDR64_SIZE; + if (nt_end > len) + compiler_panic(c, SRCLOC_NONE, + "read_coff_image: PE headers extend past end of file"); + if (coff_rd_u32(data + e_lfanew) != IMAGE_NT_SIGNATURE) + compiler_panic(c, SRCLOC_NONE, "read_coff_image: bad PE signature"); + + /* ---- IMAGE_FILE_HEADER ---- */ + const u8* fh = data + e_lfanew + 4u; + u16 machine = coff_rd_u16(fh + 0); + u16 nsec = coff_rd_u16(fh + 2); + u16 size_of_opt = coff_rd_u16(fh + 16); + u16 chars = coff_rd_u16(fh + 18); + if (machine != IMAGE_FILE_MACHINE_AMD64 && + machine != IMAGE_FILE_MACHINE_ARM64 && + machine != IMAGE_FILE_MACHINE_ARM64EC) + compiler_panic(c, SRCLOC_NONE, "read_coff_image: unsupported machine %#x", + (u32)machine); + if (size_of_opt < COFF_OPT_HDR64_SIZE) + compiler_panic(c, SRCLOC_NONE, + "read_coff_image: optional header %u too small for PE32+", + (u32)size_of_opt); + + /* ---- IMAGE_OPTIONAL_HEADER64 ---- */ + const u8* oh = fh + COFF_FILE_HEADER_SIZE; + if (coff_rd_u16(oh + 0) != IMAGE_NT_OPTIONAL_HDR64_MAGIC) + compiler_panic(c, SRCLOC_NONE, "read_coff_image: not PE32+"); + u32 entry_rva = coff_rd_u32(oh + 16); + u64 image_base = coff_rd_u64(oh + 24); + u32 sect_align = coff_rd_u32(oh + 32); + u16 subsystem = coff_rd_u16(oh + 68); + u16 dllchars = coff_rd_u16(oh + 70); + u32 num_dirs = coff_rd_u32(oh + 108); + if (num_dirs > COFF_NUM_DATA_DIRECTORIES) num_dirs = COFF_NUM_DATA_DIRECTORIES; + const u8* data_dir = oh + COFF_OPT_HDR64_SIZE - + COFF_NUM_DATA_DIRECTORIES * COFF_DATA_DIRECTORY_SIZE; + + /* ---- section table ---- */ + u64 shdrs_off = (u64)e_lfanew + 4u + COFF_FILE_HEADER_SIZE + size_of_opt; + if (shdrs_off + (u64)nsec * COFF_SECTION_HEADER_SIZE > len) + compiler_panic(c, SRCLOC_NONE, + "read_coff_image: section table extends past end of file"); + const u8* shdrs = data + shdrs_off; + + /* Arch ops resolve the RELATIVE base-reloc kind (machine validated above). */ + const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_COFF); + const ObjCoffArchOps* aops = + (fmt && fmt->coff_machine) ? fmt->coff_machine(machine) : NULL; + if (!aops) + compiler_panic(c, SRCLOC_NONE, + "read_coff_image: no arch impl for machine %#x", + (u32)machine); + RelocKind relative_kind = (aops->arch == KIT_ARCH_X86_64) ? R_X64_RELATIVE + : (aops->arch == KIT_ARCH_ARM_64) ? R_AARCH64_RELATIVE + : R_X64_RELATIVE; + + ObjBuilder* ob = obj_new(c); + if (!ob) compiler_panic(c, SRCLOC_NONE, "read_coff_image: obj_new failed"); + ObjImage* im = obj_image_ensure( + ob, (chars & IMAGE_FILE_DLL) ? OBJ_KIND_DYN : OBJ_KIND_EXEC); + if (!im) + compiler_panic(c, SRCLOC_NONE, "read_coff_image: obj_image_ensure failed"); + obj_image_set_base(im, image_base); + obj_image_set_entry(im, entry_rva ? image_base + entry_rva : 0); + + /* ---- sections + segments (dual-emit) ---- */ + for (u16 i = 0; i < nsec; ++i) { + const u8* sh = shdrs + (u64)i * COFF_SECTION_HEADER_SIZE; + const char* raw = (const char*)sh; /* Name[8], NUL-padded (no long form) */ + u32 nlen = 0; + while (nlen < 8 && raw[nlen] != '\0') ++nlen; + u32 vsize = coff_rd_u32(sh + 8); + u32 vaddr = coff_rd_u32(sh + 12); + u32 rawsize = coff_rd_u32(sh + 16); + u32 rawptr = coff_rd_u32(sh + 20); + u32 ch = coff_rd_u32(sh + 36); + + Sym sn = intern(c, raw, nlen); + u16 kind = coff_sec_kind(raw, nlen, ch); + u16 flags = coff_sec_flags(raw, nlen, ch); + u32 align = coff_sec_align(ch); + int is_bss = (ch & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0; + u16 sem = is_bss ? SSEM_NOBITS : SSEM_PROGBITS; + + ObjSecId id = + obj_section_ex(ob, sn, (SecKind)kind, (SecSem)sem, flags, align, 0u, 0u, 0u); + if (id != OBJ_SEC_NONE) { + obj_section_set_ext(ob, id, OBJ_EXT_COFF, ch, 0); + obj_section_set_addr(ob, id, image_base + vaddr); + if (is_bss) { + obj_reserve_bss(ob, id, vsize ? vsize : rawsize, align); + } else if (rawsize) { + /* Images FileAlignment-pad raw data; copy at most VirtualSize, and + * clamp leniently to the file length (vs the strict .obj path). */ + u32 copy = rawsize; + if (vsize && vsize < copy) copy = vsize; + if ((u64)rawptr + copy > len) + copy = (rawptr < len) ? (u32)(len - rawptr) : 0; + if (copy) { + u8* dst = obj_reserve(ob, id, copy); + if (dst) memcpy(dst, data + rawptr, copy); + } + } + } + + ObjSegment seg; + memset(&seg, 0, sizeof seg); + seg.name = sn; + seg.vaddr = image_base + vaddr; + seg.vsize = vsize; + seg.file_off = rawptr; + seg.file_size = rawsize; + seg.perms = ((ch & IMAGE_SCN_MEM_READ) ? OBJ_SEG_R : 0u) | + ((ch & IMAGE_SCN_MEM_WRITE) ? OBJ_SEG_W : 0u) | + ((ch & IMAGE_SCN_MEM_EXECUTE) ? OBJ_SEG_X : 0u); + seg.align = sect_align ? sect_align : 1u; + obj_image_add_segment(im, &seg); + } + + /* ---- raw escape-hatch entries: 16 data dirs + subsystem + dllchars ---- */ + for (u32 i = 0; i < COFF_NUM_DATA_DIRECTORIES; ++i) { + const u8* e = data_dir + (u64)i * COFF_DATA_DIRECTORY_SIZE; + ObjImageRaw r; + r.tag = i; + r.value = (i < num_dirs) ? coff_rd_u32(e) : 0; + r.extra = (i < num_dirs) ? coff_rd_u32(e + 4) : 0; + obj_image_add_raw(im, &r); + } + { + ObjImageRaw r = {KIT_OBJ_RAW_PE_SUBSYSTEM, subsystem, 0}; + obj_image_add_raw(im, &r); + } + { + ObjImageRaw r = {KIT_OBJ_RAW_PE_DLLCHARS, dllchars, 0}; + obj_image_add_raw(im, &r); + } + + read_pe_exports(c, im, data, len, shdrs, nsec, data_dir, num_dirs, image_base); + read_pe_imports(c, im, data, len, shdrs, nsec, data_dir, num_dirs); + read_pe_basereloc(im, data, len, shdrs, nsec, data_dir, num_dirs, image_base, + relative_kind); + + obj_finalize(ob); + return ob; +} diff --git a/src/obj/coff/read_util.c b/src/obj/coff/read_util.c @@ -0,0 +1,84 @@ +/* Shared PE/COFF reader primitives — see read_util.h. */ + +#include "obj/coff/read_util.h" + +#include <string.h> + +#include "obj/obj.h" /* SecKind / SecFlag enums */ + +int coff_rva_to_offset(const u8* shdrs, u16 nsec, u32 rva, size_t len, + u64* off_out) { + for (u16 i = 0; i < nsec; ++i) { + const u8* sh = shdrs + (u64)i * COFF_SECTION_HEADER_SIZE; + u32 vsize = coff_rd_u32(sh + 8); + u32 vaddr = coff_rd_u32(sh + 12); + u32 raw_size = coff_rd_u32(sh + 16); + u32 raw_ptr = coff_rd_u32(sh + 20); + /* Some linkers leave VirtualSize == 0 in objects; use raw_size as + * a fallback so we still resolve RVAs in well-formed images. */ + u32 span = vsize ? vsize : raw_size; + if (rva >= vaddr && rva < vaddr + span) { + u64 delta = (u64)(rva - vaddr); + if (delta >= raw_size) return 0; /* RVA past on-disk data */ + u64 off = (u64)raw_ptr + delta; + if (off >= len) return 0; + *off_out = off; + return 1; + } + } + return 0; +} + +u32 coff_read_cstr(const u8* data, size_t len, u64 off, const char** out) { + if (off >= len) { + *out = ""; + return 0; + } + const char* s = (const char*)(data + off); + u64 max = (u64)len - off; + u64 n = 0; + while (n < max && s[n] != '\0') ++n; + if (n == max) { + *out = ""; + return 0; + } /* unterminated */ + *out = s; + return (u32)n; +} + +u16 coff_sec_kind(const char* name, u32 nlen, u32 ch) { + if (ch & IMAGE_SCN_CNT_UNINITIALIZED_DATA) return SEC_BSS; + if (ch & IMAGE_SCN_CNT_CODE) return SEC_TEXT; + if (ch & IMAGE_SCN_MEM_EXECUTE) return SEC_TEXT; + if (nlen >= 7 && memcmp(name, ".debug_", 7) == 0) return SEC_DEBUG; + /* The MS toolchain spells DWARF section names with a leading ".debug$" + * (CodeView) — keep ELF-style ".debug_" detection but also treat the + * MS form as debug. */ + if (nlen >= 7 && memcmp(name, ".debug$", 7) == 0) return SEC_DEBUG; + if (ch & IMAGE_SCN_CNT_INITIALIZED_DATA) { + if (ch & IMAGE_SCN_MEM_WRITE) return SEC_DATA; + return SEC_RODATA; + } + return SEC_OTHER; +} + +u16 coff_sec_flags(const char* name, u32 nlen, u32 ch) { + u16 f = 0; + if (ch & IMAGE_SCN_MEM_READ) f |= SF_ALLOC; + if (ch & IMAGE_SCN_MEM_EXECUTE) f |= SF_EXEC; + if (ch & IMAGE_SCN_MEM_WRITE) f |= SF_WRITE; + if (ch & IMAGE_SCN_LNK_COMDAT) f |= SF_GROUP; + /* TLS sections in PE are spelled ".tls$<suffix>" (e.g. ".tls$", ".tls$ZZZ"). + * There is no characteristics bit for TLS — detection is name-based. */ + if (nlen >= 5 && memcmp(name, ".tls$", 5) == 0) f |= SF_TLS; + if (nlen == 4 && memcmp(name, ".tls", 4) == 0) f |= SF_TLS; + return f; +} + +/* Bits 20..23 of Characteristics encode alignment as (log2(align)+1). + * 0 means "default"; we collapse to align=1 for round-trip purposes. */ +u32 coff_sec_align(u32 ch) { + u32 n = (ch & IMAGE_SCN_ALIGN_MASK) >> 20; + if (n == 0) return 1; + return 1u << (n - 1u); +} diff --git a/src/obj/coff/read_util.h b/src/obj/coff/read_util.h @@ -0,0 +1,36 @@ +/* Shared PE/COFF reader primitives. Pure helpers (no ObjBuilder + * coupling) used by both the relocatable-object reader (read_coff), the + * DLL reader (read_coff_dso), and the linked-image reader + * (read_coff_image), so the RVA mapping, bounded string reads, and the + * characteristics -> canonical SecKind/SecFlag/align mapping live in one + * place. */ + +#ifndef KIT_OBJ_COFF_READ_UTIL_H +#define KIT_OBJ_COFF_READ_UTIL_H + +#include <stddef.h> + +#include "obj/coff/coff.h" /* u8/u16/u32/u64, IMAGE_SCN_*, coff_rd_* */ + +/* RVA -> file offset via the section table (40-byte ImageSectionHeader + * entries, `nsec` of them at `shdrs`). Returns 1 and fills *off_out on + * success; 0 if the RVA falls outside every section's + * [VirtualAddress, VirtualAddress + max(VirtualSize, SizeOfRawData)) + * range or the resulting file offset would exceed `len`. */ +int coff_rva_to_offset(const u8* shdrs, u16 nsec, u32 rva, size_t len, + u64* off_out); + +/* Read a NUL-terminated string at file offset `off`, bounded by `len`. + * Returns the length (excluding NUL) and writes the pointer to *out. + * Returns 0 (and *out = "") if off is out of range or the string is not + * terminated within the file. */ +u32 coff_read_cstr(const u8* data, size_t len, u64 off, const char** out); + +/* Characteristics -> canonical SecKind / SecFlag / alignment. Shared by + * the .obj reader and the image reader so both classify sections + * identically. Returns are SecKind / SecFlag values (widened to u16). */ +u16 coff_sec_kind(const char* name, u32 nlen, u32 ch); +u16 coff_sec_flags(const char* name, u32 nlen, u32 ch); +u32 coff_sec_align(u32 ch); + +#endif diff --git a/src/obj/elf/read.c b/src/obj/elf/read.c @@ -439,6 +439,16 @@ static void read_elf_image(Compiler* c, ObjBuilder* ob, const u8* data, for (u64 off = 0; off + dyn_size <= dynsz; off += dyn_size) { u64 tag = elf_rd_addr(dynp + off, is32); u64 val = elf_rd_addr(dynp + off + (is32 ? 4 : 8), is32); + /* Raw .dynamic view (escape hatch): one entry per DT_* tag, the + * terminating DT_NULL included, before the NEEDED/SONAME/RPATH + * filtering below. */ + { + ObjImageRaw r; + r.tag = (u32)tag; + r.value = val; + r.extra = 0; + obj_image_add_raw(im, &r); + } if (tag == DT_NULL) break; if (tag != DT_NEEDED && tag != DT_SONAME && tag != DT_RPATH && tag != DT_RUNPATH) diff --git a/src/obj/macho/read.c b/src/obj/macho/read.c @@ -175,6 +175,16 @@ static void read_macho_image(Compiler* c, ObjBuilder* ob, const u8* data, u32 cmdsize = rd_u32_le(data + pos + 4); if (cmdsize < 8 || pos + cmdsize > end) break; + /* Raw load-command view (escape hatch): one entry per LC_* command, + * carrying its file offset and on-disk size. */ + { + ObjImageRaw r; + r.tag = cmd; + r.value = pos; + r.extra = cmdsize; + obj_image_add_raw(im, &r); + } + if (cmd == LC_SEGMENT_64 && cmdsize >= MACHO_SEGCMD64_SIZE) { const char* segname = (const char*)(data + pos + 8); u32 seg_len = fixed16_len(segname); diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -266,6 +266,8 @@ struct ObjImage { u32 ndynsyms, cap_dynsyms; ObjImageReloc* dynrelocs; u32 ndynrelocs, cap_dynrelocs; + ObjImageRaw* raws; + u32 nraws, cap_raws; /* Undefined symbol names a DSO references (interned). Used by the linker's * --gc-sections pass to keep executable-defined symbols a shared library * needs (e.g. libc.so.7's `environ` / `__progname`) from being collected. */ @@ -277,8 +279,17 @@ static void obj_image_free_(ObjBuilder* ob) { ObjImage* im; if (!ob || !ob->image) return; im = ob->image; - /* Dep import-name arrays are caller-owned (interned in the global pool); - * only the deps vector itself is ours to release. */ + /* The image owns each dep's imports[] array container (allocated from + * im->heap by the PE reader); the Sym values inside stay interned in the + * global pool and are not freed here. ELF/Mach-O deps carry imports==NULL. */ + if (im->deps) { + for (u32 i = 0; i < im->ndeps; ++i) { + const ObjImageDep* d = &im->deps[i]; + if (d->imports) + im->heap->free(im->heap, (void*)d->imports, + sizeof(*d->imports) * d->nimports); + } + } if (im->segs) im->heap->free(im->heap, im->segs, sizeof(*im->segs) * im->cap_segs); if (im->deps) @@ -291,6 +302,8 @@ static void obj_image_free_(ObjBuilder* ob) { if (im->dynrelocs) im->heap->free(im->heap, im->dynrelocs, sizeof(*im->dynrelocs) * im->cap_dynrelocs); + if (im->raws) + im->heap->free(im->heap, im->raws, sizeof(*im->raws) * im->cap_raws); if (im->undefs) im->heap->free(im->heap, im->undefs, sizeof(*im->undefs) * im->cap_undefs); ob->heap->free(ob->heap, im, sizeof(*im)); @@ -336,9 +349,29 @@ void obj_image_add_segment(ObjImage* im, const ObjSegment* seg) { im->segs[im->nsegs++] = *seg; } void obj_image_add_dep(ObjImage* im, const ObjImageDep* dep) { + ObjImageDep d; if (!im || !dep) return; if (VEC_GROW(im->heap, im->deps, im->cap_deps, im->ndeps + 1)) return; - im->deps[im->ndeps++] = *dep; + d = *dep; + /* Deep-copy the imports[] name array into image-heap-owned memory so the + * reader may pass a transient (scratch/arena) array; obj_image_free_ + * releases this copy. The Sym values inside are global-interned and not + * owned here. ELF/Mach-O deps carry imports==NULL (nimports==0). */ + if (d.nimports && dep->imports) { + Sym* copy = (Sym*)im->heap->alloc(im->heap, sizeof(Sym) * d.nimports, + _Alignof(Sym)); + if (!copy) { + d.imports = NULL; + d.nimports = 0; + } else { + memcpy(copy, dep->imports, sizeof(Sym) * d.nimports); + d.imports = copy; + } + } else { + d.imports = NULL; + d.nimports = 0; + } + im->deps[im->ndeps++] = d; } void obj_image_add_rpath(ObjImage* im, Sym rpath) { if (!im) return; @@ -362,6 +395,11 @@ void obj_image_add_undef(ObjImage* im, Sym name) { if (VEC_GROW(im->heap, im->undefs, im->cap_undefs, im->nundefs + 1)) return; im->undefs[im->nundefs++] = name; } +void obj_image_add_raw(ObjImage* im, const ObjImageRaw* raw) { + if (!im || !raw) return; + if (VEC_GROW(im->heap, im->raws, im->cap_raws, im->nraws + 1)) return; + im->raws[im->nraws++] = *raw; +} ObjKind obj_image_kind(const ObjImage* im) { return im ? im->kind : OBJ_KIND_REL; @@ -395,6 +433,10 @@ u32 obj_image_nundefs(const ObjImage* im) { return im ? im->nundefs : 0; } Sym obj_image_undef(const ObjImage* im, u32 idx) { return (im && idx < im->nundefs) ? im->undefs[idx] : 0; } +u32 obj_image_nraws(const ObjImage* im) { return im ? im->nraws : 0; } +const ObjImageRaw* obj_image_raw(const ObjImage* im, u32 idx) { + return (im && idx < im->nraws) ? &im->raws[idx] : NULL; +} void obj_ext_set(ObjBuilder* ob, ObjExtKind kind, void* payload, ObjExtFreeFn free_fn) { diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -961,6 +961,21 @@ typedef struct ObjImageReloc { RelocKind kind; } ObjImageReloc; +/* Raw, format-specific image field that doesn't fit the neutral model. + * One flat triple list per image, in the spirit of the per-section + * kit_obj_section_format_flags escape hatch: a neutral container with + * per-format tag semantics (documented on the public KitObjImageRaw): + * PE : data dirs tag=0..15 (index), value=RVA, extra=size; + * subsystem tag=KIT_OBJ_RAW_PE_SUBSYSTEM, value=u16; + * dllchars tag=KIT_OBJ_RAW_PE_DLLCHARS, value=u16 + * ELF : .dynamic tag=d_tag, value=d_val, extra=0 + * Mach-O: load cmds tag=cmd, value=file offset, extra=cmdsize */ +typedef struct ObjImageRaw { + u32 tag; + u64 value; + u64 extra; +} ObjImageRaw; + typedef struct ObjImage ObjImage; /* defined in obj.c */ /* Accessor — NULL on relocatable inputs. */ @@ -977,13 +992,17 @@ void obj_image_set_interp(ObjImage*, Sym interp); void obj_image_set_soname(ObjImage*, Sym soname); /* Image table appenders (readers). Each copies its argument by value into a - * builder-heap-owned vector. The Sym array behind ObjImageDep.imports must - * outlive the builder (intern into the compiler's global pool). */ + * builder-heap-owned vector. obj_image_add_dep additionally deep-copies the + * ObjImageDep.imports[] name array into image-heap memory, so the reader may + * pass a transient (scratch) array; the Sym values themselves must still be + * interned in the compiler's global pool. */ void obj_image_add_segment(ObjImage*, const ObjSegment*); void obj_image_add_dep(ObjImage*, const ObjImageDep*); void obj_image_add_rpath(ObjImage*, Sym rpath); void obj_image_add_dynsym(ObjImage*, const ObjImageSym*); void obj_image_add_dynreloc(ObjImage*, const ObjImageReloc*); +/* Raw format-specific image fields (see ObjImageRaw). Copied by value. */ +void obj_image_add_raw(ObjImage*, const ObjImageRaw*); /* Undefined symbol names a DSO references (interned). The linker's * --gc-sections pass roots executable definitions of these so a shared * library's back-references (e.g. libc.so.7 → `environ` / `__progname`) @@ -1008,6 +1027,8 @@ u32 obj_image_ndynrelocs(const ObjImage*); const ObjImageReloc* obj_image_dynreloc(const ObjImage*, u32 idx); u32 obj_image_nundefs(const ObjImage*); Sym obj_image_undef(const ObjImage*, u32 idx); +u32 obj_image_nraws(const ObjImage*); +const ObjImageRaw* obj_image_raw(const ObjImage*, u32 idx); /* ---- file format emitters ---- */ void emit_elf(Compiler*, ObjBuilder*, Writer*); @@ -1041,6 +1062,17 @@ ObjBuilder* read_coff(Compiler*, const char* name, const u8* data, size_t len); * synthesized in v1 — almost all real-world imports are by name. */ ObjBuilder* read_coff_dso(Compiler*, const char* name, const u8* data, size_t len, Sym* soname_out); +/* PE32+ linked-image reader (peer of read_elf_image / read_macho_image). + * Handles both executables (IMAGE_FILE_DLL clear -> OBJ_KIND_EXEC) and + * DLLs (set -> OBJ_KIND_DYN), populating the neutral ObjImage: one + * segment per PE section, exports -> dynsyms + soname, imports -> deps + + * undefined dynsyms, base relocs -> RELATIVE dynrelocs, plus a full + * section/symbol view via the ObjBuilder Section table, and the raw + * data-directory / subsystem / dllchars escape-hatch entries. Lenient: + * malformed sub-tables are skipped; truncated core headers panic. + * Dispatched from read_coff on the DOS 'MZ' magic. */ +ObjBuilder* read_coff_image(Compiler*, const char* name, const u8* data, + size_t len); ObjBuilder* read_macho(Compiler*, const char* name, const u8* data, size_t len); /* Mach-O MH_DYLIB reader. Produces an ObjBuilder containing only the * dylib's exported symbols (as defined OBJ_SEC_NONE entries — the diff --git a/test/coff/pe-image-read.c b/test/coff/pe-image-read.c @@ -0,0 +1,415 @@ +/* PE32+ linked-image reader round-trip (read_coff_image, no external + * toolchain). + * + * Links a tiny PIE executable in memory with kit's own COFF linker — a + * .text entry plus a .data slot that takes an absolute (R_ABS64) reference + * to an imported ExitProcess from KERNEL32.dll (via a short-import shim) — + * then re-opens the emitted bytes through the public kit_obj_open and + * asserts the neutral image view the reader populates: + * - kind EXEC, nonzero entry / image base + * - segments + sections (one per PE section, .text executable) + * - dependency KERNEL32.dll carrying the ExitProcess import + * - dynamic symbol ExitProcess (undefined import) + * - base relocation(s) for the absolute .data pointer (PIE) + * - raw escape hatch: 16 data directories + subsystem + dllchars, + * IMPORT directory populated + * + * Runs on every host (the reader is ours); covers both x86_64 and aarch64 + * Windows targets. */ + +#include <kit/core.h> +#include <kit/link.h> +#include <kit/object.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "core/core.h" +#include "core/pool.h" +#include "link/link.h" +#include "obj/obj.h" + +/* ---- short-import wire constants (mirror pe-import-smoke.c). ---- */ +#define SHIM_HEADER_SIZE 20u +#define SHIM_SYM_CSTR "ExitProcess" +#define SHIM_DLL_CSTR "KERNEL32.dll" +#define SHIM_SYM_NUL_LEN 12u /* "ExitProcess\0" */ +#define SHIM_DLL_NUL_LEN 13u /* "KERNEL32.dll\0" */ +#define SHIM_DATA_LEN (SHIM_SYM_NUL_LEN + SHIM_DLL_NUL_LEN) +#define SHIM_TOTAL_LEN (SHIM_HEADER_SIZE + SHIM_DATA_LEN) +#define COFF_SHIMP_SIG2 0xFFFFu +/* TypeFlags = Type=CODE(0) | (NameType=NAME(1) << 2) = 0x0004. */ +#define COFF_SHIMP_TYPEFLAGS 0x0004u + +/* ---- env vtables --------------------------------------------------- */ + +static void* heap_alloc(KitHeap* h, size_t n, size_t a) { + (void)h; + (void)a; + return n ? malloc(n) : NULL; +} +static void* heap_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) { + (void)h; + (void)o; + (void)a; + return realloc(p, n); +} +static void heap_free(KitHeap* h, void* p, size_t n) { + (void)h; + (void)n; + free(p); +} +static KitHeap g_heap = {heap_alloc, heap_realloc, heap_free, NULL}; + +static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc, + const char* fmt, va_list ap) { + static const char* names[] = {"note", "warning", "error", "fatal"}; + (void)s; + (void)loc; + fprintf(stderr, "%s: ", names[k]); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); +} +static KitDiagSink g_diag = {diag_emit, NULL, 0, 0}; +static KitContext g_ctx; + +static int g_failures; +static const char* g_case = "?"; +#define EXPECT(cond, ...) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL [%s] %s:%d: ", g_case, __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fputc('\n', stderr); \ + g_failures++; \ + } \ + } while (0) + +/* ---- target / compiler ------------------------------------------- */ + +static void target_windows(KitTargetSpec* t, KitArchKind arch) { + memset(t, 0, sizeof *t); + t->arch = arch; + t->os = KIT_OS_WINDOWS; + t->obj = KIT_OBJ_COFF; + t->ptr_size = 8; + t->ptr_align = 8; + t->big_endian = false; + t->pic = KIT_PIC_PIE; + t->code_model = KIT_CM_SMALL; +} + +static Compiler* make_compiler(const KitTargetSpec* t) { + KitTargetOptions opts; + KitTarget* target = NULL; + KitCompiler* cc = NULL; + memset(&opts, 0, sizeof opts); + opts.spec = *t; + if (kit_target_new(&g_ctx, &opts, &target) != KIT_OK || !target) return NULL; + if (kit_compiler_new(target, &g_ctx, &cc) != KIT_OK || !cc) { + kit_target_free(target); + return NULL; + } + return (Compiler*)cc; +} + +static void free_compiler(Compiler* c) { + const KitTarget* target; + if (!c) return; + target = kit_compiler_target((KitCompiler*)c); + kit_compiler_free((KitCompiler*)c); + kit_target_free((KitTarget*)target); +} + +/* ---- short-import shim builder ------------------------------------ */ + +static void build_short_import(uint8_t buf[SHIM_TOTAL_LEN], uint16_t machine) { + memset(buf, 0, SHIM_TOTAL_LEN); + buf[2] = (uint8_t)(COFF_SHIMP_SIG2 & 0xFF); /* Sig2 = 0xFFFF */ + buf[3] = (uint8_t)((COFF_SHIMP_SIG2 >> 8) & 0xFF); + buf[6] = (uint8_t)(machine & 0xFF); + buf[7] = (uint8_t)((machine >> 8) & 0xFF); + buf[12] = (uint8_t)(SHIM_DATA_LEN & 0xFFu); /* SizeOfData */ + buf[13] = (uint8_t)((SHIM_DATA_LEN >> 8) & 0xFFu); + buf[18] = (uint8_t)(COFF_SHIMP_TYPEFLAGS & 0xFF); + buf[19] = (uint8_t)((COFF_SHIMP_TYPEFLAGS >> 8) & 0xFF); + memcpy(buf + SHIM_HEADER_SIZE, SHIM_SYM_CSTR, SHIM_SYM_NUL_LEN); + memcpy(buf + SHIM_HEADER_SIZE + SHIM_SYM_NUL_LEN, SHIM_DLL_CSTR, + SHIM_DLL_NUL_LEN); +} + +/* ---- program ObjBuilder ------------------------------------------- */ + +/* mainCRTStartup body: a single return. The exact encoding is irrelevant + * to the reader; differ per arch only so the linker sees plausible code. */ +static const uint8_t TEXT_X64[1] = {0xc3}; /* ret */ +static const uint8_t TEXT_AA64[4] = {0xc0, 0x03, 0x5f, 0xd6}; /* ret */ + +static ObjBuilder* build_program(Compiler* c, KitArchKind arch) { + ObjBuilder* ob = obj_new(c); + Pool* p = c->global; + Sym text_name = pool_intern_slice(p, SLICE_LIT(".text")); + Sym data_name = pool_intern_slice(p, SLICE_LIT(".data")); + Sym main_name = pool_intern_slice(p, SLICE_LIT("mainCRTStartup")); + Sym exit_name = pool_intern_slice(p, SLICE_LIT(SHIM_SYM_CSTR)); + const uint8_t* text = arch == KIT_ARCH_X86_64 ? TEXT_X64 : TEXT_AA64; + u32 text_len = arch == KIT_ARCH_X86_64 ? (u32)sizeof TEXT_X64 : (u32)sizeof TEXT_AA64; + ObjSecId tsec = obj_section(ob, text_name, SEC_TEXT, SF_ALLOC | SF_EXEC, 16); + ObjSecId dsec = obj_section(ob, data_name, SEC_DATA, SF_ALLOC | SF_WRITE, 8); + ObjSymId exit_sym; + uint8_t zeros[8] = {0}; + + obj_write(ob, tsec, text, text_len); + obj_symbol(ob, main_name, SB_GLOBAL, SK_FUNC, tsec, 0, text_len); + + /* .data: an 8-byte absolute pointer to the imported ExitProcess. The + * R_ABS64 both forces ExitProcess to be imported and (in a PIE) yields a + * base relocation, so the reader's import + base-reloc paths both run. */ + exit_sym = obj_symbol(ob, exit_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); + obj_write(ob, dsec, zeros, sizeof zeros); + obj_reloc(ob, dsec, 0, R_ABS64, exit_sym, 0); + + obj_finalize(ob); + return ob; +} + +/* Link a PE image and copy the emitted bytes into a fresh malloc buffer + * (so the reader runs fully independent of the producing compiler). + * Returns NULL on failure. */ +static uint8_t* link_pe(Compiler* c, KitArchKind arch, uint16_t machine, + size_t* out_len) { + ObjBuilder* prog = build_program(c, arch); + uint8_t shim[SHIM_TOTAL_LEN]; + Linker* l; + LinkImage* img; + KitWriter* w = NULL; + const uint8_t* bytes; + size_t n = 0; + uint8_t* copy = NULL; + + build_short_import(shim, machine); + + l = link_new(c); + if (!l) return NULL; + link_add_obj(l, prog); + (void)link_add_obj_bytes(l, "ExitProcess.lib-member", shim, SHIM_TOTAL_LEN); + link_set_entry(l, KIT_SLICE_LIT("mainCRTStartup")); + link_set_pie(l, 1); + link_set_emit_static_exe(l, 1); + + img = link_resolve(l); + if (!img) { + link_free(l); + return NULL; + } + if (kit_writer_mem(&g_heap, &w) != KIT_OK || !w) { + link_image_free(img); + link_free(l); + return NULL; + } + link_emit_image_writer(img, w); + bytes = kit_writer_mem_bytes(w, &n); + if (bytes && n) { + copy = (uint8_t*)malloc(n); + if (copy) memcpy(copy, bytes, n); + } + *out_len = n; + kit_writer_close(w); + link_image_free(img); + link_free(l); + return copy; +} + +/* ---- the round-trip assertions ------------------------------------ */ + +static void run_case(const char* name, KitArchKind arch, uint16_t machine) { + Compiler* c; + uint8_t* pe; + size_t pe_len = 0; + KitTargetSpec t; + KitObjFile* f = NULL; + KitSlice input; + KitObjImageInfo info; + KitStatus st; + + g_case = name; + target_windows(&t, arch); + c = make_compiler(&t); + if (!c) { + EXPECT(0, "make_compiler failed"); + return; + } + if (setjmp(c->panic)) { + EXPECT(0, "panic while linking PE"); + compiler_run_cleanups(c); + free_compiler(c); + return; + } + pe = link_pe(c, arch, machine, &pe_len); + free_compiler(c); + if (!pe || !pe_len) { + EXPECT(0, "link_pe produced no bytes"); + free(pe); + return; + } + + /* Detection should route the image to COFF/Windows. */ + EXPECT(kit_detect_fmt(pe, pe_len) == KIT_BIN_PE, "detect_fmt != KIT_BIN_PE"); + + input.data = pe; + input.len = pe_len; + st = kit_obj_open(&g_ctx, KIT_SLICE_LIT("image.exe"), &input, &f); + EXPECT(st == KIT_OK && f, "kit_obj_open failed (st=%d)", (int)st); + if (!f) { + free(pe); + return; + } + + EXPECT(kit_obj_kind(f) == KIT_OBJ_KIND_EXEC, "kind != EXEC (%d)", + (int)kit_obj_kind(f)); + + st = kit_obj_image_info(f, &info); + EXPECT(st == KIT_OK, "image_info failed"); + EXPECT(info.image_base != 0, "image_base == 0"); + EXPECT(info.entry > info.image_base, "entry (%llu) not above base (%llu)", + (unsigned long long)info.entry, (unsigned long long)info.image_base); + + /* Sections + a .text section. */ + { + KitObjSection idx; + EXPECT(kit_obj_nsections(f) > 0, "no sections"); + EXPECT(kit_obj_section_by_name(f, KIT_SLICE_LIT(".text"), &idx) == KIT_OK, + ".text section not found"); + } + + /* Segments: at least one, with an executable one present. */ + { + KitObjSegIter* it = NULL; + KitObjSegInfo seg; + int nseg = 0, nexec = 0; + EXPECT(kit_obj_segiter_new(f, &it) == KIT_OK, "segiter_new failed"); + while (it && kit_obj_segiter_next(it, &seg) == KIT_ITER_ITEM) { + ++nseg; + if (seg.perms & KIT_SEG_X) ++nexec; + EXPECT(seg.vaddr >= info.image_base, "segment vaddr below image base"); + } + kit_obj_segiter_free(it); + EXPECT(nseg > 0, "no segments"); + EXPECT(nexec > 0, "no executable segment"); + } + + /* Dependency KERNEL32.dll carrying the ExitProcess import. */ + { + KitObjDepIter* it = NULL; + KitObjDepInfo dep; + int found_dll = 0, found_imp = 0; + EXPECT(kit_obj_depiter_new(f, &it) == KIT_OK, "depiter_new failed"); + while (it && kit_obj_depiter_next(it, &dep) == KIT_ITER_ITEM) { + if (kit_slice_eq_cstr(dep.name, SHIM_DLL_CSTR)) { + found_dll = 1; + for (uint32_t i = 0; i < dep.nimports; ++i) + if (kit_slice_eq_cstr(dep.imports[i], SHIM_SYM_CSTR)) found_imp = 1; + } + } + kit_obj_depiter_free(it); + EXPECT(found_dll, "KERNEL32.dll dependency not found"); + EXPECT(found_imp, "ExitProcess import not listed under KERNEL32.dll"); + } + + /* Dynamic symbol ExitProcess (undefined import). */ + { + KitObjSymIter* it = NULL; + KitObjSymInfo sym; + int found = 0; + EXPECT(kit_obj_dynsymiter_new(f, &it) == KIT_OK, "dynsymiter_new failed"); + while (it && kit_obj_symiter_next(it, &sym) == KIT_ITER_ITEM) + if (kit_slice_eq_cstr(sym.name, SHIM_SYM_CSTR)) found = 1; + kit_obj_symiter_free(it); + EXPECT(found, "ExitProcess not in dynamic symbols"); + } + + /* Raw escape hatch: 16 data dirs + subsystem + dllchars; IMPORT set. */ + { + KitObjImageRawIter* it = NULL; + KitObjImageRaw r; + int ndatadir = 0, have_subsys = 0, have_dllchars = 0; + uint64_t import_rva = 0; + EXPECT(kit_obj_image_rawiter_new(f, &it) == KIT_OK, "rawiter_new failed"); + while (it && kit_obj_image_rawiter_next(it, &r) == KIT_ITER_ITEM) { + if (r.tag < 16) { + ++ndatadir; + if (r.tag == 1) import_rva = r.value; /* IMAGE_DIRECTORY_ENTRY_IMPORT */ + } else if (r.tag == KIT_OBJ_RAW_PE_SUBSYSTEM) { + have_subsys = 1; + EXPECT(r.value == 3, "subsystem != WINDOWS_CUI (%llu)", + (unsigned long long)r.value); + } else if (r.tag == KIT_OBJ_RAW_PE_DLLCHARS) { + have_dllchars = 1; + } + } + kit_obj_image_rawiter_free(it); + EXPECT(ndatadir == 16, "expected 16 data directories, saw %d", ndatadir); + EXPECT(have_subsys, "subsystem raw entry missing"); + EXPECT(have_dllchars, "dllcharacteristics raw entry missing"); + EXPECT(import_rva != 0, "IMPORT data directory RVA is zero"); + } + + /* Base relocations: the PIE .data absolute pointer needs at least one. */ + { + KitObjRelocIter* it = NULL; + KitObjReloc rel; + int n = 0; + EXPECT(kit_obj_dynreliter_new(f, &it) == KIT_OK, "dynreliter_new failed"); + while (it && kit_obj_reliter_next(it, &rel) == KIT_ITER_ITEM) ++n; + kit_obj_reliter_free(it); + EXPECT(n > 0, "no base relocations for PIE image"); + } + + kit_obj_free(f); + free(pe); +} + +int main(int argc, char** argv) { + memset(&g_ctx, 0, sizeof g_ctx); + g_ctx.heap = &g_heap; + g_ctx.diag = &g_diag; + g_ctx.now = -1; + + /* Optional: regenerate the committed x86_64 PE objdump fixture (no + * asserts). Used to produce test/objdump/x86_64-windows/cases/pe-image.exe + * from this same in-memory link, so the non-gated objdump golden is + * reproducible. */ + if (argc > 1) { + KitTargetSpec t; + Compiler* c; + target_windows(&t, KIT_ARCH_X86_64); + c = make_compiler(&t); + if (c && setjmp(c->panic) == 0) { + size_t n = 0; + uint8_t* pe = link_pe(c, KIT_ARCH_X86_64, 0x8664u, &n); + if (pe && n) { + FILE* fp = fopen(argv[1], "wb"); + if (fp) { + fwrite(pe, 1, n, fp); + fclose(fp); + } + fprintf(stderr, "wrote %zu bytes to %s\n", n, argv[1]); + } + free(pe); + } + free_compiler(c); + return 0; + } + + run_case("x86_64-windows", KIT_ARCH_X86_64, 0x8664u); + run_case("aarch64-windows", KIT_ARCH_ARM_64, 0xAA64u); + + if (g_failures) { + fprintf(stderr, "FAILED %d assertion(s)\n", g_failures); + return 1; + } + fprintf(stderr, "OK pe-image-read\n"); + return 0; +} diff --git a/test/objdump/x86_64-windows/cases/04-pe-sections.sh b/test/objdump/x86_64-windows/cases/04-pe-sections.sh @@ -1,7 +1,7 @@ -# Golden: objdump -h on a linked PE32+ executable. Asserts the PE -# section walker fires (since kit_obj_open does not parse PE -# images) and produces a section table with the canonical headers. -# Gated on llvm-mingw UCRT availability. +# Golden: objdump -h on a linked PE32+ executable. PE images now open +# through kit_obj_open, so -h flows through the neutral dump_sections path +# (the same "Sections:" header as ELF / Mach-O / COFF .obj), with .text and +# .idata present. Gated on llvm-mingw UCRT availability. find_sdk() { local d @@ -28,7 +28,7 @@ int main(void) { return 0; } EOF "$KIT" cc -target x86_64-windows --sysroot "$SDK" t.c -o t.exe "$KIT" objdump -h t.exe | awk ' -/Sections \(PE image\)/ {print "found: PE sections"; next} -/^Idx Name.*VMA.*Size/ {print "found: PE header row"; next} +/^Sections:/ {print "found: PE sections"; next} +/^Idx Name.*Size.*Align.*Flags/ {print "found: PE header row"; next} /^ *[0-9]+ \.text/ {print "section: .text"; next} /^ *[0-9]+ \.idata/ {print "section: .idata"; next}' diff --git a/test/objdump/x86_64-windows/cases/pe-image.exe b/test/objdump/x86_64-windows/cases/pe-image.exe Binary files differ. diff --git a/test/objdump/x86_64-windows/cases/pe-image.expected b/test/objdump/x86_64-windows/cases/pe-image.expected @@ -0,0 +1,48 @@ +pe-image.exe: file format coff64-x86_64 + +architecture: x86_64, flags 0x00000102: +EXEC_P, D_PAGED +start address 0x0000000140001000 +format: coff64, sections: 6, symbols: 0 + +subsystem: 3 (WINDOWS_CUI) + +Sections: +Idx Name Size Align Flags + 0 .text 00000008 2**0 CONTENTS,ALLOC,LOAD,READONLY,CODE + Characteristics: 0x60000020 + 1 .rdata 00000010 2**0 CONTENTS,ALLOC,LOAD,READONLY + Characteristics: 0x40000040 + 2 .idata 00000065 2**0 CONTENTS,ALLOC,LOAD,READONLY + Characteristics: 0x40000040 + 3 .data 00000008 2**0 CONTENTS,ALLOC,LOAD,DATA + Characteristics: 0xc0000040 + 4 .reloc 0000000c 2**0 CONTENTS,ALLOC,LOAD,READONLY,DISCARDABLE + Characteristics: 0x42000040 + +DYNAMIC SYMBOL TABLE: +0000000000000000 g n *UND* 0000000000000000 ExitProcess + + +pe-image.exe: PE32+ private headers + Magic: 0x20b (PE32+) + Machine: x86_64 + ImageBase: 0x140000000 + AddressOfEntryPoint: 0x1000 + Subsystem: 3 (WINDOWS_CUI) + DllCharacteristics: 0x8160 + +Data Directories: + Idx Name RVA Size + 1 IMPORT 0x00003000 0x00000028 + 5 BASERELOC 0x00005000 0x0000000c + 12 IAT 0x00003038 0x00000010 + +The Import Tables: + DLL Name: KERNEL32.dll + Name: ExitProcess + +DYNAMIC RELOCATION RECORDS +OFFSET TYPE VALUE +0000000140004000 X64_RELATIVE *ABS* + diff --git a/test/objdump/x86_64-windows/cases/pe-image.sh b/test/objdump/x86_64-windows/cases/pe-image.sh @@ -0,0 +1,14 @@ +# Golden: -f / -h / -p / -T / -R over a committed freestanding x86_64 PE32+ +# executable (pe-image.exe). Exercises the COFF/PE *image* reader and the +# raw-fields escape hatch end to end through the neutral objdump path: +# -f EXEC_P / D_PAGED flags, real entry point, Windows subsystem +# -h the PE section table (CONTENTS/ALLOC/CODE/... + raw Characteristics) +# -p PE32+ optional-header highlights, data directories, import tables +# -T the dynamic symbol table (the imported ExitProcess) +# -R the base-relocation records (X64_RELATIVE for the PIE .data pointer) +# +# Committed as a binary (like test/objdump/aarch64/exec.elf) so the golden is +# stable and decoupled from the code generator. Needs no external toolchain. +# Regenerate with: build/test/pe-image-read <path> (see test/coff/pe-image-read.c). +cp "$(dirname "$0")/pe-image.exe" pe-image.exe +"$KIT" objdump -f -h -p -T -R pe-image.exe