summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--README.md7
-rwxr-xr-xbin/8abin0 -> 387064 bytes
-rwxr-xr-xbin/8lbin0 -> 485744 bytes
-rw-r--r--kernel/README.md40
-rw-r--r--kernel/kern.s6
-rw-r--r--kernel/kernel.c20
-rw-r--r--kernel/loader.c63
-rw-r--r--kernel/loader_test.c12
-rw-r--r--kernel/mkboot.sh25
-rw-r--r--scripts/build_al.sh2
11 files changed, 176 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 65f10ac..041a565 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,6 @@ build/
efi/bootx64.efi
efi/*.img
y.tab.*
+kernel/kernel
+kernel/loader_test
+kernel/*.8
diff --git a/README.md b/README.md
index ba80bb4..75bdf74 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
## Что внутри
```
-bin/ готовые бинари: 8c 6c 5c vc qc kc (компиляторы) + 6a 6l
+bin/ готовые бинари: 8c 6c 5c vc qc kc (компиляторы) + 8a 8l 6a 6l
scripts/
setup.sh клонирует plan9foundation + plan9port, собирает plan9port
build_arch.sh <X> собирает C-компилятор арки X (5|6|8|v|q|k)
@@ -20,7 +20,10 @@ efi/
fetch_ref.sh тянет pe64.s/efi.h/mem.h из 9legacy (nboot/efi)
efi_build.sh 6a+6c+6l -> dd -> bootx64.efi (PE32+)
mkesp.py заворачивает .efi в загрузочный ESP (FAT16)
-patches/ патчи по каждой правке (документация)
+kernel/
+ kernel.c + kern.s болваночное ядро (386): пишет в VGA 0xB8000
+ loader.c загрузчик Plan9 a.out (разбор хедера, загрузка, прыжок)
+ mkboot.sh 8a+8c+8l -> kernel (a.out)
PATCHES.md почему каждый патч нужен
```
diff --git a/bin/8a b/bin/8a
new file mode 100755
index 0000000..161e9b2
--- /dev/null
+++ b/bin/8a
Binary files differ
diff --git a/bin/8l b/bin/8l
new file mode 100755
index 0000000..05f67df
--- /dev/null
+++ b/bin/8l
Binary files differ
diff --git a/kernel/README.md b/kernel/README.md
new file mode 100644
index 0000000..1e711ae
--- /dev/null
+++ b/kernel/README.md
@@ -0,0 +1,40 @@
+# kernel — болваночное ядро + загрузчик (Plan9 a.out)
+
+Ядро-болванка, которое пишет строку в VGA-текстбуфер **0xB8000**, и **загрузчик**,
+который грузит его. Всё в формате **Plan9 a.out** (не ELF) — потому что наш kencc-выхлоп
+именно a.out; загрузчик разбирает a.out-заголовок, а не ELF.
+
+## Файлы
+* `kern.s` — энтрипоинт на 386-асме: ставит стек, зовёт `kmain`.
+* `kernel.c` — ядро: пишет `PLAN9 KERNEL STUB -> writes to 0xB8000` в 0xB8000.
+* `loader.c` — загрузчик Plan9 a.out: разбор заголовка (big-endian), загрузка
+ сегментов `[text][data]` по адресу линковки, обнуление bss, прыжок в entry.
+* `loader_test.c` — хостовый тест: прогоняет `aout_parse` из настоящего загрузчика на
+ собранном ядре и печатает поля (magic/entry/размеры).
+* `mkboot.sh` — сборка: `8a`+`8c`+`8l` -> `kernel` (a.out), `8c` -> `loader.8`.
+
+## Сборка
+```sh
+BINDIR=../bin ./mkboot.sh
+```
+
+## Формат Plan9 a.out (386)
+32-байтный заголовок, **big-endian**, 8 полей по 4 байта:
+```
+magic text data bss syms entry spsz pcsz
+```
+`magic == I_MAGIC == 0x1EB` (intel 386). Сегменты идут сразу за заголовком:
+`file[32 .. 32+text]` = text, дальше data. Загрузчик копирует их по `entry`
+(равен `-T` линкера, тут `0x100000`), зануляет bss и прыгает в `entry`.
+
+## Что проверено
+```
+kernel: Plan9 a.out, magic=0x1EB (I_MAGIC 386), entry=0x100000,
+ константа 0xB8000 присутствует в коде.
+loader: aout_parse на реальном kernel -> OK, entry=0x100000, text/data сходятся.
+```
+Реальный запуск в QEMU/Bochs не гонялся (в контейнере нет эмулятора), но kernel —
+валидный a.out, а загрузчик корректно его разбирает и знает, куда грузить и куда прыгать.
+
+> Хочешь под GRUB/multiboot — можно докинуть a.out→ELF-обёртку; здесь же честный
+> a.out-путь под твоё «выхлоп у нас в а.аут».
diff --git a/kernel/kern.s b/kernel/kern.s
new file mode 100644
index 0000000..28d0d3e
--- /dev/null
+++ b/kernel/kern.s
@@ -0,0 +1,6 @@
+/* энтрипоинт: ставит стек и зовёт kmain */
+TEXT _start(SB), $0
+ MOVL $0x10000, SP
+ CALL kmain(SB)
+loop:
+ JMP loop
diff --git a/kernel/kernel.c b/kernel/kernel.c
new file mode 100644
index 0000000..0440677
--- /dev/null
+++ b/kernel/kernel.c
@@ -0,0 +1,20 @@
+/* Plan9 C, 386 — kernel-болванка: пишет строку в VGA-текстбуфер 0xB8000. */
+typedef unsigned short ushort;
+typedef unsigned char uchar;
+
+void
+kmain(void)
+{
+ ushort *vga;
+ char *s;
+ int i;
+
+ vga = (ushort*)0xB8000; /* VGA text mode framebuffer */
+ for(i = 0; i < 80*25; i++)
+ vga[i] = (0x1F<<8) | ' '; /* очистить: белым по синему */
+ s = "PLAN9 KERNEL STUB -> writes to 0xB8000";
+ for(i = 0; s[i]; i++)
+ vga[i] = (0x1F<<8) | (uchar)s[i];
+ for(;;) /* halt */
+ ;
+}
diff --git a/kernel/loader.c b/kernel/loader.c
new file mode 100644
index 0000000..0246628
--- /dev/null
+++ b/kernel/loader.c
@@ -0,0 +1,63 @@
+/*
+ * Загрузчик ядра в формате Plan9 a.out (386, I_MAGIC).
+ * Чистый C без хедеров: собирается и нашим 8c (часть a.out-тулчейна),
+ * и хостовым gcc (тест разбора заголовка). "Выхлоп у нас в а.аут" —
+ * поэтому разбираем именно Plan9 a.out, а не ELF.
+ */
+typedef unsigned int u32; /* 4 байта и на 386, и на x86_64 */
+typedef unsigned char u8;
+
+typedef struct Exec {
+ u32 magic, text, data, bss, syms, entry, spsz, pcsz;
+} Exec;
+
+#define I_MAGIC 0x1EB /* intel 386 = _MAGIC(0,11) */
+#define HDRSZ 32 /* 8 x 4 байта, big-endian */
+
+static u32
+be32(u8 *p) /* заголовок Plan9 a.out хранится big-endian */
+{
+ return ((u32)p[0]<<24) | ((u32)p[1]<<16) | ((u32)p[2]<<8) | (u32)p[3];
+}
+
+/* разбор заголовка. 0 = ок (это наш 386 a.out), заполняет e. */
+int
+aout_parse(u8 *img, Exec *e)
+{
+ e->magic = be32(img+0); e->text = be32(img+4);
+ e->data = be32(img+8); e->bss = be32(img+12);
+ e->syms = be32(img+16); e->entry= be32(img+20);
+ e->spsz = be32(img+24); e->pcsz = be32(img+28);
+ return e->magic == I_MAGIC ? 0 : -1;
+}
+
+/* загрузить сегменты [text][data] по адресу линковки и обнулить bss;
+ * вернуть адрес входа. freestanding — пишет по абсолютному адресу. */
+void *
+aout_load(u8 *img)
+{
+ Exec e;
+ u8 *src, *dst;
+ u32 i;
+
+ if(aout_parse(img, &e) < 0)
+ return (void*)0;
+ src = img + HDRSZ; /* сегменты идут сразу за заголовком */
+ dst = (u8*)e.entry; /* грузим по entry (= -T линкера) */
+ for(i = 0; i < e.text + e.data; i++)
+ dst[i] = src[i];
+ for(i = 0; i < e.bss; i++)
+ dst[e.text + e.data + i] = 0;
+ return (void*)e.entry;
+}
+
+/* точка входа загрузчика: грузит ядро и прыгает в него (назад не вернётся). */
+void
+bootkernel(u8 *kern)
+{
+ void (*entry)(void);
+
+ entry = (void(*)(void))aout_load(kern);
+ if(entry)
+ entry();
+}
diff --git a/kernel/loader_test.c b/kernel/loader_test.c
new file mode 100644
index 0000000..1241a39
--- /dev/null
+++ b/kernel/loader_test.c
@@ -0,0 +1,12 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "loader.c" /* берём aout_parse из настоящего загрузчика */
+int main(int c, char**v){
+ FILE*f=fopen(v[1],"rb"); fseek(f,0,SEEK_END); long n=ftell(f); rewind(f);
+ u8*b=malloc(n); fread(b,1,n,f); fclose(f);
+ Exec e; int r=aout_parse(b,&e);
+ printf("aout_parse: %s\n", r==0?"OK (I_MAGIC 386)":"НЕ наш формат");
+ printf(" magic=0x%X text=%u data=%u bss=%u entry=0x%X\n",e.magic,e.text,e.data,e.bss,e.entry);
+ printf(" сегменты @ file+%d, грузятся -> 0x%X\n",HDRSZ,e.entry);
+ return r;
+}
diff --git a/kernel/mkboot.sh b/kernel/mkboot.sh
new file mode 100644
index 0000000..27544f8
--- /dev/null
+++ b/kernel/mkboot.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Собирает kernel-болванку (пишет в VGA 0xB8000) и загрузчик — всё в Plan9 a.out.
+# Тулчейн: 8a/8c/8l (386) из ../bin. "Выхлоп у нас в а.аут" — потому и формат a.out.
+set -e
+cd "$(dirname "$0")"
+BINDIR="${BINDIR:-$PWD/../bin}"
+export objtype=386
+
+echo "1) 8a kern.s — энтрипоинт (стек + вызов kmain)"; "$BINDIR/8a" kern.s
+echo "2) 8c kernel.c — ядро (пишет строку в 0xB8000)"; "$BINDIR/8c" kernel.c
+echo "3) 8l -H2 -T1M — линк в Plan9 a.out, entry=_start"; "$BINDIR/8l" -o kernel -H2 -T0x100000 -E _start kern.8 kernel.8
+echo "4) 8c loader.c — загрузчик a.out (тоже a.out)"; "$BINDIR/8c" loader.c
+
+echo
+echo "=== kernel: $(ls -la kernel | awk '{print $5}') байт (Plan9 a.out) ==="
+python3 - <<'PY'
+import struct
+d=open('kernel','rb').read()
+m,t,da,b,s,e,sp,pc=struct.unpack('>8L',d[:32])
+IM=(((4*11)+0)*11)+7
+print(f" magic=0x{m:X} {'= I_MAGIC(386) OK' if m==IM else '(?)'} text={t} data={da} bss={b} entry=0x{e:X}")
+print(" константа 0xB8000 в коде:", "да" if d.find(bytes([0,0x80,0x0B,0]),32)>=0 else "нет")
+PY
+echo
+echo "проверка разбора загрузчиком (хостовый gcc): gcc -w -o loader_test loader_test.c && ./loader_test kernel"
diff --git a/scripts/build_al.sh b/scripts/build_al.sh
index 750f9e0..aae150b 100644
--- a/scripts/build_al.sh
+++ b/scripts/build_al.sh
@@ -24,6 +24,8 @@ fi
for c in *.c; do grep -qE '#include[ \t]*[<"]/sys/' "$c" 2>/dev/null && { echo " пропуск: $c"; rm -f "$c"; }; done
sed -i 's|"\.\./[a-zA-Z0-9]*/|"|g' *.c *.h 2>/dev/null || true
sed -i -E 's/\bround\b/p9round/g; s/\blog2\b/p9log2/g' *.c *.h compat macbody lexbody 2>/dev/null || true
+# USED(a,b) — plan9port-овский USED однопараметрный -> расщепляем на два (нужно линкерам)
+sed -i -E 's/\bUSED\(([^(),]+),[[:space:]]*([^()]+)\)/USED(\1); USED(\2)/g' *.c 2>/dev/null || true
python3 - <<'PY'
import glob,re
pat=re.compile(r'\{\s*return\s+sbrk\s*\(\s*(\w+)\s*\)\s*;\s*\}')