Skip to content

8l: Windows 386 PE that prints via kernel32 (-H 10)#7

Open
rafael2knokia wants to merge 2 commits into
aryx:masterfrom
rafael2knokia:windows-smoke
Open

8l: Windows 386 PE that prints via kernel32 (-H 10)#7
rafael2knokia wants to merge 2 commits into
aryx:masterfrom
rafael2knokia:windows-smoke

Conversation

@rafael2knokia

@rafael2knokia rafael2knokia commented Jul 8, 2026

Copy link
Copy Markdown

Summary

  • 386 PE (8l_ -H 10) now prints to the console instead of just returning.
  • Wires 8l's pass order and makes hello_windows_386.s call GetStdHandle + WriteFile + ExitProcess using the shared PE import machinery.

Details

  • src/cmd/8l/obj.c: call dope() before reloc() so the __imp_* import symbols resolve; force -s for -H 10 (avoids a .symdat section that would perturb the import layout; matches 6l).
  • hello_windows_386.s: prints via the Win32 __stdcall convention. The arg area is reserved with SUBL/MOVL rather than PUSHL because 8l statically balances PUSH/POP and can't see the callee pop. Thunks are called indirectly (MOVL __imp_WriteFile(SB), AX; CALL AX).
  • mkfile: build with 8l_ -s -H 10, enabled in WINDOWSBINS alongside amd64.

Stacking (please read)

This PR is stacked on top of #8 and, because GitHub cross-fork PRs can't use a fork branch as base, its diff also contains #8's commit (6l: PE32+ output for Windows amd64). The 386 printing reuses the shared kernel32 import machinery that #8 introduces in src/cmd/ld/pe.c. Please review/merge #8 first; then this PR reduces to just the 8l commit. (Happy to squash both into one PR if you prefer.)

Test plan

  • 8l_ -s -H 10 builds a PE32 executable (console) Intel 80386.
  • Runs under wine: prints Hello, World!, exits 0.
  • mk all in tests/s/mini passes; plan9 386 (-H 2) path unaffected.

Wire the PE writer into 6l so it can produce 64-bit Windows executables
that actually do I/O, the amd64 counterpart to the 386 support on 8l.

src/cmd/ld/pe.{c,h}: add the PE32+ path.
- New IMAGE_OPTIONAL_HEADER64 (ImageBase and the stack/heap sizes widen
  to 8 bytes, BaseOfData dropped; written verbatim like the 32-bit
  header). peinit() already set pe64 for thechar=='6'; asmbpe() now
  emits Magic 0x20b, MajorSubsystem/OS 6.0 (64-bit Windows predates
  4.0), LARGE_ADDRESS_AWARE, and fills oh64 from oh.
- The import table now imports GetStdHandle, WriteFile and ExitProcess
  from kernel32. peimports() (run from dope(), before reloc()) lays out
  the .idata section and defines an __imp_<name> linker symbol at each
  import's IAT slot, so a program can call through the thunk with
  "MOVQ __imp_WriteFile(SB), AX; CALL AX". IAT entries and the null
  terminator are 8 bytes wide under pe64.
- add_import_table() now only writes the bytes (layout done earlier),
  and writes them at the section's PointerToRawData rather than the
  current EOF (with non-empty .data the file position is not
  page-aligned, which left the image "truncated").
- dope() skips zero-size .data/.bss: an empty section does not advance
  the virtual-address cursor, so the next section would collide on
  VirtualAddress and the loader rejects the image.

src/cmd/6l: compile pe.c (symlink + mkfile), add "-H 10" / goos=windows
handling in obj.c (peinit + case 10, defaulting INITTEXT=PEBASE+0x1000
like 8l), call dope() before reloc() so the __imp_* symbols resolve, and
dispatch asmbpe() from asmb(). 6l has no PE symbol-table path yet, so
obj.c forces -s for -H 10.

tests/s/mini: hello_windows_amd64.s prints "Hello, World!" via
GetStdHandle + WriteFile + ExitProcess (Win64 ABI: args in CX/DX/R8/R9,
32-byte shadow space, 16-byte aligned stack), built with 6l -H 10.

Verified on Linux with wine: `6l -s -H 10` produces a
"PE32+ executable (console) x86-64" (Magic 0x20b, ImageBase 0x400000)
that prints "Hello, World!" and exits 0.
Builds on the shared PE import machinery added for amd64: hook 8l up so a
386 PE can call into kernel32 and print, the counterpart to the 6l
amd64 support.

src/cmd/8l/obj.c: call dope() before reloc() (not after) so the
__imp_<name> import symbols defined while laying out .idata resolve
during relocation; force -s for -H 10 (asmbpe() would otherwise emit a
.symdat section after .idata and perturb the layout; matches 6l).

tests/s/mini: hello_windows_386.s now prints "Hello, World!" via
GetStdHandle + WriteFile + ExitProcess instead of just returning. It
uses the Win32 __stdcall convention (args on the stack right-to-left,
callee pops them). The argument area is reserved with SUBL and filled
with MOVL rather than PUSHL: 8l statically balances PUSH/POP within a
function and cannot see that the callee pops, so PUSHL trips
"unbalanced PUSH/POP". The import thunk is called indirectly
(MOVL __imp_WriteFile(SB), AX; CALL AX) because a direct
"CALL sym(SB)" is rejected by patch() for non-text targets.

mkfile: build hello_windows_386.exe with 8l_ -s -H 10 and enable it in
WINDOWSBINS alongside amd64.

Verified on Linux with wine: `8l_ -s -H 10` produces a
"PE32 executable (console) Intel 80386" that prints "Hello, World!"
and exits 0.
@rafael2knokia rafael2knokia changed the title tests: enable hello_windows_386.exe smoke test (8l_ -H 10 + wine) 8l: Windows 386 PE that prints via kernel32 (-H 10) Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant