добавлена система тестирования
This commit is contained in:
parent
b273f42984
commit
a442f7a144
244
README.md
244
README.md
|
|
@ -9,12 +9,13 @@
|
||||||
- [Архитектура](#-архитектура)
|
- [Архитектура](#-архитектура)
|
||||||
- [Установка](#-установка)
|
- [Установка](#-установка)
|
||||||
- [Быстрый старт](#-быстрый-старт)
|
- [Быстрый старт](#-быстрый-старт)
|
||||||
|
- [Система тестирования](#-система-тестирования)
|
||||||
- [Синтаксис](#-синтаксис)
|
- [Синтаксис](#-синтаксис)
|
||||||
- [Система команд](#-система-команд)
|
- [Система команд](#-система-команд)
|
||||||
- [Регистры](#-регистры-процессора)
|
- [Регистры](#-регистры-процессора)
|
||||||
- [Директивы](#-директивы-ассемблера)
|
- [Директивы](#-директивы-ассемблера)
|
||||||
- [Примеры программ](#-примеры-программ)
|
- [Примеры программ](#-примеры-программ)
|
||||||
- [Отладка](#-отладка-и-проверка)
|
- [Отладка и проверка](#-отладка-и-проверка)
|
||||||
- [Ограничения](#-ограничения)
|
- [Ограничения](#-ограничения)
|
||||||
- [План развития](#-план-развития)
|
- [План развития](#-план-развития)
|
||||||
- [Лицензия](#-лицензия)
|
- [Лицензия](#-лицензия)
|
||||||
|
|
@ -23,7 +24,7 @@
|
||||||
|
|
||||||
- **Полностью русский синтаксис** — все инструкции, регистры и директивы на кириллице
|
- **Полностью русский синтаксис** — все инструкции, регистры и директивы на кириллице
|
||||||
- **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску
|
- **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску
|
||||||
- **Четырёхпроходная сборка** — традиционная схема с разрешением меток, подстановкой констант и перекодированием
|
- **Шестипроходная сборка** — разделение логики на независимые этапы для прозрачности и отладки
|
||||||
- **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бнд` (неинициализированные данные)
|
- **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бнд` (неинициализированные данные)
|
||||||
- **Гибкая адресация памяти**:
|
- **Гибкая адресация памяти**:
|
||||||
- абсолютная адресация по меткам `[переменная]`
|
- абсолютная адресация по меткам `[переменная]`
|
||||||
|
|
@ -32,11 +33,12 @@
|
||||||
- всё с RIP-relative кодированием для 64-битных перемещений
|
- всё с RIP-relative кодированием для 64-битных перемещений
|
||||||
- **CSV-логирование** — детальный файл с соответствием адресов, байтов, меток и исходных команд
|
- **CSV-логирование** — детальный файл с соответствием адресов, байтов, меток и исходных команд
|
||||||
- **Более 50 инструкций** — полный набор основных команд x86-64
|
- **Более 50 инструкций** — полный набор основных команд x86-64
|
||||||
|
- **Автоматическая система регрессионного тестирования** — 9+ тестов, проверяющих все аспекты работы ассемблера
|
||||||
- **Минимализм зависимостей** — только стандартная библиотека Python
|
- **Минимализм зависимостей** — только стандартная библиотека Python
|
||||||
|
|
||||||
## 🏗 Архитектура
|
## 🏗 Архитектура
|
||||||
|
|
||||||
Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через **6 этапов** (4 прохода + сборка):
|
Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через **6 этапов**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Исходный файл (.квс)
|
Исходный файл (.квс)
|
||||||
|
|
@ -56,6 +58,10 @@ CSV-файл (.csv) ← содержит все байты прог
|
||||||
│
|
│
|
||||||
▼ (kvs_pass4.py) ← ПРОХОД 4: перекодирование инструкций
|
▼ (kvs_pass4.py) ← ПРОХОД 4: перекодирование инструкций
|
||||||
│
|
│
|
||||||
|
▼ (kvs_pass5.py) ← ПРОХОД 5: генерация корректных таблиц секций для совместимости с toolchain
|
||||||
|
│
|
||||||
|
▼ (kvs_pass6.py) ← ПРОХОД 6: постобработка CSV для визуального анализа
|
||||||
|
│
|
||||||
▼ (kvs_builder.py) ← сборка ELF из CSV
|
▼ (kvs_builder.py) ← сборка ELF из CSV
|
||||||
ELF-файл (.elf)
|
ELF-файл (.elf)
|
||||||
│
|
│
|
||||||
|
|
@ -63,29 +69,35 @@ ELF-файл (.elf)
|
||||||
Исполняемый файл!
|
Исполняемый файл!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Почему 6 проходов?
|
||||||
|
|
||||||
|
1. **Первый проход** — неизвестно, какие адреса будут у меток. Ставим заглушки `0xCC`.
|
||||||
|
2. **Второй проход** — после того как все адреса меток известны, заменяем заглушки.
|
||||||
|
3. **Третий проход** — константы могут ссылаться друг на друга и на метки.
|
||||||
|
4. **Четвёртый проход** — после подстановки чисел перегенерируем машинный код (разная длина инструкций).
|
||||||
|
5. **Пятый проход** — генерируем корректные таблицы секций для работы `objdump`, `readelf` и GDB.
|
||||||
|
6. **Шестой проход** — сортируем CSV для удобного визуального анализа человеком.
|
||||||
|
|
||||||
|
Такой подход позволяет **изолировать логику** каждого этапа и делает отладку прозрачной.
|
||||||
|
|
||||||
### Модули компилятора
|
### Модули компилятора
|
||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `kvs_build.py` | Главный сборочный скрипт (запускает все этапы по порядку) |
|
| `kvs_build.py` | Главный сборочный скрипт |
|
||||||
| `kvs_lexer.py` | Лексический анализ, разбор на токены (поддерживает `[]`, `+`, `-`, `*`) |
|
| `kvs_lexer.py` | Лексический анализ |
|
||||||
| `kvs_parser.py` | Синтаксический анализ, построение AST (директивы, метки, инструкции) |
|
| `kvs_parser.py` | Синтаксический анализ, построение AST |
|
||||||
| `kvs_data.py` | Общие данные: регистры, таблица инструкций, вспомогательные функции |
|
| `kvs_data.py` | Общие данные: регистры, таблица инструкций |
|
||||||
| `kvs_encoder.py` | Фасад кодировщика (объединяет fixsize и mutsize) |
|
| `kvs_encoder.py` | Фасад кодировщика |
|
||||||
| `kvs_encoder_fixsize.py` | Кодировщик инструкций фиксированного размера |
|
| `kvs_encoder_fixsize.py` | Кодировщик инструкций фиксированного размера |
|
||||||
| `kvs_encoder_mutsize.py` | Кодировщик инструкций переменного размера + диспетчер |
|
| `kvs_encoder_mutsize.py` | Кодировщик инструкций переменного размера |
|
||||||
| `kvs_pass1.py` | Первый проход — генерация CSV с заглушками для неизвестных меток |
|
| `kvs_pass1.py` | Генерация CSV с заглушками |
|
||||||
| `kvs_pass2.py` | Второй проход — разрешение меток (замена заглушек на реальные адреса) |
|
| `kvs_pass2.py` | Разрешение меток |
|
||||||
| `kvs_pass3.py` | Третий проход — подстановка констант из директив `.константа` |
|
| `kvs_pass3.py` | Подстановка констант |
|
||||||
| `kvs_pass4.py` | Четвёртый проход — перекодирование инструкций с подставленными значениями |
|
| `kvs_pass4.py` | Перекодирование инструкций |
|
||||||
| `kvs_builder.py` | Сборка ELF: чтение CSV, заполнение байтов по адресам, установка прав на исполнение |
|
| `kvs_pass5.py` | Генерация таблиц секций |
|
||||||
|
| `kvs_pass6.py` | Сортировка CSV |
|
||||||
### Почему 4 прохода?
|
| `kvs_builder.py` | Сборка ELF из CSV |
|
||||||
|
|
||||||
1. **Первый проход** — неизвестно, какие адреса будут у меток (метка может использоваться до определения). Вместо остановки ставим заглушки `0xCC` и продолжаем.
|
|
||||||
2. **Второй проход** — после того как все адреса меток известны, заменяем заглушки на реальные значения.
|
|
||||||
3. **Третий проход** — константы могут ссылаться друг на друга и на метки, поэтому их подстановка делается отдельно.
|
|
||||||
4. **Четвёртый проход** — после подстановки чисел (адресов/констант) нужно перегенерировать машинный код, так как разные операнды могут менять длину инструкции.
|
|
||||||
|
|
||||||
### Структура CSV-файла
|
### Структура CSV-файла
|
||||||
|
|
||||||
|
|
@ -93,14 +105,14 @@ ELF-файл (.elf)
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `сегмент` | Имя секции (`.header`, `.text`, `.data`, `.bss`) |
|
| `сегмент` | Имя секции (`.header`, `.text`, `.data`, `.bss`) |
|
||||||
| `адрес` | Файловое смещение байта |
|
| `адрес` | Файловое смещение байта |
|
||||||
| `виртуальный_адрес` | Виртуальный адрес байта в памяти (для вычисления смещений) |
|
| `виртуальный_адрес` | Виртуальный адрес байта в памяти |
|
||||||
| `байт` | Исходное значение байта (из pass1) |
|
| `байт` | Исходное значение байта (из pass1) |
|
||||||
| `приводящая_метка` | Имена меток, находящихся на этом адресе (через запятую) |
|
| `приводящая_метка` | Имена меток на этом адресе |
|
||||||
| `уводящий_адрес` | Строка `ЗАГЛУШКА имя` для неразрешённых меток |
|
| `уводящий_адрес` | Строка `ЗАГЛУШКА имя` для неразрешённых меток |
|
||||||
| `исходная_команда` | Исходная инструкция (например, `переход метка`) |
|
| `исходная_команда` | Исходная инструкция |
|
||||||
| `команда_со_значениями` | Инструкция с подставленными адресами/константами (pass2/pass3) |
|
| `команда_со_значениями` | Инструкция с подставленными адресами/константами |
|
||||||
| `рассчитанный_уводящий_адрес` | Реальный адрес метки (файловое смещение) после разрешения |
|
| `рассчитанный_уводящий_адрес` | Реальный адрес метки после разрешения |
|
||||||
| `рассчитанный_байт` | Итоговый байт после перекодирования (pass4) |
|
| `рассчитанный_байт` | Итоговый байт после перекодирования |
|
||||||
|
|
||||||
## 🔧 Установка
|
## 🔧 Установка
|
||||||
|
|
||||||
|
|
@ -109,7 +121,7 @@ ELF-файл (.elf)
|
||||||
git clone https://github.com/artradeskz/kvs.git
|
git clone https://github.com/artradeskz/kvs.git
|
||||||
cd kvs
|
cd kvs
|
||||||
|
|
||||||
# Установка прав на выполнение (при необходимости)
|
# Установка прав на выполнение
|
||||||
chmod +x kvs_build.py
|
chmod +x kvs_build.py
|
||||||
|
|
||||||
# Проверка работы
|
# Проверка работы
|
||||||
|
|
@ -125,11 +137,11 @@ python3 kvs_build.py --help
|
||||||
.глобал _start
|
.глобал _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
; write(1, msg, 13)
|
; write(1, msg, 21)
|
||||||
переместить_имм раикс,1
|
переместить_имм раикс,1
|
||||||
переместить_имм рдиай,1
|
переместить_имм рдиай,1
|
||||||
переместить_имм рсиай, msg
|
переместить_имм рсиай, msg
|
||||||
переместить_имм рдикс,13
|
переместить_имм рдикс,21
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
|
||||||
; exit(0)
|
; exit(0)
|
||||||
|
|
@ -138,7 +150,7 @@ _start:
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
|
||||||
.данные
|
.данные
|
||||||
msg: .строка_нуль "Hello, World!"
|
msg: .строка "Привет мир!\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Скомпилируйте:
|
### 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
|
```assembly
|
||||||
_start: ; метка _start
|
_start: ; метка _start
|
||||||
переместить_имм раикс,1
|
переместить_имм раикс,1
|
||||||
|
|
@ -186,8 +250,6 @@ _start: ; метка _start
|
||||||
|
|
||||||
### Комментарии
|
### Комментарии
|
||||||
|
|
||||||
Однострочные комментарии начинаются с `;`:
|
|
||||||
|
|
||||||
```assembly
|
```assembly
|
||||||
; Это комментарий
|
; Это комментарий
|
||||||
переместить_имм раикс,42 ; комментарий после инструкции
|
переместить_имм раикс,42 ; комментарий после инструкции
|
||||||
|
|
@ -276,10 +338,6 @@ _start: ; метка _start
|
||||||
| `переход_если_перенос` | CF = 1 |
|
| `переход_если_перенос` | CF = 1 |
|
||||||
| `переход_если_нет_переноса` | CF = 0 |
|
| `переход_если_нет_переноса` | CF = 0 |
|
||||||
|
|
||||||
**Короткие условные переходы** (префикс `короткий_`):
|
|
||||||
- `короткий_переход_если_равно`, `короткий_переход_если_неравно`, и т.д.
|
|
||||||
- Генерируют 2-байтовые инструкции (смещение ±127 байт)
|
|
||||||
|
|
||||||
### Системные вызовы
|
### Системные вызовы
|
||||||
|
|
||||||
| Инструкция | Описание |
|
| Инструкция | Описание |
|
||||||
|
|
@ -334,12 +392,9 @@ _start: ; метка _start
|
||||||
.глобал _start
|
.глобал _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
; Запись значения в переменную
|
|
||||||
переместить_имм раикс,42
|
переместить_имм раикс,42
|
||||||
сохранить [переменная], раикс
|
сохранить [переменная], раикс
|
||||||
; Чтение значения из переменной
|
|
||||||
загрузить рбикс,[переменная]
|
загрузить рбикс,[переменная]
|
||||||
; Завершение с кодом возврата
|
|
||||||
переместить_имм раикс,60
|
переместить_имм раикс,60
|
||||||
загрузить рдиай,[переменная]
|
загрузить рдиай,[переменная]
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
|
@ -355,7 +410,7 @@ _start:
|
||||||
.глобал _start
|
.глобал _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
переместить_имм рсикс,10 ; счетчик = 10
|
переместить_имм рсикс,10
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
переместить_имм раикс,1
|
переместить_имм раикс,1
|
||||||
|
|
@ -363,10 +418,9 @@ loop:
|
||||||
переместить_имм рсиай, msg
|
переместить_имм рсиай, msg
|
||||||
переместить_имм рдикс,10
|
переместить_имм рдикс,10
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
уменьшить рсикс
|
||||||
уменьшить рсикс ; счетчик--
|
|
||||||
сравнить_с рсикс,0
|
сравнить_с рсикс,0
|
||||||
переход_если_больше loop ; если > 0, продолжаем
|
переход_если_больше loop
|
||||||
|
|
||||||
переместить_имм раикс,60
|
переместить_имм раикс,60
|
||||||
переместить_имм рдиай,0
|
переместить_имм рдиай,0
|
||||||
|
|
@ -383,8 +437,8 @@ msg: .строка "Iteration\n"
|
||||||
.глобал _start
|
.глобал _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
переместить_имм р8,0 ; сумма
|
переместить_имм р8,0
|
||||||
переместить_имм р9,10 ; счётчик
|
переместить_имм р9,10
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
прибавить р8, р9
|
прибавить р8, р9
|
||||||
|
|
@ -392,7 +446,6 @@ loop:
|
||||||
сравнить_с р9,0
|
сравнить_с р9,0
|
||||||
переход_если_больше loop
|
переход_если_больше loop
|
||||||
|
|
||||||
; exit(сумма в r8)
|
|
||||||
переместить_имм раикс,60
|
переместить_имм раикс,60
|
||||||
переместить рдикс, р8
|
переместить рдикс, р8
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
|
@ -405,12 +458,10 @@ loop:
|
||||||
.глобал _start
|
.глобал _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
; инициализация счётчика в BND
|
|
||||||
переместить_имм раикс,0
|
переместить_имм раикс,0
|
||||||
сохранить [счётчик], раикс
|
сохранить [счётчик], раикс
|
||||||
|
|
||||||
; цикл: увеличить счётчик 10 раз
|
|
||||||
переместить_имм рсикс,10
|
переместить_имм рсикс,10
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
загрузить раикс,[счётчик]
|
загрузить раикс,[счётчик]
|
||||||
увеличить раикс
|
увеличить раикс
|
||||||
|
|
@ -419,7 +470,6 @@ loop:
|
||||||
сравнить_с рсикс,0
|
сравнить_с рсикс,0
|
||||||
переход_если_больше loop
|
переход_если_больше loop
|
||||||
|
|
||||||
; выход с кодом = значение счётчика
|
|
||||||
загрузить рдиай,[счётчик]
|
загрузить рдиай,[счётчик]
|
||||||
переместить_имм раикс,60
|
переместить_имм раикс,60
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
|
@ -435,17 +485,10 @@ loop:
|
||||||
.глобал _start
|
.глобал _start
|
||||||
|
|
||||||
_start:
|
_start:
|
||||||
; загружаем адрес переменной в регистр
|
|
||||||
переместить_имм рбикс, переменная
|
переместить_имм рбикс, переменная
|
||||||
|
|
||||||
; косвенная запись
|
|
||||||
переместить_имм раикс,99
|
переместить_имм раикс,99
|
||||||
сохранить [рбикс], раикс
|
сохранить [рбикс], раикс
|
||||||
|
|
||||||
; косвенное чтение
|
|
||||||
загрузить раикс,[рбикс]
|
загрузить раикс,[рбикс]
|
||||||
|
|
||||||
; выход
|
|
||||||
переместить_имм рдиай, раикс
|
переместить_имм рдиай, раикс
|
||||||
переместить_имм раикс,60
|
переместить_имм раикс,60
|
||||||
вызов_системы
|
вызов_системы
|
||||||
|
|
@ -454,6 +497,51 @@ _start:
|
||||||
переменная: .байт 0
|
переменная: .байт 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-лог
|
||||||
|
|
||||||
После компиляции создаётся файл `программа.csv` со структурой:
|
После компиляции создаётся файл `программа.csv` с полной информацией о каждом байте программы.
|
||||||
|
|
||||||
| адрес | виртуальный_адрес | байт | приводящая_метка | исходная_команда |
|
|
||||||
|-------|-------------------|------|------------------|------------------|
|
|
||||||
| 0x1000 | 0x401000 | 48 | _start | переместить_имм раикс, 10 |
|
|
||||||
| 0x1001 | 0x401001 | B8 | | |
|
|
||||||
| ... | ... | ... | ... | ... |
|
|
||||||
|
|
||||||
### Трассировка системных вызовов
|
### Трассировка системных вызовов
|
||||||
|
|
||||||
|
|
@ -493,11 +575,11 @@ strace ./программа.elf
|
||||||
```bash
|
```bash
|
||||||
gdb ./программа.elf
|
gdb ./программа.elf
|
||||||
|
|
||||||
(gdb) break _start ; установить точку останова
|
(gdb) break _start
|
||||||
(gdb) run ; запустить
|
(gdb) run
|
||||||
(gdb) info registers ; показать регистры
|
(gdb) info registers
|
||||||
(gdb) x/10i $rip ; показать 10 инструкций
|
(gdb) x/10i $rip
|
||||||
(gdb) stepi ; выполнить одну инструкцию
|
(gdb) stepi
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚠️ Ограничения
|
## ⚠️ Ограничения
|
||||||
|
|
@ -519,11 +601,13 @@ gdb ./программа.elf
|
||||||
- Абсолютная адресация по меткам и числовым адресам `[метка]` / `[0x402000]`
|
- Абсолютная адресация по меткам и числовым адресам `[метка]` / `[0x402000]`
|
||||||
- Косвенная адресация через регистр `[раикс]`
|
- Косвенная адресация через регистр `[раикс]`
|
||||||
- RIP-relative кодирование для всех видов адресации
|
- RIP-relative кодирование для всех видов адресации
|
||||||
|
- Полная совместимость с `objdump`, `readelf`, `gdb`
|
||||||
|
|
||||||
### Не поддерживается (в разработке)
|
### Не поддерживается (в разработке)
|
||||||
|
|
||||||
- Адресация со смещением `[reg + disp]`
|
- Адресация со смещением `[reg + disp]`
|
||||||
- Сложная адресация с индексом `[reg + reg*scale]` и полная SIB-адресация
|
- Сложная адресация с индексом `[reg + reg*scale]`
|
||||||
|
- Полная SIB-адресация `[reg + reg*scale + disp]`
|
||||||
- Поддержка плавающей запятой (FPU/SSE)
|
- Поддержка плавающей запятой (FPU/SSE)
|
||||||
- Многомодульная компиляция
|
- Многомодульная компиляция
|
||||||
- Макросы
|
- Макросы
|
||||||
|
|
@ -532,19 +616,15 @@ gdb ./программа.elf
|
||||||
|
|
||||||
### Реализовано (v2.0)
|
### Реализовано (v2.0)
|
||||||
|
|
||||||
- Лексер с токенами `[`, `]`, `NUMBER`, `+`, `-`, `*`
|
- Лексер с поддержкой `[]`, `+`, `-`, `*`
|
||||||
- Парсер с поддержкой операндов в квадратных скобках
|
- Парсер с поддержкой операндов в квадратных скобках
|
||||||
- Четырёхпроходная схема сборки
|
- Шестипроходная схема сборки (включая генерацию таблиц секций и сортировку CSV)
|
||||||
- Первый проход с корректным расчётом размера инструкций с памятью
|
|
||||||
- Второй проход с разрешением меток
|
|
||||||
- Третий проход с подстановкой констант
|
|
||||||
- Четвёртый проход с перекодированием инструкций
|
|
||||||
- Поддержка меток в операндах памяти `[переменная]`
|
- Поддержка меток в операндах памяти `[переменная]`
|
||||||
- Поддержка числовых адресов `[0x402000]`
|
- Поддержка числовых адресов `[0x402000]`
|
||||||
- Поддержка косвенной адресации `[регистр]`
|
- Поддержка косвенной адресации `[регистр]`
|
||||||
- Секция `.бнд` для неинициализированных данных
|
- Секция `.бнд` для неинициализированных данных
|
||||||
- Вынесение кодировщика в отдельный модуль
|
- Автоматическая система регрессионного тестирования (9+ тестов)
|
||||||
- Колонка `виртуальный_адрес` в CSV для точного вычисления смещений
|
- Полная совместимость со стандартным toolchain Linux
|
||||||
|
|
||||||
### В планах (v3.0)
|
### В планах (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
|
;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