From c480e76433f9bb8ce8ce9d5802cdada8883db184 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 12 Jul 2026 01:31:26 +0000 Subject: 🌭 kernel/: болваночное ядро (пишет в 0xB8000) + загрузчик Plan9 a.out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - kernel.c + kern.s: 386-ядро, пишет строку в VGA-текстбуфер 0xB8000, halt. Собирается 8a+8c+8l в Plan9 a.out (I_MAGIC, entry 0x100000). - loader.c: загрузчик a.out (не ELF — выхлоп у нас a.out): разбор big-endian заголовка, загрузка сегментов по entry, зануление bss, прыжок. - проверено: kernel = валидный a.out с 0xB8000 в коде; loader.aout_parse корректно разбирает реальное ядро (entry/размеры сходятся). - bin/: добавлены 8a, 8l (386 ассемблер + линкер). Co-Authored-By: Claude Opus 4.8 --- kernel/README.md | 40 +++++++++++++++++++++++++++++++++ kernel/kern.s | 6 +++++ kernel/kernel.c | 20 +++++++++++++++++ kernel/loader.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/loader_test.c | 12 ++++++++++ kernel/mkboot.sh | 25 +++++++++++++++++++++ 6 files changed, 166 insertions(+) create mode 100644 kernel/README.md create mode 100644 kernel/kern.s create mode 100644 kernel/kernel.c create mode 100644 kernel/loader.c create mode 100644 kernel/loader_test.c create mode 100644 kernel/mkboot.sh (limited to 'kernel') 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 +#include +#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" -- cgit v1.2.3