добавлена система тестирования

This commit is contained in:
mzhoot 2026-06-09 22:43:10 +03:00
parent b273f42984
commit a442f7a144
24 changed files with 1141 additions and 111 deletions

246
README.md
View file

@ -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)

View file

@ -1,11 +0,0 @@
Косвенная адресация в КВС работает только через регистр R8 (и возможно R9R15 не работают или работают частично).
Инструкции, которые работают:
вытолкнуть (pop)
загрузить ал, [р8] (загрузка байта из памяти)
сравнить_с ал, 0 (сравнение с константой)
увеличить, переместить, исключающее_или
вызов_системы (syscall)
Строки в .данные не должны содержать символ : (двоеточие) — парсер обрезает.

View 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
View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View file

@ -2,7 +2,7 @@
;echo $? # должен быть 65
; ============================================================
; тест_r8.квс - тест косвенной адресации через R8
; тест_адресации_r8.квс - тест косвенной адресации через R8
; ============================================================
.текст

View 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()

View file

@ -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.