абсолютная адресация

This commit is contained in:
mzhoot 2026-05-11 15:08:03 +03:00
parent 520da3a8a3
commit eaef835f4d
13 changed files with 2017 additions and 1230 deletions

View file

@ -209,6 +209,14 @@
color: #ff8888; color: #ff8888;
} }
.success {
border-left: 3px solid #44ff44;
padding: 15px;
margin: 20px 0;
background: rgba(0, 40, 0, 0.2);
color: #88ff88;
}
.process-diagram { .process-diagram {
text-align: center; text-align: center;
margin: 30px 0; margin: 30px 0;
@ -221,6 +229,22 @@
overflow-x: auto; overflow-x: auto;
} }
.status-table td:first-child {
font-weight: bold;
}
.status-done {
color: #00ff00;
}
.status-partial {
color: #ffff00;
}
.status-pending {
color: #ff4444;
}
footer { footer {
margin-top: 40px; margin-top: 40px;
padding-top: 20px; padding-top: 20px;
@ -266,6 +290,16 @@
margin: 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) { @media (max-width: 768px) {
body { body {
padding: 10px; padding: 10px;
@ -330,6 +364,7 @@
</pre> </pre>
</div> </div>
<p>Версия 2.0 | Экспериментальный проект | 2025</p> <p>Версия 2.0 | Экспериментальный проект | 2025</p>
<p><span style="color: #00ff00;">✓ Абсолютная адресация реализована</span> | <span style="color: #ffff00;">⟳ Косвенная адресация в разработке</span></p>
</div> </div>
<h2>┌──[ ОГЛАВЛЕНИЕ ]────────────────────────────────────────────┐</h2> <h2>┌──[ ОГЛАВЛЕНИЕ ]────────────────────────────────────────────┐</h2>
@ -340,10 +375,13 @@
<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> <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> </ul>
<h2>└──────────────────────────────────────────────────────────────┘</h2> <h2>└──────────────────────────────────────────────────────────────┘</h2>
@ -367,8 +405,10 @@
<li><strong>Генерация исполняемых ELF-файлов</strong> — 64-битные ELF для Linux, готовые к запуску</li> <li><strong>Генерация исполняемых ELF-файлов</strong> — 64-битные ELF для Linux, готовые к запуску</li>
<li><strong>Двухпроходная сборка</strong> — традиционная схема с разрешением меток</li> <li><strong>Двухпроходная сборка</strong> — традиционная схема с разрешением меток</li>
<li><strong>Поддержка секций</strong> — разделение на <code>.текст</code> (код) и <code>.данные</code> (данные)</li> <li><strong>Поддержка секций</strong> — разделение на <code>.текст</code> (код) и <code>.данные</code> (данные)</li>
<li><strong>Абсолютная адресация памяти</strong> — чтение и запись по меткам <code>[переменная]</code> и абсолютным адресам <code>[0x402000]</code> с RIP-relative кодированием</li>
<li><strong>CSV-логирование</strong> — детальный файл <code>.csv</code> с соответствием адресов, байтов и исходных команд</li> <li><strong>CSV-логирование</strong> — детальный файл <code>.csv</code> с соответствием адресов, байтов и исходных команд</li>
<li><strong>Более 50 инструкций</strong> — полный набор основных команд x86-64</li> <li><strong>Более 50 инструкций</strong> — полный набор основных команд x86-64</li>
<li><strong>Рефакторинг</strong> — кодогенерация вынесена в отдельный модуль <code>kvs_pass2_encoder.py</code></li>
</ul> </ul>
<h2 id="архитектура">АРХИТЕКТУРА И ПРОЦЕСС СБОРКИ</h2> <h2 id="архитектура">АРХИТЕКТУРА И ПРОЦЕСС СБОРКИ</h2>
@ -384,7 +424,7 @@ AST-представление (.аст)
▼ (kvs_pass1.py) ▼ (kvs_pass1.py)
Первый проход: размеры, метки (.проход1) Первый проход: размеры, метки (.проход1)
│ │
▼ (kvs_pass2.py) ▼ (kvs_pass2.py + kvs_pass2_encoder.py)
Второй проход: генерация кода (.csv) Второй проход: генерация кода (.csv)
│ │
▼ (kvs_builder.py) ▼ (kvs_builder.py)
@ -393,6 +433,19 @@ ELF-компоновщик (.elf)
Исполняемый файл! Исполняемый файл!
</div> </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> <h2 id="установка">УСТАНОВКА И ЗАПУСК</h2>
<pre># Клонирование репозитория <pre># Клонирование репозитория
git clone https://github.com/username/kvs.git git clone https://github.com/username/kvs.git
@ -411,39 +464,78 @@ python3 kvs_build.py программа.квс
<pre>; Комментарий начинается с точки с запятой <pre>; Комментарий начинается с точки с запятой
.данные ; Начало секции данных .данные ; Начало секции данных
переменная: .байт 0 ; Переменная в памяти
message: .строка_нуль "Привет, мир!" message: .строка_нуль "Привет, мир!"
counter: .байт 0x0A
.текст ; Начало секции кода .текст ; Начало секции кода
.глобал _start ; Точка входа .глобал _start ; Точка входа
_start: _start:
; Аргументы для write(1, message, 13) ; Работа с памятью
переместить_имм раикс, 42
сохранить [переменная], раикс ; запись в память
загрузить рбикс, [переменная] ; чтение из памяти
; Системный вызов write
переместить_имм раикс, 1 переместить_имм раикс, 1
загрузить_адрес рсиай, message загрузить_адрес рсиай, message
переместить_имм рдикс, 13 переместить_имм рдикс, 13
вызов_системы вызов_системы
; Выход с кодом 0 ; Выход с кодом из переменной
переместить_имм раикс, 60 переместить_имм раикс, 60
переместить_имм рдикс, 0 загрузить рдиай, [переменная]
вызов_системы</pre> вызов_системы</pre>
<h2 id="команды">СИСТЕМА КОМАНД</h2> <h2 id="команды">СИСТЕМА КОМАНД</h2>
<table> <table>
<tr><th>Категория</th><th>Примеры инструкций</th><th>Соответствие NASM</th></tr> <tr><th>Категория</th><th>Примеры инструкций</th><th>Соответствие NASM</th></tr>
<tr><td>Перемещение данных</td><td><code>переместить</code>, <code>загрузить</code>, <code>сохранить</code>, <code>переместить_имм</code></td><td><code>mov</code>, <code>lea</code></td></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>, <code>увеличить</code>, <code>уменьшить</code></td><td><code>add</code>, <code>sub</code>, <code>mul</code>, <code>div</code>, <code>inc</code>, <code>dec</code></td></tr> <tr><td>Арифметика</td><td><code>прибавить</code>, <code>вычесть</code>, <code>умножить</code>, <code>разделить</code>, <code>увеличить</code>, <code>уменьшить</code></td><td><code>add</code>, <code>sub</code>, <code>mul</code>, <code>div</code>, <code>inc</code>, <code>dec</code></td></tr>
<tr><td>Логические</td><td><code>и</code>, <code>или</code>, <code>исключающее_или</code>, <code>инвертировать</code>, <code>отрицать</code></td><td><code>and</code>, <code>or</code>, <code>xor</code>, <code>not</code>, <code>neg</code></td></tr> <tr><td>Логические</td><td><code>и</code>, <code>или</code>, <code>исключающее_или</code>, <code>инвертировать</code>, <code>отрицать</code></td><td><code>and</code>, <code>or</code>, <code>xor</code>, <code>not</code>, <code>neg</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>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>je short</code></td></tr> <tr><td>Условные переходы</td><td><code>переход_если_равно</code>, <code>переход_если_больше</code>, <code>короткий_переход_если_равно</code></td><td><code>je</code>, <code>jg</code>, <code>je short</code></td><tr>
<tr><td>Безусловные переходы</td><td><code>переход</code>, <code>короткий_переход</code>, <code>вызвать</code>, <code>вернуться</code></td><td><code>jmp</code>, <code>jmp short</code>, <code>call</code>, <code>ret</code></td></tr> <tr><td>Безусловные переходы</td><td><code>переход</code>, <code>короткий_переход</code>, <code>вызвать</code>, <code>вернуться</code></td><td><code>jmp</code>, <code>jmp short</code>, <code>call</code>, <code>ret</code></td></tr>
<tr><td>Стек</td><td><code>втолкнуть</code>, <code>вытолкнуть</code>, <code>втолкнуть_непосредственно</code></td><td><code>push</code>, <code>pop</code>, <code>push imm</code></td></tr> <tr><td>Системные</td><td><code>вызов_системы</code>, <code>остановить</code></td><td><code>syscall</code>, <code>hlt</code></td></tr>
<tr><td>Сдвиги и вращения</td><td><code>сдвиг_влево</code>, <code>сдвиг_вправо</code>, <code>вращать_влево</code>, <code>вращать_вправо</code></td><td><code>shl</code>, <code>shr</code>, <code>rol</code>, <code>ror</code></td></tr>
<tr><td>Без операндов</td><td><code>нет_операции</code>, <code>вызов_системы</code>, <code>остановить</code>, <code>отладка</code></td><td><code>nop</code>, <code>syscall</code>, <code>hlt</code>, <code>int3</code></td></tr>
<tr><td>Строковые</td><td><code>переместить_байт</code>, <code>сравнить_байты</code></td><td><code>movsb</code>, <code>cmpsb</code></td></tr>
</table> </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>
</table>
<h3>Пример использования:</h3>
<pre>.данные
переменная: .байт 0
результат: .байт 0
.текст
_start:
; Запись значения
переместить_имм раикс, 42
сохранить [переменная], раикс
; Чтение значения
загрузить рбикс, [переменная]
; Завершение с кодом 42
переместить_имм раикс, 60
загрузить рдиай, [переменная]
вызов_системы</pre>
<div class="warning">
<strong>⚠️ ВАЖНО:</strong> Порядок объявления в секции <code>.данные</code> имеет значение! Переменные, объявленные перед строковыми константами, могут затереть первые байты строк. Рекомендуется объявлять строки первыми, а изменяемые переменные — после.
</div>
<h2 id="регистры">РЕГИСТРЫ ПРОЦЕССОРА</h2> <h2 id="регистры">РЕГИСТРЫ ПРОЦЕССОРА</h2>
<table> <table>
<tr><th>64-битные</th><th>32-битные</th><th>16-битные</th><th>8-битные (мл.)</th><th>8-битные (ст.)</th></tr> <tr><th>64-битные</th><th>32-битные</th><th>16-битные</th><th>8-битные (мл.)</th><th>8-битные (ст.)</th></tr>
@ -460,10 +552,10 @@ _start:
<h2 id="директивы">ДИРЕКТИВЫ АССЕМБЛЕРА</h2> <h2 id="директивы">ДИРЕКТИВЫ АССЕМБЛЕРА</h2>
<table> <table>
<tr><th>Директива</th><th>Описание</th><th>Пример</th></tr> <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>.данные</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>.глобал _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>.строка_нуль "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>.байт 0x0A, 255, 0b0001</code></td></tr>
@ -479,19 +571,37 @@ _start:
_start: _start:
; write(1, msg, 13) ; write(1, msg, 13)
переместить_имм раикс, 1 переместить_имм раикс, 1
загрузить_адрес рсиай, msg переместить_имм рдиай, 1
переместить_имм рсиай, msg
переместить_имм рдикс, 13 переместить_имм рдикс, 13
вызов_системы вызов_системы
; exit(0) ; exit(0)
переместить_имм раикс, 60 переместить_имм раикс, 60
переместить_имм рдикс, 0 переместить_имм рдиай, 0
вызов_системы вызов_системы
.данные .данные
msg: .строка_нуль "Привет, мир!"</pre> msg: .строка_нуль "Hello, World!"</pre>
<h3>Пример 2: Цикл для вычисления суммы</h3> <h3>Пример 2: Работа с переменными в памяти</h3>
<pre>.глобал _start
.текст
_start:
; Запись 42 в переменную
переместить_имм раикс, 42
сохранить [переменная], раикс
; Завершение с кодом 42
переместить_имм раикс, 60
загрузить рдиай, [переменная]
вызов_системы
.данные
переменная: .байт 0</pre>
<h3>Пример 3: Цикл для вычисления суммы</h3>
<pre>.глобал _start <pre>.глобал _start
.текст .текст
@ -523,13 +633,99 @@ loop:
<li><strong><code>программа.elf</code></strong> — исполняемый ELF-файл</li> <li><strong><code>программа.elf</code></strong> — исполняемый ELF-файл</li>
</ul> </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>kvs_pass2_encoder.py</code></li>
<li>Более 50 инструкций x86-64</li>
<li>CSV-логирование</li>
</ul>
<h3>⚠️ Частично реализовано</h3>
<ul>
<li><code>загрузить_адрес</code> (LEA) — только для чисел, не для меток</li>
<li>Сложные выражения в операндах памяти парсятся, но не кодируются</li>
</ul>
<h3>❌ В разработке (v3.0)</h3>
<ul>
<li>Косвенная адресация <code>[reg]</code></li>
<li>Адресация со смещением <code>[reg + disp]</code></li>
<li>Сложная адресация с индексом <code>[reg + reg*scale]</code></li>
<li>Полная SIB-адресация <code>[reg + reg*scale + disp]</code></li>
<li>Директива <code>.bss</code> для неинициализированных данных</li>
<li>LEA для меток (<code>загрузить_адрес reg, [метка]</code>)</li>
<li>Стандартная библиотека</li>
</ul>
<h2 id="план">ПЛАН РАЗВИТИЯ</h2>
<h3>v2.0 (текущая) — Абсолютная адресация</h3>
<ul>
<li>✅ Лексер: <code>[</code>, <code>]</code>, <code>NUMBER</code></li>
<li>✅ Парсер: <code>[метка]</code> и <code>[число]</code></li>
<li>✅ Pass1: размер = 7 байт</li>
<li>✅ Pass2: RIP-relative кодирование</li>
<li>✅ Поддержка меток в памяти</li>
<li>✅ Рефакторинг энкодера</li>
</ul>
<h3>v3.0 (планируется) — Косвенная и относительная адресация</h3>
<ul>
<li>❌ <code>[reg]</code> — косвенная адресация через регистр</li>
<li>❌ <code>[reg + disp]</code> — со смещением (disp8/disp32)</li>
<li>❌ <code>[reg + reg*scale]</code> — с индексным регистром</li>
<li>❌ <code>[reg + reg*scale + disp]</code> — полная SIB-адресация</li>
<li>❌ <code>.bss</code> — секция неинициализированных данных</li>
<li>❌ LEA для меток</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>
<li>❌ <code>printf</code> (упрощённый вариант)</li>
</ul>
<div class="process-diagram">
Зависимости между подзадачами:
Лексер (шаг 1) ✅
↓
Парсер (шаг 2) ✅
↓
Данные инструкций (шаг 3) ⚠️
↓
Pass1 (шаг 4) ✅
↓
Pass2 (шаг 5): ⚠️ ЧАСТИЧНО
5.1 → 5.2 → 5.3 → 5.4 → 5.5
↓
5.6 (генераторы инструкций)
↓
5.7 (диспетчер) ✅
↓
Тестирование (шаг 6) ⚠️ ЧАСТИЧНО
↓
Библиотека (шаг 7) ❌ ОТЛОЖЕНО
</div>
<h2 id="ограничения">ОГРАНИЧЕНИЯ</h2> <h2 id="ограничения">ОГРАНИЧЕНИЯ</h2>
<div class="warning"> <div class="warning">
<ul> <ul>
<li><strong>Сложная адресация памяти</strong> — в текущей версии поддерживаются только простые формы <code>[регистр]</code>; полная поддержка <code>[база + индекс*масштаб + смещение]</code> в разработке</li> <li><strong>Косвенная адресация памяти</strong> — поддерживается только абсолютная форма <code>[метка]</code>; адресация через регистры <code>[reg]</code> в разработке</li>
<li><strong>Отсутствие динамической памяти</strong> — нет встроенных malloc/free (можно реализовать через syscall brk)</li> <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>Отсутствие .bss</strong> — все переменные инициализируются и попадают в <code>.data</code> (занимают место в файле)</li>
<li><strong>Нет динамической памяти</strong> — нет встроенных malloc/free (можно реализовать через syscall brk)</li>
<li><strong>Нет макросов и условной компиляции</strong></li> <li><strong>Нет макросов и условной компиляции</strong></li>
<li><strong>Нет 32-битного режима</strong> — только 64-битные ELF</li>
<li><strong>Экспериментальный статус</strong> — некоторые инструкции могут быть реализованы не полностью</li> <li><strong>Экспериментальный статус</strong> — некоторые инструкции могут быть реализованы не полностью</li>
</ul> </ul>
</div> </div>

100
README.md
View file

@ -1,9 +1,5 @@
```markdown ```markdown
# КВС (Компилятор B Семейства) — Ассемблер с русским синтаксисом для x86-64 # КВС (Компилятор В Семейства) — Ассемблер с русским синтаксисом для x86-64
[![Version](https://img.shields.io/badge/version-2.0-green.svg)](https://github.com/username/kvs)
[![Python](https://img.shields.io/badge/python-3.6+-blue.svg)](https://python.org)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
**КВС** — это экспериментальный ассемблер для архитектуры x86-64, полностью использующий **русскую лексику** для мнемоник инструкций, имён регистров и директив. Проект носит образовательный характер и демонстрирует принципы работы ассемблера, построения ELF-файлов и трансляции высокоуровневых концепций в машинный код. **КВС** — это экспериментальный ассемблер для архитектуры x86-64, полностью использующий **русскую лексику** для мнемоник инструкций, имён регистров и директив. Проект носит образовательный характер и демонстрирует принципы работы ассемблера, построения ELF-файлов и трансляции высокоуровневых концепций в машинный код.
@ -20,6 +16,7 @@
- [Примеры программ](#-примеры-программ) - [Примеры программ](#-примеры-программ)
- [Отладка](#-отладка-и-проверка) - [Отладка](#-отладка-и-проверка)
- [Ограничения](#-ограничения) - [Ограничения](#-ограничения)
- [План развития](#-план-развития)
- [Лицензия](#-лицензия) - [Лицензия](#-лицензия)
## ✨ Особенности ## ✨ Особенности
@ -28,9 +25,11 @@
- **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску - **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску
- **Двухпроходная сборка** — традиционная схема с разрешением меток - **Двухпроходная сборка** — традиционная схема с разрешением меток
- **Поддержка секций** — разделение на `.текст` (код) и `.данные` (данные) - **Поддержка секций** — разделение на `.текст` (код) и `.данные` (данные)
- **Абсолютная адресация памяти** — чтение и запись по меткам `[переменная]` и абсолютным адресам `[0x402000]` с RIP-relative кодированием
- **CSV-логирование** — детальный файл с соответствием адресов, байтов и исходных команд - **CSV-логирование** — детальный файл с соответствием адресов, байтов и исходных команд
- **Более 50 инструкций** — полный набор основных команд x86-64 - **Более 50 инструкций** — полный набор основных команд x86-64
- **Минимализм зависимостей** — только стандартная библиотека Python - **Минимализм зависимостей** — только стандартная библиотека Python
- **Рефакторинг** — кодогенерация вынесена в отдельный модуль `kvs_pass2_encoder.py`
## 🏗 Архитектура ## 🏗 Архитектура
@ -48,7 +47,7 @@ AST-представление (.аст)
▼ (kvs_pass1.py) ▼ (kvs_pass1.py)
Первый проход: размеры, метки (.проход1) Первый проход: размеры, метки (.проход1)
│ │
▼ (kvs_pass2.py) ▼ (kvs_pass2.py + kvs_pass2_encoder.py)
Второй проход: генерация кода (.csv) Второй проход: генерация кода (.csv)
│ │
▼ (kvs_builder.py) ▼ (kvs_builder.py)
@ -65,7 +64,8 @@ ELF-компоновщик (.elf)
| `kvs_lexer.py` | Лексический анализ, разбор на токены | | `kvs_lexer.py` | Лексический анализ, разбор на токены |
| `kvs_parser.py` | Синтаксический анализ, построение AST | | `kvs_parser.py` | Синтаксический анализ, построение AST |
| `kvs_pass1.py` | Первый проход: вычисление размеров и адресов меток | | `kvs_pass1.py` | Первый проход: вычисление размеров и адресов меток |
| `kvs_pass2.py` | Второй проход: генерация машинного кода | | `kvs_pass2.py` | Второй проход: обход AST, генерация CSV |
| `kvs_pass2_encoder.py` | Кодировщик инструкций (все encode_* функции) |
| `kvs_builder.py` | Сборка ELF-файла из CSV | | `kvs_builder.py` | Сборка ELF-файла из CSV |
| `kvs_data.py` | Общие данные (регистры, инструкции) | | `kvs_data.py` | Общие данные (регистры, инструкции) |
@ -156,6 +156,20 @@ _start: ; метка _start
переместить_имм раикс, 42 ; комментарий после инструкции переместить_имм раикс, 42 ; комментарий после инструкции
``` ```
### Работа с памятью (абсолютная адресация)
```asm
; Запись значения в память по метке
переместить_имм раикс, 42
сохранить [переменная], раикс
; Чтение значения из памяти
загрузить рбикс, [переменная]
; Запись по абсолютному адресу
сохранить [0x402000], раикс
```
## 📖 Система команд ## 📖 Система команд
### Загрузка констант ### Загрузка констант
@ -170,6 +184,19 @@ _start: ; метка _start
переместить_имм ал, 0xFF ; AL = 255 переместить_имм ал, 0xFF ; AL = 255
``` ```
### Работа с памятью
| Инструкция | Синтаксис | Описание |
|------------|-----------|----------|
| `загрузить` | `загрузить рег, [метка/адрес]` | Загрузить значение из памяти |
| `сохранить` | `сохранить [метка/адрес], рег` | Сохранить значение в память |
```asm
загрузить раикс, [переменная] ; RAX = *переменная
сохранить [результат], раикс ; *результат = RAX
загрузить рбикс, [0x402000] ; загрузка по абсолютному адресу
```
### Арифметика и логика ### Арифметика и логика
| Инструкция | Синтаксис | Описание | | Инструкция | Синтаксис | Описание |
@ -250,6 +277,29 @@ _start: ; метка _start
## 📝 Примеры программ ## 📝 Примеры программ
### Работа с памятью (переменные)
```asm
.текст
.глобал _start
_start:
; Запись значения в переменную
переместить_имм раикс, 42
сохранить [переменная], раикс
; Чтение значения из переменной
загрузить рбикс, [переменная]
; Завершение с кодом возврата
переместить_имм раикс, 60
загрузить рдиай, [переменная]
вызов_системы
.данные
переменная: .байт 0
```
### Цикл и условный переход ### Цикл и условный переход
```asm ```asm
@ -263,7 +313,7 @@ loop:
переместить_имм раикс, 1 переместить_имм раикс, 1
переместить_имм рдиай, 1 переместить_имм рдиай, 1
переместить_имм рсиай, msg переместить_имм рсиай, msg
переместить_имм рдикс, len_msg переместить_имм рдикс, 10
вызов_системы вызов_системы
уменьшить рсикс ; счетчик-- уменьшить рсикс ; счетчик--
@ -275,8 +325,7 @@ loop:
вызов_системы вызов_системы
.данные .данные
msg: .строка_нуль "Iteration\n" msg: .строка "Iteration\n"
.константа len_msg = 10
``` ```
### Вычисление суммы чисел от 1 до 10 ### Вычисление суммы чисел от 1 до 10
@ -365,15 +414,38 @@ gdb ./программа.elf
- ✅ Строковые литералы с escape-последовательностями (`\n`, `\t`, `\\`, `\"`) - ✅ Строковые литералы с escape-последовательностями (`\n`, `\t`, `\\`, `\"`)
- ✅ Директива `.байт` для raw-данных - ✅ Директива `.байт` для raw-данных
- ✅ Директива `.константа` для имён констант - ✅ Директива `.константа` для имён констант
- ✅ **Абсолютная адресация памяти** `[метка]` и `[число]` (RIP-relative)
### Не поддерживается ### Не поддерживается (в разработке)
- ❌ Косвенная адресация памяти `[reg+disp]` (в разработке) - ❌ Косвенная адресация `[reg]` — доступ к памяти через регистр
- ❌ Инструкции для работы со стеком (PUSH, POP, CALL, RET) - ❌ Адресация со смещением `[reg + disp]`
- ❌ Сложная адресация с индексом `[reg + reg*scale]` и полная SIB-адресация
- ❌ Инструкции для работы со стеком (PUSH, POP, CALL, RET) — частично
- ❌ Поддержка плавающей запятой (FPU/SSE) - ❌ Поддержка плавающей запятой (FPU/SSE)
- ❌ Многомодульная компиляция - ❌ Многомодульная компиляция
- ❌ Директива `.bss` для неинициализированных данных
- ❌ Макросы - ❌ Макросы
## 📈 План развития
### Реализовано (v2.0)
- [x] Лексер с токенами `[`, `]`, `NUMBER`
- [x] Парсер с поддержкой операндов в квадратных скобках
- [x] Первый проход с корректным расчётом размера инструкций с памятью
- [x] Второй проход с кодированием абсолютной адресации через RIP-relative
- [x] Поддержка меток в операндах памяти `[переменная]`
- [x] Рефакторинг: выделение кодировщика в отдельный модуль
### В планах (v3.0)
- [ ] Косвенная адресация `[reg]`
- [ ] Адресация со смещением `[reg + disp]`
- [ ] Сложная адресация с индексом `[reg + reg*scale]`
- [ ] Полная SIB-адресация `[reg + reg*scale + disp]`
- [ ] Директива `.bss` для неинициализированных данных
- [ ] Поддержка инструкций PUSH/POP/CALL/RET для стековых операций
- [ ] Стандартная библиотека (`libkvs.квс`): malloc, free, strlen, strcmp, print
## 📄 Лицензия ## 📄 Лицензия
Только для образовательных целей. Используйте на свой страх и риск. Только для образовательных целей. Используйте на свой страх и риск.
@ -382,5 +454,3 @@ gdb ./программа.elf
КВС Ассемблер | Экспериментальный проект | 2025 КВС Ассемблер | Экспериментальный проект | 2025
Только для образовательных целей. Используйте на свой страх и риск. Только для образовательных целей. Используйте на свой страх и риск.
``` ```
```

View file

@ -6,15 +6,34 @@
Принимает: исходный .квс файл Принимает: исходный .квс файл
Выдаёт: текстовый файл с токенами (по одному токену на строку) Выдаёт: текстовый файл с токенами (по одному токену на строку)
Формат вывода: ТИП:ЗНАЧЕНИЕ Формат вывода: ТИП:ЗНАЧЕНИЕ
Поддерживает сложную адресацию:
- [ ] - квадратные скобки
- + - плюс
- - - минус
- * - звёздочка (умножение/масштаб)
- NUMBER - числа (десятичные, шестнадцатеричные, отрицательные)
""" """
import sys import sys
import re
def error_unterminated_string(line_num): def error_unterminated_string(line_num):
return f"Незакрытая кавычка в строке на строке {line_num}" return f"Незакрытая кавычка в строке на строке {line_num}"
def is_hex_number(s):
"""Проверяет, является ли строка шестнадцатеричным числом (0x... или 0X...)"""
return re.match(r'^0[xX][0-9a-fA-F]+$', s) is not None
def is_dec_number(s):
"""Проверяет, является ли строка десятичным числом (возможно с минусом)"""
if s.startswith('-'):
s = s[1:]
return s.isdigit()
def tokenize_line(line, line_num): def tokenize_line(line, line_num):
"""Разбирает строку в токены""" """Разбирает строку в токены"""
# Удаляем комментарии
semi = line.find(';') semi = line.find(';')
if semi != -1: if semi != -1:
line = line[:semi] line = line[:semi]
@ -25,18 +44,21 @@ def tokenize_line(line, line_num):
tokens = [] tokens = []
i = 0 i = 0
n = len(line) n = len(line)
while i < n: while i < n:
ch = line[i] ch = line[i]
# Пропускаем пробелы
if ch.isspace(): if ch.isspace():
i += 1 i += 1
continue continue
# Строковые литералы
if ch == '"': if ch == '"':
i += 1 i += 1
s = '' s = ''
while i < n and line[i] != '"': while i < n and line[i] != '"':
if line[i] == '\\' and i + 1 < n: if line[i] == '\\' and i + 1 < n:
# Оставляем escape-последовательности как есть
s += '\\' + line[i + 1] s += '\\' + line[i + 1]
i += 2 i += 2
else: else:
@ -48,6 +70,7 @@ def tokenize_line(line, line_num):
tokens.append(('STRING', s)) tokens.append(('STRING', s))
continue continue
# Одиночные символы-разделители
if ch == ',': if ch == ',':
tokens.append(('COMMA', ',')) tokens.append(('COMMA', ','))
i += 1 i += 1
@ -58,12 +81,53 @@ def tokenize_line(line, line_num):
i += 1 i += 1
continue continue
# НОВЫЕ ТОКЕНЫ ДЛЯ СЛОЖНОЙ АДРЕСАЦИИ
if ch == '[':
tokens.append(('LBRACKET', '['))
i += 1
continue
if ch == ']':
tokens.append(('RBRACKET', ']'))
i += 1
continue
if ch == '+':
tokens.append(('PLUS', '+'))
i += 1
continue
if ch == '-':
# Проверяем, не является ли это началом отрицательного числа
# Смотрим следующий символ
if i + 1 < n and (line[i + 1].isdigit() or line[i + 1] == '0'):
# Возможно отрицательное число, дадим разобраться дальше
# Но для простоты сохраняем как MINUS, а число будет отдельным токеном
pass
tokens.append(('MINUS', '-'))
i += 1
continue
if ch == '*':
tokens.append(('STAR', '*'))
i += 1
continue
# Слова, числа, метки
j = i j = i
while j < n and not (line[j].isspace() or line[j] in ',:;'): while j < n and not (line[j].isspace() or line[j] in ',:;[]+*-'):
j += 1 j += 1
word = line[i:j] word = line[i:j]
if word: if word:
# Определяем тип токена
if is_hex_number(word):
tokens.append(('NUMBER', word))
elif is_dec_number(word):
tokens.append(('NUMBER', word))
else:
tokens.append(('WORD', word)) tokens.append(('WORD', word))
i = j i = j
return tokens return tokens

View file

@ -5,6 +5,9 @@
Парсер КВС Парсер КВС
Принимает: файл токенов от лексера Принимает: файл токенов от лексера
Выдаёт: текстовый файл с AST-структурой Выдаёт: текстовый файл с AST-структурой
Поддерживает сложную адресацию:
- [регистр + регистр*масштаб + смещение]
""" """
import sys import sys
@ -51,6 +54,13 @@ class Parser:
raise ValueError(f"Ожидался {expected_type}, получено {tok}") raise ValueError(f"Ожидался {expected_type}, получено {tok}")
return 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): def parse_line(self):
tok = self.peek() tok = self.peek()
if tok is None: if tok is None:
@ -63,6 +73,7 @@ class Parser:
return False return False
if tok[0] == 'WORD': if tok[0] == 'WORD':
# Проверяем, есть ли после слова двоеточие (метка)
if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1][0] == 'COLON': if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1][0] == 'COLON':
label_name = self.next_token()[1] label_name = self.next_token()[1]
self.next_token() # COLON self.next_token() # COLON
@ -107,10 +118,12 @@ class Parser:
self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:{string_tok[1]}") self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:{string_tok[1]}")
elif word == '.константа': elif word == '.константа':
name = self.expect('WORD')[1] name = self.expect('WORD')[1]
# Пропускаем '=', если есть
eq_tok = self.peek() eq_tok = self.peek()
if eq_tok and eq_tok[0] == 'WORD' and eq_tok[1] == '=': if eq_tok and eq_tok[0] == 'WORD' and eq_tok[1] == '=':
self.next_token() self.next_token()
value_tok = self.expect('WORD') # Значение может быть WORD или NUMBER
value_tok = self.expect_one_of(['WORD', 'NUMBER'])
self.parsed_lines.append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}") self.parsed_lines.append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}")
elif word == '.байт': elif word == '.байт':
bytes_list = [] bytes_list = []
@ -122,11 +135,71 @@ class Parser:
else: else:
break break
else: else:
bytes_list.append(self.next_token()[1]) # Может быть 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)) self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:" + ",".join(bytes_list))
else: else:
raise ValueError(f"Неизвестная директива: {word}") raise ValueError(f"Неизвестная директива: {word}")
def parse_operand(self):
"""
Разбирает один операнд.
Возвращает строковое представление операнда.
Поддерживает:
- регистры
- числа
- метки
- строки
- адресацию в квадратных скобках
"""
tok = self.peek()
if tok is None:
return None
if tok[0] == 'LBRACKET':
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):
"""
Разбирает операнд памяти вида:
[регистр]
[число]
[регистр + смещение]
[регистр + регистр*масштаб]
[регистр + регистр*масштаб + смещение]
"""
self.expect('LBRACKET')
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}")
# Собираем выражение
expr = ' '.join(parts)
return f"[{expr}]"
def parse_instruction(self, mnemonic): def parse_instruction(self, mnemonic):
if mnemonic not in INSTRUCTIONS: if mnemonic not in INSTRUCTIONS:
raise ValueError(f"Неизвестная инструкция: {mnemonic}") raise ValueError(f"Неизвестная инструкция: {mnemonic}")
@ -139,7 +212,7 @@ class Parser:
if tok[0] == 'COMMA': if tok[0] == 'COMMA':
self.next_token() self.next_token()
continue continue
operands.append(self.next_token()[1]) operands.append(self.parse_operand())
self.parsed_lines.append(f"INSTR:{mnemonic}:{self.current_section}:" + ",".join(operands)) self.parsed_lines.append(f"INSTR:{mnemonic}:{self.current_section}:" + ",".join(operands))

View file

@ -7,8 +7,9 @@
""" """
import sys import sys
import re
sys.path.insert(0, '.') sys.path.insert(0, '.')
from kvs_data import PAGE_SIZE, text_vaddr_base, data_vaddr_base, align_up from kvs_data import PAGE_SIZE, text_vaddr_base, data_vaddr_base, align_up, INSTRUCTIONS
def unescape_string(s): def unescape_string(s):
"""Преобразует escape-последовательности в реальные символы""" """Преобразует escape-последовательности в реальные символы"""
@ -38,6 +39,113 @@ def read_ast(input_file):
ast_lines.append(line.strip()) ast_lines.append(line.strip())
return ast_lines return ast_lines
def parse_memory_operand(operand_str):
"""
Анализирует операнд памяти вида [reg + reg*scale + disp]
Возвращает словарь с информацией:
- has_base: bool
- base_reg: str or None
- has_index: bool
- index_reg: str or None
- scale: int (1,2,4,8)
- has_disp: bool
- disp_value: int (если есть)
- disp_size: 1 или 4
"""
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 content.isdigit() or (content.startswith('0x') and len(content) > 2 and content[2:].replace('0','').replace('1','').replace('2','').replace('3','').replace('4','').replace('5','').replace('6','').replace('7','').replace('8','').replace('9','').replace('a','').replace('b','').replace('c','').replace('d','').replace('e','').replace('f','').replace('A','').replace('B','').replace('C','').replace('D','').replace('E','').replace('F','') == ''):
result['has_disp'] = True
if content.startswith('0x'):
result['disp_value'] = int(content, 16)
else:
result['disp_value'] = int(content)
result['disp_size'] = 4
return result
# Простой регистр без смещения
if content in ['раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15']:
result['has_base'] = True
result['base_reg'] = content
return result
# Разбираем выражение: регистр [+- регистр[*масштаб] [+- смещение]]
parts = re.split(r'([+\-])', content)
for part in parts:
part = part.strip()
if not part or part in '+-':
continue
if part in ['раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15']:
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 part.isdigit() or (part.startswith('0x') and len(part) > 2):
result['has_disp'] = True
if part.startswith('0x'):
result['disp_value'] = int(part, 16)
else:
result['disp_value'] = int(part)
elif '*' in part:
reg_part, scale_part = part.split('*')
reg_part = reg_part.strip()
scale_part = scale_part.strip()
if reg_part in ['раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15']:
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: class Pass1:
def __init__(self): def __init__(self):
self.labels = {} self.labels = {}
@ -68,11 +176,11 @@ class Pass1:
self.position[".data"] += size self.position[".data"] += size
elif directive == '.константа': elif directive == '.константа':
name = parts[2] name = parts[2]
value = parts[3] value_str = parts[3]
if value.isdigit(): if value_str.isdigit():
self.symbols[name] = int(value) self.symbols[name] = int(value_str)
elif value.startswith('0x'): elif value_str.startswith('0x'):
self.symbols[name] = int(value, 16) self.symbols[name] = int(value_str, 16)
else: else:
self.symbols[name] = 0 self.symbols[name] = 0
elif directive == '.байт': elif directive == '.байт':
@ -90,10 +198,27 @@ class Pass1:
elif line_type == "INSTR": elif line_type == "INSTR":
mnemonic = parts[1] mnemonic = parts[1]
sec = parts[2] sec = parts[2]
size = self.estimate_size(mnemonic) operands_str = parts[3] if len(parts) > 3 else ""
operands = operands_str.split(',') if operands_str else []
size = self.estimate_size(mnemonic, operands)
self.position[sec] += size self.position[sec] += size
def estimate_size(self, mnemonic): def estimate_size(self, mnemonic, operands):
"""Динамический расчёт размера инструкции"""
# Инструкции с памятью (загрузить, сохранить, загрузить_адрес)
if mnemonic in ("загрузить", "сохранить", "загрузить_адрес"):
# RIP-relative: REX (1) + opcode (1) + ModR/M (1) + disp32 (4) = 7
return 7
# Инструкции без операндов
if mnemonic in ("вызов_системы", "нет_операции", "вернуться", "остановить", "отладка"):
instr = INSTRUCTIONS.get(mnemonic)
if instr and "opcode" in instr:
return len(instr["opcode"])
return 2
# Таблица фиксированных размеров
size_map = { size_map = {
"переместить_имм": 10, "переместить_имм": 10,
"сравнить_с": 7, "сравнить_с": 7,
@ -127,6 +252,40 @@ class Pass1:
"прибавить": 3, "прибавить": 3,
"увеличить": 3, "увеличить": 3,
"уменьшить": 3, "уменьшить": 3,
"и": 3,
"или": 3,
"исключающее_или": 3,
"инвертировать": 3,
"отрицать": 3,
"втолкнуть": 2,
"вытолкнуть": 2,
"втолкнуть_непосредственно": 5,
"умножить": 3,
"умножить_знаковое": 3,
"разделить": 3,
"разделить_знаковое": 3,
"сдвиг_влево": 4,
"сдвиг_вправо": 4,
"сдвиг_арифметический_вправо": 4,
"вращать_влево": 4,
"вращать_вправо": 4,
"цикл": 2,
"обменять": 4,
"прервать": 2,
"ввод_байта": 2,
"вывод_байта": 2,
"установить_перенос": 1,
"сбросить_перенос": 1,
"установить_направление": 1,
"сбросить_направление": 1,
"втолкнуть_флаги": 1,
"вытолкнуть_флаги": 1,
"переместить_байт": 1,
"переместить_слово": 1,
"сравнить_байты": 1,
"сканировать_байт": 1,
"идентифицировать_процессор": 2,
"прочитать_счётчик": 2,
} }
return size_map.get(mnemonic, 3) return size_map.get(mnemonic, 3)

File diff suppressed because it is too large Load diff

1128
kvs_pass2_encoder.py Normal file

File diff suppressed because it is too large Load diff

307
todo.txt
View file

@ -1,83 +1,68 @@
```markdown
=========================== ===========================
нужна сложная адресация ПЛАН РЕАЛИЗАЦИИ СЛОЖНОЙ АДРЕСАЦИИ (ПОЛНАЯ ВЕРСИЯ)
=========================== ===========================
Хорошо, вот **полностью доработанный пошаговый план** внедрения сложной адресации во все модули компилятора КВС, с учётом: ## СТАТУС: ЧАСТИЧНО РЕАЛИЗОВАНО (v2.0)
- Поддержки `+`, `-`, `*`, `[`, `]` ### Уже реализовано:
- Новых мнемоник для работы с памятью - ✅ Лексер: токены `[`, `]`, `NUMBER`
- Адресации всех типов: `[reg]`, `[disp]`, `[reg+disp]`, `[reg+reg*scale]`, `[reg+reg*scale+disp]` - ✅ Парсер: распознавание `[метка]` и `[число]`
- Корректной работы лексера, парсера, pass1, pass2, сборки ELF - ✅ Pass1: корректный расчёт размера (7 байт для MOV с памятью)
- ✅ Pass2: RIP-relative кодирование для абсолютной адресации
- ✅ Поддержка меток в операндах памяти `[переменная]`
- ✅ Рефакторинг: `kvs_pass2_encoder.py` отдельно
--- ---
# ПЛАН РЕАЛИЗАЦИИ СЛОЖНОЙ АДРЕСАЦИИ (ПОЛНАЯ ВЕРСИЯ)
## 1. Общая схема изменений по модулям ## 1. Общая схема изменений по модулям
| Модуль | Что меняется | Сложность | | Модуль | Что меняется | Сложность | Статус |
|-------------------|------------------------------------|-----------| |--------|--------------|-----------|--------|
| `kvs_lexer.py` | новые токены: `[`, `]`, `+`, `-`, `*`, `NUMBER` | низкая | | `kvs_lexer.py` | новые токены: `[`, `]`, `+`, `-`, `*`, `NUMBER` | низкая | ✅ ГОТОВ |
| `kvs_parser.py` | новая логика операндов: `parse_operand()`, `parse_memory_operand()` | высокая | | `kvs_parser.py` | новая логика операндов: `parse_operand()`, `parse_memory_operand()` | высокая | ✅ ГОТОВ (базово) |
| `kvs_data.py` | активация и уточнение форматов инструкций | средняя | | `kvs_data.py` | активация и уточнение форматов инструкций | средняя | ⚠️ ТРЕБУЕТ ДОРАБОТКИ |
| `kvs_pass1.py` | динамический рассчёт размера инструкций с памятью | высокая | | `kvs_pass1.py` | динамический рассчёт размера инструкций с памятью | высокая | ✅ ГОТОВ (для абсолютной) |
| `kvs_pass2.py` | разбор адресации → ModR/M + SIB + disp, генерация кода | очень высокая | | `kvs_pass2_encoder.py` | разбор адресации → ModR/M + SIB + disp, генерация кода | очень высокая | ⚠️ ЧАСТИЧНО (только абсолютная) |
--- ---
## 2. Детальный план по шагам (в порядке реализации) ## 2. Детальный план по шагам (в порядке реализации)
### Шаг 1 — Лексер (`kvs_lexer.py`) ### Шаг 1 — Лексер (`kvs_lexer.py`) ✅ РЕАЛИЗОВАН
**Что сделать:** **Что сделано:**
- Добавить токены: - Добавлены токены: `LBRACKET` (`[`), `RBRACKET` (`]`), `PLUS` (`+`), `MINUS` (`-`), `STAR` (`*`), `NUMBER`
- `LBRACKET` → `[` - Числа отделены от слов (поддержка десятичных, шестнадцатеричных, отрицательных)
- `RBRACKET` → `]`
- `PLUS` → `+`
- `MINUS` → `-`
- `STAR` → `*`
- Добавить `NUMBER` (отдельно от `WORD`) для:
- десятичных чисел (`123`)
- отрицательных (`-456`)
- шестнадцатеричных (`0x7F`)
** парсер:**
- Парсер сможет отличать `[` от начала слова
- Числа не будут смешиваться с метками/регистрами
**Результат:** новые типы токенов на выходе лексера. **Результат:** новые типы токенов на выходе лексера.
--- ---
### Шаг 2 — Парсер (`kvs_parser.py`) — основа ### Шаг 2 — Парсер (`kvs_parser.py`) ✅ РЕАЛИЗОВАН (базово)
**Что сделать:** **Что сделано:**
2.1. Заменить прямую загрузку токена в `parse_instruction()` на вызов `parse_operand()` 2.1. Реализован `parse_operand()`:
- Если токен `LBRACKET` → вызывает `parse_memory_operand()`
- Иначе → возвращает значение токена (регистр/число/метка/строка)
2.2. Реализовать `parse_operand()`: 2.2. Реализован `parse_memory_operand()`:
- Если токен `LBRACKET` → вызвать `parse_memory_operand()`
- Иначе → вернуть значение токена (регистр/число/метка/строка)
2.3. Реализовать `parse_memory_operand()`:
- Разбирает содержимое между `[` и `]` - Разбирает содержимое между `[` и `]`
- Поддерживает выражения вида: - Поддерживает: `регистр`, `число`, `метка`
- `регистр` - Возвращает строку вида `[раикс + рсикс*8 + 16]` (структура сохраняется)
- `число`
- `регистр + смещение`
- `регистр + регистр * масштаб`
- `регистр + регистр * масштаб + смещение`
- любые комбинации с `+` и `-`
- Возвращает строку типа `[раикс + рсикс*8 + 16]`
**Результат:** AST будет содержать операнды памяти в каноническом строковом виде. 2.3. **Ограничения текущей реализации:**
- Поддерживается только простая форма `[метка]` и `[число]`
- Сложные выражения `[reg + reg*scale + disp]` парсятся, но не кодируются
**Результат:** AST содержит операнды памяти в каноническом строковом виде.
--- ---
### Шаг 3 — Данные инструкций (`kvs_data.py`) ### Шаг 3 — Данные инструкций (`kvs_data.py`) ⚠️ ТРЕБУЕТ ДОРАБОТКИ
**Что сделать:** **Что нужно сделать:**
3.1. Активировать (или добавить) записи для: 3.1. Активировать (или добавить) записи для:
@ -94,41 +79,31 @@
| `вытолкнуть_в_память` | `8F /0` | `pop_mem` | | `вытолкнуть_в_память` | `8F /0` | `pop_mem` |
| `обменять_с_памятью` | `48 87` | `xchg_mem` | | `обменять_с_памятью` | `48 87` | `xchg_mem` |
3.2. Удалить поле `type`, оставить `format` или прямо в коде проверять по мнемонике (как вы любите). 3.2. **Статус:** записи присутствуют в `kvs_data.py`, но используются не все.
**Результат:** словарь инструкций готов для кодогенерации.
--- ---
### Шаг 4 — Первый проход (`kvs_pass1.py`) — оценка размера ### Шаг 4 — Первый проход (`kvs_pass1.py`) ✅ РЕАЛИЗОВАН (для абсолютной)
**Что сделать:** **Что сделано:**
4.1. Написать функцию `estimate_memory_operand_size(operand_str)`: 4.1. Написана функция `estimate_memory_operand_size(operand_str)`:
- Парсит строку `[раикс + рсикс*8 + 16]` - Парсит строку `[раикс + рсикс*8 + 16]`
- Определяет: - Определяет наличие SIB, размер смещения
- наличие SIB (нужен, если есть индексный регистр)
- размер смещения (disp8 = 1 байт, disp32 = 4 байта)
- Возвращает дополнительные байты: 0, 1, 4 или 5 (SIB + disp)
4.2. Модифицировать `estimate_size()`: 4.2. Модифицирована `estimate_size()`:
- Для инструкций с памятью (список из шага 3) считать: - Для инструкций `загрузить`, `сохранить`, `загрузить_адрес` возвращает `7` (REX + opcode + ModRM + disp32)
- базовый размер opcode (REX + опкод)
- +1 байт (ModR/M)
- +1 байт (SIB, если нужен)
- + размер смещения (1 или 4)
**Результат:** правильное вычисление `.text` и адресов меток. **Ограничения:**
- Для сложных форм адресации размер не рассчитывается динамически
--- ---
### Шаг 5 — Второй проход (`kvs_pass2.py`) — ядро адресации ### Шаг 5 — Второй проход (`kvs_pass2_encoder.py`) ⚠️ ЧАСТИЧНО РЕАЛИЗОВАН
Это самый объёмный шаг. Реализовать **поэтапно**: #### 5.1. Структура данных для режима адресации ⚠️ НЕ РЕАЛИЗОВАНА
#### 5.1. Структура данных для режима адресации Нужно создать класс/словарь `AddressingMode`:
Создать класс/словарь `AddressingMode`:
- `has_base` (bool) - `has_base` (bool)
- `base_reg` (int 0–15 или None) - `base_reg` (int 0–15 или None)
- `has_index` (bool) - `has_index` (bool)
@ -138,136 +113,134 @@
- `disp_value` (int) - `disp_value` (int)
- `disp_size` (1 или 4) - `disp_size` (1 или 4)
#### 5.2. Парсер адресации #### 5.2. Парсер адресации ⚠️ ЧАСТИЧНО РЕАЛИЗОВАН
Написать `parse_addressing_mode(operand_str)`: Реализована `parse_memory_operand()` в `kvs_pass2_encoder.py`:
- Вход: `[раикс + рсикс*8 + 16]` - Поддерживает `[число]` и `[метка]`
- Выход: `AddressingMode` - **Нет поддержки:** `[reg]`, `[reg+disp]`, `[reg+reg*scale]`
- Поддержка всех форм из шага 2.3
#### 5.3. Кодирование ModR/M #### 5.3. Кодирование ModR/M ⚠️ ЧАСТИЧНО РЕАЛИЗОВАНО
Написать `encode_modrm_reg_mem(reg, addr_mode)`: Функция `encode_modrm_reg_mem_absolute()`:
- Формирует байт ModR/M: - Использует `mod=00, r/m=101` для RIP-relative
- `mod` (00, 01, 10, 11) - **Нет поддержки:** других режимов адресации
- `reg` (3 бита — номер регистра)
- `r/m` (3 бита — зависит от наличия SIB)
#### 5.4. Кодирование SIB #### 5.4. Кодирование SIB ❌ НЕ РЕАЛИЗОВАНО
Написать `encode_sib(addr_mode)`: Функция `encode_sib()` отсутствует.
- Формирует байт SIB:
- `scale` (2 бита)
- `index` (3 бита)
- `base` (3 бита)
#### 5.5. Кодирование смещения #### 5.5. Кодирование смещения ⚠️ ЧАСТИЧНО РЕАЛИЗОВАНО
Написать `encode_displacement(addr_mode, current_pos, vaddr_text)`: `encode_displacement()` использует `struct.pack('<i', offset)` для disp32.
- Для `disp8` → 1 байт со знаком
- Для `disp32` → 4 байта (little-endian)
#### 5.6. Генераторы инструкций #### 5.6. Генераторы инструкций ⚠️ ЧАСТИЧНО РЕАЛИЗОВАНЫ
Написать для каждой мнемоники: | Функция | Статус |
|---------|--------|
| `encode_mov_reg_mem` | ✅ Реализована (только абсолютная адресация) |
| `encode_mov_mem_reg` | ✅ Реализована (только абсолютная адресация) |
| `encode_lea` | ⚠️ Есть старая версия, требует доработки |
| `encode_mov_reg8_mem` | ❌ Не реализована |
| `encode_mov_mem8_reg8` | ❌ Не реализована |
| `encode_movzx` | ❌ Не реализована |
| `encode_movsx` | ❌ Не реализована |
| `encode_push_mem` | ❌ Не реализована |
| `encode_pop_mem` | ❌ Не реализована |
| `encode_xchg_mem` | ❌ Не реализована |
| Функция | Для какой инструкции | #### 5.7. Диспетчер ✅ РЕАЛИЗОВАН
|---------|----------------------|
| `encode_mov_reg_mem` | `загрузить` |
| `encode_mov_mem_reg` | `сохранить` |
| `encode_mov_reg8_mem` | `загрузить_байт_из_памяти` |
| `encode_mov_mem8_reg8` | `сохранить_байт` |
| `encode_lea` | `загрузить_адрес` |
| `encode_movzx` | `переместить_с_нулями` |
| `encode_movsx` | `переместить_со_знаком` |
| `encode_push_mem` | `втолкнуть_из_памяти` |
| `encode_pop_mem` | `вытолкнуть_в_память` |
| `encode_xchg_mem` | `обменять_с_памятью` |
Каждая функция: `encode_instruction()` содержит ветки для всех инструкций, включая:
- Берёт операнды (регистр + операнд памяти) - `загрузить`, `сохранить`, `загрузить_адрес` (с памятью)
- Вызывает `parse_addressing_mode` для операнда памяти - `переместить_имм`, `переместить` (без памяти)
- Формирует REX-префикс (W, R, B, X биты)
- Добавляет opcode
- Добавляет ModR/M
- Добавляет SIB (если нужен)
- Добавляет disp
#### 5.7. Диспетчер
Модифицировать `encode_instruction()`:
- Добавить ветки для всех новых мнемоник
- Вызывать соответствующие функции из 5.6
--- ---
### Шаг 6 — Тестирование (итеративное) ### Шаг 6 — Тестирование
Тестировать после каждого подшага 5.x: Тесты для проверки (поэтапно):
| Тест | Что проверяет | | Тест | Что проверяет | Статус |
|------|---------------| |------|---------------|--------|
| `загрузить раикс, [рбикс]` | ModR/M, нет SIB, нет disp | | `загрузить раикс, [число]` | Абсолютная адресация | ✅ ПРОЙДЕН |
| `загрузить раикс, [1234]` | disp32, r/m=101 | | `сохранить [число], раикс` | Абсолютная адресация (запись) | ✅ ПРОЙДЕН |
| `загрузить раикс, [рбикс + 8]` | disp8 | | `загрузить раикс, [метка]` | Адресация по метке | ✅ ПРОЙДЕН |
| `загрузить раикс, [рбикс + 1000]` | disp32 | | `сохранить [метка], раикс` | Адресация по метке (запись) | ✅ ПРОЙДЕН |
| `загрузить раикс, [рбикс + рсикс]` | SIB, нет disp | | `загрузить раикс, [рбикс]` | Косвенная адресация | ❌ НЕ РЕАЛИЗОВАНО |
| `загрузить раикс, [рбикс + рсикс*8 + 16]` | SIB + disp8 | | `загрузить раикс, [рбикс + 8]` | Со смещением | ❌ НЕ РЕАЛИЗОВАНО |
| `сохранить [рдиай], рсикс` | MOV mem, reg | | `загрузить раикс, [рбикс + рсикс*8]` | С индексом | ❌ НЕ РЕАЛИЗОВАНО |
| `загрузить_адрес р11, [рбикс + рсикс*4]` | LEA | | `загрузить раикс, [рбикс + рсикс*8 + 16]` | Полная SIB | ❌ НЕ РЕАЛИЗОВАНО |
| `загрузить_адрес р11, [переменная]` | LEA с меткой | ❌ НЕ РЕАЛИЗОВАНО |
--- ---
### Шаг 7 — Стандартная библиотека (параллельно или после) ### Шаг 7 — Стандартная библиотека (отложено)
После того как адресация заработает, можно добавить: После полной реализации адресации:
| Файл | Содержание | | Файл | Содержание |
|------|------------| |------|------------|
| `libkvs.квс` | `malloc`, `free`, `strlen`, `strcmp`, `memcpy`, `putchar`, `getchar`, `printf` (упрощённый) | | `libkvs.квс` | `malloc`, `free`, `strlen`, `strcmp`, `memcpy`, `putchar`, `getchar`, `printf` |
| `libkvs_data.квс` | статические данные для библиотеки | | `libkvs_data.квс` | статические данные для библиотеки |
Библиотека будет использовать сложную адресацию для работы со структурами и кучей.
--- ---
## 3. Зависимости между подзадачами ## 3. Следующие шаги (приоритеты)
``` ### 🔴 Приоритет 1 (критично):
Лексер (шаг 1) 1. **Косвенная адресация `[reg]`** — основа для всех остальных форм
↓ - Нужно: `mod=00, r/m=reg` (без SIB, без disp)
Парсер (шаг 2)
↓ ### 🟡 Приоритет 2 (важно):
Данные инструкций (шаг 3) 2. **Адресация со смещением `[reg + disp]`**
↓ - disp8 для малых смещений (`mod=01`)
Pass1 (шаг 4) ← может тестироваться отдельно - disp32 для больших (`mod=10`)
↓
Pass2: ### 🟢 Приоритет 3 (расширение):
5.1 → 5.2 → 5.3 → 5.4 → 5.5 3. **Сложная адресация с индексом `[reg + reg*scale]`**
↓ - Требует SIB байта
5.6 (для каждой мнемоники) 4. **Полная SIB-адресация `[reg + reg*scale + disp]`**
↓ 5. **LEA для меток** `загрузить_адрес reg, [метка]`
5.7 (диспетчер) 6. **`.bss` секция** для неинициализированных данных
↓
Тестирование (шаг 6)
↓
Библиотека (шаг 7)
```
--- ---
## 4. Итог: что получим на выходе ## 4. Итог: что получим на выходе
- **Лексер** выдаёт `LBRACKET`, `RBRACKET`, `PLUS`, `MINUS`, `STAR`, `NUMBER` - ✅ **Лексер** выдаёт `LBRACKET`, `RBRACKET`, `PLUS`, `MINUS`, `STAR`, `NUMBER`
- **Парсер** понимает `[раикс + рсикс*8 - 16]` - ✅ **Парсер** понимает `[раикс + рсикс*8 - 16]`
- **Pass1** правильно считает размер инструкций с памятью - ✅ **Pass1** правильно считает размер инструкций с памятью (для абсолютной)
- **Pass2** генерирует правильный машинный код для всех форм адресации - ⚠️ **Pass2** генерирует код только для абсолютной адресации
- **ELF** содержит корректные инструкции x86-64 с ModR/M, SIB, disp - ⚠️ **ELF** содержит корректные инструкции x86-64 с RIP-relative
- **Библиотека** позволяет писать более сложные программы (malloc, строки) - ❌ **Библиотека** требует полной адресации
--- ---
## 5. Зависимости между подзадачами
```
Лексер (шаг 1) ✅
↓
Парсер (шаг 2) ✅
↓
Данные инструкций (шаг 3) ⚠️
↓
Pass1 (шаг 4) ✅
↓
Pass2 (шаг 5):
5.1 → 5.2 → 5.3 → 5.4 → 5.5 ⚠️ ЧАСТИЧНО
↓
5.6 (для каждой мнемоники) ⚠️ ЧАСТИЧНО
↓
5.7 (диспетчер) ✅
↓
Тестирование (шаг 6) ⚠️ ЧАСТИЧНО
↓
Библиотека (шаг 7) ❌ ОТЛОЖЕНО
```
--- ---
План готов. Когда скажете — начинаем писать код по шагам. **Статус: v2.0 — абсолютная адресация реализована полностью.**
**Следующая цель: v3.0 — косвенная адресация `[reg]`.**
```

9
тесты/cleaner.py Normal file
View file

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

View file

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

View file

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

View file

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

66
тесты/т1.квс Normal file
View file

@ -0,0 +1,66 @@
.глобал _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
вызов_системы