#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ kvs_4.py КВС - язык программирования без использования re """ import sys import struct # Глобальная переменная для имени исходного файла source_filename = "" def safe_close(f): if f is not None: f.close() # === Глобальные данные === INSTRUCTIONS = { "переместить_имм": {"code": None, "type": "reg_imm"}, "сравнить_с": {"code": None, "type": "cmp_reg_imm"}, "переход_если_неравно": {"code": b"\x0F\x85", "type": "jcc"}, "вызов_системы": {"code": b"\x0F\x05", "type": "none"}, "переход": {"code": b"\xE9", "type": "jmp"}, } REGISTERS = { "раикс": 0, # RAX "рдикс": 2, # RDX "рсиай": 6, # RSI "рдиай": 7, # RDI } # Глобальное состояние ассемблера labels = {} label_sections = {} symbols = {} sections = {".text": bytearray(), ".data": bytearray()} current_section = ".text" entry_point = "_start" position = {".text": 0, ".data": 0} pass_num = 0 # === Для логирования === log_entries = [] # === ELF layout (глобальные переменные для второго прохода и записи) === PAGE_SIZE = 0x1000 elf_base_vaddr = 0x400000 # первая страница — заголовки text_vaddr_base = 0x401000 # вторая страница — .text data_vaddr_base = 0x402000 # третья страница — .data # Смещения и адреса (вычисляются после первого прохода) text_size = 0 data_size = 0 comment_size = 0 offset_text = 0 offset_data = 0 offset_comment = 0 shstrtab_offset = 0 shdr_offset = 0 vaddr_text = 0 vaddr_data = 0 def align_up(x, align): return (x + align - 1) & ~(align - 1) def get_reg_info(reg_name): return {"size": 64, "index": REGISTERS[reg_name]} # === Функции формирования ошибок === def make_error_msg(line_num, line_text, detail): return f"Ошибка в файле {source_filename}, строка {line_num}:\n {line_text}\n{detail}" def error_invalid_operand_count(mnemonic, expected, got): return f"Инструкция '{mnemonic}':\n ожидается {expected} операнд(а/ов), получено: {got}" def error_unknown_mnemonic(mnemonic): return f"Неизвестная инструкция: '{mnemonic}'" def error_invalid_register(op_name, reg_name, mnemonic): return f"Инструкция '{mnemonic}':\n операнд '{op_name}' = '{reg_name}' не является допустимым регистром.\n Допустимые регистры: {', '.join(list(REGISTERS.keys()))}" def error_invalid_number_format(s): return f"Недопустимый формат числа: '{s}'" def error_unknown_directive(word): return f"Неизвестная директива: '{word}'" def error_missing_label(label): return f"Метка не найдена: '{label}'" def error_unexpected_string_in_byte(): return ".байт требует числовые значения, не строки" def error_unterminated_string(): return "Незакрытая кавычка в строке" # === Лексер === def tokenize_line(line): semi = line.find(';') if semi != -1: line = line[:semi] line = line.rstrip() if not line: return [] tokens = [] i = 0 n = len(line) while i < n: ch = line[i] if ch.isspace(): i += 1 continue if ch == '"': i += 1 s = '' while i < n and line[i] != '"': if line[i] == '\\' and i + 1 < n: i += 1 esc = line[i] if esc == 'n': s += '\n' elif esc == 't': s += '\t' elif esc == '"': s += '"' elif esc == '\\': s += '\\' else: s += '\\' + esc i += 1 else: s += line[i] i += 1 if i >= n: raise ValueError(error_unterminated_string()) i += 1 tokens.append(('string', s)) continue if ch == ',': tokens.append(('comma', ',')) i += 1 continue if ch == ':': tokens.append(('colon', ':')) i += 1 continue j = i while j < n and not (line[j].isspace() or line[j] in ',:;'): j += 1 word = line[i:j] if word: tokens.append(('word', word)) i = j return tokens # === Парсинг === def parse_operand(operand): if operand.isdigit(): return int(operand) if operand.startswith("0x"): return int(operand, 16) if operand in labels: section = label_sections[operand] if section == ".text": # ВО ВТОРОМ ПРОХОДЕ используем вычисленные ранее адреса if pass_num == 2: return labels[operand] + vaddr_text else: return labels[operand] # В первом проходе - только смещение elif section == ".data": if pass_num == 2: return labels[operand] + vaddr_data else: return labels[operand] else: raise ValueError(f"Метка в неизвестной секции: {section}") if operand in symbols: return symbols[operand] raise ValueError(f"Неизвестный операнд: {operand}") def parse_number_token(token): if token[0] != 'word': raise ValueError(f"Ожидалось число, получено: {token}") s = token[1] if s.isdigit(): return int(s) if s.startswith("0x"): return int(s, 16) raise ValueError(error_invalid_number_format(s)) def encode_instruction(mnemonic, operands, line_num, line_text): instr = INSTRUCTIONS[mnemonic] code = bytearray() itype = instr["type"] if itype == "none": code.extend(instr["code"]) elif itype == "reg_imm": if len(operands) != 2: raise ValueError(error_invalid_operand_count(mnemonic, 2, len(operands))) reg_info = get_reg_info(operands[0]) if reg_info is None: raise ValueError(error_invalid_register("назначения", operands[0], mnemonic)) reg = reg_info["index"] imm = parse_operand(operands[1]) if 0 <= reg <= 7: code.extend(b'\x48') code.append(0xB8 + reg) code.extend(struct.pack('= 8: rex |= 0x01 code.append(rex) code.append(0x81) modrm = 0xF8 | (reg & 7) code.append(modrm) code.extend(struct.pack(' 0x7FFFFFFF: raise ValueError(f"Цель слишком далеко для 32-битного смещения в инструкции '{mnemonic}': {offset} (должно быть в [-2^31, 2^31-1])") code.extend(struct.pack(' 0x7FFFFFFF: raise ValueError(f"Цель условного перехода слишком далеко: {offset}") code.extend(struct.pack('= 2 and tokens[0][0] == 'word' and tokens[1][0] == 'colon': label = tokens[0][1] labels[label] = position[current_section] label_sections[label] = current_section rest = tokens[2:] if rest: parse_instruction_or_directive(rest, line_num, line_text) return parse_instruction_or_directive(tokens, line_num, line_text) def parse(source): global pass_num, text_size, data_size, comment_size global offset_text, offset_data, offset_comment, shstrtab_offset, shdr_offset global vaddr_text, vaddr_data global sections, position, log_entries # Очистка лога перед новой сборкой log_entries = [] lines = source.split('\n') # === ПРОХОД 1: анализ (только подсчет размеров) === pass_num = 1 position[".text"] = 0 position[".data"] = 0 labels.clear() label_sections.clear() for line_num, line in enumerate(lines, start=1): original_line = line.rstrip() try: tokens = tokenize_line(line) parse_tokens(tokens, line_num, original_line) except Exception as e: error_full = make_error_msg(line_num, original_line, str(e)) print(error_full, file=sys.stderr) sys.exit(1) # СОХРАНИТЬ размеры из первого прохода text_size_pass1 = position[".text"] data_size_pass1 = position[".data"] print(f"ПРОХОД 1: text_size={text_size_pass1} bytes, data_size={data_size_pass1} bytes") # === ВЫЧИСЛЕНИЕ РАЗМЕЩЕНИЯ с фиксированными размерами === 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_pass1, PAGE_SIZE) # ВИРТУАЛЬНЫЕ АДРЕСА - ИСПРАВЛЕНИЕ! vaddr_text = text_vaddr_base # 0x401000 vaddr_data = align_up(vaddr_text + text_size_pass1, PAGE_SIZE) # Ключевое исправление! # <<< ИЗМЕНЕНИЕ: новое содержимое .comment >>> comment_content = "Сборщик КВС".encode('utf-8') + b'\x00' comment_size = len(comment_content) offset_comment = align_up(offset_data + data_size_pass1, 1) # Обновлённый .shstrtab с .comment 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_size = 64 shdr_num = 5 shdr_offset = align_up(shstrtab_offset + shstrtab_size, 16) print(f"РАЗМЕЩЕНИЕ: .text at offset={hex(offset_text)}, .data at offset={hex(offset_data)}") print(f"ВИРТУАЛЬНЫЕ АДРЕСА: .text at {hex(vaddr_text)}, .data at {hex(vaddr_data)}") print(f"РАССТОЯНИЕ МЕЖДУ СЕКЦИЯМИ: {hex(vaddr_data - vaddr_text)} bytes") print(f"РАЗМЕР .text: {text_size_pass1} bytes, требуется выравнивание до: {hex(align_up(text_size_pass1, PAGE_SIZE))}") # Сохраняем .comment для второго прохода sections[".comment"] = comment_content # === ПРОХОД 2: генерация (с фиксированными размерами) === pass_num = 2 position[".text"] = 0 position[".data"] = 0 sections[".text"] = bytearray() sections[".data"] = bytearray() # ВТОРОЙ ПРОХОД: используем сохраненные размеры для вычисления адресов for line_num, line in enumerate(lines, start=1): original_line = line.rstrip() try: tokens = tokenize_line(line) parse_tokens(tokens, line_num, original_line) except Exception as e: error_full = make_error_msg(line_num, original_line, str(e)) print(error_full, file=sys.stderr) sys.exit(1) # ПРОВЕРКА: размеры во втором проходе должны совпадать с первым text_size_pass2 = len(sections[".text"]) data_size_pass2 = len(sections[".data"]) print(f"ПРОХОД 2: text_size={text_size_pass2} bytes, data_size={data_size_pass2} bytes") if text_size_pass1 != text_size_pass2: print(f"ПРЕДУПРЕЖДЕНИЕ: Размер .text изменился между проходами: {text_size_pass1} -> {text_size_pass2}") if data_size_pass1 != data_size_pass2: print(f"ПРЕДУПРЕЖДЕНИЕ: Размер .data изменился между проходами: {data_size_pass1} -> {data_size_pass2}") # Используем фактические размеры для финальных вычислений text_size = text_size_pass2 data_size = data_size_pass2 def create_elf(filename): global sections, entry_point, labels, vaddr_text, vaddr_data global offset_text, offset_data, offset_comment, shstrtab_offset, shdr_offset text = sections[".text"] data = sections[".data"] comment = sections[".comment"] # ПЕРЕПРОВЕРКА и коррекция адресов actual_text_size = len(text) actual_data_size = len(data) # Убедись, что vaddr_data вычислен правильно expected_vaddr_data = align_up(vaddr_text + actual_text_size, PAGE_SIZE) if vaddr_data != expected_vaddr_data : print(f"КОРРЕКЦИЯ: vaddr_data исправлен с {hex(vaddr_data)} на {hex(expected_vaddr_data)}") vaddr_data = expected_vaddr_data print(f"ФАКТИЧЕСКИЕ РАЗМЕРЫ: .text={actual_text_size}, .data={actual_data_size}") print(f"КОНЕЧНЫЕ АДРЕСА: .text ends at {hex(vaddr_text + actual_text_size)}, .data starts at {hex(vaddr_data)}") print(f"ПРОВЕРКА ПЕРЕКРЫТИЯ: {'ПЕРЕКРЫТИЕ!' if (vaddr_text + actual_text_size) > vaddr_data else 'OK'}") # Точка входа entry_addr = vaddr_text + labels.get(entry_point, 0) # .shstrtab shstrtab_content = b"\x00.text\x00.data\x00.comment\x00.shstrtab\x00" shstrtab_size = len(shstrtab_content) # Размеры elf_header_size = 64 ph_size = 56 ph_num = 3 shdr_size = 64 shdr_num = 5 file_size = shdr_offset + shdr_num * shdr_size elf_data = bytearray(file_size) # === 1. ELF-заголовок === e_ident = b"\x7fELF\x02\x01\x01\x00" + b"\x00" * 8 elf_header = struct.pack( '<16sHHIQQQIHHHHHH', e_ident, 2, # ET_EXEC 0x3e, # EM_X86_64 1, # version entry_addr, # entry elf_header_size,# phoff shdr_offset, # shoff 0, # flags elf_header_size, ph_size, ph_num, shdr_size, shdr_num, 4 # .shstrtab index ) elf_data[0:64] = elf_header # === 2. Program Headers === def phdr(p_type, p_flags, p_offset, p_vaddr, p_filesz, p_memsz): return struct.pack('") sys.exit(1) source_file = sys.argv[1] source_filename = source_file if not source_file.endswith('.квс'): print("Ошибка: файл должен иметь расширение .квс") sys.exit(1) f_in = None try: f_in = open(source_file, "r", encoding="utf-8") source = f_in.read() f_in.close() except FileNotFoundError: safe_close(f_in) print(f"Ошибка: файл '{source_file}' не найден.") sys.exit(1) except UnicodeDecodeError as e: safe_close(f_in) print(f"Ошибка кодировки в файле '{source_file}': {e}") sys.exit(1) try: parse(source) elf_file = source_file[:-4] + ".elf" create_elf(elf_file) # === ЗАПИСЬ CSV-ЛОГА С НОВЫМ ИМЕНЕМ СТОЛБЦА === log_file = source_file[:-4] + ".log.csv" with open(log_file, "w", encoding="utf-8") as f_log: f_log.write("адрес;байт;целевой_адрес;исходная_команда\n") # ← изменено имя столбца for addr, byte, target, cmd in log_entries: if cmd and ('"' in cmd or ';' in cmd): cmd = '"' + cmd.replace('"', '""') + '"' f_log.write(f"{addr};{byte};{target};{cmd}\n") print(f"Лог создан: {log_file}") print(f"ELF-файл создан: {elf_file}") print("Запустить: ./" + elf_file) print(f"Проверить: objdump -D {elf_file}") print(f"Проверить: eu-readelf -a {elf_file}") print(f"Проверить: readelf -l {elf_file}") print(f"Проверить: file {elf_file}") except SystemExit: raise except Exception as e: print(f"Внутренняя ошибка ассемблера: {e}", file=sys.stderr) sys.exit(1)