From a4dce7f4d920e492d38c32ad3e42efdcfef3b3f1 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 12 Jul 2026 01:19:32 +0000 Subject: 🌭 plan9-kencc-linux: кросс-тулчейн Plan9 (cc/as/ld) + EFI-болванка MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build_arch.sh / build_al.sh: сборка kencc-компиляторов, ассемблеров, линкеров всех арок (5/6/8/v/q/k) на Linux-хосте через plan9port. - патчи под musl/gcc: sbrk->mmap, round/log2->p9*, Runemask, swcmp const, -fcommon, machcap/omachcap, /sys рантайм-файлы. Подробно в PATCHES.md. - bin/: готовые 8c 6c 5c vc qc kc + 6a 6l (Linux/musl x86_64). - efi/: болваночный bootx64.efi (PE32+) тем же тулчейном (6a+6c+6l+dd), референс — 9legacy nboot/efi; ESP-образ через mkesp.py. Co-Authored-By: Claude Opus 4.8 --- efi/README.md | 35 ++++++++++++++++++ efi/efi_build.sh | 32 +++++++++++++++++ efi/fetch_ref.sh | 24 +++++++++++++ efi/hello.c | 52 +++++++++++++++++++++++++++ efi/mkesp.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 efi/README.md create mode 100644 efi/efi_build.sh create mode 100644 efi/fetch_ref.sh create mode 100644 efi/hello.c create mode 100644 efi/mkesp.py (limited to 'efi') diff --git a/efi/README.md b/efi/README.md new file mode 100644 index 0000000..fa1cdd1 --- /dev/null +++ b/efi/README.md @@ -0,0 +1,35 @@ +# efi — болваночный UEFI-апп на портированном тулчейне Plan 9 + +Минимальное `bootx64.efi` (amd64, формат **PE32+**), собранное **только** +портированными Plan 9 kencc `6a`/`6c`/`6l` — без юниксовых компиляторов/линкеров. + +## Цепочка +| шаг | утилита | что делает | +|-----|---------|-----------| +| 1 | **6a** | ассемблирует `pe64min.s` — DOS/PE32+ заголовок + entry + `eficall` | +| 2 | **6c** | компилирует `hello.c` (плановские `` и т.д.) | +| 3 | **6l** | линкует свободностоящий образ по `-T0x8000` | +| 4 | `dd skip=40` | срезает 40-байтный plan9 a.out заголовок → обнажает PE32+ | + +`pe64.s` кладёт DOS+PE32+ заголовки первыми в текст-сегмент; линкер раскладывает +образ от `IMAGEBASE`, а `dd` срезает плановский заголовок — остаётся чистый PE, +который прошивка грузит из `/EFI/BOOT/BOOTX64.EFI`. + +## Сборка +```sh +./fetch_ref.sh # pe64.s/efi.h/mem.h из 9legacy +PLAN9SRC=.../plan9foundation ./efi_build.sh # -> bootx64.efi +python3 mkesp.py bootx64.efi esp.img # -> загрузочный ESP (FAT16) +qemu-system-x86_64 -bios OVMF.fd -drive format=raw,file=esp.img +``` + +## Что своё, что референс +* `hello.c`, `efi_build.sh`, `mkesp.py`, `fetch_ref.sh` — наш код. +* `pe64.s`, `efi.h`, `mem.h` — из 9legacy `nboot/efi` (тянутся, не хранятся). + Для болванки от `pe64.s` берём только заголовки+entry+`eficall` (`head -162`); + машинерию загрузки ядра (`jump`/`_warp32`/`_gdt`) отбрасываем — ей тут нечего делать. + +## Проверка +`MZ` + `PE\0\0` + Machine `0x8664` + Magic `0x20B` (PE32+) + Subsystem `10` +(EFI_APPLICATION). `mkesp.py` дополнительно сам обходит FAT обратно и сверяет +`/EFI/BOOT/BOOTX64.EFI` байт-в-байт с исходным `.efi`. diff --git a/efi/efi_build.sh b/efi/efi_build.sh new file mode 100644 index 0000000..51f0ff9 --- /dev/null +++ b/efi/efi_build.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Собирает болваночный bootx64.efi (PE32+) ПОРТИРОВАННЫМ тулчейном 6a/6c/6l. +# Предварительно: ./fetch_ref.sh (достаёт pe64.s/efi.h/mem.h из 9legacy). +# Требует: BINDIR с 6a/6c/6l (по умолч. ../bin) и PLAN9SRC (плановские include). +set -e +cd "$(dirname "$0")" +BINDIR="${BINDIR:-$PWD/../bin}" +: "${PLAN9SRC:?export PLAN9SRC — нужны плановские include (amd64/include, sys/include)}" +IMAGEBASE=0x8000 +export objtype=amd64 + +[ -f pe64.s ] || { echo "нет pe64.s — сначала ./fetch_ref.sh"; exit 1; } +# минимальный pe64: MZ+PE заголовки + entry + eficall (без загрузчика ядра) +head -162 pe64.s > pe64min.s + +rm -f *.6 *.out bootx64.efi +echo "1) 6a pe64min.s"; "$BINDIR/6a" -DIMAGEBASE=$IMAGEBASE pe64min.s +echo "2) 6c hello.c"; "$BINDIR/6c" -I"$PLAN9SRC/amd64/include" -I"$PLAN9SRC/sys/include" hello.c +echo "3) 6l link"; "$BINDIR/6l" -l -s -R1 -T$IMAGEBASE -o bootx64.out pe64min.6 hello.6 +echo "4) dd strip 40b (plan9 a.out header) -> PE32+" +dd if=bootx64.out bs=1 skip=40 of=bootx64.efi 2>/dev/null + +echo "bootx64.efi: $(ls -la bootx64.efi | awk '{print $5}') байт" +python3 - <<'PY' +d=open('bootx64.efi','rb').read(); po=int.from_bytes(d[0x3c:0x40],'little'); o=po+24 +ok = d[:2]==b'MZ' and d[po:po+4]==b'PE\x00\x00' \ + and int.from_bytes(d[po+4:po+6],'little')==0x8664 \ + and int.from_bytes(d[o:o+2],'little')==0x20b \ + and int.from_bytes(d[o+68:o+70],'little')==10 +print("PE32+ x64 EFI_APPLICATION:", "OK" if ok else "BAD") +PY +echo "ESP-образ: python3 mkesp.py bootx64.efi esp.img" diff --git a/efi/fetch_ref.sh b/efi/fetch_ref.sh new file mode 100644 index 0000000..67b726b --- /dev/null +++ b/efi/fetch_ref.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Тянет из 9legacy референсный EFI-загрузчик (nboot/efi) и достаёт из него +# pe64.s (PE32+ обёртка), efi.h и mem.h. Это НЕ наш код — это Plan9/9legacy, +# поэтому качаем, а не храним в репе. Наш код — hello.c/efi_build.sh/mkesp.py. +set -e +cd "$(dirname "$0")" +echo ">> качаю 9legacy nboot.diff ..." +python3 -c "import urllib.request; urllib.request.urlretrieve('http://9legacy.org/9legacy/patch/nboot.diff','nboot.diff')" +python3 - <<'PY' +import re +d=open('nboot.diff').read().splitlines() +want={'pe64.s','efi.h','mem.h'}; cur=None; buf=[]; out={} +for l in d: + m=re.match(r'^\+\+\+ .*/nboot/efi/(\S+)', l) + if m: + if cur: out[cur]='\n'.join(buf) + cur=m.group(1) if m.group(1) in want else None; buf=[]; continue + if cur is not None: + if l.startswith('--- '): out[cur]='\n'.join(buf); cur=None; continue + if l.startswith('+'): buf.append(l[1:]) +if cur: out[cur]='\n'.join(buf) +for k,v in sorted(out.items()): open(k,'w').write(v); print(' извлёк', k, f'({len(v.splitlines())} строк)') +PY +echo ">> готово: pe64.s efi.h mem.h (референс 9legacy)" diff --git a/efi/hello.c b/efi/hello.c new file mode 100644 index 0000000..35cd86d --- /dev/null +++ b/efi/hello.c @@ -0,0 +1,52 @@ +/* + * Болваночный EFI-апп для amd64 (bootx64.efi), PE32+. + * Собирается ПОРТИРОВАННЫМ тулчейном Plan9 (6c/6a/6l) на Linux/musl: + * PE-заголовок и entry -> pe64.s (ассемблер 6a), из 9legacy nboot/efi. + * Печать -> UEFI SIMPLE_TEXT_OUTPUT_PROTOCOL (efi.h из 9legacy). + * Контракт входа задаёт pe64.s: reloc кладёт SystemTable, прыгает на efimain(ih, st). + */ +#include +#include "efi.h" + +/* глобалы, которых ждёт контракт (как в референсном efi.c) */ +EFI_HANDLE IH; +EFI_SYSTEM_TABLE *ST; + +/* вызов EFI-функции по MS-x64 ABI — реализован в pe64.s */ +extern uintptr eficall(void *fn, ...); + +/* печать ASCII-строки через ConOut (конверт в UTF-16, \n -> \r\n) */ +static void +eputs(char *s) +{ + CHAR16 w[2]; + + w[1] = 0; + for(; *s; s++){ + if(*s == '\n'){ + w[0] = '\r'; + eficall(ST->ConOut->OutputString, ST->ConOut, w); + } + w[0] = (uchar)*s; + eficall(ST->ConOut->OutputString, ST->ConOut, w); + } +} + +void +efimain(EFI_HANDLE ih, EFI_SYSTEM_TABLE *st) +{ + IH = ih; + ST = st; + + eputs("\n"); + eputs("+--------------------------------------------------+\n"); + eputs("| Hello from a Plan 9 kencc EFI application |\n"); + eputs("| compiler/assembler/linker: 6c / 6a / 6l |\n"); + eputs("| cross-built on Linux/musl, wrapped as PE32+ |\n"); + eputs("+--------------------------------------------------+\n"); + eputs("\nefimain reached OK. Idling via BootServices->Stall.\n"); + + /* болванка: просто крутимся, чтобы прошивка не считала нас упавшими */ + for(;;) + eficall(ST->BootServices->Stall, (UINTN)1000000); +} diff --git a/efi/mkesp.py b/efi/mkesp.py new file mode 100644 index 0000000..d9ab581 --- /dev/null +++ b/efi/mkesp.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Собирает загрузочный ESP-образ (FAT16) c /EFI/BOOT/BOOTX64.EFI. +# Без mtools/mkfs — вручную по спецификации FAT, плюс самопроверка (обход обратно). +import struct, sys, os, math + +EFI = sys.argv[1] if len(sys.argv)>1 else "/data/work/chat_-1002129270612/plan9/build/efi/bootx64.efi" +OUT = sys.argv[2] if len(sys.argv)>2 else "/data/work/chat_-1002129270612/plan9/build/efi/esp.img" +data = open(EFI,"rb").read() + +BPS=512; SPC=1; RSVD=1; NFAT=2; ROOTENT=512; TOT=8192 +rootsecs=(ROOTENT*32+BPS-1)//BPS +tmp1=TOT-(RSVD+rootsecs); tmp2=256*SPC+NFAT +FATSZ=(tmp1+tmp2-1)//tmp2 +first_data=RSVD+NFAT*FATSZ+rootsecs +nclus=(TOT-first_data)//SPC +assert nclus>4085, f"стало бы FAT12 ({nclus} кластеров)" + +img=bytearray(TOT*BPS) +# --- boot sector / BPB --- +bs=img +bs[0:3]=b'\xEB\x3C\x90'; bs[3:11]=b'MSWIN4.1' +struct.pack_into(' cl2) +root_off=(RSVD+NFAT*FATSZ)*BPS +img[root_off:root_off+32]=dirent("EFI","",0x10,efi_cl,0) +# EFI dir (cl2): '.', '..', BOOT(dir->cl3) +o=clus_off(efi_cl) +img[o:o+32]=dirent(".","",0x10,efi_cl,0) +img[o+32:o+64]=dirent("..","",0x10,0,0) +img[o+64:o+96]=dirent("BOOT","",0x10,boot_cl,0) +# BOOT dir (cl3): '.', '..', BOOTX64.EFI(file->cl4) +o=clus_off(boot_cl) +img[o:o+32]=dirent(".","",0x10,boot_cl,0) +img[o+32:o+64]=dirent("..","",0x10,efi_cl,0) +img[o+64:o+96]=dirent("BOOTX64","EFI",0x20,file_cl,len(data)) +# данные файла +for i,c in enumerate(file_clusters): + chunk=data[i*BPS*SPC:(i+1)*BPS*SPC] + off=clus_off(c); img[off:off+len(chunk)]=chunk + +open(OUT,"wb").write(img) + +# --- САМОПРОВЕРКА: обойти FAT и достать /EFI/BOOT/BOOTX64.EFI --- +im=open(OUT,"rb").read() +def rd(a,n): return im[a:a+n] +def fat(c): return struct.unpack_from('