Compare commits
1 commit
main
...
feature/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d76c3815e7 |
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,3 @@
|
|||
__pycache__/
|
||||
.gigacode
|
||||
*.pyc
|
||||
*.pyo
|
||||
58
AGENTS.md
58
AGENTS.md
|
|
@ -1,4 +1,4 @@
|
|||
```markdown
|
||||
|
||||
# AGENTS.md
|
||||
|
||||
## Правила для агентов и участников проекта КВС
|
||||
|
|
@ -11,59 +11,33 @@
|
|||
Разбор строк — только посимвольно, через циклы и сравнения. Это делает код переносимым на другие языки без изменений логики.
|
||||
|
||||
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`).
|
||||
|
||||
### Структура проекта
|
||||
|
||||
```
|
||||
квс_сборка.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 — Кодировщик инструкций переменного размера + диспетчер
|
||||
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`)
|
||||
- Комментарии и документация в большинстве файлов
|
||||
- Пользовательский синтаксис: инструкции, регистры, директивы
|
||||
|
||||
**Остались на английском:**
|
||||
- Имена файлов: `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` **не переименованы** — их имена остались на английском.
|
||||
```
|
||||
785
README.html
Normal file
785
README.html
Normal file
|
|
@ -0,0 +1,785 @@
|
|||
<!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
422
README.md
|
|
@ -3,123 +3,76 @@
|
|||
|
||||
**КВС** — это экспериментальный ассемблер для архитектуры x86-64, полностью использующий **русскую лексику** для мнемоник инструкций, имён регистров и директив. Проект носит образовательный характер и демонстрирует принципы работы ассемблера, построения ELF-файлов и трансляции высокоуровневых концепций в машинный код.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Оглавление
|
||||
|
||||
- [Особенности](#-особенности)
|
||||
- [Архитектура](#-архитектура)
|
||||
- [Установка](#-установка)
|
||||
- [Быстрый старт](#-быстрый-старт)
|
||||
- [Система тестирования](#-система-тестирования)
|
||||
- [Синтаксис](#-синтаксис)
|
||||
- [Система команд](#-система-команд)
|
||||
- [Регистры](#-регистры-процессора)
|
||||
- [Директивы](#-директивы-ассемблера)
|
||||
- [Отладка и проверка](#-отладка-и-проверка)
|
||||
- [Примеры программ](#-примеры-программ)
|
||||
- [Отладка](#-отладка-и-проверка)
|
||||
- [Ограничения](#-ограничения)
|
||||
- [План развития](#-план-развития)
|
||||
- [Лицензия](#-лицензия)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Особенности
|
||||
|
||||
- **Полностью русский синтаксис** — все инструкции, регистры и директивы на кириллице
|
||||
- **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску
|
||||
- **Шестипроходная сборка** — разделение логики на независимые этапы для прозрачности и отладки
|
||||
- **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бнд` (неинициализированные данные)
|
||||
- **Двухпроходная сборка** — традиционная схема с разрешением меток
|
||||
- **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бсс` (неинициализированные данные)
|
||||
- **Гибкая адресация памяти**:
|
||||
- абсолютная адресация по меткам `[переменная]`
|
||||
- абсолютная адресация по числовому адресу `[0x402000]`
|
||||
- косвенная адресация через регистр `[раикс]`
|
||||
- всё с RIP-relative кодированием для 64-битных перемещений
|
||||
- **CSV-логирование** — детальный файл с соответствием адресов, байтов, меток и исходных команд
|
||||
- **CSV-логирование** — детальный файл с соответствием адресов, байтов и исходных команд
|
||||
- **Более 50 инструкций** — полный набор основных команд x86-64
|
||||
- **Автоматическая система регрессионного тестирования** — 9+ тестов, проверяющих все аспекты работы ассемблера
|
||||
- **Минимализм зависимостей** — только стандартная библиотека Python
|
||||
|
||||
---
|
||||
- **Рефакторинг** — кодогенерация вынесена в отдельный модуль `kvs_pass2_encoder.py`
|
||||
|
||||
## 🏗 Архитектура
|
||||
|
||||
Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через **6 этапов**:
|
||||
Процесс трансляции исходного кода `.квс` в исполняемый файл `.elf` проходит через несколько этапов:
|
||||
|
||||
```
|
||||
Исходный файл (.квс)
|
||||
│
|
||||
▼ (квс_лексер.py) ← лексический анализ
|
||||
▼ (kvs_lexer.py)
|
||||
Файл токенов (.токены)
|
||||
│
|
||||
▼ (квс_парсер.py) ← синтаксический анализ
|
||||
▼ (kvs_parser.py)
|
||||
AST-представление (.аст)
|
||||
│
|
||||
▼ (квс_проход1.py) ← ПРОХОД 1: генерация CSV с заглушками (0xCC)
|
||||
CSV-файл (.csv) ← содержит все байты программы, метки, виртуальные адреса
|
||||
▼ (kvs_pass1.py)
|
||||
Первый проход: размеры, метки (.проход1)
|
||||
│
|
||||
▼ (квс_проход2.py) ← ПРОХОД 2: разрешение меток
|
||||
▼ (kvs_pass2.py + kvs_pass2_encoder.py)
|
||||
Второй проход: генерация кода (.csv)
|
||||
│
|
||||
▼ (квс_проход3.py) ← ПРОХОД 3: подстановка констант (.константа)
|
||||
│
|
||||
▼ (квс_проход4.py) ← ПРОХОД 4: перекодирование инструкций
|
||||
│
|
||||
▼ (квс_проход5.py) ← ПРОХОД 5: генерация корректных таблиц секций для совместимости с toolchain
|
||||
│
|
||||
▼ (квс_проход6.py) ← ПРОХОД 6: постобработка CSV для визуального анализа
|
||||
│
|
||||
▼ (квс_финсбор.py) ← сборка ELF из CSV
|
||||
ELF-файл (.elf)
|
||||
▼ (kvs_builder.py)
|
||||
ELF-компоновщик (.elf)
|
||||
│
|
||||
▼
|
||||
Исполняемый файл!
|
||||
```
|
||||
|
||||
### Почему 6 проходов?
|
||||
|
||||
1. **Первый проход** — неизвестно, какие адреса будут у меток. Ставим заглушки `0xCC`.
|
||||
2. **Второй проход** — после того как все адреса меток известны, заменяем заглушки.
|
||||
3. **Третий проход** — константы могут ссылаться друг на друга и на метки.
|
||||
4. **Четвёртый проход** — после подстановки чисел перегенерируем машинный код (разная длина инструкций).
|
||||
5. **Пятый проход** — генерируем корректные таблицы секций для работы `objdump`, `readelf` и GDB.
|
||||
6. **Шестой проход** — сортируем CSV для удобного визуального анализа человеком.
|
||||
|
||||
Такой подход позволяет **изолировать логику** каждого этапа и делает отладку прозрачной.
|
||||
|
||||
### Модули компилятора
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|-------------|
|
||||
| `квс_сборка.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) |
|
||||
| `приводящая_метка` | Имена меток на этом адресе |
|
||||
| `уводящий_адрес` | Строка `ЗАГЛУШКА имя` для неразрешённых меток |
|
||||
| `исходная_команда` | Исходная инструкция |
|
||||
| `команда_со_значениями` | Инструкция с подставленными адресами/константами |
|
||||
| `рассчитанный_уводящий_адрес` | Реальный адрес метки после разрешения |
|
||||
| `рассчитанный_байт` | Итоговый байт после перекодирования |
|
||||
|
||||
---
|
||||
| `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` | Общие данные (регистры, инструкции) |
|
||||
|
||||
## 🔧 Установка
|
||||
|
||||
|
|
@ -128,15 +81,13 @@ ELF-файл (.elf)
|
|||
git clone https://github.com/artradeskz/kvs.git
|
||||
cd kvs
|
||||
|
||||
# Установка прав на выполнение
|
||||
chmod +x квс_сборка.py
|
||||
# Установка прав на выполнение (при необходимости)
|
||||
chmod +x kvs_build.py
|
||||
|
||||
# Проверка работы
|
||||
python3 квс_сборка.py --help
|
||||
python3 kvs_build.py --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Создайте файл `hello.квс`:
|
||||
|
|
@ -146,11 +97,11 @@ python3 квс_сборка.py --help
|
|||
.глобал _start
|
||||
|
||||
_start:
|
||||
; write(1, msg, 21)
|
||||
; write(1, msg, 13)
|
||||
переместить_имм раикс,1
|
||||
переместить_имм рдиай,1
|
||||
переместить_имм рсиай, msg
|
||||
переместить_имм рдикс,21
|
||||
переместить_имм рдикс,13
|
||||
вызов_системы
|
||||
|
||||
; exit(0)
|
||||
|
|
@ -159,13 +110,13 @@ _start:
|
|||
вызов_системы
|
||||
|
||||
.данные
|
||||
msg: .строка "Привет мир!\n"
|
||||
msg: .строка_нуль "Hello, World!"
|
||||
```
|
||||
|
||||
### 2. Скомпилируйте:
|
||||
|
||||
```bash
|
||||
python3 квс_сборка.py hello.квс
|
||||
python3 kvs_build.py hello.квс
|
||||
```
|
||||
|
||||
### 3. Запустите:
|
||||
|
|
@ -177,71 +128,9 @@ python3 квс_сборка.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 тестов
|
||||
|
||||
---
|
||||
|
||||
## 📝 Синтаксис
|
||||
|
||||
### Структура программы
|
||||
|
|
@ -253,12 +142,14 @@ python3 тесты/прогон_тестов.py
|
|||
.данные ; секция данных (опциональна)
|
||||
; ... инициализированные данные ...
|
||||
|
||||
.бнд ; секция неинициализированных данных (опциональна)
|
||||
.бсс ; секция неинициализированных данных (опциональна)
|
||||
; ... переменные без начального значения ...
|
||||
```
|
||||
|
||||
### Метки
|
||||
|
||||
Метка — это имя, за которым следует двоеточие:
|
||||
|
||||
```assembly
|
||||
_start: ; метка _start
|
||||
переместить_имм раикс,1
|
||||
|
|
@ -267,6 +158,8 @@ _start: ; метка _start
|
|||
|
||||
### Комментарии
|
||||
|
||||
Однострочные комментарии начинаются с `;`:
|
||||
|
||||
```assembly
|
||||
; Это комментарий
|
||||
переместить_имм раикс,42 ; комментарий после инструкции
|
||||
|
|
@ -287,15 +180,13 @@ _start: ; метка _start
|
|||
|
||||
; Косвенная адресация через регистр
|
||||
переместить_имм рбикс, переменная
|
||||
загрузить раикс,[рбикс]
|
||||
загрузить раикс, [рбикс]
|
||||
|
||||
; Работа с BND-секцией
|
||||
.бнд
|
||||
; Работа с BSS-секцией
|
||||
.бсс
|
||||
счётчик: .байт 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 Система команд
|
||||
|
||||
### Загрузка констант
|
||||
|
|
@ -357,6 +248,10 @@ _start: ; метка _start
|
|||
| `переход_если_перенос` | CF = 1 |
|
||||
| `переход_если_нет_переноса` | CF = 0 |
|
||||
|
||||
**Короткие условные переходы** (префикс `короткий_`):
|
||||
- `короткий_переход_если_равно`, `короткий_переход_если_неравно`, и т.д.
|
||||
- Генерируют 2-байтовые инструкции (смещение ±127 байт)
|
||||
|
||||
### Системные вызовы
|
||||
|
||||
| Инструкция | Описание |
|
||||
|
|
@ -370,8 +265,6 @@ _start: ; метка _start
|
|||
вызов_системы
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💾 Регистры процессора
|
||||
|
||||
КВС поддерживает все основные 64-битные регистры x86-64:
|
||||
|
|
@ -387,24 +280,147 @@ _start: ; метка _start
|
|||
| `рсиай` | `есиай` | `эс` | `сил` |
|
||||
| `рдиай` | `едиай` | `ди` | `дил` |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Директивы ассемблера
|
||||
|
||||
| Директива | Назначение | Пример |
|
||||
|-----------|------------|--------|
|
||||
| `.текст` | Начало секции кода | `.текст` |
|
||||
| `.данные` | Начало секции инициализированных данных | `.данные` |
|
||||
| `.бнд` | Начало секции неинициализированных данных | `.бнд` |
|
||||
| `.бсс` | Начало секции неинициализированных данных | `.бсс` |
|
||||
| `.глобал` | Объявление глобальной метки | `.глобал _start` |
|
||||
| `.строка` | Строка без завершающего нуля | `.строка "Hello"` |
|
||||
| `.строка_нуль` | Строка с завершающим нулём | `.строка_нуль "Hello"` |
|
||||
| `.байт` | Последовательность байтов | `.байт 0x48, 0x65, 108` |
|
||||
| `.константа` | Определение константы | `.константа LEN = 10` |
|
||||
| `.резб` | Резервирование байтов в BND | `.резб 100` |
|
||||
| `.резс` | Резервирование слов в BND | `.резс 50` |
|
||||
| `.рездс` | Резервирование двойных слов в BND | `.рездс 25` |
|
||||
| `.резкс` | Резервирование квадрослов в BND | `.резкс 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
|
||||
```
|
||||
|
||||
## 🐛 Отладка и проверка
|
||||
|
||||
|
|
@ -426,7 +442,14 @@ strings программа.elf
|
|||
|
||||
### CSV-лог
|
||||
|
||||
После компиляции создаётся файл `программа.csv` с полной информацией о каждом байте программы.
|
||||
После компиляции создаётся файл `программа.csv` со структурой:
|
||||
|
||||
| адрес | байт | целевой_адрес | исходная_команда |
|
||||
|-------|------|---------------|------------------|
|
||||
| 0x401000 | 48 | | переместить_имм раикс, 1 |
|
||||
| 0x401001 | B8 | | |
|
||||
| 0x401002 | 01 | | |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
### Трассировка системных вызовов
|
||||
|
||||
|
|
@ -439,87 +462,70 @@ strace ./программа.elf
|
|||
```bash
|
||||
gdb ./программа.elf
|
||||
|
||||
(gdb) break _start
|
||||
(gdb) run
|
||||
(gdb) info registers
|
||||
(gdb) x/10i $rip
|
||||
(gdb) stepi
|
||||
(gdb) break _start ; установить точку останова
|
||||
(gdb) run ; запустить
|
||||
(gdb) info registers ; показать регистры
|
||||
(gdb) x/10i $rip ; показать 10 инструкций
|
||||
(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 кодирование для всех видов адресации
|
||||
- Полная совместимость с `objdump`, `readelf`, `gdb`
|
||||
- ✅ 64-битные, 32-битные, 16-битные и 8-битные регистры (AL, BL, CL, DL)
|
||||
- ✅ Непосредственная загрузка констант (imm) в регистры
|
||||
- ✅ Сравнение регистров и регистров с константами
|
||||
- ✅ Условные и безусловные переходы (длинные и короткие)
|
||||
- ✅ Арифметические операции (ADD, SUB, INC, DEC)
|
||||
- ✅ Логическая операция TEST
|
||||
- ✅ Системные вызовы Linux (syscall)
|
||||
- ✅ Секции `.текст`, `.данные` и `.бсс`
|
||||
- ✅ Строковые литералы с escape-последовательностями (`\n`, `\t`, `\\`, `\"`)
|
||||
- ✅ Директива `.байт` для raw-данных
|
||||
- ✅ Директива `.константа` для имён констант
|
||||
- ✅ **Абсолютная адресация** по меткам и числовым адресам `[метка]` / `[0x402000]`
|
||||
- ✅ **Косвенная адресация через регистр** `[раикс]`
|
||||
- ✅ RIP-relative кодирование для всех видов адресации
|
||||
|
||||
### Не поддерживается (в разработке)
|
||||
|
||||
- Регистр р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) на языке квс. Выводит мнемоники на русском.
|
||||
|
||||
---
|
||||
- ❌ Адресация со смещением `[reg + disp]`
|
||||
- ❌ Сложная адресация с индексом `[reg + reg*scale]` и полная SIB-адресация
|
||||
- ❌ Инструкции для работы со стеком (PUSH, POP, CALL, RET)
|
||||
- ❌ Старшие 8-битные регистры (AH, BH, CH, DH) и расширенные младшие (SPL, BPL, SIL, DIL)
|
||||
- ❌ Регистры R8–R15 и их байтовые/словные версии
|
||||
- ❌ Поддержка плавающей запятой (FPU/SSE)
|
||||
- ❌ Многомодульная компиляция
|
||||
- ❌ Макросы
|
||||
|
||||
## 📈 План развития
|
||||
|
||||
### Реализовано (v2.0)
|
||||
|
||||
- Лексер с поддержкой `[]`, `+`, `-`, `*`
|
||||
- Лексер с токенами `[`, `]`, `NUMBER`
|
||||
- Парсер с поддержкой операндов в квадратных скобках
|
||||
- Шестипроходная схема сборки (включая генерацию таблиц секций и сортировку CSV)
|
||||
- Первый проход с корректным расчётом размера инструкций с памятью
|
||||
- Второй проход с кодированием абсолютной адресации через RIP-relative
|
||||
- Поддержка меток в операндах памяти `[переменная]`
|
||||
- Поддержка числовых адресов `[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*
|
||||
*Только для образовательных целей. Используйте на свой страх и риск.*
|
||||
```
|
||||
|
|
@ -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 квс_сборка.py <файл.квс>")
|
||||
print("Использование: python kvs_build.py <файл.квс>")
|
||||
sys.exit(1)
|
||||
|
||||
source_file = sys.argv[1]
|
||||
|
|
@ -23,19 +23,17 @@ 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 = [
|
||||
("квс_лексер.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"),
|
||||
("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"),
|
||||
]
|
||||
|
||||
for script, args, description in stages:
|
||||
|
|
@ -50,9 +48,10 @@ 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}")
|
||||
217
kvs_builder.py
Normal file
217
kvs_builder.py
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#!/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])
|
||||
231
kvs_data.py
231
kvs_data.py
|
|
@ -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,45 +331,6 @@ VARIABLE_SIZE_INSTRUCTIONS = {
|
|||
"переместить_со_знаком", # зависит от размера регистра
|
||||
}
|
||||
|
||||
# === ИНСТРУКЦИИ, ВСЕГДА ТРЕБУЮЩИЕ ЗАГЛУШКУ НА ПЕРВОМ ПРОХОДЕ ===
|
||||
# Эти инструкции содержат целевые метки в операндах, которые могут быть
|
||||
# определены позже (в том числе в других секциях)
|
||||
ALWAYS_STUB_INSTRUCTIONS = {
|
||||
# Безусловные переходы
|
||||
"переход",
|
||||
"короткий_переход",
|
||||
|
||||
# Вызовы
|
||||
"вызвать",
|
||||
|
||||
# Циклы
|
||||
"цикл",
|
||||
|
||||
# Длинные условные переходы (32-битное смещение)
|
||||
"переход_если_равно",
|
||||
"переход_если_неравно",
|
||||
"переход_если_меньше",
|
||||
"переход_если_больше",
|
||||
"переход_если_меньше_или_равно",
|
||||
"переход_если_больше_или_равно",
|
||||
"переход_если_перенос",
|
||||
"переход_если_нет_переноса",
|
||||
"переход_если_ноль",
|
||||
"переход_если_не_ноль",
|
||||
|
||||
# Короткие условные переходы (8-битное смещение)
|
||||
"короткий_переход_если_равно",
|
||||
"короткий_переход_если_неравно",
|
||||
"короткий_переход_если_меньше",
|
||||
"короткий_переход_если_больше",
|
||||
"короткий_переход_если_меньше_или_равно",
|
||||
"короткий_переход_если_больше_или_равно",
|
||||
"короткий_переход_если_перенос",
|
||||
"короткий_переход_если_нет_переноса",
|
||||
"короткий_переход_если_ноль",
|
||||
"короткий_переход_если_не_ноль",
|
||||
}
|
||||
|
||||
|
||||
# === ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===
|
||||
|
||||
|
|
|
|||
|
|
@ -2,24 +2,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Лексер КВС (русская версия)
|
||||
Лексер КВС
|
||||
Принимает: исходный .квс файл
|
||||
Выдаёт: текстовый файл с токенами (по одному токену на строку)
|
||||
Формат вывода: ТИП:ЗНАЧЕНИЕ
|
||||
|
||||
Все типы токенов переведены на русский язык:
|
||||
- СТРОКА → строковый литерал
|
||||
- СЛОВО → идентификатор
|
||||
- ЧИСЛО → числовой литерал
|
||||
- ЗАПЯТАЯ → разделитель
|
||||
- ДВОЕТОЧИЕ → для меток
|
||||
- СК_ОТКР → [
|
||||
- СК_ЗАКР → ]
|
||||
- ПЛЮС → +
|
||||
- МИНУС → -
|
||||
- ЗВЕЗДА → *
|
||||
- НОВСТР → маркер конца строки
|
||||
|
||||
Поддерживает сложную адресацию:
|
||||
- [ ] - квадратные скобки
|
||||
- + - плюс
|
||||
|
|
@ -59,7 +46,7 @@ def is_dec_number(s):
|
|||
return True
|
||||
|
||||
def tokenize_line(line, line_num):
|
||||
"""Разбирает строку в токены (русские названия типов)"""
|
||||
"""Разбирает строку в токены"""
|
||||
# Удаляем комментарии
|
||||
semi = -1
|
||||
for idx in range(len(line)):
|
||||
|
|
@ -98,53 +85,43 @@ def tokenize_line(line, line_num):
|
|||
if i >= n:
|
||||
raise ValueError(error_unterminated_string(line_num))
|
||||
i += 1
|
||||
tokens.append(('СТРОКА', s))
|
||||
tokens.append(('STRING', s))
|
||||
continue
|
||||
|
||||
# Одиночные символы-разделители
|
||||
if ch == ',':
|
||||
tokens.append(('ЗАПЯТАЯ', ','))
|
||||
tokens.append(('COMMA', ','))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == ':':
|
||||
tokens.append(('ДВОЕТОЧИЕ', ':'))
|
||||
tokens.append(('COLON', ':'))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Токены для сложной адресации
|
||||
# ТОКЕНЫ ДЛЯ СЛОЖНОЙ АДРЕСАЦИИ
|
||||
if ch == '[':
|
||||
tokens.append(('СК_ОТКР', '['))
|
||||
tokens.append(('LBRACKET', '['))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == ']':
|
||||
tokens.append(('СК_ЗАКР', ']'))
|
||||
tokens.append(('RBRACKET', ']'))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == '+':
|
||||
tokens.append(('ПЛЮС', '+'))
|
||||
tokens.append(('PLUS', '+'))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == '-':
|
||||
# Проверяем, не является ли минус частью отрицательного числа
|
||||
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(('МИНУС', '-'))
|
||||
tokens.append(('MINUS', '-'))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ch == '*':
|
||||
tokens.append(('ЗВЕЗДА', '*'))
|
||||
tokens.append(('STAR', '*'))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
|
|
@ -157,11 +134,11 @@ def tokenize_line(line, line_num):
|
|||
if word:
|
||||
# Определяем тип токена
|
||||
if is_hex_number(word):
|
||||
tokens.append(('ЧИСЛО', word))
|
||||
tokens.append(('NUMBER', word))
|
||||
elif is_dec_number(word):
|
||||
tokens.append(('ЧИСЛО', word))
|
||||
tokens.append(('NUMBER', word))
|
||||
else:
|
||||
tokens.append(('СЛОВО', word))
|
||||
tokens.append(('WORD', word))
|
||||
|
||||
i = j
|
||||
|
||||
|
|
@ -176,7 +153,7 @@ def lex_source(source_text):
|
|||
try:
|
||||
tokens = tokenize_line(line, line_num)
|
||||
if tokens:
|
||||
all_tokens.append(('НОВСТР', str(line_num)))
|
||||
all_tokens.append(('NEWLINE', str(line_num)))
|
||||
for tok_type, tok_value in tokens:
|
||||
all_tokens.append((tok_type, tok_value))
|
||||
except ValueError as e:
|
||||
347
kvs_parser.py
Normal file
347
kvs_parser.py
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
#!/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}")
|
||||
400
kvs_pass1.py
Normal file
400
kvs_pass1.py
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
#!/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}")
|
||||
349
kvs_pass2.py
Normal file
349
kvs_pass2.py
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#!/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}")
|
||||
|
|
@ -13,7 +13,7 @@ import sys
|
|||
from kvs_data import INSTRUCTIONS, REGISTERS, get_reg_info
|
||||
|
||||
# Импорт из fixsize
|
||||
from kvs_encoder_fixsize import (
|
||||
from kvs_pass2_encoder_fixsize import (
|
||||
encode_mov_reg_reg,
|
||||
encode_mov_reg8_imm8,
|
||||
encode_cmp_reg_reg,
|
||||
|
|
@ -41,7 +41,7 @@ from kvs_encoder_fixsize import (
|
|||
)
|
||||
|
||||
# Импорт из mutsize
|
||||
from kvs_encoder_mutsize import (
|
||||
from kvs_pass2_encoder_mutsize import (
|
||||
parse_operand,
|
||||
parse_memory_operand,
|
||||
encode_mov_reg_mem_absolute,
|
||||
|
|
@ -186,114 +186,44 @@ def encode_mov_mem_reg_absolute(reg_info, mem_info, current_pos, vaddr_text):
|
|||
return code
|
||||
|
||||
|
||||
def encode_mov_reg_mem_indirect(reg_info: dict, base_reg: str) -> bytearray:
|
||||
def encode_mov_reg_mem_indirect(reg_info, base_reg):
|
||||
"""
|
||||
Кодирует MOV reg, [base_reg] — косвенная адресация через регистр.
|
||||
Кодирует MOV reg, [base_reg] — косвенная адресация.
|
||||
Пример: mov rax, [rbx] → 48 8B 03
|
||||
|
||||
Особые случаи x86-64:
|
||||
- [rsp] и [r12] (индексы 4 и 12) требуют SIB-байта при mod=00
|
||||
- [rbp] и [r13] (индексы 5 и 13) требуют disp8=0 при mod=00
|
||||
Аргументы:
|
||||
- reg_info: словарь от get_reg_info() с ключами 'index' (int) и 'size' (int)
|
||||
- base_reg: строка с именем регистра, например 'рбикс'
|
||||
|
||||
REGISTERS[base_reg] возвращает число-индекс (int), НЕ словарь.
|
||||
"""
|
||||
code = bytearray()
|
||||
reg = reg_info['index']
|
||||
base = REGISTERS[base_reg] # REGISTERS возвращает int (индекс регистра)
|
||||
|
||||
# === 1. Получаем индексы регистров ===
|
||||
reg_index = reg_info['index']
|
||||
reg_size = reg_info['size']
|
||||
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
|
||||
|
||||
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
|
||||
# Особый случай: [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
|
||||
else:
|
||||
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)
|
||||
# mod=00, reg, r/m=base
|
||||
modrm = ((reg & 7) << 3) | (base & 7)
|
||||
code.append(modrm)
|
||||
|
||||
return code
|
||||
|
||||
|
||||
|
||||
|
||||
def encode_mov_mem_reg_indirect(base_reg, reg_info):
|
||||
"""
|
||||
Кодирует MOV [base_reg], reg — косвенная адресация.
|
||||
|
|
@ -962,10 +892,7 @@ def encode_pop_reg(operands): # ДУБЛИКАТ
|
|||
code.append(0x58 + reg)
|
||||
else:
|
||||
code.append(0x41)
|
||||
if reg<12: # так тесты работают
|
||||
code.append(0x58 + (reg - 8))
|
||||
else:
|
||||
code.append(0x58 + (reg - 8))
|
||||
code.append(0x58 + (reg - 8))
|
||||
elif size == 16:
|
||||
code.append(0x66)
|
||||
if reg < 8:
|
||||
247
kvs_verify.py
Normal file
247
kvs_verify.py
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
#!/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)
|
||||
21
todo.txt
21
todo.txt
|
|
@ -252,6 +252,21 @@ Pass2 (шаг 5):
|
|||
|
||||
сохранить_байт и загрузить_байт_из_памяти есть в INSTRUCTIONS, но не реализованы в диспетчере encode_instruction
|
||||
|
||||
тестировать вытолкнуть
|
||||
тестировать косвенную адресацию на р9-15
|
||||
бага с двоеточием в строке
|
||||
|
||||
|
||||
|
||||
|
||||
это же почти стандартная библиотека зародилась?...
|
||||
|
||||
|
||||
itoa — преобразование числа в строку (аналог sprintf/itoa в C)
|
||||
|
||||
длина_числа — утилита для работы с числами
|
||||
|
||||
Вывод в терминал — через write (аналог puts/printf)
|
||||
|
||||
Работа с .бнд — выделение памяти под переменные
|
||||
|
||||
Циклы и переходы — управляющие конструкции
|
||||
|
||||
Следующий шаг — atoi (строка → число) и можно будет делать полноценный ввод/вывод.
|
||||
|
|
@ -1,878 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
#!/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}"
|
||||
351
квс_парсер.py
351
квс_парсер.py
|
|
@ -1,351 +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
|
||||
|
||||
|
||||
# ==================== СОСТОЯНИЕ ПАРСЕРА ====================
|
||||
# Хранится в словаре, передаётся во все функции явно
|
||||
|
||||
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
955
квс_проход1.py
|
|
@ -1,955 +0,0 @@
|
|||
#!/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
203
квс_проход2.py
|
|
@ -1,203 +0,0 @@
|
|||
#!/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
141
квс_проход3.py
|
|
@ -1,141 +0,0 @@
|
|||
#!/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
292
квс_проход4.py
|
|
@ -1,292 +0,0 @@
|
|||
#!/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
678
квс_проход5.py
|
|
@ -1,678 +0,0 @@
|
|||
#!/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
131
квс_проход6.py
|
|
@ -1,131 +0,0 @@
|
|||
#!/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()
|
||||
104
квс_финсбор.py
104
квс_финсбор.py
|
|
@ -1,104 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
#!/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
|
||||
136
тесты/bss_bug.txt
Normal file
136
тесты/bss_bug.txt
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
## Исходник
|
||||
|
||||
```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` для меток `.бнд`.
|
||||
53
тесты/bss_bug.квс
Normal file
53
тесты/bss_bug.квс
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; =============================================
|
||||
; ТЕСТ 1: Абсолютная адресация в .data
|
||||
; =============================================
|
||||
переместить_имм раикс, 0x42
|
||||
сохранить [абс_данные], раикс
|
||||
|
||||
; =============================================
|
||||
; ТЕСТ 2: Косвенная адресация в .data
|
||||
; =============================================
|
||||
переместить_имм рбикс, косв_данные
|
||||
переместить_имм раикс, 0x99
|
||||
сохранить [рбикс], раикс
|
||||
|
||||
; =============================================
|
||||
; ТЕСТ 3: Косвенная адресация в .бнд
|
||||
; =============================================
|
||||
переместить_имм рсикс, неиниц_бнд
|
||||
переместить_имм раикс, 0x77
|
||||
сохранить [рсикс], раикс
|
||||
|
||||
; =============================================
|
||||
; Читаем всё обратно и проверяем
|
||||
; =============================================
|
||||
|
||||
; Читаем абсолютную из .data
|
||||
загрузить р8, [абс_данные]
|
||||
|
||||
; Читаем косвенную из .data
|
||||
загрузить р9, [рбикс]
|
||||
|
||||
; Читаем косвенную из .бнд
|
||||
загрузить р10, [рсикс]
|
||||
|
||||
; =============================================
|
||||
; ЗАВЕРШЕНИЕ: возвращаем значение из абсолютной .data
|
||||
; =============================================
|
||||
загрузить рдиай, [абс_данные]
|
||||
переместить_имм раикс, 60
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
абс_данные:
|
||||
.байт 0
|
||||
косв_данные:
|
||||
.байт 0
|
||||
|
||||
.бнд
|
||||
неиниц_бнд:
|
||||
.резб 8
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
|
||||
extensions = {'.аст', '.csv', '.токены', '.проход1', '.elf', '.log', '.txt'}
|
||||
extensions = {'.аст', '.csv', '.токены', '.проход1', '.elf'}
|
||||
|
||||
for file in os.listdir('.'):
|
||||
if os.path.isfile(file) and os.path.splitext(file)[1] in extensions:
|
||||
|
|
|
|||
67
тесты/itoa0.квс
Normal file
67
тесты/itoa0.квс
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
.текст
|
||||
.глобал _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
|
||||
80
тесты/itoa1.квс
Normal file
80
тесты/itoa1.квс
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
.текст
|
||||
.глобал _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
|
||||
94
тесты/itoa2.квс
Normal file
94
тесты/itoa2.квс
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
.текст
|
||||
.глобал _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
|
||||
94
тесты/itoa3.квс
Normal file
94
тесты/itoa3.квс
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
.текст
|
||||
.глобал _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
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
активен;папка;описание
|
||||
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.
|
337
тесты/t1.py
Normal file
337
тесты/t1.py
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#!/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()
|
||||
9
тесты/test_bss.квс
Normal file
9
тесты/test_bss.квс
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
_start:
|
||||
переместить_имм раикс, 60
|
||||
переместить_имм рдиай, 0
|
||||
вызов_системы
|
||||
|
||||
.бнд
|
||||
буфер: .резб 1048576 ; 1 МБ = 1024 * 1024
|
||||
45
тесты/test_bss_only.квс
Normal file
45
тесты/test_bss_only.квс
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; =============================================
|
||||
; ТЕСТ 1: Абсолютная адресация в .бнд
|
||||
; =============================================
|
||||
переместить_имм раикс, 0x42
|
||||
сохранить [абс_перем], раикс
|
||||
|
||||
; =============================================
|
||||
; ТЕСТ 2: Косвенная адресация в .бнд
|
||||
; =============================================
|
||||
переместить_имм рбикс, косв_перем
|
||||
переместить_имм раикс, 0x99
|
||||
сохранить [рбикс], раикс
|
||||
|
||||
; =============================================
|
||||
; ТЕСТ 3: Ещё одна косвенная адресация в .бнд
|
||||
; =============================================
|
||||
переместить_имм рсикс, ещё_перем
|
||||
переместить_имм раикс, 0x77
|
||||
сохранить [рсикс], раикс
|
||||
|
||||
; =============================================
|
||||
; Читаем всё обратно
|
||||
; =============================================
|
||||
загрузить р8, [абс_перем]
|
||||
загрузить р9, [рбикс]
|
||||
загрузить р10, [рсикс]
|
||||
|
||||
; =============================================
|
||||
; ЗАВЕРШЕНИЕ: возвращаем из абс_перем
|
||||
; =============================================
|
||||
загрузить рдиай, [абс_перем]
|
||||
переместить_имм раикс, 60
|
||||
вызов_системы
|
||||
|
||||
.бнд
|
||||
абс_перем:
|
||||
.резб 1
|
||||
косв_перем:
|
||||
.резб 1
|
||||
ещё_перем:
|
||||
.резб 8
|
||||
15
тесты/test_label.квс
Normal file
15
тесты/test_label.квс
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Запись значения 42 в переменную по метке
|
||||
переместить_имм раикс, 42
|
||||
сохранить [переменная], раикс
|
||||
|
||||
; Завершение с кодом 42
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [переменная]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
переменная: .байт 0
|
||||
|
|
@ -1,8 +1,3 @@
|
|||
; тестирует абсолютную адресацию по адресу
|
||||
; не вылетел - значит работает (дополнительно проверить отладчиком)
|
||||
; ./тесты/тест_адресации1.elf
|
||||
; echo $? # выведет 42
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
34
тесты/test_minimal2.квс
Normal file
34
тесты/test_minimal2.квс
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; === ТЕСТ 1: Запись через косвенную адресацию ===
|
||||
; Загружаем адрес переменной в регистр
|
||||
переместить_имм рбикс, тестовая_переменная
|
||||
|
||||
; Записываем 42 через косвенную адресацию [рбикс]
|
||||
переместить_имм раикс, 42
|
||||
сохранить [рбикс], раикс
|
||||
|
||||
; === ТЕСТ 2: Чтение через косвенную адресацию ===
|
||||
; Загружаем адрес другой переменной в регистр
|
||||
переместить_имм рсикс, вторая_переменная
|
||||
|
||||
; Читаем через косвенную адресацию [рсикс]
|
||||
загрузить рдикс, [рсикс]
|
||||
|
||||
; === ТЕСТ 3: Проверяем, что записалось ===
|
||||
; Читаем первую переменную через косвенную
|
||||
загрузить рсикс, [рбикс]
|
||||
|
||||
; === ЗАВЕРШЕНИЕ ===
|
||||
; Выходим с кодом из первой переменной (должно быть 42)
|
||||
переместить_имм раикс, 60
|
||||
переместить рдиай, рсикс
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
тестовая_переменная:
|
||||
.байт 0
|
||||
вторая_переменная:
|
||||
.байт 99
|
||||
50
тесты/test_minimal3.квс
Normal file
50
тесты/test_minimal3.квс
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
.текст
|
||||
.глобал _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 байт под указатель на строку
|
||||
28
тесты/test_success.квс
Normal file
28
тесты/test_success.квс
Normal 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 изобретать (или кучу?)
|
||||
30
тесты/test_success2.квс
Normal file
30
тесты/test_success2.квс
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
.текст
|
||||
.глобал _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)
|
||||
36
тесты/test_success3.квс
Normal file
36
тесты/test_success3.квс
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; 25 - длина сообщения из метки msg
|
||||
; Запись значения 25 в переменную в .бнд
|
||||
переместить_имм рдикс, 25
|
||||
сохранить [переменная], рдикс
|
||||
|
||||
; Загружаем адрес строки и сохраняем в буфер в .бнд
|
||||
загрузить_адрес раикс, [msg]
|
||||
сохранить [буфер_строки], раикс
|
||||
|
||||
; перепишем для теста
|
||||
переместить_имм рдикс, 0
|
||||
|
||||
|
||||
; Системный вызов write(1, msg, len)
|
||||
переместить_имм раикс, 1
|
||||
переместить_имм рдиай, 1
|
||||
загрузить рсиай, [буфер_строки]
|
||||
загрузить рдикс, [переменная]
|
||||
вызов_системы
|
||||
|
||||
|
||||
; Системный вызов exit()
|
||||
переместить_имм раикс, 60
|
||||
переместить_имм рдиай, 44
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
msg: .строка "Тест пройден!\n"
|
||||
|
||||
.бнд
|
||||
переменная: .резкс 1 ; 8 байт под длину
|
||||
буфер_строки: .резкс 1 ; 8 байт под указатель на строку
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
.текст
|
||||
.глобал _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"
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
;./тесты/вывод_многозначного/вывод_многозначного.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 байт с запасом
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
;./тесты/вывод_однозначного/вывод_однозначного.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
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
;./тесты/вызов_функции/вызов_функции.elf
|
||||
;echo $? # должен быть 30
|
||||
|
||||
; ============================================================
|
||||
; вызов_функции.квс - тест вызова функций и возврата
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Вызов функции с двумя аргументами
|
||||
переместить_имм рдиай, 10 ; первый аргумент = 10
|
||||
переместить_имм рсиай, 20 ; второй аргумент = 20
|
||||
вызвать сложить_два_числа
|
||||
|
||||
; Результат в RAX — используем как код возврата
|
||||
переместить рдиай, раикс ; первый аргумент sys_exit = результат
|
||||
переместить_имм раикс, 60 ; sys_exit
|
||||
вызов_системы
|
||||
|
||||
сложить_два_числа:
|
||||
; Пролог
|
||||
втолкнуть рбипи
|
||||
переместить рбипи, рсипи
|
||||
|
||||
; Тело функции: сложить аргументы
|
||||
прибавить рдиай, рсиай ; RDI += RSI
|
||||
переместить раикс, рдиай ; результат в RAX
|
||||
|
||||
; Эпилог
|
||||
переместить рсипи, рбипи
|
||||
вытолкнуть рбипи
|
||||
вернуться
|
||||
|
||||
.данные
|
||||
тестовая_переменная: .байт 0
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#!/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;вычитание_цепочка;Тест последовательного вычитания
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
; тесты/вычитание/вычитание.квс
|
||||
; Тест вычитания: 50 - 8 = 42
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Загружаем числа
|
||||
переместить_имм раикс, 50
|
||||
переместить_имм рбикс, 12
|
||||
|
||||
; Вычитаем: раикс = раикс - рбикс
|
||||
вычесть раикс, рбикс
|
||||
|
||||
; Результат (42) в RDI для exit
|
||||
переместить рдиай, раикс
|
||||
|
||||
; Выход
|
||||
переместить_имм раикс, 60
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
_пусто: .байт 0
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Системный вызов write(1, msg, len)
|
||||
переместить_имм раикс, 1 ; номер syscall write = 1
|
||||
переместить_имм рдиай, 1 ; stdout = 1
|
||||
переместить_имм рсиай, msg ; указатель на строку
|
||||
переместить_имм рдикс, 21 ; длина строки
|
||||
вызов_системы
|
||||
|
||||
; Системный вызов exit(0)
|
||||
переместить_имм раикс, 60 ; номер syscall exit = 60
|
||||
переместить_имм рдиай, 0 ; код возврата
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
msg: .строка "Приве : мир!\n"
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
#!/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()
|
||||
73
тесты/длина_числа.квс
Normal file
73
тесты/длина_числа.квс
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
.текст
|
||||
.глобал _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
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
;./тесты/остаток_от_деления/остаток_от_деления.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
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
; Тест операции сложения (прибавить)
|
||||
; Результат должен быть 42
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Загружаем первое число в регистр
|
||||
переместить_имм раикс, 40
|
||||
|
||||
; Загружаем второе число в другой регистр
|
||||
переместить_имм рбикс, 2
|
||||
|
||||
; Складываем: раикс = раикс + рбикс (40 + 2)
|
||||
прибавить раикс, рбикс
|
||||
|
||||
; Результат (42) теперь в RAX
|
||||
; Перемещаем результат в RDI (первый аргумент для exit)
|
||||
переместить рдиай, раикс
|
||||
|
||||
; Системный вызов exit (код 60)
|
||||
переместить_имм раикс, 60
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
тестовая_переменная: .байт 0
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
#!/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()
|
||||
66
тесты/т1.квс
Normal file
66
тесты/т1.квс
Normal 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
|
||||
вызов_системы
|
||||
112
тесты/т1_.квс
Normal file
112
тесты/т1_.квс
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
.глобал _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
|
||||
вызов_системы
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
; тестирует абсолютную адресацию по метке
|
||||
; не вылетел - значит работает (дополнительно проверить отладчиком)
|
||||
; ./тесты/тест_адресации2.elf
|
||||
; echo $? # выведет 43
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Запись значения 43 в переменную по метке
|
||||
переместить_имм раикс, 43
|
||||
сохранить [переменная], раикс
|
||||
|
||||
; Завершение с кодом 43
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [переменная]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
переменная: .байт 0
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
; тестирует косвенную адресацию через регистр
|
||||
; не вылетел - значит работает (дополнительно проверить отладчиком)
|
||||
; ./тесты/тест_адресации3.elf
|
||||
; echo $? # выведет 99
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Загружаем адрес переменной в регистр RBX
|
||||
переместить_имм рбикс, переменная
|
||||
|
||||
; Запись значения 99 в переменную через косвенную адресацию
|
||||
переместить_имм раикс, 99
|
||||
сохранить [рбикс], раикс
|
||||
|
||||
; Завершение с кодом 99 (читаем из переменной через косвенную адресацию)
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [рбикс]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
переменная: .байт 0
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р10.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r10.квс - тест косвенной адресации через R10
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р10 ; argv[1] в R10
|
||||
|
||||
; Загружаем первый байт из argv[1] через R10
|
||||
загрузить ал, [р10]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р11.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r11.квс - тест косвенной адресации через R11
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р11 ; argv[1] в R11
|
||||
|
||||
; Загружаем первый байт из argv[1] через R11
|
||||
загрузить ал, [р11]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р12.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r12.квс - тест косвенной адресации через R12
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р12 ; argv[1] в R12
|
||||
|
||||
; Загружаем первый байт из argv[1] через R12
|
||||
загрузить ал, [р12]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р13.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r13.квс - тест косвенной адресации через R13
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р13 ; argv[1] в R13
|
||||
|
||||
; Загружаем первый байт из argv[1] через R13
|
||||
загрузить ал, [р13]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р14.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r14.квс - тест косвенной адресации через R14
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р14 ; argv[1] в R14
|
||||
|
||||
; Загружаем первый байт из argv[1] через R14
|
||||
загрузить ал, [р14]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р15.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r15.квс - тест косвенной адресации через R15
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р15 ; argv[1] в R15
|
||||
|
||||
; Загружаем первый байт из argv[1] через R15
|
||||
загрузить ал, [р15]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р8.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r8.квс - тест косвенной адресации через R8
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р8 ; argv[1] в R8
|
||||
|
||||
; Загружаем первый байт из argv[1] через R8
|
||||
загрузить ал, [р8]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
;./тесты/тест_адресации_р9.elf "A"
|
||||
;echo $? # должен быть 65
|
||||
|
||||
; ============================================================
|
||||
; тест_адресации_r9.квс - тест косвенной адресации через R8
|
||||
; ============================================================
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; Получаем argv[1]
|
||||
вытолкнуть рсикс ; argc
|
||||
вытолкнуть рсиай ; argv[0]
|
||||
вытолкнуть р9 ; argv[1] в R9
|
||||
|
||||
; Загружаем первый байт из argv[1] через R9
|
||||
загрузить ал, [р9]
|
||||
|
||||
; Сохраняем в переменную (не обязательно, но для проверки)
|
||||
сохранить [байт], ал
|
||||
|
||||
; Выход с кодом = загруженный байт
|
||||
переместить_имм раикс, 60
|
||||
загрузить рдиай, [байт]
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
байт: .байт 0
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
; тестируем работу bss
|
||||
; readelf -l ./тесты/тест_бнд.elf
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
_start:
|
||||
переместить_имм раикс, 60
|
||||
переместить_имм рдиай, 0
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
пустые_данные: .байт 0
|
||||
|
||||
.бнд
|
||||
буфер: .резб 1048576 ; 1 МБ = 1024 * 1024
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
; тест_бнд_1.квс
|
||||
; Полный цикл: запись → очистка → чтение из BSS
|
||||
|
||||
.текст
|
||||
.глобал _start
|
||||
|
||||
_start:
|
||||
; =============================================
|
||||
; ТЕСТ 1: Абсолютная адресация в .бнд (полный цикл)
|
||||
; =============================================
|
||||
|
||||
; 1. Записываем значение в BSS
|
||||
переместить_имм рдиай, 0x42
|
||||
сохранить [абс_перем], рдиай
|
||||
|
||||
; 2. Очищаем регистр (чтобы проверить, что значение реально из BSS)
|
||||
переместить_имм рдиай, 0x00
|
||||
|
||||
; 3. Загружаем значение обратно из BSS
|
||||
загрузить рдиай, [абс_перем]
|
||||
|
||||
; 4. Выход с кодом = значению из BSS
|
||||
переместить_имм раикс, 60
|
||||
вызов_системы
|
||||
|
||||
.данные
|
||||
; Заглушка для pass5 (обязательна!)
|
||||
пустые_данные: .байт 0
|
||||
|
||||
.бнд
|
||||
абс_перем: .резб 1
|
||||
_запас: .резб 4095 ; гарантирует, что BSS внутри страницы
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#!/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
Loading…
Reference in a new issue