From a442f7a144b126384f54afafbd634569b87b503a Mon Sep 17 00:00:00 2001 From: mzhoot Date: Tue, 9 Jun 2026 22:43:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 246 ++++++++++++------ особенности | 11 - .../compare_elf.sh | 0 тесты/kvs_full_test.csv | 10 + тесты/kvs_full_test.py | 107 ++++++++ .../вывод_аргумента.py | 112 ++++++++ .../вывод_аргумента.квс | 0 .../длин_тест/длин_тест.py | 76 ++++++ .../длин_тест.квс | 0 тесты/привет/привет.py | 85 ++++++ .../{ => привет}/привет.квс | 0 .../сложный_тест.py | 85 ++++++ .../сложный_тест.квс | 0 .../тест_адресации1.py | 74 ++++++ .../тест_адресации1.квс | 0 .../тест_адресации2.py | 74 ++++++ .../тест_адресации2.квс | 0 .../тест_адресации3.py | 74 ++++++ .../тест_адресации3.квс | 0 .../тест_адресации_р8.py | 87 +++++++ .../тест_адресации_р8.квс | 2 +- тесты/тест_бнд/тест_бнд.py | 193 ++++++++++++++ .../тест_бнд.квс | 0 черновик.txt | 16 -- 24 files changed, 1141 insertions(+), 111 deletions(-) delete mode 100644 особенности rename тесты/{сравнитель => _сравнитель}/compare_elf.sh (100%) create mode 100644 тесты/kvs_full_test.csv create mode 100644 тесты/kvs_full_test.py create mode 100644 тесты/вывод_аргумента/вывод_аргумента.py rename тесты/{ => вывод_аргумента}/вывод_аргумента.квс (100%) create mode 100644 тесты/длин_тест/длин_тест.py rename тесты/{ => длин_тест}/длин_тест.квс (100%) create mode 100644 тесты/привет/привет.py rename тесты/{ => привет}/привет.квс (100%) create mode 100644 тесты/сложный_тест/сложный_тест.py rename тесты/{ => сложный_тест}/сложный_тест.квс (100%) create mode 100644 тесты/тест_адресации1/тест_адресации1.py rename тесты/{ => тест_адресации1}/тест_адресации1.квс (100%) create mode 100644 тесты/тест_адресации2/тест_адресации2.py rename тесты/{ => тест_адресации2}/тест_адресации2.квс (100%) create mode 100644 тесты/тест_адресации3/тест_адресации3.py rename тесты/{ => тест_адресации3}/тест_адресации3.квс (100%) create mode 100644 тесты/тест_адресации_р8/тест_адресации_р8.py rename тесты/{ => тест_адресации_р8}/тест_адресации_р8.квс (88%) create mode 100644 тесты/тест_бнд/тест_бнд.py rename тесты/{ => тест_бнд}/тест_бнд.квс (100%) delete mode 100644 черновик.txt diff --git a/README.md b/README.md index 0034b9f..f47a7c5 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,13 @@ - [Архитектура](#-архитектура) - [Установка](#-установка) - [Быстрый старт](#-быстрый-старт) +- [Система тестирования](#-система-тестирования) - [Синтаксис](#-синтаксис) - [Система команд](#-система-команд) - [Регистры](#-регистры-процессора) - [Директивы](#-директивы-ассемблера) - [Примеры программ](#-примеры-программ) -- [Отладка](#-отладка-и-проверка) +- [Отладка и проверка](#-отладка-и-проверка) - [Ограничения](#-ограничения) - [План развития](#-план-развития) - [Лицензия](#-лицензия) @@ -23,7 +24,7 @@ - **Полностью русский синтаксис** — все инструкции, регистры и директивы на кириллице - **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску -- **Четырёхпроходная сборка** — традиционная схема с разрешением меток, подстановкой констант и перекодированием +- **Шестипроходная сборка** — разделение логики на независимые этапы для прозрачности и отладки - **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бнд` (неинициализированные данные) - **Гибкая адресация памяти**: - абсолютная адресация по меткам `[переменная]` @@ -32,11 +33,12 @@ - всё с RIP-relative кодированием для 64-битных перемещений - **CSV-логирование** — детальный файл с соответствием адресов, байтов, меток и исходных команд - **Более 50 инструкций** — полный набор основных команд x86-64 +- **Автоматическая система регрессионного тестирования** — 9+ тестов, проверяющих все аспекты работы ассемблера - **Минимализм зависимостей** — только стандартная библиотека Python ## 🏗 Архитектура -Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через **6 этапов** (4 прохода + сборка): +Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через **6 этапов**: ``` Исходный файл (.квс) @@ -56,6 +58,10 @@ CSV-файл (.csv) ← содержит все байты прог │ ▼ (kvs_pass4.py) ← ПРОХОД 4: перекодирование инструкций │ + ▼ (kvs_pass5.py) ← ПРОХОД 5: генерация корректных таблиц секций для совместимости с toolchain + │ + ▼ (kvs_pass6.py) ← ПРОХОД 6: постобработка CSV для визуального анализа + │ ▼ (kvs_builder.py) ← сборка ELF из CSV ELF-файл (.elf) │ @@ -63,29 +69,35 @@ ELF-файл (.elf) Исполняемый файл! ``` +### Почему 6 проходов? + +1. **Первый проход** — неизвестно, какие адреса будут у меток. Ставим заглушки `0xCC`. +2. **Второй проход** — после того как все адреса меток известны, заменяем заглушки. +3. **Третий проход** — константы могут ссылаться друг на друга и на метки. +4. **Четвёртый проход** — после подстановки чисел перегенерируем машинный код (разная длина инструкций). +5. **Пятый проход** — генерируем корректные таблицы секций для работы `objdump`, `readelf` и GDB. +6. **Шестой проход** — сортируем CSV для удобного визуального анализа человеком. + +Такой подход позволяет **изолировать логику** каждого этапа и делает отладку прозрачной. + ### Модули компилятора | Файл | Назначение | |------|-------------| -| `kvs_build.py` | Главный сборочный скрипт (запускает все этапы по порядку) | -| `kvs_lexer.py` | Лексический анализ, разбор на токены (поддерживает `[]`, `+`, `-`, `*`) | -| `kvs_parser.py` | Синтаксический анализ, построение AST (директивы, метки, инструкции) | -| `kvs_data.py` | Общие данные: регистры, таблица инструкций, вспомогательные функции | -| `kvs_encoder.py` | Фасад кодировщика (объединяет fixsize и mutsize) | +| `kvs_build.py` | Главный сборочный скрипт | +| `kvs_lexer.py` | Лексический анализ | +| `kvs_parser.py` | Синтаксический анализ, построение AST | +| `kvs_data.py` | Общие данные: регистры, таблица инструкций | +| `kvs_encoder.py` | Фасад кодировщика | | `kvs_encoder_fixsize.py` | Кодировщик инструкций фиксированного размера | -| `kvs_encoder_mutsize.py` | Кодировщик инструкций переменного размера + диспетчер | -| `kvs_pass1.py` | Первый проход — генерация CSV с заглушками для неизвестных меток | -| `kvs_pass2.py` | Второй проход — разрешение меток (замена заглушек на реальные адреса) | -| `kvs_pass3.py` | Третий проход — подстановка констант из директив `.константа` | -| `kvs_pass4.py` | Четвёртый проход — перекодирование инструкций с подставленными значениями | -| `kvs_builder.py` | Сборка ELF: чтение CSV, заполнение байтов по адресам, установка прав на исполнение | - -### Почему 4 прохода? - -1. **Первый проход** — неизвестно, какие адреса будут у меток (метка может использоваться до определения). Вместо остановки ставим заглушки `0xCC` и продолжаем. -2. **Второй проход** — после того как все адреса меток известны, заменяем заглушки на реальные значения. -3. **Третий проход** — константы могут ссылаться друг на друга и на метки, поэтому их подстановка делается отдельно. -4. **Четвёртый проход** — после подстановки чисел (адресов/констант) нужно перегенерировать машинный код, так как разные операнды могут менять длину инструкции. +| `kvs_encoder_mutsize.py` | Кодировщик инструкций переменного размера | +| `kvs_pass1.py` | Генерация CSV с заглушками | +| `kvs_pass2.py` | Разрешение меток | +| `kvs_pass3.py` | Подстановка констант | +| `kvs_pass4.py` | Перекодирование инструкций | +| `kvs_pass5.py` | Генерация таблиц секций | +| `kvs_pass6.py` | Сортировка CSV | +| `kvs_builder.py` | Сборка ELF из CSV | ### Структура CSV-файла @@ -93,14 +105,14 @@ ELF-файл (.elf) |---------|-------------| | `сегмент` | Имя секции (`.header`, `.text`, `.data`, `.bss`) | | `адрес` | Файловое смещение байта | -| `виртуальный_адрес` | Виртуальный адрес байта в памяти (для вычисления смещений) | +| `виртуальный_адрес` | Виртуальный адрес байта в памяти | | `байт` | Исходное значение байта (из pass1) | -| `приводящая_метка` | Имена меток, находящихся на этом адресе (через запятую) | +| `приводящая_метка` | Имена меток на этом адресе | | `уводящий_адрес` | Строка `ЗАГЛУШКА имя` для неразрешённых меток | -| `исходная_команда` | Исходная инструкция (например, `переход метка`) | -| `команда_со_значениями` | Инструкция с подставленными адресами/константами (pass2/pass3) | -| `рассчитанный_уводящий_адрес` | Реальный адрес метки (файловое смещение) после разрешения | -| `рассчитанный_байт` | Итоговый байт после перекодирования (pass4) | +| `исходная_команда` | Исходная инструкция | +| `команда_со_значениями` | Инструкция с подставленными адресами/константами | +| `рассчитанный_уводящий_адрес` | Реальный адрес метки после разрешения | +| `рассчитанный_байт` | Итоговый байт после перекодирования | ## 🔧 Установка @@ -109,7 +121,7 @@ ELF-файл (.elf) git clone https://github.com/artradeskz/kvs.git cd kvs -# Установка прав на выполнение (при необходимости) +# Установка прав на выполнение chmod +x kvs_build.py # Проверка работы @@ -125,11 +137,11 @@ python3 kvs_build.py --help .глобал _start _start: - ; write(1, msg, 13) + ; write(1, msg, 21) переместить_имм раикс,1 переместить_имм рдиай,1 переместить_имм рсиай, msg - переместить_имм рдикс,13 + переместить_имм рдикс,21 вызов_системы ; exit(0) @@ -138,7 +150,7 @@ _start: вызов_системы .данные -msg: .строка_нуль "Hello, World!" +msg: .строка "Привет мир!\n" ``` ### 2. Скомпилируйте: @@ -156,9 +168,63 @@ python3 kvs_build.py hello.квс **Вывод:** ``` -Hello, World! +Привет мир! ``` +## 🧪 Система тестирования + +Проект включает автоматическую систему регрессионного тестирования, которая проверяет все аспекты работы ассемблера. + +### Структура тестов + +``` +тесты/ +├── kvs_full_test.py # диспетчер тестов +├── kvs_full_test.csv # таблица активных тестов +├── cleaner.py # очистка артефактов +├── привет/ # тест вывода строки +│ ├── привет.квс +│ └── привет.py +├── сложный_тест/ # тест запутанных переходов +├── длин_тест/ # тест длинных переходов (jmp rel32) +├── вывод_аргумента/ # тест аргументов командной строки +├── тест_адресации_р8/ # косвенная адресация через R8 +├── тест_адресации1/ # абсолютная адресация по числовому адресу +├── тест_адресации2/ # абсолютная адресация по метке +├── тест_адресации3/ # косвенная адресация через RBX +└── тест_бнд/ # проверка BSS секции (.bss) +``` + +### Запуск тестов + +```bash +python3 тесты/kvs_full_test.py +``` + +### Добавление нового теста + +1. Создать папку `тесты/имя_теста/` +2. Положить туда `имя_теста.квс` и `имя_теста.py` +3. Добавить строку в `kvs_full_test.csv`: + ``` + 1;имя_теста;Описание теста + ``` +4. Запустить `python3 тесты/kvs_full_test.py` + +### Что проверяют тесты + +| Тест | Проверяет | +|------|-----------| +| `привет` | Вывод строки на русском, exit(0) | +| `сложный_тест` | Короткие условные переходы | +| `длин_тест` | Длинные переходы (jmp rel32) | +| `вывод_аргумента` | Аргументы командной строки (2 сценария) | +| `тест_адресации_р8` | Косвенную адресацию через R8 | +| `тест_адресации1` | Абсолютную адресацию `[0x402000]` | +| `тест_адресации2` | Абсолютную адресацию по метке | +| `тест_адресации3` | Косвенную адресацию через RBX | +| `тест_бнд` | BSS секцию: NOBITS, размер, p_memsz | + ## 📝 Синтаксис ### Структура программы @@ -176,8 +242,6 @@ Hello, World! ### Метки -Метка — это имя, за которым следует двоеточие: - ```assembly _start: ; метка _start переместить_имм раикс,1 @@ -186,8 +250,6 @@ _start: ; метка _start ### Комментарии -Однострочные комментарии начинаются с `;`: - ```assembly ; Это комментарий переместить_имм раикс,42 ; комментарий после инструкции @@ -276,10 +338,6 @@ _start: ; метка _start | `переход_если_перенос` | CF = 1 | | `переход_если_нет_переноса` | CF = 0 | -**Короткие условные переходы** (префикс `короткий_`): -- `короткий_переход_если_равно`, `короткий_переход_если_неравно`, и т.д. -- Генерируют 2-байтовые инструкции (смещение ±127 байт) - ### Системные вызовы | Инструкция | Описание | @@ -334,12 +392,9 @@ _start: ; метка _start .глобал _start _start: - ; Запись значения в переменную переместить_имм раикс,42 сохранить [переменная], раикс - ; Чтение значения из переменной загрузить рбикс,[переменная] - ; Завершение с кодом возврата переместить_имм раикс,60 загрузить рдиай,[переменная] вызов_системы @@ -355,7 +410,7 @@ _start: .глобал _start _start: - переместить_имм рсикс,10 ; счетчик = 10 + переместить_имм рсикс,10 loop: переместить_имм раикс,1 @@ -363,10 +418,9 @@ loop: переместить_имм рсиай, msg переместить_имм рдикс,10 вызов_системы - - уменьшить рсикс ; счетчик-- + уменьшить рсикс сравнить_с рсикс,0 - переход_если_больше loop ; если > 0, продолжаем + переход_если_больше loop переместить_имм раикс,60 переместить_имм рдиай,0 @@ -383,8 +437,8 @@ msg: .строка "Iteration\n" .глобал _start _start: - переместить_имм р8,0 ; сумма - переместить_имм р9,10 ; счётчик + переместить_имм р8,0 + переместить_имм р9,10 loop: прибавить р8, р9 @@ -392,7 +446,6 @@ loop: сравнить_с р9,0 переход_если_больше loop - ; exit(сумма в r8) переместить_имм раикс,60 переместить рдикс, р8 вызов_системы @@ -405,12 +458,10 @@ loop: .глобал _start _start: - ; инициализация счётчика в BND переместить_имм раикс,0 сохранить [счётчик], раикс - - ; цикл: увеличить счётчик 10 раз переместить_имм рсикс,10 + loop: загрузить раикс,[счётчик] увеличить раикс @@ -418,8 +469,7 @@ loop: уменьшить рсикс сравнить_с рсикс,0 переход_если_больше loop - - ; выход с кодом = значение счётчика + загрузить рдиай,[счётчик] переместить_имм раикс,60 вызов_системы @@ -435,17 +485,10 @@ loop: .глобал _start _start: - ; загружаем адрес переменной в регистр переместить_имм рбикс, переменная - - ; косвенная запись переместить_имм раикс,99 сохранить [рбикс], раикс - - ; косвенное чтение загрузить раикс,[рбикс] - - ; выход переместить_имм рдиай, раикс переместить_имм раикс,60 вызов_системы @@ -454,6 +497,51 @@ _start: переменная: .байт 0 ``` +### Вывод аргумента командной строки + +```assembly +.текст +.глобал _start + +_start: + сравнить_с рдиай, 2 + переход_если_не_равно no_arg + + загрузить р8, [рсиай + 8] + переместить рдиай, р8 + исключающее_или рдикс, рдикс + +strlen: + загрузить ал, [р8] + сравнить_с ал, 0 + переход_если_равно strlen_done + увеличить р8 + увеличить рдикс + переход strlen + +strlen_done: + переместить_имм раикс, 1 + переместить рсиай, рдиай + переместить_имм рдиай, 1 + вызов_системы + исключающее_или рдиай, рдиай + переместить_имм раикс, 60 + вызов_системы + +no_arg: + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_no_arg + переместить_имм рдикс, 26 + вызов_системы + переместить_имм рдиай, 1 + переместить_имм раикс, 60 + вызов_системы + +.данные +msg_no_arg: .строка "Ошибка не передан аргумент\n" +``` + ## 🐛 Отладка и проверка ### Просмотр сгенерированного кода @@ -474,13 +562,7 @@ strings программа.elf ### CSV-лог -После компиляции создаётся файл `программа.csv` со структурой: - -| адрес | виртуальный_адрес | байт | приводящая_метка | исходная_команда | -|-------|-------------------|------|------------------|------------------| -| 0x1000 | 0x401000 | 48 | _start | переместить_имм раикс, 10 | -| 0x1001 | 0x401001 | B8 | | | -| ... | ... | ... | ... | ... | +После компиляции создаётся файл `программа.csv` с полной информацией о каждом байте программы. ### Трассировка системных вызовов @@ -493,11 +575,11 @@ strace ./программа.elf ```bash gdb ./программа.elf -(gdb) break _start ; установить точку останова -(gdb) run ; запустить -(gdb) info registers ; показать регистры -(gdb) x/10i $rip ; показать 10 инструкций -(gdb) stepi ; выполнить одну инструкцию +(gdb) break _start +(gdb) run +(gdb) info registers +(gdb) x/10i $rip +(gdb) stepi ``` ## ⚠️ Ограничения @@ -519,11 +601,13 @@ gdb ./программа.elf - Абсолютная адресация по меткам и числовым адресам `[метка]` / `[0x402000]` - Косвенная адресация через регистр `[раикс]` - RIP-relative кодирование для всех видов адресации +- Полная совместимость с `objdump`, `readelf`, `gdb` ### Не поддерживается (в разработке) - Адресация со смещением `[reg + disp]` -- Сложная адресация с индексом `[reg + reg*scale]` и полная SIB-адресация +- Сложная адресация с индексом `[reg + reg*scale]` +- Полная SIB-адресация `[reg + reg*scale + disp]` - Поддержка плавающей запятой (FPU/SSE) - Многомодульная компиляция - Макросы @@ -532,19 +616,15 @@ gdb ./программа.elf ### Реализовано (v2.0) -- Лексер с токенами `[`, `]`, `NUMBER`, `+`, `-`, `*` +- Лексер с поддержкой `[]`, `+`, `-`, `*` - Парсер с поддержкой операндов в квадратных скобках -- Четырёхпроходная схема сборки -- Первый проход с корректным расчётом размера инструкций с памятью -- Второй проход с разрешением меток -- Третий проход с подстановкой констант -- Четвёртый проход с перекодированием инструкций +- Шестипроходная схема сборки (включая генерацию таблиц секций и сортировку CSV) - Поддержка меток в операндах памяти `[переменная]` - Поддержка числовых адресов `[0x402000]` - Поддержка косвенной адресации `[регистр]` - Секция `.бнд` для неинициализированных данных -- Вынесение кодировщика в отдельный модуль -- Колонка `виртуальный_адрес` в CSV для точного вычисления смещений +- Автоматическая система регрессионного тестирования (9+ тестов) +- Полная совместимость со стандартным toolchain Linux ### В планах (v3.0) diff --git a/особенности b/особенности deleted file mode 100644 index 0cedf17..0000000 --- a/особенности +++ /dev/null @@ -1,11 +0,0 @@ - Косвенная адресация в КВС работает только через регистр R8 (и возможно R9–R15 не работают или работают частично). - - Инструкции, которые работают: - вытолкнуть (pop) - загрузить ал, [р8] (загрузка байта из памяти) - сравнить_с ал, 0 (сравнение с константой) - увеличить, переместить, исключающее_или - вызов_системы (syscall) - - Строки в .данные не должны содержать символ : (двоеточие) — парсер обрезает. - diff --git a/тесты/сравнитель/compare_elf.sh b/тесты/_сравнитель/compare_elf.sh similarity index 100% rename from тесты/сравнитель/compare_elf.sh rename to тесты/_сравнитель/compare_elf.sh diff --git a/тесты/kvs_full_test.csv b/тесты/kvs_full_test.csv new file mode 100644 index 0000000..cef0f9b --- /dev/null +++ b/тесты/kvs_full_test.csv @@ -0,0 +1,10 @@ +активен;папка;описание +1;привет;Проверка вывода "Привет мир!" и кода возврата 0 +1;сложный_тест;Проверка запутанных переходов: должно вывести "Живой из 3!" и "Успех!" +1;длин_тест;Проверка длинных переходов (jmp rel32): должно вывести "Живой из 3!" и "Успех!" +1;вывод_аргумента;Проверка вывода аргумента командной строки (2 сценария: с аргументом и без) +1;тест_адресации_р8;Проверка косвенной адресации через R8 (ASCII код первого символа аргумента) +1;тест_адресации1;Проверка абсолютной адресации по числовому адресу [0x402000] (код возврата 42) +1;тест_адресации2;Проверка абсолютной адресации по метке [переменная] (код возврата 43) +1;тест_адресации3;Проверка косвенной адресации через регистр [рбикс] (код возврата 99) +1;тест_бнд;Проверка BSS (.бнд): размер файла мал (<100KB), секция .bss тип NOBITS размер 1MB, сегмент LOAD c p_memsz>=1MB, код возврата 0 \ No newline at end of file diff --git a/тесты/kvs_full_test.py b/тесты/kvs_full_test.py new file mode 100644 index 0000000..74a584c --- /dev/null +++ b/тесты/kvs_full_test.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import csv +from pathlib import Path + +def main(): + # Диспетчер лежит в тесты/kvs_full_test.py + # Значит, корень с табличкой — та же папка (тесты/) + tests_dir = Path(__file__).parent + csv_file = tests_dir / "kvs_full_test.csv" + + if not csv_file.exists(): + print(f"❌ Нет файла {csv_file}") + print(" Создайте его с колонками: активен;папка;описание") + sys.exit(1) + + passed = 0 + failed = 0 + skipped = 0 + results = [] + + # Читаем табличку + with open(csv_file, 'r', encoding='utf-8') as f: + reader = csv.DictReader(f, delimiter=';') + for row in reader: + active = row.get('активен', '0').strip() + folder = row.get('папка', '').strip() + description = row.get('описание', '').strip() + + if not folder: + continue + + if active != '1': + print(f"\n⏭️ {folder}: пропуск (отключён)") + skipped += 1 + continue + + test_dir = tests_dir / folder + checker = test_dir / f"{folder}.py" + + if not test_dir.exists(): + print(f"\n❌ {folder}: папка не найдена") + failed += 1 + results.append((folder, "FAIL", "папка отсутствует")) + continue + + if not checker.exists(): + print(f"\n❌ {folder}: нет {folder}.py") + failed += 1 + results.append((folder, "FAIL", "нет проверяльщика")) + continue + + # Запуск теста + print(f"\n📝 {folder}: {description}") + print(" ", end="", flush=True) + + result = subprocess.run([sys.executable, checker], + cwd=test_dir, + capture_output=True, + text=True) + + if result.returncode == 0: + print("✅") + passed += 1 + results.append((folder, "PASS", "")) + else: + print("❌") + failed += 1 + # Показываем ВСЮ отладочную информацию + print(f"\n --- stdout теста ---") + if result.stdout: + for line in result.stdout.strip().split('\n'): + print(f" {line}") + else: + print(" (пусто)") + print(f" --- stderr теста ---") + if result.stderr: + for line in result.stderr.strip().split('\n'): + print(f" {line}") + else: + print(" (пусто)") + print(f" --------------------") + results.append((folder, "FAIL", result.stderr.strip().split('\n')[0] if result.stderr else "ошибка")) + + # Итоги + print(f"\n{'='*50}") + print(f"✅ Пройдено: {passed}") + print(f"❌ Провалено: {failed}") + print(f"⏭️ Пропущено: {skipped}") + + if failed > 0: + print(f"\nПроваленные тесты:") + for name, status, msg in results: + if status == "FAIL": + print(f" • {name}: {msg}") + sys.exit(1) + else: + print(f"\n🎉 Все активные тесты пройдены!") + sys.exit(0) + +if __name__ == "__main__": + # Очистка перед запуском (отключено, чтобы не удалять табличку) + # subprocess.run([sys.executable, "cleaner.py"], + # cwd=Path(__file__).parent, + # capture_output=True) + main() \ No newline at end of file diff --git a/тесты/вывод_аргумента/вывод_аргумента.py b/тесты/вывод_аргумента/вывод_аргумента.py new file mode 100644 index 0000000..d8c6ddb --- /dev/null +++ b/тесты/вывод_аргумента/вывод_аргумента.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +PROJECT_ROOT = SCRIPT_DIR.parent.parent +SOURCE_REL = SCRIPT_DIR / "вывод_аргумента.квс" + +def clean(): + files = [ + "вывод_аргумента.elf", + "вывод_аргумента.csv", + "вывод_аргумента.аст", + "вывод_аргумента.токены", + "вывод_аргумента.константа", + "вывод_аргумента_pass4.log" + ] + for f in files: + f_path = SCRIPT_DIR / f + if f_path.exists(): + f_path.unlink() + +def build(): + os.chdir(PROJECT_ROOT) + build = subprocess.run(["python3", "kvs_build.py", str(SOURCE_REL)], + capture_output=True, text=True) + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(build.stderr, file=sys.stderr) + return False + return True + +def test_no_arg(): + """Сценарий 1: запуск без аргумента""" + print("DEBUG: Тест без аргумента", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + elf_path = SCRIPT_DIR / "вывод_аргумента.elf" + if not elf_path.exists(): + print("❌ ELF файл не найден", file=sys.stderr) + return False + + run = subprocess.run([str(elf_path)], capture_output=True, text=True) + + # Ожидаемый вывод с нулевым байтом (как в сложном_тесте) + expected_output = "Ошибка не передан аргумент\n\x00" + + if run.stdout != expected_output: + print(f"❌ Без аргумента: вывод не совпадает", file=sys.stderr) + print(f" Ожидалось: {repr(expected_output)}", file=sys.stderr) + print(f" Получено: {repr(run.stdout)}", file=sys.stderr) + return False + + # Код возврата должен быть 1 (ошибка) + if run.returncode != 1: + print(f"❌ Без аргумента: код возврата {run.returncode} (ожидался 1)", file=sys.stderr) + return False + + print("DEBUG: Тест без аргумента пройден", file=sys.stderr) + return True + +def test_with_arg(): + """Сценарий 2: запуск с аргументом""" + print("DEBUG: Тест с аргументом", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + test_arg = "ccsfghdfngbf" + elf_path = SCRIPT_DIR / "вывод_аргумента.elf" + + run = subprocess.run([str(elf_path), test_arg], capture_output=True, text=True) + + # Ожидаемый вывод = переданный аргумент (без \n и без \x00) + expected_output = test_arg + + if run.stdout != expected_output: + print(f"❌ С аргументом: вывод не совпадает", file=sys.stderr) + print(f" Аргумент: {test_arg}", file=sys.stderr) + print(f" Ожидалось: {repr(expected_output)}", file=sys.stderr) + print(f" Получено: {repr(run.stdout)}", file=sys.stderr) + return False + + # Код возврата должен быть 0 + if run.returncode != 0: + print(f"❌ С аргументом: код возврата {run.returncode} (ожидался 0)", file=sys.stderr) + return False + + print("DEBUG: Тест с аргументом пройден", file=sys.stderr) + return True + +def main(): + print("DEBUG: Начало теста вывод_аргумента", file=sys.stderr) + + if not build(): + clean() + sys.exit(1) + + no_arg_ok = test_no_arg() + with_arg_ok = test_with_arg() + + clean() + + if no_arg_ok and with_arg_ok: + print("✅", file=sys.stderr) + sys.exit(0) + else: + print("❌", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/вывод_аргумента.квс b/тесты/вывод_аргумента/вывод_аргумента.квс similarity index 100% rename from тесты/вывод_аргумента.квс rename to тесты/вывод_аргумента/вывод_аргумента.квс diff --git a/тесты/длин_тест/длин_тест.py b/тесты/длин_тест/длин_тест.py new file mode 100644 index 0000000..a709211 --- /dev/null +++ b/тесты/длин_тест/длин_тест.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os + +# Пути относительно папки длин_тест/ +PROJECT_ROOT = "../../" +SOURCE_REL = "тесты/длин_тест/длин_тест.квс" + +def clean(): + """Удалить временные файлы после теста""" + files = [ + "длин_тест.elf", + "длин_тест.csv", + "длин_тест.аст", + "длин_тест.токены", + "длин_тест.константа", + "длин_тест_pass4.log" + ] + for f in files: + if os.path.exists(f): + os.remove(f) + +def main(): + print("DEBUG: Начало теста длин_тест", file=sys.stderr) + + os.chdir(PROJECT_ROOT) + print(f"DEBUG: Рабочая папка = {os.getcwd()}", file=sys.stderr) + + # 1. Сборка + print("DEBUG: Сборка длин_тест.квс", file=sys.stderr) + build = subprocess.run(["python3", "kvs_build.py", SOURCE_REL], + capture_output=True, text=True) + + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(f"STDERR: {build.stderr}", file=sys.stderr) + clean() + sys.exit(1) + + print("DEBUG: Сборка успешна", file=sys.stderr) + + os.chdir("тесты/длин_тест") + + if not os.path.exists("длин_тест.elf"): + print("❌ Файл длин_тест.elf не создан", file=sys.stderr) + clean() + sys.exit(1) + + print("DEBUG: Запуск ELF", file=sys.stderr) + run = subprocess.run(["./длин_тест.elf"], capture_output=True, text=True) + + print(f"DEBUG: Код возврата = {run.returncode}", file=sys.stderr) + print(f"DEBUG: stdout = {repr(run.stdout)}", file=sys.stderr) + + # Ожидаемый вывод (с \x00, как в сложном_тесте) + expected_output = "Живой из 3!Успех!\n" + + if run.stdout != expected_output: + print(f"❌ Вывод не совпадает", file=sys.stderr) + print(f" Ожидалось: {repr(expected_output)}", file=sys.stderr) + print(f" Получено: {repr(run.stdout)}", file=sys.stderr) + clean() + sys.exit(1) + + if run.returncode != 0: + print(f"❌ Код возврата: ожидался 0, получен {run.returncode}", file=sys.stderr) + clean() + sys.exit(1) + + print("✅", file=sys.stderr) + clean() + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/длин_тест.квс b/тесты/длин_тест/длин_тест.квс similarity index 100% rename from тесты/длин_тест.квс rename to тесты/длин_тест/длин_тест.квс diff --git a/тесты/привет/привет.py b/тесты/привет/привет.py new file mode 100644 index 0000000..9a36970 --- /dev/null +++ b/тесты/привет/привет.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os + +# Пути относительно папки привет/ +PROJECT_ROOT = "../../" # корень kvs/ +SOURCE_REL = "тесты/привет/привет.квс" +ELF_REL = "тесты/привет/привет.elf" + +def clean(): + """Удалить временные файлы после теста""" + files = [ + "привет.elf", + "привет.csv", + "привет.аст", + "привет.токены", + "привет.константа", + "привет_pass4.log" + ] + for f in files: + if os.path.exists(f): + os.remove(f) + +def main(): + print("DEBUG: Начало теста привет", file=sys.stderr) + + # Переходим в корень проекта для сборки + os.chdir(PROJECT_ROOT) + print(f"DEBUG: Рабочая папка = {os.getcwd()}", file=sys.stderr) + + # 1. Сборка (запускаем из корня) + print("DEBUG: Сборка привет.квс", file=sys.stderr) + build = subprocess.run(["python3", "kvs_build.py", SOURCE_REL], + capture_output=True, text=True) + + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(f"STDERR: {build.stderr}", file=sys.stderr) + print(f"STDOUT: {build.stdout}", file=sys.stderr) + clean() + sys.exit(1) + + print("DEBUG: Сборка успешна", file=sys.stderr) + + # Возвращаемся в папку привет для запуска + os.chdir("тесты/привет") + + # 2. Проверка, что ELF создан + if not os.path.exists("привет.elf"): + print("❌ Файл привет.elf не создан", file=sys.stderr) + clean() + sys.exit(1) + + print("DEBUG: ELF файл существует", file=sys.stderr) + + # 3. Запуск и проверка + print("DEBUG: Запуск ELF", file=sys.stderr) + run = subprocess.run(["./привет.elf"], capture_output=True, text=True) + + print(f"DEBUG: Код возврата = {run.returncode}", file=sys.stderr) + print(f"DEBUG: stdout = {repr(run.stdout)}", file=sys.stderr) + + # Проверка вывода + expected_output = "Привет мир!\n" + if run.stdout != expected_output: + print(f"❌ Вывод не совпадает", file=sys.stderr) + print(f" Ожидалось: {repr(expected_output)}", file=sys.stderr) + print(f" Получено: {repr(run.stdout)}", file=sys.stderr) + clean() + sys.exit(1) + + # Проверка кода возврата + if run.returncode != 0: + print(f"❌ Код возврата: ожидался 0, получен {run.returncode}", file=sys.stderr) + clean() + sys.exit(1) + + # Успех + print("✅", file=sys.stderr) + clean() + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/привет.квс b/тесты/привет/привет.квс similarity index 100% rename from тесты/привет.квс rename to тесты/привет/привет.квс diff --git a/тесты/сложный_тест/сложный_тест.py b/тесты/сложный_тест/сложный_тест.py new file mode 100644 index 0000000..6a3b1fc --- /dev/null +++ b/тесты/сложный_тест/сложный_тест.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os + +# Пути относительно папки сложный_тест/ +PROJECT_ROOT = "../../" # корень kvs/ +SOURCE_REL = "тесты/сложный_тест/сложный_тест.квс" + +def clean(): + """Удалить временные файлы после теста""" + files = [ + "сложный_тест.elf", + "сложный_тест.csv", + "сложный_тест.аст", + "сложный_тест.токены", + "сложный_тест.константа", + "сложный_тест_pass4.log" + ] + for f in files: + if os.path.exists(f): + os.remove(f) + +def main(): + print("DEBUG: Начало теста сложный_тест", file=sys.stderr) + + # Переходим в корень проекта для сборки + os.chdir(PROJECT_ROOT) + print(f"DEBUG: Рабочая папка = {os.getcwd()}", file=sys.stderr) + + # 1. Сборка + print("DEBUG: Сборка сложный_тест.квс", file=sys.stderr) + build = subprocess.run(["python3", "kvs_build.py", SOURCE_REL], + capture_output=True, text=True) + + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(f"STDERR: {build.stderr}", file=sys.stderr) + print(f"STDOUT: {build.stdout}", file=sys.stderr) + clean() + sys.exit(1) + + print("DEBUG: Сборка успешна", file=sys.stderr) + + # Возвращаемся в папку сложный_тест для запуска + os.chdir("тесты/сложный_тест") + + # 2. Проверка, что ELF создан + if not os.path.exists("сложный_тест.elf"): + print("❌ Файл сложный_тест.elf не создан", file=sys.stderr) + clean() + sys.exit(1) + + print("DEBUG: ELF файл существует", file=sys.stderr) + + # 3. Запуск и проверка + print("DEBUG: Запуск ELF", file=sys.stderr) + run = subprocess.run(["./сложный_тест.elf"], capture_output=True, text=True) + + print(f"DEBUG: Код возврата = {run.returncode}", file=sys.stderr) + print(f"DEBUG: stdout = {repr(run.stdout)}", file=sys.stderr) + + # Проверка вывода (должно быть два сообщения: "Живой из 3!" и "Успех!") + expected_output = "Живой из 3!\nУспех!\n\x00" + + if run.stdout != expected_output: + print(f"❌ Вывод не совпадает", file=sys.stderr) + print(f" Ожидалось: {repr(expected_output)}", file=sys.stderr) + print(f" Получено: {repr(run.stdout)}", file=sys.stderr) + clean() + sys.exit(1) + + # Проверка кода возврата + if run.returncode != 0: + print(f"❌ Код возврата: ожидался 0, получен {run.returncode}", file=sys.stderr) + clean() + sys.exit(1) + + # Успех + print("✅", file=sys.stderr) + clean() + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/сложный_тест.квс b/тесты/сложный_тест/сложный_тест.квс similarity index 100% rename from тесты/сложный_тест.квс rename to тесты/сложный_тест/сложный_тест.квс diff --git a/тесты/тест_адресации1/тест_адресации1.py b/тесты/тест_адресации1/тест_адресации1.py new file mode 100644 index 0000000..1c8016b --- /dev/null +++ b/тесты/тест_адресации1/тест_адресации1.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +PROJECT_ROOT = SCRIPT_DIR.parent.parent +SOURCE_REL = SCRIPT_DIR / "тест_адресации1.квс" + +def clean(): + files = [ + "тест_адресации1.elf", + "тест_адресации1.csv", + "тест_адресации1.аст", + "тест_адресации1.токены", + "тест_адресации1.константа", + "тест_адресации1_pass4.log" + ] + for f in files: + f_path = SCRIPT_DIR / f + if f_path.exists(): + f_path.unlink() + +def build(): + os.chdir(PROJECT_ROOT) + build = subprocess.run(["python3", "kvs_build.py", str(SOURCE_REL)], + capture_output=True, text=True) + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(build.stderr, file=sys.stderr) + return False + return True + +def test_absolute_addressing(): + """Проверка абсолютной адресации по числовому адресу""" + print("DEBUG: Тест абсолютной адресации [0x402000]", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + elf_path = SCRIPT_DIR / "тест_адресации1.elf" + if not elf_path.exists(): + print("❌ ELF файл не найден", file=sys.stderr) + return False + + run = subprocess.run([str(elf_path)], capture_output=True, text=True) + + # Ожидаемый код возврата = 42 + expected_code = 42 + + if run.returncode != expected_code: + print(f"❌ Код возврата: ожидался {expected_code}, получен {run.returncode}", file=sys.stderr) + return False + + print("DEBUG: Тест абсолютной адресации пройден", file=sys.stderr) + return True + +def main(): + print("DEBUG: Начало теста тест_адресации1", file=sys.stderr) + + if not build(): + clean() + sys.exit(1) + + if test_absolute_addressing(): + clean() + print("✅", file=sys.stderr) + sys.exit(0) + else: + clean() + print("❌", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/тест_адресации1.квс b/тесты/тест_адресации1/тест_адресации1.квс similarity index 100% rename from тесты/тест_адресации1.квс rename to тесты/тест_адресации1/тест_адресации1.квс diff --git a/тесты/тест_адресации2/тест_адресации2.py b/тесты/тест_адресации2/тест_адресации2.py new file mode 100644 index 0000000..4a5b6dc --- /dev/null +++ b/тесты/тест_адресации2/тест_адресации2.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +PROJECT_ROOT = SCRIPT_DIR.parent.parent +SOURCE_REL = SCRIPT_DIR / "тест_адресации2.квс" + +def clean(): + files = [ + "тест_адресации2.elf", + "тест_адресации2.csv", + "тест_адресации2.аст", + "тест_адресации2.токены", + "тест_адресации2.константа", + "тест_адресации2_pass4.log" + ] + for f in files: + f_path = SCRIPT_DIR / f + if f_path.exists(): + f_path.unlink() + +def build(): + os.chdir(PROJECT_ROOT) + build = subprocess.run(["python3", "kvs_build.py", str(SOURCE_REL)], + capture_output=True, text=True) + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(build.stderr, file=sys.stderr) + return False + return True + +def test_label_addressing(): + """Проверка абсолютной адресации по метке""" + print("DEBUG: Тест абсолютной адресации по метке [переменная]", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + elf_path = SCRIPT_DIR / "тест_адресации2.elf" + if not elf_path.exists(): + print("❌ ELF файл не найден", file=sys.stderr) + return False + + run = subprocess.run([str(elf_path)], capture_output=True, text=True) + + # Ожидаемый код возврата = 43 + expected_code = 43 + + if run.returncode != expected_code: + print(f"❌ Код возврата: ожидался {expected_code}, получен {run.returncode}", file=sys.stderr) + return False + + print("DEBUG: Тест абсолютной адресации по метке пройден", file=sys.stderr) + return True + +def main(): + print("DEBUG: Начало теста тест_адресации2", file=sys.stderr) + + if not build(): + clean() + sys.exit(1) + + if test_label_addressing(): + clean() + print("✅", file=sys.stderr) + sys.exit(0) + else: + clean() + print("❌", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/тест_адресации2.квс b/тесты/тест_адресации2/тест_адресации2.квс similarity index 100% rename from тесты/тест_адресации2.квс rename to тесты/тест_адресации2/тест_адресации2.квс diff --git a/тесты/тест_адресации3/тест_адресации3.py b/тесты/тест_адресации3/тест_адресации3.py new file mode 100644 index 0000000..884df24 --- /dev/null +++ b/тесты/тест_адресации3/тест_адресации3.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +PROJECT_ROOT = SCRIPT_DIR.parent.parent +SOURCE_REL = SCRIPT_DIR / "тест_адресации3.квс" + +def clean(): + files = [ + "тест_адресации3.elf", + "тест_адресации3.csv", + "тест_адресации3.аст", + "тест_адресации3.токены", + "тест_адресации3.константа", + "тест_адресации3_pass4.log" + ] + for f in files: + f_path = SCRIPT_DIR / f + if f_path.exists(): + f_path.unlink() + +def build(): + os.chdir(PROJECT_ROOT) + build = subprocess.run(["python3", "kvs_build.py", str(SOURCE_REL)], + capture_output=True, text=True) + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(build.stderr, file=sys.stderr) + return False + return True + +def test_indirect_addressing(): + """Проверка косвенной адресации через регистр""" + print("DEBUG: Тест косвенной адресации через регистр [рбикс]", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + elf_path = SCRIPT_DIR / "тест_адресации3.elf" + if not elf_path.exists(): + print("❌ ELF файл не найден", file=sys.stderr) + return False + + run = subprocess.run([str(elf_path)], capture_output=True, text=True) + + # Ожидаемый код возврата = 99 + expected_code = 99 + + if run.returncode != expected_code: + print(f"❌ Код возврата: ожидался {expected_code}, получен {run.returncode}", file=sys.stderr) + return False + + print("DEBUG: Тест косвенной адресации через регистр пройден", file=sys.stderr) + return True + +def main(): + print("DEBUG: Начало теста тест_адресации3", file=sys.stderr) + + if not build(): + clean() + sys.exit(1) + + if test_indirect_addressing(): + clean() + print("✅", file=sys.stderr) + sys.exit(0) + else: + clean() + print("❌", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/тест_адресации3.квс b/тесты/тест_адресации3/тест_адресации3.квс similarity index 100% rename from тесты/тест_адресации3.квс rename to тесты/тест_адресации3/тест_адресации3.квс diff --git a/тесты/тест_адресации_р8/тест_адресации_р8.py b/тесты/тест_адресации_р8/тест_адресации_р8.py new file mode 100644 index 0000000..b2caf66 --- /dev/null +++ b/тесты/тест_адресации_р8/тест_адресации_р8.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +PROJECT_ROOT = SCRIPT_DIR.parent.parent +SOURCE_REL = SCRIPT_DIR / "тест_адресации_р8.квс" + +def clean(): + files = [ + "тест_адресации_р8.elf", + "тест_адресации_р8.csv", + "тест_адресации_р8.аст", + "тест_адресации_р8.токены", + "тест_адресации_р8.константа", + "тест_адресации_р8_pass4.log" + ] + for f in files: + f_path = SCRIPT_DIR / f + if f_path.exists(): + f_path.unlink() + +def build(): + os.chdir(PROJECT_ROOT) + build = subprocess.run(["python3", "kvs_build.py", str(SOURCE_REL)], + capture_output=True, text=True) + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(build.stderr, file=sys.stderr) + return False + return True + +def test_with_char(char, expected_code): + """Проверка с конкретным символом""" + print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + elf_path = SCRIPT_DIR / "тест_адресации_р8.elf" + if not elf_path.exists(): + print("❌ ELF файл не найден", file=sys.stderr) + return False + + run = subprocess.run([str(elf_path), char], capture_output=True, text=True) + + # Проверяем код возврата (должен быть ASCII код символа) + if run.returncode != expected_code: + print(f"❌ Код возврата: ожидался {expected_code}, получен {run.returncode}", file=sys.stderr) + return False + + print(f"DEBUG: Тест с символом '{char}' пройден", file=sys.stderr) + return True + +def main(): + print("DEBUG: Начало теста тест_адресации_р8", file=sys.stderr) + + if not build(): + clean() + sys.exit(1) + + # Тестируем разные символы + tests = [ + ('A', 65), + ('B', 66), + ('0', 48), + ('z', 122), + ('!', 33), + ] + + all_passed = True + for char, expected_code in tests: + if not test_with_char(char, expected_code): + all_passed = False + break + + clean() + + if all_passed: + print("✅", file=sys.stderr) + sys.exit(0) + else: + print("❌", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/тест_адресации_р8.квс b/тесты/тест_адресации_р8/тест_адресации_р8.квс similarity index 88% rename from тесты/тест_адресации_р8.квс rename to тесты/тест_адресации_р8/тест_адресации_р8.квс index c001020..4398225 100644 --- a/тесты/тест_адресации_р8.квс +++ b/тесты/тест_адресации_р8/тест_адресации_р8.квс @@ -2,7 +2,7 @@ ;echo $? # должен быть 65 ; ============================================================ -; тест_r8.квс - тест косвенной адресации через R8 +; тест_адресации_r8.квс - тест косвенной адресации через R8 ; ============================================================ .текст diff --git a/тесты/тест_бнд/тест_бнд.py b/тесты/тест_бнд/тест_бнд.py new file mode 100644 index 0000000..af2c706 --- /dev/null +++ b/тесты/тест_бнд/тест_бнд.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +import re +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.absolute() +PROJECT_ROOT = SCRIPT_DIR.parent.parent +SOURCE_REL = SCRIPT_DIR / "тест_бнд.квс" + +def clean(): + files = [ + "тест_бнд.elf", + "тест_бнд.csv", + "тест_бнд.аст", + "тест_бнд.токены", + "тест_бнд.константа", + "тест_бнд_pass4.log" + ] + for f in files: + f_path = SCRIPT_DIR / f + if f_path.exists(): + f_path.unlink() + +def build(): + os.chdir(PROJECT_ROOT) + build = subprocess.run(["python3", "kvs_build.py", str(SOURCE_REL)], + capture_output=True, text=True) + if build.returncode != 0: + print(f"❌ Ошибка сборки: {build.returncode}", file=sys.stderr) + print(build.stderr, file=sys.stderr) + return False + return True + +def test_file_size(): + """Проверка: файл должен быть маленьким (BSS не в файле)""" + print("DEBUG: Проверка размера файла", file=sys.stderr) + elf_path = SCRIPT_DIR / "тест_бнд.elf" + size = elf_path.stat().st_size + + max_size = 100 * 1024 # 100 КБ + if size >= max_size: + print(f"❌ Размер файла {size} байт (ожидалось < {max_size})", file=sys.stderr) + return False + + print(f"DEBUG: Размер файла {size} байт (OK)", file=sys.stderr) + return True + +def test_bss_section(): + """Проверка секции .bss через readelf -S""" + print("DEBUG: Проверка секции .bss", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + result = subprocess.run(["readelf", "-S", "тест_бнд.elf"], + capture_output=True, text=True) + + lines = result.stdout.split('\n') + expected_size = 1048576 # 1 МБ = 0x100000 + + for i, line in enumerate(lines): + if '.bss' in line and 'NOBITS' in line: + if i + 1 < len(lines): + size_line = lines[i + 1].strip() + match = re.search(r'^([0-9a-fA-F]+)', size_line) + if match: + size = int(match.group(1), 16) + if size == expected_size: + print(f"DEBUG: Размер .bss = {size} (OK)", file=sys.stderr) + return True + else: + print(f"❌ Размер .bss = {size} (ожидался {expected_size})", file=sys.stderr) + return False + + print("❌ Секция .bss не найдена или имеет неверный формат", file=sys.stderr) + return False + +def test_load_segment(): + """Проверка сегмента LOAD с BSS""" + print("DEBUG: Проверка сегмента LOAD", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + result = subprocess.run(["readelf", "-l", "тест_бнд.elf"], + capture_output=True, text=True) + + print("DEBUG: Вывод readelf -l:", file=sys.stderr) + print(result.stdout, file=sys.stderr) + + lines = result.stdout.split('\n') + expected_min = 1048576 + + # Поиск LOAD сегмента с RW + for i, line in enumerate(lines): + print(f"DEBUG: Строка {i}: {repr(line)}", file=sys.stderr) + if 'LOAD' in line and 'RW' in line: + print(f"DEBUG: Найден LOAD RW на строке {i}: {line}", file=sys.stderr) + # Ищем в следующих строках p_memsz + for j in range(i + 1, min(i + 5, len(lines))): + print(f"DEBUG: Строка {j}: {repr(lines[j])}", file=sys.stderr) + # Формат: 0x0000000000000001 0x0000000000101000 RW 0x1000 + matches = re.findall(r'0x([0-9a-fA-F]+)', lines[j]) + print(f"DEBUG: Найдено hex: {matches}", file=sys.stderr) + if len(matches) >= 2: + filesz = int(matches[0], 16) + memsz = int(matches[1], 16) + print(f"DEBUG: p_filesz = {filesz}, p_memsz = {memsz}", file=sys.stderr) + if memsz >= expected_min: + print(f"DEBUG: p_memsz = {memsz} (OK)", file=sys.stderr) + return True + else: + print(f"❌ p_memsz = {memsz} (ожидалось >= {expected_min})", file=sys.stderr) + return False + break + else: + # Альтернативный поиск: ищем по соответствию с .bss + print("DEBUG: LOAD RW не найден, ищем по соответствию с .bss", file=sys.stderr) + in_mapping = False + for i, line in enumerate(lines): + if 'Соответствие раздел-сегмент' in line: + in_mapping = True + print(f"DEBUG: Найдено 'Соответствие раздел-сегмент' на строке {i}", file=sys.stderr) + continue + if in_mapping and '.bss' in line: + print(f"DEBUG: Найдена строка с .bss: {line}", file=sys.stderr) + # Находим номер сегмента + seg_match = re.search(r'(\d+)', line) + if seg_match: + seg_num = int(seg_match.group(1)) + print(f"DEBUG: Сегмент {seg_num} содержит .bss", file=sys.stderr) + # Теперь ищем этот сегмент в заголовках + for k, l in enumerate(lines): + if f'LOAD' in l and k < i: + print(f"DEBUG: Найден LOAD на строке {k}: {l}", file=sys.stderr) + # Проверяем его размер + for m in range(k + 1, min(k + 5, len(lines))): + matches = re.findall(r'0x([0-9a-fA-F]+)', lines[m]) + if len(matches) >= 2: + memsz = int(matches[1], 16) + if memsz >= expected_min: + print(f"DEBUG: p_memsz = {memsz} (OK)", file=sys.stderr) + return True + break + + print("❌ Не найден LOAD сегмент с BSS", file=sys.stderr) + return False + +def test_execution(): + """Проверка: программа должна выполниться и вернуть 0""" + print("DEBUG: Проверка выполнения", file=sys.stderr) + os.chdir(SCRIPT_DIR) + + elf_path = SCRIPT_DIR / "тест_бнд.elf" + run = subprocess.run([str(elf_path)], capture_output=True, text=True) + + if run.returncode != 0: + print(f"❌ Код возврата {run.returncode} (ожидался 0)", file=sys.stderr) + return False + + print("DEBUG: Выполнение успешно", file=sys.stderr) + return True + +def main(): + print("DEBUG: Начало теста тест_бнд", file=sys.stderr) + + if not build(): + clean() + sys.exit(1) + + tests = [ + ("Размер файла", test_file_size), + ("Секция .bss", test_bss_section), + ("Сегмент LOAD", test_load_segment), + ("Выполнение", test_execution), + ] + + all_passed = True + for name, test_func in tests: + if not test_func(): + print(f"❌ Провалена проверка: {name}", file=sys.stderr) + all_passed = False + break + + clean() + + if all_passed: + print("✅", file=sys.stderr) + sys.exit(0) + else: + print("❌", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/тесты/тест_бнд.квс b/тесты/тест_бнд/тест_бнд.квс similarity index 100% rename from тесты/тест_бнд.квс rename to тесты/тест_бнд/тест_бнд.квс diff --git a/черновик.txt b/черновик.txt deleted file mode 100644 index bfa59a7..0000000 --- a/черновик.txt +++ /dev/null @@ -1,16 +0,0 @@ -Резюме: Почему нужна специальная обработка рспикс/р12икс и рбпикс/р13икс -В архитектуре x86-64 байт ModR/M имеет исторические ограничения. Определенные значения в поле r/m (младшие 3 бита) имеют двойное значение в зависимости от поля mod. Если это не учесть, энкодер сгенерирует байты, которые выглядят логично, но процессор декодирует их совершенно иначе. -🪤 Ловушка №1: рспикс / р12икс (индекс 4) - - Суть: В ModR/M значение r/m = 100 (десятичное 4) — это не код регистра. Это специальный сигнал процессору: "Следующий байт инструкции — это байт SIB (Scale-Index-Base)". - Что будет, если проигнорировать: Если закодировать [рспикс] просто как mod=00, r/m=4, процессор не остановится на этом байте. Он прочитает первый байт следующей инструкции (или данных) как байт SIB. Это вызовет сдвиг потока декодирования, превратив последующий код в бессмыслицу, что приведёт к SIGILL (недопустимая инструкция) или SIGSEGV. - Правильное решение: Для [рспикс] или [р12икс] энкодер обязан сгенерировать ModR/M с r/m=4, а сразу за ним — байт SIB 0x24 (где scale=00, index=100 (нет индекса), base=100 (rsp/r12)). - -🪤 Ловушка №2: рбпикс / р13икс (индекс 5) - - Суть: В 64-битном режиме комбинация mod=00 и r/m=101 (десятичное 5) зарезервирована исключительно под RIP-relative адресацию (например, [метка] для доступа к глобальным данным). - Что будет, если проигнорировать: Если попытаться закодировать честное [рбпикс] как mod=00, r/m=5, процессор проигнорирует тот факт, что вы хотели использовать регистр. Вместо этого он прочитает следующие 4 байта как 32-битное смещение относительно текущего указателя инструкций (RIP). Это приведёт к чтению мусора из памяти или крашу программы. - Правильное решение: Использовать mod=00 для этих регистров запрещено. Энкодер обязан принудительно переключиться на mod=01 (8-битное смещение) и добавить фиктивный байт смещения 0x00 сразу после ModR/M. Из-за этого инструкция [рбпикс] всегда на 1 байт длиннее, чем [раикс]. - -💡 Итог -Без этих двух проверок (if base == 4 и if base == 5) косвенная адресация через эти конкретные регистры будет тихо ломать выполнение программы. Это классические "грабли", на которые наступают почти все разработчики своих ассемблеров или JIT-компиляторов под x86-64. \ No newline at end of file