добавлена система тестирования
This commit is contained in:
parent
b273f42984
commit
a442f7a144
246
README.md
246
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)
|
||||
|
||||
|
|
|
|||
11
особенности
11
особенности
|
|
@ -1,11 +0,0 @@
|
|||
Косвенная адресация в КВС работает только через регистр R8 (и возможно R9–R15 не работают или работают частично).
|
||||
|
||||
Инструкции, которые работают:
|
||||
вытолкнуть (pop)
|
||||
загрузить ал, [р8] (загрузка байта из памяти)
|
||||
сравнить_с ал, 0 (сравнение с константой)
|
||||
увеличить, переместить, исключающее_или
|
||||
вызов_системы (syscall)
|
||||
|
||||
Строки в .данные не должны содержать символ : (двоеточие) — парсер обрезает.
|
||||
|
||||
10
тесты/kvs_full_test.csv
Normal file
10
тесты/kvs_full_test.csv
Normal file
|
|
@ -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
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 46.
|
107
тесты/kvs_full_test.py
Normal file
107
тесты/kvs_full_test.py
Normal file
|
|
@ -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()
|
||||
112
тесты/вывод_аргумента/вывод_аргумента.py
Normal file
112
тесты/вывод_аргумента/вывод_аргумента.py
Normal file
|
|
@ -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()
|
||||
76
тесты/длин_тест/длин_тест.py
Normal file
76
тесты/длин_тест/длин_тест.py
Normal file
|
|
@ -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()
|
||||
85
тесты/привет/привет.py
Normal file
85
тесты/привет/привет.py
Normal file
|
|
@ -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()
|
||||
85
тесты/сложный_тест/сложный_тест.py
Normal file
85
тесты/сложный_тест/сложный_тест.py
Normal file
|
|
@ -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()
|
||||
74
тесты/тест_адресации1/тест_адресации1.py
Normal file
74
тесты/тест_адресации1/тест_адресации1.py
Normal file
|
|
@ -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()
|
||||
74
тесты/тест_адресации2/тест_адресации2.py
Normal file
74
тесты/тест_адресации2/тест_адресации2.py
Normal file
|
|
@ -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()
|
||||
74
тесты/тест_адресации3/тест_адресации3.py
Normal file
74
тесты/тест_адресации3/тест_адресации3.py
Normal file
|
|
@ -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()
|
||||
87
тесты/тест_адресации_р8/тест_адресации_р8.py
Normal file
87
тесты/тест_адресации_р8/тест_адресации_р8.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_r8.квс - тест косвенной адресации через R8
|
||||
; тест_адресации_r8.квс - тест косвенной адресации через R8
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
193
тесты/тест_бнд/тест_бнд.py
Normal file
193
тесты/тест_бнд/тест_бнд.py
Normal file
|
|
@ -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()
|
||||
16
черновик.txt
16
черновик.txt
|
|
@ -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.
|
||||
Loading…
Reference in a new issue