From b0bbc0a6142c6703fe1cb24c3da069246a41bfa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= <140026428+AMDRIP@users.noreply.github.com> Date: Sun, 1 Mar 2026 06:12:01 +0400 Subject: [PATCH 1/6] vfs: fix path splitting and open/create safety --- kernel/src/vfs.cpp | 226 ++++++++++++++------------------------------- 1 file changed, 70 insertions(+), 156 deletions(-) diff --git a/kernel/src/vfs.cpp b/kernel/src/vfs.cpp index 0c30dc3..9923cd4 100644 --- a/kernel/src/vfs.cpp +++ b/kernel/src/vfs.cpp @@ -103,6 +103,47 @@ static int extract_path_component(const char* path, int start_idx, char* out_buf return i - start_idx; } +static bool split_parent_and_name(const char* path, char* dir_out, int dir_max, char* name_out, int name_max) { + if (!path || !dir_out || !name_out || dir_max < 2 || name_max < 2) return false; + + int len = 0; + while (path[len]) len++; + + // Trim trailing slashes but preserve root ("/"). + while (len > 1 && path[len - 1] == '/') len--; + + if (len <= 0) return false; + if (len == 1 && path[0] == '/') return false; // root has no name component + + int last_slash = -1; + for (int i = len - 1; i >= 0; i--) { + if (path[i] == '/') { + last_slash = i; + break; + } + } + + if (last_slash <= 0) { + dir_out[0] = '/'; + dir_out[1] = '\0'; + } else { + int di = 0; + while (di < last_slash && di < dir_max - 1) { + dir_out[di] = path[di]; + di++; + } + dir_out[di] = '\0'; + } + + int ni = 0; + for (int i = last_slash + 1; i < len && ni < name_max - 1; i++) { + name_out[ni++] = path[i]; + } + name_out[ni] = '\0'; + + return name_out[0] != '\0'; +} + // Parses paths like /A/B/C and resolves them recursively. int vfs_resolve_path(const char* path, vnode** out) { if (!path || !out) return -1; @@ -158,15 +199,23 @@ int vfs_open(const char* path, int flags, int mode) { // Если файла нет и есть флаг O_CREAT, надо попытаться создать файл if (resolve_res != 0) { if (flags & O_CREAT) { - // Берем корневой узел - if (num_mount_points == 0) return -1; - vnode* root = mount_points[0]->root_vnode; - if (root && root->ops && root->ops->create) { - int i = 0; - if (path[0] == '/') i++; - int cr = root->ops->create(root, path + i, mode, &vn); + char parent_path[256]; + char filename[64]; + if (!split_parent_and_name(path, parent_path, sizeof(parent_path), filename, sizeof(filename))) { + return -1; + } + + vnode* parent = nullptr; + if (vfs_resolve_path(parent_path, &parent) != 0 || !parent) { + return -1; + } + + if (parent->ops && parent->ops->create) { + int cr = parent->ops->create(parent, filename, mode, &vn); + vnode_release(parent); if (cr != 0) return -1; } else { + vnode_release(parent); return -1; } } else { @@ -178,7 +227,10 @@ int vfs_open(const char* path, int flags, int mode) { if (vn->ops && vn->ops->open) { int op_res = vn->ops->open(vn); - if (op_res != 0) return -1; + if (op_res != 0) { + vnode_release(vn); + return -1; + } } // Вместо возвращения FD тут, мы должны вернуть указатель на абстрактную структуру file*, @@ -226,33 +278,8 @@ int vfs_stat(const char* path, vfs_stat_t* out) { // Resolve parent directory and filename char dir_path[256]; char filename[64]; - int len = 0; - while (path[len]) len++; - - int last_slash = -1; - for (int i = len - 1; i >= 0; i--) { - if (path[i] == '/') { - last_slash = i; - break; - } - } - - if (last_slash == -1) { - dir_path[0] = '/'; dir_path[1] = '\0'; - int i; - for (i = 0; path[i] && i < 63; i++) filename[i] = path[i]; - filename[i] = '\0'; - } else { - if (last_slash == 0) { - dir_path[0] = '/'; dir_path[1] = '\0'; - } else { - int i; - for (i = 0; i < last_slash && i < 255; i++) dir_path[i] = path[i]; - dir_path[i] = '\0'; - } - int o = 0; - for (int i = last_slash + 1; path[i] && o < 63; i++) filename[o++] = path[i]; - filename[o] = '\0'; + if (!split_parent_and_name(path, dir_path, sizeof(dir_path), filename, sizeof(filename))) { + return -1; } vnode* parent = nullptr; @@ -271,39 +298,11 @@ int vfs_stat(const char* path, vfs_stat_t* out) { int vfs_unlink(const char* path) { if (!path) return -1; - // Resolve parent directory and filename char dir_path[256]; char filename[64]; - int len = 0; - while (path[len]) len++; - - int last_slash = -1; - for (int i = len - 1; i >= 0; i--) { - if (path[i] == '/') { - last_slash = i; - break; - } - } - - if (last_slash == 0 && path[1] == '\0') { - return -1; // Cannot unlink root - } - - if (last_slash == -1) { - dir_path[0] = '/'; dir_path[1] = '\0'; - int i; - for (i = 0; path[i] && i < 63; i++) filename[i] = path[i]; - filename[i] = '\0'; - } else if (last_slash == 0) { - dir_path[0] = '/'; dir_path[1] = '\0'; - } else { - int i; - for (i = 0; i < last_slash && i < 255; i++) dir_path[i] = path[i]; - dir_path[i] = '\0'; + if (!split_parent_and_name(path, dir_path, sizeof(dir_path), filename, sizeof(filename))) { + return -1; } - int o = 0; - for (int i = last_slash + 1; path[i] && o < 63; i++) filename[o++] = path[i]; - filename[o] = '\0'; vnode* parent = nullptr; if (vfs_resolve_path(dir_path, &parent) != 0 || !parent) return -1; @@ -322,38 +321,10 @@ int vfs_write_file(const char* path, const uint8_t* data, uint32_t size) { if (!path) return -1; if (num_mount_points == 0) return -1; - // Extract directory path and filename char dir_path[256]; char filename[64]; - int len = 0; - while (path[len]) len++; - - int last_slash = -1; - for (int i = len - 1; i >= 0; i--) { - if (path[i] == '/') { - last_slash = i; - break; - } - } - - if (last_slash == -1) { - dir_path[0] = '/'; dir_path[1] = '\0'; - int i; - for (i = 0; path[i] && i < 63; i++) filename[i] = path[i]; - filename[i] = '\0'; - } else { - if (last_slash == 0) { - dir_path[0] = '/'; dir_path[1] = '\0'; - } else { - int i; - for (i = 0; i < last_slash && i < 255; i++) dir_path[i] = path[i]; - dir_path[i] = '\0'; - } - int o = 0; - for (int i = last_slash + 1; path[i] && o < 63; i++) { - filename[o++] = path[i]; - } - filename[o] = '\0'; + if (!split_parent_and_name(path, dir_path, sizeof(dir_path), filename, sizeof(filename))) { + return -1; } vnode* parent = nullptr; @@ -394,39 +365,11 @@ int vfs_mkdir(const char* path, int mode) { if (!path) return -1; if (num_mount_points == 0) return -1; - // Extract directory path and new dir name char dir_path[256]; char dirname[64]; - int len = 0; - while (path[len]) len++; - - int last_slash = -1; - for (int i = len - 1; i >= 0; i--) { - if (path[i] == '/') { - last_slash = i; - break; - } - } - - if (last_slash == 0 && path[1] == '\0') { - return -1; // Cannot mkdir root - } - - if (last_slash == -1) { - dir_path[0] = '/'; dir_path[1] = '\0'; - int i; - for (i = 0; path[i] && i < 63; i++) dirname[i] = path[i]; - dirname[i] = '\0'; - } else if (last_slash == 0) { - dir_path[0] = '/'; dir_path[1] = '\0'; - } else { - int i; - for (i = 0; i < last_slash && i < 255; i++) dir_path[i] = path[i]; - dir_path[i] = '\0'; + if (!split_parent_and_name(path, dir_path, sizeof(dir_path), dirname, sizeof(dirname))) { + return -1; } - int o = 0; - for (int i = last_slash + 1; path[i] && o < 63; i++) dirname[o++] = path[i]; - dirname[o] = '\0'; vnode* parent = nullptr; if (vfs_resolve_path(dir_path, &parent) != 0 || !parent) { @@ -443,46 +386,17 @@ int vfs_mkdir(const char* path, int mode) { return rc; } -static void split_parent_and_name(const char* path, char* dir_out, int dir_max, char* name_out, int name_max) { - int len = 0; - while (path[len]) len++; - - int last_slash = -1; - for (int i = len - 1; i >= 0; i--) { - if (path[i] == '/') { last_slash = i; break; } - } - - if (last_slash == -1) { - dir_out[0] = '/'; dir_out[1] = '\0'; - int i = 0; - while (path[i] && i < name_max - 1) { name_out[i] = path[i]; i++; } - name_out[i] = '\0'; - } else if (last_slash == 0) { - dir_out[0] = '/'; dir_out[1] = '\0'; - int o = 0; - for (int i = 1; path[i] && o < name_max - 1; i++) name_out[o++] = path[i]; - name_out[o] = '\0'; - } else { - int i = 0; - while (i < last_slash && i < dir_max - 1) { dir_out[i] = path[i]; i++; } - dir_out[i] = '\0'; - int o = 0; - for (int j = last_slash + 1; path[j] && o < name_max - 1; j++) name_out[o++] = path[j]; - name_out[o] = '\0'; - } -} - int vfs_rename(const char* oldpath, const char* newpath) { if (!oldpath || !newpath) return -1; if (num_mount_points == 0) return -1; char old_dir_path[256]; char old_filename[64]; - split_parent_and_name(oldpath, old_dir_path, 256, old_filename, 64); + if (!split_parent_and_name(oldpath, old_dir_path, 256, old_filename, 64)) return -1; char new_dir_path[256]; char new_filename[64]; - split_parent_and_name(newpath, new_dir_path, 256, new_filename, 64); + if (!split_parent_and_name(newpath, new_dir_path, 256, new_filename, 64)) return -1; if (old_filename[0] == '\0' || new_filename[0] == '\0') return -1; From bbff97ac940c7d7fdd8a25edda748302f780d20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= <140026428+AMDRIP@users.noreply.github.com> Date: Sun, 1 Mar 2026 06:19:43 +0400 Subject: [PATCH 2/6] docs: add DriverAPI stabilization implementation plan --- docs/DRIVER_API_STABILIZATION.md | 181 +++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 docs/DRIVER_API_STABILIZATION.md diff --git a/docs/DRIVER_API_STABILIZATION.md b/docs/DRIVER_API_STABILIZATION.md new file mode 100644 index 0000000..a283d06 --- /dev/null +++ b/docs/DRIVER_API_STABILIZATION.md @@ -0,0 +1,181 @@ +# DriverAPI — план стабилизации + +> Цель: перевести текущий DriverAPI из режима «доверенной демо-модели» в режим предсказуемого и безопасного ABI для долгоживущих Ring3-драйверов. + +## 1) Текущее состояние (по коду) + +1. User-space `DriverAPI` задаёт только C++-интерфейс `init/read/write/seek/ioctl/stop` без ABI-версии, feature-flags и без стандартной модели ошибок. +2. Syscall-слой даёт прямой доступ к `inb/outb/inw/outw`, `wait_irq`, `map_mmio`, `grant_mmio`, `set_driver`. +3. Сейчас есть «временный» bypass: все ELF, загруженные через `elf_exec`, получают `is_driver = true`. +4. В `sys_map_mmio` есть логическая ошибка проверки grant-диапазонов: для `is_driver == true` доступ разрешается без проверки списка grant. +5. В DriverContext отсутствует жизненный цикл capability-хэндлов (IRQ/MMIO/PIO), поэтому драйвер не может безопасно «освобождать» возможности. +6. `PS2Driver` работает через busy-wait и не имеет явной state-машины и recovery-политики. + +## 2) Главные риски стабильности + +- **Эскалация привилегий**: auto-driver для любого ELF и широкие I/O syscalls. +- **Непредсказуемость ABI**: нет version negotiation для user driver binary. +- **Дрейф поведения ошибок**: разные syscalls возвращают `0`/`-1` без единой errno-модели. +- **Зависание в I/O**: нет timeout/error surface в ожидании IRQ и I/O handshakes. +- **Невозможность безопасного hot-restart**: нет единого stop/teardown протокола в ядре. + +## 3) Реализации для стабилизации (рекомендуемый порядок) + +## 3.1 Capability-first DriverAPI (MVP) + +Вместо «глобального is_driver» ввести capability-токены, выдаваемые ядром: + +- `cap_irq_wait(irq)` +- `cap_pio_read(port, width)` / `cap_pio_write(...)` +- `cap_mmio_map(cap_id, virt, pages)` + +Идея: `sys_set_driver` переводится в «может запрашивать capabilities», а не «полный доступ к железу». + +### Минимальные syscall-добавления + +- `SYS_CAP_ACQUIRE(type, arg0, arg1)` -> `cap_id | <0` +- `SYS_CAP_RELEASE(cap_id)` +- `SYS_CAP_PIO_RW(cap_id, op, value)` +- `SYS_CAP_IRQ_WAIT(cap_id, timeout_ms)` +- `SYS_CAP_MMIO_MAP(cap_id, virt, pages)` + +Это сразу решает гранулярность прав и даёт обратимый teardown. + +## 3.2 Исправление политики MMIO (обязательный hotfix) + +Текущее поведение: `if (cur.is_driver) allowed = true;`. + +Исправление: + +- Разрешать `map_mmio` **только** если диапазон входит в `allowed_mmio[]`. +- Исключение только для `tid==0` (kernel bootstrap thread), и то под compile-time флагом `RE36_DEV_TRUST_BOOT=1`. + +## 3.3 Driver manifest + trust policy + +Для ELF-драйвера рядом класть `*.drv.json`: + +```json +{ + "name": "ps2kbd", + "abi": 1, + "caps": { + "irq": [1], + "pio": [{"start":96,"end":100}], + "mmio": [] + }, + "restart": "on-failure" +} +``` + +Kernel loader: + +1. читает manifest, +2. валидирует ABI, +3. выдаёт caps, +4. только после этого запускает driver thread. + +## 3.4 DriverContext v2 (ABI-stable) + +Предлагаемая структура: + +```c +struct DriverContextV2 { + uint32_t abi_version; // = 2 + uint32_t driver_tid; + uint32_t device_id; + uint32_t flags; + + uint32_t irq_caps[8]; + uint32_t pio_caps[8]; + uint32_t mmio_caps[8]; + + void* shared_cfg; + uint32_t shared_cfg_size; + + int32_t last_error; // errno-like +}; +``` + +Плюс `ioctl`-контракт: + +- `DRV_IOCTL_GET_VERSION` +- `DRV_IOCTL_GET_HEALTH` +- `DRV_IOCTL_RESET` +- `DRV_IOCTL_QUIESCE` + +## 3.5 Единая ошибка/диагностика + +Ввести таблицу для драйверного слоя: + +- `DRV_OK = 0` +- `DRV_EPERM`, `DRV_EINVAL`, `DRV_ETIMEDOUT`, `DRV_EIO`, `DRV_EBUSY`, `DRV_EAGAIN`, `DRV_ENOTSUP`. + +Правило: syscall всегда возвращает `<0` при ошибке и заполняет `last_error`. + +## 3.6 Надёжный lifecycle драйвера + +Ядро должно поддерживать единый протокол: + +1. `probe` (resource check) +2. `init` +3. `run` +4. `quiesce` +5. `stop` +6. `recover` (опционально) + +Технически: watchdog-счётчик heartbeat + restart policy (`never`, `on-failure`, `always`). + +## 3.7 Стабилизация PS/2 как эталонного драйвера + +Для `PS2Driver` внедрить: + +- state-machine (`Reset -> SelfTest -> Config -> Streaming`), +- timeout-aware `wait_read/write` с кодами ошибок, +- retry budget (например, 3 попытки для ACK), +- отдельный ring buffer событий, +- `ioctl(GET_STATS)` для диагностики (timeouts, parity, overruns). + +## 4) План внедрения (по PR) + +### PR-1 (без ломки API) + +- Hotfix `sys_map_mmio` policy. +- Убрать auto `is_driver=true` для всех ELF, оставить whitelist (shell/testdriver). +- Унифицировать return codes (`-1`/negative errno). + +### PR-2 (новые syscall capabilities) + +- Добавить `SYS_CAP_*`. +- Обернуть существующие `sys_inb/outb/...` через capability checks. +- Добавить revoke path в `thread_cleanup`. + +### PR-3 (manifest + loader) + +- Парсинг `.drv.json`. +- Выдача caps из manifest. +- Запуск/перезапуск по policy. + +### PR-4 (DriverContext v2 + PS2 refactor) + +- Ввести `DriverContextV2`. +- Перенести PS2 driver на v2 + telemetry. + +## 5) Критерии готовности (Definition of Done) + +- Драйвер без capability не может: + - делать PIO, + - ждать IRQ, + - map/unmap MMIO. +- После `thread_cleanup` все caps ревокнуты и MMIO unmapped. +- Driver ABI version check обязателен при старте. +- Watchdog фиксирует зависшие драйверы и применяет policy без kernel panic. +- Нагрузочный тест (10k циклов init/stop) без утечек и зависаний. + +## 6) Быстрые точечные изменения прямо сейчас + +1. В `sys_map_mmio` убрать unconditional `allowed=true` для `is_driver`. +2. В `elf_exec` убрать выдачу driver-флага всем ELF. +3. В `PS2Driver::wait_read/write` возвращать код таймаута и прокидывать его в `read/write/init`. +4. Ввести единый `driver_errno.h` для user-space драйверов. + +Эти четыре пункта дадут максимум прироста стабильности при минимальном размере изменений. From 4051f2e9ebca1c1022aea36fa179f68096c0dddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= <140026428+AMDRIP@users.noreply.github.com> Date: Sun, 1 Mar 2026 06:26:59 +0400 Subject: [PATCH 3/6] docs: add prioritized top-10 critical fixes list --- docs/TOP10_CRITICAL_FIXES.md | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/TOP10_CRITICAL_FIXES.md diff --git a/docs/TOP10_CRITICAL_FIXES.md b/docs/TOP10_CRITICAL_FIXES.md new file mode 100644 index 0000000..111eaee --- /dev/null +++ b/docs/TOP10_CRITICAL_FIXES.md @@ -0,0 +1,61 @@ +# TOP-10 критичных мест для исправления в первую очередь + +Ниже — приоритетный список мест, которые дают наибольший риск по безопасности/стабильности и должны исправляться первыми. + +## 1. Эскалация прав: любой ELF получает `is_driver=true` +- Где: `elf_exec()`. +- Почему критично: любой загруженный ELF получает привилегии драйвера (PIO/MMIO/IRQ), что ломает модель изоляции Ring3. +- Минимальный фикс: убрать auto-grant, оставить только whitelist/manifest-driven выдачу прав. + +## 2. Ошибка политики MMIO в `sys_map_mmio` +- Где: `sys_map_mmio`. +- Почему критично: для `is_driver` сейчас доступ разрешается без проверки grant-диапазонов. +- Минимальный фикс: всегда проверять `allowed_mmio[]` (кроме строго ограниченного bootstrap-исключения). + +## 3. Грубая модель DriverAPI через флаг `is_driver` +- Где: `sys_inb/outb/inw/outw`, `sys_wait_irq`, `sys_set_driver`. +- Почему критично: нет capability-гранулярности (порт/IRQ/диапазон), одна ошибка в policy = полный доступ. +- Минимальный фикс: capability tokens + revoke на cleanup. + +## 4. VFS ABI-хак: `vnode*` возвращается как `int` +- Где: `vfs_open()` и использование в syscall-слое. +- Почему критично: риск UB/переполнений/разъезда ABI, усложнение безопасного управления lifetime. +- Минимальный фикс: нормальный контракт `file*`/fd без pointer-cast. + +## 5. VFS не поддерживает реальные mountpoint-префиксы +- Где: `vfs_resolve_path` (жёстко `mount_points[0]`). +- Почему критично: расширение на несколько ФС/дисков фактически блокировано; возможны неверные резолвы пути. +- Минимальный фикс: выбрать superblock по самому длинному совпавшему mount-prefix. + +## 6. Неконсистентные коды ошибок в syscalls +- Где: `sys_outb/sys_outw` возвращают `0` даже при запрете; часть API возвращает `-1`, часть `0`. +- Почему критично: user-space не может надёжно отличить success/failure, ошибки теряются. +- Минимальный фикс: единая negative errno-модель для всех driver-related syscalls. + +## 7. Отсутствие timeout/error surface в ожидании IRQ +- Где: `sys_wait_irq` + user-driver loops. +- Почему критично: зависания драйвера в вечном ожидании и непрозрачные deadlock-сценарии. +- Минимальный фикс: `wait_irq(irq, timeout_ms)` + диагностируемые коды таймаута. + +## 8. Busy-wait без явного результата в PS/2 драйвере +- Где: `PS2Driver::wait_read/write`. +- Почему критично: функции "молча" выходят по timeout, а вызывающий код продолжает I/O как будто всё ок. +- Минимальный фикс: возвращать статус/ошибку, пробрасывать в `init/read/write`. + +## 9. Shell запускается как driver-thread +- Где: `kernel_main` выставляет `threads[shell_tid].is_driver = true`. +- Почему критично: CLI-процесс получает повышенные привилегии по умолчанию; высокий blast radius. +- Минимальный фикс: убрать broad privilege, выдавать только минимальные capabilities. + +## 10. `sys_set_driver` без строгой trust-цепочки +- Где: `sys_set_driver`. +- Почему критично: при компромете одного driver-thread можно массово повышать привилегии другим потокам. +- Минимальный фикс: policy check (manifest owner/signature/ACL) + аудит вызовов. + +--- + +## Рекомендуемый порядок внедрения (коротко) +1) Пункты 1+2+9 (быстрое снижение риска эскалации). +2) Пункты 3+6+7 (формализация API и ошибок). +3) Пункты 4+5 (архитектурная чистка VFS). +4) Пункты 8+10 (драйверная надёжность и trust policy). From 8b22bfb07b6074ef48f1cd59b0bb6c673d7076bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= <140026428+AMDRIP@users.noreply.github.com> Date: Sun, 1 Mar 2026 06:45:58 +0400 Subject: [PATCH 4/6] user: add graphical Tetris app and build target --- build.sh | 4 + user/tests/tetris.cpp | 265 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 user/tests/tetris.cpp diff --git a/build.sh b/build.sh index d32c04d..85d7779 100644 --- a/build.sh +++ b/build.sh @@ -171,6 +171,10 @@ x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nos x86_64-linux-gnu-ld -m elf_i386 -T user/user.ld user_crt0.o user_anim.o user_libc.a -o ANIM.ELF mcopy -i data.img ANIM.ELF ::/tests/ANIM.ELF +x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nostdlib -nostdinc -Iuser -Iuser/libc/include -c user/tests/tetris.cpp -o user_tetris.o +x86_64-linux-gnu-ld -m elf_i386 -T user/user.ld user_crt0.o user_tetris.o user_libc.a -o TETRIS.ELF +mcopy -i data.img TETRIS.ELF ::/tests/TETRIS.ELF + x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nostdlib -nostdinc -Iuser -Iuser/libc/include -c user/ps2_driver.cpp -o user_ps2_driver.o x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nostdlib -nostdinc -Iuser -Iuser/libc/include -c user/tests/ps2_test.cpp -o user_ps2_test.o x86_64-linux-gnu-ld -m elf_i386 -T user/user.ld user_crt0.o user_ps2_test.o user_ps2_driver.o user_libc.a -o PS2TEST.ELF diff --git a/user/tests/tetris.cpp b/user/tests/tetris.cpp new file mode 100644 index 0000000..bc47dc3 --- /dev/null +++ b/user/tests/tetris.cpp @@ -0,0 +1,265 @@ +#include "app_api.h" +#include "vesa_driver.h" + +void operator delete(void*, unsigned int) {} +void operator delete(void*) {} +extern "C" void __cxa_pure_virtual() { + vlsmc::App::print("Pure virtual function call!\n"); + vlsmc::App::exit(1); +} + +using namespace vlsmc; + +namespace { + +static const int BOARD_W = 10; +static const int BOARD_H = 20; +static const int CELL = 16; + +static const int FIELD_X = 80; +static const int FIELD_Y = 40; + +struct Vec2 { int x; int y; }; + +// [piece][rotation][block] +static const Vec2 PIECES[7][4][4] = { + // I + {{{0,1},{1,1},{2,1},{3,1}}, {{2,0},{2,1},{2,2},{2,3}}, {{0,2},{1,2},{2,2},{3,2}}, {{1,0},{1,1},{1,2},{1,3}}}, + // O + {{{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}}}, + // T + {{{1,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{2,1},{1,2}}, {{0,1},{1,1},{2,1},{1,2}}, {{1,0},{0,1},{1,1},{1,2}}}, + // S + {{{1,0},{2,0},{0,1},{1,1}}, {{1,0},{1,1},{2,1},{2,2}}, {{1,1},{2,1},{0,2},{1,2}}, {{0,0},{0,1},{1,1},{1,2}}}, + // Z + {{{0,0},{1,0},{1,1},{2,1}}, {{2,0},{1,1},{2,1},{1,2}}, {{0,1},{1,1},{1,2},{2,2}}, {{1,0},{0,1},{1,1},{0,2}}}, + // J + {{{0,0},{0,1},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{1,2}}, {{0,1},{1,1},{2,1},{2,2}}, {{1,0},{1,1},{0,2},{1,2}}}, + // L + {{{2,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{1,2},{2,2}}, {{0,1},{1,1},{2,1},{0,2}}, {{0,0},{1,0},{1,1},{1,2}}}, +}; + +static const uint32_t COLORS[8] = { + 0xFF111111, // empty + 0xFF00FFFF, // I + 0xFFFFFF00, // O + 0xFFFF00FF, // T + 0xFF00FF00, // S + 0xFFFF0000, // Z + 0xFF0000FF, // J + 0xFFFFA500 // L +}; + +struct Piece { + int type; + int rot; + int x; + int y; +}; + +int board[BOARD_H][BOARD_W]; +uint32_t rng_state = 0; + +uint32_t next_rand() { + rng_state = rng_state * 1664525u + 1013904223u; + return rng_state; +} + +int random_piece() { + return (int)(next_rand() % 7u); +} + +void fill_rect(VesaDriver& drv, int x, int y, int w, int h, uint32_t color) { + for (int py = 0; py < h; py++) { + for (int px = 0; px < w; px++) { + drv.draw_pixel((uint32_t)(x + px), (uint32_t)(y + py), color); + } + } +} + +void draw_cell(VesaDriver& drv, int gx, int gy, int cell, bool active) { + int px = FIELD_X + gx * CELL; + int py = FIELD_Y + gy * CELL; + uint32_t c = COLORS[cell]; + + fill_rect(drv, px, py, CELL - 1, CELL - 1, c); + if (active) { + fill_rect(drv, px + 2, py + 2, CELL - 5, 2, 0xFFFFFFFF); + } +} + +bool collides(const Piece& p, int nx, int ny, int nrot) { + for (int i = 0; i < 4; i++) { + int x = nx + PIECES[p.type][nrot][i].x; + int y = ny + PIECES[p.type][nrot][i].y; + + if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H) return true; + if (board[y][x] != 0) return true; + } + return false; +} + +void lock_piece(const Piece& p) { + for (int i = 0; i < 4; i++) { + int x = p.x + PIECES[p.type][p.rot][i].x; + int y = p.y + PIECES[p.type][p.rot][i].y; + if (x >= 0 && x < BOARD_W && y >= 0 && y < BOARD_H) { + board[y][x] = p.type + 1; + } + } +} + +int clear_lines() { + int cleared = 0; + for (int y = BOARD_H - 1; y >= 0; y--) { + bool full = true; + for (int x = 0; x < BOARD_W; x++) { + if (board[y][x] == 0) { full = false; break; } + } + if (full) { + cleared++; + for (int yy = y; yy > 0; yy--) { + for (int x = 0; x < BOARD_W; x++) board[yy][x] = board[yy - 1][x]; + } + for (int x = 0; x < BOARD_W; x++) board[0][x] = 0; + y++; + } + } + return cleared; +} + +void draw_field(VesaDriver& drv, const Piece& active, bool game_over) { + fill_rect(drv, 0, 0, 1024, 768, game_over ? 0xFF220000 : 0xFF000000); + + fill_rect(drv, FIELD_X - 3, FIELD_Y - 3, BOARD_W * CELL + 6, BOARD_H * CELL + 6, 0xFF555555); + fill_rect(drv, FIELD_X, FIELD_Y, BOARD_W * CELL, BOARD_H * CELL, 0xFF111111); + + for (int y = 0; y < BOARD_H; y++) { + for (int x = 0; x < BOARD_W; x++) { + draw_cell(drv, x, y, board[y][x], false); + } + } + + if (!game_over) { + for (int i = 0; i < 4; i++) { + int x = active.x + PIECES[active.type][active.rot][i].x; + int y = active.y + PIECES[active.type][active.rot][i].y; + if (x >= 0 && x < BOARD_W && y >= 0 && y < BOARD_H) { + draw_cell(drv, x, y, active.type + 1, true); + } + } + } + + // Side panel bars (simple no-font HUD) + fill_rect(drv, FIELD_X + BOARD_W * CELL + 24, FIELD_Y, 220, 20, 0xFF333333); + fill_rect(drv, FIELD_X + BOARD_W * CELL + 24, FIELD_Y + 28, 220, 14, 0xFF666666); + fill_rect(drv, FIELD_X + BOARD_W * CELL + 24, FIELD_Y + 50, 220, 14, 0xFF444444); +} + +void reset_board() { + for (int y = 0; y < BOARD_H; y++) { + for (int x = 0; x < BOARD_W; x++) board[y][x] = 0; + } +} + +} // namespace + +int main() { + VesaDriver drv; + DriverContext ctx{}; + + if (drv.init(&ctx) != 0) { + App::print("TETRIS: VESA init failed\n"); + return 1; + } + + rng_state = App::uptime() ^ 0xA5A55A5Au; + if (rng_state == 0) rng_state = 1; + + reset_board(); + + Piece cur{random_piece(), 0, 3, 0}; + int lines = 0; + int score = 0; + bool game_over = false; + + uint32_t last_tick = App::uptime(); + uint32_t last_drop = last_tick; + uint32_t drop_ms = 500; + + while (true) { + uint32_t now = App::uptime(); + if (now - last_tick < 30) { + App::sleep(1); + continue; + } + last_tick = now; + + // Non-blocking keyboard poll through PS/2 controller status/data ports. + while ((App::inb(0x64) & 0x01) != 0) { + uint8_t sc = App::inb(0x60); + if (sc & 0x80) continue; // ignore key release + + if (sc == 0x10) { // Q + drv.stop(&ctx); + App::print("TETRIS: exit\n"); + return 0; + } + + if (game_over) { + if (sc == 0x39) { // SPACE restart + reset_board(); + cur = Piece{random_piece(), 0, 3, 0}; + lines = 0; + score = 0; + drop_ms = 500; + game_over = false; + } + continue; + } + + if (sc == 0x1E) { // A left + if (!collides(cur, cur.x - 1, cur.y, cur.rot)) cur.x--; + } else if (sc == 0x20) { // D right + if (!collides(cur, cur.x + 1, cur.y, cur.rot)) cur.x++; + } else if (sc == 0x1F) { // S soft drop + if (!collides(cur, cur.x, cur.y + 1, cur.rot)) cur.y++; + } else if (sc == 0x11) { // W rotate + int nr = (cur.rot + 1) & 3; + if (!collides(cur, cur.x, cur.y, nr)) cur.rot = nr; + } else if (sc == 0x39) { // SPACE hard drop + while (!collides(cur, cur.x, cur.y + 1, cur.rot)) cur.y++; + last_drop = 0; + } + } + + if (!game_over && now - last_drop >= drop_ms) { + last_drop = now; + + if (!collides(cur, cur.x, cur.y + 1, cur.rot)) { + cur.y++; + } else { + lock_piece(cur); + int c = clear_lines(); + lines += c; + score += (c == 1 ? 100 : c == 2 ? 300 : c == 3 ? 500 : c >= 4 ? 800 : 0); + + if (lines < 50) { + uint32_t speedup = (uint32_t)(lines * 8); + drop_ms = (500 > speedup + 120) ? (500 - speedup) : 120; + } + + cur = Piece{random_piece(), 0, 3, 0}; + if (collides(cur, cur.x, cur.y, cur.rot)) { + game_over = true; + App::print("TETRIS: GAME OVER (SPACE to restart, Q to quit)\n"); + (void)score; + } + } + } + + draw_field(drv, cur, game_over); + } + + return 0; +} From 6dfdd52557e7ae8063a7ddbd08d4a0ee14fded3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= <140026428+AMDRIP@users.noreply.github.com> Date: Sun, 1 Mar 2026 06:46:14 +0400 Subject: [PATCH 5/6] user: add graphical text editor app (GFXEDIT) --- build.sh | 4 + user/tests/gfxedit.cpp | 403 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 user/tests/gfxedit.cpp diff --git a/build.sh b/build.sh index 85d7779..a45eee5 100644 --- a/build.sh +++ b/build.sh @@ -175,6 +175,10 @@ x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nos x86_64-linux-gnu-ld -m elf_i386 -T user/user.ld user_crt0.o user_tetris.o user_libc.a -o TETRIS.ELF mcopy -i data.img TETRIS.ELF ::/tests/TETRIS.ELF +x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nostdlib -nostdinc -Iuser -Iuser/libc/include -c user/tests/gfxedit.cpp -o user_gfxedit.o +x86_64-linux-gnu-ld -m elf_i386 -T user/user.ld user_crt0.o user_gfxedit.o user_libc.a -o GFXEDIT.ELF +mcopy -i data.img GFXEDIT.ELF ::/tests/GFXEDIT.ELF + x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nostdlib -nostdinc -Iuser -Iuser/libc/include -c user/ps2_driver.cpp -o user_ps2_driver.o x86_64-linux-gnu-g++ -m32 -ffreestanding -fno-pie -fno-exceptions -fno-rtti -nostdlib -nostdinc -Iuser -Iuser/libc/include -c user/tests/ps2_test.cpp -o user_ps2_test.o x86_64-linux-gnu-ld -m elf_i386 -T user/user.ld user_crt0.o user_ps2_test.o user_ps2_driver.o user_libc.a -o PS2TEST.ELF diff --git a/user/tests/gfxedit.cpp b/user/tests/gfxedit.cpp new file mode 100644 index 0000000..34f7afd --- /dev/null +++ b/user/tests/gfxedit.cpp @@ -0,0 +1,403 @@ +#include "app_api.h" +#include "vesa_driver.h" + +void operator delete(void*, unsigned int) {} +void operator delete(void*) {} +extern "C" void __cxa_pure_virtual() { + vlsmc::App::print("Pure virtual function call!\n"); + vlsmc::App::exit(1); +} + +using namespace vlsmc; + +namespace { + +// Public-domain style 8x8 ASCII font (0..127) +static const uint8_t FONT8X8[128][8] = { +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00}, +{0x36,0x36,0x24,0x00,0x00,0x00,0x00,0x00},{0x36,0x36,0x7F,0x36,0x7F,0x36,0x36,0x00}, +{0x18,0x3E,0x03,0x1E,0x30,0x1F,0x18,0x00},{0x00,0x63,0x33,0x18,0x0C,0x66,0x63,0x00}, +{0x1C,0x36,0x1C,0x6E,0x3B,0x33,0x6E,0x00},{0x06,0x06,0x03,0x00,0x00,0x00,0x00,0x00}, +{0x18,0x0C,0x06,0x06,0x06,0x0C,0x18,0x00},{0x06,0x0C,0x18,0x18,0x18,0x0C,0x06,0x00}, +{0x00,0x66,0x3C,0x7F,0x3C,0x66,0x00,0x00},{0x00,0x0C,0x0C,0x3F,0x0C,0x0C,0x00,0x00}, +{0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x06},{0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00}, +{0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00},{0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x00}, +{0x3E,0x63,0x73,0x7B,0x6F,0x67,0x3E,0x00},{0x0C,0x0E,0x0C,0x0C,0x0C,0x0C,0x3F,0x00}, +{0x1E,0x33,0x30,0x1C,0x06,0x33,0x3F,0x00},{0x1E,0x33,0x30,0x1C,0x30,0x33,0x1E,0x00}, +{0x38,0x3C,0x36,0x33,0x7F,0x30,0x78,0x00},{0x3F,0x03,0x1F,0x30,0x30,0x33,0x1E,0x00}, +{0x1C,0x06,0x03,0x1F,0x33,0x33,0x1E,0x00},{0x3F,0x33,0x30,0x18,0x0C,0x0C,0x0C,0x00}, +{0x1E,0x33,0x33,0x1E,0x33,0x33,0x1E,0x00},{0x1E,0x33,0x33,0x3E,0x30,0x18,0x0E,0x00}, +{0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x00},{0x00,0x0C,0x0C,0x00,0x00,0x0C,0x0C,0x06}, +{0x18,0x0C,0x06,0x03,0x06,0x0C,0x18,0x00},{0x00,0x00,0x3F,0x00,0x00,0x3F,0x00,0x00}, +{0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00},{0x1E,0x33,0x30,0x18,0x0C,0x00,0x0C,0x00}, +{0x3E,0x63,0x7B,0x7B,0x7B,0x03,0x1E,0x00},{0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00}, +{0x3F,0x66,0x66,0x3E,0x66,0x66,0x3F,0x00},{0x3C,0x66,0x03,0x03,0x03,0x66,0x3C,0x00}, +{0x1F,0x36,0x66,0x66,0x66,0x36,0x1F,0x00},{0x7F,0x46,0x16,0x1E,0x16,0x46,0x7F,0x00}, +{0x7F,0x46,0x16,0x1E,0x16,0x06,0x0F,0x00},{0x3C,0x66,0x03,0x03,0x73,0x66,0x7C,0x00}, +{0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x00},{0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00}, +{0x78,0x30,0x30,0x30,0x33,0x33,0x1E,0x00},{0x67,0x66,0x36,0x1E,0x36,0x66,0x67,0x00}, +{0x0F,0x06,0x06,0x06,0x46,0x66,0x7F,0x00},{0x63,0x77,0x7F,0x7F,0x6B,0x63,0x63,0x00}, +{0x63,0x67,0x6F,0x7B,0x73,0x63,0x63,0x00},{0x1C,0x36,0x63,0x63,0x63,0x36,0x1C,0x00}, +{0x3F,0x66,0x66,0x3E,0x06,0x06,0x0F,0x00},{0x1E,0x33,0x33,0x33,0x3B,0x1E,0x38,0x00}, +{0x3F,0x66,0x66,0x3E,0x36,0x66,0x67,0x00},{0x1E,0x33,0x07,0x0E,0x38,0x33,0x1E,0x00}, +{0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},{0x33,0x33,0x33,0x33,0x33,0x33,0x3F,0x00}, +{0x33,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00},{0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00}, +{0x63,0x63,0x36,0x1C,0x1C,0x36,0x63,0x00},{0x33,0x33,0x33,0x1E,0x0C,0x0C,0x1E,0x00}, +{0x7F,0x73,0x19,0x0C,0x26,0x63,0x7F,0x00},{0x1E,0x06,0x06,0x06,0x06,0x06,0x1E,0x00}, +{0x03,0x06,0x0C,0x18,0x30,0x60,0x40,0x00},{0x1E,0x18,0x18,0x18,0x18,0x18,0x1E,0x00}, +{0x08,0x1C,0x36,0x63,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF}, +{0x0C,0x0C,0x18,0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x1E,0x30,0x3E,0x33,0x6E,0x00}, +{0x07,0x06,0x06,0x3E,0x66,0x66,0x3B,0x00},{0x00,0x00,0x1E,0x33,0x03,0x33,0x1E,0x00}, +{0x38,0x30,0x30,0x3E,0x33,0x33,0x6E,0x00},{0x00,0x00,0x1E,0x33,0x3F,0x03,0x1E,0x00}, +{0x1C,0x36,0x06,0x0F,0x06,0x06,0x0F,0x00},{0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x1F}, +{0x07,0x06,0x36,0x6E,0x66,0x66,0x67,0x00},{0x0C,0x00,0x0E,0x0C,0x0C,0x0C,0x1E,0x00}, +{0x30,0x00,0x30,0x30,0x30,0x33,0x33,0x1E},{0x07,0x06,0x66,0x36,0x1E,0x36,0x67,0x00}, +{0x0E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},{0x00,0x00,0x33,0x7F,0x7F,0x6B,0x63,0x00}, +{0x00,0x00,0x1F,0x33,0x33,0x33,0x33,0x00},{0x00,0x00,0x1E,0x33,0x33,0x33,0x1E,0x00}, +{0x00,0x00,0x3B,0x66,0x66,0x3E,0x06,0x0F},{0x00,0x00,0x6E,0x33,0x33,0x3E,0x30,0x78}, +{0x00,0x00,0x3B,0x6E,0x66,0x06,0x0F,0x00},{0x00,0x00,0x3E,0x03,0x1E,0x30,0x1F,0x00}, +{0x08,0x0C,0x3E,0x0C,0x0C,0x2C,0x18,0x00},{0x00,0x00,0x33,0x33,0x33,0x33,0x6E,0x00}, +{0x00,0x00,0x33,0x33,0x33,0x1E,0x0C,0x00},{0x00,0x00,0x63,0x6B,0x7F,0x7F,0x36,0x00}, +{0x00,0x00,0x63,0x36,0x1C,0x36,0x63,0x00},{0x00,0x00,0x33,0x33,0x33,0x3E,0x30,0x1F}, +{0x00,0x00,0x3F,0x19,0x0C,0x26,0x3F,0x00},{0x38,0x0C,0x0C,0x07,0x0C,0x0C,0x38,0x00}, +{0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00},{0x07,0x0C,0x0C,0x38,0x0C,0x0C,0x07,0x00}, +{0x6E,0x3B,0x00,0x00,0x00,0x00,0x00,0x00},{0,0,0,0,0,0,0,0}, + +// Remaining control chars 96..127 unused fallback to empty/simple +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}, +{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0} +}; + +static const int SCREEN_W = 1024; +static const int SCREEN_H = 768; +static const int CHAR_W = 8; +static const int CHAR_H = 8; + +static const int TOP_BAR_H = 20; +static const int STATUS_H = 20; +static const int TEXT_X = 8; +static const int TEXT_Y = TOP_BAR_H + 4; +static const int TEXT_W = (SCREEN_W - 16) / CHAR_W; +static const int TEXT_H = (SCREEN_H - TOP_BAR_H - STATUS_H - 8) / CHAR_H; + +static const int MAX_TEXT = 128 * 1024; +static char text_buf[MAX_TEXT]; +static int text_len = 0; +static int cursor = 0; +static int view_line = 0; +static bool modified = false; +static const char* FILE_PATH = "/EDIT.TXT"; + +void mem_move(char* dst, const char* src, int n) { + if (n <= 0 || dst == src) return; + if (dst < src) { + for (int i = 0; i < n; i++) dst[i] = src[i]; + } else { + for (int i = n - 1; i >= 0; i--) dst[i] = src[i]; + } +} + +int str_len(const char* s) { + int n = 0; + while (s && s[n]) n++; + return n; +} + +void fill_rect(VesaDriver& drv, int x, int y, int w, int h, uint32_t color) { + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > SCREEN_W) w = SCREEN_W - x; + if (y + h > SCREEN_H) h = SCREEN_H - y; + if (w <= 0 || h <= 0) return; + + for (int yy = 0; yy < h; yy++) { + for (int xx = 0; xx < w; xx++) { + drv.draw_pixel((uint32_t)(x + xx), (uint32_t)(y + yy), color); + } + } +} + +void draw_char(VesaDriver& drv, int cx, int cy, char ch, uint32_t fg, uint32_t bg) { + uint8_t idx = (uint8_t)ch; + const uint8_t* g = FONT8X8[idx]; + int px = TEXT_X + cx * CHAR_W; + int py = TEXT_Y + cy * CHAR_H; + + for (int y = 0; y < 8; y++) { + uint8_t bits = g[y]; + for (int x = 0; x < 8; x++) { + bool on = (bits & (1u << x)) != 0; + drv.draw_pixel((uint32_t)(px + x), (uint32_t)(py + y), on ? fg : bg); + } + } +} + +void draw_text(VesaDriver& drv, int px, int py, const char* s, uint32_t fg, uint32_t bg) { + int x = px; + for (int i = 0; s[i]; i++) { + uint8_t idx = (uint8_t)s[i]; + const uint8_t* g = FONT8X8[idx]; + for (int y = 0; y < 8; y++) { + uint8_t bits = g[y]; + for (int xx = 0; xx < 8; xx++) { + bool on = (bits & (1u << xx)) != 0; + drv.draw_pixel((uint32_t)(x + xx), (uint32_t)(py + y), on ? fg : bg); + } + } + x += 8; + } +} + +void index_to_linecol(int idx, int& line, int& col) { + line = 0; + col = 0; + for (int i = 0; i < idx && i < text_len; i++) { + if (text_buf[i] == '\n') { line++; col = 0; } + else col++; + } +} + +int linecol_to_index(int target_line, int target_col) { + int line = 0; + int col = 0; + for (int i = 0; i < text_len; i++) { + if (line == target_line && col == target_col) return i; + if (text_buf[i] == '\n') { + if (line == target_line) return i; + line++; col = 0; + } else { + col++; + } + } + return text_len; +} + +void ensure_cursor_visible() { + int line, col; + index_to_linecol(cursor, line, col); + (void)col; + if (line < view_line) view_line = line; + if (line >= view_line + TEXT_H) view_line = line - TEXT_H + 1; + if (view_line < 0) view_line = 0; +} + +void insert_char(char c) { + if (text_len >= MAX_TEXT - 1) return; + mem_move(text_buf + cursor + 1, text_buf + cursor, text_len - cursor); + text_buf[cursor] = c; + cursor++; + text_len++; + modified = true; +} + +void backspace_char() { + if (cursor <= 0) return; + mem_move(text_buf + cursor - 1, text_buf + cursor, text_len - cursor); + cursor--; + text_len--; + modified = true; +} + +void delete_char() { + if (cursor >= text_len) return; + mem_move(text_buf + cursor, text_buf + cursor + 1, text_len - cursor - 1); + text_len--; + modified = true; +} + +bool load_file() { + int fd = App::open(FILE_PATH, FMODE_READ); + if (fd < 0) { + text_len = 0; + cursor = 0; + modified = false; + return false; + } + + int got = App::read(fd, text_buf, MAX_TEXT - 1); + App::close(fd); + + if (got < 0) { + text_len = 0; + cursor = 0; + modified = false; + return false; + } + + text_len = got; + cursor = 0; + modified = false; + return true; +} + +bool save_file() { + int fd = App::open(FILE_PATH, FMODE_WRITE); + if (fd < 0) return false; + int wr = App::write(fd, text_buf, (uint32_t)text_len); + App::close(fd); + if (wr < 0) return false; + modified = false; + return true; +} + +char map_scancode(uint8_t sc, bool shift) { + static const char normal[58] = { + 0, + 27,'1','2','3','4','5','6','7','8','9','0','-','=', '\b', + '\t','q','w','e','r','t','y','u','i','o','p','[',']','\n', + 0,'a','s','d','f','g','h','j','k','l',';', '\'', '`', + 0,'\\','z','x','c','v','b','n','m',',','.','/',0, + '*',0,' ' + }; + + static const char shifted[58] = { + 0, + 27,'!','@','#','$','%','^','&','*','(',')','_','+', '\b', + '\t','Q','W','E','R','T','Y','U','I','O','P','{','}','\n', + 0,'A','S','D','F','G','H','J','K','L',':', '"', '~', + 0,'|','Z','X','C','V','B','N','M','<','>','?',0, + '*',0,' ' + }; + + if (sc >= 58) return 0; + return shift ? shifted[sc] : normal[sc]; +} + +void draw_editor(VesaDriver& drv, bool save_ok, bool loaded_ok) { + fill_rect(drv, 0, 0, SCREEN_W, SCREEN_H, 0xFF101418); + + fill_rect(drv, 0, 0, SCREEN_W, TOP_BAR_H, 0xFF202A36); + draw_text(drv, 8, 6, "GFXEDIT - F2 Save | ESC Quit | Arrows Move", 0xFFFFFFFF, 0xFF202A36); + + fill_rect(drv, 0, SCREEN_H - STATUS_H, SCREEN_W, STATUS_H, 0xFF202A36); + draw_text(drv, 8, SCREEN_H - STATUS_H + 6, FILE_PATH, 0xFF8BE9FD, 0xFF202A36); + draw_text(drv, 220, SCREEN_H - STATUS_H + 6, modified ? "[modified]" : "[saved]", modified ? 0xFFFFB86C : 0xFF50FA7B, 0xFF202A36); + draw_text(drv, 340, SCREEN_H - STATUS_H + 6, loaded_ok ? "load:ok" : "load:new", 0xFFBD93F9, 0xFF202A36); + draw_text(drv, 430, SCREEN_H - STATUS_H + 6, save_ok ? "save:ok" : "save:--", 0xFF50FA7B, 0xFF202A36); + + int cur_line = 0, cur_col = 0; + index_to_linecol(cursor, cur_line, cur_col); + + int line = 0; + int col = 0; + int screen_line = 0; + + for (int i = 0; i <= text_len; i++) { + bool end = (i == text_len); + char ch = end ? '\0' : text_buf[i]; + + if (line >= view_line && screen_line < TEXT_H) { + if (!end && ch != '\n' && col < TEXT_W) { + draw_char(drv, col, screen_line, ch, 0xFFE6E6E6, 0xFF101418); + } + } + + if (end || ch == '\n') { + if (line >= view_line) screen_line++; + line++; + col = 0; + if (screen_line >= TEXT_H) break; + } else { + col++; + } + } + + int cy = cur_line - view_line; + if (cy >= 0 && cy < TEXT_H && cur_col >= 0 && cur_col < TEXT_W) { + int px = TEXT_X + cur_col * CHAR_W; + int py = TEXT_Y + cy * CHAR_H; + fill_rect(drv, px, py + 7, 8, 1, 0xFFFFFF00); + } +} + +} // namespace + +int main() { + VesaDriver drv; + DriverContext ctx{}; + if (drv.init(&ctx) != 0) { + App::print("GFXEDIT: VESA init failed\n"); + return 1; + } + + bool loaded_ok = load_file(); + bool save_ok = false; + bool shift = false; + bool ext = false; + + while (true) { + draw_editor(drv, save_ok, loaded_ok); + + while ((App::inb(0x64) & 0x01) != 0) { + uint8_t sc = App::inb(0x60); + + if (sc == 0xE0) { ext = true; continue; } + + bool release = (sc & 0x80) != 0; + uint8_t code = sc & 0x7F; + + if (code == 0x2A || code == 0x36) { // shift + shift = !release; + ext = false; + continue; + } + + if (release) { ext = false; continue; } + + if (ext) { + // arrows + delete + int line, col; + index_to_linecol(cursor, line, col); + if (code == 0x48) { // up + if (line > 0) cursor = linecol_to_index(line - 1, col); + } else if (code == 0x50) { // down + cursor = linecol_to_index(line + 1, col); + } else if (code == 0x4B) { // left + if (cursor > 0) cursor--; + } else if (code == 0x4D) { // right + if (cursor < text_len) cursor++; + } else if (code == 0x53) { + delete_char(); + } + ext = false; + ensure_cursor_visible(); + continue; + } + + if (code == 0x01) { // ESC + drv.stop(&ctx); + App::print("GFXEDIT: exit\n"); + return 0; + } + if (code == 0x3C) { // F2 + save_ok = save_file(); + continue; + } + + if (code == 0x0E) { + backspace_char(); + } else if (code == 0x1C) { + insert_char('\n'); + } else if (code == 0x4B) { + if (cursor > 0) cursor--; + } else if (code == 0x4D) { + if (cursor < text_len) cursor++; + } else { + char c = map_scancode(code, shift); + if (c >= 32 || c == '\t') insert_char(c); + } + + ensure_cursor_visible(); + } + + App::sleep(12); + } + + return 0; +} From 3a25f9f87db83b730bcd217ee59db26d9c81e4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= <140026428+AMDRIP@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:13:42 +0400 Subject: [PATCH 6/6] gfxedit: fix kernel panic by moving text buffer off .bss --- user/tests/gfxedit.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/user/tests/gfxedit.cpp b/user/tests/gfxedit.cpp index 34f7afd..4480786 100644 --- a/user/tests/gfxedit.cpp +++ b/user/tests/gfxedit.cpp @@ -1,8 +1,6 @@ #include "app_api.h" #include "vesa_driver.h" -void operator delete(void*, unsigned int) {} -void operator delete(void*) {} extern "C" void __cxa_pure_virtual() { vlsmc::App::print("Pure virtual function call!\n"); vlsmc::App::exit(1); @@ -87,7 +85,7 @@ static const int TEXT_W = (SCREEN_W - 16) / CHAR_W; static const int TEXT_H = (SCREEN_H - TOP_BAR_H - STATUS_H - 8) / CHAR_H; static const int MAX_TEXT = 128 * 1024; -static char text_buf[MAX_TEXT]; +static char* text_buf = nullptr; static int text_len = 0; static int cursor = 0; static int view_line = 0; @@ -95,7 +93,7 @@ static bool modified = false; static const char* FILE_PATH = "/EDIT.TXT"; void mem_move(char* dst, const char* src, int n) { - if (n <= 0 || dst == src) return; + if (!dst || !src || n <= 0 || dst == src) return; if (dst < src) { for (int i = 0; i < n; i++) dst[i] = src[i]; } else { @@ -212,6 +210,8 @@ void delete_char() { } bool load_file() { + if (!text_buf) return false; + int fd = App::open(FILE_PATH, FMODE_READ); if (fd < 0) { text_len = 0; @@ -319,9 +319,17 @@ void draw_editor(VesaDriver& drv, bool save_ok, bool loaded_ok) { } // namespace int main() { + text_buf = (char*)App::malloc(MAX_TEXT); + if (!text_buf) { + App::print("GFXEDIT: OOM for text buffer\n"); + return 1; + } + VesaDriver drv; DriverContext ctx{}; if (drv.init(&ctx) != 0) { + App::free(text_buf); + text_buf = nullptr; App::print("GFXEDIT: VESA init failed\n"); return 1; } @@ -372,6 +380,8 @@ int main() { if (code == 0x01) { // ESC drv.stop(&ctx); + App::free(text_buf); + text_buf = nullptr; App::print("GFXEDIT: exit\n"); return 0; } @@ -399,5 +409,7 @@ int main() { App::sleep(12); } + App::free(text_buf); + text_buf = nullptr; return 0; }