From 84646f02c6e9ea6379d15613508e1d4873996d9e Mon Sep 17 00:00:00 2001 From: mzhoot Date: Sat, 23 May 2026 18:56:43 +0300 Subject: [PATCH] =?UTF-8?q?=D1=83=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=80=D0=B0=D1=81=D1=81=D1=87=D0=B5=D1=82=20?= =?UTF-8?q?=D1=81=D0=BC=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 +-- kvs_build.py | 1 - kvs_pass1.py | 319 ++++++++++++++-------- kvs_pass2.py | 220 +++++++-------- kvs_pass4.py | 190 ++++++------- тесты/сложный_тест.квс | 204 ++++++++++++++ 6 files changed, 604 insertions(+), 370 deletions(-) create mode 100644 тесты/сложный_тест.квс diff --git a/README.md b/README.md index 26d353c..0034b9f 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ - **Полностью русский синтаксис** — все инструкции, регистры и директивы на кириллице - **Генерация исполняемых ELF-файлов** — 64-битные ELF для Linux, готовые к запуску -- **Четырёхпроходная сборка** — традиционная схема с разрешением меток, констант и перекодированием +- **Четырёхпроходная сборка** — традиционная схема с разрешением меток, подстановкой констант и перекодированием - **Поддержка секций** — разделение на `.текст` (код), `.данные` (данные) и `.бнд` (неинициализированные данные) - **Гибкая адресация памяти**: - абсолютная адресация по меткам `[переменная]` - абсолютная адресация по числовому адресу `[0x402000]` - косвенная адресация через регистр `[раикс]` - всё с RIP-relative кодированием для 64-битных перемещений -- **CSV-логирование** — детальный файл с соответствием адресов, байтов и исходных команд +- **CSV-логирование** — детальный файл с соответствием адресов, байтов, меток и исходных команд - **Более 50 инструкций** — полный набор основных команд x86-64 - **Минимализм зависимостей** — только стандартная библиотека Python @@ -48,9 +48,9 @@ AST-представление (.аст) │ ▼ (kvs_pass1.py) ← ПРОХОД 1: генерация CSV с заглушками (0xCC) -CSV-файл (.csv) ← содержит все байты программы, метки, заглушки +CSV-файл (.csv) ← содержит все байты программы, метки, виртуальные адреса │ - ▼ (kvs_pass2.py) ← ПРОХОД 2: разрешение меток (замена заглушек) + ▼ (kvs_pass2.py) ← ПРОХОД 2: разрешение меток │ ▼ (kvs_pass3.py) ← ПРОХОД 3: подстановка констант (.константа) │ @@ -68,24 +68,24 @@ ELF-файл (.elf) | Файл | Назначение | |------|-------------| | `kvs_build.py` | Главный сборочный скрипт (запускает все этапы по порядку) | -| `kvs_lexer.py` | Лексический анализ, разбор на токены (с поддержкой `[]`, `+`, `-`, `*`) | +| `kvs_lexer.py` | Лексический анализ, разбор на токены (поддерживает `[]`, `+`, `-`, `*`) | | `kvs_parser.py` | Синтаксический анализ, построение AST (директивы, метки, инструкции) | | `kvs_data.py` | Общие данные: регистры, таблица инструкций, вспомогательные функции | -| `kvs_encoder.py` | Фасад кодировщика (реэкспорт из fixsize + mutsize) | +| `kvs_encoder.py` | Фасад кодировщика (объединяет fixsize и mutsize) | | `kvs_encoder_fixsize.py` | Кодировщик инструкций фиксированного размера | | `kvs_encoder_mutsize.py` | Кодировщик инструкций переменного размера + диспетчер | -| `kvs_pass1.py` | Первый проход — вычисление размеров секций, адресов меток, генерация CSV с заглушками | -| `kvs_pass2.py` | Второй проход — разрешение меток | -| `kvs_pass3.py` | Третий проход — подстановка констант | -| `kvs_pass4.py` | Четвёртый проход — перекодирование инструкций | +| `kvs_pass1.py` | Первый проход — генерация CSV с заглушками для неизвестных меток | +| `kvs_pass2.py` | Второй проход — разрешение меток (замена заглушек на реальные адреса) | +| `kvs_pass3.py` | Третий проход — подстановка констант из директив `.константа` | +| `kvs_pass4.py` | Четвёртый проход — перекодирование инструкций с подставленными значениями | | `kvs_builder.py` | Сборка ELF: чтение CSV, заполнение байтов по адресам, установка прав на исполнение | ### Почему 4 прохода? 1. **Первый проход** — неизвестно, какие адреса будут у меток (метка может использоваться до определения). Вместо остановки ставим заглушки `0xCC` и продолжаем. -2. **Второй проход** — после того как все адреса меток известны, можно заменить заглушки на реальные значения. +2. **Второй проход** — после того как все адреса меток известны, заменяем заглушки на реальные значения. 3. **Третий проход** — константы могут ссылаться друг на друга и на метки, поэтому их подстановка делается отдельно. -4. **Четвёртый проход** — после подстановки чисел адреса/констант нужно перегенерировать машинный код (разные операнды могут менять длину инструкции). +4. **Четвёртый проход** — после подстановки чисел (адресов/констант) нужно перегенерировать машинный код, так как разные операнды могут менять длину инструкции. ### Структура CSV-файла @@ -93,6 +93,7 @@ ELF-файл (.elf) |---------|-------------| | `сегмент` | Имя секции (`.header`, `.text`, `.data`, `.bss`) | | `адрес` | Файловое смещение байта | +| `виртуальный_адрес` | Виртуальный адрес байта в памяти (для вычисления смещений) | | `байт` | Исходное значение байта (из pass1) | | `приводящая_метка` | Имена меток, находящихся на этом адресе (через запятую) | | `уводящий_адрес` | Строка `ЗАГЛУШКА имя` для неразрешённых меток | @@ -475,12 +476,11 @@ strings программа.elf После компиляции создаётся файл `программа.csv` со структурой: -| адрес | байт | целевой_адрес | исходная_команда | -|-------|------|---------------|------------------| -| 0x401000 | 48 | | переместить_имм раикс, 1 | -| 0x401001 | B8 | | | -| 0x401002 | 01 | | | -| ... | ... | ... | ... | +| адрес | виртуальный_адрес | байт | приводящая_метка | исходная_команда | +|-------|-------------------|------|------------------|------------------| +| 0x1000 | 0x401000 | 48 | _start | переместить_имм раикс, 10 | +| 0x1001 | 0x401001 | B8 | | | +| ... | ... | ... | ... | ... | ### Трассировка системных вызовов @@ -524,7 +524,6 @@ gdb ./программа.elf - Адресация со смещением `[reg + disp]` - Сложная адресация с индексом `[reg + reg*scale]` и полная SIB-адресация -- Инструкции для работы со стеком (PUSH, POP, CALL, RET) — кроме базовых PUSH/POP reg - Поддержка плавающей запятой (FPU/SSE) - Многомодульная компиляция - Макросы @@ -544,7 +543,8 @@ gdb ./программа.elf - Поддержка числовых адресов `[0x402000]` - Поддержка косвенной адресации `[регистр]` - Секция `.бнд` для неинициализированных данных -- Рефакторинг: выделение кодировщика в отдельный модуль +- Вынесение кодировщика в отдельный модуль +- Колонка `виртуальный_адрес` в CSV для точного вычисления смещений ### В планах (v3.0) diff --git a/kvs_build.py b/kvs_build.py index 098e61c..572fa3d 100644 --- a/kvs_build.py +++ b/kvs_build.py @@ -33,7 +33,6 @@ if __name__ == "__main__": ("kvs_pass2.py", [csv_file], "2 проход"), ("kvs_pass3.py", [ast_file, csv_file], "3 проход"), ("kvs_pass4.py", [csv_file], "4 проход"), - #("kvs_pass2.py", [csv_file], "Второй проход: разрешение меток"), ("kvs_builder.py", [csv_file, elf_file], "Сборка ELF"), ] diff --git a/kvs_pass1.py b/kvs_pass1.py index 1451093..ee6eb17 100644 --- a/kvs_pass1.py +++ b/kvs_pass1.py @@ -6,8 +6,9 @@ Генерирует CSV-таблицу напрямую из AST - Неизвестные метки заменяются заглушками (0xCC) с пометкой в колонке 'уводящий_адрес' - В колонке 'приводящая_метка' записываются имена меток, находящихся по данному адресу (через запятую) -- Колонка 'команда_со_значениями' оставлена пустой (для возможного расширения) +- Колонка 'команда_со_значениями' оставлена пустой (для второго прохода) - Колонки 'рассчитанный_уводящий_адрес' и 'рассчитанный_байт' оставлены для второго прохода +- Колонка 'виртуальный_адрес' содержит виртуальный адрес байта (для pass4) """ import sys @@ -18,7 +19,7 @@ from kvs_data import PAGE_SIZE, text_vaddr_base, align_up from kvs_encoder import encode_instruction # ========== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ========== -entries = [] # (segment, address, byte, target, source, label, source_with_values, calc_target, calc_byte) +entries = [] # (segment, address, virt_addr, byte, target, source, label, cmd_with_values, calc_target, calc_byte) current_address = 0 current_segment = '.header' last_segment = None @@ -31,12 +32,11 @@ text_buffer = bytearray() data_buffer = bytearray() bnd_size = 0 -labels = {} # имя метки -> адрес -label_sections = {} # имя метки -> секция -symbols = {} # имя константы -> значение +labels = {} +label_sections = {} +symbols = {} -# Для сбора нескольких меток на один адрес -pending_labels = [] # список имён меток, ожидающих привязки к следующему байту +pending_labels = [] text_pos = 0 data_pos = 0 @@ -44,6 +44,34 @@ data_pos = 0 entry_point = "_start" relocations = [] +# Список инструкций, которые всегда должны получать заглушки в первом проходе +ALWAYS_STUB_INSTRUCTIONS = { + "переход", + "короткий_переход", + "вызвать", + "цикл", + "переход_если_равно", + "переход_если_неравно", + "переход_если_меньше", + "переход_если_больше", + "переход_если_меньше_или_равно", + "переход_если_больше_или_равно", + "переход_если_перенос", + "переход_если_нет_переноса", + "переход_если_ноль", + "переход_если_не_ноль", + "короткий_переход_если_равно", + "короткий_переход_если_неравно", + "короткий_переход_если_меньше", + "короткий_переход_если_больше", + "короткий_переход_если_меньше_или_равно", + "короткий_переход_если_больше_или_равно", + "короткий_переход_если_перенос", + "короткий_переход_если_нет_переноса", + "короткий_переход_если_ноль", + "короткий_переход_если_не_ноль", +} + def reset_state(): global entries, current_address, current_segment, last_segment @@ -75,11 +103,21 @@ def set_segment(segment: str): current_segment = segment +def get_virtual_address(): + """Возвращает виртуальный адрес для текущей позиции""" + 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 - 0x2000) + 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) @@ -90,20 +128,23 @@ def add_byte(value: int, source: str = "", target=None, labels_list=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 "", # segment - current_address, # address - value & 0xFF, # byte - target if target else "", # target (уводящий_адрес) - source, # source (исходная_команда) - effective_labels, # label (приводящая_метка) - несколько через запятую - "", # source_with_values (команда_со_значениями) - всегда пусто - "", # calc_target - "" # calc_byte + show_segment if show_segment else "", + current_address, + f"0x{virt_addr:08x}", # виртуальный_адрес + f"0x{byte_val:02x}", # байт (в виде hex-строки) + target if target else "", + source, + effective_labels, + "", # команда_со_значениями (заполняется во втором проходе) + "", # рассчитанный_уводящий_адрес + f"0x{byte_val:02x}" # рассчитанный_байт (изначально = байт) )) current_address += 1 - # После добавления байта сбрасываем отложенные метки if pending_labels: pending_labels = [] @@ -114,7 +155,6 @@ def add_bytes(values, source: str = "", target=None): def add_word16(value: int, source: str = "", target=None): - """Добавить 16-битное слово (little-endian)""" add_bytes([ (value >> 0) & 0xFF, (value >> 8) & 0xFF, @@ -122,7 +162,6 @@ def add_word16(value: int, source: str = "", target=None): def add_word32(value: int, source: str = "", target=None): - """Добавить 32-битное слово (little-endian)""" add_bytes([ (value >> 0) & 0xFF, (value >> 8) & 0xFF, @@ -132,7 +171,6 @@ def add_word32(value: int, source: str = "", target=None): def add_word64(value: int, source: str = "", target=None): - """Добавить 64-битное слово (little-endian)""" add_bytes([ (value >> 0) & 0xFF, (value >> 8) & 0xFF, @@ -174,48 +212,29 @@ def unescape_string(s): def generate_elf_header(): - # EI_MAGIC add_bytes([0x7F, 0x45, 0x4C, 0x46], "ELF magic") - # EI_CLASS, EI_DATA, EI_VERSION add_byte(2, "ELFCLASS64") add_byte(1, "ELFDATA2LSB") add_byte(1, "ELF version") - # EI_OSABI, EI_ABIVERSION add_byte(0, "OS ABI") add_byte(0, "ABI version") - # EI_PAD add_bytes([0] * 7, "Padding") - - # e_type (2 байта) add_word16(2, "e_type = ET_EXEC") - # e_machine (2 байта) add_word16(62, "e_machine = EM_X86_64") - # e_version (4 байта) add_word32(1, "e_version") - # e_entry (8 байт, заглушка) add_word64(0, "e_entry (stub)") - # e_phoff (8 байт) add_word64(64, "e_phoff") - # e_shoff (8 байт) add_word64(0, "e_shoff = 0") - # e_flags (4 байта) add_word32(0, "e_flags") - # e_ehsize (2 байта) add_word16(64, "e_ehsize") - # e_phentsize (2 байта) add_word16(56, "e_phentsize") - # e_phnum (2 байта) add_word16(2, "e_phnum = 2") - # e_shentsize (2 байта) add_word16(0, "e_shentsize = 0") - # e_shnum (2 байта) add_word16(0, "e_shnum = 0") - # e_shstrndx (2 байта) add_word16(0, "e_shstrndx") def generate_program_headers(): - # Program Header 1: TEXT (RX) add_word32(1, "p_type = PT_LOAD") add_word32(5, "p_flags = RX") add_word64(0, "p_offset (text stub)") @@ -225,7 +244,6 @@ def generate_program_headers(): add_word64(0, "p_memsz (text stub)") add_word64(PAGE_SIZE, "p_align") - # Program Header 2: DATA (RW) add_word32(1, "p_type = PT_LOAD") add_word32(6, "p_flags = RW") add_word64(0, "p_offset (data stub)") @@ -258,7 +276,6 @@ def parse_ast_line(line): 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", { @@ -313,7 +330,17 @@ def parse_ast_line(line): def get_stub_size(mnemonic, operands): - if mnemonic == "переместить_имм" and len(operands) >= 2: + 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'): @@ -322,46 +349,88 @@ def get_stub_size(mnemonic, operands): return 7 else: return 2 - elif mnemonic in ("переход", "вызвать"): - return 5 - elif mnemonic.startswith("короткий_переход"): - return 2 - elif mnemonic.startswith("переход_если"): - return 6 - elif mnemonic.startswith("короткий_переход_если"): - return 2 elif mnemonic in ("загрузить", "сохранить", "загрузить_адрес"): return 7 + elif mnemonic == "сравнить_с": + return 7 + elif mnemonic in ("прибавить_непосредственно", "вычесть_непосредственно"): + return 7 + elif mnemonic in ("переместить_с_нулями", "переместить_со_знаком"): + return 7 return 1 -def has_unresolved_labels(operands): +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в', + 'р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 in labels: + if inner.isdigit() or (inner.startswith('0x') and len(inner) > 2): continue - if inner.isdigit() or (inner.startswith('0x')): - continue - return True - elif op in labels: + if inner not in labels: + return True continue - elif op.isdigit() or (op.startswith('0x')): + if op.isdigit() or (op.startswith('0x') and len(op) > 2): continue - else: - if op in symbols: - continue + if op in symbols: + continue + if op not in labels: return True + return False @@ -380,7 +449,8 @@ def get_unresolved_labels(operands): if op not in ('раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай', 'еаикс', 'ебикс', 'есикс', 'едикс', 'аикс', 'бикс', 'сикс', 'дикс', - 'ал', 'бл', 'кл', 'дл'): + 'ал', 'бл', 'кл', 'дл', + 'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'): if op not in symbols: unresolved.append(op) return unresolved @@ -505,17 +575,25 @@ def process_ast_line(line): source = f"{mnemonic} {', '.join(operands)}" if operands else mnemonic - source_with_values = "" + need_stub = has_unresolved_labels(operands, mnemonic) - if has_unresolved_labels(operands): + if need_stub: stub_size = get_stub_size(mnemonic, operands) unresolved = get_unresolved_labels(operands) - reloc_text = f"ЗАГЛУШКА {', '.join(unresolved)}" + + 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, + 'labels': unresolved if unresolved else [label_from_op] if label_from_op else [], 'source': source, 'mnemonic': mnemonic }) @@ -560,9 +638,9 @@ def process_ast_line(line): except Exception as e: print(f"Ошибка кодирования {source}: {e}", file=sys.stderr) - labels_list = pending_labels - add_byte(0xCC, source, None, f"ОШИБКА: {e}", labels_list) - pending_labels = [] + 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) @@ -589,113 +667,113 @@ def finalize_headers(): i = 0 while i < len(entries): - segment, addr, byte, target, source, label, source_with_values, calc_target, calc_byte = entries[i] + segment, addr, virt_addr, byte_str, target, source, label, cmd_with_values, calc_target, calc_byte = entries[i] - # e_entry - 8 байт if 'e_entry (stub)' in source: new_bytes = [(entry_point_addr >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_offset (text) - 8 байт elif 'p_offset (text stub)' in source: new_bytes = [(text_offset >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_filesz (text) - 8 байт elif 'p_filesz (text stub)' in source: new_bytes = [(text_filesz >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_memsz (text) - 8 байт elif 'p_memsz (text stub)' in source: new_bytes = [(text_memsz >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_offset (data) - 8 байт elif 'p_offset (data stub)' in source: new_bytes = [(data_offset >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_vaddr (data) - 8 байт elif 'p_vaddr (data stub)' in source: new_bytes = [(vaddr_data_final >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_paddr (data) - 8 байт elif 'p_paddr (data stub)' in source: new_bytes = [(vaddr_data_final >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_filesz (data) - 8 байт elif 'p_filesz (data stub)' in source: new_bytes = [(data_filesz >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue - # p_memsz (data) - 8 байт elif 'p_memsz (data stub)' in source: new_bytes = [(data_memsz >> j*8) & 0xFF for j in range(8)] for j, nb in enumerate(new_bytes): - entries[i + j] = (segment, addr + j, nb, target, + 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 "", - "", "", - f"0x{nb:02x}") + "", "", byte_hex) i += 8 continue @@ -705,19 +783,20 @@ def 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(['сегмент', 'адрес', 'байт', 'приводящая_метка', 'уводящий_адрес', - 'исходная_команда', 'команда_со_значениями', + writer.writerow(['сегмент', 'адрес', 'виртуальный_адрес', 'байт', 'приводящая_метка', + 'уводящий_адрес', 'исходная_команда', 'команда_со_значениями', 'рассчитанный_уводящий_адрес', 'рассчитанный_байт']) - for segment, addr, byte, target, source, label, source_with_values, calc_target, calc_byte in entries: + 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}", - f"0x{byte:02x}", + virt_addr, + byte_str, label, target, source, - source_with_values, + cmd_with_values, calc_target, calc_byte ]) @@ -748,16 +827,16 @@ def main(): 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)} байт, виртуальный адрес=0x{vaddr_text:x}") - print(f" .data: {len(data_buffer)} байт, виртуальный адрес=0x{vaddr_data_final:x}") - print(f" .bnd: {bnd_size} байт, виртуальный адрес=0x{vaddr_bnd_final:x}") + 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}") + 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" записей в CSV: {len(entries)}") - print(f" CSV сохранен: {csv_file}") + print(f" записей: {len(entries)}") + print(f" CSV: {csv_file}") if __name__ == "__main__": diff --git a/kvs_pass2.py b/kvs_pass2.py index c06e1c9..e8b8361 100644 --- a/kvs_pass2.py +++ b/kvs_pass2.py @@ -2,13 +2,11 @@ # -*- coding: utf-8 -*- """ -Второй проход КВС -- Собирает адреса всех приводящих меток (поддерживает несколько меток через запятую) +Второй проход КВС (упрощённый) +- Собирает адреса всех приводящих меток из колонки 'виртуальный_адрес' - Заменяет заглушки в уводящих адресах на реальные адреса - Записывает рассчитанные значения в колонку 'рассчитанный_уводящий_адрес' (файловое смещение) -- Формирует команду с подставленными адресами в колонку 'команда_со_значениями' (виртуальный адрес) -- Пропускает заглушки, которые не найдены (возможно это константы) -- Выводит статистику по неразрешённым заглушкам +- Формирует команду с подставленными виртуальными адресами в колонку 'команда_со_значениями' """ import sys @@ -30,55 +28,17 @@ def read_csv(csv_file): return headers, entries -def get_virtual_addresses(entries, headers): - """ - Извлекает виртуальные адреса секций из заголовка CSV - Возвращает (vaddr_text, vaddr_data) - """ - vaddr_text = 0x401000 - vaddr_data = 0x402000 - - try: - source_idx = headers.index('исходная_команда') - calc_target_idx = headers.index('рассчитанный_уводящий_адрес') - except ValueError: - return vaddr_text, vaddr_data - - for row in entries: - source = row[source_idx].strip() - calc_target = row[calc_target_idx].strip() - - if 'p_vaddr (text)' in source and calc_target: - parts = calc_target.split('=') - if len(parts) >= 2: - addr_str = parts[1].strip() - if addr_str.startswith('0x'): - vaddr_text = int(addr_str, 16) - - elif 'p_vaddr (data)' in source and calc_target: - parts = calc_target.split('=') - if len(parts) >= 2: - addr_str = parts[1].strip() - if addr_str.startswith('0x'): - vaddr_data = int(addr_str, 16) - - return vaddr_text, vaddr_data - - -def collect_labels(entries, headers, vaddr_text, vaddr_data): +def collect_labels(entries, headers): """ Собирает адреса всех приводящих меток. - Возвращает два словаря: - - labels_file: имя метки -> файловое смещение - - labels_virt: имя метки -> виртуальный адрес + Использует колонку 'виртуальный_адрес' для получения адреса метки. + Возвращает словарь: имя метки -> виртуальный адрес """ - labels_file = {} # имя метки -> файловое смещение - labels_virt = {} # имя метки -> виртуальный адрес + labels = {} try: label_idx = headers.index('приводящая_метка') - addr_idx = headers.index('адрес') - segment_idx = headers.index('сегмент') + virt_addr_idx = headers.index('виртуальный_адрес') except ValueError as e: print(f"Ошибка: не найдена нужная колонка в CSV: {e}") sys.exit(1) @@ -86,33 +46,33 @@ def collect_labels(entries, headers, vaddr_text, vaddr_data): for row in entries: label_str = row[label_idx].strip() if label_str: - addr_str = row[addr_idx].strip() - if addr_str.startswith('0x'): - file_addr = int(addr_str, 16) - segment = row[segment_idx].strip() + virt_addr_str = row[virt_addr_idx].strip() + if virt_addr_str.startswith('0x'): + virt_addr = int(virt_addr_str, 16) - # Вычисляем виртуальный адрес - if segment == '.text': - virt_addr = vaddr_text + (file_addr - 0x1000) - elif segment == '.data': - virt_addr = vaddr_data + (file_addr - 0x2000) - else: - virt_addr = file_addr - - # Разделяем несколько меток через запятую for label_name in label_str.split(','): label_name = label_name.strip() if label_name: - labels_file[label_name] = file_addr - labels_virt[label_name] = virt_addr + labels[label_name] = virt_addr - return labels_file, labels_virt + # Выводим статистику собранных меток + 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_file, labels_virt): +def resolve_targets(entries, headers, labels): """ Заменяет уводящие адреса-заглушки на реальные адреса - - рассчитанный_уводящий_адрес: файловое смещение + - рассчитанный_уводящий_адрес: файловое смещение (берём из колонки 'адрес') - команда_со_значениями: виртуальный адрес """ try: @@ -120,12 +80,13 @@ def resolve_targets(entries, headers, labels_file, labels_virt): 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 = [] # имя_метки + resolved = [] + unresolved = [] for row in entries: target = row[target_idx].strip() @@ -133,17 +94,22 @@ def resolve_targets(entries, headers, labels_file, labels_virt): if target and target.startswith('ЗАГЛУШКА '): label_name = target.replace('ЗАГЛУШКА ', '').strip() - if label_name in labels_file: - file_addr = labels_file[label_name] - virt_addr = labels_virt[label_name] - file_hex = f"0x{file_addr:08x}" + if label_name in labels: + virt_addr = labels[label_name] virt_hex = f"0x{virt_addr:08x}" - # Файловое смещение в рассчитанный_уводящий_адрес - row[calc_target_idx] = file_hex - resolved.append((label_name, file_addr, virt_addr)) + # Файловое смещение берём из колонки 'адрес' текущей строки + 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) @@ -152,6 +118,46 @@ def resolve_targets(entries, headers, labels_file, labels_virt): 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 @@ -163,36 +169,6 @@ def save_csv(csv_file, headers, entries): writer.writerows(entries) -def print_statistics(labels_count, resolved, unresolved, vaddr_text, vaddr_data): - """Выводит статистику по разрешению меток""" - print(f"\n--- Статистика разрешения меток ---") - print(f" Виртуальный адрес .text: 0x{vaddr_text:x}") - print(f" Виртуальный адрес .data: 0x{vaddr_data:x}") - print(f" Всего приводящих меток: {labels_count}") - print(f" Разрешено заглушек: {len(resolved)}") - print(f" Не разрешено заглушек (возможно константы): {len(unresolved)}") - - 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--- Разрешённые заглушки (первые 20) ---") - for i, (name, (file_addr, virt_addr)) in enumerate(list(unique_resolved.items())[:20]): - print(f" {name} -> файл:0x{file_addr:08x}, вирт:0x{virt_addr:08x}") - if len(unique_resolved) > 20: - print(f" ... и ещё {len(unique_resolved) - 20}") - - if unresolved: - unresolved_counts = Counter(unresolved) - print(f"\n--- Неразрешённые заглушки (возможно константы) ---") - for name, count in sorted(unresolved_counts.items(), key=lambda x: x[1], reverse=True)[:20]: - print(f" {name}: {count} раз(а)") - if len(unresolved_counts) > 20: - print(f" ... и ещё {len(unresolved_counts) - 20} различных имён") - - def main(): if len(sys.argv) != 2: print("Использование: python kvs_pass2.py <входной.csv>") @@ -200,33 +176,27 @@ def main(): csv_file = sys.argv[1] - print(f"\n=== Второй проход КВС ===") + print(f"\n=== ВТОРОЙ ПРОХОД КВС (упрощённый) ===") print(f"Чтение CSV: {csv_file}") headers, entries = read_csv(csv_file) - vaddr_text, vaddr_data = get_virtual_addresses(entries, headers) - print(f"Виртуальный адрес .text: 0x{vaddr_text:x}") - print(f"Виртуальный адрес .data: 0x{vaddr_data:x}") + print(f"\n--- СБОР ПРИВОДЯЩИХ МЕТОК ---") + labels = collect_labels(entries, headers) - print(f"\n--- Сбор приводящих меток ---") - labels_file, labels_virt = collect_labels(entries, headers, vaddr_text, vaddr_data) - print(f"Найдено приводящих меток: {len(labels_file)}") + print(f"\n--- РАЗРЕШЕНИЕ УВОДЯЩИХ АДРЕСОВ ---") + resolved, unresolved = resolve_targets(entries, headers, labels) - if labels_file: - print(f"\n--- Примеры приводящих меток (первые 20) ---") - for i, name in enumerate(list(labels_file.keys())[:20]): - print(f" {name} -> файл:0x{labels_file[name]:08x}, вирт:0x{labels_virt[name]:08x}") - if len(labels_file) > 20: - print(f" ... и ещё {len(labels_file) - 20}") - - print(f"\n--- Разрешение уводящих адресов и формирование команд ---") - resolved, unresolved = resolve_targets(entries, headers, labels_file, labels_virt) - - print_statistics(len(labels_file), resolved, unresolved, vaddr_text, vaddr_data) + 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}") + print(f"\nCSV сохранён: {csv_file}") if __name__ == "__main__": diff --git a/kvs_pass4.py b/kvs_pass4.py index 39d891e..5d0ae3d 100644 --- a/kvs_pass4.py +++ b/kvs_pass4.py @@ -4,13 +4,13 @@ """ Четвёртый проход КВС - Перекодирует инструкции, используя подставленные значения -- Смотрит колонки 'команда_со_значениями' и 'рассчитанный_уводящий_адрес' +- Смотрит колонку 'команда_со_значениями' - Записывает перекодированные байты в колонку 'рассчитанный_байт' +- Использует виртуальные адреса из CSV для вычисления смещений """ import sys import csv -import struct sys.path.insert(0, '.') from kvs_data import PAGE_SIZE, text_vaddr_base, align_up @@ -18,16 +18,15 @@ from kvs_encoder import encode_instruction # ========== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ========== vaddr_text = text_vaddr_base -vaddr_data = None +vaddr_data = 0x402000 vaddr_bnd = None -labels = {} # имя метки -> адрес -label_sections = {} # имя метки -> секция -symbols = {} # имя константы -> значение +labels = {} +label_sections = {} +symbols = {} def read_csv(csv_file): - """Читает CSV файл и возвращает список записей и заголовки""" entries = [] headers = [] @@ -41,7 +40,6 @@ def read_csv(csv_file): def parse_command(cmd_str): - """Разбирает команду вида 'мнемоника операнд1, операнд2, ...'""" if not cmd_str: return None, [] @@ -50,75 +48,44 @@ def parse_command(cmd_str): operands = [] if len(parts) > 1 and parts[1]: - # Разбираем операнды, разделённые запятыми operands = [op.strip() for op in parts[1].split(',')] return mnemonic, operands def collect_labels_from_csv(entries, headers): - """Собирает метки из CSV для перекодирования""" - global labels, label_sections, vaddr_data, vaddr_bnd + """ + Собирает метки из CSV, используя колонку 'виртуальный_адрес' + """ + global labels, label_sections try: label_idx = headers.index('приводящая_метка') - addr_idx = headers.index('адрес') - segment_idx = headers.index('сегмент') - byte_idx = headers.index('байт') + virt_addr_idx = headers.index('виртуальный_адрес') except ValueError as e: print(f"Ошибка: не найдена нужная колонка в CSV: {e}") sys.exit(1) - # Сначала нужно определить виртуальные адреса секций - max_addr = 0 - for row in entries: - addr_str = row[addr_idx].strip() - if addr_str.startswith('0x'): - addr = int(addr_str, 16) - if addr > max_addr: - max_addr = addr - segment = row[segment_idx].strip() - - # Определяем vaddr_data по первому адресу в .data - if segment == '.data' and vaddr_data is None: - vaddr_data = addr - # Определяем vaddr_bnd по первому адресу в .bss - elif segment == '.bss' and vaddr_bnd is None: - vaddr_bnd = addr - - if vaddr_data is None: - vaddr_data = align_up(vaddr_text + max_addr, PAGE_SIZE) - if vaddr_bnd is None: - vaddr_bnd = align_up(vaddr_data + max_addr, PAGE_SIZE) - - # Собираем метки for row in entries: label_str = row[label_idx].strip() if label_str: - addr_str = row[addr_idx].strip() - if addr_str.startswith('0x'): - addr = int(addr_str, 16) + 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] = addr - # Определяем секцию по адресу - if addr >= vaddr_text and addr < vaddr_text + PAGE_SIZE: - label_sections[label_name] = '.text' - elif vaddr_data and addr >= vaddr_data and addr < vaddr_data + PAGE_SIZE: - label_sections[label_name] = '.data' - elif vaddr_bnd and addr >= vaddr_bnd: - label_sections[label_name] = '.bss' + labels[label_name] = virt_addr + label_sections[label_name] = '' def reencode_instructions(entries, headers): - """Перекодирует инструкции, используя подставленные значения""" try: source_with_values_idx = headers.index('команда_со_значениями') - calc_target_idx = headers.index('рассчитанный_уводящий_адрес') calc_byte_idx = headers.index('рассчитанный_байт') addr_idx = headers.index('адрес') - segment_idx = headers.index('сегмент') + virt_addr_idx = headers.index('виртуальный_адрес') byte_idx = headers.index('байт') except ValueError as e: print(f"Ошибка: не найдена нужная колонка в CSV: {e}") @@ -127,7 +94,6 @@ def reencode_instructions(entries, headers): reencoded_count = 0 error_count = 0 - # Проходим по всем строкам i = 0 while i < len(entries): row = entries[i] @@ -136,76 +102,86 @@ def reencode_instructions(entries, headers): i += 1 continue - addr = int(addr_str, 16) - segment = row[segment_idx].strip() + 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() - calc_target = row[calc_target_idx].strip() - - # Определяем, нужно ли перекодировать - need_reencode = False if cmd: - # Есть команда с подставленными константами - need_reencode = True - elif calc_target and calc_target.startswith('0x'): - # Есть рассчитанный адрес для заглушки - # Нужно взять исходную команду и подставить адрес - source_idx = headers.index('исходная_команда') - source = row[source_idx].strip() - if source: - # Заменяем ЗАГЛУШКА имя на реальный адрес - target_addr = int(calc_target, 16) - cmd = source.replace('ЗАГЛУШКА', '').strip() - # Добавляем адрес как операнд - cmd = f"{cmd} 0x{target_addr:08x}" - need_reencode = True - - if need_reencode and cmd: try: mnemonic, operands = parse_command(cmd) if mnemonic: - # Вычисляем текущую позицию в секции - if segment == '.text': - current_pos = addr - vaddr_text - elif segment == '.data' and vaddr_data: - current_pos = addr - vaddr_data - else: - current_pos = 0 + # ОТЛАДОЧНЫЙ ВЫВОД (только для переходов) + if mnemonic.startswith('короткий_переход') or mnemonic.startswith('переход'): + print(f"\n=== ОТЛАДКА ===") + print(f" Адрес в CSV: 0x{file_addr:08x}") + print(f" Виртуальный адрес инструкции: 0x{instr_virt_addr:08x}") + print(f" Команда: {cmd}") + print(f" Мнемоника: {mnemonic}") + print(f" Операнды: {operands}") + + if operands and len(operands) > 0: + target_str = operands[0] + if target_str.startswith('0x'): + target_addr = int(target_str, 16) + # Определяем размер инструкции + if mnemonic.startswith('короткий'): + instr_size = 2 + else: + instr_size = 5 + offset = target_addr - (instr_virt_addr + instr_size) + print(f" Целевой адрес: 0x{target_addr:08x}") + print(f" Размер инструкции: {instr_size}") + print(f" RIP = 0x{instr_virt_addr + instr_size:08x}") + print(f" Смещение = {offset} (0x{offset & 0xFFFFFFFF:08x})") + print(f" В диапазоне [-128..127]: {-128 <= offset <= 127}") - # Кодируем инструкцию + # Вычисляем current_pos как смещение в секции + # Определяем секцию по виртуальному адресу + if 0x401000 <= instr_virt_addr < 0x402000: + current_pos = instr_virt_addr - vaddr_text + elif 0x402000 <= instr_virt_addr < 0x403000: + current_pos = instr_virt_addr - vaddr_data + else: + current_pos = instr_virt_addr + + # Преобразуем операнды: если строка с 0x, оставляем как есть + # Кодировщик сам разберётся с isdigit() 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 + vaddr_text, vaddr_data, current_pos, vaddr_bnd ) - # Записываем байты в колонку рассчитанный_байт - # Для много-байтовых инструкций записываем в последовательные строки + if mnemonic.startswith('короткий_переход') or mnemonic.startswith('переход'): + print(f" Закодировано: {[f'0x{b:02x}' for b in encoded]}") + print(f" РЕЗУЛЬТАТ: УСПЕШНО") + for j, byte in enumerate(encoded): if i + j < len(entries): entries[i + j][calc_byte_idx] = f"0x{byte:02x}" - else: - # Не хватает строк, создаём новую - # (в нормальной ситуации не должно происходить) - pass reencoded_count += 1 - - # Пропускаем следующие строки, принадлежащие этой инструкции - # (обычно в CSV одна строка = один байт) i += len(encoded) continue except Exception as e: - print(f"Ошибка перекодирования '{cmd}': {e}") + print(f"\n=== ОТЛАДКА (ОШИБКА) ===") + print(f" Команда: {cmd}") + print(f" Мнемоника: {mnemonic if 'mnemonic' in locals() else '?'}") + print(f" Операнды: {operands if 'operands' in locals() else '?'}") + print(f" Виртуальный адрес инструкции: 0x{instr_virt_addr:08x}") + print(f" ОШИБКА: {e}") error_count += 1 - # Записываем INT3 как заглушку 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 @@ -215,19 +191,20 @@ def reencode_instructions(entries, headers): 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(reencoded_count, error_count): - """Выводит статистику перекодирования""" +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(): @@ -240,21 +217,26 @@ def main(): print(f"\n=== Четвёртый проход КВС ===") print(f"Чтение CSV: {csv_file}") - # Читаем CSV headers, entries = read_csv(csv_file) print(f"\n--- Сбор меток из CSV ---") collect_labels_from_csv(entries, headers) print(f"Найдено меток: {len(labels)}") + if labels: + print(f"\n--- Примеры меток (виртуальные адреса) ---") + for i, name in enumerate(sorted(labels.keys())[:10]): + print(f" {name} -> 0x{labels[name]:08x}") + if len(labels) > 10: + print(f" ... и ещё {len(labels) - 10}") + print(f"\n--- Перекодирование инструкций ---") reencoded_count, error_count = reencode_instructions(entries, headers) - print_statistics(reencoded_count, error_count) + print_statistics(reencoded_count, error_count, len(labels)) - # Сохраняем результат save_csv(csv_file, headers, entries) - print(f"\nCSV сохранен: {csv_file}") + print(f"\nCSV сохранён: {csv_file}") print(f"Колонка 'рассчитанный_байт' обновлена") diff --git a/тесты/сложный_тест.квс b/тесты/сложный_тест.квс new file mode 100644 index 0000000..a029e43 --- /dev/null +++ b/тесты/сложный_тест.квс @@ -0,0 +1,204 @@ +; сложный_тест9.квс +; Тест: запутанные переходы, крякмис + +.текст +.глобал _start + +_start: + ; Инициализация регистров + переместить_имм раикс, 10 ; rax = 10 + переместить_имм рбикс, 5 ; rbx = 5 + переместить_имм рсикс, 10 ; rcx = 10 (для проверки ZF в jz) + + ; ПЕРВЫЙ ПЕРЕХОД: короткий_переход_если_не_ноль + ; ZF=0 (так как rax=10), значит jnz сработает + ; Прыгаем к метке 'проверка_2', пропуская мертвый_код_1 + короткий_переход_если_не_ноль проверка_2 + + ; --- МЕРТВЫЙ КОД 1 --- + ; Этот код НЕ должен выполниться + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_1 + переместить_имм рдикс, len_msg_dead_1 + вызов_системы + ; --------------------- + +проверка_2: + ; ДОПОЛНИТЕЛЬНЫЙ ПЕРЕХОД 1: "Два шага вперед" - короткий_переход через мертвый_код_2 и мертвый_код_3 + ; Прыгаем к проверка_4, минуя мертвый_код_2 и мертвый_код_3 + короткий_переход проверка_4 + + ; --- МЕРТВЫЙ КОД 2 --- + ; Этот код НЕ должен выполниться из-за прыжка выше + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_2 + переместить_имм рдикс, len_msg_dead_2 + вызов_системы + ; --------------------- + +мертвый_код_3: + ; ЖИВОЙ КОД 3: Этот код выполнится благодаря прыжку "назад" + ; Выведем сообщение, чтобы убедиться, что он выполнился + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_live_from_3 + переместить_имм рдикс, len_msg_live_from_3 + вызов_системы + ; ДОПОЛНИТЕЛЬНЫЙ ПЕРЕХОД 3: "Четыре вперед" - переход через мертвый_код_4, 5, 6, 7 + ; Цель слишком далеко для короткого перехода. Используем длинный. + ; Прыгаем к проверка_8 + переход проверка_8 + ; --------------------- + +проверка_4: + ; ЧЕТВЁРТЫЙ ПЕРЕХОД: короткий_переход_если_больше + ; Сравниваем rax (10) и rbx (5) -> rax > rbx, ZF=0, SF=0, OF=0 + сравнить раикс, рбикс + ; Условие jg: ZF=0 (истина) И SF=OF (0=0, истина). jg СРАБОТАЕТ. + ; Мы хотим прыгнуть МИМО мертвого кода 4, если jg сработает (т.е. если rax > rbx). + короткий_переход_если_больше мимо_мертвого_кода_4 + ; --- МЕРТВЫЙ КОД 4 --- + ; Этот код НЕ должен выполниться (так как jg сработал) + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_4 + переместить_имм рдикс, len_msg_dead_4 + вызов_системы + ; --------------------- +мимо_мертвого_кода_4: + + ; ДОПОЛНИТЕЛЬНЫЙ ПЕРЕХОД 2: "Один назад" - короткий_переход к мертвому_коду_3 (делая его живым) + короткий_переход мертвый_код_3 + + ; --- МЕРТВЫЙ КОД 5 --- + ; Этот код НЕ должен выполниться + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_5 + переместить_имм рдикс, len_msg_dead_5 + вызов_системы + ; --------------------- + + ; --- МЕРТВЫЙ КОД 6 --- + ; Этот код НЕ должен выполниться + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_6 + переместить_имм рдикс, len_msg_dead_6 + вызов_системы + ; --------------------- + + ; --- МЕРТВЫЙ КОД 7 --- + ; Этот код НЕ должен выполниться + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_7 + переместить_имм рдикс, len_msg_dead_7 + вызов_системы + ; --------------------- + +проверка_8: + ; СЕДЬМОЙ ПЕРЕХОД: короткий_переход_если_перенос + ; Выполним вычитание, которое вызовет перенос: rbx - rax = 5 - 10 + переместить_имм рбикс, 5 ; rbx = 5 + переместить_имм раикс, 10 ; rax = 10 + ; rbx - rax = 5 - 10 -> CF=1 + вычесть рбикс, раикс ; rbx = rbx - rax = 5 - 10. Это вызовет перенос (CF=1). + ; Условие jc: CF=1. CF=1. jc СРАБОТАЕТ. + ; Мы хотим прыгнуть МИМО мертвого кода 8, если jc сработает (т.е. если был перенос). + короткий_переход_если_перенос мимо_мертвого_кода_8 + ; --- МЕРТВЫЙ КОД 8 --- + ; Этот код НЕ должен выполниться (так как jc сработал) + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_8 + переместить_имм рдикс, len_msg_dead_8 + вызов_системы + ; --------------------- + +мимо_мертвого_кода_8: + + ; ВОСЬМОЙ ПЕРЕХОД: короткий_переход_если_нет_переноса + ; Убедимся, что CF=0. Выполним операцию, которая сбросит CF. + сравнить раикс, раикс ; CF = 0 (так как rax - rax = 0, без переноса) + ; Условие jnc: CF=0. CF=0. jnc СРАБОТАЕТ. + ; Мы хотим прыгнуть МИМО мертвого кода 9, если jnc сработает (т.е. если НЕ было переноса). + короткий_переход_если_нет_переноса мимо_мертвого_кода_9 + ; --- МЕРТВЫЙ КОД 9 --- + ; Этот код НЕ должен выполниться (так как jnc сработал) + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_9 + переместить_имм рдикс, len_msg_dead_9 + вызов_системы + ; --------------------- + +мимо_мертвого_кода_9: + + ; ДЕВЯТЫЙ ПЕРЕХОД: короткий_переход (безусловный) + ; Просто прыгаем мимо мертвого кода 10. + короткий_переход мимо_мертвого_кода_10 + ; --- МЕРТВЫЙ КОД 10 --- + ; Этот код НЕ должен выполниться (так как jmp сработал) + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_dead_10 + переместить_имм рдикс, len_msg_dead_10 + вызов_системы + ; --------------------- + +мимо_мертвого_кода_10: + + ; --- ВЫВОД ЦЕЛЕВОГО СООБЩЕНИЯ --- +вывести_сообщение: + переместить_имм раикс, 1 + переместить_имм рдиай, 1 + переместить_имм рсиай, msg_success + переместить_имм рдикс, len_msg_success + вызов_системы + + ; --- ЗАВЕРШЕНИЕ --- + переместить_имм раикс, 60 + переместить_имм рдиай, 0 + вызов_системы + +.данные + ; Перемешиваем строки и константы + ; "Живой из 3!\n" = 8 букв * 2 + 1 (!) + 2 (\n) = 16 + 3 = 19 байт на содержимое. len = 19 + msg_live_from_3: .строка_нуль "Живой из 3!\n" + .константа len_msg_live_from_3 = 19 + ; "Успех!\n" = 5 букв * 2 + 1 (!) + 2 (\n) = 13 байт на содержимое. len = 13 + msg_success: .строка_нуль "Успех!\n" + .константа len_msg_success = 13 + ; "Мертвый код 10!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_10: .строка_нуль "Мертвый код 10!" + .константа len_msg_dead_10 = 18 + ; "Мертвый код 9!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_9: .строка_нуль "Мертвый код 9!" + .константа len_msg_dead_9 = 18 + ; "Мертвый код 8!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_8: .строка_нуль "Мертвый код 8!" + .константа len_msg_dead_8 = 18 + ; "Мертвый код 7!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_7: .строка_нуль "Мертвый код 7!" + .константа len_msg_dead_7 = 18 + ; "Мертвый код 6!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_6: .строка_нуль "Мертвый код 6!" + .константа len_msg_dead_6 = 18 + ; "Мертвый код 5!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_5: .строка_нуль "Мертвый код 5!" + .константа len_msg_dead_5 = 18 + ; "Мертвый код 4!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_4: .строка_нуль "Мертвый код 4!" + .константа len_msg_dead_4 = 18 + ; "Мертвый код 3!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_3: .строка_нуль "Мертвый код 3!" + .константа len_msg_dead_3 = 18 + ; "Мертвый код 2!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_2: .строка_нуль "Мертвый код 2!" + .константа len_msg_dead_2 = 18 + ; "Мертвый код 1!" = 7 букв * 2 + 4 символа * 1 = 14 + 4 = 18 байт на содержимое. len = 18 + msg_dead_1: .строка_нуль "Мертвый код 1!" + .константа len_msg_dead_1 = 18 \ No newline at end of file