Compare commits

..

49 commits

Author SHA1 Message Date
mzhoot f07f3684ae правки в ридми 2026-07-04 17:56:41 +03:00
mzhoot df8f193317 правки в ридми 2026-07-04 17:55:52 +03:00
mzhoot d9ad905291 правки в ридми 2026-07-04 17:54:10 +03:00
mzhoot f1f602c617 вывод многозначного числа в терминал 2026-06-28 12:06:14 +03:00
mzhoot ab8479a757 вывод однозначного числа в терминал 2026-06-28 11:58:44 +03:00
mzhoot 70ed97cec6 тест остатк от деления на 10 2026-06-28 11:50:50 +03:00
mzhoot 309bbd460a тест вызовов функций 2026-06-27 14:11:25 +03:00
mzhoot 3493d06200 починить р12 и sib-байт 2026-06-27 13:33:19 +03:00
mzhoot 1209ca732a починить двоеточие в строках 2026-06-27 12:21:23 +03:00
mzhoot 7ba9590286 убрать ооп 2026-06-27 11:24:45 +03:00
mzhoot a3194093e4 регистры р13 р14 р15 2026-06-20 14:35:00 +03:00
mzhoot ce1800f236 ремонт регистров р9 - р11 2026-06-20 10:34:59 +03:00
mzhoot 50d9a5b7be переименован диспетчер тестов 2026-06-20 09:30:22 +03:00
mzhoot fa7511a36f Обновить 2026-06-20 09:27:22 +03:00
mzhoot 02c74f35c9 Обновить ридми после русификации имен скриптов 2026-06-20 09:23:49 +03:00
mzhoot b452f0bae6 Обновить ридми после русификации имен скриптов 2026-06-20 09:23:03 +03:00
mzhoot 3c751e10ad Обновить ридми после русификации имен скриптов 2026-06-20 09:21:31 +03:00
mzhoot ccfe0f7a05 Обновить ридми после русификации имен скриптов 2026-06-20 09:16:31 +03:00
mzhoot b0fa390abb русификация основных скриптов 2026-06-20 05:32:45 +03:00
mzhoot 4962a4a49e русификация скрипта запуска 2026-06-20 05:20:34 +03:00
mzhoot 5bc6436c07 русификация лексера 2026-06-20 04:16:50 +03:00
mzhoot d445f40ac4 уточнение по тестам 2026-06-14 22:45:26 +03:00
mzhoot 6b0b31a0ad уточнение по тестам 2026-06-14 22:44:17 +03:00
mzhoot 81813ae2ec тесты сложения и вычитания 2026-06-14 22:43:01 +03:00
mzhoot bcaf3de8d0 тесты адресации бнд 2026-06-14 21:58:38 +03:00
artradeskz 159e3c1d62
Подсветка синтаксиса
Ссылка на расширение с подсветкой синтаксиса в ридми
2026-06-14 14:02:03 +03:00
artradeskz ed136fb092
Посветка синтаксиса квс
Добавлена ссылка на расширение vscode(vscodium) для подсветки синтаксиса квс
2026-06-14 14:00:41 +03:00
mzhoot a442f7a144 добавлена система тестирования 2026-06-09 22:43:10 +03:00
mzhoot b273f42984 вывод аргумента 2026-06-07 22:59:34 +03:00
mzhoot 8c652e8aa0 добавить сортировщик для таблицы 2026-06-07 16:26:54 +03:00
mzhoot dd57d5d64e починить бнд и учесть длину коментария 2026-06-07 15:55:19 +03:00
mzhoot 14bd2584c5 починить бнд и учесть длину коментария 2026-06-07 15:50:48 +03:00
mzhoot 54b1de926e совместимость с readelf objdump 2026-06-07 13:17:07 +03:00
mzhoot 403b999d42 адресация в bss 2026-05-25 20:36:43 +03:00
mzhoot a75dc24a61 тест бнд без адресации 2026-05-25 19:05:11 +03:00
mzhoot f7c1b07cc4 тест бнд без адресации 2026-05-25 19:04:55 +03:00
mzhoot bd2a165167 тест бнд 2026-05-25 19:04:13 +03:00
mzhoot 94f8cdb71a тесты адресации 2026-05-25 18:51:03 +03:00
mzhoot 99579d2538 маркеры секций 2026-05-25 18:19:13 +03:00
mzhoot 876e00a1ca убрать харкод в 4м проходе 2026-05-23 21:14:49 +03:00
mzhoot 05b4230058 рефакторинг первого прохода 2026-05-23 20:35:41 +03:00
mzhoot 171cb1af10 рефакторинг общих данных 2026-05-23 20:30:15 +03:00
mzhoot 84646f02c6 упростить рассчет смещений 2026-05-23 18:56:43 +03:00
mzhoot bb80efac8d обновить ридми 2026-05-23 16:30:12 +03:00
mzhoot f133b48363 однотабличная сборка_ 2026-05-23 08:58:10 +03:00
mzhoot 3a04dc5447 однотабличная сборка 2026-05-23 08:52:52 +03:00
mzhoot 6400d6105a выключатель проверки сборки 2026-05-22 23:36:33 +03:00
mzhoot 9e1fef82db перенос инструментов 2026-05-22 23:19:22 +03:00
mzhoot a59a2302fe дизассемблер 2026-05-21 23:39:30 +03:00
113 changed files with 8754 additions and 4823 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
__pycache__/
.gigacode
*.pyc
*.pyo

View file

@ -1,4 +1,4 @@
```markdown
# AGENTS.md
## Правила для агентов и участников проекта КВС
@ -11,33 +11,59 @@
Разбор строк — только посимвольно, через циклы и сравнения. Это делает код переносимым на другие языки без изменений логики.
2. **Минимум ООП.**
- Существующие классы (`Pass1`, `Pass2`, `Parser`) **не переписывать** без явной команды. Они работают — не трогай.
- Новые классы **не создавать**. Код пишется процедурно: функции принимают данные, возвращают данные.
- Классы **не создавать**. Код пишется процедурно: функции принимают данные, возвращают данные.
3. **Изоляция логики важнее DRY.**
Если модуль может быть автономным — он должен быть автономным. Допустимо дублирование кода ради независимости модуля от других. Пример: `kvs_pass2_encoder_mutsize.py` содержит копии функций из `kvs_pass2_encoder_fixsize.py`, чтобы не импортировать их и оставаться полностью самостоятельным.
Если модуль может быть автономным — он должен быть автономным. Допустимо дублирование кода ради независимости модуля от других. Пример: кодировщики фиксированного и переменного размера содержат копии функций, чтобы не импортировать их и оставаться полностью самостоятельными.
4. **Русские названия везде, кроме ELF.**
Внутри компилятора — русские имена секций, директив, регистров. В ELF-файле — стандартные латинские имена (`.text`, `.data`, `.bss`).
- Внутри компилятора — русские имена секций, директив, регистров, инструкций.
- В ELF-файле — стандартные латинские имена (`.text`, `.data`, `.bss`).
- Имена файлов, переменных и функций в коде — **английские** (кроме русифицированных скриптов верхнего уровня).
### Структура проекта
```
kvs_lexer.py — лексер (исходник → токены)
kvs_parser.py — парсер (токены → AST)
kvs_pass1.py — первый проход (размеры секций, адреса меток)
kvs_pass2.py — второй проход (AST + pass1 → CSV с байтами)
kvs_builder.py — сборщик ELF (CSV + pass1 → исполняемый файл)
kvs_data.py — общие константы и данные
kvs_build.py — главный сборочный скрипт (вызывает всё по порядку)
kvs_pass2_encoder_fixsize.py — кодировщик инструкций фиксированного размера
kvs_pass2_encoder_mutsize.py — кодировщик инструкций переменного размера + диспетчер
kvs_pass2_encoder.py — реэкспорт из fixsize + mutsize (совместимость)
квс_сборка.py — Главный сборочный скрипт (запускает все проходы по порядку)
квс_лексер.py — Лексер (исходник → токены)
квс_парсер.py — Парсер (токены → AST)
квс_проход1.py — Первый проход: генерация CSV с заглушками для неизвестных меток
квс_проход2.py — Второй проход: разрешение меток (замена заглушек на реальные адреса)
квс_проход3.py — Третий проход: подстановка констант в команды
квс_проход4.py — Четвёртый проход: перекодирование инструкций с подставленными значениями
квс_проход5.py — Пятый проход: генерация корректных таблиц секций для совместимости с toolchain
квс_проход6.py — Шестой проход: постобработка и сортировка CSV для визуального анализа
квс_финсбор.py — Сборщик ELF (CSV → исполняемый файл)
kvs_data.py — Общие константы и данные (регистры, инструкции, функции)
kvs_encoder.py — Фасад кодировщика (реэкспорт из fixsize + mutsize)
kvs_encoder_fixsize.py — Кодировщик инструкций фиксированного размера
kvs_encoder_mutsize.py — Кодировщик инструкций переменного размера + диспетчер
```
### Русификация
**Русифицированы (пользовательская часть):**
- Имена скриптов верхнего уровня: `квс_*.py` (кроме `квс_финсбор.py`, `квс_сборка.py`)
- Комментарии и документация в большинстве файлов
- Пользовательский синтаксис: инструкции, регистры, директивы
**Остались на английском:**
- Имена файлов: `kvs_data.py`, `kvs_encoder.py`, `kvs_encoder_fixsize.py`, `kvs_encoder_mutsize.py`
- Внутренние имена переменных, констант и функций внутри этих файлов
- Технические идентификаторы в коде (ключи словарей, имена функций)
### Стиль
- Комментарии и документация на русском.
- Комментарии и документация — на русском (в русифицированных файлах).
- Имена функций — `snake_case`, осмысленные.
- Размеры инструкций — явные числа, никаких `suffix`-трюков.
- Каждая директива резервирования — отдельная ветка `if`, а не словарь.
- Для файлов, оставшихся на английском (`kvs_*.py`), комментарии допустимы на русском или английском — по усмотрению автора.
### Важно
- `kvs_build.py` переименован в `квс_сборка.py` — используйте актуальное имя.
- `kvs_builder.py` переименован в `квс_финсбор.py`.
- Все проходы (`kvs_pass*.py`) переименованы в `квс_проход*.py`.
- Кодировщики и `kvs_data.py` **не переименованы** — их имена остались на английском.
```

View file

@ -1,785 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>КВС - Ассемблер с русским синтаксисом</title>
<style>
/* Сброс и базовые настройки */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', 'Courier', monospace;
font-size: 14px;
line-height: 1.4;
color: #00ff00;
background-color: #000000;
background-image:
linear-gradient(rgba(0, 20, 0, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 20, 0, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
padding: 20px;
min-height: 100vh;
text-shadow: 0 0 5px rgba(0, 255, 0, 0.3);
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.cursor {
display: inline-block;
width: 8px;
height: 16px;
background-color: #00ff00;
margin-left: 2px;
vertical-align: text-bottom;
animation: blink 1s infinite;
}
.terminal {
max-width: 1200px;
margin: 0 auto;
border: 2px solid #00aa00;
padding: 20px;
position: relative;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.2), inset 0 0 20px rgba(0, 255, 0, 0.1);
}
.terminal::before {
content: "┌───[ КВС v2.0 ]───┐";
position: absolute;
top: -11px;
left: 20px;
background: #000;
padding: 0 10px;
font-size: 12px;
color: #00aa00;
}
.terminal::after {
content: "└─────────────────────┘";
position: absolute;
bottom: -11px;
right: 20px;
background: #000;
padding: 0 10px;
font-size: 12px;
color: #00aa00;
}
h1, h2, h3, h4 {
color: #00ff00;
margin: 25px 0 15px 0;
border-bottom: 1px dashed #00aa00;
padding-bottom: 5px;
text-transform: uppercase;
letter-spacing: 1px;
}
h1 {
font-size: 24px;
color: #00ff88;
text-align: center;
margin-top: 0;
padding: 10px;
border: 1px solid #00aa00;
background: rgba(0, 20, 0, 0.3);
}
h2 {
font-size: 18px;
color: #00ff66;
}
h3 {
font-size: 16px;
color: #00ff44;
}
p {
margin: 15px 0;
text-align: justify;
}
ul, ol {
margin: 15px 0 15px 30px;
}
li {
margin: 8px 0;
position: relative;
}
li::before {
content: "›";
color: #00ff00;
position: absolute;
left: -15px;
}
a {
color: #00ffff;
text-decoration: none;
border-bottom: 1px dotted #00aaaa;
}
a:hover {
color: #ffff00;
border-bottom: 1px solid #ffff00;
text-shadow: 0 0 10px rgba(255, 255, 0, 0.5);
}
code, pre {
font-family: 'Courier New', monospace;
background: rgba(0, 30, 0, 0.5);
border: 1px solid #005500;
padding: 15px;
margin: 15px 0;
display: block;
overflow-x: auto;
white-space: pre;
color: #88ff88;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
position: relative;
}
pre::before {
content: "┌──[ код ]";
position: absolute;
top: -10px;
left: 10px;
background: #000;
padding: 0 5px;
font-size: 11px;
color: #00aa00;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
border: 1px solid #005500;
}
th {
background: rgba(0, 40, 0, 0.5);
color: #00ff88;
padding: 10px;
border: 1px solid #005500;
text-align: left;
font-weight: bold;
text-transform: uppercase;
font-size: 12px;
}
td {
padding: 10px;
border: 1px solid #005500;
color: #88ff88;
vertical-align: top;
}
tr:nth-child(even) {
background: rgba(0, 25, 0, 0.3);
}
tr:hover {
background: rgba(0, 50, 0, 0.4);
}
.note {
border-left: 3px solid #ffff00;
padding: 15px;
margin: 20px 0;
background: rgba(40, 40, 0, 0.2);
color: #ffff88;
}
.warning {
border-left: 3px solid #ff4444;
padding: 15px;
margin: 20px 0;
background: rgba(40, 0, 0, 0.2);
color: #ff8888;
}
.success {
border-left: 3px solid #44ff44;
padding: 15px;
margin: 20px 0;
background: rgba(0, 40, 0, 0.2);
color: #88ff88;
}
.process-diagram {
text-align: center;
margin: 30px 0;
font-family: monospace;
white-space: pre;
color: #00ffaa;
background: rgba(0, 20, 0, 0.3);
padding: 20px;
border: 1px dashed #00aa00;
overflow-x: auto;
}
.status-table td:first-child {
font-weight: bold;
}
.status-done {
color: #00ff00;
}
.status-partial {
color: #ffff00;
}
.status-pending {
color: #ff4444;
}
footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #005500;
text-align: center;
color: #00aa00;
font-size: 12px;
}
.typewriter {
overflow: hidden;
border-right: 3px solid #00ff00;
white-space: nowrap;
margin: 0 auto;
animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite;
}
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
@keyframes blink-caret {
from, to { border-color: transparent; }
50% { border-color: #00ff00; }
}
.ascii-art {
text-align: center;
margin: 30px 0;
font-family: monospace;
color: #00ff88;
line-height: 1.2;
white-space: pre;
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
.ascii-art-box {
display: inline-block;
border: none;
background: none;
padding: 0;
margin: 0;
}
.badge {
display: inline-block;
padding: 2px 6px;
margin-left: 8px;
font-size: 10px;
border-radius: 2px;
background: #005500;
color: #00ff00;
}
@media (max-width: 768px) {
body {
padding: 10px;
font-size: 12px;
}
.terminal {
padding: 15px;
}
h1 {
font-size: 18px;
}
h2 {
font-size: 16px;
}
table {
font-size: 11px;
}
pre, code {
font-size: 11px;
padding: 10px;
}
.ascii-art {
font-size: 10px;
overflow-x: auto;
}
}
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: rgba(0, 20, 0, 0.3);
border: 1px solid #005500;
}
::-webkit-scrollbar-thumb {
background: #005500;
border: 1px solid #00aa00;
}
::-webkit-scrollbar-thumb:hover {
background: #00aa00;
}
</style>
</head>
<body>
<div class="terminal">
<h1 class="typewriter">КВС — Ассемблер с русским синтаксисом для x86-64</h1>
<div style="text-align: center; margin: 30px 0; color: #00ff88;">
<div class="ascii-art">
<pre class="ascii-art-box">
╔══════════════════════════════════╗
║ ██╗ ██╗ ██╗ ██╗ ███████╗ ║
║ ██║ ██╔╝ ██╗ ██╗ ██╔════╝ ║
║ █████╔╝ ██╗ ██╗ ███████╗ ║
║ ██║╚██╗ ╚██╗ ██╔╝ ╚════██║ ║
║ ██║ ╚██╗ ╚██╔╝ ███████║ ║
║ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ║
╚══════════════════════════════════╝
</pre>
</div>
<p>Версия 2.0 | Экспериментальный проект | 2026</p>
<p><span style="color: #00ff00;">✓ Абсолютная адресация реализована</span> | <span style="color: #00ff00;">✓ Косвенная адресация реализована</span> | <span style="color: #00ff00;">✓ Секция .bss реализована</span></p>
</div>
<h2>┌──[ ОГЛАВЛЕНИЕ ]────────────────────────────────────────────┐</h2>
<ul style="columns: 2; column-gap: 40px;">
<li><a href="#назначение">Назначение и концепция</a></li>
<li><a href="#особенности">Особенности и возможности</a></li>
<li><a href="#архитектура">Архитектура и процесс сборки</a></li>
<li><a href="#установка">Установка и запуск</a></li>
<li><a href="#синтаксис">Синтаксис исходных файлов (.квс)</a></li>
<li><a href="#команды">Система команд</a></li>
<li><a href="#память">Работа с памятью</a></li>
<li><a href="#регистры">Регистры процессора</a></li>
<li><a href="#директивы">Директивы ассемблера</a></li>
<li><a href="#примеры">Примеры программ</a></li>
<li><a href="#трансляция">Процесс трансляции</a></li>
<li><a href="#статус">Статус реализации</a></li>
<li><a href="#план">План развития</a></li>
<li><a href="#ограничения">Ограничения</a></li>
</ul>
<h2>└──────────────────────────────────────────────────────────────┘</h2>
<h2 id="назначение">НАЗНАЧЕНИЕ И КОНЦЕПЦИЯ</h2>
<p><strong>КВС</strong> — это ассемблер для архитектуры x86-64, полностью использующий <strong>русскую лексику</strong> для мнемоник инструкций, имён регистров и директив. Проект носит <strong>образовательный и экспериментальный характер</strong> и демонстрирует принципы работы ассемблера, построения ELF-файлов и трансляции высокоуровневых концепций в машинный код.</p>
<div class="note">
<strong>Ключевые принципы проекта:</strong>
<ul>
<li><strong>Русскоязычный синтаксис</strong> — все инструкции, регистры и директивы на кириллице</li>
<li><strong>Модульная архитектура</strong> — лексер, парсер, два прохода, сборщик ELF</li>
<li><strong>Минимализм зависимостей</strong> — только стандартная библиотека Python</li>
<li><strong>Подготовка к самокомпиляции</strong> — архитектура, допускающая написание компилятора на самом КВС</li>
</ul>
</div>
<h2 id="особенности">ОСОБЕННОСТИ И ВОЗМОЖНОСТИ</h2>
<ul>
<li><strong>Полностью русский синтаксис</strong> — <code>переместить</code>, <code>прибавить</code>, <code>вызвать</code>, регистры <code>раикс</code>, <code>есипи</code>, <code>ал</code>, директивы <code>.текст</code>, <code>.данные</code>, <code>.бсс</code></li>
<li><strong>Генерация исполняемых ELF-файлов</strong> — 64-битные ELF для Linux, готовые к запуску</li>
<li><strong>Двухпроходная сборка</strong> — традиционная схема с разрешением меток</li>
<li><strong>Поддержка секций</strong> — разделение на <code>.текст</code> (код), <code>.данные</code> (инициализированные данные) и <code>.бсс</code> (неинициализированные данные)</li>
<li><strong>Полная адресация памяти</strong>:
<ul>
<li>Абсолютная адресация по меткам <code>[переменная]</code></li>
<li>Абсолютная адресация по числовому адресу <code>[0x402000]</code></li>
<li>Косвенная адресация через регистр <code>[раикс]</code></li>
<li>Всё с RIP-relative кодированием для 64-битных перемещений</li>
</ul>
</li>
<li><strong>CSV-логирование</strong> — детальный файл <code>.csv</code> с соответствием адресов, байтов и исходных команд</li>
<li><strong>Более 50 инструкций</strong> — полный набор основных команд x86-64</li>
<li><strong>Рефакторинг</strong> — кодогенерация вынесена в отдельный модуль <code>kvs_pass2_encoder.py</code></li>
</ul>
<h2 id="архитектура">АРХИТЕКТУРА И ПРОЦЕСС СБОРКИ</h2>
<div class="process-diagram">
Исходный файл (.квс)
│
▼ (kvs_lexer.py)
Файл токенов (.токены)
│
▼ (kvs_parser.py)
AST-представление (.аст)
│
▼ (kvs_pass1.py)
Первый проход: размеры, метки (.проход1)
│
▼ (kvs_pass2.py + kvs_pass2_encoder.py)
Второй проход: генерация кода (.csv)
│
▼ (kvs_builder.py)
ELF-компоновщик (.elf)
Исполняемый файл!
</div>
<h3>Модули компилятора</h3>
<table>
<tr><th>Файл</th><th>Назначение</th></tr>
<tr><td><code>kvs_build.py</code></td><td>Главный сборочный скрипт</td></tr>
<tr><td><code>kvs_lexer.py</code></td><td>Лексический анализ, разбор на токены</td></tr>
<tr><td><code>kvs_parser.py</code></td><td>Синтаксический анализ, построение AST</td></tr>
<tr><td><code>kvs_pass1.py</code></td><td>Первый проход: вычисление размеров и адресов меток</td></tr>
<tr><td><code>kvs_pass2.py</code></td><td>Второй проход: обход AST, генерация CSV</td></tr>
<tr><td><code>kvs_pass2_encoder.py</code></td><td>Кодировщик инструкций (все encode_* функции)</td></tr>
<tr><td><code>kvs_builder.py</code></td><td>Сборка ELF-файла из CSV</td></tr>
<tr><td><code>kvs_data.py</code></td><td>Общие данные (регистры, инструкции)</td></tr>
</table>
<h2 id="установка">УСТАНОВКА И ЗАПУСК</h2>
<pre># Клонирование репозитория
git clone https://github.com/artradeskz/kvs.git
cd kvs
# Установка прав на выполнение (при необходимости)
chmod +x kvs_build.py
# Компиляция программы
python3 kvs_build.py программа.квс
# Запуск скомпилированной программы
./программа.elf</pre>
<h2 id="синтаксис">СИНТАКСИС ИСХОДНЫХ ФАЙЛОВ (.квс)</h2>
<pre>; Комментарий начинается с точки с запятой
.бсс ; Начало секции неинициализированных данных
счётчик: .байт 0 ; Переменная без начального значения
.данные ; Начало секции инициализированных данных
message: .строка_нуль "Привет, мир!"
.текст ; Начало секции кода
.глобал _start ; Точка входа
_start:
; Абсолютная адресация через метку
переместить_имм раикс, 42
сохранить [счётчик], раикс ; запись в BSS-переменную
; Абсолютная адресация через число
сохранить [0x402000], раикс ; запись по абсолютному адресу
; Косвенная адресация через регистр
переместить_имм рбикс, message
загрузить раикс, [рбикс] ; чтение через регистр
; Системный вызов write
переместить_имм раикс, 1
переместить_имм рсиай, message
переместить_имм рдикс, 13
вызов_системы
; Выход с кодом из счётчика
загрузить рдиай, [счётчик]
переместить_имм раикс, 60
вызов_системы</pre>
<h2 id="команды">СИСТЕМА КОМАНД</h2>
<table>
<tr><th>Категория</th><th>Примеры инструкций</th><th>Соответствие NASM</th></tr>
<tr><td>Перемещение данных</td><td><code>переместить</code>, <code>переместить_имм</code></td><td><code>mov</code>, <code>mov reg, imm</code></td></tr>
<tr><td>Работа с памятью</td><td><code>загрузить</code>, <code>сохранить</code>, <code>загрузить_адрес</code></td><td><code>mov reg, mem</code>, <code>mov mem, reg</code>, <code>lea</code></td></tr>
<tr><td>Арифметика</td><td><code>прибавить</code>, <code>вычесть</code>, <code>увеличить</code>, <code>уменьшить</code></td><td><code>add</code>, <code>sub</code>, <code>inc</code>, <code>dec</code></td></tr>
<tr><td>Логические</td><td><code>сравнить</code>, <code>сравнить_с</code>, <code>проверить</code></td><td><code>cmp</code>, <code>test</code></td></tr>
<tr><td>Условные переходы</td><td><code>переход_если_равно</code>, <code>переход_если_больше</code>, <code>короткий_переход</code></td><td><code>je</code>, <code>jg</code>, <code>jmp short</code></td></tr>
<tr><td>Безусловные переходы</td><td><code>переход</code>, <code>короткий_переход</code></td><td><code>jmp</code>, <code>jmp short</code></td></tr>
<tr><td>Системные</td><td><code>вызов_системы</code></td><td><code>syscall</code></td></tr>
</table>
<h2 id="память">РАБОТА С ПАМЯТЬЮ</h2>
<div class="success">
<strong>✓ РЕАЛИЗОВАНО (v2.0):</strong> Абсолютная адресация (метки и числа), косвенная адресация через регистры
</div>
<h3>Поддерживаемые формы:</h3>
<table>
<tr><th>Синтаксис</th><th>Описание</th><th>Пример</th></tr>
<tr><td><code>[метка]</code></td><td>Адрес метки (из .данные или .бсс)</td><td><code>загрузить раикс, [переменная]</code></td></tr>
<tr><td><code>[число]</code></td><td>Абсолютный адрес</td><td><code>сохранить [0x402000], раикс</code></td></tr>
<tr><td><code>[регистр]</code></td><td>Косвенная адресация через регистр</td><td><code>загрузить раикс, [рбикс]</code></td></tr>
</table>
<h3>Пример использования:</h3>
<pre>.бсс
счётчик: .байт 0
указатель: .байт 0
.данные
переменная: .байт 42
message: .строка_нуль "Hello"
.текст
_start:
; Абсолютная адресация через метку
загрузить раикс, [переменная]
; Абсолютная адресация через число
сохранить [0x402000], раикс
; Косвенная адресация
переместить_имм рбикс, переменная
загрузить раикс, [рбикс]
; Работа с BSS
сохранить [счётчик], раикс
; Завершение
переместить_имм раикс, 60
загрузить рдиай, [счётчик]
вызов_системы</pre>
<div class="note">
<strong>📌 ВАЖНО:</strong> Все тесты из папки <code>тесты/</code> успешно собираются и запускаются, включая тест, воспроизводящий баг с BSS-секцией. Адресация работает корректно во всех формах.
</div>
<h2 id="регистры">РЕГИСТРЫ ПРОЦЕССОРА</h2>
<table>
<tr><th>64-битные</th><th>32-битные</th><th>16-битные</th><th>8-битные (мл.)</th></tr>
<tr><td><code>раикс</code></td><td><code>еаикс</code></td><td><code>аикс</code></td><td><code>ал</code></td></tr>
<tr><td><code>рбикс</code></td><td><code>ебикс</code></td><td><code>бикс</code></td><td><code>бл</code></td></tr>
<tr><td><code>рсикс</code></td><td><code>есикс</code></td><td><code>сикс</code></td><td><code>кл</code></td></tr>
<tr><td><code>рдикс</code></td><td><code>едикс</code></td><td><code>дикс</code></td><td><code>дл</code></td></tr>
<tr><td><code>рсипи</code></td><td><code>есипи</code></td><td><code>эсп</code></td><td><code>спл</code></td></tr>
<tr><td><code>рбипи</code></td><td><code>ебипи</code></td><td><code>бипи</code></td><td><code>бпл</code></td></tr>
<tr><td><code>рсиай</code></td><td><code>есиай</code></td><td><code>эс</code></td><td><code>сил</code></td></tr>
<tr><td><code>рдиай</code></td><td><code>едиай</code></td><td><code>ди</code></td><td><code>дил</code></td></tr>
</table>
<div class="warning">
<strong>⚠️ ПРИМЕЧАНИЕ:</strong> Старшие 8-битные регистры (AH, BH, CH, DH) и расширенные младшие (SPL, BPL, SIL, DIL) не поддерживаются. Регистры R8–R15 также отсутствуют.
</div>
<h2 id="директивы">ДИРЕКТИВЫ АССЕМБЛЕРА</h2>
<table>
<tr><th>Директива</th><th>Назначение</th><th>Пример</th></tr>
<tr><td><code>.текст</code></td><td>Начало секции кода</td><td><code>.текст</code></td></tr>
<tr><td><code>.данные</code></td><td>Начало секции инициализированных данных</td><td><code>.данные</code></td></tr>
<tr><td><code>.бсс</code></td><td>Начало секции неинициализированных данных</td><td><code>.бсс</code></td></tr>
<tr><td><code>.глобал</code></td><td>Объявление глобальной метки</td><td><code>.глобал _start</code></td></tr>
<tr><td><code>.строка</code></td><td>Строка без нулевого терминатора</td><td><code>.строка "Hello"</code></td></tr>
<tr><td><code>.строка_нуль</code></td><td>Строка с нулевым терминатором</td><td><code>.строка_нуль "Hello"</code></td></tr>
<tr><td><code>.байт</code></td><td>Резервирование байтов</td><td><code>.байт 0x0A, 255, 0b0001</code></td></tr>
<tr><td><code>.константа</code></td><td>Определение константы</td><td><code>.константа SIZE = 100</code></td></tr>
</table>
<h2 id="примеры">ПРИМЕРЫ ПРОГРАММ</h2>
<h3>Пример 1: "Привет, мир!"</h3>
<pre>.глобал _start
.текст
_start:
; write(1, msg, 13)
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, msg
переместить_имм рдикс, 13
вызов_системы
; exit(0)
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.данные
msg: .строка_нуль "Hello, World!"</pre>
<h3>Пример 2: Работа с BSS-секцией</h3>
<pre>.глобал _start
.текст
_start:
; Инициализация счётчика
переместить_имм раикс, 0
сохранить [счётчик], раикс
; Цикл: увеличить счётчик 10 раз
переместить_имм рсикс, 10
loop:
загрузить раикс, [счётчик]
увеличить раикс
сохранить [счётчик], раикс
уменьшить рсикс
сравнить_с рсикс, 0
переход_если_больше loop
; Выход с кодом = значение счётчика
загрузить рдиай, [счётчик]
переместить_имм раикс, 60
вызов_системы
.бсс
счётчик: .байт 0</pre>
<h3>Пример 3: Косвенная адресация</h3>
<pre>.глобал _start
.текст
_start:
; Загружаем адрес переменной в регистр
переместить_имм рбикс, переменная
; Косвенная запись
переместить_имм раикс, 99
сохранить [рбикс], раикс
; Косвенное чтение
загрузить раикс, [рбикс]
; Выход
переместить_имм рдиай, раикс
переместить_имм раикс, 60
вызов_системы
.данные
переменная: .байт 0</pre>
<h3>Пример 4: Абсолютный адрес</h3>
<pre>.глобал _start
.текст
_start:
; Запись по абсолютному адресу 0x402000
переместить_имм раикс, 0x2A
сохранить [0x402000], раикс
; Чтение по абсолютному адресу
загрузить рбикс, [0x402000]
; Выход с кодом 42
переместить_имм раикс, 60
переместить_имм рдиай, 0x2A
вызов_системы</pre>
<h2 id="трансляция">ПРОЦЕСС ТРАНСЛЯЦИИ И ВЫХОДНЫЕ ФАЙЛЫ</h2>
<p>Запуск ассемблера:</p>
<pre>python3 kvs_build.py программа.квс</pre>
<p><strong>В результате создаются файлы:</strong></p>
<ul>
<li><strong><code>программа.токены</code></strong> — результат лексического анализа</li>
<li><strong><code>программа.аст</code></strong> — AST-представление</li>
<li><strong><code>программа.проход1</code></strong> — результаты первого прохода (размеры, метки)</li>
<li><strong><code>программа.csv</code></strong> — детальный лог с адресами, байтами и командами</li>
<li><strong><code>программа.elf</code></strong> — исполняемый ELF-файл</li>
</ul>
<h2 id="статус">СТАТУС РЕАЛИЗАЦИИ</h2>
<h3>✅ Полностью реализовано (v2.0)</h3>
<ul>
<li>Лексер с токенами <code>[</code>, <code>]</code>, <code>NUMBER</code></li>
<li>Парсер с поддержкой операндов в квадратных скобках</li>
<li>Первый проход с корректным расчётом размера инструкций с памятью</li>
<li>Второй проход с кодированием адресации через RIP-relative</li>
<li>Абсолютная адресация по меткам <code>[переменная]</code></li>
<li>Абсолютная адресация по числовым адресам <code>[0x402000]</code></li>
<li>Косвенная адресация через регистры <code>[раикс]</code></li>
<li>Секция <code>.бсс</code> для неинициализированных данных</li>
<li>Рефакторинг: выделение кодировщика в отдельный модуль</li>
<li>Более 50 инструкций x86-64</li>
<li>CSV-логирование</li>
</ul>
<h3>⚠️ Частично реализовано / ограничено</h3>
<ul>
<li><code>загрузить_адрес</code> (LEA) — только для чисел, не для меток</li>
<li>Старшие 8-битные регистры (AH, BH, CH, DH) не поддерживаются</li>
<li>Регистры R8–R15 не поддерживаются</li>
</ul>
<h2 id="план">ПЛАН РАЗВИТИЯ</h2>
<h3>v2.0 (текущая) — Адресация и BSS</h3>
<ul>
<li>✅ Лексер: <code>[</code>, <code>]</code>, <code>NUMBER</code></li>
<li>✅ Парсер: <code>[метка]</code>, <code>[число]</code>, <code>[регистр]</code></li>
<li>✅ Pass1: корректный расчёт размера инструкций</li>
<li>✅ Pass2: RIP-relative кодирование</li>
<li>✅ Абсолютная адресация (метки и числа)</li>
<li>✅ Косвенная адресация (<code>[reg]</code>)</li>
<li>✅ Секция <code>.бсс</code></li>
<li>✅ Рефакторинг энкодера</li>
</ul>
<h3>v3.0 (планируется) — Расширенная адресация</h3>
<ul>
<li>❌ <code>[reg + disp]</code> — адресация со смещением</li>
<li>❌ <code>[reg + reg*scale]</code> — с индексным регистром</li>
<li>❌ <code>[reg + reg*scale + disp]</code> — полная SIB-адресация</li>
<li>❌ LEA для меток</li>
<li>❌ PUSH/POP/CALL/RET</li>
</ul>
<h3>v4.0 (отложено) — Стандартная библиотека</h3>
<ul>
<li>❌ <code>malloc</code>, <code>free</code></li>
<li>❌ <code>strlen</code>, <code>strcmp</code>, <code>memcpy</code></li>
<li>❌ <code>putchar</code>, <code>getchar</code></li>
</ul>
<h2 id="ограничения">ОГРАНИЧЕНИЯ</h2>
<div class="warning">
<ul>
<li><strong>Адресация со смещением</strong> — формы <code>[reg+disp]</code>, <code>[reg+reg*scale]</code>, <code>[reg+reg*scale+disp]</code> не реализованы</li>
<li><strong>LEA для меток</strong> — <code>загрузить_адрес reg, [метка]</code> не работает, используйте <code>переместить_имм reg, метка</code></li>
<li><strong>Старшие 8-битные регистры</strong> — AH, BH, CH, DH не поддерживаются</li>
<li><strong>Расширенные регистры</strong> — SPL, BPL, SIL, DIL, а также R8–R15 не поддерживаются</li>
<li><strong>Нет умножения и деления</strong> — инструкции MUL/DIV не реализованы</li>
<li><strong>Нет операций со стеком</strong> — PUSH/POP/CALL/RET отсутствуют</li>
<li><strong>Нет динамической памяти</strong> — нет встроенных malloc/free</li>
<li><strong>Нет макросов и условной компиляции</strong></li>
<li><strong>Экспериментальный статус</strong> — некоторые инструкции могут быть реализованы не полностью</li>
</ul>
</div>
<footer>
<p>───────────────────────────────────────────────────────────────────────</p>
<p>КВС Ассемблер v2.0 | Экспериментальный проект | 2026</p>
<p>Только для образовательных целей. Используйте на свой страх и риск.</p>
<p style="margin-top: 10px; font-size: 10px; color: #005500;">
> Система готова. Введите команду...
<span class="cursor"></span>
</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('h1, h2, h3, p, li, td, th, pre, code');
elements.forEach((el, index) => {
el.style.opacity = '0';
el.style.transition = 'opacity 0.5s ease';
setTimeout(() => { el.style.opacity = '1'; }, 100 + index * 10);
});
});
</script>
</body>
</html>

422
README.md
View file

@ -3,76 +3,123 @@
**КВС** — это экспериментальный ассемблер для архитектуры x86-64, полностью использующий **русскую лексику** для мнемоник инструкций, имён регистров и директив. Проект носит образовательный характер и демонстрирует принципы работы ассемблера, построения ELF-файлов и трансляции высокоуровневых концепций в машинный код.
---
## 📋 Оглавление
- [Особенности](#-особенности)
- [Архитектура](#-архитектура)
- [Установка](#-установка)
- [Быстрый старт](#-быстрый-старт)
- [Система тестирования](#-система-тестирования)
- [Синтаксис](#-синтаксис)
- [Система команд](#-система-команд)
- [Регистры](#-регистры-процессора)
- [Директивы](#-директивы-ассемблера)
- [Примеры программ](#-примеры-программ)
- [Отладка](#-отладка-и-проверка)
- [Отладка и проверка](#-отладка-и-проверка)
- [Ограничения](#-ограничения)
- [План развития](#-план-развития)
- [Лицензия](#-лицензия)
---
## ✨ Особенности
- **Полностью русский синтаксис** — все инструкции, регистры и директивы на кириллице
- **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску
- **Двухпроходная сборка** — традиционная схема с разрешением меток
- **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бсс` (неинициализированные данные)
- **Шестипроходная сборка** — разделение логики на независимые этапы для прозрачности и отладки
- **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бнд` (неинициализированные данные)
- **Гибкая адресация памяти**:
- абсолютная адресация по меткам `[переменная]`
- абсолютная адресация по числовому адресу `[0x402000]`
- косвенная адресация через регистр `[раикс]`
- всё с RIP-relative кодированием для 64-битных перемещений
- **CSV-логирование** — детальный файл с соответствием адресов, байтов и исходных команд
- **CSV-логирование** — детальный файл с соответствием адресов, байтов, меток и исходных команд
- **Более 50 инструкций** — полный набор основных команд x86-64
- **Автоматическая система регрессионного тестирования** — 9+ тестов, проверяющих все аспекты работы ассемблера
- **Минимализм зависимостей** — только стандартная библиотека Python
- **Рефакторинг** — кодогенерация вынесена в отдельный модуль `kvs_pass2_encoder.py`
---
## 🏗 Архитектура
Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через несколько этапов:
Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через **6 этапов**:
```
Исходный файл (.квс)
│
▼ (kvs_lexer.py)
▼ (квс_лексер.py) ← лексический анализ
Файл токенов (.токены)
│
▼ (kvs_parser.py)
▼ (квс_парсер.py) ← синтаксический анализ
AST-представление (.аст)
│
▼ (kvs_pass1.py)
Первый проход: размеры, метки (.проход1)
▼ (квс_проход1.py) ← ПРОХОД 1: генерация CSV с заглушками (0xCC)
CSV-файл (.csv) ← содержит все байты программы, метки, виртуальные адреса
│
▼ (kvs_pass2.py + kvs_pass2_encoder.py)
Второй проход: генерация кода (.csv)
▼ (квс_проход2.py) ← ПРОХОД 2: разрешение меток
│
▼ (kvs_builder.py)
ELF-компоновщик (.elf)
▼ (квс_проход3.py) ← ПРОХОД 3: подстановка констант (.константа)
│
▼ (квс_проход4.py) ← ПРОХОД 4: перекодирование инструкций
│
▼ (квс_проход5.py) ← ПРОХОД 5: генерация корректных таблиц секций для совместимости с toolchain
│
▼ (квс_проход6.py) ← ПРОХОД 6: постобработка CSV для визуального анализа
│
▼ (квс_финсбор.py) ← сборка ELF из CSV
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_pass1.py` | Первый проход: вычисление размеров и адресов меток |
| `kvs_pass2.py` | Второй проход: обход AST, генерация CSV |
| `kvs_pass2_encoder.py` | Кодировщик инструкций (все encode_* функции) |
| `kvs_builder.py` | Сборка ELF-файла из CSV |
| `kvs_data.py` | Общие данные (регистры, инструкции) |
| `квс_сборка.py` | Главный сборочный скрипт |
| `квс_лексер.py` | Лексический анализ |
| `квс_парсер.py` | Синтаксический анализ, построение AST |
| `kvs_data.py` | Общие данные: регистры, таблица инструкций |
| `kvs_encoder.py` | Фасад кодировщика |
| `kvs_encoder_fixsize.py` | Кодировщик инструкций фиксированного размера |
| `kvs_encoder_mutsize.py` | Кодировщик инструкций переменного размера |
| `квс_проход1.py` | Генерация CSV с заглушками |
| `квс_проход2.py` | Разрешение меток |
| `квс_проход3.py` | Подстановка констант |
| `квс_проход4.py` | Перекодирование инструкций |
| `квс_проход5.py` | Генерация таблиц секций |
| `квс_проход6.py` | Сортировка CSV |
| `квс_финсбор.py` | Сборка ELF из CSV |
### Структура CSV-файла
| Колонка | Назначение |
|---------|-------------|
| `сегмент` | Имя секции (`.header`, `.text`, `.data`, `.bss`) |
| `адрес` | Файловое смещение байта |
| `виртуальный_адрес` | Виртуальный адрес байта в памяти |
| `байт` | Исходное значение байта (из pass1) |
| `приводящая_метка` | Имена меток на этом адресе |
| `уводящий_адрес` | Строка `ЗАГЛУШКА имя` для неразрешённых меток |
| `исходная_команда` | Исходная инструкция |
| `команда_со_значениями` | Инструкция с подставленными адресами/константами |
| `рассчитанный_уводящий_адрес` | Реальный адрес метки после разрешения |
| `рассчитанный_байт` | Итоговый байт после перекодирования |
---
## 🔧 Установка
@ -81,13 +128,15 @@ ELF-компоновщик (.elf)
git clone https://github.com/artradeskz/kvs.git
cd kvs
# Установка прав на выполнение (при необходимости)
chmod +x kvs_build.py
# Установка прав на выполнение
chmod +x квс_сборка.py
# Проверка работы
python3 kvs_build.py --help
python3 квс_сборка.py --help
```
---
## 🚀 Быстрый старт
### 1. Создайте файл `hello.квс`:
@ -97,11 +146,11 @@ python3 kvs_build.py --help
.глобал _start
_start:
; write(1, msg, 13)
; write(1, msg, 21)
переместить_имм раикс,1
переместить_имм рдиай,1
переместить_имм рсиай, msg
переместить_имм рдикс,13
переместить_имм рдикс,21
вызов_системы
; exit(0)
@ -110,13 +159,13 @@ _start:
вызов_системы
.данные
msg: .строка_нуль "Hello, World!"
msg: .строка "Привет мир!\n"
```
### 2. Скомпилируйте:
```bash
python3 kvs_build.py hello.квс
python3 квс_сборка.py hello.квс
```
### 3. Запустите:
@ -128,9 +177,71 @@ python3 kvs_build.py hello.квс
**Вывод:**
```
Hello, World!
Привет мир!
```
---
## 🧪 Система тестирования
Проект включает автоматическую систему регрессионного тестирования, которая проверяет все аспекты работы ассемблера.
### Структура тестов
```
тесты/
├── прогон_тестов.py # диспетчер тестов
├── kvs_full_test.csv # таблица активных тестов
├── cleaner.py # очистка артефактов
├── привет/ # тест вывода строки
│ ├── привет.квс
│ └── привет.py
├── сложный_тест/ # тест запутанных переходов
├── длин_тест/ # тест длинных переходов (jmp rel32)
├── вывод_аргумента/ # тест аргументов командной строки
├── тест_адресации_р8/ # косвенная адресация через R8
├── тест_адресации1/ # абсолютная адресация по числовому адресу
├── тест_адресации2/ # абсолютная адресация по метке
├── тест_адресации3/ # косвенная адресация через RBX
├── тест_бнд/ # проверка BSS секции (.bss)
└── ...
```
### Запуск тестов
```bash
python3 тесты/прогон_тестов.py
```
### Добавление нового теста
1. Создать папку `тесты/имя_теста/`
2. Положить туда `имя_теста.квс` и `имя_теста.py`
3. Добавить строку в `kvs_full_test.csv`:
```
1;имя_теста;Описание теста
```
4. Запустить `python3 тесты/прогон_тестов.py`
### Что проверяют тесты
| Тест | Проверяет |
|------|-----------|
| `привет` | Вывод строки на русском, exit(0) |
| `сложный_тест` | Короткие условные переходы |
| `длин_тест` | Длинные переходы (jmp rel32) |
| `вывод_аргумента` | Аргументы командной строки (2 сценария) |
| `тест_адресации_р8` | Косвенную адресацию через R8 |
| `тест_адресации1` | Абсолютную адресацию `[0x402000]` |
| `тест_адресации2` | Абсолютную адресацию по метке |
| `тест_адресации3` | Косвенную адресацию через RBX |
| `тест_бнд` | BSS секцию: NOBITS, размер, p_memsz |
...
всего 26 тестов
---
## 📝 Синтаксис
### Структура программы
@ -142,14 +253,12 @@ Hello, World!
.данные ; секция данных (опциональна)
; ... инициализированные данные ...
.бсс ; секция неинициализированных данных (опциональна)
.бнд ; секция неинициализированных данных (опциональна)
; ... переменные без начального значения ...
```
### Метки
Метка — это имя, за которым следует двоеточие:
```assembly
_start: ; метка _start
переместить_имм раикс,1
@ -158,8 +267,6 @@ _start: ; метка _start
### Комментарии
Однострочные комментарии начинаются с `;`:
```assembly
; Это комментарий
переместить_имм раикс,42 ; комментарий после инструкции
@ -180,13 +287,15 @@ _start: ; метка _start
; Косвенная адресация через регистр
переместить_имм рбикс, переменная
загрузить раикс, [рбикс]
загрузить раикс,[рбикс]
; Работа с BSS-секцией
.бсс
; Работа с BND-секцией
.бнд
счётчик: .байт 0
```
---
## 📖 Система команд
### Загрузка констант
@ -248,10 +357,6 @@ _start: ; метка _start
| `переход_если_перенос` | CF = 1 |
| `переход_если_нет_переноса` | CF = 0 |
**Короткие условные переходы** (префикс `короткий_`):
- `короткий_переход_если_равно`, `короткий_переход_если_неравно`, и т.д.
- Генерируют 2-байтовые инструкции (смещение ±127 байт)
### Системные вызовы
| Инструкция | Описание |
@ -265,6 +370,8 @@ _start: ; метка _start
вызов_системы
```
---
## 💾 Регистры процессора
КВС поддерживает все основные 64-битные регистры x86-64:
@ -280,147 +387,24 @@ _start: ; метка _start
| `рсиай` | `есиай` | `эс` | `сил` |
| `рдиай` | `едиай` | `ди` | `дил` |
---
## 🔧 Директивы ассемблера
| Директива | Назначение | Пример |
|-----------|------------|--------|
| `.текст` | Начало секции кода | `.текст` |
| `.данные` | Начало секции инициализированных данных | `.данные` |
| `.бсс` | Начало секции неинициализированных данных | `.бсс` |
| `.бнд` | Начало секции неинициализированных данных | `.бнд` |
| `.глобал` | Объявление глобальной метки | `.глобал _start` |
| `.строка` | Строка без завершающего нуля | `.строка "Hello"` |
| `.строка_нуль` | Строка с завершающим нулём | `.строка_нуль "Hello"` |
| `.байт` | Последовательность байтов | `.байт 0x48, 0x65, 108` |
| `.константа` | Определение константы | `.константа LEN = 10` |
## 📝 Примеры программ
### Работа с памятью (переменные)
```assembly
.текст
.глобал _start
_start:
; Запись значения в переменную
переместить_имм раикс,42
сохранить [переменная], раикс
; Чтение значения из переменной
загрузить рбикс,[переменная]
; Завершение с кодом возврата
переместить_имм раикс,60
загрузить рдиай,[переменная]
вызов_системы
.данные
переменная: .байт 0
```
### Цикл и условный переход
```assembly
.текст
.глобал _start
_start:
переместить_имм рсикс,10 ; счетчик = 10
loop:
переместить_имм раикс,1
переместить_имм рдиай,1
переместить_имм рсиай, msg
переместить_имм рдикс,10
вызов_системы
уменьшить рсикс ; счетчик--
сравнить_с рсикс,0
переход_если_больше loop ; если > 0, продолжаем
переместить_имм раикс,60
переместить_имм рдиай,0
вызов_системы
.данные
msg: .строка "Iteration\n"
```
### Вычисление суммы чисел от 1 до 10
```assembly
.текст
.глобал _start
_start:
переместить_имм р8,0 ; сумма
переместить_имм р9,10 ; счётчик
loop:
прибавить р8, р9
уменьшить р9
сравнить_с р9,0
переход_если_больше loop
; exit(сумма в r8)
переместить_имм раикс,60
переместить рдикс, р8
вызов_системы
```
### Использование BSS-секции
```assembly
.текст
.глобал _start
_start:
; инициализация счётчика в BSS
переместить_имм раикс,0
сохранить [счётчик], раикс
; цикл: увеличить счётчик 10 раз
переместить_имм рсикс,10
loop:
загрузить раикс,[счётчик]
увеличить раикс
сохранить [счётчик], раикс
уменьшить рсикс
сравнить_с рсикс,0
переход_если_больше loop
; выход с кодом = значение счётчика
загрузить рдиай,[счётчик]
переместить_имм раикс,60
вызов_системы
.бсс
счётчик: .байт 0
```
### Косвенная адресация
```assembly
.текст
.глобал _start
_start:
; загружаем адрес переменной в регистр
переместить_имм рбикс, переменная
; косвенная запись
переместить_имм раикс,99
сохранить [рбикс], раикс
; косвенное чтение
загрузить раикс,[рбикс]
; выход
переместить_имм рдиай, раикс
переместить_имм раикс,60
вызов_системы
.данные
переменная: .байт 0
```
| `.резб` | Резервирование байтов в BND | `.резб 100` |
| `.резс` | Резервирование слов в BND | `.резс 50` |
| `.рездс` | Резервирование двойных слов в BND | `.рездс 25` |
| `.резкс` | Резервирование квадрослов в BND | `.резкс 10` |
## 🐛 Отладка и проверка
@ -442,14 +426,7 @@ strings программа.elf
### CSV-лог
После компиляции создаётся файл `программа.csv` со структурой:
| адрес | байт | целевой_адрес | исходная_команда |
|-------|------|---------------|------------------|
| 0x401000 | 48 | | переместить_имм раикс, 1 |
| 0x401001 | B8 | | |
| 0x401002 | 01 | | |
| ... | ... | ... | ... |
После компиляции создаётся файл `программа.csv` с полной информацией о каждом байте программы.
### Трассировка системных вызовов
@ -462,70 +439,87 @@ 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
```
---
## ⚠️ Ограничения
### Поддерживается
- ✅ 64-битные, 32-битные, 16-битные и 8-битные регистры (AL, BL, CL, DL)
- ✅ Непосредственная загрузка констант (imm) в регистры
- ✅ Сравнение регистров и регистров с константами
- ✅ Условные и безусловные переходы (длинные и короткие)
- ✅ Арифметические операции (ADD, SUB, INC, DEC)
- ✅ Логическая операция TEST
- ✅ Системные вызовы Linux (syscall)
- ✅ Секции `.текст`, `.данные` и `.бсс`
- ✅ Строковые литералы с escape-последовательностями (`\n`, `\t`, `\\`, `\"`)
- ✅ Директива `.байт` для raw-данных
- ✅ Директива `.константа` для имён констант
- ✅ **Абсолютная адресация** по меткам и числовым адресам `[метка]` / `[0x402000]`
- ✅ **Косвенная адресация через регистр** `[раикс]`
- ✅ RIP-relative кодирование для всех видов адресации
- 64-битные, 32-битные, 16-битные и 8-битные регистры (AL, BL, CL, DL)
- Непосредственная загрузка констант (imm) в регистры
- Сравнение регистров и регистров с константами
- Условные и безусловные переходы (длинные и короткие)
- Арифметические операции (ADD, SUB, INC, DEC)
- Логическая операция TEST
- Системные вызовы Linux (syscall)
- Секции `.текст`, `.данные` и `.бнд`
- Строковые литералы с escape-последовательностями (`\n`, `\t`, `\\`, `\"`)
- Директива `.байт` для raw-данных
- Директива `.константа` для имён констант
- Директивы резервирования `.резб`, `.резс`, `.рездс`, `.резкс`
- Абсолютная адресация по меткам и числовым адресам `[метка]` / `[0x402000]`
- Косвенная адресация через регистр `[раикс]`
- RIP-relative кодирование для всех видов адресации
- Полная совместимость с `objdump`, `readelf`, `gdb`
### Не поддерживается (в разработке)
- ❌ Адресация со смещением `[reg + disp]`
- ❌ Сложная адресация с индексом `[reg + reg*scale]` и полная SIB-адресация
- ❌ Инструкции для работы со стеком (PUSH, POP, CALL, RET)
- ❌ Старшие 8-битные регистры (AH, BH, CH, DH) и расширенные младшие (SPL, BPL, SIL, DIL)
- ❌ Регистры R8–R15 и их байтовые/словные версии
- ❌ Поддержка плавающей запятой (FPU/SSE)
- ❌ Многомодульная компиляция
- ❌ Макросы
- Регистр р12 избегать пока
- Адресация со смещением `[reg + disp]`
- Сложная адресация с индексом `[reg + reg*scale]`
- Полная SIB-адресация `[reg + reg*scale + disp]`
- Поддержка плавающей запятой (FPU/SSE)
- Многомодульная компиляция
- Макросы
---
## Инструменты разработчика
**Подсветка синтаксиса для VS Code** — доступно расширение [Universal Assembly Syntax Highlighting](https://github.com/artradeskz/Universal-Assembly-Syntax-Highlighting) с поддержкой языка КВС (русские инструкции, регистры, секции и директивы)
**КМП - кодомнемонический преобразователь.** [Дизассемблер](https://github.com/artradeskz/kmp) на языке квс. Выводит мнемоники на русском.
---
## 📈 План развития
### Реализовано (v2.0)
- Лексер с токенами `[`, `]`, `NUMBER`
- Лексер с поддержкой `[]`, `+`, `-`, `*`
- Парсер с поддержкой операндов в квадратных скобках
- Первый проход с корректным расчётом размера инструкций с памятью
- Второй проход с кодированием абсолютной адресации через RIP-relative
- Шестипроходная схема сборки (включая генерацию таблиц секций и сортировку CSV)
- Поддержка меток в операндах памяти `[переменная]`
- Поддержка числовых адресов `[0x402000]`
- Поддержка косвенной адресации `[регистр]`
- Секция `.бсс` для неинициализированных данных
- Рефакторинг: выделение кодировщика в отдельный модуль
- Секция `.бнд` для неинициализированных данных
- Автоматическая система регрессионного тестирования (9+ тестов)
- Полная совместимость со стандартным toolchain Linux
- Русификация основных скриптов сборки
### В планах (v3.0)
- Адресация со смещением `[reg + disp]`
- Сложная адресация с индексом `[reg + reg*scale]`
- Полная SIB-адресация `[reg + reg*scale + disp]`
- Поддержка инструкций PUSH/POP/CALL/RET для стековых операций
- Стандартная библиотека (`libkvs.квс`): malloc, free, strlen, strcmp, print
- Полная русификация всех модулей (включая кодировщики)
---
## 📄 Лицензия
Только для образовательных целей. Используйте на свой страх и риск.
---
*КВС Ассемблер | Экспериментальный проект | 2026*
*Только для образовательных целей. Используйте на свой страх и риск.*
```

View file

@ -1,217 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Сборщик ELF из CSV
"""
import sys
import struct
import os
sys.path.insert(0, '.')
from kvs_data import PAGE_SIZE, align_up, SHT_NOBITS, SHF_WRITE, SHF_ALLOC
def read_csv(input_file, vaddr_text, vaddr_data, text_size, data_size):
"""Загружает байты из CSV используя точные виртуальные адреса"""
text_bytes = bytearray(text_size)
data_bytes = bytearray(data_size)
with open(input_file, 'r', encoding='utf-8') as f:
next(f)
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(';')
if len(parts) < 2:
continue
addr_str = parts[0]
byte_str = parts[1]
try:
addr = int(addr_str, 16)
byte_val = int(byte_str, 16)
except ValueError:
continue
if addr >= vaddr_data:
offset = addr - vaddr_data
if 0 <= offset < data_size:
data_bytes[offset] = byte_val
elif addr >= vaddr_text:
offset = addr - vaddr_text
if 0 <= offset < text_size:
text_bytes[offset] = byte_val
# Заменяем хвостовые нули на NOP'ы (0x90)
# Находим последний ненулевой байт
last_nonzero = -1
for i in range(len(text_bytes) - 1, -1, -1):
if text_bytes[i] != 0:
last_nonzero = i
break
if last_nonzero >= 0 and last_nonzero < len(text_bytes) - 1:
for i in range(last_nonzero + 1, len(text_bytes)):
text_bytes[i] = 0x90
return text_bytes, data_bytes
def read_pass1(input_file):
data = {}
labels = {}
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(':')
if parts[0] == "PARAM":
key = parts[1]
value = parts[2]
if value.isdigit():
value = int(value)
data[key] = value
elif parts[0] == "LABEL":
label = parts[1]
pos = int(parts[3])
labels[label] = pos
return data, labels
def create_elf(text_bytes, data_bytes, pass1_data, labels, output_file):
vaddr_text = pass1_data.get("vaddr_text", 0x401000)
text_size = pass1_data.get("text_size", len(text_bytes))
data_size = pass1_data.get("data_size", len(data_bytes))
bnd_size = pass1_data.get("bnd_size", 0)
# Вычисляем виртуальные адреса
vaddr_data = align_up(vaddr_text + text_size, PAGE_SIZE)
vaddr_bnd = pass1_data.get("vaddr_bnd", align_up(vaddr_data + data_size, PAGE_SIZE))
offset_text = pass1_data.get("offset_text", 0x1000)
offset_data = align_up(offset_text + len(text_bytes), PAGE_SIZE)
offset_comment = pass1_data.get("offset_comment", align_up(offset_data + len(data_bytes), 1))
shstrtab_offset = pass1_data.get("shstrtab_offset", align_up(offset_comment + 32, 8))
shdr_offset = pass1_data.get("shdr_offset", align_up(shstrtab_offset + 32, 16))
entry_point_name = pass1_data.get("entry_point", "_start")
entry_addr = vaddr_text + labels.get(entry_point_name, 0)
comment_content = "Сборщик КВС".encode('utf-8') + b'\x00'
# shstrtab с учётом .bss
if bnd_size > 0:
shstrtab_content = b"\x00.text\x00.data\x00.bss\x00.comment\x00.shstrtab\x00"
else:
shstrtab_content = b"\x00.text\x00.data\x00.comment\x00.shstrtab\x00"
elf_header_size = 64
ph_size = 56
ph_num = 3
shdr_size = 64
shdr_num = 5 + (1 if bnd_size > 0 else 0) # 5 базовых + .bss если есть
file_size = shdr_offset + shdr_num * shdr_size
elf_data = bytearray(file_size)
# ELF header
e_ident = b"\x7fELF\x02\x01\x01\x00" + b"\x00" * 8
elf_header = struct.pack(
'<16sHHIQQQIHHHHHH',
e_ident, 2, 0x3e, 1, entry_addr, elf_header_size, shdr_offset,
0, elf_header_size, ph_size, ph_num, shdr_size, shdr_num, 4
)
elf_data[0:64] = elf_header
def phdr(p_type, p_flags, p_offset, p_vaddr, p_filesz, p_memsz):
return struct.pack('<IIQQQQQQ',
p_type, p_flags, p_offset, p_vaddr, p_vaddr,
p_filesz, p_memsz, PAGE_SIZE
)
# Program headers
ph0 = phdr(1, 4, 0, 0x400000, elf_header_size + ph_num * ph_size, PAGE_SIZE)
ph1 = phdr(1, 5, offset_text, vaddr_text, len(text_bytes), align_up(len(text_bytes), PAGE_SIZE))
if bnd_size > 0:
data_segment_filesz = len(data_bytes)
# memsz должно покрывать от vaddr_data до конца .bss
bss_end = vaddr_bnd + bnd_size
data_segment_memsz = align_up(bss_end - vaddr_data, PAGE_SIZE)
ph2 = phdr(1, 6, offset_data, vaddr_data, data_segment_filesz, data_segment_memsz)
else:
ph2 = phdr(1, 6, offset_data, vaddr_data, len(data_bytes), align_up(len(data_bytes), PAGE_SIZE))
phdrs = ph0 + ph1 + ph2
elf_data[elf_header_size : elf_header_size + len(phdrs)] = phdrs
# Данные секций
elf_data[offset_text : offset_text + len(text_bytes)] = text_bytes
elf_data[offset_data : offset_data + len(data_bytes)] = data_bytes
elf_data[offset_comment : offset_comment + len(comment_content)] = comment_content
elf_data[shstrtab_offset : shstrtab_offset + len(shstrtab_content)] = shstrtab_content
def shdr(name_idx, sh_type, flags, addr, offset, size, addralign=1):
return struct.pack('<IIQQQQIIQQ',
name_idx, sh_type, flags, addr, offset, size,
0, 0, addralign, 0
)
# Индексы имён в shstrtab зависят от наличия .bss
if bnd_size > 0:
name_text = 1 # ".text"
name_data = 7 # ".data"
name_bss = 13 # ".bss"
name_comment = 18 # ".comment"
name_shstrtab = 27 # ".shstrtab"
else:
name_text = 1
name_data = 7
name_comment = 13
name_shstrtab = 22
# Section headers
sh0 = shdr(0, 0, 0, 0, 0, 0) # NULL
sh1 = shdr(name_text, 1, 6, vaddr_text, offset_text, len(text_bytes), 16) # .text
sh2 = shdr(name_data, 1, 3, vaddr_data, offset_data, len(data_bytes), 8) # .data
if bnd_size > 0:
sh3 = shdr(name_bss, SHT_NOBITS, SHF_WRITE | SHF_ALLOC, vaddr_bnd, 0, bnd_size, 16) # .bss
sh4 = shdr(name_comment, 1, 0, 0, offset_comment, len(comment_content), 1) # .comment
sh5 = shdr(name_shstrtab, 3, 0, 0, shstrtab_offset, len(shstrtab_content), 1) # .shstrtab
shdrs = sh0 + sh1 + sh2 + sh3 + sh4 + sh5
else:
sh3 = shdr(name_comment, 1, 0, 0, offset_comment, len(comment_content), 1)
sh4 = shdr(name_shstrtab, 3, 0, 0, shstrtab_offset, len(shstrtab_content), 1)
shdrs = sh0 + sh1 + sh2 + sh3 + sh4
elf_data[shdr_offset : shdr_offset + len(shdrs)] = shdrs
with open(output_file, "wb") as f:
f.write(elf_data)
os.chmod(output_file, 0o755)
print(f"Сборщик: ELF-файл создан: {output_file}")
print(f" .text: {len(text_bytes)} байт, адрес: 0x{vaddr_text:x}")
print(f" .data: {len(data_bytes)} байт, адрес: 0x{vaddr_data:x}")
if bnd_size > 0:
print(f" .bss: {bnd_size} байт, адрес: 0x{vaddr_bnd:x}")
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Использование: python kvs_builder.py <вход.csv> <вход.проход1> <выход.elf>")
sys.exit(1)
pass1_data, labels = read_pass1(sys.argv[2])
vaddr_text = pass1_data["vaddr_text"]
text_size = pass1_data["text_size"]
data_size = pass1_data["data_size"]
# Вычисляем vaddr_data
vaddr_data = align_up(vaddr_text + text_size, PAGE_SIZE)
text_bytes, data_bytes = read_csv(sys.argv[1], vaddr_text, vaddr_data, text_size, data_size)
create_elf(text_bytes, data_bytes, pass1_data, labels, sys.argv[3])

View file

@ -50,16 +50,16 @@ REGISTERS = {
"р15": 15, # R15 (общего назначения, пятнадцатый)
# 32-битные младшие части (обнуляют старшие 32 бита при записи)
"еаикс": 0, # EAX - младшие 32 бита RAX
"есикс": 1, # ECX - младшие 32 бита RCX
"едикс": 2, # EDX - младшие 32 бита RDX
"ебикс": 3, # EBX - младшие 32 бита RBX
"есипи": 4, # ESP - младшие 32 бита RSP
"ебипи": 5, # EBP - младшие 32 бита RBP
"есиай": 6, # ESI - младшие 32 бита RSI
"едиай": 7, # EDI - младшие 32 бита RDI
"р8д": 8, # R8D - младшие 32 бита R8
"р9д": 9, # R9D - младшие 32 бита R9
"еаикс": 0, # EAX - младшие 32 бита RAX
"есикс": 1, # ECX - младшие 32 бита RCX
"едикс": 2, # EDX - младшие 32 бита RDX
"ебикс": 3, # EBX - младшие 32 бита RBX
"есипи": 4, # ESP - младшие 32 бита RSP
"ебипи": 5, # EBP - младшие 32 бита RBP
"есиай": 6, # ESI - младшие 32 бита RSI
"едиай": 7, # EDI - младшие 32 бита RDI
"р8д": 8, # R8D - младшие 32 бита R8
"р9д": 9, # R9D - младшие 32 бита R9
"р10д": 10, # R10D - младшие 32 бита R10
"р11д": 11, # R11D - младшие 32 бита R11
"р12д": 12, # R12D - младшие 32 бита R12
@ -68,16 +68,16 @@ REGISTERS = {
"р15д": 15, # R15D - младшие 32 бита R15
# 16-битные младшие части (не обнуляют старшие биты)
"аикс": 0, # AX - младшие 16 бит RAX
"сикс": 1, # CX - младшие 16 бит RCX
"дикс": 2, # DX - младшие 16 бит RDX
"бикс": 3, # BX - младшие 16 бит RBX
"эсп": 4, # SP - младшие 16 бит RSP
"бипи": 5, # BP - младшие 16 бит RBP
"эс": 6, # SI - младшие 16 бит RSI
"ди": 7, # DI - младшие 16 бит RDI
"р8в": 8, # R8W - младшие 16 бит R8
"р9в": 9, # R9W - младшие 16 бит R9
"аикс": 0, # AX - младшие 16 бит RAX
"сикс": 1, # CX - младшие 16 бит RCX
"дикс": 2, # DX - младшие 16 бит RDX
"бикс": 3, # BX - младшие 16 бит RBX
"эсп": 4, # SP - младшие 16 бит RSP
"бипи": 5, # BP - младшие 16 бит RBP
"эс": 6, # SI - младшие 16 бит RSI
"ди": 7, # DI - младшие 16 бит RDI
"р8в": 8, # R8W - младшие 16 бит R8
"р9в": 9, # R9W - младшие 16 бит R9
"р10в": 10, # R10W - младшие 16 бит R10
"р11в": 11, # R11W - младшие 16 бит R11
"р12в": 12, # R12W - младшие 16 бит R12
@ -87,15 +87,15 @@ REGISTERS = {
# 8-битные младшие части
"ал": 0, # AL - младший байт AX (биты 0-7)
"кл": 1, # CL - младший байт CX (используется в сдвигах)
"дл": 2, # DL - младший байт DX
"бл": 3, # BL - младший байт BX
"спл": 4, # SPL - младший байт SP
"бпл": 5, # BPL - младший байт BP
"сил": 6, # SIL - младший байт SI
"дил": 7, # DIL - младший байт DI
"р8б": 8, # R8B - младший байт R8
"р9б": 9, # R9B - младший байт R9
"кл": 1, # CL - младший байт CX (используется в сдвигах)
"дл": 2, # DL - младший байт DX
"бл": 3, # BL - младший байт BX
"спл": 4, # SPL - младший байт SP
"бпл": 5, # BPL - младший байт BP
"сил": 6, # SIL - младший байт SI
"дил": 7, # DIL - младший байт DI
"р8б": 8, # R8B - младший байт R8
"р9б": 9, # R9B - младший байт R9
"р10б": 10, # R10B - младший байт R10
"р11б": 11, # R11B - младший байт R11
"р12б": 12, # R12B - младший байт R12
@ -242,75 +242,75 @@ INSTRUCTIONS = {
# Инструкции с ФИКСИРОВАННЫМ размером (не зависят от операндов)
FIXED_SIZE_INSTRUCTIONS = {
"переместить": 3,
"сравнить": 3,
"сравнить_байт": 3,
"проверить": 3,
"прибавить": 3,
"прибавить_байт": 3,
"вычесть": 3,
"вычесть_байт": 3,
"увеличить": 3,
"уменьшить": 3,
"умножить": 3,
"умножить_знаковое": 3,
"разделить": 3,
"разделить_знаковое": 3,
"и": 3,
"или": 3,
"исключающее_или": 3,
"инвертировать": 3,
"отрицать": 3,
"переход": 5,
"короткий_переход": 2,
"переход_если_равно": 6,
"переход_если_неравно": 6,
"переход_если_ноль": 6,
"переход_если_не_ноль": 6,
"переход_если_меньше": 6,
"переход_если_больше": 6,
"переход_если_меньше_или_равно": 6,
"переход_если_больше_или_равно": 6,
"переход_если_перенос": 6,
"переход_если_нет_переноса": 6,
"короткий_переход_если_равно": 2,
"короткий_переход_если_неравно": 2,
"короткий_переход_если_меньше": 2,
"короткий_переход_если_больше": 2,
"переместить" : 3,
"сравнить" : 3,
"сравнить_байт" : 3,
"проверить" : 3,
"прибавить" : 3,
"прибавить_байт" : 3,
"вычесть" : 3,
"вычесть_байт" : 3,
"увеличить" : 3,
"уменьшить" : 3,
"умножить" : 3,
"умножить_знаковое" : 3,
"разделить" : 3,
"разделить_знаковое" : 3,
"и" : 3,
"или" : 3,
"исключающее_или" : 3,
"инвертировать" : 3,
"отрицать" : 3,
"переход" : 5,
"короткий_переход" : 2,
"переход_если_равно" : 6,
"переход_если_неравно" : 6,
"переход_если_ноль" : 6,
"переход_если_не_ноль" : 6,
"переход_если_меньше" : 6,
"переход_если_больше" : 6,
"переход_если_меньше_или_равно" : 6,
"переход_если_больше_или_равно" : 6,
"переход_если_перенос" : 6,
"переход_если_нет_переноса" : 6,
"короткий_переход_если_равно" : 2,
"короткий_переход_если_неравно" : 2,
"короткий_переход_если_меньше" : 2,
"короткий_переход_если_больше" : 2,
"короткий_переход_если_меньше_или_равно": 2,
"короткий_переход_если_больше_или_равно": 2,
"короткий_переход_если_перенос": 2,
"короткий_переход_если_нет_переноса": 2,
"короткий_переход_если_ноль": 2,
"короткий_переход_если_не_ноль": 2,
"вызвать": 5,
"вернуться": 1,
"вызов_системы": 2,
"нет_операции": 1,
"остановить": 1,
"отладка": 1,
"цикл": 2,
"обменять": 4,
"сдвиг_влево": 4,
"сдвиг_вправо": 4,
"сдвиг_арифметический_вправо": 4,
"вращать_влево": 4,
"вращать_вправо": 4,
"прервать": 2,
"ввод_байта": 2,
"вывод_байта": 2,
"установить_перенос": 1,
"сбросить_перенос": 1,
"установить_направление": 1,
"сбросить_направление": 1,
"втолкнуть_флаги": 1,
"вытолкнуть_флаги": 1,
"переместить_байт": 1,
"переместить_слово": 1,
"сравнить_байты": 1,
"сканировать_байт": 1,
"идентифицировать_процессор": 2,
"прочитать_счётчик": 2,
"короткий_переход_если_перенос" : 2,
"короткий_переход_если_нет_переноса" : 2,
"короткий_переход_если_ноль" : 2,
"короткий_переход_если_не_ноль" : 2,
"вызвать" : 5,
"вернуться" : 1,
"вызов_системы" : 2,
"нет_операции" : 1,
"остановить" : 1,
"отладка" : 1,
"цикл" : 2,
"обменять" : 4,
"сдвиг_влево" : 4,
"сдвиг_вправо" : 4,
"сдвиг_арифметический_вправо" : 4,
"вращать_влево" : 4,
"вращать_вправо" : 4,
"прервать" : 2,
"ввод_байта" : 2,
"вывод_байта" : 2,
"установить_перенос" : 1,
"сбросить_перенос" : 1,
"установить_направление" : 1,
"сбросить_направление" : 1,
"втолкнуть_флаги" : 1,
"вытолкнуть_флаги" : 1,
"переместить_байт" : 1,
"переместить_слово" : 1,
"сравнить_байты" : 1,
"сканировать_байт" : 1,
"идентифицировать_процессор" : 2,
"прочитать_счётчик" : 2,
}
# Инструкции с ПЕРЕМЕННЫМ размером (зависят от операндов)
@ -331,6 +331,45 @@ VARIABLE_SIZE_INSTRUCTIONS = {
"переместить_со_знаком", # зависит от размера регистра
}
# === ИНСТРУКЦИИ, ВСЕГДА ТРЕБУЮЩИЕ ЗАГЛУШКУ НА ПЕРВОМ ПРОХОДЕ ===
# Эти инструкции содержат целевые метки в операндах, которые могут быть
# определены позже (в том числе в других секциях)
ALWAYS_STUB_INSTRUCTIONS = {
# Безусловные переходы
"переход",
"короткий_переход",
# Вызовы
"вызвать",
# Циклы
"цикл",
# Длинные условные переходы (32-битное смещение)
"переход_если_равно",
"переход_если_неравно",
"переход_если_меньше",
"переход_если_больше",
"переход_если_меньше_или_равно",
"переход_если_больше_или_равно",
"переход_если_перенос",
"переход_если_нет_переноса",
"переход_если_ноль",
"переход_если_не_ноль",
# Короткие условные переходы (8-битное смещение)
"короткий_переход_если_равно",
"короткий_переход_если_неравно",
"короткий_переход_если_меньше",
"короткий_переход_если_больше",
"короткий_переход_если_меньше_или_равно",
"короткий_переход_если_больше_или_равно",
"короткий_переход_если_перенос",
"короткий_переход_если_нет_переноса",
"короткий_переход_если_ноль",
"короткий_переход_если_не_ноль",
}
# === ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===

View file

@ -13,7 +13,7 @@ import sys
from kvs_data import INSTRUCTIONS, REGISTERS, get_reg_info
# Импорт из fixsize
from kvs_pass2_encoder_fixsize import (
from kvs_encoder_fixsize import (
encode_mov_reg_reg,
encode_mov_reg8_imm8,
encode_cmp_reg_reg,
@ -41,7 +41,7 @@ from kvs_pass2_encoder_fixsize import (
)
# Импорт из mutsize
from kvs_pass2_encoder_mutsize import (
from kvs_encoder_mutsize import (
parse_operand,
parse_memory_operand,
encode_mov_reg_mem_absolute,

View file

@ -186,44 +186,114 @@ def encode_mov_mem_reg_absolute(reg_info, mem_info, current_pos, vaddr_text):
return code
def encode_mov_reg_mem_indirect(reg_info, base_reg):
def encode_mov_reg_mem_indirect(reg_info: dict, base_reg: str) -> bytearray:
"""
Кодирует MOV reg, [base_reg] — косвенная адресация.
Пример: mov rax, [rbx] → 48 8B 03
Кодирует MOV reg, [base_reg] — косвенная адресация через регистр.
Аргументы:
- reg_info: словарь от get_reg_info() с ключами 'index' (int) и 'size' (int)
- base_reg: строка с именем регистра, например 'рбикс'
REGISTERS[base_reg] возвращает число-индекс (int), НЕ словарь.
Особые случаи x86-64:
- [rsp] и [r12] (индексы 4 и 12) требуют SIB-байта при mod=00
- [rbp] и [r13] (индексы 5 и 13) требуют disp8=0 при mod=00
"""
code = bytearray()
reg = reg_info['index']
base = REGISTERS[base_reg] # REGISTERS возвращает int (индекс регистра)
rex = 0x48 if reg_info['size'] == 64 else 0x40
if reg >= 8:
rex |= 0x04 # REX.R
if base >= 8:
rex |= 0x01 # REX.B
code.append(rex)
code.append(0x8B) # MOV r64, r/m64
# === 1. Получаем индексы регистров ===
reg_index = reg_info['index']
reg_size = reg_info['size']
# Особый случай: [rbp] или [r13] требует disp8=0 при mod=00
# (mod=00 с r/m=101 означает RIP-relative, а не [rbp])
if (base & 7) == 5:
# mod=01 (disp8), reg, r/m=5
modrm = 0x40 | ((reg & 7) << 3) | 5
code.append(modrm)
code.append(0x00) # disp8 = 0
if base_reg not in REGISTERS:
raise ValueError(f"Неизвестный базовый регистр: {base_reg}")
base_index = REGISTERS[base_reg]
# === 2. Выбираем opcode и префиксы ===
#
# В x86-64 для MOV существуют разные opcodes:
# - 8A /r — MOV r8, r/m8 (только 8-бит!)
# - 8B /r — MOV r16/r32/r64, r/m16/r/m32/r/m64
#
# Для 16-бит нужен префикс 0x66
# Для 64-бит нужен REX.W (бит 3 в REX)
#
if reg_size == 8:
opcode = 0x8A # MOV r8, r/m8
rex_base = 0x40 # Без REX.W
need_prefix_66 = False
elif reg_size == 16:
opcode = 0x8B # MOV r16, r/m16
rex_base = 0x40 # Без REX.W
need_prefix_66 = True # ← ВАЖНО: префикс 0x66 для 16-бит!
elif reg_size == 32:
opcode = 0x8B # MOV r32, r/m32
rex_base = 0x40 # Без REX.W
need_prefix_66 = False
elif reg_size == 64:
opcode = 0x8B # MOV r64, r/m64
rex_base = 0x48 # С REX.W (бит 3 = 1)
need_prefix_66 = False
else:
# mod=00, reg, r/m=base
modrm = ((reg & 7) << 3) | (base & 7)
code.append(modrm)
raise ValueError(f"Неподдерживаемый размер регистра: {reg_size} бит")
# === 3. Формируем REX-префикс ===
rex = rex_base
# REX.R: для регистра-источника (reg) — нужен если индекс >= 8
if reg_index >= 8:
rex |= 0x04 # REX.R = 1
# REX.B: для базового регистра (base) — нужен если индекс >= 8
if base_index >= 8:
rex |= 0x01 # REX.B = 1
# === 4. Формируем ModR/M и SIB байты ===
base_low = base_index & 7 # младшие 3 бита индекса
if base_low == 4:
# Особый случай: [rsp] или [r12] — требуется SIB-байт
# В x86-64 mod=00 с r/m=4 означает [SIB] без смещения
# SIB: base=4 (rsp/r12), index=4 (нет индексного регистра), scale=0
modrm_byte = 0x00 | ((reg_index & 7) << 3) | 0x04
sib_byte = 0x20 | (4 << 3) | 4 # scale=0, index=4(нет), base=4
disp8 = None
has_sib = True
elif base_low == 5:
# Особый случай: [rbp] или [r13] — требуется disp8=0
# В x86-64 mod=00 с r/m=5 означает [RIP+disp32],
# поэтому для [rbp]/[r13] используем mod=01 с disp8=0
modrm_byte = 0x40 | ((reg_index & 7) << 3) | 0x05
disp8 = 0x00
has_sib = False
else:
# Обычный случай: [reg] — mod=00, без SIB, без смещения
modrm_byte = ((reg_index & 7) << 3) | base_low
disp8 = None
has_sib = False
# === 5. Собираем инструкцию ===
# 5a. Префикс 0x66 для 16-битных операндов (ДО REX!)
if need_prefix_66:
code.append(0x66)
# 5b. REX-префикс (если не равен 0x40)
if rex != 0x40:
code.append(rex)
# 5c. Opcode
code.append(opcode)
# 5d. ModR/M байт
code.append(modrm_byte)
# 5e. SIB байт (только для RSP/R12)
if has_sib:
code.append(sib_byte)
# 5f. Disp8 (только для RBP/R13)
if disp8 is not None:
code.append(disp8)
return code
def encode_mov_mem_reg_indirect(base_reg, reg_info):
"""
Кодирует MOV [base_reg], reg — косвенная адресация.
@ -892,7 +962,10 @@ def encode_pop_reg(operands): # ДУБЛИКАТ
code.append(0x58 + reg)
else:
code.append(0x41)
code.append(0x58 + (reg - 8))
if reg<12: # так тесты работают
code.append(0x58 + (reg - 8))
else:
code.append(0x58 + (reg - 8))
elif size == 16:
code.append(0x66)
if reg < 8:

View file

@ -1,347 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Парсер КВС
Принимает: файл токенов от лексера
Выдаёт: текстовый файл с AST-структурой
Поддерживает сложную адресацию:
- [регистр + регистр*масштаб + смещение]
"""
import sys
sys.path.insert(0, '.')
from kvs_data import REGISTERS, INSTRUCTIONS
def make_memory_operand(addr_type, base_reg=None, displacement=None, label=None):
"""Создаёт кортеж операнда памяти.
Формат: (addr_type, base_reg, displacement, label)
"""
return (addr_type, base_reg, displacement, label)
def memory_operand_to_ast_string(mem_op):
"""Преобразует кортеж памяти в строку AST.
Формат: 'MEM:тип:данные'
"""
addr_type = mem_op[0]
if addr_type == 'absolute':
if mem_op[3] is not None: # label
return f"MEM:absolute:{mem_op[3]}"
else: # displacement
return f"MEM:absolute:{mem_op[2]}"
elif addr_type == 'register_indirect':
return f"MEM:reg_indirect:{mem_op[1]}"
elif addr_type == 'complex':
# Пока не поддерживается, но зарезервировано
return f"MEM:complex:{mem_op[1]}+{mem_op[2]}*{mem_op[3]}+{mem_op[4]}"
else:
return f"MEM:unknown"
def read_tokens(input_file):
"""Читает токены из файла"""
tokens = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
colon_pos = line.find(':')
if colon_pos == -1:
continue
tok_type = line[:colon_pos]
tok_value = line[colon_pos + 1:]
tokens.append((tok_type, tok_value))
return tokens
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.pos = 0
self.current_section = ".text"
self.parsed_lines = []
def peek(self):
if self.pos < len(self.tokens):
return self.tokens[self.pos]
return None
def next_token(self):
tok = self.peek()
if tok:
self.pos += 1
return tok
def expect(self, expected_type):
tok = self.next_token()
if tok is None or tok[0] != expected_type:
raise ValueError(f"Ожидался {expected_type}, получено {tok}")
return tok
def expect_one_of(self, expected_types):
"""Ожидает один из нескольких типов токенов"""
tok = self.peek()
if tok is None or tok[0] not in expected_types:
raise ValueError(f"Ожидался один из {expected_types}, получено {tok}")
return self.next_token()
def parse_line(self):
tok = self.peek()
if tok is None:
return False
if tok[0] == 'NEWLINE':
self.next_token()
tok = self.peek()
if tok is None:
return False
if tok[0] == 'WORD':
# Проверяем, есть ли после слова двоеточие (метка)
if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1][0] == 'COLON':
label_name = self.next_token()[1]
self.next_token() # COLON
self.parsed_lines.append(f"LABEL:{label_name}:{self.current_section}")
next_tok = self.peek()
if next_tok and next_tok[0] != 'NEWLINE':
return self.parse_instruction_or_directive()
return True
return self.parse_instruction_or_directive()
def parse_instruction_or_directive(self):
tok = self.peek()
if tok is None or tok[0] == 'NEWLINE':
return True
if tok[0] != 'WORD':
raise ValueError(f"Ожидалось слово, получено: {tok}")
word = self.next_token()[1]
if word.startswith('.'):
self.parse_directive(word)
else:
self.parse_instruction(word)
return True
def parse_directive(self, word):
"""
Разбор директив ассемблера.
Вызывает ошибку при некорректном синтаксисе или контексте.
"""
# === Секции ===
if word == '.текст':
self.current_section = ".text"
self.parsed_lines.append(f"DIRECTIVE:{word}")
elif word == '.данные':
self.current_section = ".data"
self.parsed_lines.append(f"DIRECTIVE:{word}")
elif word == '.бнд':
self.current_section = ".бнд"
self.parsed_lines.append(f"DIRECTIVE:{word}")
# === Глобальные метки ===
elif word == '.глобал':
label = self.expect('WORD')[1]
self.parsed_lines.append(f"DIRECTIVE:{word}:{label}")
# === Строковые директивы (запрещены в .бнд) ===
elif word in ('.строка_нуль', '.строка'):
if self.current_section == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
string_tok = self.expect('STRING')
self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:{string_tok[1]}")
# === Константы (запрещены в .бнд) ===
elif word == '.константа':
if self.current_section == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
name = self.expect('WORD')[1]
# Пропускаем '=', если есть
eq_tok = self.peek()
if eq_tok and eq_tok[0] == 'WORD' and eq_tok[1] == '=':
self.next_token()
# Значение может быть WORD или NUMBER
value_tok = self.expect_one_of(['WORD', 'NUMBER'])
self.parsed_lines.append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}")
# === Байты (запрещены в .бнд) ===
elif word == '.байт':
if self.current_section == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
bytes_list = []
while True:
tok = self.peek()
if tok is None or tok[0] == 'NEWLINE' or tok[0] == 'COMMA':
if tok and tok[0] == 'COMMA':
self.next_token()
else:
break
else:
# Может быть WORD (метка) или NUMBER (число)
val_tok = self.expect_one_of(['WORD', 'NUMBER', 'STRING'])
bytes_list.append(val_tok[1])
self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:" + ",".join(bytes_list))
# === Директивы резервирования (только в .бнд) ===
elif word in ('.резб', '.резс', '.рездс', '.резкс'):
if self.current_section != ".бнд":
raise ValueError(
f"Ошибка: директива {word} допустима только внутри секции .бнд"
)
count_tok = self.expect('NUMBER')
count = int(count_tok[1])
if count <= 0:
raise ValueError(
f"Ошибка: размер резервирования должен быть положительным целым числом"
)
self.parsed_lines.append(
f"DIRECTIVE:{word}:{self.current_section}:{count}"
)
else:
raise ValueError(f"Неизвестная директива: {word}")
def parse_operand(self):
"""
Разбирает один операнд.
Возвращает строку (регистр, число, метка, строка, старый формат памяти)
или кортеж (новый формат памяти).
Поддерживает:
- регистры
- числа
- метки
- строки
- адресацию в квадратных скобках
"""
tok = self.peek()
if tok is None:
return None
if tok[0] == 'LBRACKET':
self.next_token() # съедаем '['
return self.parse_memory_operand()
elif tok[0] == 'NUMBER':
return self.next_token()[1]
elif tok[0] == 'STRING':
return self.next_token()[1]
elif tok[0] == 'WORD':
return self.next_token()[1]
else:
raise ValueError(f"Неожиданный токен в операнде: {tok}")
def parse_memory_operand(self):
"""
Разбирает операнд памяти.
Открывающая скобка уже прочитана в parse_operand.
Возвращает:
- строку "[выражение]" для абсолютной адресации (старый формат)
- кортеж (addr_type, base_reg, displacement, label) для косвенной
Поддерживает:
[регистр] → косвенная адресация (кортеж)
[число] → абсолютная (строка)
[метка] → абсолютная (строка)
[регистр + ...] → пока не поддерживается (строка)
"""
parts = []
# Разбираем содержимое до закрывающей скобки
while True:
tok = self.peek()
if tok is None:
raise ValueError("Незакрытая квадратная скобка")
if tok[0] == 'RBRACKET':
self.next_token()
break
if tok[0] in ('WORD', 'NUMBER', 'PLUS', 'MINUS', 'STAR'):
parts.append(self.next_token()[1])
else:
raise ValueError(f"Неожиданный токен в адресации: {tok}")
# Анализируем собранные части
if len(parts) == 1:
part = parts[0]
# Проверяем, регистр ли это
if part in REGISTERS:
# Косвенная адресация: [регистр]
return make_memory_operand('register_indirect', base_reg=part)
# Иначе: [число] или [метка] — возвращаем строку (старый формат)
# Старое поведение для всего остального
# (абсолютная адресация, комплексная - пока не поддерживается)
expr = ' '.join(parts)
return f"[{expr}]"
def parse_instruction(self, mnemonic):
if mnemonic not in INSTRUCTIONS:
raise ValueError(f"Неизвестная инструкция: {mnemonic}")
operands = []
while True:
tok = self.peek()
if tok is None or tok[0] == 'NEWLINE':
break
if tok[0] == 'COMMA':
self.next_token()
continue
operands.append(self.parse_operand())
# Преобразуем операнды в строки для AST
str_operands = []
for op in operands:
if isinstance(op, tuple) and len(op) >= 2 and op[0] in ('absolute', 'register_indirect', 'complex'):
# Это кортеж памяти
str_operands.append(memory_operand_to_ast_string(op))
else:
# Это обычная строка (регистр, число, метка, старый формат памяти)
str_operands.append(op)
self.parsed_lines.append(f"INSTR:{mnemonic}:{self.current_section}:" + ",".join(str_operands))
def parse_all(self):
while self.pos < len(self.tokens):
self.parse_line()
return self.parsed_lines
def write_ast(ast_lines, output_file):
with open(output_file, 'w', encoding='utf-8') as f:
for line in ast_lines:
f.write(line + '\n')
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python kvs_parser.py <вход.токены> <выход.аст>")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
tokens = read_tokens(input_file)
parser = Parser(tokens)
ast_lines = parser.parse_all()
write_ast(ast_lines, output_file)
print(f"Парсер: {len(ast_lines)} строк AST записано в {output_file}")

View file

@ -1,400 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Первый проход КВС
Вычисляет размеры секций и адреса меток
"""
import sys
sys.path.insert(0, '.')
from kvs_data import PAGE_SIZE, text_vaddr_base, align_up, INSTRUCTIONS
from kvs_data import FIXED_SIZE_INSTRUCTIONS, VARIABLE_SIZE_INSTRUCTIONS
# ========== РЕЖИМ ОЦЕНКИ РАЗМЕРОВ ==========
# True — пессимистичный: все неизвестные инструкции = 10 байт (максимум)
# False — оптимистичный: использовать точный анализ операндов
PESSIMISTIC_SIZE_ESTIMATE = False
def unescape_string(s):
"""Преобразует escape-последовательности в реальные символы"""
result = []
i = 0
while i < len(s):
if s[i] == '\\' and i + 1 < len(s):
if s[i + 1] == 'n':
result.append('\n')
elif s[i + 1] == 't':
result.append('\t')
elif s[i + 1] == 'r':
result.append('\r')
else:
result.append(s[i])
result.append(s[i + 1])
i += 2
else:
result.append(s[i])
i += 1
return ''.join(result)
def read_ast(input_file):
ast_lines = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
ast_lines.append(line.strip())
return ast_lines
def is_hex_number(s):
"""Проверяет, является ли строка шестнадцатеричным числом"""
if len(s) < 3:
return False
if not (s[0] == '0' and (s[1] == 'x' or s[1] == 'X')):
return False
for ch in s[2:]:
if not (('0' <= ch <= '9') or ('a' <= ch <= 'f') or ('A' <= ch <= 'F')):
return False
return True
def is_dec_number(s):
"""Проверяет, является ли строка десятичным числом (возможно с минусом)"""
if not s:
return False
start = 0
if s[0] == '-':
if len(s) == 1:
return False
start = 1
for ch in s[start:]:
if ch < '0' or ch > '9':
return False
return True
def is_register(s):
"""Проверяет, является ли строка именем регистра"""
return s in ['раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15']
def split_by_operators(content):
"""
Разбивает строку по операторам + и - с сохранением операторов.
Аналог re.split(r'([+\-])', content)
Пример: "раикс + рбикс*4 - 8" -> ["раикс ", "+", " рбикс*4 ", "-", " 8"]
"""
parts = []
current = ''
for ch in content:
if ch == '+' or ch == '-':
if current:
parts.append(current)
parts.append(ch)
current = ''
else:
current += ch
if current:
parts.append(current)
return parts
def parse_memory_operand(operand_str):
"""
Анализирует операнд памяти вида [reg + reg*scale + disp]
Возвращает словарь с информацией.
"""
if not operand_str.startswith('[') or not operand_str.endswith(']'):
return None
content = operand_str[1:-1].strip()
if not content:
return None
result = {
'has_base': False,
'base_reg': None,
'has_index': False,
'index_reg': None,
'scale': 1,
'has_disp': False,
'disp_value': 0,
'disp_size': 0
}
if is_dec_number(content) or is_hex_number(content):
result['has_disp'] = True
if content.startswith('0x') or content.startswith('0X'):
result['disp_value'] = int(content, 16)
else:
result['disp_value'] = int(content)
result['disp_size'] = 4
return result
if is_register(content):
result['has_base'] = True
result['base_reg'] = content
return result
parts = split_by_operators(content)
for part in parts:
part = part.strip()
if not part or part == '+' or part == '-':
continue
if is_register(part):
if not result['has_base']:
result['has_base'] = True
result['base_reg'] = part
elif not result['has_index']:
result['has_index'] = True
result['index_reg'] = part
elif is_dec_number(part) or is_hex_number(part):
result['has_disp'] = True
if part.startswith('0x') or part.startswith('0X'):
result['disp_value'] = int(part, 16)
else:
result['disp_value'] = int(part)
elif '*' in part:
star_pos = part.find('*')
if star_pos != -1:
reg_part = part[:star_pos].strip()
scale_part = part[star_pos + 1:].strip()
if is_register(reg_part):
result['has_index'] = True
result['index_reg'] = reg_part
result['scale'] = int(scale_part)
if result['has_disp']:
if -128 <= result['disp_value'] <= 127:
result['disp_size'] = 1
else:
result['disp_size'] = 4
return result
def estimate_memory_operand_size(operand_str):
"""Оценивает размер операнда памяти в байтах (ModR/M + SIB + disp)"""
if not operand_str:
return 0
addr_mode = parse_memory_operand(operand_str)
if addr_mode is None:
return 0
size = 1
if addr_mode['has_index']:
size += 1
if addr_mode['has_disp']:
size += addr_mode['disp_size']
return size
class Pass1:
def __init__(self):
self.labels = {}
self.label_sections = {}
self.symbols = {}
self.position = {".text": 0, ".data": 0, ".бнд": 0}
self.current_section = ".text"
self.entry_point = "_start"
def process_line(self, line):
parts = line.split(':')
line_type = parts[0]
if line_type == "DIRECTIVE":
directive = parts[1]
if directive == '.текст':
self.current_section = ".text"
elif directive == '.данные':
self.current_section = ".data"
elif directive == '.бнд':
self.current_section = ".бнд"
elif directive == '.глобал':
self.entry_point = parts[2]
elif directive in ('.строка_нуль', '.строка'):
s = parts[3] if len(parts) > 3 else ""
real_s = unescape_string(s)
bstring = real_s.encode('utf-8')
add_null = (directive == '.строка_нуль')
size = len(bstring) + (1 if add_null else 0)
self.position[".data"] += size
elif directive == '.константа':
name = parts[2]
value_str = parts[3]
if is_dec_number(value_str):
self.symbols[name] = int(value_str)
elif is_hex_number(value_str):
self.symbols[name] = int(value_str, 16)
else:
self.symbols[name] = 0
elif directive == '.байт':
sec = parts[2]
if len(parts) > 3 and parts[3]:
num_bytes = 1
for ch in parts[3]:
if ch == ',':
num_bytes += 1
self.position[sec] += num_bytes
elif directive == '.резб':
count = int(parts[3])
self.position['.бнд'] += count * 1
elif directive == '.резс':
count = int(parts[3])
self.position['.бнд'] += count * 2
elif directive == '.рездс':
count = int(parts[3])
self.position['.бнд'] += count * 4
elif directive == '.резкс':
count = int(parts[3])
self.position['.бнд'] += count * 8
elif line_type == "LABEL":
label_name = parts[1]
sec = parts[2]
self.labels[label_name] = self.position[sec]
self.label_sections[label_name] = sec
elif line_type == "INSTR":
mnemonic = parts[1]
sec = parts[2]
operands_str = parts[3] if len(parts) > 3 else ""
operands = []
if operands_str:
current = ''
for ch in operands_str:
if ch == ',':
operands.append(current)
current = ''
else:
current += ch
if current:
operands.append(current)
size = self.estimate_size(mnemonic, operands)
self.position[sec] += size
def estimate_size(self, mnemonic, operands):
"""Динамический расчёт размера инструкции"""
# Инструкции с фиксированным размером
if mnemonic in FIXED_SIZE_INSTRUCTIONS:
return FIXED_SIZE_INSTRUCTIONS[mnemonic]
# Инструкции с переменным размером — анализируем операнды
if mnemonic in ("загрузить", "сохранить", "загрузить_адрес"):
for op in operands:
if op.startswith('MEM:reg_indirect:'):
return 3 # косвенная: REX + opcode + ModR/M
return 7 # абсолютная: REX + opcode + ModR/M + disp32
if mnemonic in ("втолкнуть", "вытолкнуть"):
if operands:
reg = operands[0]
if reg in ('р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
return 2 # нужен REX-префикс
return 1 # без REX
if mnemonic in ("прибавить_непосредственно", "вычесть_непосредственно", "сравнить_с"):
return 7 # пессимистично: REX + opcode + ModR/M + imm32
if mnemonic == "переместить_имм":
return 10 # пессимистично: mov reg64, imm64
if mnemonic in ("переместить_с_нулями", "переместить_со_знаком"):
return 8 # REX + 3-байтный opcode + ModR/M + disp32
if mnemonic == "загрузить_байт":
return 2 # opcode + imm8
if mnemonic == "втолкнуть_непосредственно":
return 5 # пессимистично: opcode + imm32
# Неизвестная инструкция
if PESSIMISTIC_SIZE_ESTIMATE:
return 10
else:
return 3
def calculate_layout(self):
text_size = self.position[".text"]
data_size = self.position[".data"]
bnd_size = self.position[".бнд"]
elf_header_size = 64
ph_size = 56
ph_num = 3
ph_table_size = ph_num * ph_size
offset_text = align_up(elf_header_size + ph_table_size, PAGE_SIZE)
offset_data = align_up(offset_text + text_size, PAGE_SIZE)
vaddr_text = text_vaddr_base
vaddr_data = align_up(vaddr_text + text_size, PAGE_SIZE)
vaddr_bnd = vaddr_data + PAGE_SIZE
comment_size = len("Сборщик КВС".encode('utf-8')) + 1
offset_comment = align_up(offset_data + data_size, 1)
if bnd_size > 0:
shstrtab_content = b"\x00.text\x00.data\x00.bss\x00.comment\x00.shstrtab\x00"
else:
shstrtab_content = b"\x00.text\x00.data\x00.comment\x00.shstrtab\x00"
shstrtab_size = len(shstrtab_content)
shstrtab_offset = align_up(offset_comment + comment_size, 8)
shdr_offset = align_up(shstrtab_offset + shstrtab_size, 16)
return {
"text_size": text_size,
"data_size": data_size,
"bnd_size": bnd_size,
"offset_text": offset_text,
"offset_data": offset_data,
"vaddr_text": vaddr_text,
"vaddr_data": vaddr_data,
"vaddr_bnd": vaddr_bnd,
"offset_comment": offset_comment,
"comment_size": comment_size,
"shstrtab_offset": shstrtab_offset,
"shstrtab_size": shstrtab_size,
"shdr_offset": shdr_offset,
"entry_point": self.entry_point,
}
def write_pass1(pass1_data, labels, label_sections, symbols, output_file):
with open(output_file, 'w', encoding='utf-8') as f:
for key, value in pass1_data.items():
f.write(f"PARAM:{key}:{value}\n")
for label, pos in labels.items():
sec = label_sections.get(label, ".text")
f.write(f"LABEL:{label}:{sec}:{pos}\n")
for name, value in symbols.items():
f.write(f"SYMBOL:{name}:{value}\n")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python kvs_pass1.py <вход.аст> <выход.проход1>")
sys.exit(1)
ast_lines = read_ast(sys.argv[1])
pass1 = Pass1()
for line in ast_lines:
pass1.process_line(line)
layout = pass1.calculate_layout()
write_pass1(layout, pass1.labels, pass1.label_sections, pass1.symbols, sys.argv[2])
print(f"Проход 1 (режим: {'пессимистичный' if PESSIMISTIC_SIZE_ESTIMATE else 'оптимистичный'}):")
print(f" .text: размер={layout['text_size']} байт, смещение_в_файле=0x{layout['offset_text']:x}, виртуальный_адрес=0x{layout['vaddr_text']:x}")
print(f" .data: размер={layout['data_size']} байт, смещение_в_файле=0x{layout['offset_data']:x}, виртуальный_адрес=0x{layout['vaddr_data']:x}")
print(f" .бнд: размер={layout['bnd_size']} байт, виртуальный_адрес=0x{layout['vaddr_bnd']:x}")
print(f" entry_point: {layout['entry_point']}")
print(f" метки:")
for label, pos in pass1.labels.items():
sec = pass1.label_sections.get(label, ".text")
if sec == ".text":
addr = layout['vaddr_text'] + pos
elif sec == ".data":
addr = layout['vaddr_data'] + pos
elif sec == ".бнд":
addr = layout['vaddr_bnd'] + pos
else:
addr = pos
print(f" {label}: секция={sec}, смещение={pos}, адрес=0x{addr:x}")

View file

@ -1,349 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Второй проход КВС
Генерирует машинный код и создаёт CSV-файл
"""
import sys
import struct
sys.path.insert(0, '.')
from kvs_data import PAGE_SIZE, align_up
from kvs_pass2_encoder import encode_instruction, parse_operand
def unescape_string(s):
"""Преобразует escape-последовательности в реальные символы"""
result = []
i = 0
while i < len(s):
if s[i] == '\\' and i + 1 < len(s):
if s[i + 1] == 'n':
result.append('\n')
elif s[i + 1] == 't':
result.append('\t')
elif s[i + 1] == 'r':
result.append('\r')
else:
result.append(s[i])
result.append(s[i + 1])
i += 2
else:
result.append(s[i])
i += 1
return ''.join(result)
def read_ast(input_file):
"""Читает AST из файла"""
ast_lines = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
ast_lines.append(line.strip())
return ast_lines
def read_pass1(input_file):
"""
Читает результаты первого прохода:
- PARAM: параметры компоновки (размеры, адреса)
- LABEL: метки и их позиции
- SYMBOL: константы
"""
data = {}
labels = {}
label_sections = {}
symbols = {}
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(':')
if parts[0] == "PARAM":
key = parts[1]
value = parts[2]
if value.isdigit() or (value.startswith('-') and value[1:].isdigit()):
value = int(value)
data[key] = value
elif parts[0] == "LABEL":
label = parts[1]
sec = parts[2]
pos = int(parts[3])
labels[label] = pos
label_sections[label] = sec
elif parts[0] == "SYMBOL":
name = parts[1]
value = int(parts[2])
symbols[name] = value
return data, labels, label_sections, symbols
# ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ПАРСИНГА AST ==========
def parse_ast_line(line):
"""
Универсальный разбор строки AST.
Возвращает (тип, словарь_полей).
Форматы AST:
- DIRECTIVE:имя
- DIRECTIVE:имя:секция
- DIRECTIVE:имя:секция:данные
- DIRECTIVE:имя:имя_константы:значение
- LABEL:имя:секция
- INSTR:мнемоника:секция:операнд1,операнд2,...
Операнды могут содержать двоеточия (MEM:reg_indirect:рбикс).
Поэтому для INSTR используется специальный разбор: первые 3 двоеточия
отделяют служебные поля, остальное — операнды.
"""
if not line:
return None, {}
line_type = line.split(':')[0]
if line_type == "INSTR":
# Формат: INSTR:мнемоника:секция:операнды
# Находим первые три двоеточия
first_colon = line.find(':')
second_colon = line.find(':', first_colon + 1)
third_colon = line.find(':', second_colon + 1)
if first_colon == -1 or second_colon == -1 or third_colon == -1:
return "INSTR", {}
mnemonic = line[first_colon + 1 : second_colon]
section = line[second_colon + 1 : third_colon]
operands_str = line[third_colon + 1 :]
operands = []
if operands_str:
operands = operands_str.split(',')
return "INSTR", {
'mnemonic': mnemonic,
'section': section,
'operands': operands,
'operands_str': operands_str
}
elif line_type == "DIRECTIVE":
parts = line.split(':')
directive = parts[1] if len(parts) > 1 else ""
if directive in ('.текст', '.данные', '.бнд'):
return "DIRECTIVE", {'directive': directive}
elif directive == '.глобал':
label = parts[2] if len(parts) > 2 else ""
return "DIRECTIVE", {'directive': directive, 'label': label}
elif directive in ('.строка_нуль', '.строка'):
sec = parts[2] if len(parts) > 2 else ""
s = parts[3] if len(parts) > 3 else ""
return "DIRECTIVE", {'directive': directive, 'section': sec, 'string': s}
elif directive == '.константа':
name = parts[2] if len(parts) > 2 else ""
value = parts[3] if len(parts) > 3 else ""
return "DIRECTIVE", {'directive': directive, 'name': name, 'value': value}
elif directive == '.байт':
sec = parts[2] if len(parts) > 2 else ""
bytes_str = parts[3] if len(parts) > 3 else ""
return "DIRECTIVE", {'directive': directive, 'section': sec, 'bytes': bytes_str}
elif directive in ('.резб', '.резс', '.рездс', '.резкс'):
sec = parts[2] if len(parts) > 2 else ""
count = parts[3] if len(parts) > 3 else "0"
return "DIRECTIVE", {'directive': directive, 'section': sec, 'count': count}
else:
return "DIRECTIVE", {'directive': directive}
elif line_type == "LABEL":
parts = line.split(':')
label_name = parts[1] if len(parts) > 1 else ""
sec = parts[2] if len(parts) > 2 else ""
return "LABEL", {'name': label_name, 'section': sec}
return line_type, {}
# ========== ОСНОВНОЙ КЛАСС ВТОРОГО ПРОХОДА ==========
class Pass2:
def __init__(self, pass1_data, labels, label_sections, symbols):
self.labels = labels
self.label_sections = label_sections
self.symbols = symbols
self.vaddr_text = pass1_data["vaddr_text"]
self.vaddr_data = pass1_data.get("vaddr_data", align_up(self.vaddr_text + pass1_data["text_size"], PAGE_SIZE))
self.vaddr_bnd = pass1_data.get("vaddr_bnd", self.vaddr_data + PAGE_SIZE)
self.position = {".text": 0, ".data": 0}
self.current_section = ".text"
self.csv_lines = []
self.data_bytes = {".text": bytearray(), ".data": bytearray()}
def get_target_addr(self, mnemonic, operands):
"""Вычисляет целевой адрес для инструкций перехода"""
jump_instructions = {
"переход", "короткий_переход", "вызвать", "цикл",
"переход_если_равно", "переход_если_неравно", "переход_если_меньше",
"переход_если_больше", "переход_если_меньше_или_равно", "переход_если_больше_или_равно",
"переход_если_перенос", "переход_если_нет_переноса", "переход_если_ноль", "переход_если_не_ноль",
"короткий_переход_если_равно", "короткий_переход_если_неравно", "короткий_переход_если_меньше",
"короткий_переход_если_больше", "короткий_переход_если_меньше_или_равно", "короткий_переход_если_больше_или_равно",
"короткий_переход_если_перенос", "короткий_переход_если_нет_переноса",
"короткий_переход_если_ноль", "короткий_переход_если_не_ноль"
}
try:
if mnemonic in jump_instructions:
target = parse_operand(operands[0], self.labels, self.label_sections,
self.symbols, self.vaddr_text, self.vaddr_data)
return hex(target)
elif mnemonic == "переместить_имм" and len(operands) >= 2:
op_str = operands[1]
if op_str in self.labels:
sec = self.label_sections[op_str]
if sec == ".text":
addr = self.labels[op_str] + self.vaddr_text
elif sec == ".data":
addr = self.labels[op_str] + self.vaddr_data
elif sec == ".бнд":
addr = self.labels[op_str] + self.vaddr_bnd
else:
addr = self.labels[op_str] + self.vaddr_text
return hex(addr)
elif op_str in self.symbols:
return hex(self.symbols[op_str])
except Exception:
return "ошибка"
return ""
def process_line(self, line):
"""Обрабатывает одну строку AST"""
line_type, fields = parse_ast_line(line)
if line_type == "DIRECTIVE":
directive = fields.get('directive', '')
if directive == '.текст':
self.current_section = ".text"
elif directive == '.данные':
self.current_section = ".data"
elif directive in ('.строка_нуль', '.строка'):
s = fields.get('string', '')
real_s = unescape_string(s)
bstring = real_s.encode('utf-8')
if directive == '.строка_нуль':
bstring += b'\x00'
original_cmd = f'{directive} "{s}"'
start_addr = self.vaddr_data + self.position[".data"]
for i, byte in enumerate(bstring):
addr = start_addr + i
byte_hex = "{:02X}".format(byte)
cmd = original_cmd if i == 0 else ""
self.csv_lines.append((hex(addr), byte_hex, "", cmd))
self.data_bytes[".data"] += bstring
self.position[".data"] += len(bstring)
elif directive == '.байт':
bytes_str = fields.get('bytes', '')
if bytes_str:
byte_values = bytes_str.split(',')
original_cmd = f".байт {bytes_str}"
start_addr = self.vaddr_data + self.position[".data"]
for i, byte_str in enumerate(byte_values):
if byte_str.startswith('0x'):
val = int(byte_str, 16)
elif byte_str.isdigit() or (byte_str.startswith('-') and byte_str[1:].isdigit()):
val = int(byte_str)
else:
continue
addr = start_addr + i
byte_hex = "{:02X}".format(val & 0xFF)
cmd = original_cmd if i == 0 else ""
self.csv_lines.append((hex(addr), byte_hex, "", cmd))
self.data_bytes[".data"].append(val & 0xFF)
self.position[".data"] += len(byte_values)
elif line_type == "LABEL":
label_name = fields.get('name', '')
sec = fields.get('section', '')
if label_name in self.labels:
self.position[sec] = self.labels[label_name]
elif line_type == "INSTR":
mnemonic = fields.get('mnemonic', '')
sec = fields.get('section', '')
operands = fields.get('operands', [])
if operands:
original_cmd = f"{mnemonic} {', '.join(operands)}"
else:
original_cmd = mnemonic
current_pos = self.position[sec]
code = encode_instruction(mnemonic, operands, self.labels, self.label_sections,
self.symbols, self.vaddr_text, self.vaddr_data, current_pos,
self.vaddr_bnd)
target_addr = self.get_target_addr(mnemonic, operands)
start_addr = (self.vaddr_text if sec == ".text" else self.vaddr_data) + current_pos
for i, byte in enumerate(code):
addr = start_addr + i
byte_hex = "{:02X}".format(byte)
cmd = original_cmd if i == 0 else ""
addr_target = target_addr if i == 0 else ""
self.csv_lines.append((hex(addr), byte_hex, addr_target, cmd))
self.data_bytes[sec] += code
self.position[sec] += len(code)
def process_all(self, ast_lines):
"""Обрабатывает все строки AST"""
for line in ast_lines:
self.process_line(line)
return self.csv_lines
def write_csv(csv_lines, output_file):
"""Записывает сгенерированные байты в CSV файл"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write("адрес;байт;целевой_адрес;исходная_команда\n")
for addr, byte, target, cmd in csv_lines:
if cmd and (';' in cmd or '\n' in cmd):
cmd = '"' + cmd.replace('"', '""') + '"'
f.write(f"{addr};{byte};{target};{cmd}\n")
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Использование: python kvs_pass2.py <вход.аст> <вход.проход1> <выход.csv>")
sys.exit(1)
ast_lines = read_ast(sys.argv[1])
pass1_data, labels, label_sections, symbols = read_pass1(sys.argv[2])
pass2 = Pass2(pass1_data, labels, label_sections, symbols)
csv_lines = pass2.process_all(ast_lines)
write_csv(csv_lines, sys.argv[3])
print(f"Проход 2: {len(csv_lines)} строк CSV записано в {sys.argv[3]}")
print(f" .text: {pass2.position['.text']} байт, виртуальный адрес: 0x{pass2.vaddr_text:x}")
print(f" .data: {pass2.position['.data']} байт, виртуальный адрес: 0x{pass2.vaddr_data:x}")

View file

@ -1,247 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Верификатор КВС
Сравнивает размеры инструкций из pass1 с реальными размерами из CSV.
Выводит расхождения и останавливает сборку при ошибках.
"""
import sys
# Если True — разрешает сборку, когда единственная ошибка это общий размер .text
# (расхождение до 16 байт считается допустимым)
ALLOW_TEXT_SIZE_MISMATCH = True
MAX_TEXT_SIZE_MISMATCH = 25
def read_pass1(input_file):
"""Читает pass1-файл: PARAM и LABEL"""
params = {}
labels = {}
label_sections = {}
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(':')
if parts[0] == "PARAM":
key = parts[1]
value = parts[2]
if value.isdigit() or (value.startswith('-') and value[1:].isdigit()):
value = int(value)
params[key] = value
elif parts[0] == "LABEL":
label = parts[1]
sec = parts[2]
pos = int(parts[3])
labels[label] = pos
label_sections[label] = sec
return params, labels, label_sections
def read_csv(input_file):
"""
Читает CSV-файл.
Возвращает:
- instr_list: список инструкций с адресами и размерами
"""
instr_list = []
current_cmd = None
current_start = None
current_size = 0
with open(input_file, 'r', encoding='utf-8') as f:
header = f.readline()
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(';')
if len(parts) < 2:
continue
addr_str = parts[0]
byte_str = parts[1]
cmd_str = parts[3] if len(parts) > 3 else ""
try:
addr = int(addr_str, 16)
except ValueError:
continue
if addr >= 0x402000:
break
if cmd_str:
if current_cmd is not None:
instr_list.append({
'cmd': current_cmd,
'start': current_start,
'size': current_size
})
current_cmd = cmd_str
current_start = addr
current_size = 1
else:
current_size += 1
if current_cmd is not None:
instr_list.append({
'cmd': current_cmd,
'start': current_start,
'size': current_size
})
return instr_list
def verify(pass1_file, csv_file):
"""Сравнивает pass1 и pass2, возвращает список ошибок"""
from kvs_data import FIXED_SIZE_INSTRUCTIONS, INSTRUCTIONS
params, labels, label_sections = read_pass1(pass1_file)
instr_list = read_csv(csv_file)
vaddr_text = params.get("vaddr_text", 0x401000)
text_size_pass1 = params.get("text_size", 0)
text_size_pass2 = sum(instr['size'] for instr in instr_list)
errors = []
warnings = []
print(f"Pass1 .text: {text_size_pass1} байт")
print(f"Pass2 .text: {text_size_pass2} байт")
print()
# Проверка общего размера
text_size_error = None
if text_size_pass1 != text_size_pass2:
diff = text_size_pass2 - text_size_pass1
text_size_error = f"Общий размер .text: pass1={text_size_pass1}, pass2={text_size_pass2} (разница {diff:+d} байт)"
if ALLOW_TEXT_SIZE_MISMATCH and abs(diff) <= MAX_TEXT_SIZE_MISMATCH:
warnings.append(text_size_error + " — допустимо, сборка продолжена")
else:
errors.append(text_size_error)
# Проверка позиций меток
print("Проверка позиций меток (.text):")
for label, pass1_offset in labels.items():
if label_sections.get(label) != ".text":
continue
pass1_addr = vaddr_text + pass1_offset
found = False
for instr in instr_list:
if instr['start'] == pass1_addr:
found = True
break
if found:
print(f" [✓] {label} — 0x{pass1_addr:x}")
else:
closest = None
for instr in instr_list:
if instr['start'] <= pass1_addr:
closest = instr
if closest:
real_addr = closest['start']
shift = real_addr - pass1_addr
if shift != 0:
errors.append(f"Метка '{label}': pass1=0x{pass1_addr:x}, pass2=0x{real_addr:x} (сдвиг {shift:+d} байт)")
print(f" [✗] {label} — pass1: 0x{pass1_addr:x}, pass2: 0x{real_addr:x} (сдвиг {shift:+d})")
else:
print(f" [✓] {label} — 0x{pass1_addr:x}")
else:
warnings.append(f"Метка '{label}' не найдена в CSV")
print(f" [?] {label} — не найдена в CSV")
print()
# Сравнение размеров инструкций
print("Проверка размеров инструкций:")
for instr in instr_list:
cmd = instr['cmd']
real_size = instr['size']
real_addr = instr['start']
mnemonic = cmd.split()[0] if cmd else ""
if mnemonic in FIXED_SIZE_INSTRUCTIONS:
pass1_size = FIXED_SIZE_INSTRUCTIONS[mnemonic]
elif mnemonic in ("загрузить", "сохранить", "загрузить_адрес"):
if 'MEM:reg_indirect:' in cmd:
pass1_size = 3
else:
pass1_size = 7
elif mnemonic in ("втолкнуть", "вытолкнуть"):
parts = cmd.split()
if len(parts) >= 2:
reg = parts[1]
if reg in ('р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
pass1_size = 2
else:
pass1_size = 1
else:
pass1_size = 1
elif mnemonic in ("прибавить_непосредственно", "вычесть_непосредственно", "сравнить_с"):
pass1_size = 7
elif mnemonic == "переместить_имм":
pass1_size = 10
elif mnemonic in ("переместить_с_нулями", "переместить_со_знаком"):
pass1_size = 8
elif mnemonic == "загрузить_байт":
pass1_size = 2
elif mnemonic == "втолкнуть_непосредственно":
pass1_size = 5
else:
instr_info = INSTRUCTIONS.get(mnemonic)
if instr_info and "opcode" in instr_info and instr_info["opcode"] is not None:
pass1_size = len(instr_info["opcode"])
else:
pass1_size = 3
status = "✓" if pass1_size == real_size else "✗"
if pass1_size != real_size:
diff = real_size - pass1_size
errors.append(f"Инструкция '{cmd}' на 0x{real_addr:x}: pass1={pass1_size}, pass2={real_size} (ошибка {diff:+d})")
print(f" [{status}] 0x{real_addr:x} {cmd} — pass1: {pass1_size}, pass2: {real_size}" +
(f" (ошибка {real_size - pass1_size:+d})" if pass1_size != real_size else ""))
print()
return errors, warnings
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python kvs_verify.py <вход.проход1> <вход.csv>")
sys.exit(1)
pass1_file = sys.argv[1]
csv_file = sys.argv[2]
errors, warnings = verify(pass1_file, csv_file)
if warnings:
print(f"Предупреждений: {len(warnings)}")
for w in warnings:
print(f" ⚠ {w}")
print()
if errors:
print(f"ОШИБОК: {len(errors)}")
for e in errors:
print(f" ❌ {e}")
print("\nСборка остановлена. Исправьте расхождения в размерах инструкций.")
sys.exit(1)
else:
print("Все размеры инструкций совпадают. Можно собирать.")
sys.exit(0)

View file

@ -252,21 +252,6 @@ Pass2 (шаг 5):
сохранить_байт и загрузить_байт_из_памяти есть в INSTRUCTIONS, но не реализованы в диспетчере encode_instruction
это же почти стандартная библиотека зародилась?...
itoa — преобразование числа в строку (аналог sprintf/itoa в C)
длина_числа — утилита для работы с числами
Вывод в терминал — через write (аналог puts/printf)
Работа с .бнд — выделение памяти под переменные
Циклы и переходы — управляющие конструкции
Следующий шаг — atoi (строка → число) и можно будет делать полноценный ввод/вывод.
тестировать вытолкнуть
тестировать косвенную адресацию на р9-15
бага с двоеточием в строке

View file

@ -0,0 +1,878 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Первый проход КВС (однопроходная версия с заглушками)
Генерирует CSV-таблицу напрямую из AST
- Неизвестные метки заменяются заглушками (0xCC) с пометкой в колонке 'уводящий_адрес'
- В колонке 'приводящая_метка' записываются имена меток, находящихся по данному адресу (через запятую)
- Колонка 'команда_со_значениями' оставлена пустой (для второго прохода)
- Колонки 'рассчитанный_уводящий_адрес' и 'рассчитанный_байт' оставлены для второго прохода
- Колонка 'виртуальный_адрес' содержит виртуальный адрес байта (для pass4)
"""
import sys
import csv
import struct # <--- НОВЫЙ ИМПОРТ
sys.path.insert(0, '.')
from kvs_data import (
PAGE_SIZE, text_vaddr_base, align_up,
ALWAYS_STUB_INSTRUCTIONS
)
from kvs_encoder import encode_instruction
# ========== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ==========
entries = []
current_address = 0
current_segment = '.header'
last_segment = None
vaddr_text = text_vaddr_base
vaddr_data = None
vaddr_bnd = None
data_offset = 0x2000 # начальное значение, будет пересчитано в finalize_headers
text_buffer = bytearray()
data_buffer = bytearray()
bnd_size = 0
labels = {}
label_sections = {}
symbols = {}
pending_labels = []
text_pos = 0
data_pos = 0
entry_point = "_start"
relocations = []
def reset_state():
global entries, current_address, current_segment, last_segment
global text_buffer, data_buffer, bnd_size, vaddr_data, vaddr_bnd
global labels, label_sections, symbols, text_pos, data_pos, entry_point, relocations
global pending_labels
entries = []
current_address = 0
current_segment = '.header'
last_segment = None
text_buffer = bytearray()
data_buffer = bytearray()
bnd_size = 0
vaddr_data = None
vaddr_bnd = None
data_offset = 0x2000 # добавить
labels = {}
label_sections = {}
symbols = {}
text_pos = 0
data_pos = 0
entry_point = "_start"
relocations = []
pending_labels = []
def set_segment(segment: str):
global current_segment
current_segment = segment
def get_virtual_address():
"""Возвращает виртуальный адрес для текущей позиции"""
global data_offset # добавить
if current_segment == '.text':
return vaddr_text + (current_address - 0x1000)
elif current_segment == '.data':
if vaddr_data is None:
return current_address
return vaddr_data + (current_address - data_offset)
else:
return current_address
def add_byte(value: int, source: str = "", target=None, labels_list=None):
global current_address, last_segment, pending_labels
effective_labels = ""
if labels_list is not None and labels_list:
effective_labels = ','.join(labels_list)
elif pending_labels:
effective_labels = ','.join(pending_labels)
show_segment = current_segment# if current_segment != last_segment else None
if show_segment:
last_segment = current_segment
virt_addr = get_virtual_address()
byte_val = value & 0xFF
entries.append((
show_segment if show_segment else "",
current_address,
f"0x{virt_addr:08x}",
f"0x{byte_val:02x}",
target if target else "",
source,
effective_labels,
"",
"",
f"0x{byte_val:02x}"
))
current_address += 1
if pending_labels:
pending_labels = []
def add_bytes(values, source: str = "", target=None):
for i, v in enumerate(values):
add_byte(v, source if i == 0 else "", target if i == 0 else None)
# ========== РЕФАКТОРИНГ: ИСПОЛЬЗУЕМ struct.pack ==========
def add_word16(value: int, source: str = "", target=None):
"""Добавляет 16-битное значение (little-endian)"""
add_bytes(struct.pack('<H', value & 0xFFFF), source, target)
def add_word32(value: int, source: str = "", target=None):
"""Добавляет 32-битное значение (little-endian)"""
add_bytes(struct.pack('<I', value & 0xFFFFFFFF), source, target)
def add_word64(value: int, source: str = "", target=None):
"""Добавляет 64-битное значение (little-endian)"""
add_bytes(struct.pack('<Q', value & 0xFFFFFFFFFFFFFFFF), source, target)
def add_dwords(values: list, source: str = "", target=None):
"""Добавляет несколько 32-битных значений"""
for val in values:
add_word32(val, source, target)
def add_qwords(values: list, source: str = "", target=None):
"""Добавляет несколько 64-битных значений"""
for val in values:
add_word64(val, source, target)
def align_to(alignment: int):
global current_address, pending_labels
padding = (alignment - (current_address % alignment)) % alignment
if padding > 0:
pending_labels = []
current_address += padding
def unescape_string(s):
result = []
i = 0
while i < len(s):
if s[i] == '\\' and i + 1 < len(s):
if s[i + 1] == 'n':
result.append('\n')
elif s[i + 1] == 't':
result.append('\t')
elif s[i + 1] == 'r':
result.append('\r')
else:
result.append(s[i + 1])
i += 2
else:
result.append(s[i])
i += 1
return ''.join(result)
def generate_elf_header():
"""Генерирует заголовок ELF (64 байта)"""
# ELF magic
add_bytes([0x7F, 0x45, 0x4C, 0x46], "ELF magic")
add_byte(2, "ELFCLASS64")
add_byte(1, "ELFDATA2LSB")
add_byte(1, "ELF version")
add_byte(0, "OS ABI")
add_byte(0, "ABI version")
add_bytes([0] * 7, "Padding")
# e_type, e_machine, e_version
add_word16(2, "e_type = ET_EXEC")
add_word16(62, "e_machine = EM_X86_64")
add_word32(1, "e_version")
# e_entry (stub - будет заполнен позже)
add_word64(0, "e_entry (stub)")
# e_phoff, e_shoff
add_word64(64, "e_phoff")
add_word64(0, "e_shoff = 0")
# e_flags, e_ehsize, e_phentsize
add_word32(0, "e_flags")
add_word16(64, "e_ehsize")
add_word16(56, "e_phentsize")
# e_phnum, e_shentsize, e_shnum, e_shstrndx
add_word16(2, "e_phnum = 2")
add_word16(0, "e_shentsize = 0")
add_word16(0, "e_shnum = 0")
add_word16(0, "e_shstrndx")
def generate_program_headers():
"""Генерирует два программных заголовка (PT_LOAD)"""
# === Первый заголовок: TEXT (RX) ===
add_word32(1, "p_type = PT_LOAD")
add_word32(5, "p_flags = RX")
add_word64(0, "p_offset (text stub)") # будет заполнено позже
add_word64(vaddr_text, "p_vaddr (text)")
add_word64(vaddr_text, "p_paddr (text)")
add_word64(0, "p_filesz (text stub)") # будет заполнено позже
add_word64(0, "p_memsz (text stub)") # будет заполнено позже
add_word64(PAGE_SIZE, "p_align")
# === Второй заголовок: DATA (RW) ===
add_word32(1, "p_type = PT_LOAD")
add_word32(6, "p_flags = RW")
add_word64(0, "p_offset (data stub)") # будет заполнено позже
add_word64(0, "p_vaddr (data stub)") # будет заполнено позже
add_word64(0, "p_paddr (data stub)") # будет заполнено позже
add_word64(0, "p_filesz (data stub)") # будет заполнено позже
add_word64(0, "p_memsz (data stub)") # будет заполнено позже
add_word64(PAGE_SIZE, "p_align")
def read_ast(input_file):
with open(input_file, 'r', encoding='utf-8') as f:
return [line.strip() for line in f]
def parse_ast_line(line):
if not line:
return None, {}
line_type = line.split(':')[0]
if line_type == "INSTR":
first = line.find(':')
second = line.find(':', first + 1)
third = line.find(':', second + 1)
if first == -1 or second == -1 or third == -1:
return "INSTR", {}
mnemonic = line[first + 1:second]
section = line[second + 1:third]
operands_str = line[third + 1:]
operands = operands_str.split(',') if operands_str else []
return "INSTR", {
'mnemonic': mnemonic,
'section': section,
'operands': operands
}
elif line_type == "DIRECTIVE":
parts = line.split(':')
directive = parts[1] if len(parts) > 1 else ""
if directive in ('.текст', '.данные', '.бнд'):
return "DIRECTIVE", {'directive': directive}
elif directive == '.глобал':
return "DIRECTIVE", {'directive': directive, 'label': parts[2] if len(parts) > 2 else ""}
elif directive in ('.строка', '.строка_нуль'):
return "DIRECTIVE", {
'directive': directive,
'section': parts[2] if len(parts) > 2 else "",
'string': parts[3] if len(parts) > 3 else ""
}
elif directive == '.константа':
return "DIRECTIVE", {
'directive': directive,
'name': parts[2] if len(parts) > 2 else "",
'value': parts[3] if len(parts) > 3 else ""
}
elif directive == '.байт':
return "DIRECTIVE", {
'directive': directive,
'section': parts[2] if len(parts) > 2 else "",
'bytes': parts[3] if len(parts) > 3 else ""
}
elif directive in ('.резб', '.резс', '.рездс', '.резкс'):
return "DIRECTIVE", {
'directive': directive,
'section': parts[2] if len(parts) > 2 else "",
'count': parts[3] if len(parts) > 3 else "0"
}
else:
return "DIRECTIVE", {'directive': directive}
elif line_type == "LABEL":
parts = line.split(':')
return "LABEL", {
'name': parts[1] if len(parts) > 1 else "",
'section': parts[2] if len(parts) > 2 else ""
}
return line_type, {}
def get_stub_size(mnemonic, operands):
if mnemonic in ("переход", "вызвать"):
return 5
elif mnemonic == "короткий_переход":
return 2
elif mnemonic.startswith("переход_если") and not mnemonic.startswith("короткий"):
return 6
elif mnemonic.startswith("короткий_переход_если"):
return 2
elif mnemonic == "цикл":
return 2
elif mnemonic == "переместить_имм" and len(operands) >= 2:
reg = operands[0]
if reg in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
return 10
elif reg in ('еаикс', 'ебикс', 'есикс', 'едикс'):
return 7
else:
return 2
elif mnemonic in ("загрузить", "сохранить", "загрузить_адрес"):
return 7
elif mnemonic == "сравнить_с":
return 7
elif mnemonic in ("прибавить_непосредственно", "вычесть_непосредственно"):
return 7
elif mnemonic in ("переместить_с_нулями", "переместить_со_знаком"):
return 7
return 1
def extract_label_from_operands(operands):
"""Извлекает первую метку из операндов инструкции"""
for op in operands:
if not op:
continue
# Пропускаем регистры
if op in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
continue
# Пропускаем числа
if op.isdigit() or (op.startswith('0x') and len(op) > 2):
continue
# Пропускаем косвенную адресацию через регистр
if op.startswith('MEM:reg_indirect:'):
continue
# Извлекаем метку из [метка]
if op.startswith('[') and op.endswith(']'):
inner = op[1:-1]
if inner not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай'):
if not inner.isdigit() and not inner.startswith('0x'):
return inner
# Обычная метка
elif op not in symbols:
return op
return None
def has_unresolved_labels(operands, mnemonic=""):
if mnemonic in ALWAYS_STUB_INSTRUCTIONS:
for op in operands:
if op and not op.isdigit() and not op.startswith('0x'):
if op not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл'):
return True
for op in operands:
if not op:
continue
if op in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15',
'р8д', 'р9д', 'р10д', 'р11д', 'р12д', 'р13д', 'р14д', 'р15д',
'р8в', 'р9в', 'р10в', 'р11в', 'р12в', 'р13в', 'р14в', 'р15в',
'р8б', 'р9б', 'р10б', 'р11б', 'р12б', 'р13б', 'р14б', 'р15б',
'спл', 'бпл', 'сил', 'дил'):
continue
if op.startswith('MEM:reg_indirect:'):
continue
if op.startswith('[') and op.endswith(']'):
inner = op[1:-1]
if inner in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай'):
continue
if inner.isdigit() or (inner.startswith('0x') and len(inner) > 2):
continue
if inner not in labels:
return True
continue
if op.isdigit() or (op.startswith('0x') and len(op) > 2):
continue
if op in symbols:
continue
if op not in labels:
return True
return False
def get_unresolved_labels(operands):
unresolved = []
for op in operands:
if not op:
continue
if op.startswith('[') and op.endswith(']'):
inner = op[1:-1]
if inner not in labels and not inner.isdigit() and not (inner.startswith('0x')):
if inner not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай'):
if inner not in symbols:
unresolved.append(inner)
elif op not in labels and not op.isdigit() and not (op.startswith('0x')):
if op not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
if op not in symbols:
unresolved.append(op)
return unresolved
def process_ast_line(line):
global text_pos, data_pos, vaddr_data, vaddr_bnd, bnd_size
global text_buffer, data_buffer, entry_point, symbols, pending_labels
line_type, fields = parse_ast_line(line)
if line_type == "DIRECTIVE":
directive = fields.get('directive', '')
if directive == '.текст':
set_segment('.text')
elif directive == '.данные':
set_segment('.data')
align_to(PAGE_SIZE)
# Пересчитываем data_offset, так как .text уже полностью обработан
global data_offset
data_offset = align_up(0x1000 + text_pos, PAGE_SIZE)
# Для отладки (можно убрать)
print(f" .data: пересчитан data_offset = 0x{data_offset:x} (text_pos={text_pos})")
elif directive == '.бнд':
set_segment('.bss')
elif directive == '.глобал':
entry_point = fields.get('label', '_start')
elif directive == '.константа':
name = fields.get('name', '')
value_str = fields.get('value', '')
if value_str.startswith('0x') or value_str.startswith('0X'):
value = int(value_str, 16)
else:
value = int(value_str)
symbols[name] = value
print(f" константа: {name} = {value}")
elif directive in ('.строка', '.строка_нуль'):
s = fields.get('string', '')
real_s = unescape_string(s)
bstring = real_s.encode('utf-8')
if directive == '.строка_нуль':
bstring += b'\x00'
source = f'{directive} "{s}"'
for i, byte in enumerate(bstring):
labels_list = pending_labels if i == 0 else None
add_byte(byte, source if i == 0 else "", None, labels_list)
data_buffer.append(byte)
if labels_list:
pending_labels = []
data_pos += len(bstring)
elif directive == '.байт':
bytes_str = fields.get('bytes', '')
if bytes_str:
byte_values = bytes_str.split(',')
source = f'.байт {bytes_str}'
bstring = bytearray()
for byte_str in byte_values:
byte_str = byte_str.strip()
if byte_str.startswith('0x'):
val = int(byte_str, 16)
else:
val = int(byte_str)
bstring.append(val & 0xFF)
for i, byte in enumerate(bstring):
labels_list = pending_labels if i == 0 else None
add_byte(byte, source if i == 0 else "", None, labels_list)
data_buffer.append(byte)
if labels_list:
pending_labels = []
data_pos += len(bstring)
elif directive in ('.резб', '.резс', '.рездс', '.резкс'):
count = int(fields.get('count', '0'))
multiplier = {'резб': 1, 'резс': 2, 'рездс': 4, 'резкс': 8}
bnd_size += count * multiplier.get(directive, 1)
elif line_type == "LABEL":
label_name = fields.get('name', '')
sec = fields.get('section', '')
if sec == '.text':
addr = vaddr_text + text_pos
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
elif sec == '.data':
if vaddr_data is None:
vaddr_data = align_up(vaddr_text + text_pos, PAGE_SIZE)
addr = vaddr_data + data_pos
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
elif sec == '.бнд':
if vaddr_bnd is None:
vaddr_bnd = align_up(vaddr_data + data_pos, PAGE_SIZE)
addr = vaddr_bnd + bnd_size
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
else:
addr = current_address
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
elif line_type == "INSTR":
mnemonic = fields.get('mnemonic', '')
sec = fields.get('section', '')
operands = fields.get('operands', [])
source = f"{mnemonic} {', '.join(operands)}" if operands else mnemonic
need_stub = has_unresolved_labels(operands, mnemonic)
if need_stub:
stub_size = get_stub_size(mnemonic, operands)
unresolved = get_unresolved_labels(operands)
if unresolved:
reloc_text = f"ЗАГЛУШКА {', '.join(unresolved)}"
else:
label_from_op = extract_label_from_operands(operands)
if label_from_op:
reloc_text = f"ЗАГЛУШКА {label_from_op}"
else:
reloc_text = "ЗАГЛУШКА (неизвестная метка)"
relocations.append({
'address': current_address,
'size': stub_size,
'labels': unresolved if unresolved else [label_from_op] if label_from_op else [],
'source': source,
'mnemonic': mnemonic
})
for i in range(stub_size):
labels_list = pending_labels if i == 0 else None
add_byte(0xCC, source if i == 0 else "", reloc_text if i == 0 else None, labels_list)
if labels_list:
pending_labels = []
if sec == '.text':
text_buffer.extend([0xCC] * stub_size)
text_pos += stub_size
else:
data_buffer.extend([0xCC] * stub_size)
data_pos += stub_size
else:
try:
if sec == '.text':
current_pos = text_pos
else:
current_pos = data_pos
encoded = encode_instruction(
mnemonic, operands, labels, label_sections, symbols,
vaddr_text, vaddr_data if vaddr_data else 0,
current_pos, vaddr_bnd if vaddr_bnd else 0
)
for i, byte in enumerate(encoded):
labels_list = pending_labels if i == 0 else None
add_byte(byte, source if i == 0 else "", None, labels_list)
if labels_list:
pending_labels = []
if sec == '.text':
text_buffer.extend(encoded)
text_pos += len(encoded)
else:
data_buffer.extend(encoded)
data_pos += len(encoded)
except Exception as e:
print(f"Ошибка кодирования {source}: {e}", file=sys.stderr)
labels_to_attach = pending_labels.copy()
add_byte(0xCC, source, f"ОШИБКА: {e}", labels_to_attach)
pending_labels.clear()
if sec == '.text':
text_buffer.append(0xCC)
text_pos += 1
else:
data_buffer.append(0xCC)
data_pos += 1
def finalize_headers():
global entries, data_offset # добавить data_offset в global
text_offset = 0x1000
text_filesz = len(text_buffer)
text_memsz = len(text_buffer)
# ВЫЧИСЛЯЕМ ФАЙЛОВОЕ СМЕЩЕНИЕ ДЛЯ DATA ДИНАМИЧЕСКИ
data_offset = align_up(text_offset + text_filesz, PAGE_SIZE)
data_filesz = len(data_buffer)
data_memsz = len(data_buffer) + bnd_size
vaddr_data_final = align_up(vaddr_text + text_filesz, PAGE_SIZE)
entry_point_addr = labels.get(entry_point, vaddr_text)
i = 0
while i < len(entries):
segment, addr, virt_addr, byte_str, target, source, label, cmd_with_values, calc_target, calc_byte = entries[i]
if 'e_entry (stub)' in source:
# Вставляем 8 байт (uint64_t)
new_bytes = struct.pack('<Q', entry_point_addr)
for j, nb in enumerate(new_bytes):
virt = f"0x{entry_point_addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"e_entry = 0x{entry_point_addr:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_offset (text stub)' in source:
new_bytes = struct.pack('<Q', text_offset)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_offset (text) = 0x{text_offset:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_filesz (text stub)' in source:
new_bytes = struct.pack('<Q', text_filesz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_filesz (text) = {text_filesz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_memsz (text stub)' in source:
new_bytes = struct.pack('<Q', text_memsz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_memsz (text) = {text_memsz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_offset (data stub)' in source:
new_bytes = struct.pack('<Q', data_offset)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_offset (data) = 0x{data_offset:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_vaddr (data stub)' in source:
new_bytes = struct.pack('<Q', vaddr_data_final)
for j, nb in enumerate(new_bytes):
virt = f"0x{vaddr_data_final + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_vaddr (data) = 0x{vaddr_data_final:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_paddr (data stub)' in source:
new_bytes = struct.pack('<Q', vaddr_data_final)
for j, nb in enumerate(new_bytes):
virt = f"0x{vaddr_data_final + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_paddr (data) = 0x{vaddr_data_final:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_filesz (data stub)' in source:
new_bytes = struct.pack('<Q', data_filesz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_filesz (data) = {data_filesz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
elif 'p_memsz (data stub)' in source:
new_bytes = struct.pack('<Q', data_memsz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_memsz (data) = {data_memsz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
i += 1
def save_to_csv(filename: str):
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(['сегмент', 'адрес', 'виртуальный_адрес', 'байт', 'приводящая_метка',
'уводящий_адрес', 'исходная_команда', 'команда_со_значениями',
'рассчитанный_уводящий_адрес', 'рассчитанный_байт'])
for segment, addr, virt_addr, byte_str, target, source, label, cmd_with_values, calc_target, calc_byte in entries:
writer.writerow([
segment,
f"0x{addr:08x}",
virt_addr,
byte_str,
label,
target,
source,
cmd_with_values,
calc_target,
calc_byte
])
def print_section_info():
"""Выводит информацию о секциях с учётом выравнивания"""
text_start_offset = 0x1000
text_end_offset = text_start_offset + len(text_buffer)
text_end_aligned = align_up(text_end_offset, PAGE_SIZE)
data_start_offset = text_end_aligned
data_end_offset = data_start_offset + len(data_buffer)
data_end_aligned = align_up(data_end_offset, PAGE_SIZE)
bnd_start_offset = data_end_aligned
bnd_end_offset = bnd_start_offset + bnd_size
print(f"\n=== Информация о размещении секций (файловые смещения) ===")
print(f" .text: {len(text_buffer)} байт, смещение 0x{text_start_offset:x} - 0x{text_end_offset-1:x}")
print(f" выровнено: следующий сектор начнётся с 0x{text_end_aligned:x}")
print(f" .data: {len(data_buffer)} байт, смещение 0x{data_start_offset:x} - 0x{data_end_offset-1:x}")
print(f" выровнено: следующий сектор начнётся с 0x{data_end_aligned:x}")
if bnd_size > 0:
print(f" .bnd: {bnd_size} байт, смещение 0x{bnd_start_offset:x} - 0x{bnd_end_offset-1:x}")
print(f"\n=== Виртуальные адреса ===")
print(f" .text: 0x{vaddr_text:x} - 0x{vaddr_text + len(text_buffer) - 1:x}")
data_vaddr = align_up(vaddr_text + len(text_buffer), PAGE_SIZE)
print(f" .data: 0x{data_vaddr:x} - 0x{data_vaddr + len(data_buffer) - 1:x}")
if bnd_size > 0:
bnd_vaddr = align_up(data_vaddr + len(data_buffer), PAGE_SIZE)
print(f" .bnd: 0x{bnd_vaddr:x} - 0x{bnd_vaddr + bnd_size - 1:x}")
def main():
if len(sys.argv) != 3:
print("Использование: python kvs_pass1.py <вход.аст> <выход.csv>")
sys.exit(1)
ast_file = sys.argv[1]
csv_file = sys.argv[2]
reset_state()
set_segment('.header')
generate_elf_header()
generate_program_headers()
align_to(PAGE_SIZE)
ast_lines = read_ast(ast_file)
for line in ast_lines:
process_ast_line(line)
finalize_headers()
save_to_csv(csv_file)
# Добавить эту строку:
print_section_info()
vaddr_data_final = align_up(vaddr_text + len(text_buffer), PAGE_SIZE)
vaddr_bnd_final = align_up(vaddr_data_final + len(data_buffer), PAGE_SIZE)
print(f"\n=== Первый проход ===")
print(f" .text: {len(text_buffer)} байт, vaddr=0x{vaddr_text:x}")
print(f" .data: {len(data_buffer)} байт, vaddr=0x{vaddr_data_final:x}, offset=0x{align_up(0x1000 + len(text_buffer), PAGE_SIZE):x}")
print(f" .bnd: {bnd_size} байт, vaddr=0x{vaddr_bnd_final:x}")
print(f" точка входа: {entry_point} = 0x{labels.get(entry_point, vaddr_text):x}")
print(f" меток: {len(labels)}")
print(f" констант: {len(symbols)}")
print(f" релокаций: {len(relocations)}")
print(f" записей: {len(entries)}")
print(f" CSV: {csv_file}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,325 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kvsd.py
Дизассемблер ELF-файлов для КВС с кастомным синтаксисом
"""
import argparse
import os
import subprocess
import sys
from kvsd_data import translate_mnemonic, translate_operand
try:
from elfreader import (
read_elf_header, read_section_headers, get_section_name,
SHT_PROGBITS, EM_X86_64
)
except ImportError:
print("Ошибка: не найден модуль elfreader.py", file=sys.stderr)
sys.exit(1)
def parse_bytes_hex(bytes_str):
"""Преобразует строку шестнадцатеричных байтов в список байтов."""
result = []
for i in range(0, len(bytes_str), 2):
byte_hex = bytes_str[i:i+2]
if byte_hex:
result.append(int(byte_hex, 16))
return result
def extract_displacement(bytes_list, pos, size, little_endian=True):
"""Извлекает смещение из байтов."""
if pos + size > len(bytes_list):
return None
if little_endian:
result = 0
for i in range(size):
result |= bytes_list[pos + i] << (i * 8)
return result
else:
result = 0
for i in range(size):
result = (result << 8) | bytes_list[pos + i]
return result
def get_instruction_length(mnemonic, bytes_list):
"""Определяет длину инструкции в байтах."""
fixed_lengths = {
"вернуться": 1, "нет_операции": 1, "остановить": 1, "отладка": 1,
"втолкнуть_флаги": 1, "вытолкнуть_флаги": 1,
"установить_перенос": 1, "сбросить_перенос": 1,
"установить_направление": 1, "сбросить_направление": 1,
"переместить_байт": 1, "переместить_слово": 1,
"сравнить_байты": 1, "сканировать_байт": 1,
"вызов_системы": 2, "прервать": 2, "цикл": 2,
"короткий_переход": 2, "короткий_переход_если_равно": 2,
"короткий_переход_если_неравно": 2, "короткий_переход_если_меньше": 2,
"короткий_переход_если_больше": 2, "ввод_байта": 2, "вывод_байта": 2,
"переместить": 3, "сравнить": 3, "проверить": 3,
"прибавить": 3, "вычесть": 3, "и": 3, "или": 3, "исключающее_или": 3,
"увеличить": 3, "уменьшить": 3, "загрузить": 3, "сохранить": 3,
"загрузить_адрес": 3, "обменять": 4,
"сдвиг_влево": 4, "сдвиг_вправо": 4, "сдвиг_арифметический_вправо": 4,
"вращать_влево": 4, "вращать_вправо": 4,
"вращать_через_перенос_влево": 4, "вращать_через_перенос_вправо": 4,
"переход": 5, "вызвать": 5, "переход_если_равно": 6,
"переход_если_неравно": 6, "переход_если_ноль": 6,
"переход_если_не_ноль": 6, "переход_если_меньше": 6,
"переход_если_больше": 6,
}
if mnemonic in fixed_lengths:
return fixed_lengths[mnemonic]
if mnemonic == "переместить_имм":
if len(bytes_list) >= 2:
opcode = bytes_list[0] if bytes_list else 0
if opcode == 0x48 and len(bytes_list) > 1 and bytes_list[1] >= 0xB8:
return 10
elif opcode >= 0xB8 and opcode <= 0xBF:
return 5
return 7
if mnemonic in ["сравнить_с", "прибавить_непосредственно", "вычесть_непосредственно"]:
return 7
return len(bytes_list)
def compute_target_address(address, mnemonic, bytes_list):
"""Вычисляет целевой адрес для инструкций перехода."""
relative_jumps = {
"переход", "короткий_переход", "переход_если_равно", "переход_если_неравно",
"переход_если_меньше", "переход_если_больше", "переход_если_ноль",
"переход_если_не_ноль", "короткий_переход_если_равно", "короткий_переход_если_неравно",
"короткий_переход_если_меньше", "короткий_переход_если_больше",
"короткий_переход_если_перенос", "короткий_переход_если_нет_переноса",
"вызвать", "цикл"
}
if mnemonic not in relative_jumps:
return ""
instr_len = get_instruction_length(mnemonic, bytes_list)
if instr_len == 2 and len(bytes_list) >= 2:
displacement = bytes_list[1]
if displacement > 127:
displacement = displacement - 256
target = address + instr_len + displacement
return f"0x{target:x}"
elif instr_len == 5 and len(bytes_list) >= 5:
displacement = extract_displacement(bytes_list, 1, 4)
if displacement is not None:
if displacement > 0x7FFFFFFF:
displacement = displacement - 0x100000000
target = address + instr_len + displacement
return f"0x{target:x}"
elif instr_len == 6 and len(bytes_list) >= 6:
displacement = extract_displacement(bytes_list, 2, 4)
if displacement is not None:
if displacement > 0x7FFFFFFF:
displacement = displacement - 0x100000000
target = address + instr_len + displacement
return f"0x{target:x}"
return ""
def parse_ndisasm_line(line):
"""Разбирает строку вывода ndisasm и переводит её."""
line = line.strip()
if not line:
return None
parts = line.split(maxsplit=2)
if len(parts) < 3:
return None
address_str = parts[0]
bytes_hex = parts[1]
rest = parts[2]
# Разделяем мнемонику и операнды
rest_parts = rest.split(maxsplit=1)
mnemonic_orig = rest_parts[0]
operands_orig = rest_parts[1] if len(rest_parts) > 1 else ""
# Вычисляем длину инструкции
bytes_list = parse_bytes_hex(bytes_hex)
instr_len = len(bytes_list)
# Переводим мнемонику (с учётом операндов и длины)
translated_mnemonic = translate_mnemonic(mnemonic_orig, operands_orig, instr_len)
# Переводим операнды (регистры внутри)
translated_operands = translate_operand(operands_orig)
return {
'address': int(address_str, 16),
'address_hex': address_str,
'bytes_hex': bytes_hex,
'mnemonic': translated_mnemonic,
'operands': translated_operands,
'instr_len': instr_len
}
def filter_by_address_range(disasm_output, start_addr, end_addr):
"""
Оставляет только строки, адрес которых входит в диапазон [start_addr, end_addr).
"""
filtered_lines = []
for line in disasm_output.splitlines():
if not line.strip():
continue
addr_str = line.split(maxsplit=1)[0]
try:
addr = int(addr_str, 16)
if start_addr <= addr < end_addr:
filtered_lines.append(line)
except ValueError:
continue
return '\n'.join(filtered_lines)
def generate_csv_table(disasm_output):
"""Генерирует CSV-таблицу из вывода ndisasm."""
rows = []
for line in disasm_output.splitlines():
parsed = parse_ndisasm_line(line)
if not parsed:
continue
address = parsed['address']
bytes_hex = parsed['bytes_hex']
mnemonic = parsed['mnemonic']
operands = parsed['operands']
# Формируем исходную команду
if operands:
original_cmd = f"{mnemonic} {operands}"
else:
original_cmd = mnemonic
bytes_list = parse_bytes_hex(bytes_hex)
target = compute_target_address(address, mnemonic, bytes_list)
# Разбиваем байты на отдельные
byte_parts = [bytes_hex[i:i+2] for i in range(0, len(bytes_hex), 2)]
for i, byte in enumerate(byte_parts):
byte_address = address + i
if i == 0:
rows.append({
'address': f"0x{byte_address:x}",
'bytes': byte,
'target': target,
'cmd': original_cmd
})
else:
rows.append({
'address': f"0x{byte_address:x}",
'bytes': byte,
'target': "",
'cmd': ""
})
return rows
def disassemble_elf_to_csv(elf_file, output_file=None):
"""Дизассемблирует ELF-файл и сохраняет результат в CSV."""
try:
if not os.path.exists(elf_file):
return False, f"Файл {elf_file} не найден"
if not output_file:
base_name = os.path.splitext(elf_file)[0]
output_file = f"{base_name}.elf.csv"
with open(elf_file, 'rb') as f:
hdr = read_elf_header(f)
if hdr['e_machine'] != EM_X86_64:
print(f"Предупреждение: архитектура не x86-64")
if hdr['e_shnum'] == 0:
return False, "Файл не содержит таблицы секций"
sections = read_section_headers(f, hdr['e_shoff'], hdr['e_shnum'])
shstrtab = sections[hdr['e_shstrndx']]
shstrtab_offset = shstrtab['sh_offset']
shstrtab_size = shstrtab['sh_size']
offset, size, vaddr = None, None, None
for sec in sections:
name = get_section_name(f, shstrtab_offset, shstrtab_size, sec['sh_name'])
if name == '.text' and sec['sh_type'] == SHT_PROGBITS:
offset = sec['sh_offset']
size = sec['sh_size']
vaddr = sec['sh_addr']
break
if offset is None:
return False, "Секция .text не найдена"
print(f"Секция .text: адрес 0x{vaddr:x}, размер 0x{size:x} ({size} байт)")
cmd = ['ndisasm', '-b64', '-o', f'0x{vaddr - offset:x}', elf_file]
print(f"Запуск: {' '.join(cmd)}")
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode != 0:
return False, f"Ошибка ndisasm: {result.stderr.strip()}"
end_addr = vaddr + size
filtered_output = filter_by_address_range(result.stdout, vaddr, end_addr)
if not filtered_output:
print("Предупреждение: после фильтрации не осталось строк")
rows = []
else:
rows = generate_csv_table(filtered_output)
with open(output_file, 'w', encoding='utf-8') as out_f:
out_f.write("адрес;байт;целевой_адрес;исходная_команда\n")
for row in rows:
out_f.write(f"{row['address']};{row['bytes']};{row['target']};{row['cmd']}\n")
print(f"CSV сохранён: {output_file} (строк: {len(rows)})")
return True, output_file
except FileNotFoundError:
return False, "ndisasm не найден. Установите NASM"
except Exception as e:
return False, str(e)
def main():
parser = argparse.ArgumentParser(description='Дизассемблер ELF-файлов для КВС')
parser.add_argument('input_file', help='Путь к ELF-файлу')
parser.add_argument('-o', '--output', help='Выходной CSV-файл')
args = parser.parse_args()
print(f"Дизассемблирование: {args.input_file}")
success, result = disassemble_elf_to_csv(args.input_file, args.output)
if success:
print(f"\nГотово! Результат: {result}")
else:
print(f"Ошибка: {result}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,243 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kvsd_data.py
Словари для дизассемблера КВС
Трансляция x86-64 мнемоник в кастомный синтаксис
"""
# === СЛОВАРЬ ТРАНСЛЯЦИИ МНЕМОНИК ===
MNEMONIC_TRANSLATION = {
"mov": "переместить",
"movzx": "переместить_с_нулями",
"movsx": "переместить_со_знаком",
"lea": "загрузить_адрес",
"add": "прибавить",
"sub": "вычесть",
"inc": "увеличить",
"dec": "уменьшить",
"mul": "умножить",
"div": "разделить",
"imul": "умножить_знаковое",
"idiv": "разделить_знаковое",
"neg": "отрицать",
"and": "и",
"or": "или",
"xor": "исключающее_или",
"not": "инвертировать",
"cmp": "сравнить",
"test": "проверить",
"jmp": "переход",
"je": "переход_если_равно",
"jz": "переход_если_ноль",
"jne": "переход_если_неравно",
"jnz": "переход_если_не_ноль",
"jl": "переход_если_меньше",
"jg": "переход_если_больше",
"jle": "переход_если_меньше_или_равно",
"jge": "переход_если_больше_или_равно",
"jc": "переход_если_перенос",
"jnc": "переход_если_нет_переноса",
"jo": "переход_если_переполнение",
"jno": "переход_если_нет_переполнения",
"js": "переход_если_знак",
"jns": "переход_если_нет_знака",
"jp": "переход_если_чётность",
"jnp": "переход_если_не_чётность",
"jbe": "переход_если_ниже_или_равно",
"ja": "переход_если_выше",
"call": "вызвать",
"ret": "вернуться",
"retn": "вернуться",
"push": "втолкнуть",
"pop": "вытолкнуть",
"pushf": "втолкнуть_флаги",
"popf": "вытолкнуть_флаги",
"shl": "сдвиг_влево",
"sal": "сдвиг_влево",
"shr": "сдвиг_вправо",
"sar": "сдвиг_арифметический_вправо",
"rol": "вращать_влево",
"ror": "вращать_вправо",
"rcl": "вращать_через_перенос_влево",
"rcr": "вращать_через_перенос_вправо",
"syscall": "вызов_системы",
"int": "прервать",
"sysenter": "войти_в_систему",
"hlt": "остановить",
"cpuid": "идентифицировать_процессор",
"rdtsc": "прочитать_счётчик",
"nop": "нет_операции",
"int3": "отладка",
"movsb": "переместить_байт",
"movsw": "переместить_слово",
"movsd": "переместить_двойное_слово",
"movsq": "переместить_квадрослово",
"cmpsb": "сравнить_байты",
"cmpsw": "сравнить_слова",
"scasb": "сканировать_байт",
"stosb": "сохранить_байт",
"stc": "установить_перенос",
"clc": "сбросить_перенос",
"std": "установить_направление",
"cld": "сбросить_направление",
"in": "ввод_байта",
"out": "вывод_байта",
"loop": "цикл",
"loope": "цикл_если_равно",
"loopne": "цикл_если_не_равно",
"xchg": "обменять",
}
# === СЛОВАРЬ ТРАНСЛЯЦИИ РЕГИСТРОВ ===
REGISTER_TRANSLATION = {
"rax": "раикс", "rcx": "рсикс", "rdx": "рдикс", "rbx": "рбикс",
"rsp": "рсипи", "rbp": "рбипи", "rsi": "рсиай", "rdi": "рдиай",
"r8": "р8", "r9": "р9", "r10": "р10", "r11": "р11",
"r12": "р12", "r13": "р13", "r14": "р14", "r15": "р15",
"eax": "еаикс", "ecx": "есикс", "edx": "едикс", "ebx": "ебикс",
"esp": "есипи", "ebp": "ебипи", "esi": "есиай", "edi": "едиай",
"r8d": "р8д", "r9d": "р9д", "r10d": "р10д", "r11d": "р11д",
"r12d": "р12д", "r13d": "р13д", "r14d": "р14д", "r15d": "р15д",
"ax": "аикс", "cx": "сикс", "dx": "дикс", "bx": "бикс",
"sp": "эсп", "bp": "бипи", "si": "эс", "di": "ди",
"r8w": "р8в", "r9w": "р9в", "r10w": "р10в", "r11w": "р11в",
"r12w": "р12в", "r13w": "р13в", "r14w": "р14в", "r15w": "р15в",
"al": "ал", "cl": "кл", "dl": "дл", "bl": "бл",
"spl": "спл", "bpl": "бпл", "sil": "сил", "dil": "дил",
"r8b": "р8б", "r9b": "р9б", "r10b": "р10б", "r11b": "р11б",
"r12b": "р12б", "r13b": "р13б", "r14b": "р14б", "r15b": "р15б",
"ah": "аш", "ch": "чш", "dh": "дш", "bh": "бш",
"rip": "рип", "rflags": "рфлаги",
}
def translate_mnemonic(mnemonic, operand_str="", instr_len=0):
"""
Переводит мнемонику x86-64 в кастомный синтаксис КВС.
Аргументы:
mnemonic (str): исходная мнемоника
operand_str (str): строка операндов (для определения short)
instr_len (int): длина инструкции в байтах (для определения коротких/длинных переходов)
"""
# Для JMP: определяем по наличию "short" в операндах
if mnemonic == "jmp" and "short" in operand_str:
return "короткий_переход"
# Для условных переходов: определяем по длине инструкции
if mnemonic in ["je", "jz", "jne", "jnz", "jl", "jg", "jle", "jge",
"jc", "jnc", "jo", "jno", "js", "jns", "jp", "jnp",
"jbe", "ja"]:
# Длина 2 байта = короткий переход (rel8)
if instr_len == 2:
translation = {
"je": "короткий_переход_если_равно",
"jz": "короткий_переход_если_ноль",
"jne": "короткий_переход_если_неравно",
"jnz": "короткий_переход_если_не_ноль",
"jl": "короткий_переход_если_меньше",
"jg": "короткий_переход_если_больше",
"jle": "короткий_переход_если_меньше_или_равно",
"jge": "короткий_переход_если_больше_или_равно",
"jc": "короткий_переход_если_перенос",
"jnc": "короткий_переход_если_нет_переноса",
"jo": "короткий_переход_если_переполнение",
"jno": "короткий_переход_если_нет_переполнения",
"js": "короткий_переход_если_знак",
"jns": "короткий_переход_если_нет_знака",
"jp": "короткий_переход_если_чётность",
"jnp": "короткий_переход_если_не_чётность",
"jbe": "короткий_переход_если_ниже_или_равно",
"ja": "короткий_переход_если_выше",
}
return translation.get(mnemonic, mnemonic)
# Длина 6 байт = длинный переход (rel32)
elif instr_len == 6:
translation = {
"je": "переход_если_равно",
"jz": "переход_если_ноль",
"jne": "переход_если_неравно",
"jnz": "переход_если_не_ноль",
"jl": "переход_если_меньше",
"jg": "переход_если_больше",
"jle": "переход_если_меньше_или_равно",
"jge": "переход_если_больше_или_равно",
"jc": "переход_если_перенос",
"jnc": "переход_если_нет_переноса",
"jo": "переход_если_переполнение",
"jno": "переход_если_нет_переполнения",
"js": "переход_если_знак",
"jns": "переход_если_нет_знака",
"jp": "переход_если_чётность",
"jnp": "переход_если_не_чётность",
"jbe": "переход_если_ниже_или_равно",
"ja": "переход_если_выше",
}
return translation.get(mnemonic, mnemonic)
return MNEMONIC_TRANSLATION.get(mnemonic, mnemonic)
def translate_operand(operand):
"""
Переводит операнд: заменяет все регистры на кастомные имена.
Также удаляет слово "short" из операндов.
"""
if not operand:
return operand
result = operand
# Удаляем "short" из операндов (для коротких переходов)
result = result.replace("short ", "")
result = result.replace(" short", "")
# Сортируем регистры по длине (от длинных к коротким)
sorted_regs = sorted(REGISTER_TRANSLATION.keys(), key=len, reverse=True)
for reg in sorted_regs:
trans_reg = REGISTER_TRANSLATION[reg]
for sep in ['', ' ', ',', '+', '-', '*', '/', '(', ')', '[', ']', ':', '&', '|']:
result = result.replace(reg + sep, trans_reg + sep)
result = result.replace(sep + reg, sep + trans_reg)
if result.startswith(reg):
result = trans_reg + result[len(reg):]
if result.endswith(reg):
result = result[:-len(reg)] + trans_reg
result = result.replace(reg, trans_reg)
return result
def translate_line(line):
"""
Переводит строку дизассемблера.
Формат: "00401000 48B80A0000000000 mov rax,0xa"
"""
if not line.strip():
return line
parts = line.split(maxsplit=2)
if len(parts) < 3:
return line
address = parts[0]
bytes_hex = parts[1]
rest = parts[2]
rest_parts = rest.split(maxsplit=1)
mnemonic = rest_parts[0]
operands = rest_parts[1] if len(rest_parts) > 1 else ""
# Вычисляем длину инструкции
instr_len = len(bytes_hex) // 2 if bytes_hex else 0
new_mnemonic = translate_mnemonic(mnemonic, operands, instr_len)
new_operands = translate_operand(operands)
if new_operands:
return f"{address} {bytes_hex} {new_mnemonic} {new_operands}"
else:
return f"{address} {bytes_hex} {new_mnemonic}"

View file

@ -2,11 +2,24 @@
# -*- coding: utf-8 -*-
"""
Лексер КВС
Лексер КВС (русская версия)
Принимает: исходный .квс файл
Выдаёт: текстовый файл с токенами (по одному токену на строку)
Формат вывода: ТИП:ЗНАЧЕНИЕ
Все типы токенов переведены на русский язык:
- СТРОКА → строковый литерал
- СЛОВО → идентификатор
- ЧИСЛО → числовой литерал
- ЗАПЯТАЯ → разделитель
- ДВОЕТОЧИЕ → для меток
- СК_ОТКР → [
- СК_ЗАКР → ]
- ПЛЮС → +
- МИНУС → -
- ЗВЕЗДА → *
- НОВСТР → маркер конца строки
Поддерживает сложную адресацию:
- [ ] - квадратные скобки
- + - плюс
@ -46,7 +59,7 @@ def is_dec_number(s):
return True
def tokenize_line(line, line_num):
"""Разбирает строку в токены"""
"""Разбирает строку в токены (русские названия типов)"""
# Удаляем комментарии
semi = -1
for idx in range(len(line)):
@ -85,43 +98,53 @@ def tokenize_line(line, line_num):
if i >= n:
raise ValueError(error_unterminated_string(line_num))
i += 1
tokens.append(('STRING', s))
tokens.append(('СТРОКА', s))
continue
# Одиночные символы-разделители
if ch == ',':
tokens.append(('COMMA', ','))
tokens.append(('ЗАПЯТАЯ', ','))
i += 1
continue
if ch == ':':
tokens.append(('COLON', ':'))
tokens.append(('ДВОЕТОЧИЕ', ':'))
i += 1
continue
# ТОКЕНЫ ДЛЯ СЛОЖНОЙ АДРЕСАЦИИ
# Токены для сложной адресации
if ch == '[':
tokens.append(('LBRACKET', '['))
tokens.append(('СК_ОТКР', '['))
i += 1
continue
if ch == ']':
tokens.append(('RBRACKET', ']'))
tokens.append(('СК_ЗАКР', ']'))
i += 1
continue
if ch == '+':
tokens.append(('PLUS', '+'))
tokens.append(('ПЛЮС', '+'))
i += 1
continue
if ch == '-':
tokens.append(('MINUS', '-'))
# Проверяем, не является ли минус частью отрицательного числа
if i + 1 < n and (line[i+1].isdigit() or line[i+1] == '0'):
j = i + 1
while j < n and (line[j].isdigit() or line[j] in 'xXabcdefABCDEF'):
j += 1
word = line[i:j]
if is_hex_number(word) or is_dec_number(word):
tokens.append(('ЧИСЛО', word))
i = j
continue
tokens.append(('МИНУС', '-'))
i += 1
continue
if ch == '*':
tokens.append(('STAR', '*'))
tokens.append(('ЗВЕЗДА', '*'))
i += 1
continue
@ -134,11 +157,11 @@ def tokenize_line(line, line_num):
if word:
# Определяем тип токена
if is_hex_number(word):
tokens.append(('NUMBER', word))
tokens.append(('ЧИСЛО', word))
elif is_dec_number(word):
tokens.append(('NUMBER', word))
tokens.append(('ЧИСЛО', word))
else:
tokens.append(('WORD', word))
tokens.append(('СЛОВО', word))
i = j
@ -153,7 +176,7 @@ def lex_source(source_text):
try:
tokens = tokenize_line(line, line_num)
if tokens:
all_tokens.append(('NEWLINE', str(line_num)))
all_tokens.append(('НОВСТР', str(line_num)))
for tok_type, tok_value in tokens:
all_tokens.append((tok_type, tok_value))
except ValueError as e:

351
квс_парсер.py Normal file
View file

@ -0,0 +1,351 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Парсер КВС (адаптирован под русские токены)
Принимает: файл токенов от лексера (русские названия типов)
Выдаёт: текстовый файл с AST-структурой
Поддерживает сложную адресацию:
- [регистр + регистр*масштаб + смещение]
"""
import sys
sys.path.insert(0, '.')
from kvs_data import REGISTERS, INSTRUCTIONS
def make_memory_operand(addr_type, base_reg=None, displacement=None, label=None):
"""Создаёт кортеж операнда памяти.
Формат: (addr_type, base_reg, displacement, label)
"""
return (addr_type, base_reg, displacement, label)
def memory_operand_to_ast_string(mem_op):
"""Преобразует кортеж памяти в строку AST.
Формат: 'MEM:тип:данные'
"""
addr_type = mem_op[0]
if addr_type == 'absolute':
if mem_op[3] is not None: # label
return f"MEM:absolute:{mem_op[3]}"
else: # displacement
return f"MEM:absolute:{mem_op[2]}"
elif addr_type == 'register_indirect':
return f"MEM:reg_indirect:{mem_op[1]}"
elif addr_type == 'complex':
return f"MEM:complex:{mem_op[1]}+{mem_op[2]}*{mem_op[3]}+{mem_op[4]}"
else:
return f"MEM:unknown"
def read_tokens(input_file):
"""Читает токены из файла (русские названия типов)"""
tokens = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
colon_pos = line.find(':')
if colon_pos == -1:
continue
tok_type = line[:colon_pos]
tok_value = line[colon_pos + 1:]
tokens.append((tok_type, tok_value))
return tokens
# ==================== СОСТОЯНИЕ ПАРСЕРА ====================
# Хранится в словаре, передаётся во все функции явно
def parser_init(tokens):
"""Создаёт начальное состояние парсера"""
return {
'tokens': tokens,
'pos': 0,
'current_section': '.text',
'parsed_lines': []
}
def parser_peek(state):
"""Подсмотреть текущий токен"""
if state['pos'] < len(state['tokens']):
return state['tokens'][state['pos']]
return None
def parser_next_token(state):
"""Взять текущий токен и продвинуться"""
tok = parser_peek(state)
if tok:
state['pos'] += 1
return tok
def parser_expect(state, expected_type):
"""Ожидать токен определённого типа"""
tok = parser_next_token(state)
if tok is None or tok[0] != expected_type:
raise ValueError(f"Ожидался {expected_type}, получено {tok}")
return tok
def parser_expect_one_of(state, expected_types):
"""Ожидает один из нескольких типов токенов"""
tok = parser_peek(state)
if tok is None or tok[0] not in expected_types:
raise ValueError(f"Ожидался один из {expected_types}, получено {tok}")
return parser_next_token(state)
# ==================== РАЗБОР ОПЕРАНДОВ ====================
def parse_operand(state):
"""Разбирает один операнд"""
tok = parser_peek(state)
if tok is None:
return None
if tok[0] == 'СК_ОТКР':
parser_next_token(state)
return parse_memory_operand(state)
elif tok[0] == 'ЧИСЛО':
return parser_next_token(state)[1]
elif tok[0] == 'СТРОКА':
return parser_next_token(state)[1]
elif tok[0] == 'СЛОВО':
return parser_next_token(state)[1]
else:
raise ValueError(f"Неожиданный токен в операнде: {tok}")
def parse_memory_operand(state):
"""Разбирает операнд памяти"""
parts = []
while True:
tok = parser_peek(state)
if tok is None:
raise ValueError("Незакрытая квадратная скобка")
if tok[0] == 'СК_ЗАКР':
parser_next_token(state)
break
if tok[0] in ('СЛОВО', 'ЧИСЛО', 'ПЛЮС', 'МИНУС', 'ЗВЕЗДА'):
parts.append(parser_next_token(state)[1])
else:
raise ValueError(f"Неожиданный токен в адресации: {tok}")
if len(parts) == 1:
part = parts[0]
if part in REGISTERS:
return make_memory_operand('register_indirect', base_reg=part)
expr = ' '.join(parts)
return f"[{expr}]"
# ==================== РАЗБОР ДИРЕКТИВ ====================
def parse_directive(state, word):
"""
Разбор директив ассемблера.
Вызывает ошибку при некорректном синтаксисе или контексте.
"""
# === Секции ===
if word == '.текст':
state['current_section'] = ".text"
state['parsed_lines'].append(f"DIRECTIVE:{word}")
elif word == '.данные':
state['current_section'] = ".data"
state['parsed_lines'].append(f"DIRECTIVE:{word}")
elif word == '.бнд':
state['current_section'] = ".бнд"
state['parsed_lines'].append(f"DIRECTIVE:{word}")
# === Глобальные метки ===
elif word == '.глобал':
label = parser_expect(state, 'СЛОВО')[1]
state['parsed_lines'].append(f"DIRECTIVE:{word}:{label}")
# === Строковые директивы (запрещены в .бнд) ===
elif word in ('.строка_нуль', '.строка'):
if state['current_section'] == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
string_tok = parser_expect(state, 'СТРОКА')
state['parsed_lines'].append(f"DIRECTIVE:{word}:{state['current_section']}:{string_tok[1]}")
# === Константы (запрещены в .бнд) ===
elif word == '.константа':
if state['current_section'] == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
name = parser_expect(state, 'СЛОВО')[1]
eq_tok = parser_peek(state)
if eq_tok and eq_tok[0] == 'СЛОВО' and eq_tok[1] == '=':
parser_next_token(state)
value_tok = parser_expect_one_of(state, ['СЛОВО', 'ЧИСЛО'])
state['parsed_lines'].append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}")
# === Байты (запрещены в .бнд) ===
elif word == '.байт':
if state['current_section'] == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
bytes_list = []
while True:
tok = parser_peek(state)
if tok is None or tok[0] == 'НОВСТР' or tok[0] == 'ЗАПЯТАЯ':
if tok and tok[0] == 'ЗАПЯТАЯ':
parser_next_token(state)
else:
break
else:
val_tok = parser_expect_one_of(state, ['СЛОВО', 'ЧИСЛО', 'СТРОКА'])
bytes_list.append(val_tok[1])
state['parsed_lines'].append(f"DIRECTIVE:{word}:{state['current_section']}:" + ",".join(bytes_list))
# === Директивы резервирования (только в .бнд) ===
elif word in ('.резб', '.резс', '.рездс', '.резкс'):
if state['current_section'] != ".бнд":
raise ValueError(
f"Ошибка: директива {word} допустима только внутри секции .бнд"
)
count_tok = parser_expect(state, 'ЧИСЛО')
count = int(count_tok[1])
if count <= 0:
raise ValueError(
f"Ошибка: размер резервирования должен быть положительным целым числом"
)
state['parsed_lines'].append(
f"DIRECTIVE:{word}:{state['current_section']}:{count}"
)
else:
raise ValueError(f"Неизвестная директива: {word}")
# ==================== РАЗБОР ИНСТРУКЦИЙ ====================
def parse_instruction(state, mnemonic):
"""Разбор инструкции с операндами"""
if mnemonic not in INSTRUCTIONS:
raise ValueError(f"Неизвестная инструкция: {mnemonic}")
operands = []
while True:
tok = parser_peek(state)
if tok is None or tok[0] == 'НОВСТР':
break
if tok[0] == 'ЗАПЯТАЯ':
parser_next_token(state)
continue
operands.append(parse_operand(state))
str_operands = []
for op in operands:
if isinstance(op, tuple) and len(op) >= 2 and op[0] in ('absolute', 'register_indirect', 'complex'):
str_operands.append(memory_operand_to_ast_string(op))
else:
str_operands.append(op)
state['parsed_lines'].append(f"INSTR:{mnemonic}:{state['current_section']}:" + ",".join(str_operands))
# ==================== РАЗБОР ИНСТРУКЦИИ ИЛИ ДИРЕКТИВЫ ====================
def parse_instruction_or_directive(state):
"""Определяет, инструкция или директива, и вызывает нужный разбор"""
tok = parser_peek(state)
if tok is None or tok[0] == 'НОВСТР':
return True
if tok[0] != 'СЛОВО':
raise ValueError(f"Ожидалось слово, получено: {tok}")
word = parser_next_token(state)[1]
if word.startswith('.'):
parse_directive(state, word)
else:
parse_instruction(state, word)
return True
# ==================== РАЗБОР СТРОКИ ====================
def parse_line(state):
"""Разбор одной строки (может содержать метку)"""
tok = parser_peek(state)
if tok is None:
return False
# Пропускаем маркер конца строки
if tok[0] == 'НОВСТР':
parser_next_token(state)
tok = parser_peek(state)
if tok is None:
return False
# Проверяем, есть ли метка (СЛОВО за которым следует ДВОЕТОЧИЕ)
if tok[0] == 'СЛОВО':
if state['pos'] + 1 < len(state['tokens']) and state['tokens'][state['pos'] + 1][0] == 'ДВОЕТОЧИЕ':
label_name = parser_next_token(state)[1]
parser_next_token(state) # ДВОЕТОЧИЕ
state['parsed_lines'].append(f"LABEL:{label_name}:{state['current_section']}")
next_tok = parser_peek(state)
if next_tok and next_tok[0] != 'НОВСТР':
return parse_instruction_or_directive(state)
return True
return parse_instruction_or_directive(state)
# ==================== ГЛАВНЫЙ ЦИКЛ ====================
def parse_all(tokens):
"""Разбирает все токены, возвращает список строк AST"""
state = parser_init(tokens)
while state['pos'] < len(state['tokens']):
parse_line(state)
return state['parsed_lines']
# ==================== ЗАПИСЬ РЕЗУЛЬТАТА ====================
def write_ast(ast_lines, output_file):
"""Записывает строки AST в файл"""
with open(output_file, 'w', encoding='utf-8') as f:
for line in ast_lines:
f.write(line + '\n')
# ==================== ТОЧКА ВХОДА ====================
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python kvs_parser.py <вход.токены> <выход.аст>")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
tokens = read_tokens(input_file)
ast_lines = parse_all(tokens)
write_ast(ast_lines, output_file)
print(f"Парсер: {len(ast_lines)} строк AST записано в {output_file}")

955
квс_проход1.py Normal file
View file

@ -0,0 +1,955 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Первый проход КВС (однопроходная версия с заглушками)
Генерирует CSV-таблицу напрямую из AST
- Неизвестные метки заменяются заглушками (0xCC) с пометкой в колонке 'уводящий_адрес'
- В колонке 'приводящая_метка' записываются имена меток, находящихся по данному адресу (через запятую)
- Колонка 'команда_со_значениями' оставлена пустой (для второго прохода)
- Колонки 'рассчитанный_уводящий_адрес' и 'рассчитанный_байт' оставлены для второго прохода
- Колонка 'виртуальный_адрес' содержит виртуальный адрес байта (для pass4)
"""
import sys
import csv
import struct
sys.path.insert(0, '.')
from kvs_data import (
PAGE_SIZE, text_vaddr_base, align_up,
ALWAYS_STUB_INSTRUCTIONS
)
from kvs_encoder import encode_instruction
# ========== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ==========
entries = []
current_address = 0
current_segment = '.header'
last_segment = None
vaddr_text = text_vaddr_base
vaddr_data = None
vaddr_bnd = None
data_offset = 0x2000
text_buffer = bytearray()
data_buffer = bytearray()
bss_size = 0
labels = {}
label_sections = {}
symbols = {}
pending_labels = []
text_pos = 0
data_pos = 0
entry_point = "_start"
relocations = []
def reset_state():
global entries, current_address, current_segment, last_segment
global text_buffer, data_buffer, bss_size
global vaddr_data, vaddr_bnd
global labels, label_sections, symbols, text_pos, data_pos, entry_point, relocations
global pending_labels
entries = []
current_address = 0
current_segment = '.header'
last_segment = None
text_buffer = bytearray()
data_buffer = bytearray()
bss_size = 0
vaddr_data = None
vaddr_bnd = None
data_offset = 0x2000
labels = {}
label_sections = {}
symbols = {}
text_pos = 0
data_pos = 0
entry_point = "_start"
relocations = []
pending_labels = []
def set_segment(segment: str):
global current_segment
current_segment = segment
def get_virtual_address():
"""Возвращает виртуальный адрес для текущей позиции"""
global data_offset
if current_segment == '.text':
return vaddr_text + (current_address - 0x1000)
elif current_segment == '.data':
if vaddr_data is None:
return current_address
return vaddr_data + (current_address - data_offset)
else:
return current_address
def add_byte(value: int, source: str = "", target=None, labels_list=None):
global current_address, last_segment, pending_labels
effective_labels = ""
if labels_list is not None and labels_list:
effective_labels = ','.join(labels_list)
elif pending_labels:
effective_labels = ','.join(pending_labels)
show_segment = current_segment
if show_segment:
last_segment = current_segment
virt_addr = get_virtual_address()
byte_val = value & 0xFF
entries.append((
show_segment if show_segment else "",
current_address,
f"0x{virt_addr:08x}",
f"0x{byte_val:02x}",
target if target else "",
source,
effective_labels,
"",
"",
f"0x{byte_val:02x}"
))
current_address += 1
if pending_labels:
pending_labels = []
def add_bss_entry(size: int, label: str = None):
global bss_size, vaddr_bnd
bss_start = bss_size
bss_size += size
# Виртуальный адрес начала блока
virt_addr = vaddr_bnd + bss_start
entries.append((
".bss", # сегмент
"BSS", # файловый адрес (специальное значение)
f"0x{virt_addr:08x}", # виртуальный адрес
"", # байт
"", # уводящий адрес
f".резб {size}", # исходная команда
label if label else "", # приводящая метка
"", "", ""
))
"""Добавляет запись для BSS в CSV """
"""
def add_bss_entry(size: int, label: str = None):
global bss_size, vaddr_bnd
bss_start = bss_size
bss_size += size
bss_file_addr = "BSS"
bss_file_addr = current_address
for offset in range(size):
virt_addr = vaddr_bnd + bss_start + offset
label_name = label if offset == 0 and label else ""
# ПОРЯДОК ПОЛЕЙ ДОЛЖЕН СООТВЕТСТВОВАТЬ РАСПАКОВКЕ В save_to_csv:
# segment, addr, virt_addr, byte_str, target, source, label, cmd_with_values, calc_target, calc_byte
entries.append((
".bss", # segment
f"0x{bss_file_addr + offset:08x}", # addr
f"0x{virt_addr:08x}", # virt_addr
"0x00", # byte_str
"", # target (уводящий_адрес) - пусто
f".резб {size}" if offset == 0 else "", # source (исходная_команда)
label_name, # label (приводящая_метка) ← сюда имя метки!
"", # cmd_with_values
"", # calc_target
"0x00" # calc_byte
))
"""
def add_bytes(values, source: str = "", target=None):
for i, v in enumerate(values):
add_byte(v, source if i == 0 else "", target if i == 0 else None)
def add_word16(value: int, source: str = "", target=None):
add_bytes(struct.pack('<H', value & 0xFFFF), source, target)
def add_word32(value: int, source: str = "", target=None):
add_bytes(struct.pack('<I', value & 0xFFFFFFFF), source, target)
def add_word64(value: int, source: str = "", target=None):
add_bytes(struct.pack('<Q', value & 0xFFFFFFFFFFFFFFFF), source, target)
def add_dwords(values: list, source: str = "", target=None):
for val in values:
add_word32(val, source, target)
def add_qwords(values: list, source: str = "", target=None):
for val in values:
add_word64(val, source, target)
def align_to(alignment: int):
global current_address, pending_labels
padding = (alignment - (current_address % alignment)) % alignment
if padding > 0:
pending_labels = []
current_address += padding
def unescape_string(s):
result = []
i = 0
while i < len(s):
if s[i] == '\\' and i + 1 < len(s):
if s[i + 1] == 'n':
result.append('\n')
elif s[i + 1] == 't':
result.append('\t')
elif s[i + 1] == 'r':
result.append('\r')
else:
result.append(s[i + 1])
i += 2
else:
result.append(s[i])
i += 1
return ''.join(result)
def generate_elf_header():
add_bytes([0x7F, 0x45, 0x4C, 0x46], "ELF magic")
add_byte(2, "ELFCLASS64")
add_byte(1, "ELFDATA2LSB")
add_byte(1, "ELF version")
add_byte(0, "OS ABI")
add_byte(0, "ABI version")
add_bytes([0] * 7, "Padding")
add_word16(2, "e_type = ET_EXEC")
add_word16(62, "e_machine = EM_X86_64")
add_word32(1, "e_version")
add_word64(0, "e_entry (stub)")
add_word64(64, "e_phoff")
add_word64(0, "e_shoff = 0")
add_word32(0, "e_flags")
add_word16(64, "e_ehsize")
add_word16(56, "e_phentsize")
add_word16(2, "e_phnum = 2")
add_word16(0, "e_shentsize = 0")
add_word16(0, "e_shnum = 0")
add_word16(0, "e_shstrndx")
def generate_program_headers():
add_word32(1, "p_type = PT_LOAD")
add_word32(5, "p_flags = RX")
add_word64(0, "p_offset (text stub)")
add_word64(vaddr_text, "p_vaddr (text)")
add_word64(vaddr_text, "p_paddr (text)")
add_word64(0, "p_filesz (text stub)")
add_word64(0, "p_memsz (text stub)")
add_word64(PAGE_SIZE, "p_align")
add_word32(1, "p_type = PT_LOAD")
add_word32(6, "p_flags = RW")
add_word64(0, "p_offset (data stub)")
add_word64(0, "p_vaddr (data stub)")
add_word64(0, "p_paddr (data stub)")
add_word64(0, "p_filesz (data stub)")
add_word64(0, "p_memsz (data stub)")
add_word64(PAGE_SIZE, "p_align")
def read_ast(input_file):
with open(input_file, 'r', encoding='utf-8') as f:
return [line.strip() for line in f]
def parse_ast_line(line):
if not line:
return None, {}
line_type = line.split(':')[0]
if line_type == "INSTR":
first = line.find(':')
second = line.find(':', first + 1)
third = line.find(':', second + 1)
if first == -1 or second == -1 or third == -1:
return "INSTR", {}
mnemonic = line[first + 1:second]
section = line[second + 1:third]
operands_str = line[third + 1:]
operands = operands_str.split(',') if operands_str else []
return "INSTR", {
'mnemonic': mnemonic,
'section': section,
'operands': operands
}
elif line_type == "DIRECTIVE":
# Разбиваем максимум на 4 части: DIRECTIVE, имя, секция, остальное
parts = line.split(':', 3)
directive = parts[1] if len(parts) > 1 else ""
if directive in ('.текст', '.данные', '.бнд'):
return "DIRECTIVE", {'directive': directive}
elif directive == '.глобал':
return "DIRECTIVE", {'directive': directive, 'label': parts[2] if len(parts) > 2 else ""}
elif directive in ('.строка', '.строка_нуль'):
return "DIRECTIVE", {
'directive': directive,
'section': parts[2] if len(parts) > 2 else "",
'string': parts[3] if len(parts) > 3 else "" # ← вся оставшаяся строка
}
elif directive == '.константа':
return "DIRECTIVE", {
'directive': directive,
'name': parts[2] if len(parts) > 2 else "",
'value': parts[3] if len(parts) > 3 else ""
}
elif directive == '.байт':
return "DIRECTIVE", {
'directive': directive,
'section': parts[2] if len(parts) > 2 else "",
'bytes': parts[3] if len(parts) > 3 else ""
}
elif directive in ('.резб', '.резс', '.рездс', '.резкс'):
return "DIRECTIVE", {
'directive': directive,
'section': parts[2] if len(parts) > 2 else "",
'count': parts[3] if len(parts) > 3 else "0"
}
else:
return "DIRECTIVE", {'directive': directive}
elif line_type == "LABEL":
parts = line.split(':')
return "LABEL", {
'name': parts[1] if len(parts) > 1 else "",
'section': parts[2] if len(parts) > 2 else ""
}
return line_type, {}
def get_stub_size(mnemonic, operands):
if mnemonic in ("переход", "вызвать"):
return 5
elif mnemonic == "короткий_переход":
return 2
elif mnemonic.startswith("переход_если") and not mnemonic.startswith("короткий"):
return 6
elif mnemonic.startswith("короткий_переход_если"):
return 2
elif mnemonic == "цикл":
return 2
elif mnemonic == "переместить_имм" and len(operands) >= 2:
reg = operands[0]
if reg in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
return 10
elif reg in ('еаикс', 'ебикс', 'есикс', 'едикс'):
return 7
else:
return 2
elif mnemonic in ("загрузить", "сохранить", "загрузить_адрес"):
return 7
elif mnemonic == "сравнить_с":
return 7
elif mnemonic in ("прибавить_непосредственно", "вычесть_непосредственно"):
return 7
elif mnemonic in ("переместить_с_нулями", "переместить_со_знаком"):
return 7
return 1
def extract_label_from_operands(operands):
for op in operands:
if not op:
continue
if op in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
continue
if op.isdigit() or (op.startswith('0x') and len(op) > 2):
continue
if op.startswith('MEM:reg_indirect:'):
continue
if op.startswith('[') and op.endswith(']'):
inner = op[1:-1]
if inner not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай'):
if not inner.isdigit() and not inner.startswith('0x'):
return inner
elif op not in symbols:
return op
return None
def has_unresolved_labels(operands, mnemonic=""):
if mnemonic in ALWAYS_STUB_INSTRUCTIONS:
for op in operands:
if op and not op.isdigit() and not op.startswith('0x'):
if op not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл'):
return True
for op in operands:
if not op:
continue
if op in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15',
'р8д', 'р9д', 'р10д', 'р11д', 'р12д', 'р13д', 'р14д', 'р15д',
'р8в', 'р9в', 'р10в', 'р11в', 'р12в', 'р13в', 'р14в', 'р15в',
'р8б', 'р9б', 'р10б', 'р11б', 'р12б', 'р13б', 'р14б', 'р15б',
'спл', 'бпл', 'сил', 'дил'):
continue
if op.startswith('MEM:reg_indirect:'):
continue
if op.startswith('[') and op.endswith(']'):
inner = op[1:-1]
if inner in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай'):
continue
if inner.isdigit() or (inner.startswith('0x') and len(inner) > 2):
continue
if inner not in labels:
return True
continue
if op.isdigit() or (op.startswith('0x') and len(op) > 2):
continue
if op in symbols:
continue
if op not in labels:
return True
return False
def get_unresolved_labels(operands):
unresolved = []
for op in operands:
if not op:
continue
if op.startswith('[') and op.endswith(']'):
inner = op[1:-1]
if inner not in labels and not inner.isdigit() and not (inner.startswith('0x')):
if inner not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай'):
if inner not in symbols:
unresolved.append(inner)
elif op not in labels and not op.isdigit() and not (op.startswith('0x')):
if op not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'еаикс', 'ебикс', 'есикс', 'едикс',
'аикс', 'бикс', 'сикс', 'дикс',
'ал', 'бл', 'кл', 'дл',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
if op not in symbols:
unresolved.append(op)
return unresolved
def process_ast_line(line):
global text_pos, data_pos, vaddr_data, vaddr_bnd, bss_size
global text_buffer, data_buffer, entry_point, symbols, pending_labels
line_type, fields = parse_ast_line(line)
if line_type == "DIRECTIVE":
directive = fields.get('directive', '')
if directive == '.текст':
set_segment('.text')
elif directive == '.данные':
set_segment('.data')
align_to(PAGE_SIZE)
global data_offset
data_offset = align_up(0x1000 + text_pos, PAGE_SIZE)
print(f" .data: пересчитан data_offset = 0x{data_offset:x} (text_pos={text_pos})")
elif directive == '.бнд':
set_segment('.bss')
# Инициализируем vaddr_bnd когда входим в BSS
if vaddr_bnd is None:
data_vaddr = vaddr_data if vaddr_data else align_up(vaddr_text + text_pos, PAGE_SIZE)
vaddr_bnd = align_up(data_vaddr + data_pos, PAGE_SIZE)
elif directive == '.глобал':
entry_point = fields.get('label', '_start')
elif directive == '.константа':
name = fields.get('name', '')
value_str = fields.get('value', '')
if value_str.startswith('0x') or value_str.startswith('0X'):
value = int(value_str, 16)
else:
value = int(value_str)
symbols[name] = value
print(f" константа: {name} = {value}")
elif directive in ('.строка', '.строка_нуль'):
s = fields.get('string', '')
real_s = unescape_string(s)
bstring = real_s.encode('utf-8')
if directive == '.строка_нуль':
bstring += b'\x00'
source = f'{directive} "{s}"'
for i, byte in enumerate(bstring):
labels_list = pending_labels if i == 0 else None
add_byte(byte, source if i == 0 else "", None, labels_list)
data_buffer.append(byte)
if labels_list:
pending_labels = []
data_pos += len(bstring)
elif directive == '.байт':
bytes_str = fields.get('bytes', '')
if bytes_str:
byte_values = bytes_str.split(',')
source = f'.байт {bytes_str}'
bstring = bytearray()
for byte_str in byte_values:
byte_str = byte_str.strip()
if byte_str.startswith('0x'):
val = int(byte_str, 16)
else:
val = int(byte_str)
bstring.append(val & 0xFF)
for i, byte in enumerate(bstring):
labels_list = pending_labels if i == 0 else None
add_byte(byte, source if i == 0 else "", None, labels_list)
data_buffer.append(byte)
if labels_list:
pending_labels = []
data_pos += len(bstring)
elif directive in ('.резб', '.резс', '.рездс', '.резкс'):
count = int(fields.get('count', '0'))
multiplier = {'резб': 1, 'резс': 2, 'рездс': 4, 'резкс': 8}
size = count * multiplier.get(directive, 1)
# Добавляем BSS в CSV
label_name = pending_labels[0] if pending_labels else None
add_bss_entry(size, label_name)
if label_name and pending_labels:
pending_labels = []
elif line_type == "LABEL":
label_name = fields.get('name', '')
sec = fields.get('section', '')
if sec == '.text':
addr = vaddr_text + text_pos
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
elif sec == '.data':
if vaddr_data is None:
vaddr_data = align_up(vaddr_text + text_pos, PAGE_SIZE)
addr = vaddr_data + data_pos
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
elif sec == '.бнд':
if vaddr_bnd is None:
data_vaddr = vaddr_data if vaddr_data else align_up(vaddr_text + text_pos, PAGE_SIZE)
vaddr_bnd = align_up(data_vaddr + data_pos, PAGE_SIZE)
addr = vaddr_bnd + bss_size
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
else:
addr = current_address
labels[label_name] = addr
label_sections[label_name] = sec
if label_name not in pending_labels:
pending_labels.append(label_name)
elif line_type == "INSTR":
mnemonic = fields.get('mnemonic', '')
sec = fields.get('section', '')
operands = fields.get('operands', [])
source = f"{mnemonic} {', '.join(operands)}" if operands else mnemonic
need_stub = has_unresolved_labels(operands, mnemonic)
if need_stub:
stub_size = get_stub_size(mnemonic, operands)
unresolved = get_unresolved_labels(operands)
if unresolved:
reloc_text = f"ЗАГЛУШКА {', '.join(unresolved)}"
else:
label_from_op = extract_label_from_operands(operands)
if label_from_op:
reloc_text = f"ЗАГЛУШКА {label_from_op}"
else:
reloc_text = "ЗАГЛУШКА (неизвестная метка)"
relocations.append({
'address': current_address,
'size': stub_size,
'labels': unresolved if unresolved else [label_from_op] if label_from_op else [],
'source': source,
'mnemonic': mnemonic
})
for i in range(stub_size):
labels_list = pending_labels if i == 0 else None
add_byte(0xCC, source if i == 0 else "", reloc_text if i == 0 else None, labels_list)
if labels_list:
pending_labels = []
if sec == '.text':
text_buffer.extend([0xCC] * stub_size)
text_pos += stub_size
else:
data_buffer.extend([0xCC] * stub_size)
data_pos += stub_size
else:
try:
if sec == '.text':
current_pos = text_pos
else:
current_pos = data_pos
encoded = encode_instruction(
mnemonic, operands, labels, label_sections, symbols,
vaddr_text, vaddr_data if vaddr_data else 0,
current_pos, vaddr_bnd if vaddr_bnd else 0
)
for i, byte in enumerate(encoded):
labels_list = pending_labels if i == 0 else None
add_byte(byte, source if i == 0 else "", None, labels_list)
if labels_list:
pending_labels = []
if sec == '.text':
text_buffer.extend(encoded)
text_pos += len(encoded)
else:
data_buffer.extend(encoded)
data_pos += len(encoded)
except Exception as e:
print(f"Ошибка кодирования {source}: {e}", file=sys.stderr)
labels_to_attach = pending_labels.copy()
add_byte(0xCC, source, f"ОШИБКА: {e}", labels_to_attach)
pending_labels.clear()
if sec == '.text':
text_buffer.append(0xCC)
text_pos += 1
else:
data_buffer.append(0xCC)
data_pos += 1
def finalize_headers():
global entries, data_offset, vaddr_data, vaddr_bnd
print("\n[DEBUG finalize_headers] Начало")
text_offset = 0x1000
text_filesz = len(text_buffer)
text_memsz = len(text_buffer)
# Выравнивание для начала секции данных
data_offset = align_up(text_offset + text_filesz, PAGE_SIZE)
print(f"[DEBUG] text_filesz={text_filesz}, data_offset=0x{data_offset:x}")
data_filesz = len(data_buffer)
# Вычисляем виртуальные адреса с учётом выравнивания на PAGE_SIZE
vaddr_data_start = align_up(vaddr_text + text_filesz, PAGE_SIZE)
vaddr_bss_start = align_up(vaddr_data_start + data_filesz, PAGE_SIZE)
# Сохраняем в глобальные переменные
vaddr_data = vaddr_data_start
vaddr_bnd = vaddr_bss_start
# memsz должен покрывать всё: от начала .data до конца .bss
# Важно: включаем выравнивание между .data и .bss!
data_memsz = (vaddr_bss_start + bss_size) - vaddr_data_start
print(f"[DEBUG] vaddr_data_start=0x{vaddr_data_start:x}")
print(f"[DEBUG] vaddr_bss_start=0x{vaddr_bss_start:x}")
print(f"[DEBUG] data_filesz={data_filesz}, bss_size={bss_size}")
print(f"[DEBUG] data_memsz={data_memsz} (файл: {data_filesz}, память: {data_memsz})")
# Обновляем виртуальные адреса для BSS записей в CSV
for i, entry in enumerate(entries):
if entry[0] == ".bss" and entry[1] == "BSS":
old_virt = int(entry[2], 16)
# Вычисляем смещение внутри BSS
bss_offset = old_virt - vaddr_bss_start if vaddr_bss_start else 0
new_virt = vaddr_bss_start + bss_offset
entries[i] = (entry[0], entry[1], f"0x{new_virt:08x}", entry[3],
entry[4], entry[5], entry[6], entry[7], entry[8], entry[9])
print(f"[DEBUG] BSS запись: virt 0x{old_virt:x} -> 0x{new_virt:x}")
entry_point_addr = labels.get(entry_point, vaddr_text)
print(f"[DEBUG] entry_point_addr=0x{entry_point_addr:x}")
i = 0
while i < len(entries):
segment, addr, virt_addr, byte_str, target, source, label, cmd_with_values, calc_target, calc_byte = entries[i]
# Пропускаем BSS-записи (они уже обработаны выше)
if segment == ".bss":
i += 1
continue
# Заполнение точки входа
if 'e_entry (stub)' in source:
new_bytes = struct.pack('<Q', entry_point_addr)
for j, nb in enumerate(new_bytes):
virt = f"0x{entry_point_addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"e_entry = 0x{entry_point_addr:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение смещения текстовой секции
elif 'p_offset (text stub)' in source:
new_bytes = struct.pack('<Q', text_offset)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_offset (text) = 0x{text_offset:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение размера текстовой секции в файле
elif 'p_filesz (text stub)' in source:
new_bytes = struct.pack('<Q', text_filesz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_filesz (text) = {text_filesz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение размера текстовой секции в памяти
elif 'p_memsz (text stub)' in source:
new_bytes = struct.pack('<Q', text_memsz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_memsz (text) = {text_memsz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение смещения секции данных
elif 'p_offset (data stub)' in source:
new_bytes = struct.pack('<Q', data_offset)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_offset (data) = 0x{data_offset:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение виртуального адреса секции данных
elif 'p_vaddr (data stub)' in source:
new_bytes = struct.pack('<Q', vaddr_data_start)
for j, nb in enumerate(new_bytes):
virt = f"0x{vaddr_data_start + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_vaddr (data) = 0x{vaddr_data_start:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение физического адреса секции данных
elif 'p_paddr (data stub)' in source:
new_bytes = struct.pack('<Q', vaddr_data_start)
for j, nb in enumerate(new_bytes):
virt = f"0x{vaddr_data_start + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_paddr (data) = 0x{vaddr_data_start:x}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение размера секции данных в файле
elif 'p_filesz (data stub)' in source:
new_bytes = struct.pack('<Q', data_filesz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_filesz (data) = {data_filesz}",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
# Заполнение размера секции данных в памяти (ВКЛЮЧАЯ BSS И ВЫРАВНИВАНИЕ!)
elif 'p_memsz (data stub)' in source:
new_bytes = struct.pack('<Q', data_memsz)
for j, nb in enumerate(new_bytes):
virt = f"0x{addr + j:08x}"
byte_hex = f"0x{nb:02x}"
entries[i + j] = (segment, addr + j, virt, byte_hex, target,
f"p_memsz (data) = {data_memsz} (data: {data_filesz}, bss: {bss_size}, выравнивание: {data_memsz - data_filesz - bss_size})",
label if j == 0 else "",
"", "", byte_hex)
i += 8
continue
i += 1
print("[DEBUG finalize_headers] Завершено")
def save_to_csv(filename: str):
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(['сегмент', 'адрес', 'виртуальный_адрес', 'байт', 'приводящая_метка',
'уводящий_адрес', 'исходная_команда', 'команда_со_значениями',
'рассчитанный_уводящий_адрес', 'рассчитанный_байт'])
for segment, addr, virt_addr, byte_str, target, source, label, cmd_with_values, calc_target, calc_byte in entries:
writer.writerow([
segment,
f"0x{addr:08x}" if isinstance(addr, int) else addr,
virt_addr,
byte_str,
label,
target,
source,
cmd_with_values,
calc_target,
calc_byte
])
def print_section_info():
text_start_offset = 0x1000
text_end_offset = text_start_offset + len(text_buffer)
text_end_aligned = align_up(text_end_offset, PAGE_SIZE)
data_start_offset = text_end_aligned
data_end_offset = data_start_offset + len(data_buffer)
data_end_aligned = align_up(data_end_offset, PAGE_SIZE)
print(f"\n=== Информация о размещении секций (файловые смещения) ===")
print(f" .text: {len(text_buffer)} байт, смещение 0x{text_start_offset:x} - 0x{text_end_offset-1:x}")
print(f" выровнено: следующий сектор начнётся с 0x{text_end_aligned:x}")
print(f" .data: {len(data_buffer)} байт, смещение 0x{data_start_offset:x} - 0x{data_end_offset-1:x}")
print(f" выровнено: следующий сектор начнётся с 0x{data_end_aligned:x}")
if bss_size > 0:
print(f" .bnd: {bss_size} байт (только в памяти, не в файле)")
print(f"\n=== Виртуальные адреса ===")
print(f" .text: 0x{vaddr_text:x} - 0x{vaddr_text + len(text_buffer) - 1:x}")
data_vaddr = align_up(vaddr_text + len(text_buffer), PAGE_SIZE)
print(f" .data: 0x{data_vaddr:x} - 0x{data_vaddr + len(data_buffer) - 1:x}")
if bss_size > 0:
bnd_vaddr = align_up(data_vaddr + len(data_buffer), PAGE_SIZE)
print(f" .bnd: 0x{bnd_vaddr:x} - 0x{bnd_vaddr + bss_size - 1:x}")
def main():
if len(sys.argv) != 3:
print("Использование: python kvs_pass1.py <вход.аст> <выход.csv>")
sys.exit(1)
ast_file = sys.argv[1]
csv_file = sys.argv[2]
reset_state()
set_segment('.header')
generate_elf_header()
generate_program_headers()
align_to(PAGE_SIZE)
ast_lines = read_ast(ast_file)
for line in ast_lines:
process_ast_line(line)
finalize_headers()
save_to_csv(csv_file)
print_section_info()
vaddr_data_final = align_up(vaddr_text + len(text_buffer), PAGE_SIZE)
vaddr_bnd_final = align_up(vaddr_data_final + len(data_buffer), PAGE_SIZE)
print(f"\n=== Первый проход ===")
print(f" .text: {len(text_buffer)} байт, vaddr=0x{vaddr_text:x}")
print(f" .data: {len(data_buffer)} байт, vaddr=0x{vaddr_data_final:x}, offset=0x{align_up(0x1000 + len(text_buffer), PAGE_SIZE):x}")
print(f" .bnd: {bss_size} байт, vaddr=0x{vaddr_bnd_final:x} (только в памяти)")
print(f" точка входа: {entry_point} = 0x{labels.get(entry_point, vaddr_text):x}")
print(f" меток: {len(labels)}")
print(f" констант: {len(symbols)}")
print(f" релокаций: {len(relocations)}")
print(f" записей: {len(entries)}")
print(f" CSV: {csv_file}")
if __name__ == "__main__":
main()

203
квс_проход2.py Normal file
View file

@ -0,0 +1,203 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Второй проход КВС (упрощённый)
- Собирает адреса всех приводящих меток из колонки 'виртуальный_адрес'
- Заменяет заглушки в уводящих адресах на реальные адреса
- Записывает рассчитанные значения в колонку 'рассчитанный_уводящий_адрес' (файловое смещение)
- Формирует команду с подставленными виртуальными адресами в колонку 'команда_со_значениями'
"""
import sys
import csv
from collections import Counter
def read_csv(csv_file):
"""Читает CSV файл и возвращает список записей и заголовки"""
entries = []
headers = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.reader(f, delimiter=';')
headers = next(reader)
for row in reader:
entries.append(row)
return headers, entries
def collect_labels(entries, headers):
"""
Собирает адреса всех приводящих меток.
Использует колонку 'виртуальный_адрес' для получения адреса метки.
Возвращает словарь: имя метки -> виртуальный адрес
"""
labels = {}
try:
label_idx = headers.index('приводящая_метка')
virt_addr_idx = headers.index('виртуальный_адрес')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
for row in entries:
label_str = row[label_idx].strip()
if label_str:
virt_addr_str = row[virt_addr_idx].strip()
if virt_addr_str.startswith('0x'):
virt_addr = int(virt_addr_str, 16)
for label_name in label_str.split(','):
label_name = label_name.strip()
if label_name:
labels[label_name] = virt_addr
# Выводим статистику собранных меток
print(f"\n{'='*80}")
print(f"СОБРАННЫЕ ПРИВОДЯЩИЕ МЕТКИ (всего: {len(labels)})")
print(f"{'='*80}")
print(f"\n{'Имя метки':<35} {'Виртуальный адрес':<22}")
print(f"{'-'*57}")
for name in sorted(labels.keys()):
virt_addr = labels[name]
print(f"{name:<35} 0x{virt_addr:08x} ({virt_addr})")
return labels
def resolve_targets(entries, headers, labels):
"""
Заменяет уводящие адреса-заглушки на реальные адреса
- рассчитанный_уводящий_адрес: файловое смещение (берём из колонки 'адрес')
- команда_со_значениями: виртуальный адрес
"""
try:
target_idx = headers.index('уводящий_адрес')
calc_target_idx = headers.index('рассчитанный_уводящий_адрес')
source_idx = headers.index('исходная_команда')
source_with_values_idx = headers.index('команда_со_значениями')
addr_idx = headers.index('адрес')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
resolved = []
unresolved = []
for row in entries:
target = row[target_idx].strip()
if target and target.startswith('ЗАГЛУШКА '):
label_name = target.replace('ЗАГЛУШКА ', '').strip()
if label_name in labels:
virt_addr = labels[label_name]
virt_hex = f"0x{virt_addr:08x}"
# Файловое смещение берём из колонки 'адрес' текущей строки
addr_str = row[addr_idx].strip()
if addr_str.startswith('0x'):
file_addr = int(addr_str, 16)
file_hex = f"0x{file_addr:08x}"
row[calc_target_idx] = file_hex
resolved.append((label_name, file_addr, virt_addr))
else:
row[calc_target_idx] = ""
resolved.append((label_name, 0, virt_addr))
# Подставляем виртуальный адрес в команду
source_cmd = row[source_idx].strip()
if source_cmd:
cmd_with_addr = source_cmd.replace(label_name, virt_hex)
row[source_with_values_idx] = cmd_with_addr
else:
unresolved.append(label_name)
row[calc_target_idx] = ""
# Выводим статистику разрешённых заглушек
print(f"\n{'='*80}")
print(f"РАЗРЕШЁННЫЕ ЗАГЛУШКИ (всего: {len(resolved)})")
print(f"{'='*80}")
if resolved:
unique_resolved = {}
for name, file_addr, virt_addr in resolved:
if name not in unique_resolved:
unique_resolved[name] = (file_addr, virt_addr)
print(f"\nУникальных меток: {len(unique_resolved)}")
print(f"\n{'Имя метки':<35} {'Файловое смещение':<22} {'Виртуальный адрес':<22}")
print(f"{'-'*79}")
for name in sorted(unique_resolved.keys()):
file_addr, virt_addr = unique_resolved[name]
if file_addr:
print(f"{name:<35} 0x{file_addr:08x} ({file_addr:<10}) 0x{virt_addr:08x} ({virt_addr})")
else:
print(f"{name:<35} {'(не известно)':<22} 0x{virt_addr:08x} ({virt_addr})")
else:
print("\n--- НЕТ РАЗРЕШЁННЫХ ЗАГЛУШЕК ---")
# Выводим статистику неразрешённых заглушек
print(f"\n{'='*80}")
print(f"НЕРАЗРЕШЁННЫЕ ЗАГЛУШКИ (всего: {len(unresolved)})")
print(f"{'='*80}")
if unresolved:
unresolved_counts = Counter(unresolved)
print(f"\nУникальных неразрешённых имён: {len(unresolved_counts)}")
print(f"\n{'Имя метки':<35} {'Количество вхождений':<20}")
print(f"{'-'*55}")
for name, count in sorted(unresolved_counts.items(), key=lambda x: x[1], reverse=True):
print(f"{name:<35} {count}")
else:
print("\n--- НЕТ НЕРАЗРЕШЁННЫХ ЗАГЛУШЕК ---")
return resolved, unresolved
def save_csv(csv_file, headers, entries):
"""Сохраняет обновлённый CSV файл"""
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(headers)
writer.writerows(entries)
def main():
if len(sys.argv) != 2:
print("Использование: python kvs_pass2.py <входной.csv>")
sys.exit(1)
csv_file = sys.argv[1]
print(f"\n=== ВТОРОЙ ПРОХОД КВС (упрощённый) ===")
print(f"Чтение CSV: {csv_file}")
headers, entries = read_csv(csv_file)
print(f"\n--- СБОР ПРИВОДЯЩИХ МЕТОК ---")
labels = collect_labels(entries, headers)
print(f"\n--- РАЗРЕШЕНИЕ УВОДЯЩИХ АДРЕСОВ ---")
resolved, unresolved = resolve_targets(entries, headers, labels)
print(f"\n{'='*80}")
print(f"ОБЩАЯ СТАТИСТИКА")
print(f"{'='*80}")
print(f" Приводящих меток в CSV: {len(labels)}")
print(f" Разрешено заглушек: {len(resolved)}")
print(f" Не разрешено заглушек: {len(unresolved)}")
print(f"{'='*80}")
save_csv(csv_file, headers, entries)
print(f"\nCSV сохранён: {csv_file}")
if __name__ == "__main__":
main()

141
квс_проход3.py Normal file
View file

@ -0,0 +1,141 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Третий проход КВС
- Читает AST для получения констант
- Заменяет имена констант в 'исходная_команда' на их числовые значения
- Записывает результат в колонку 'команда_со_значениями'
- Поддерживает константы в операндах (как непосредственные значения)
"""
import sys
import csv
def read_ast(ast_file):
"""Читает AST файл и извлекает константы"""
symbols = {} # имя константы -> значение
with open(ast_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(':')
if parts[0] == "DIRECTIVE" and len(parts) >= 4:
directive = parts[1]
if directive == '.константа':
name = parts[2]
value_str = parts[3]
if value_str.startswith('0x') or value_str.startswith('0X'):
value = int(value_str, 16)
else:
value = int(value_str)
symbols[name] = value
print(f" константа: {name} = {value}")
return symbols
def read_csv(csv_file):
"""Читает CSV файл и возвращает список записей и заголовки"""
entries = []
headers = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.reader(f, delimiter=';')
headers = next(reader)
for row in reader:
entries.append(row)
return headers, entries
def substitute_constants(entries, headers, symbols):
"""Заменяет константы в исходной команде на их значения"""
try:
source_idx = headers.index('исходная_команда')
source_with_values_idx = headers.index('команда_со_значениями')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
substituted_count = 0
for row in entries:
source = row[source_idx].strip()
if not source:
continue
# Копируем исходную команду
new_source = source
# Заменяем константы в команде
for name, value in symbols.items():
if name in new_source:
# Заменяем имя константы на её значение
new_source = new_source.replace(name, str(value))
# Если команда изменилась, записываем в колонку команда_со_значениями
if new_source != source:
row[source_with_values_idx] = new_source
substituted_count += 1
return substituted_count
def save_csv(csv_file, headers, entries):
"""Сохраняет обновлённый CSV файл"""
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(headers)
writer.writerows(entries)
def print_statistics(symbols_count, substituted_count, symbols):
"""Выводит статистику по заменам констант"""
print(f"\n--- Статистика замены констант ---")
print(f" Всего констант в AST: {symbols_count}")
print(f" Заменено в командах: {substituted_count}")
if symbols:
print(f"\n--- Найденные константы ---")
for name, value in sorted(symbols.items()):
print(f" {name} = {value}")
def main():
if len(sys.argv) != 3:
print("Использование: python kvs_pass3.py <аст.файл> <входной.csv>")
print(" <аст.файл> - файл с AST (например, программа.аст)")
print(" <входной.csv> - CSV файл для обновления")
sys.exit(1)
ast_file = sys.argv[1]
csv_file = sys.argv[2]
print(f"\n=== Третий проход КВС ===")
print(f"Чтение AST: {ast_file}")
# Читаем константы из AST
symbols = read_ast(ast_file)
print(f"Найдено констант: {len(symbols)}")
print(f"\nЧтение CSV: {csv_file}")
headers, entries = read_csv(csv_file)
print(f"\n--- Замена констант в командах ---")
substituted_count = substitute_constants(entries, headers, symbols)
print_statistics(len(symbols), substituted_count, symbols)
# Сохраняем результат
save_csv(csv_file, headers, entries)
print(f"\nCSV сохранен: {csv_file}")
print(f"Колонка 'команда_со_значениями' обновлена")
if __name__ == "__main__":
main()

292
квс_проход4.py Normal file
View file

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Четвёртый проход КВС
- Перекодирует инструкции, используя подставленные значения
- Смотрит колонку 'команда_со_значениями'
- Записывает перекодированные байты в колонку 'рассчитанный_байт'
- Использует виртуальные адреса из CSV для вычисления смещений
"""
import sys
import csv
sys.path.insert(0, '.')
from kvs_data import PAGE_SIZE, text_vaddr_base, align_up
from kvs_encoder import encode_instruction
# ========== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ==========
vaddr_text = None # будет прочитано из CSV
vaddr_data = None # будет прочитано из CSV
vaddr_bnd = None # будет прочитано из CSV
labels = {}
label_sections = {}
symbols = {}
def read_csv(csv_file):
entries = []
headers = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.reader(f, delimiter=';')
headers = next(reader)
for row in reader:
entries.append(row)
return headers, entries
def parse_command(cmd_str):
if not cmd_str:
return None, []
parts = cmd_str.split(' ', 1)
mnemonic = parts[0]
operands = []
if len(parts) > 1 and parts[1]:
operands = [op.strip() for op in parts[1].split(',')]
return mnemonic, operands
def detect_section_addresses(entries, headers, log_file):
"""
Определяет виртуальные адреса секций из CSV.
"""
global vaddr_text, vaddr_data, vaddr_bnd
try:
segment_idx = headers.index('сегмент')
virt_addr_idx = headers.index('виртуальный_адрес')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
found_text = False
found_data = False
found_bnd = False
for row in entries:
segment = row[segment_idx].strip()
virt_addr_str = row[virt_addr_idx].strip()
if not virt_addr_str.startswith('0x'):
continue
virt_addr = int(virt_addr_str, 16)
if segment == '.text' and not found_text:
vaddr_text = virt_addr
found_text = True
log_file.write(f" .text: 0x{vaddr_text:x}\n")
elif segment == '.data' and not found_data:
vaddr_data = virt_addr
found_data = True
log_file.write(f" .data: 0x{vaddr_data:x}\n")
elif segment == '.bss' and not found_bnd:
vaddr_bnd = virt_addr
found_bnd = True
log_file.write(f" .bss: 0x{vaddr_bnd:x}\n")
# Проверяем, что все нужные секции найдены
if not found_text:
print(" Ошибка: секция .text не найдена в CSV")
sys.exit(1)
if not found_data:
log_file.write(" Предупреждение: секция .data не найдена в CSV, используем 0x402000\n")
vaddr_data = 0x402000 # fallback
def collect_labels_from_csv(entries, headers, log_file):
"""
Собирает метки из CSV, используя колонку 'виртуальный_адрес'
"""
global labels, label_sections
try:
label_idx = headers.index('приводящая_метка')
virt_addr_idx = headers.index('виртуальный_адрес')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
for row in entries:
label_str = row[label_idx].strip()
if label_str:
virt_addr_str = row[virt_addr_idx].strip()
if virt_addr_str.startswith('0x'):
virt_addr = int(virt_addr_str, 16)
for label_name in label_str.split(','):
label_name = label_name.strip()
if label_name:
labels[label_name] = virt_addr
label_sections[label_name] = ''
log_file.write(f"Найдено меток: {len(labels)}\n")
def reencode_instructions(entries, headers, log_file):
try:
source_with_values_idx = headers.index('команда_со_значениями')
calc_byte_idx = headers.index('рассчитанный_байт')
addr_idx = headers.index('адрес')
virt_addr_idx = headers.index('виртуальный_адрес')
byte_idx = headers.index('байт')
segment_idx = headers.index('сегмент')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
reencoded_count = 0
error_count = 0
i = 0
while i < len(entries):
row = entries[i]
addr_str = row[addr_idx].strip()
if not addr_str.startswith('0x'):
i += 1
continue
file_addr = int(addr_str, 16)
# Берём виртуальный адрес инструкции из CSV
virt_addr_str = row[virt_addr_idx].strip()
if virt_addr_str.startswith('0x'):
instr_virt_addr = int(virt_addr_str, 16)
else:
instr_virt_addr = file_addr
cmd = row[source_with_values_idx].strip()
if cmd:
try:
mnemonic, operands = parse_command(cmd)
if mnemonic:
# Для отладки: записываем в лог все инструкции переместить_имм
if mnemonic == 'переместить_имм' and operands and '0x00403014' in cmd:
log_file.write(f"\nDEBUG MOV:\n")
log_file.write(f" Адрес в CSV: 0x{file_addr:08x}\n")
log_file.write(f" Команда: {cmd}\n")
log_file.write(f" Операнды: {operands}\n")
# Вычисляем current_pos как смещение в секции
section = row[segment_idx].strip()
if section == '.text':
current_pos = instr_virt_addr - vaddr_text
elif section == '.data':
current_pos = instr_virt_addr - vaddr_data
elif section == '.bss':
current_pos = instr_virt_addr - vaddr_bnd if vaddr_bnd else 0
else:
# Fallback: определяем по диапазону адресов - ФУФЛО, УБРАТЬ
if vaddr_text and 0x401000 <= instr_virt_addr < 0x402000:
current_pos = instr_virt_addr - vaddr_text
elif vaddr_data and 0x402000 <= instr_virt_addr < 0x403000:
current_pos = instr_virt_addr - vaddr_data
else:
current_pos = instr_virt_addr
encoded = encode_instruction(
mnemonic, operands, labels, label_sections, symbols,
vaddr_text, vaddr_data if vaddr_data else 0,
current_pos, vaddr_bnd if vaddr_bnd else 0
)
# Для отладки: записываем результат
if mnemonic == 'переместить_имм' and operands and '0x00403014' in cmd:
log_file.write(f" Закодировано: {[f'0x{b:02x}' for b in encoded]}\n")
log_file.write(f" Длина: {len(encoded)} байт\n")
for j, byte in enumerate(encoded):
if i + j < len(entries):
entries[i + j][calc_byte_idx] = f"0x{byte:02x}"
reencoded_count += 1
i += len(encoded)
continue
except Exception as e:
log_file.write(f"\n=== ОШИБКА ===\n")
log_file.write(f" Команда: {cmd}\n")
log_file.write(f" Мнемоника: {mnemonic if 'mnemonic' in locals() else '?'}\n")
log_file.write(f" Ошибка: {e}\n")
error_count += 1
row[calc_byte_idx] = "0xcc"
i += 1
else:
# Если нет команды, копируем исходный байт
byte_str = row[byte_idx].strip()
if byte_str.startswith('0x'):
row[calc_byte_idx] = byte_str
i += 1
return reencoded_count, error_count
def save_csv(csv_file, headers, entries):
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(headers)
writer.writerows(entries)
def print_statistics(reencoded_count, error_count, labels_count):
print(f"\n--- Статистика перекодирования ---")
print(f" Меток в словаре: {labels_count}")
print(f" Перекодировано инструкций: {reencoded_count}")
if error_count:
print(f" Ошибок: {error_count}")
else:
print(f" Ошибок: нет")
def main():
if len(sys.argv) != 2:
print("Использование: python kvs_pass4.py <входной.csv>")
sys.exit(1)
csv_file = sys.argv[1]
log_file_name = csv_file.replace('.csv', '_pass4.log')
print(f"\n=== Четвёртый проход КВС ===")
print(f"Чтение CSV: {csv_file}")
with open(log_file_name, 'w', encoding='utf-8') as log_file:
log_file.write(f"=== Четвёртый проход КВС ===\n")
log_file.write(f"CSV: {csv_file}\n\n")
headers, entries = read_csv(csv_file)
log_file.write(f"--- Определение адресов секций из CSV ---\n")
detect_section_addresses(entries, headers, log_file)
log_file.write(f"\n--- Сбор меток из CSV ---\n")
collect_labels_from_csv(entries, headers, log_file)
log_file.write(f"\n--- Перекодирование инструкций ---\n")
reencoded_count, error_count = reencode_instructions(entries, headers, log_file)
log_file.write(f"\n--- Статистика ---\n")
log_file.write(f" Меток в словаре: {len(labels)}\n")
log_file.write(f" Перекодировано инструкций: {reencoded_count}\n")
log_file.write(f" Ошибок: {error_count}\n")
print_statistics(reencoded_count, error_count, len(labels))
save_csv(csv_file, headers, entries)
print(f"\nCSV сохранён: {csv_file}")
print(f"Лог сохранён: {log_file_name}")
if __name__ == "__main__":
main()

678
квс_проход5.py Normal file
View file

@ -0,0 +1,678 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Пятый проход КВС — генерация и добавление таблицы секций в CSV
Сканирует CSV, собирает информацию о секциях, обновляет ELF-заголовок
ПЕРЕСОЗДАЁТ ПРОГРАММНЫЕ ЗАГОЛОВКИ на основе реальных размеров секций
и добавляет таблицу секций в конец файла
ДОБАВЛЕНА НУЛЕВАЯ СЕКЦИЯ (индекс 0)
ДОБАВЛЕНА СЕКЦИЯ .comment со строкой "Сборщик КВС"
РАЗМЕЩЕНИЕ СЕКЦИЙ КАК В kvs_8.py
ПРАВИЛЬНЫЕ ИНДЕКСЫ В .shstrtab (с учётом наличия .bss)
"""
import sys
import csv
import struct
PAGE_SIZE = 0x1000
ELF_BASE_VADDR = 0x400000
DEBUG_PRINTS = True
def align_up(value, alignment):
"""Выравнивание вверх"""
return ((value + alignment - 1) // alignment) * alignment
def analyze_csv(csv_file):
"""Анализирует CSV и собирает информацию о секциях"""
sections = {
'.text': {'start': None, 'end': None, 'size': 0, 'virt_start': None, 'virt_end': None},
'.data': {'start': None, 'end': None, 'size': 0, 'virt_start': None, 'virt_end': None},
}
bss_blocks = []
max_addr = 0
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
for row in reader:
seg = row['сегмент']
addr_str = row['адрес']
virt_addr_str = row['виртуальный_адрес']
label = row['приводящая_метка']
source = row['исходная_команда']
if not seg:
continue
# Обновляем максимальный файловый адрес (только числовые адреса)
if addr_str and addr_str != 'BSS' and addr_str != '':
try:
addr = int(addr_str, 16)
if addr > max_addr:
max_addr = addr
except:
pass
# BSS секция (одна запись на блок)
if seg == '.bss':
if source and source.startswith('.резб'):
# Формат: ".резб 1048576"
parts = source.split()
if len(parts) >= 2:
size = int(parts[1])
virt_start = int(virt_addr_str, 16) if virt_addr_str else 0
bss_blocks.append({
'virt_start': virt_start,
'size': size,
'label': label,
'source': source
})
continue
# Обычные секции
if addr_str == 'BSS' or addr_str == '':
continue
try:
addr = int(addr_str, 16)
virt_addr = int(virt_addr_str, 16)
except:
continue
if seg == '.text':
if sections['.text']['start'] is None:
sections['.text']['start'] = addr
sections['.text']['virt_start'] = virt_addr
sections['.text']['end'] = addr
sections['.text']['virt_end'] = virt_addr
sections['.text']['size'] += 1
elif seg == '.data':
if sections['.data']['start'] is None:
sections['.data']['start'] = addr
sections['.data']['virt_start'] = virt_addr
sections['.data']['end'] = addr
sections['.data']['virt_end'] = virt_addr
sections['.data']['size'] += 1
# Пересчёт размеров секций из адресов
if sections['.text']['start'] and sections['.text']['end']:
sections['.text']['size'] = sections['.text']['end'] - sections['.text']['start'] + 1
if sections['.data']['start'] and sections['.data']['end']:
sections['.data']['size'] = sections['.data']['end'] - sections['.data']['start'] + 1
# Вычисляем общий размер BSS и начальный виртуальный адрес
bss_total_size = sum(block['size'] for block in bss_blocks)
bss_start_vaddr = min((block['virt_start'] for block in bss_blocks), default=0)
return sections, bss_blocks, max_addr, bss_total_size, bss_start_vaddr
def generate_comment_data():
"""Генерирует данные для секции .comment (UTF-8 строка 'Сборщик КВС' с нуль-терминатором)"""
comment_str = 'Сборщик КВС'
return comment_str.encode('utf-8') + b'\x00'
def generate_shstrtab_binary(has_bss):
"""Генерирует бинарную таблицу имён секций (как в kvs_8.py)"""
if has_bss:
names = ['', '.text', '.data', '.bss', '.comment', '.shstrtab']
else:
names = ['', '.text', '.data', '.comment', '.shstrtab']
result = b''
for name in names:
result += name.encode('ascii') + b'\x00'
return result
def create_null_section_entry(shdr_offset):
"""Создаёт нулевую секцию (индекс 0) — все поля нулевые"""
entries = []
for j in range(64):
byte_val = 0x01 if j == 48 else 0x00 # sh_addralign = 1
entries.append({
'сегмент': '.shdr',
'адрес': f"0x{shdr_offset + j:08x}",
'виртуальный_адрес': '0x00000000',
'байт': f"0x{byte_val:02x}",
'приводящая_метка': 'NULL' if j == 0 else '',
'уводящий_адрес': '',
'исходная_команда': '.shdr NULL' if j == 0 else '',
'команда_со_значениями': '',
'рассчитанный_уводящий_адрес': '',
'рассчитанный_байт': f"0x{byte_val:02x}"
})
return entries
def create_section_table_entries(sections, has_bss, shdr_offset, shstrtab_offset,
comment_offset, comment_size, shstrtab_data,
bss_start_vaddr, bss_total_size):
"""Создаёт записи таблицы секций для CSV (с нулевой секцией, .comment и .shstrtab)"""
if DEBUG_PRINTS:
print("\n[DEBUG] create_section_table_entries")
print(f" has_bss = {has_bss}")
print(f" bss_start_vaddr = 0x{bss_start_vaddr:x}")
print(f" bss_total_size = {bss_total_size}")
print(f" shdr_offset = 0x{shdr_offset:x}")
print(f" shstrtab_offset = 0x{shstrtab_offset:x}")
print(f" comment_offset = 0x{comment_offset:x}")
print(f" comment_size = {comment_size}")
entries = []
# Нулевая секция
null_entries = create_null_section_entry(shdr_offset)
entries.extend(null_entries)
if DEBUG_PRINTS:
print(f" Нулевая секция: {len(null_entries)} байт по адресу 0x{shdr_offset:x}")
next_offset = shdr_offset + 64
if DEBUG_PRINTS:
print(f" next_offset = 0x{next_offset:x}")
# Индексы в .shstrtab
if has_bss:
idx_text = 1
idx_data = 7
idx_bss = 13
idx_comment = 18
idx_shstrtab = 27 # это харкод, если поменяю название сборщика - придется менять
else:
idx_text = 1
idx_data = 7
idx_comment = 13
idx_shstrtab = 22
if DEBUG_PRINTS:
print(f" Индексы в .shstrtab: text={idx_text}, data={idx_data}, bss={idx_bss if has_bss else 'N/A'}, comment={idx_comment}, shstrtab={idx_shstrtab}")
# 1. .text
header_text = struct.pack('<IIQQQQIIQQ',
idx_text, 1, 6,
sections['.text']['virt_start'],
sections['.text']['start'],
sections['.text']['size'],
0, 0, 16, 0)
# 2. .data
header_data = struct.pack('<IIQQQQIIQQ',
idx_data, 1, 3,
sections['.data']['virt_start'],
sections['.data']['start'],
sections['.data']['size'],
0, 0, 8, 0)
headers = [header_text, header_data]
header_names = ['.text', '.data']
# 3. .bss (если есть)
if has_bss and bss_total_size > 0:
# SHT_NOBITS = 8, SHF_WRITE|SHF_ALLOC = 3
header_bss = struct.pack('<IIQQQQIIQQ',
idx_bss, 8, 3,
bss_start_vaddr, # sh_addr
0, # sh_offset (для NOBITS неважно)
bss_total_size, # sh_size
0, 0, 16, 0)
headers.append(header_bss)
header_names.append('.bss')
if DEBUG_PRINTS:
print(f" Заголовок .bss: addr=0x{bss_start_vaddr:x}, size={bss_total_size}")
# 4. .comment
header_comment = struct.pack('<IIQQQQIIQQ',
idx_comment, 1, 0,
0, comment_offset, comment_size,
0, 0, 1, 0)
headers.append(header_comment)
header_names.append('.comment')
# 5. .shstrtab
shstrtab_size = len(shstrtab_data)
header_shstrtab = struct.pack('<IIQQQQIIQQ',
idx_shstrtab, 3, 0,
0, shstrtab_offset, shstrtab_size,
0, 0, 1, 0)
headers.append(header_shstrtab)
header_names.append('.shstrtab')
# Записываем заголовки секций
if DEBUG_PRINTS:
print(f"\n Запись заголовков секций в CSV:")
for i, header in enumerate(headers):
base_addr = next_offset + i * 64
if DEBUG_PRINTS:
print(f" Секция {i+1}: {header_names[i]} по адресу 0x{base_addr:x}")
for j, byte in enumerate(header):
entries.append({
'сегмент': '.shdr',
'адрес': f"0x{base_addr + j:08x}",
'виртуальный_адрес': '0x00000000',
'байт': f"0x{byte:02x}",
'приводящая_метка': header_names[i] if j == 0 else '',
'уводящий_адрес': '',
'исходная_команда': f'.shdr {header_names[i]}' if j == 0 else '',
'команда_со_значениями': '',
'рассчитанный_уводящий_адрес': '',
'рассчитанный_байт': f"0x{byte:02x}"
})
if DEBUG_PRINTS:
print(f"\n Всего заголовков: {len(headers)}")
print(f" Возвращаем секций: {len(headers) + 1} (включая NULL)")
return entries, len(headers) + 1
def regenerate_program_headers_in_csv(csv_file, sections, bss_total_size, text_offset, data_offset):
"""Обновляет программные заголовки по адресу, вставляет новые при необходимости"""
# Читаем CSV
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
fieldnames = reader.fieldnames
rows = list(reader)
def get_addr_num(row):
addr_str = row.get('адрес', '')
if addr_str and addr_str != 'BSS' and addr_str.startswith('0x'):
try:
return int(addr_str, 16)
except:
return None
return None
addr_to_row = {}
rows_without_addr = []
for row in rows:
addr = get_addr_num(row)
if addr is not None:
addr_to_row[addr] = row
else:
rows_without_addr.append(row)
text_size = sections['.text']['size']
data_size = sections['.data']['size']
vaddr_text = sections['.text']['virt_start']
vaddr_data = sections['.data']['virt_start']
text_memsz = align_up(text_size, PAGE_SIZE)
data_memsz = align_up(data_size + bss_total_size, PAGE_SIZE)
new_entries = []
header_defs = [
# PHDR 0: ELF headers (R)
(0x40, 0x01, 'I', 'p_type = 0x1'),
(0x44, 0x04, 'I', 'p_flags = 0x4'),
(0x48, 0x00, 'Q', 'p_offset = 0x0'),
(0x50, ELF_BASE_VADDR, 'Q', 'p_vaddr = 0x400000'),
(0x58, ELF_BASE_VADDR, 'Q', 'p_paddr = 0x400000'),
(0x60, 0xe8, 'Q', 'p_filesz = 232'),
(0x68, align_up(0xe8, PAGE_SIZE), 'Q', 'p_memsz = 4096'),
(0x70, PAGE_SIZE, 'Q', 'p_align = 0x1000'),
# PHDR 1: .text (R E)
(0x78, 0x01, 'I', 'p_type = 0x1'),
(0x7c, 0x05, 'I', 'p_flags = 0x5'),
(0x80, text_offset, 'Q', f'p_offset = 0x{text_offset:x}'),
(0x88, vaddr_text, 'Q', f'p_vaddr = 0x{vaddr_text:x}'),
(0x90, vaddr_text, 'Q', f'p_paddr = 0x{vaddr_text:x}'),
(0x98, text_size, 'Q', f'p_filesz = {text_size}'),
(0xa0, text_memsz, 'Q', f'p_memsz = {text_memsz}'),
(0xa8, PAGE_SIZE, 'Q', 'p_align = 0x1000'),
# PHDR 2: .data + .bss (RW)
(0xb0, 0x01, 'I', 'p_type = 0x1'),
(0xb4, 0x06, 'I', 'p_flags = 0x6'),
(0xb8, data_offset, 'Q', f'p_offset = 0x{data_offset:x}'),
(0xc0, vaddr_data, 'Q', f'p_vaddr = 0x{vaddr_data:x}'),
(0xc8, vaddr_data, 'Q', f'p_paddr = 0x{vaddr_data:x}'),
(0xd0, data_size, 'Q', f'p_filesz = {data_size}'),
(0xd8, data_memsz, 'Q', f'p_memsz = {data_memsz}'),
(0xe0, PAGE_SIZE, 'Q', 'p_align = 0x1000'),
]
for addr, value, pack_type, command in header_defs:
if pack_type == 'I':
bytes_data = struct.pack('<I', value)
else:
bytes_data = struct.pack('<Q', value)
for i, byte in enumerate(bytes_data):
current_addr = addr + i
new_row = {
'сегмент': '.header',
'адрес': f"0x{current_addr:08x}",
'виртуальный_адрес': f"0x{current_addr:08x}",
'байт': f"0x{byte:02x}",
'приводящая_метка': '',
'уводящий_адрес': '',
'исходная_команда': command if i == 0 else '',
'команда_со_значениями': '',
'рассчитанный_уводящий_адрес': '',
'рассчитанный_байт': f"0x{byte:02x}"
}
new_entries.append((current_addr, new_row))
for addr, new_row in new_entries:
addr_to_row[addr] = new_row
sorted_addrs = sorted(addr_to_row.keys())
final_rows = []
for addr in sorted_addrs:
final_rows.append(addr_to_row[addr])
final_rows.extend(rows_without_addr)
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';', restval='')
writer.writeheader()
writer.writerows(final_rows)
print(f"✅ Программные заголовки обновлены в {csv_file}")
print(f" Обновлено/добавлено записей: {len(new_entries)}")
print(f" Всего записей с адресами: {len(sorted_addrs)}")
def update_elf_header_in_csv(csv_file, e_shoff, e_shnum, e_shentsize, e_shstrndx):
"""Обновляет ELF-заголовок в CSV файле"""
rows = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
fieldnames = reader.fieldnames
for row in reader:
rows.append(row)
# e_shoff
shoff_bytes = struct.pack('<Q', e_shoff)
for i in range(8):
addr = f"0x{0x28 + i:08x}"
for row in rows:
if row['адрес'] == addr:
row['рассчитанный_байт'] = f"0x{shoff_bytes[i]:02x}"
row['байт'] = f"0x{shoff_bytes[i]:02x}"
row['исходная_команда'] = f"e_shoff = 0x{e_shoff:x}"
# e_shnum
shnum_bytes = struct.pack('<H', e_shnum)
for i in range(2):
addr = f"0x{0x3c + i:08x}"
for row in rows:
if row['адрес'] == addr:
row['рассчитанный_байт'] = f"0x{shnum_bytes[i]:02x}"
row['байт'] = f"0x{shnum_bytes[i]:02x}"
row['исходная_команда'] = f"e_shnum = {e_shnum}"
# e_shentsize
shentsize_bytes = struct.pack('<H', e_shentsize)
for i in range(2):
addr = f"0x{0x3a + i:08x}"
for row in rows:
if row['адрес'] == addr:
row['рассчитанный_байт'] = f"0x{shentsize_bytes[i]:02x}"
row['байт'] = f"0x{shentsize_bytes[i]:02x}"
row['исходная_команда'] = f"e_shentsize = {e_shentsize}"
# e_shstrndx
shstrndx_bytes = struct.pack('<H', e_shstrndx)
for i in range(2):
addr = f"0x{0x3e + i:08x}"
for row in rows:
if row['адрес'] == addr:
row['рассчитанный_байт'] = f"0x{shstrndx_bytes[i]:02x}"
row['байт'] = f"0x{shstrndx_bytes[i]:02x}"
row['исходная_команда'] = f"e_shstrndx = {e_shstrndx}"
# e_phnum
phnum_bytes = struct.pack('<H', 3)
for i in range(2):
addr = f"0x{0x38 + i:08x}"
for row in rows:
if row['адрес'] == addr:
row['рассчитанный_байт'] = f"0x{phnum_bytes[i]:02x}"
row['байт'] = f"0x{phnum_bytes[i]:02x}"
row['исходная_команда'] = f"e_phnum = 3"
# e_phoff
phoff_bytes = struct.pack('<Q', 0x40)
for i in range(8):
addr = f"0x{0x20 + i:08x}"
for row in rows:
if row['адрес'] == addr:
row['рассчитанный_байт'] = f"0x{phoff_bytes[i]:02x}"
row['байт'] = f"0x{phoff_bytes[i]:02x}"
row['исходная_команда'] = f"e_phoff = 0x40"
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';', restval='')
writer.writeheader()
writer.writerows(rows)
print(f"✅ ELF-заголовок обновлён в {csv_file}")
print(f" e_shoff = 0x{e_shoff:x}")
print(f" e_shnum = {e_shnum}")
print(f" e_shentsize = {e_shentsize}")
print(f" e_shstrndx = {e_shstrndx}")
def merge_csv_with_section_table(csv_file, section_entries, comment_entries, shstrtab_entries):
"""Добавляет записи таблицы секций, .comment и .shstrtab в конец CSV файла"""
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
fieldnames = reader.fieldnames
rows = list(reader)
existing_addrs = set()
for row in rows:
addr = row['адрес']
if addr and addr != 'BSS':
existing_addrs.add(addr)
new_count = 0
for entry in section_entries:
if entry['адрес'] not in existing_addrs:
rows.append(entry)
existing_addrs.add(entry['адрес'])
new_count += 1
for entry in comment_entries:
if entry['адрес'] not in existing_addrs:
rows.append(entry)
existing_addrs.add(entry['адрес'])
new_count += 1
for entry in shstrtab_entries:
if entry['адрес'] not in existing_addrs:
rows.append(entry)
existing_addrs.add(entry['адрес'])
new_count += 1
print(f" Добавлено {new_count} новых записей")
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';', restval='')
writer.writeheader()
writer.writerows(rows)
def add_comment_data(comment_offset):
"""Создаёт записи для данных .comment"""
entries = []
comment_data = generate_comment_data()
for j, byte in enumerate(comment_data):
entries.append({
'сегмент': '.comment',
'адрес': f"0x{comment_offset + j:08x}",
'виртуальный_адрес': '0x00000000',
'байт': f"0x{byte:02x}",
'приводящая_метка': 'Сборщик КВС' if j == 0 else '',
'уводящий_адрес': '',
'исходная_команда': '.comment byte' if j == 0 else '',
'команда_со_значениями': '',
'рассчитанный_уводящий_адрес': '',
'рассчитанный_байт': f"0x{byte:02x}"
})
return entries
def add_shstrtab_data(shstrtab_offset, shstrtab_data):
"""Создаёт записи для данных .shstrtab"""
entries = []
for j, byte in enumerate(shstrtab_data):
entries.append({
'сегмент': '.shstrtab',
'адрес': f"0x{shstrtab_offset + j:08x}",
'виртуальный_адрес': '0x00000000',
'байт': f"0x{byte:02x}",
'приводящая_метка': '',
'уводящий_адрес': '',
'исходная_команда': '.shstrtab byte' if j == 0 else '',
'команда_со_значениями': '',
'рассчитанный_уводящий_адрес': '',
'рассчитанный_байт': f"0x{byte:02x}"
})
return entries
def print_section_info(sections, has_bss, max_addr, bss_total_size, bss_start_vaddr):
"""Выводит основную информацию о секциях"""
print("\n" + "="*60)
print("ДИАГНОСТИКА ПЯТОГО ПРОХОДА")
print("="*60)
if sections['.text']['start']:
print(f"\n📦 Секция .text:")
print(f" Файловое смещение: 0x{sections['.text']['start']:08x} - 0x{sections['.text']['end']:08x}")
print(f" Виртуальный адрес: 0x{sections['.text']['virt_start']:08x} - 0x{sections['.text']['virt_end']:08x}")
print(f" Размер: {sections['.text']['size']} байт")
if sections['.data']['start']:
print(f"\n📦 Секция .data:")
print(f" Файловое смещение: 0x{sections['.data']['start']:08x} - 0x{sections['.data']['end']:08x}")
print(f" Виртуальный адрес: 0x{sections['.data']['virt_start']:08x} - 0x{sections['.data']['virt_end']:08x}")
print(f" Размер: {sections['.data']['size']} байт")
if has_bss:
print(f"\n📦 Секция .bnd (BSS):")
print(f" Виртуальный адрес: 0x{bss_start_vaddr:08x}")
print(f" Размер: {bss_total_size} байт")
else:
print(f"\n📦 Секция .bnd (BSS):")
print(f" Нет BSS-блоков")
print(f"\n📍 Максимальный файловый адрес: 0x{max_addr:08x}")
def main():
if len(sys.argv) != 2:
print("Использование: python kvs_pass5.py <файл.csv>")
sys.exit(1)
csv_file = sys.argv[1]
print(f"\n📊 Анализ CSV: {csv_file}")
sections, bss_blocks, max_addr, bss_total_size, bss_start_vaddr = analyze_csv(csv_file)
has_bss = bss_total_size > 0
print_section_info(sections, has_bss, max_addr, bss_total_size, bss_start_vaddr)
text_offset = sections['.text']['start']
data_offset = sections['.data']['start']
text_size = sections['.text']['size']
data_size = sections['.data']['size']
comment_data = generate_comment_data()
comment_size = len(comment_data)
comment_offset = data_offset + data_size
print(f" comment_offset = 0x{comment_offset:x}")
shstrtab_data = generate_shstrtab_binary(has_bss)
shstrtab_size = len(shstrtab_data)
shstrtab_offset = align_up(comment_offset + comment_size, 8)
print(f" shstrtab_offset = 0x{shstrtab_offset:x}")
print(f" shstrtab_size = {shstrtab_size}")
num_sections = 5
if has_bss:
num_sections += 1
shdr_offset = align_up(shstrtab_offset + shstrtab_size, 16)
print(f" shdr_offset = 0x{shdr_offset:x}")
print(f"\n📍 Размещение секций (как в kvs_8.py):")
print(f" .data: 0x{data_offset:08x}")
print(f" .comment: 0x{comment_offset:08x} (размер {comment_size} байт)")
print(f" .shstrtab: 0x{shstrtab_offset:08x} (размер {shstrtab_size} байт)")
print(f" Таблица секций: 0x{shdr_offset:08x}")
print(f" Количество секций: {num_sections}")
print(f" Наличие BSS: {has_bss}")
# === НОВЫЙ КОД: обновляем max_addr ===
max_addr = max(max_addr, comment_offset + comment_size - 1)
max_addr = max(max_addr, shstrtab_offset + shstrtab_size - 1)
max_addr = max(max_addr, shdr_offset + (num_sections * 64) - 1)
print(f"\n📍 Обновлённый максимальный файловый адрес: 0x{max_addr:08x}")
# === КОНЕЦ НОВОГО КОДА ===
# Обновляем программные заголовки с учётом BSS
regenerate_program_headers_in_csv(csv_file, sections, bss_total_size, text_offset, data_offset)
# Создаём записи таблицы секций
entries, num_headers = create_section_table_entries(
sections, has_bss, shdr_offset, shstrtab_offset, comment_offset, comment_size,
shstrtab_data, bss_start_vaddr, bss_total_size
)
comment_entries = add_comment_data(comment_offset)
shstrtab_entries = add_shstrtab_data(shstrtab_offset, shstrtab_data)
e_shoff = shdr_offset
e_shnum = num_headers
e_shentsize = 64
e_shstrndx = num_headers - 1
print(f"\n📝 Обновление ELF-заголовка:")
print(f" e_shoff = 0x{e_shoff:08x}")
print(f" e_shnum = {e_shnum}")
print(f" e_shentsize = {e_shentsize}")
print(f" e_shstrndx = {e_shstrndx}")
update_elf_header_in_csv(csv_file, e_shoff, e_shnum, e_shentsize, e_shstrndx)
merge_csv_with_section_table(csv_file, entries, comment_entries, shstrtab_entries)
print(f"\n💾 Таблица секций добавлена в {csv_file}")
print(f" Добавлено {len(entries)} записей заголовков")
print(f" Добавлено {len(comment_entries)} записей .comment")
print(f" Добавлено {len(shstrtab_entries)} записей .shstrtab")
print(f"\n✅ Готово! Размещение секций как в kvs_8.py:")
print(f" .comment сразу после .data")
print(f" .shstrtab выровнен на 8 байт")
print(f" Таблица секций выровнена на 16 байт")
if has_bss:
print(f" BSS присутствует, размер {bss_total_size} байт, адрес 0x{bss_start_vaddr:x}")
if __name__ == "__main__":
main()

131
квс_проход6.py Normal file
View file

@ -0,0 +1,131 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Шестой проход КВС — сортировка CSV по адресам
- Сортирует все записи по числовому значению адреса
- Записи с адресом "BSS" помещает в конец (после всех реальных адресов)
- Обеспечивает правильный порядок байтов в итоговом ELF-файле
"""
import sys
import csv
def get_addr_value(addr_str):
"""
Преобразует адрес в число для сортировки
BSS и пустые адреса отправляются в конец (большое число)
"""
if not addr_str or addr_str == '':
return 999999999 # Пустые адреса в конец
if addr_str == 'BSS':
return 999999999 # BSS в самый конец (после всех реальных адресов)
if addr_str.startswith('0x'):
try:
return int(addr_str, 16)
except ValueError:
return 999999999
return 999999999
def sort_csv_by_address(input_csv, output_csv):
"""
Сортирует CSV по адресам (числовым значениям)
BSS и пустые адреса помещаются в конец
Args:
input_csv: входной CSV файл
output_csv: выходной CSV файл
"""
print(f"\n=== ШЕСТОЙ ПРОХОД КВС: сортировка CSV ===")
print(f"Чтение: {input_csv}")
# Читаем CSV
with open(input_csv, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f, delimiter=';')
fieldnames = reader.fieldnames
rows = list(reader)
print(f"Найдено записей: {len(rows)}")
# Подсчёт статистики до сортировки
bss_count = sum(1 for row in rows if row.get('адрес') == 'BSS')
empty_count = sum(1 for row in rows if not row.get('адрес') or row.get('адрес') == '')
normal_count = len(rows) - bss_count - empty_count
print(f" Обычных записей (с числовыми адресами): {normal_count}")
print(f" BSS-записей: {bss_count}")
print(f" Пустых адресов: {empty_count}")
# Сортировка
def sort_key(row):
addr = row.get('адрес', '')
value = get_addr_value(addr)
return (value, 0)
rows.sort(key=sort_key)
# Записываем отсортированный CSV
with open(output_csv, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';', restval='')
writer.writeheader()
writer.writerows(rows)
print(f"\n✅ CSV отсортирован и сохранён: {output_csv}")
# Показываем первые 10 адресов для проверки
print("\n📊 Первые 10 адресов после сортировки:")
for i, row in enumerate(rows[:10]):
addr = row.get('адрес', '')
seg = row.get('сегмент', '')
source = row.get('исходная_команда', '')[:30]
print(f" {i+1:2d}. {addr:12s} [{seg:10s}] {source}")
# Показываем последние 5 адресов (должны быть BSS)
if bss_count > 0:
print("\n📊 Последние 5 записей (BSS в конце):")
for i, row in enumerate(rows[-5:]):
addr = row.get('адрес', '')
seg = row.get('сегмент', '')
source = row.get('исходная_команда', '')[:30]
print(f" {len(rows)-5+i+1:2d}. {addr:12s} [{seg:10s}] {source}")
# Проверка: убеждаемся, что после сортировки нет числовых адресов после BSS
found_bss = False
for row in rows:
addr = row.get('адрес', '')
if addr == 'BSS':
found_bss = True
elif found_bss and addr and addr != '' and addr.startswith('0x'):
print(f"\n⚠️ ВНИМАНИЕ: Найден числовой адрес {addr} после BSS!")
print(" Это может указывать на проблему в данных.")
break
else:
if found_bss:
print("\n✅ Проверка пройдена: все BSS-записи в конце, числовых адресов после них нет.")
else:
print("\n✅ BSS-записей не найдено.")
def main():
if len(sys.argv) not in (2, 3):
print("Использование: python kvs_pass6.py <входной.csv> [выходной.csv]")
print(" Если выходной не указан, перезаписывается входной файл")
sys.exit(1)
input_csv = sys.argv[1]
# Определяем выходной файл
if len(sys.argv) >= 3:
output_csv = sys.argv[2]
else:
# По умолчанию: перезаписываем исходный файл
output_csv = input_csv
sort_csv_by_address(input_csv, output_csv)
if __name__ == "__main__":
main()

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""квс_сборка.py
Главный сборочный скрипт КВС
Последовательно запускает все этапы компиляции
"""
@ -11,7 +11,7 @@ import subprocess
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Использование: python kvs_build.py <файл.квс>")
print("Использование: python квс_сборка.py <файл.квс>")
sys.exit(1)
source_file = sys.argv[1]
@ -23,17 +23,19 @@ if __name__ == "__main__":
tokens_file = base_name + ".токены"
ast_file = base_name + ".аст"
pass1_file = base_name + ".проход1"
csv_file = base_name + ".csv"
elf_file = base_name + ".elf"
stages = [
("kvs_lexer.py", [source_file, tokens_file], "Лексический анализ"),
("kvs_parser.py", [tokens_file, ast_file], "Парсинг"),
("kvs_pass1.py", [ast_file, pass1_file], "Первый проход"),
("kvs_pass2.py", [ast_file, pass1_file, csv_file], "Второй проход и генерация CSV"),
("kvs_verify.py", [pass1_file, csv_file], "Проверка размеров инструкций"),
("kvs_builder.py", [csv_file, pass1_file, elf_file], "Сборка ELF"),
("квс_лексер.py", [source_file, tokens_file], "Лексический анализ"),
("квс_парсер.py", [tokens_file, ast_file], "Парсинг"),
("квс_проход1.py", [ast_file, csv_file], "Первый проход: генерация кода и CSV"),
("квс_проход2.py", [csv_file], "2 проход"),
("квс_проход3.py", [ast_file, csv_file], "3 проход"),
("квс_проход4.py", [csv_file], "4 проход"),
("квс_проход5.py", [csv_file], "5 проход"),
("квс_проход6.py", [csv_file], "6 проход"),
("квс_финсбор.py", [csv_file, elf_file], "Сборка ELF"),
]
for script, args, description in stages:
@ -48,10 +50,9 @@ if __name__ == "__main__":
print(result.stdout)
sys.exit(1)
# Для верификатора показываем stdout (там детали проверки)
if result.stdout:
print(result.stdout)
print(f"\n=== Компиляция успешно завершена ===")
print(f"Исполняемый файл: {elf_file}")
print(f"Запустить: ./{elf_file}")
#print(f"\n=== Компиляция успешно завершена ===")
#print(f"Исполняемый файл: {elf_file}")
#print(f"Запустить: ./{elf_file}")

104
квс_финсбор.py Normal file
View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
квс_финсбор.py - Сборка ELF из CSV таблицы
Читает CSV, берёт колонки 'адрес' и 'рассчитанный_байт',
заполняет пропуски в адресах нулями и записывает выходной файл
"""
import sys
import csv
import os
import stat
def read_csv(csv_file):
"""Читает CSV и возвращает список адресов и байтов"""
addresses = []
bytes_list = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.reader(f, delimiter=';')
headers = next(reader)
# Находим индексы нужных колонок
try:
addr_idx = headers.index('адрес')
byte_idx = headers.index('рассчитанный_байт')
except ValueError as e:
print(f"Ошибка: не найдена нужная колонка в CSV: {e}")
sys.exit(1)
for row in reader:
addr_str = row[addr_idx].strip()
byte_str = row[byte_idx].strip()
if addr_str and addr_str.startswith('0x'):
addr = int(addr_str, 16)
addresses.append(addr)
if byte_str and byte_str.startswith('0x'):
bytes_list.append(int(byte_str, 16))
else:
bytes_list.append(0)
return addresses, bytes_list
def build_file(addresses, bytes_list, output_file):
"""Собирает файл, заполняя пропуски в адресах нулями"""
if not addresses:
print("Нет данных для сборки")
return False
# Определяем размер файла (максимальный адрес + 1)
max_addr = max(addresses)
file_size = max_addr + 1
# Создаём буфер, заполненный нулями
buffer = bytearray(file_size)
# Заполняем байты по известным адресам
for addr, byte_val in zip(addresses, bytes_list):
buffer[addr] = byte_val
# Записываем файл
with open(output_file, 'wb') as f:
f.write(buffer)
# Добавляем права на исполнение (chmod +x)
st = os.stat(output_file)
os.chmod(output_file, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
return True
def main():
if len(sys.argv) != 3:
print("Использование: python kvs_builder.py <входной.csv> <выходной.elf>")
sys.exit(1)
csv_file = sys.argv[1]
elf_file = sys.argv[2]
print(f"\n=== Сборка ELF из CSV ===")
print(f"Чтение CSV: {csv_file}")
addresses, bytes_list = read_csv(csv_file)
print(f"Найдено {len(addresses)} записей")
if build_file(addresses, bytes_list, elf_file):
print(f"\n=== Сборка завершена ===")
print(f" Записей в CSV: {len(addresses)}")
print(f" Размер файла: {max(addresses) + 1} байт (0x{max(addresses) + 1:x})")
print(f" Выходной файл: {elf_file}")
print(f"\n Исполняемый файл готов!")
print(f" Запустить: {elf_file}")
else:
print("Ошибка при сборке")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,149 @@
#!/bin/bash
# Сравнение ELF-файлов побайтно
# Сохраняет отчёт в файл
# Нужен для проверки совместимости нового компилятора с 8й версией
if [ $# -ne 2 ]; then
echo "Использование: $0 <файл1.elf> <файл2.elf>"
exit 1
fi
FILE1="$1"
FILE2="$2"
OUTPUT="compare_$(basename "$1")_$(basename "$2").txt"
echo "Сравнение: $FILE1 vs $FILE2"
echo "Отчёт: $OUTPUT"
# Заголовок отчёта
{
echo "========================================"
echo "ПОБАЙТНОЕ СРАВНЕНИЕ ELF-ФАЙЛОВ"
echo "========================================"
echo "Файл 1: $FILE1"
echo "Файл 2: $FILE2"
echo "Время: $(date)"
echo "========================================"
echo ""
# Размеры файлов
SIZE1=$(stat -c %s "$FILE1")
SIZE2=$(stat -c %s "$FILE2")
echo "Размер файла 1: $SIZE1 байт"
echo "Размер файла 2: $SIZE2 байт"
echo ""
if [ "$SIZE1" -ne "$SIZE2" ]; then
echo "ВНИМАНИЕ: Размеры файлов отличаются!"
echo ""
fi
# Побайтное сравнение
echo "ПОБАЙТНОЕ СРАВНЕНИЕ:"
echo "--------------"
# Используем cmp для побайтного сравнения
cmp -l "$FILE1" "$FILE2" 2>/dev/null | while read offset byte1 byte2; do
echo "Смещение 0x$(printf "%08x" $((offset-1))): байт1=0x$byte1, байт2=0x$byte2"
done
DIFF_COUNT=$(cmp -l "$FILE1" "$FILE2" 2>/dev/null | wc -l)
if [ "$DIFF_COUNT" -eq 0 ]; then
echo "Файлы идентичны!"
else
echo ""
echo "Всего различий: $DIFF_COUNT"
fi
echo ""
echo "========================================"
echo "СРАВНЕНИЕ ELF-ЗАГОЛОВКОВ"
echo "========================================"
# Сравнение ELF-заголовков
echo ""
echo "--- readelf -h ---"
echo "=== $FILE1 ===" >> /tmp/elf1_header.txt
readelf -h "$FILE1" 2>/dev/null >> /tmp/elf1_header.txt
echo "=== $FILE2 ===" >> /tmp/elf2_header.txt
readelf -h "$FILE2" 2>/dev/null >> /tmp/elf2_header.txt
diff /tmp/elf1_header.txt /tmp/elf2_header.txt 2>/dev/null || echo "ELF-заголовки отличаются"
rm -f /tmp/elf1_header.txt /tmp/elf2_header.txt
echo ""
echo "--- readelf -l (программные заголовки) ---"
echo "=== $FILE1 ===" >> /tmp/elf1_ph.txt
readelf -l "$FILE1" 2>/dev/null >> /tmp/elf1_ph.txt
echo "=== $FILE2 ===" >> /tmp/elf2_ph.txt
readelf -l "$FILE2" 2>/dev/null >> /tmp/elf2_ph.txt
diff /tmp/elf1_ph.txt /tmp/elf2_ph.txt 2>/dev/null || echo "Программные заголовки отличаются"
rm -f /tmp/elf1_ph.txt /tmp/elf2_ph.txt
echo ""
echo "--- readelf -S (секции) ---"
echo "=== $FILE1 ===" >> /tmp/elf1_sec.txt
readelf -S "$FILE1" 2>/dev/null >> /tmp/elf1_sec.txt
echo "=== $FILE2 ===" >> /tmp/elf2_sec.txt
readelf -S "$FILE2" 2>/dev/null >> /tmp/elf2_sec.txt
diff /tmp/elf1_sec.txt /tmp/elf2_sec.txt 2>/dev/null || echo "Секции отличаются"
rm -f /tmp/elf1_sec.txt /tmp/elf2_sec.txt
echo ""
echo "========================================"
echo "HEX-ДАМП ПЕРВЫХ 256 БАЙТ"
echo "========================================"
echo ""
echo "=== $FILE1 ==="
hexdump -C "$FILE1" -n 256
echo ""
echo "=== $FILE2 ==="
hexdump -C "$FILE2" -n 256
echo ""
echo "========================================"
echo "HEX-ДАМП ОБЛАСТИ ПРОГРАММНЫХ ЗАГОЛОВКОВ (0x40-0xe8)"
echo "========================================"
echo ""
echo "=== $FILE1 ==="
hexdump -C "$FILE1" -s 0x40 -n 168
echo ""
echo "=== $FILE2 ==="
hexdump -C "$FILE2" -s 0x40 -n 168
echo ""
echo "========================================"
echo "HEX-ДАМП ОБЛАСТИ ТАБЛИЦЫ СЕКЦИЙ (0x2040-0x2140)"
echo "========================================"
echo ""
echo "=== $FILE1 ==="
hexdump -C "$FILE1" -s 0x2040 -n 256 2>/dev/null || echo "Нет данных по адресу 0x2040"
echo ""
echo "=== $FILE2 ==="
hexdump -C "$FILE2" -s 0x2040 -n 256 2>/dev/null || echo "Нет данных по адресу 0x2040"
echo ""
echo "========================================"
echo "СРАВНЕНИЕ ВЫПОЛНЕНИЯ"
echo "========================================"
echo "=== Выполнение $FILE1 ==="
./"$FILE1"
echo "Код возврата: $?"
echo "=== Выполнение $FILE2 ==="
./"$FILE2"
echo "Код возврата: $?"
} > "$OUTPUT"
echo "Отчёт сохранён в: $OUTPUT"
# Показать количество различий
DIFF_COUNT=$(cmp -l "$FILE1" "$FILE2" 2>/dev/null | wc -l)
if [ "$DIFF_COUNT" -eq 0 ]; then
echo "✅ Файлы идентичны!"
else
echo "❌ Найдено $DIFF_COUNT различий"
fi

View file

@ -1,136 +0,0 @@
## Исходник
```asm
.текст
.глобал _start
_start:
; ТЕСТ 1: Абсолютная адресация в .data
переместить_имм раикс, 0x42
сохранить [абс_данные], раикс ; запись 0x42 по адресу абс_данные (0x402000)
; ТЕСТ 2: Косвенная адресация в .data
переместить_имм рбикс, косв_данные ; рбикс = адрес косв_данные (0x402001)
переместить_имм раикс, 0x99
сохранить [рбикс], раикс ; запись 0x99 по адресу 0x402001
; ТЕСТ 3: Косвенная адресация в .бнд
переместить_имм рсикс, неиниц_бнд ; рсикс = адрес неиниц_бнд
переместить_имм раикс, 0x77
сохранить [рсикс], раикс ; запись 0x77 по адресу неиниц_бнд
; Читаем всё обратно
загрузить р8, [абс_данные] ; чтение из 0x402000
загрузить р9, [рбикс] ; чтение из 0x402001
загрузить р10, [рсикс] ; чтение из неиниц_бнд
; ЗАВЕРШЕНИЕ: возвращаем значение из абс_данные
загрузить рдиай, [абс_данные] ; рдиай = значение из 0x402000
переместить_имм раикс, 60 ; номер системного вызова exit
вызов_системы
.данные
абс_данные:
.байт 0
косв_данные:
.байт 0
.бнд
неиниц_бнд:
.резб 8
```
## Размещение секций в памяти
```
0x401000 ─ .text (код)
0x402000 ─ .data
├── абс_данные (смещение 0): байт 0
└── косв_данные (смещение 1): байт 0
0x403000 ─ .бнд
└── неиниц_бнд (смещение 0): 8 байт, ноль
```
## Что подразумевается
1. **Тест 1** пишет `0x42` в `абс_данные` по адресу `0x402000`.
2. **Тест 2** пишет `0x99` в `косв_данные` по адресу `0x402001`.
3. **Тест 3** пишет `0x77` в `неиниц_бнд` по адресу **`0x403000`**.
После этого:
- `[0x402000]` содержит `0x42`
- `[0x402001]` содержит `0x99`
- `[0x403000]` содержит `0x77`
Финал читает `[абс_данные]` = `[0x402000]` = `0x42` в `рдиай` и завершается.
**Ожидаемый код возврата:** `66` (это `0x42`).
## Что происходит на самом деле
**Фактический код возврата:** `119` (это `0x77`).
## Причина
Инструкция `переместить_имм рсикс, неиниц_бнд` загружает в `рсикс` **неправильный адрес**. Вместо `0x403000` (адрес `.бнд`) регистр получает `0x402002` (адрес внутри `.data`).
Это происходит потому, что при вычислении адреса метки `неиниц_бнд` компилятор ошибочно использует базовый адрес секции `.data` (`0x402000`) вместо базового адреса секции `.бнд` (`0x403000`).
Смещение метки `неиниц_бнд` внутри `.бнд` равно `0`. Правильный расчёт должен быть: `0x403000 + 0 = 0x403000`. Но компилятор считает: `0x402000 + 0 = 0x402000`.
## Цепочка последствий
1. `рсикс` получает `0x402000` вместо `0x403000`.
2. `сохранить [рсикс], раикс` пишет `0x77` по адресу `0x402000` — **это ячейка `абс_данные`**.
3. Значение `0x42`, записанное в Тесте 1, **затирается** значением `0x77`.
4. Финал читает `[абс_данные]` и получает `0x77` вместо `0x42`.
5. Код возврата становится `119` вместо `66`.
## Как проверить
Если закомментировать Тест 3 (строки с `рсикс` и `неиниц_бнд`), код возврата станет `66` — баг исчезает, потому что `0x402000` не перезаписывается.
`vaddr_data` считается в **двух** местах:
### 1. `kvs_pass1.py` — метод `calculate_layout`
```python
vaddr_text = text_vaddr_base # 0x401000
vaddr_data = align_up(vaddr_text + text_size, PAGE_SIZE) # 0x402000
vaddr_bnd = align_up(vaddr_data + data_size, PAGE_SIZE) # 0x402000 или 0x403000
```
Эти значения записываются в pass1-файл как `PARAM:vaddr_text:...`, `PARAM:vaddr_data:...`, `PARAM:vaddr_bnd:...`.
### 2. `kvs_pass2.py` — конструктор `Pass2.__init__`
```python
self.vaddr_text = pass1_data["vaddr_text"] # читает из pass1
self.vaddr_data = align_up(self.vaddr_text + text_size, PAGE_SIZE) # вычисляет заново
```
**Pass2 не читает `vaddr_data` из pass1, а вычисляет сам.** А `vaddr_bnd` не читает и не вычисляет вообще.
### 3. `kvs_builder.py` — функция `create_elf`
```python
vaddr_data = align_up(vaddr_text + text_size, PAGE_SIZE) # вычисляет заново
vaddr_bnd = pass1_data.get("vaddr_bnd", ...) # читает из pass1
```
**Builder читает `vaddr_bnd` из pass1**, но энкодер (pass2) его не получает.
---
## Корень бага
| Модуль | `vaddr_data` | `vaddr_bnd` |
|--------|:-----------:|:----------:|
| pass1 | вычисляет ✅ | вычисляет ✅ |
| pass2 | вычисляет ✅ | **не читает** ❌ |
| builder | вычисляет ✅ | читает из pass1 ✅ |
| encoder (mutsize) | получает от pass2 ✅ | **не получает** ❌ |
Pass2 не передаёт `vaddr_bnd` в энкодер, поэтому `parse_operand` получает `vaddr_bnd=None` и подставляет `vaddr_data` для меток `.бнд`.

View file

@ -1,53 +0,0 @@
.текст
.глобал _start
_start:
; =============================================
; ТЕСТ 1: Абсолютная адресация в .data
; =============================================
переместить_имм раикс, 0x42
сохранить [абс_данные], раикс
; =============================================
; ТЕСТ 2: Косвенная адресация в .data
; =============================================
переместить_имм рбикс, косв_данные
переместить_имм раикс, 0x99
сохранить [рбикс], раикс
; =============================================
; ТЕСТ 3: Косвенная адресация в .бнд
; =============================================
переместить_имм рсикс, неиниц_бнд
переместить_имм раикс, 0x77
сохранить [рсикс], раикс
; =============================================
; Читаем всё обратно и проверяем
; =============================================
; Читаем абсолютную из .data
загрузить р8, [абс_данные]
; Читаем косвенную из .data
загрузить р9, [рбикс]
; Читаем косвенную из .бнд
загрузить р10, [рсикс]
; =============================================
; ЗАВЕРШЕНИЕ: возвращаем значение из абсолютной .data
; =============================================
загрузить рдиай, [абс_данные]
переместить_имм раикс, 60
вызов_системы
.данные
абс_данные:
.байт 0
косв_данные:
.байт 0
.бнд
неиниц_бнд:
.резб 8

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os
extensions = {'.аст', '.csv', '.токены', '.проход1', '.elf'}
extensions = {'.аст', '.csv', '.токены', '.проход1', '.elf', '.log', '.txt'}
for file in os.listdir('.'):
if os.path.isfile(file) and os.path.splitext(file)[1] in extensions:

View file

@ -1,67 +0,0 @@
.текст
.глобал _start
_start:
; Число для преобразования
переместить_имм раикс, 73
; Сохраняем число в .бнд
сохранить [число], раикс
; Загружаем обратно
загрузить раикс, [число]
; Делим на 10
переместить_имм рдикс, 0
переместить_имм рсикс, 10
разделить рсикс ; rax = 7 (частное), rdx = 3 (остаток)
; Сохраняем остаток в .бнд
сохранить [остаток], рдикс
; Частное (rax) — в цифру и выводим
прибавить_непосредственно раикс, 0x30
сохранить [буфер], раикс
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, буфер
переместить_имм рдикс, 1
вызов_системы
; Загружаем остаток из .бнд
загрузить раикс, [остаток]
; Остаток — в цифру и выводим
прибавить_непосредственно раикс, 0x30
сохранить [буфер], раикс
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, буфер
переместить_имм рдикс, 1
вызов_системы
; Перевод строки
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, nl
переместить_имм рдикс, 1
вызов_системы
; exit(0)
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.данные
буфер:
.байт 0, 0, 0, 0, 0, 0, 0, 0
nl:
.байт 0x0A
.бнд
число:
.резб 8
остаток:
.резб 8

View file

@ -1,80 +0,0 @@
.текст
.глобал _start
_start:
; Конвертируем 1034
переместить_имм раикс, 1034
; 1234 / 10 = 123, остаток 4
переместить_имм рдикс, 0
переместить_имм рсикс, 10
разделить рсикс
прибавить_непосредственно рдикс, 0x30
сохранить [цифра3], рдикс
; 123 / 10 = 12, остаток 3
переместить_имм рдикс, 0
разделить рсикс
прибавить_непосредственно рдикс, 0x30
сохранить [цифра2], рдикс
; 12 / 10 = 1, остаток 2
переместить_имм рдикс, 0
разделить рсикс
прибавить_непосредственно рдикс, 0x30
сохранить [цифра1], рдикс
; Частное 1 → последняя цифра
прибавить_непосредственно раикс, 0x30
сохранить [цифра0], раикс
; Выводим 4 цифры по одной
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра0
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра1
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра2
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра3
переместить_имм рдикс, 1
вызов_системы
; Перевод строки
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, nl
переместить_имм рдикс, 1
вызов_системы
; exit(0)
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.данные
nl:
.байт 0x0A
.бнд
цифра0:
.резб 8
цифра1:
.резб 8
цифра2:
.резб 8
цифра3:
.резб 8

View file

@ -1,94 +0,0 @@
.текст
.глобал _start
_start:
; МЕНЯЙ ЧИСЛО:
переместить_имм раикс, 57321
; 1-й разряд (единицы): число / 10
переместить_имм рдикс, 0
переместить_имм рсикс, 10
разделить рсикс ; rax = 5432, rdx = 1
прибавить_непосредственно рдикс, 0x30
сохранить [цифра4], рдикс
; 2-й разряд (десятки): частное / 10
переместить_имм рдикс, 0
разделить рсикс ; rax = 543, rdx = 2
прибавить_непосредственно рдикс, 0x30
сохранить [цифра3], рдикс
; 3-й разряд (сотни)
переместить_имм рдикс, 0
разделить рсикс ; rax = 54, rdx = 3
прибавить_непосредственно рдикс, 0x30
сохранить [цифра2], рдикс
; 4-й разряд (тысячи)
переместить_имм рдикс, 0
разделить рсикс ; rax = 5, rdx = 4
прибавить_непосредственно рдикс, 0x30
сохранить [цифра1], рдикс
; 5-й разряд (десятки тысяч)
прибавить_непосредственно раикс, 0x30
сохранить [цифра0], раикс
; Выводим 5 цифр по одной
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра0
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра1
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра2
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра3
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра4
переместить_имм рдикс, 1
вызов_системы
; Перевод строки
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, nl
переместить_имм рдикс, 1
вызов_системы
; exit(0)
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.данные
nl:
.байт 0x0A
.бнд
цифра0:
.резб 8
цифра1:
.резб 8
цифра2:
.резб 8
цифра3:
.резб 8
цифра4:
.резб 8

View file

@ -1,94 +0,0 @@
.текст
.глобал _start
_start:
; МЕНЯЙ ЧИСЛО:
переместить_имм раикс, 59321
; 1-й разряд (единицы): число / 10
переместить_имм рдикс, 0
переместить_имм рсикс, 10
разделить рсикс ; rax = 5432, rdx = 1
прибавить_непосредственно рдикс, 0x30
сохранить [цифра4], рдикс
; 2-й разряд (десятки): частное / 10
переместить_имм рдикс, 0
разделить рсикс ; rax = 543, rdx = 2
прибавить_непосредственно рдикс, 0x30
сохранить [цифра3], рдикс
; 3-й разряд (сотни)
переместить_имм рдикс, 0
разделить рсикс ; rax = 54, rdx = 3
прибавить_непосредственно рдикс, 0x30
сохранить [цифра2], рдикс
; 4-й разряд (тысячи)
переместить_имм рдикс, 0
разделить рсикс ; rax = 5, rdx = 4
прибавить_непосредственно рдикс, 0x30
сохранить [цифра1], рдикс
; 5-й разряд (десятки тысяч)
прибавить_непосредственно раикс, 0x30
сохранить [цифра0], раикс
; Выводим 5 цифр по одной
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра0
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра1
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра2
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра3
переместить_имм рдикс, 1
вызов_системы
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, цифра4
переместить_имм рдикс, 1
вызов_системы
; Перевод строки
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, nl
переместить_имм рдикс, 1
вызов_системы
; exit(0)
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.данные
nl:
.байт 0x0A
.бнд
цифра0:
.резб 8
цифра1:
.резб 8
цифра2:
.резб 8
цифра3:
.резб 8
цифра4:
.резб 8

View file

@ -0,0 +1,28 @@
активен;папка;описание
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
1;тест_бнд_1;Проверка BSS: абсолютная адресация (запись/чтение)
1;тест_бнд_2;Проверка BSS: косвенная адресация через регистр (запись/чтение)
1;тест_бнд_3;Проверка BSS: смешанная адресация (абсолютная запись + косвенное чтение)
1;сложение;Тест сложения 40+2=42 (проверка через код возврата)
0;сложение+;Тест сложения 40+2=42 (проверка через код возврата+)
1;вычитание;Тест вычитания 50-8=42
1;тест_адресации_р9;Проверка косвенной адресации через R9 (ASCII код первого символа аргумента)
1;тест_адресации_р10;Проверка косвенной адресации через R10 (ASCII код первого символа аргумента)
1;тест_адресации_р11;Проверка косвенной адресации через R11 (ASCII код первого символа аргумента)
1;тест_адресации_р13;Проверка косвенной адресации через R13 (ASCII код первого символа аргумента)
1;тест_адресации_р14;Проверка косвенной адресации через R14 (ASCII код первого символа аргумента)
1;тест_адресации_р15;Проверка косвенной адресации через R15 (ASCII код первого символа аргумента)
1;двоеточие_в_строке;Проверка обработки строк с двоеточием внутри
1;тест_адресации_р12;Проверка косвенной адресации через R12 (ASCII код первого символа аргумента)
1;вызов_функции;Проверка вызова функций и возврата: сложение 10+20=30 через код возврата
1;остаток_от_деления;Проверка деления и получения остатка: 42 % 10 = 2 (проверка через код возврата)
1;вывод_однозначного;Проверка вывода однозначного числа: цифра 7 → символ '7' через write
1;вывод_многозначного;Проверка вывода многозначного числа: 12345 → "12345" через деление, стек и циклы
Can't render this file because it contains an unexpected character in line 2 and column 46.

View file

@ -1,337 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тестовый скрипт для проверки инструкций с фиксированной длиной
Запуск: python3 test_fixed_instructions.py [--keep-files]
"""
import sys
import os
import subprocess
import csv
# ANSI color codes
GREEN = '\033[92m'
RED = '\033[91m'
YELLOW = '\033[93m'
RESET = '\033[0m'
# === ТЕСТОВЫЙ ИСХОДНИК ===
TEST_SOURCE = '''.глобал _start
.текст
_start:
нет_операции
вызов_системы
вернуться
втолкнуть раикс
втолкнуть р8
вытолкнуть раикс
вытолкнуть р8
переместить раикс, рбикс
переместить р8, р9
сравнить раикс, рбикс
сравнить р8, р9
проверить раикс, рбикс
проверить р8, р9
прибавить раикс, рбикс
прибавить р8, р9
вычесть раикс, рбикс
вычесть р8, р9
и раикс, рбикс
и р8, р9
или раикс, рбикс
или р8, р9
исключающее_или раикс, рбикс
исключающее_или р8, р9
увеличить раикс
увеличить р8
увеличить еаикс
уменьшить раикс
уменьшить р8
уменьшить еаикс
инвертировать раикс
инвертировать еаикс
отрицать раикс
отрицать еаикс
переместить_имм раикс, 10
переместить_имм рбикс, 5
умножить рбикс
переместить_имм раикс, 100
переместить_имм рбикс, 7
разделить рбикс
умножить_знаковое рбикс
разделить_знаковое рбикс
сдвиг_влево раикс, 2
сдвиг_вправо раикс, 2
сдвиг_арифметический_вправо раикс, 2
вращать_влево раикс, 2
вращать_вправо раикс, 2
переход метка_длинная
короткий_переход метка_короткая
цикл метка_короткая
вызвать метка_длинная
метка_длинная:
нет_операции
нет_операции
нет_операции
метка_короткая:
нет_операции
переместить_имм раикс, 60
переместить_имм рдикс, 0
вызов_системы
'''
# === ОЖИДАЕМЫЕ РЕЗУЛЬТАТЫ ===
# Для инструкций с фиксированным кодом — полная проверка
EXPECTED_INSTRUCTIONS_FULL = {
"нет_операции": [0x90],
"вызов_системы": [0x0F, 0x05],
"вернуться": [0xC3],
"втолкнуть раикс": [0x50],
"втолкнуть р8": [0x41, 0x50],
"вытолкнуть раикс": [0x58],
"вытолкнуть р8": [0x41, 0x58],
"переместить раикс, рбикс": [0x48, 0x89, 0xD8],
"переместить р8, р9": [0x4D, 0x89, 0xC8],
"сравнить раикс, рбикс": [0x48, 0x39, 0xD8],
"сравнить р8, р9": [0x4D, 0x39, 0xC8],
"проверить раикс, рбикс": [0x48, 0x85, 0xD8],
"проверить р8, р9": [0x4D, 0x85, 0xC8],
"прибавить раикс, рбикс": [0x48, 0x01, 0xD8],
"прибавить р8, р9": [0x4D, 0x01, 0xC8],
"вычесть раикс, рбикс": [0x48, 0x29, 0xD8],
"вычесть р8, р9": [0x4D, 0x29, 0xC8],
"и раикс, рбикс": [0x48, 0x21, 0xD8],
"и р8, р9": [0x4D, 0x21, 0xC8],
"или раикс, рбикс": [0x48, 0x09, 0xD8],
"или р8, р9": [0x4D, 0x09, 0xC8],
"исключающее_или раикс, рбикс": [0x48, 0x31, 0xD8],
"исключающее_или р8, р9": [0x4D, 0x31, 0xC8],
"увеличить раикс": [0x48, 0xFF, 0xC0],
"увеличить р8": [0x49, 0xFF, 0xC0],
"увеличить еаикс": [0xFF, 0xC0],
"уменьшить раикс": [0x48, 0xFF, 0xC8],
"уменьшить р8": [0x49, 0xFF, 0xC8],
"уменьшить еаикс": [0xFF, 0xC8],
"инвертировать раикс": [0x48, 0xF7, 0xD0],
"инвертировать еаикс": [0xF7, 0xD0],
"отрицать раикс": [0x48, 0xF7, 0xD8],
"отрицать еаикс": [0xF7, 0xD8],
"умножить рбикс": [0x48, 0xF7, 0xE0],
"разделить рбикс": [0x48, 0xF7, 0xF0],
"умножить_знаковое рбикс": [0x48, 0xF7, 0xE8],
"разделить_знаковое рбикс": [0x48, 0xF7, 0xF8],
"сдвиг_влево раикс, 2": [0x48, 0xC1, 0xE0, 0x02],
"сдвиг_вправо раикс, 2": [0x48, 0xC1, 0xE8, 0x02],
"сдвиг_арифметический_вправо раикс, 2": [0x48, 0xC1, 0xF8, 0x02],
"вращать_влево раикс, 2": [0x48, 0xC1, 0xC0, 0x02],
"вращать_вправо раикс, 2": [0x48, 0xC1, 0xC8, 0x02],
"переместить_имм раикс, 10": [0x48, 0xB8, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
"переместить_имм рбикс, 5": [0x48, 0xBB, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
"переместить_имм раикс, 100": [0x48, 0xB8, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
"переместить_имм рбикс, 7": [0x48, 0xBB, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
"переместить_имм раикс, 60": [0x48, 0xB8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
"переместить_имм рдикс, 0": [0x48, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
# Инструкции, у которых проверяем только opcode (первые байты)
EXPECTED_INSTRUCTIONS_PREFIX = {
"переход метка_длинная": [0xE9], # JMP rel32
"короткий_переход метка_короткая": [0xEB], # JMP SHORT
"цикл метка_короткая": [0xE2], # LOOP
"вызвать метка_длинная": [0xE8], # CALL rel32
}
def read_csv_instructions(csv_file):
"""Читает CSV и возвращает словарь {мнемоника: [байты]}"""
instructions = {}
current_mnemonic = None
current_bytes = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.reader(f, delimiter=';')
header = next(reader)
for row in reader:
if len(row) < 4:
continue
cmd = row[3].strip()
byte_str = row[1].strip()
if not byte_str:
continue
byte_val = int(byte_str, 16)
if cmd:
if current_mnemonic and current_bytes:
instructions[current_mnemonic] = current_bytes
current_mnemonic = cmd
current_bytes = [byte_val]
else:
if current_mnemonic:
current_bytes.append(byte_val)
if current_mnemonic and current_bytes:
instructions[current_mnemonic] = current_bytes
return instructions
def run_tests(generated_instructions):
"""Сравнивает сгенерированные инструкции с ожидаемыми"""
errors = []
missing = []
# Проверяем инструкции с полным совпадением
for mnemonic, expected_bytes in EXPECTED_INSTRUCTIONS_FULL.items():
if mnemonic not in generated_instructions:
missing.append(mnemonic)
continue
actual_bytes = generated_instructions[mnemonic]
if actual_bytes != expected_bytes:
expected_hex = ' '.join(f'{b:02X}' for b in expected_bytes)
actual_hex = ' '.join(f'{b:02X}' for b in actual_bytes)
errors.append(f" {mnemonic}: ожидалось [{expected_hex}], получено [{actual_hex}]")
# Проверяем инструкции только по префиксу
for mnemonic, expected_prefix in EXPECTED_INSTRUCTIONS_PREFIX.items():
if mnemonic not in generated_instructions:
missing.append(mnemonic)
continue
actual_bytes = generated_instructions[mnemonic]
prefix_len = len(expected_prefix)
if len(actual_bytes) < prefix_len:
errors.append(f" {mnemonic}: слишком короткая ({len(actual_bytes)} байт, ожидалось минимум {prefix_len})")
elif actual_bytes[:prefix_len] != expected_prefix:
expected_hex = ' '.join(f'{b:02X}' for b in expected_prefix)
actual_hex = ' '.join(f'{b:02X}' for b in actual_bytes[:prefix_len])
errors.append(f" {mnemonic}: префикс [{expected_hex}...], получено [{actual_hex}...]")
return errors, missing
def cleanup(files_to_delete, keep_files=False):
"""Удаляет временные файлы"""
if keep_files:
print("\nФлаг --keep-files указан, промежуточные файлы сохранены:")
for f in files_to_delete:
if os.path.exists(f):
print(f" {f}")
return
deleted = []
for f in files_to_delete:
if os.path.exists(f):
os.remove(f)
deleted.append(f)
if deleted:
print(f"\nУдалено {len(deleted)} временных файлов")
def main():
keep_files = '--keep-files' in sys.argv or '-k' in sys.argv
base_name = "т1"
source_file = f"{base_name}.квс"
tokens_file = f"{base_name}.токены"
ast_file = f"{base_name}.аст"
p1_file = f"{base_name}.проход1"
csv_file = f"{base_name}.csv"
elf_file = f"{base_name}.elf"
temp_files = [tokens_file, ast_file, p1_file, csv_file, elf_file]
print("КВС Тестировщик - Инструкции с фиксированной длиной\n")
print("=" * 60)
# Шаг 1: Создание исходного файла
print("Шаг 1: Создание тестового исходника...")
with open(source_file, 'w', encoding='utf-8') as f:
f.write(TEST_SOURCE)
print(f" Создан {source_file}")
# Шаг 2: Компиляция
print("\nШаг 2: Компиляция...")
result = subprocess.run(
[sys.executable, "kvs_build.py", source_file],
capture_output=True,
text=True
)
if result.returncode != 0:
print(" ОШИБКА компиляции:")
print(result.stderr)
print("\nОставшиеся файлы для отладки:")
print(f" {source_file}")
if os.path.exists(csv_file):
print(f" {csv_file}")
sys.exit(1)
print(" Компиляция успешна")
# Шаг 3: Проверка результатов
print("\nШаг 3: Проверка сгенерированного кода...")
if not os.path.exists(csv_file):
print(f" Файл {csv_file} не найден!")
sys.exit(1)
generated_instructions = read_csv_instructions(csv_file)
errors, missing = run_tests(generated_instructions)
# Вывод результатов
print("\n" + "=" * 60)
print("РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ:")
print("=" * 60)
if missing:
print(f"\n{YELLOW}ОТСУТСТВУЮТ ИНСТРУКЦИИ ({len(missing)}):{RESET}")
for m in missing[:10]:
print(f" - {m}")
if len(missing) > 10:
print(f" ... и ещё {len(missing)-10}")
if errors:
print(f"\n{RED}НАЙДЕНО {len(errors)} ОШИБОК В КОДЕ:{RESET}")
for err in errors:
print(err)
success = False
else:
print(f"\n{GREEN}НЕТ ОШИБОК! ВСЕ ИНСТРУКЦИИ ЗАКОДИРОВАНЫ ВЕРНО!{RESET}")
success = True
# Статистика
print(f"\nСтатистика:")
print(f" Ожидалось инструкций (полная проверка): {len(EXPECTED_INSTRUCTIONS_FULL)}")
print(f" Ожидалось инструкций (проверка префикса): {len(EXPECTED_INSTRUCTIONS_PREFIX)}")
print(f" Сгенерировано инструкций: {len(generated_instructions)}")
# Шаг 4: Очистка
cleanup([source_file] + temp_files, keep_files)
# Финальный вердикт
print("\n" + "=" * 60)
if success:
print(f"{GREEN}ТЕСТЫ ПРОЙДЕНЫ УСПЕШНО!{RESET}")
sys.exit(0)
else:
print(f"{RED}ТЕСТЫ НЕ ПРОЙДЕНЫ!{RESET}")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -1,9 +0,0 @@
.текст
.глобал _start
_start:
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.бнд
буфер: .резб 1048576 ; 1 МБ = 1024 * 1024

View file

@ -1,45 +0,0 @@
.текст
.глобал _start
_start:
; =============================================
; ТЕСТ 1: Абсолютная адресация в .бнд
; =============================================
переместить_имм раикс, 0x42
сохранить [абс_перем], раикс
; =============================================
; ТЕСТ 2: Косвенная адресация в .бнд
; =============================================
переместить_имм рбикс, косв_перем
переместить_имм раикс, 0x99
сохранить [рбикс], раикс
; =============================================
; ТЕСТ 3: Ещё одна косвенная адресация в .бнд
; =============================================
переместить_имм рсикс, ещё_перем
переместить_имм раикс, 0x77
сохранить [рсикс], раикс
; =============================================
; Читаем всё обратно
; =============================================
загрузить р8, [абс_перем]
загрузить р9, [рбикс]
загрузить р10, [рсикс]
; =============================================
; ЗАВЕРШЕНИЕ: возвращаем из абс_перем
; =============================================
загрузить рдиай, [абс_перем]
переместить_имм раикс, 60
вызов_системы
.бнд
абс_перем:
.резб 1
косв_перем:
.резб 1
ещё_перем:
.резб 8

View file

@ -1,15 +0,0 @@
.текст
.глобал _start
_start:
; Запись значения 42 в переменную по метке
переместить_имм раикс, 42
сохранить [переменная], раикс
; Завершение с кодом 42
переместить_имм раикс, 60
загрузить рдиай, [переменная]
вызов_системы
.данные
переменная: .байт 0

View file

@ -1,34 +0,0 @@
.текст
.глобал _start
_start:
; === ТЕСТ 1: Запись через косвенную адресацию ===
; Загружаем адрес переменной в регистр
переместить_имм рбикс, тестовая_переменная
; Записываем 42 через косвенную адресацию [рбикс]
переместить_имм раикс, 42
сохранить [рбикс], раикс
; === ТЕСТ 2: Чтение через косвенную адресацию ===
; Загружаем адрес другой переменной в регистр
переместить_имм рсикс, вторая_переменная
; Читаем через косвенную адресацию [рсикс]
загрузить рдикс, [рсикс]
; === ТЕСТ 3: Проверяем, что записалось ===
; Читаем первую переменную через косвенную
загрузить рсикс, [рбикс]
; === ЗАВЕРШЕНИЕ ===
; Выходим с кодом из первой переменной (должно быть 42)
переместить_имм раикс, 60
переместить рдиай, рсикс
вызов_системы
.данные
тестовая_переменная:
.байт 0
вторая_переменная:
.байт 99

View file

@ -1,50 +0,0 @@
.текст
.глобал _start
_start:
; =============================================
; ТЕСТ 1: Абсолютная адресация
; =============================================
переместить_имм раикс, 0x42
сохранить [абс_данные], раикс
; =============================================
; ТЕСТ 2: Косвенная адресация
; =============================================
переместить_имм рбикс, косв_данные
переместить_имм раикс, 0x99
сохранить [рбикс], раикс
; =============================================
; ТЕСТ 3: Ещё одна косвенная адресация
; =============================================
переместить_имм рсикс, ещё_данные
переместить_имм раикс, 0x77
сохранить [рсикс], раикс
; =============================================
; Читаем всё обратно
; =============================================
загрузить р8, [абс_данные]
загрузить р9, [рбикс]
загрузить р10, [рсикс]
; =============================================
; ЗАВЕРШЕНИЕ: возвращаем из абсолютной переменной
; =============================================
загрузить рдиай, [абс_данные]
переместить_имм раикс, 60
вызов_системы
.данные
абс_данные:
.байт 0
косв_данные:
.байт 0
ещё_данные:
.байт 0, 0, 0, 0, 0, 0, 0, 0
.бнд
буфер: .резб 1048576 ; 1 МБ = 1024 * 1024
переменная: .резкс 1 ; 8 байт под длину
буфер_строки: .резкс 1 ; 8 байт под указатель на строку

View file

@ -1,28 +0,0 @@
.текст
.глобал _start
_start:
; 25 - длина сообщения из метки msg
; Запись значения 25 в переменную по метке
переместить_имм рдикс, 25
сохранить [переменная], рдикс
; перепишем для теста
переместить_имм рдикс, 0
; Системный вызов write(1, msg, len)
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, msg
загрузить рдикс, [переменная]
вызов_системы
; Системный вызов exit()
переместить_имм раикс, 60 ; номер syscall exit = 60
переместить_имм рдиай, 44 ; код возврата (старый метод)
вызов_системы
.данные
msg: .строка "Тест пройден!\n" ; не менять их местами, чтоб не затереть текст
переменная: .байт 0 ; тут либо резервировать пустые, либо .bss изобретать (или кучу?)

View file

@ -1,30 +0,0 @@
.текст
.глобал _start
_start:
; 25 - длина сообщения из метки msg
; Запись значения 25 в переменную по метке
переместить_имм рдикс, 25
сохранить [переменная], рдикс
; перепишем для теста
переместить_имм рдикс, 0
; Системный вызов write(1, msg, len)
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, msg
загрузить рдикс, [переменная]
вызов_системы
; Системный вызов exit()
переместить_имм раикс, 60 ; номер syscall exit = 60
переместить_имм рдиай, 44 ; код возврата (старый метод)
вызов_системы
.данные
msg: .строка "Тест пройден!\n" ; строка остаётся в .данные
.бнд
переменная: .резкс 1 ; 8 байт под qword (как и было .байт 0)

View file

@ -1,36 +0,0 @@
.текст
.глобал _start
_start:
; 25 - длина сообщения из метки msg
; Запись значения 25 в переменную в .бнд
переместить_имм рдикс, 25
сохранить [переменная], рдикс
; Загружаем адрес строки и сохраняем в буфер в .бнд
загрузить_адрес раикс, [msg]
сохранить [буфер_строки], раикс
; перепишем для теста
переместить_имм рдикс, 0
; Системный вызов write(1, msg, len)
переместить_имм раикс, 1
переместить_имм рдиай, 1
загрузить рсиай, [буфер_строки]
загрузить рдикс, [переменная]
вызов_системы
; Системный вызов exit()
переместить_имм раикс, 60
переместить_имм рдиай, 44
вызов_системы
.данные
msg: .строка "Тест пройден!\n"
.бнд
переменная: .резкс 1 ; 8 байт под длину
буфер_строки: .резкс 1 ; 8 байт под указатель на строку

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", "квс_сборка.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,44 @@
.текст
.глобал _start
_start:
вытолкнуть рсикс
сравнить_с рсикс, 2
переход_если_меньше no_arg
вытолкнуть рсиай
вытолкнуть р8 ; argv[1] в R8
переместить рдиай, р8
исключающее_или рдикс, рдикс
strlen:
загрузить ал, [р8]
сравнить_с ал, 0
переход_если_равно strlen_done
увеличить р8
увеличить рдикс
переход strlen
strlen_done:
переместить_имм раикс, 1
переместить рсиай, рдиай
переместить_имм рдиай, 1
вызов_системы
исключающее_или рдиай, рдиай
переместить_имм раикс, 60
вызов_системы
no_arg:
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, msg_no_arg
переместить_имм рдикс, 51
вызов_системы
переместить_имм рдиай, 1
переместить_имм раикс, 60
вызов_системы
.данные
msg_no_arg:
.строка "Ошибка не передан аргумент\n"

View file

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки вывода многозначного числа
Преобразует число 12345 в строку через деление на 10 и стек
Ожидаемый вывод: "12345"
"""
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()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""Собирает тестовую программу с помощью КВС"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_output() -> bool:
"""Запускает программу и проверяет вывод"""
print("DEBUG: Запуск ELF, ожидается вывод '12345'", 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 = "12345"
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
if run.returncode != 0:
print(f"❌ Код возврата: ожидался 0, получен {run.returncode}", file=sys.stderr)
return False
print(f"DEBUG: Тест пройден (вывод '{run.stdout}', код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста вывод_многозначного", file=sys.stderr)
if not build():
clean()
sys.exit(1)
if not test_output():
clean()
sys.exit(1)
clean()
print("✅ Все тесты пройдены!", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,81 @@
;./тесты/вывод_многозначного/вывод_многозначного.elf
; Ожидаемый вывод: "12345"
; ============================================================
; вывод_многозначного.квс - вывод многозначного числа
; Преобразует число 12345 в строку через деление на 10 и стек
; ============================================================
.текст
.глобал _start
_start:
; Загружаем число для вывода
переместить_имм раикс, 12345 ; число для преобразования
; Счётчик цифр = 0
переместить_имм р8, 0 ; счётчик цифр в R8
цикл_деления:
; Проверяем, не ноль ли остался
сравнить_с раикс, 0
переход_если_равно вывод_цифр ; если 0 — всё, выводим
; Обнуляем RDX перед делением (старшая часть делимого)
переместить_имм рдикс, 0
; Делим на 10: RAX = частное, RDX = остаток
переместить_имм рбикс, 10
разделить рбикс ; RAX/RBX → RAX(частное), RDX(остаток)
; Преобразуем остаток в ASCII: остаток + '0'
прибавить_непосредственно рдикс, 48 ; RDX = цифра + 48 = ASCII-код
; Сохраняем цифру в стеке
втолкнуть рдикс
; Увеличиваем счётчик
увеличить р8
; Продолжаем цикл (RAX уже содержит частное)
короткий_переход цикл_деления
вывод_цифр:
; Теперь в стеке цифры в обратном порядке, счётчик в R8
; Указатель на буфер в R9
переместить_имм р9, буфер
цикл_вывода:
; Проверяем, все ли цифры вытащили
сравнить_с р8, 0
переход_если_равно готово
; Достаём цифру из стека
вытолкнуть раикс
; Записываем байт в буфер
сохранить [р9], ал
; Сдвигаем указатель буфера
увеличить р9
; Уменьшаем счётчик
уменьшить р8
короткий_переход цикл_вывода
готово:
; Системный вызов write(1, буфер, длина)
переместить_имм раикс, 1 ; номер syscall write = 1
переместить_имм рдиай, 1 ; stdout = 1
переместить_имм рсиай, буфер ; указатель на буфер
переместить_имм рдикс, 5 ; длина = 5 (для 12345)
вызов_системы
; Системный вызов exit(0)
переместить_имм раикс, 60 ; номер syscall exit = 60
переместить_имм рдиай, 0 ; код возврата = 0
вызов_системы
.данные
буфер: .байт 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; 10 байт с запасом

View file

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки вывода однозначного числа
Преобразует цифру 7 в символ '7' и выводит через write
"""
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()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""Собирает тестовую программу с помощью КВС"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_output() -> bool:
"""Запускает программу и проверяет вывод"""
print("DEBUG: Запуск ELF, ожидается вывод '7'", 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 = "7"
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
if run.returncode != 0:
print(f"❌ Код возврата: ожидался 0, получен {run.returncode}", file=sys.stderr)
return False
print(f"DEBUG: Тест пройден (вывод '{run.stdout}', код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста вывод_однозначного", file=sys.stderr)
if not build():
clean()
sys.exit(1)
if not test_output():
clean()
sys.exit(1)
clean()
print("✅ Все тесты пройдены!", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,36 @@
;./тесты/вывод_однозначного/вывод_однозначного.elf
; Ожидаемый вывод: "7"
; ============================================================
; вывод_однозначного.квс - вывод однозначного числа без циклов
; Преобразует цифру 7 в символ '7' через сложение с '0'
; и выводит в терминал
; ============================================================
.текст
.глобал _start
_start:
; Загружаем цифру для вывода
переместить_имм раикс, 7 ; цифра 7
; Преобразуем цифру в ASCII-символ: '0' = 48
прибавить_непосредственно раикс, 48 ; RAX = 7 + 48 = 55 = '7'
; Сохраняем символ в буфер
сохранить [буфер], ал ; записываем байт '7' в буфер
; Системный вызов write(1, буфер, 1)
переместить_имм раикс, 1 ; номер syscall write = 1
переместить_имм рдиай, 1 ; stdout = 1
переместить_имм рсиай, буфер ; указатель на буфер
переместить_имм рдикс, 1 ; длина = 1 байт
вызов_системы
; Системный вызов exit(0)
переместить_имм раикс, 60 ; номер syscall exit = 60
переместить_имм рдиай, 0 ; код возврата = 0
вызов_системы
.данные
буфер: .байт 0

View file

@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки вызова функций и возврата
Запускает скомпилированную программу и проверяет код возврата (должен быть 30 = 10+20)
"""
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()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_call_return() -> bool:
"""
Запускает скомпилированную программу и проверяет код возврата
Returns:
bool: True если код возврата = 30 (10+20), иначе False
"""
print("DEBUG: Запуск ELF, ожидается код возврата 30", 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_code = 30 # 10 + 20
if run.returncode != expected_code:
print(f"❌ Код возврата: ожидался {expected_code}, получен {run.returncode}", file=sys.stderr)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста вызов_функции", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Запускаем тест
if not test_call_return():
clean()
sys.exit(1)
# Очищаем артефакты
clean()
print("✅ Все тесты пройдены!", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,37 @@
;./тесты/вызов_функции/вызов_функции.elf
;echo $? # должен быть 30
; ============================================================
; вызов_функции.квс - тест вызова функций и возврата
; ============================================================
.текст
.глобал _start
_start:
; Вызов функции с двумя аргументами
переместить_имм рдиай, 10 ; первый аргумент = 10
переместить_имм рсиай, 20 ; второй аргумент = 20
вызвать сложить_два_числа
; Результат в RAX — используем как код возврата
переместить рдиай, раикс ; первый аргумент sys_exit = результат
переместить_имм раикс, 60 ; sys_exit
вызов_системы
сложить_два_числа:
; Пролог
втолкнуть рбипи
переместить рбипи, рсипи
; Тело функции: сложить аргументы
прибавить рдиай, рсиай ; RDI += RSI
переместить раикс, рдиай ; результат в RAX
; Эпилог
переместить рсипи, рбипи
вытолкнуть рбипи
вернуться
.данные
тестовая_переменная: .байт 0

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python3
# тесты/вычитание/вычитание.py
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", "квс_сборка.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)
sys.exit(1)
print("DEBUG: Сборка успешна", file=sys.stderr)
# 2. Переходим в папку теста
os.chdir("тесты/вычитание")
# 3. Проверка ELF
if not os.path.exists("вычитание.elf"):
print("❌ Файл вычитание.elf не создан", file=sys.stderr)
clean()
sys.exit(1)
# 4. Запуск
print("DEBUG: Запуск ELF", file=sys.stderr)
run = subprocess.run(["./вычитание.elf"], capture_output=True, text=True)
print(f"DEBUG: Код возврата = {run.returncode}", file=sys.stderr)
# 5. Проверка (50 - 12 = 38)
expected = 38
if run.returncode == expected:
print(f"✅ Тест пройден: 50 - 12 = {run.returncode}", file=sys.stderr)
clean()
sys.exit(0)
else:
print(f"❌ Тест не пройден: ожидалось {expected}, получено {run.returncode}", file=sys.stderr)
clean()
sys.exit(1)
if __name__ == "__main__":
main()
#1;вычитание;Тест вычитания 50-8=42
#1;вычитание_отрицательное;Тест вычитания с отрицательным результатом
#1;вычитание_память;Тест вычитания через память
#1;вычитание_цепочка;Тест последовательного вычитания

View file

@ -0,0 +1,22 @@
; тесты/вычитание/вычитание.квс
; Тест вычитания: 50 - 8 = 42
.текст
.глобал _start
_start:
; Загружаем числа
переместить_имм раикс, 50
переместить_имм рбикс, 12
; Вычитаем: раикс = раикс - рбикс
вычесть раикс, рбикс
; Результат (42) в RDI для exit
переместить рдиай, раикс
; Выход
переместить_имм раикс, 60
вызов_системы
.данные
_пусто: .байт 0

View file

@ -0,0 +1,90 @@
#!/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", "квс_сборка.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)
# Дополнительная диагностика
if "Приве " in run.stdout and " мир!\n" not in run.stdout:
print(f" 💡 Похоже, строка обрезана на пробеле или двоеточии", file=sys.stderr)
elif "Приве" in run.stdout and "мир" not in run.stdout:
print(f" 💡 Похоже, строка обрезана на двоеточии", 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,18 @@
.текст
.глобал _start
_start:
; Системный вызов write(1, msg, len)
переместить_имм раикс, 1 ; номер syscall write = 1
переместить_имм рдиай, 1 ; stdout = 1
переместить_имм рсиай, msg ; указатель на строку
переместить_имм рдикс, 21 ; длина строки
вызов_системы
; Системный вызов exit(0)
переместить_имм раикс, 60 ; номер syscall exit = 60
переместить_имм рдиай, 0 ; код возврата
вызов_системы
.данные
msg: .строка "Приве : мир!\n"

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", "квс_сборка.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

@ -1,73 +0,0 @@
.текст
.глобал _start
_start:
; Тест: длина числа 1234
переместить_имм раикс, 1234
вызвать длина_числа ; rax = количество цифр
; Выводим результат как цифру
прибавить_непосредственно раикс, 0x30
сохранить [буфер], раикс
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, буфер
переместить_имм рдикс, 1
вызов_системы
; Перевод строки
переместить_имм раикс, 1
переместить_имм рдиай, 1
переместить_имм рсиай, nl
переместить_имм рдикс, 1
вызов_системы
; exit(0)
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
; =============================================
; длина_числа: количество десятичных цифр в rax
; Вход: rax = число
; Выход: rax = количество цифр (1..20)
; =============================================
длина_числа:
втолкнуть р8
втолкнуть р9
переместить р8, раикс ; р8 = число
переместить_имм р9, 0 ; счётчик = 0
; Проверка на 0
сравнить_с р8, 0
короткий_переход_если_неравно длина_цикл
переместить_имм раикс, 1 ; 0 — это 1 цифра
вытолкнуть р9
вытолкнуть р8
вернуться
длина_цикл:
увеличить р9 ; счётчик++
; Делим на 10
переместить раикс, р8
переместить_имм рдикс, 0
переместить_имм рсикс, 10
разделить рсикс
переместить р8, раикс ; частное
сравнить_с р8, 0
короткий_переход_если_неравно длина_цикл
переместить раикс, р9 ; результат
вытолкнуть р9
вытолкнуть р8
вернуться
.данные
буфер:
.байт 0, 0, 0, 0, 0, 0, 0, 0
nl:
.байт 0x0A

View file

@ -0,0 +1,117 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки операции деления и получения остатка
Запускает скомпилированную программу и проверяет код возврата
Ожидаемый результат: 42 % 10 = 2
"""
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()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_division_remainder() -> bool:
"""
Запускает скомпилированную программу и проверяет код возврата
42 % 10 = 2
Returns:
bool: True если код возврата = 2, иначе False
"""
print("DEBUG: Запуск ELF, ожидается код возврата 2", 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_code = 2 # 42 % 10 = 2
if run.returncode != expected_code:
print(f"❌ Код возврата: ожидался {expected_code}, получен {run.returncode}", file=sys.stderr)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста остаток_от_деления", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Запускаем тест
if not test_division_remainder():
clean()
sys.exit(1)
# Очищаем артефакты
clean()
print("✅ Все тесты пройдены!", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,35 @@
;./тесты/остаток_от_деления/остаток_от_деления.elf
;echo $? # должен быть 2
;для вывода чисел в терминал нужно преобразование в строку
;а для этого нужен остаток от деления (но тут надо бы больше случаев отработать)
; ============================================================
; остаток_от_деления.квс - тест операции деления и остатка
; Проверяем: 42 / 10 = 4 остаток 2
; ============================================================
.текст
.глобал _start
_start:
; Подготовка делимого: RDX:RAX = 0:42
переместить_имм раикс, 42 ; делимое = 42
переместить_имм рдикс, 0 ; обнуляем RDX (старшая часть 128-битного делимого)
; Делитель в регистр
переместить_имм рбикс, 10 ; делитель = 10
; Выполняем деление: RDX:RAX / RBX
; После деления: RAX = частное (4), RDX = остаток (2)
разделить рбикс
; Перемещаем остаток в RDI для кода возврата
переместить рдиай, рдикс ; остаток из RDX в RDI
; Системный вызов exit
переместить_имм раикс, 60 ; номер syscall exit = 60
вызов_системы
.данные
тестовая_переменная: .байт 0

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", "квс_сборка.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,107 @@
#!/usr/bin/env python3
import subprocess
import sys
import csv
from pathlib import Path
def main():
# Диспетчер лежит в тесты/прогон_тестов.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,86 @@
#!/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)
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def main():
print("DEBUG: Начало теста сложение", file=sys.stderr)
# Сохраняем текущую директорию
original_dir = os.getcwd()
# Переходим в корень проекта для сборки
os.chdir(PROJECT_ROOT)
print(f"DEBUG: Рабочая папка = {os.getcwd()}", file=sys.stderr)
# 1. Сборка (запускаем из корня)
print("DEBUG: Сборка сложение.квс", file=sys.stderr)
build = subprocess.run(["python3", "квс_сборка.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("тесты/сложение")
print(f"DEBUG: Запуск в папке {os.getcwd()}", file=sys.stderr)
# 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)
print(f"DEBUG: stderr = {repr(run.stderr)}", file=sys.stderr)
# 4. Проверка результата (42)
expected_code = 42
if run.returncode != expected_code:
print(f"❌ Код возврата не совпадает", file=sys.stderr)
print(f" Ожидалось: {expected_code}", file=sys.stderr)
print(f" Получено: {run.returncode}", file=sys.stderr)
clean()
sys.exit(1)
# Успех
print(f"✅ Тест пройден: {run.returncode} (40+2=42)", file=sys.stderr)
clean()
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,26 @@
; Тест операции сложения (прибавить)
; Результат должен быть 42
.текст
.глобал _start
_start:
; Загружаем первое число в регистр
переместить_имм раикс, 40
; Загружаем второе число в другой регистр
переместить_имм рбикс, 2
; Складываем: раикс = раикс + рбикс (40 + 2)
прибавить раикс, рбикс
; Результат (42) теперь в RAX
; Перемещаем результат в RDI (первый аргумент для exit)
переместить рдиай, раикс
; Системный вызов exit (код 60)
переместить_имм раикс, 60
вызов_системы
.данные
тестовая_переменная: .байт 0

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", "квс_сборка.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

@ -1,66 +0,0 @@
.глобал _start
.текст
_start:
нет_операции
вызов_системы
вернуться
втолкнуть раикс
втолкнуть р8
вытолкнуть раикс
вытолкнуть р8
переместить раикс, рбикс
переместить р8, р9
сравнить раикс, рбикс
сравнить р8, р9
проверить раикс, рбикс
проверить р8, р9
прибавить раикс, рбикс
прибавить р8, р9
вычесть раикс, рбикс
вычесть р8, р9
и раикс, рбикс
и р8, р9
или раикс, рбикс
или р8, р9
исключающее_или раикс, рбикс
исключающее_или р8, р9
увеличить раикс
увеличить р8
увеличить еаикс
уменьшить раикс
уменьшить р8
уменьшить еаикс
инвертировать раикс
инвертировать еаикс
отрицать раикс
отрицать еаикс
переместить_имм раикс, 10
переместить_имм рбикс, 5
умножить рбикс
переместить_имм раикс, 100
переместить_имм рбикс, 7
разделить рбикс
умножить_знаковое рбикс
разделить_знаковое рбикс
сдвиг_влево раикс, 2
сдвиг_вправо раикс, 2
сдвиг_арифметический_вправо раикс, 2
вращать_влево раикс, 2
вращать_вправо раикс, 2
переход метка_длинная
короткий_переход метка_короткая
цикл метка_короткая
вызвать метка_длинная
метка_длинная:
нет_операции
нет_операции
нет_операции
метка_короткая:
нет_операции
переместить_имм раикс, 60
переместить_имм рдикс, 0
вызов_системы

View file

@ -1,112 +0,0 @@
.глобал _start
.текст
_start:
; ===== ГРУППА 1: Без операндов (реализованные) =====
нет_операции
вызов_системы
вернуться
; ===== ГРУППА 2: PUSH/POP =====
втолкнуть раикс
втолкнуть р8
вытолкнуть раикс
вытолкнуть р8
; ===== ГРУППА 3: Регистр-регистр =====
переместить раикс, рбикс
переместить р8, р9
сравнить раикс, рбикс
сравнить р8, р9
проверить раикс, рбикс
проверить р8, р9
прибавить раикс, рбикс
прибавить р8, р9
вычесть раикс, рбикс
вычесть р8, р9
и раикс, рбикс
и р8, р9
или раикс, рбикс
или р8, р9
исключающее_или раикс, рбикс
исключающее_или р8, р9
; ===== ГРУППА 4: INC/DEC =====
увеличить раикс
увеличить р8
увеличить еаикс
уменьшить раикс
уменьшить р8
уменьшить еаикс
; ===== ГРУППА 5: NOT/NEG =====
инвертировать раикс
инвертировать еаикс
отрицать раикс
отрицать еаикс
; ===== ГРУППА 6: MUL/DIV =====
переместить_имм раикс, 10
переместить_имм рбикс, 5
умножить рбикс
переместить_имм раикс, 100
переместить_имм рбикс, 7
разделить рбикс
умножить_знаковое рбикс
разделить_знаковое рбикс
; ===== ГРУППА 7: Битовые операции =====
сдвиг_влево раикс, 2
сдвиг_вправо раикс, 2
сдвиг_арифметический_вправо раикс, 2
вращать_влево раикс, 2
вращать_вправо раикс, 2
; ===== ГРУППА 8: Переходы =====
переход метка_длинная
переход_если_равно метка_длинная
переход_если_неравно метка_длинная
переход_если_меньше метка_длинная
переход_если_больше метка_длинная
переход_если_меньше_или_равно метка_длинная
переход_если_больше_или_равно метка_длинная
переход_если_перенос метка_длинная
переход_если_нет_переноса метка_длинная
короткий_переход метка_короткая
короткий_переход_если_равно метка_короткая
короткий_переход_если_неравно метка_короткая
короткий_переход_если_меньше метка_короткая
короткий_переход_если_больше метка_короткая
короткий_переход_если_меньше_или_равно метка_короткая
короткий_переход_если_больше_или_равно метка_короткая
короткий_переход_если_перенос метка_короткая
короткий_переход_если_нет_переноса метка_короткая
цикл метка_короткая
вызвать метка_длинная
метка_длинная:
нет_операции
нет_операции
нет_операции
метка_короткая:
нет_операции
; Выход
переместить_имм раикс, 60
переместить_имм рдикс, 0
вызов_системы

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", "квс_сборка.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

@ -1,3 +1,8 @@
; тестирует абсолютную адресацию по адресу
; не вылетел - значит работает (дополнительно проверить отладчиком)
; ./тесты/тест_адресации1.elf
; echo $? # выведет 42
.текст
.глобал _start

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", "квс_сборка.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,20 @@
; тестирует абсолютную адресацию по метке
; не вылетел - значит работает (дополнительно проверить отладчиком)
; ./тесты/тест_адресации2.elf
; echo $? # выведет 43
.текст
.глобал _start
_start:
; Запись значения 43 в переменную по метке
переместить_имм раикс, 43
сохранить [переменная], раикс
; Завершение с кодом 43
переместить_имм раикс, 60
загрузить рдиай, [переменная]
вызов_системы
.данные
переменная: .байт 0

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", "квс_сборка.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,23 @@
; тестирует косвенную адресацию через регистр
; не вылетел - значит работает (дополнительно проверить отладчиком)
; ./тесты/тест_адресации3.elf
; echo $? # выведет 99
.текст
.глобал _start
_start:
; Загружаем адрес переменной в регистр RBX
переместить_имм рбикс, переменная
; Запись значения 99 в переменную через косвенную адресацию
переместить_имм раикс, 99
сохранить [рбикс], раикс
; Завершение с кодом 99 (читаем из переменной через косвенную адресацию)
переместить_имм раикс, 60
загрузить рдиай, [рбикс]
вызов_системы
.данные
переменная: .байт 0

View file

@ -0,0 +1,140 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R10
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р10.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р10.elf",
"тест_адресации_р10.csv",
"тест_адресации_р10.аст",
"тест_адресации_р10.токены",
"тест_адресации_р10.константа",
"тест_адресации_р10_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р10.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р10", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65), # Латинская A
('B', 66), # Латинская B
('0', 48), # Цифра 0
('z', 122), # Латинская z
('!', 33), # Восклицательный знак
('x', 120), # Латинская x
('5', 53), # Цифра 5
('_', 95), # Подчёркивание
('$', 36), # Знак доллара
('@', 64), # Собака
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р10.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r10.квс - тест косвенной адресации через R10
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р10 ; argv[1] в R10
; Загружаем первый байт из argv[1] через R10
загрузить ал, [р10]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

View file

@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R11
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р11.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р11.elf",
"тест_адресации_р11.csv",
"тест_адресации_р11.аст",
"тест_адресации_р11.токены",
"тест_адресации_р11.константа",
"тест_адресации_р11_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р11.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р11", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65), # Латинская A
('B', 66), # Латинская B
('0', 48), # Цифра 0
('z', 122), # Латинская z
('!', 33), # Восклицательный знак
('x', 120), # Латинская x
('5', 53), # Цифра 5
('_', 95), # Подчёркивание
('$', 36), # Знак доллара
('@', 64), # Собака
('#', 35), # Решётка
('*', 42), # Звёздочка
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р11.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r11.квс - тест косвенной адресации через R11
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р11 ; argv[1] в R11
; Загружаем первый байт из argv[1] через R11
загрузить ал, [р11]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

View file

@ -0,0 +1,145 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R12
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р12.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р12.elf",
"тест_адресации_р12.csv",
"тест_адресации_р12.аст",
"тест_адресации_р12.токены",
"тест_адресации_р12.константа",
"тест_адресации_р12_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р12.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р12", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65), # Латинская A
('B', 66), # Латинская B
('0', 48), # Цифра 0
('z', 122), # Латинская z
('!', 33), # Восклицательный знак
('x', 120), # Латинская x
('5', 53), # Цифра 5
('_', 95), # Подчёркивание
('$', 36), # Знак доллара
('@', 64), # Собака
('#', 35), # Решётка
('*', 42), # Звёздочка
('~', 126), # Тильда
(' ', 32), # Пробел
('\t', 9), # Табуляция (может не сработать из-за экранирования)
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р12.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r12.квс - тест косвенной адресации через R12
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р12 ; argv[1] в R12
; Загружаем первый байт из argv[1] через R12
загрузить ал, [р12]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

View file

@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R13
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р13.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р13.elf",
"тест_адресации_р13.csv",
"тест_адресации_р13.аст",
"тест_адресации_р13.токены",
"тест_адресации_р13.константа",
"тест_адресации_р13_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р13.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р12", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65), # Латинская A
('B', 66), # Латинская B
('0', 48), # Цифра 0
('z', 122), # Латинская z
('!', 33), # Восклицательный знак
('x', 120), # Латинская x
('5', 53), # Цифра 5
('_', 95), # Подчёркивание
('$', 36), # Знак доллара
('@', 64), # Собака
('#', 35), # Решётка
('*', 42), # Звёздочка
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р13.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r13.квс - тест косвенной адресации через R13
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р13 ; argv[1] в R13
; Загружаем первый байт из argv[1] через R13
загрузить ал, [р13]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

View file

@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R14
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р14.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р14.elf",
"тест_адресации_р14.csv",
"тест_адресации_р14.аст",
"тест_адресации_р14.токены",
"тест_адресации_р14.константа",
"тест_адресации_р14_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р14.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р14", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65),
('B', 66),
('0', 48),
('z', 122),
('!', 33),
('x', 120),
('5', 53),
('_', 95),
('$', 36),
('@', 64),
('#', 35),
('*', 42),
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р14.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r14.квс - тест косвенной адресации через R14
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р14 ; argv[1] в R14
; Загружаем первый байт из argv[1] через R14
загрузить ал, [р14]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

View file

@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R15
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р15.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р15.elf",
"тест_адресации_р15.csv",
"тест_адресации_р15.аст",
"тест_адресации_р15.токены",
"тест_адресации_р15.константа",
"тест_адресации_р15_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р15.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р15", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65),
('B', 66),
('0', 48),
('z', 122),
('!', 33),
('x', 120),
('5', 53),
('_', 95),
('$', 36),
('@', 64),
('#', 35),
('*', 42),
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р15.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r15.квс - тест косвенной адресации через R15
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р15 ; argv[1] в R15
; Загружаем первый байт из argv[1] через R15
загрузить ал, [р15]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

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", "квс_сборка.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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р8.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r8.квс - тест косвенной адресации через R8
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р8 ; argv[1] в R8
; Загружаем первый байт из argv[1] через R8
загрузить ал, [р8]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

View file

@ -0,0 +1,138 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест для проверки косвенной адресации через регистр R9
Запускает скомпилированную программу с различными аргументами
и проверяет код возврата (должен быть равен ASCII коду первого символа)
"""
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 / "тест_адресации_р9.квс"
def clean():
"""Очищает артефакты сборки"""
files = [
"тест_адресации_р9.elf",
"тест_адресации_р9.csv",
"тест_адресации_р9.аст",
"тест_адресации_р9.токены",
"тест_адресации_р9.константа",
"тест_адресации_р9_pass4.log"
]
for f in files:
f_path = SCRIPT_DIR / f
if f_path.exists():
f_path.unlink()
print(f"DEBUG: Удалён {f}", file=sys.stderr)
def build() -> bool:
"""
Собирает тестовую программу с помощью КВС
Returns:
bool: True если сборка успешна, иначе False
"""
print(f"DEBUG: Сборка {SOURCE_REL}", file=sys.stderr)
os.chdir(PROJECT_ROOT)
build = subprocess.run(
["python3", "квс_сборка.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
print("DEBUG: Сборка успешна", file=sys.stderr)
return True
def test_with_char(char: str, expected_code: int) -> bool:
"""
Запускает скомпилированную программу с одним символом в качестве аргумента
и проверяет код возврата
Args:
char: символ для передачи в программу
expected_code: ожидаемый ASCII код символа
Returns:
bool: True если код возврата совпадает с ожидаемым, иначе False
"""
print(f"DEBUG: Тест с символом '{char}' (ожидается код {expected_code})", file=sys.stderr)
os.chdir(SCRIPT_DIR)
elf_path = SCRIPT_DIR / "тест_адресации_р9.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)
if run.stderr:
print(f" stderr: {run.stderr}", file=sys.stderr)
if run.stdout:
print(f" stdout: {run.stdout}", file=sys.stderr)
return False
print(f"DEBUG: Тест с символом '{char}' пройден (код {run.returncode})", file=sys.stderr)
return True
def main():
"""Главная функция теста"""
print("DEBUG: Начало теста тест_адресации_р9", file=sys.stderr)
# Собираем программу
if not build():
clean()
sys.exit(1)
# Тестируем разные символы
tests = [
('A', 65), # Латинская A
('B', 66), # Латинская B
('0', 48), # Цифра 0
('z', 122), # Латинская z
('!', 33), # Восклицательный знак
('x', 120), # Латинская x
('5', 53), # Цифра 5
('_', 95), # Подчёркивание
]
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

@ -0,0 +1,29 @@
;./тесты/тест_адресации_р9.elf "A"
;echo $? # должен быть 65
; ============================================================
; тест_адресации_r9.квс - тест косвенной адресации через R8
; ============================================================
.текст
.глобал _start
_start:
; Получаем argv[1]
вытолкнуть рсикс ; argc
вытолкнуть рсиай ; argv[0]
вытолкнуть р9 ; argv[1] в R9
; Загружаем первый байт из argv[1] через R9
загрузить ал, [р9]
; Сохраняем в переменную (не обязательно, но для проверки)
сохранить [байт], ал
; Выход с кодом = загруженный байт
переместить_имм раикс, 60
загрузить рдиай, [байт]
вызов_системы
.данные
байт: .байт 0

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", "квс_сборка.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

@ -0,0 +1,15 @@
; тестируем работу bss
; readelf -l ./тесты/тест_бнд.elf
.текст
.глобал _start
_start:
переместить_имм раикс, 60
переместить_имм рдиай, 0
вызов_системы
.данные
пустые_данные: .байт 0
.бнд
буфер: .резб 1048576 ; 1 МБ = 1024 * 1024

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", "квс_сборка.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_bss_absolute_addressing():
"""Проверка абсолютной адресации в BSS: запись → очистка → чтение"""
print("DEBUG: Тест BSS абсолютной адресации (запись/чтение)", 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)
# Ожидаемый код возврата = 0x42 = 66
expected_code = 66
if run.returncode != expected_code:
print(f"❌ Код возврата: ожидался {expected_code} (0x42), получен {run.returncode}", file=sys.stderr)
return False
print("DEBUG: BSS абсолютная адресация работает: записано и прочитано 0x42", file=sys.stderr)
return True
def main():
print("DEBUG: Начало теста тест_бнд_1 (BSS absolute addressing)", file=sys.stderr)
if not build():
clean()
sys.exit(1)
if test_bss_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,32 @@
; тест_бнд_1.квс
; Полный цикл: запись → очистка → чтение из BSS
.текст
.глобал _start
_start:
; =============================================
; ТЕСТ 1: Абсолютная адресация в .бнд (полный цикл)
; =============================================
; 1. Записываем значение в BSS
переместить_имм рдиай, 0x42
сохранить [абс_перем], рдиай
; 2. Очищаем регистр (чтобы проверить, что значение реально из BSS)
переместить_имм рдиай, 0x00
; 3. Загружаем значение обратно из BSS
загрузить рдиай, [абс_перем]
; 4. Выход с кодом = значению из BSS
переместить_имм раикс, 60
вызов_системы
.данные
; Заглушка для pass5 (обязательна!)
пустые_данные: .байт 0
.бнд
абс_перем: .резб 1
_запас: .резб 4095 ; гарантирует, что BSS внутри страницы

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", "квс_сборка.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_bss_indirect_addressing():
"""Проверка косвенной адресации в BSS: запись через регистр → очистка → чтение через регистр"""
print("DEBUG: Тест BSS косвенной адресации (запись/чтение через регистр)", 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)
# Ожидаемый код возврата = 0x99 = 153
expected_code = 153
if run.returncode != expected_code:
print(f"❌ Код возврата: ожидался {expected_code} (0x99), получен {run.returncode}", file=sys.stderr)
return False
print("DEBUG: BSS косвенная адресация работает: записано и прочитано 0x99", file=sys.stderr)
return True
def main():
print("DEBUG: Начало теста тест_бнд_2 (BSS indirect addressing)", file=sys.stderr)
if not build():
clean()
sys.exit(1)
if test_bss_indirect_addressing():
clean()
print("✅", file=sys.stderr)
sys.exit(0)
else:
clean()
print("❌", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

Some files were not shown because too many files have changed in this diff Show more