summaryrefslogtreecommitdiff
path: root/efi
diff options
context:
space:
mode:
authorclaude <[email protected]>2026-07-12 01:19:32 +0000
committerclaude <[email protected]>2026-07-12 01:19:32 +0000
commita4dce7f4d920e492d38c32ad3e42efdcfef3b3f1 (patch)
treeb0a0ad513792f5c0ddc524d67f6ab89e2044c642 /efi
downloadplan9-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.md35
-rw-r--r--efi/efi_build.sh32
-rw-r--r--efi/fetch_ref.sh24
-rw-r--r--efi/hello.c52
-rw-r--r--efi/mkesp.py108
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")