diff options
| author | claude <[email protected]> | 2026-07-12 01:19:32 +0000 |
|---|---|---|
| committer | claude <[email protected]> | 2026-07-12 01:19:32 +0000 |
| commit | a4dce7f4d920e492d38c32ad3e42efdcfef3b3f1 (patch) | |
| tree | b0a0ad513792f5c0ddc524d67f6ab89e2044c642 /efi | |
| download | plan9-kencc-linux-a4dce7f4d920e492d38c32ad3e42efdcfef3b3f1.tar.gz plan9-kencc-linux-a4dce7f4d920e492d38c32ad3e42efdcfef3b3f1.zip | |
🌭 plan9-kencc-linux: кросс-тулчейн Plan9 (cc/as/ld) + EFI-болванка
- 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 <[email protected]>
Diffstat (limited to 'efi')
| -rw-r--r-- | efi/README.md | 35 | ||||
| -rw-r--r-- | efi/efi_build.sh | 32 | ||||
| -rw-r--r-- | efi/fetch_ref.sh | 24 | ||||
| -rw-r--r-- | efi/hello.c | 52 | ||||
| -rw-r--r-- | efi/mkesp.py | 108 |
5 files changed, 251 insertions, 0 deletions
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` (плановские `<u.h>` и т.д.) | +| 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 <u.h> +#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('<H',bs,11,BPS); bs[13]=SPC +struct.pack_into('<H',bs,14,RSVD); bs[16]=NFAT +struct.pack_into('<H',bs,17,ROOTENT); struct.pack_into('<H',bs,19,TOT) +bs[21]=0xF8; struct.pack_into('<H',bs,22,FATSZ) +struct.pack_into('<H',bs,24,32); struct.pack_into('<H',bs,26,8) +bs[36]=0x80; bs[38]=0x29; struct.pack_into('<I',bs,39,0x12345678) +bs[43:54]=b'PLAN9 EFI '; bs[54:62]=b'FAT16 ' +bs[510]=0x55; bs[511]=0xAA + +# --- распределение кластеров --- +def nclus_for(n): return max(1,(n+BPS*SPC-1)//(BPS*SPC)) +# EFI(dir)=cl2, BOOT(dir)=cl3, файл=cl4.. +efi_cl=2; boot_cl=3; file_cl=4 +file_ncl=nclus_for(len(data)) +chain={efi_cl:[0xFFFF], boot_cl:[0xFFFF]} +file_clusters=list(range(file_cl,file_cl+file_ncl)) + +FAT=[0]*(nclus+2) +FAT[0]=0xFFF8; FAT[1]=0xFFFF +FAT[efi_cl]=0xFFFF; FAT[boot_cl]=0xFFFF +for i,c in enumerate(file_clusters): + FAT[c]=0xFFFF if i==len(file_clusters)-1 else file_clusters[i+1] + +def wfat(): + raw=bytearray(FATSZ*BPS) + for i,v in enumerate(FAT): + if i*2+2<=len(raw): struct.pack_into('<H',raw,i*2,v&0xFFFF) + for k in range(NFAT): + off=(RSVD+k*FATSZ)*BPS; img[off:off+len(raw)]=raw +wfat() + +def dirent(name,ext,attr,clus,size): + e=bytearray(32) + e[0:11]=(name.ljust(8)[:8]+ext.ljust(3)[:3]).encode('ascii').upper() + e[11]=attr + struct.pack_into('<H',e,26,clus&0xFFFF) + struct.pack_into('<I',e,28,size) + return e + +def clus_off(c): return (first_data+(c-2)*SPC)*BPS + +# root dir: EFI (dir -> 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('<H',im,(RSVD*BPS)+c*2)[0] +def readdir(off): + ents={} + for i in range(0,BPS,32): + e=im[off+i:off+i+32] + if e[0] in (0,0xE5): continue + nm=e[0:8].decode().strip(); ex=e[8:11].decode().strip() + cl=struct.unpack_from('<H',e,26)[0]; sz=struct.unpack_from('<I',e,28)[0] + key=nm+('.'+ex if ex else '') + ents[key]=(cl,sz,e[11]) + return ents +root=readdir((RSVD+NFAT*FATSZ)*BPS) +efi=readdir(clus_off(root['EFI'][0])) +boot=readdir(clus_off(efi['BOOT'][0])) +cl,sz,_=boot['BOOTX64.EFI'] +out=bytearray() +while cl<0xFFF8: + out+=im[clus_off(cl):clus_off(cl)+BPS*SPC]; cl=fat(cl) +out=out[:sz] +ok = bytes(out)==data +print(f"ESP: {OUT} {len(im)} байт (FAT16, {nclus} кластеров, FATSz={FATSZ})") +print(f"путь /EFI/BOOT/BOOTX64.EFI: {sz} байт") +print("самопроверка (обход FAT == оригинал):", "OK" if ok else "FAIL") |
