#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ kvs_5.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"}, # === Короткие переходы (добавлено) === "короткий_переход": {"code": b"\xEB", "type": "short_jmp"}, "короткий_переход_если_равно" : {"code": b"\x74", "type": "short_jcc"}, "короткий_переход_если_неравно" : {"code": b"\x75", "type": "short_jcc"}, "короткий_переход_если_меньше" : {"code": b"\x7C", "type": "short_jcc"}, "короткий_переход_если_больше" : {"code": b"\x7F", "type": "short_jcc"}, "короткий_переход_если_меньше_или_равно": {"code": b"\x7E", "type": "short_jcc"}, "короткий_переход_если_больше_или_равно": {"code": b"\x7D", "type": "short_jcc"}, "короткий_переход_если_перенос" : {"code": b"\x72", "type": "short_jcc"}, "короткий_переход_если_нет_переноса" : {"code": b"\x73", "type": "short_jcc"}, "короткий_переход_если_ноль" : {"code": b"\x74", "type": "short_jcc"}, "короткий_переход_если_не_ноль" : {"code": b"\x75", "type": "short_jcc"}, } REGISTERS = { # 64-битные "раикс": 0, "рсикс": 1, "рдикс": 2, "рбикс": 3, "рсипи": 4, "рбипи": 5, "рсиай": 6, "рдиай": 7, "р8": 8, "р9": 9, "р10": 10, "р11": 11, "р12": 12, "р13": 13, "р14": 14, "р15": 15, # 32-битные "еаикс": 0, "есикс": 1, "едикс": 2, "ебикс": 3, "есипи": 4, "ебипи": 5, "есиай": 6, "едиай": 7, "р8д": 8, "р9д": 9, "р10д": 10, "р11д": 11, "р12д": 12, "р13д": 13, "р14д": 14, "р15д": 15, # 16-битные "аикс": 0, "сикс": 1, "дикс": 2, "бикс": 3, "эсп": 4, "бипи": 5, "эс": 6, "ди": 7, "р8в": 8, "р9в": 9, "р10в": 10, "р11в": 11, "р12в": 12, "р13в": 13, "р14в": 14, "р15в": 15, # 8-битные младшие "ал": 0, "кл": 1, "дл": 2, "бл": 3, "спл": 4, "бпл": 5, "сил": 6, "дил": 7, "р8б": 8, "р9б": 9, "р10б": 10, "р11б": 11, "р12б": 12, "р13б": 13, "р14б": 14, "р15б": 15, # 8-битные старшие (только первые 4) "аш": 0, "чш": 1, "дш": 2, "бш": 3, } # Глобальное состояние ассемблера 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 data_vaddr_base = 0x402000 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): if reg_name in ["аш", "чш", "дш", "бш"]: return {"size": 8, "index": REGISTERS[reg_name], "high8": True} elif reg_name.endswith(("б", "л")) or reg_name in ["ал", "кл", "дл", "бл", "спл", "бпл", "сил", "дил"]: return {"size": 8, "index": REGISTERS[reg_name], "high8": False} elif reg_name.endswith("в") or reg_name in ["аикс", "сикс", "дикс", "бикс", "эсп", "бипи", "эс", "ди"]: return {"size": 16, "index": REGISTERS[reg_name]} elif reg_name.endswith("д") or reg_name in ["еаикс", "есикс", "едикс", "ебикс", "есипи", "ебипи", "есиай", "едиай"]: return {"size": 32, "index": REGISTERS[reg_name]} else: return {"size": 64, "index": REGISTERS[reg_name]} # === Функции формирования ошибок === def make_error_msg(line_num, line_text, detail): return "Ошибка в файле " + source_filename + ", строка " + str(line_num) + ":\n " + line_text + "\n" + detail def error_invalid_operand_count(mnemonic, expected, got): return "Инструкция '" + mnemonic + "':\n ожидается " + str(expected) + " операнд(а/ов), получено: " + str(got) def error_unknown_mnemonic(mnemonic): return "Неизвестная инструкция: '" + mnemonic + "'" def error_invalid_register(op_name, reg_name, mnemonic): return ("Инструкция '" + mnemonic + "':\n операнд '" + op_name + "' = '" + reg_name + "' не является допустимым регистром.\n Допустимые регистры: " + ", ".join(list(REGISTERS.keys()))) def error_invalid_number_format(s): return "Недопустимый формат числа: '" + s + "'" def error_unknown_directive(word): return "Неизвестная директива: '" + word + "'" def error_missing_label(label): return "Метка не найдена: '" + 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 pass_num == 2: base = vaddr_text if section == ".text" else vaddr_data return labels[operand] + base else: return labels[operand] if operand in symbols: return symbols[operand] raise ValueError("Неизвестный операнд: " + operand) 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("Цель слишком далеко для 32-битного смещения в инструкции '" + mnemonic + "': " + str(offset)) code.extend(struct.pack(' 0x7FFFFFFF: raise ValueError("Цель условного перехода слишком далеко: " + str(offset)) code.extend(struct.pack(' 127: raise ValueError("Цель слишком далеко для короткого перехода: " + str(offset)) code.append(0xEB) code.append(offset & 0xFF) elif itype == "short_jcc": if len(operands) != 1: raise ValueError(error_invalid_operand_count(mnemonic, 1, len(operands))) code.extend(instr["code"]) target = parse_operand(operands[0]) current_addr = vaddr_text + position[".text"] offset = target - (current_addr + 2) if offset < -128 or offset > 127: raise ValueError("Цель слишком далеко для короткого условного перехода: " + str(offset)) code.append(offset & 0xFF) else: raise ValueError("Неизвестный тип инструкции: " + itype) return bytes(code) def parse_instruction_or_directive(tokens, line_num, line_text): global current_section, entry_point first = tokens[0] if first[0] != 'word': raise ValueError("Ожидалось слово, получено: " + str(first)) word = first[1] if word.startswith('.'): if word == '.текст': current_section = ".text" elif word == '.данные': current_section = ".data" elif word == '.глобал': if len(tokens) < 2 or tokens[1][0] != 'word': raise ValueError(".глобал требует имя метки") entry_point = tokens[1][1] elif word == '.строка_нуль' or word == '.строка': if len(tokens) < 2 or tokens[1][0] != 'string': raise ValueError(word + " требует строку в кавычках") if current_section != ".data": raise ValueError("Строки разрешены только в секции .data") s = tokens[1][1] bstring = s.encode('utf-8') add_null = (word == '.строка_нуль') size = len(bstring) + (1 if add_null else 0) if pass_num == 1: position[".data"] += size elif pass_num == 2: sections[".data"] += bstring if add_null: sections[".data"] += b'\x00' position[".data"] += size else: raise ValueError(error_unknown_directive(word)) return mnemonic = word if mnemonic not in INSTRUCTIONS: raise ValueError(error_unknown_mnemonic(mnemonic)) operands = [] for tok in tokens[1:]: if tok[0] == 'word': operands.append(tok[1]) elif tok[0] == 'comma': continue else: raise ValueError("Недопустимый токен в операндах: " + str(tok)) if pass_num == 2: instr_info = INSTRUCTIONS[mnemonic] itype = instr_info["type"] code = encode_instruction(mnemonic, operands, line_num, line_text) start_pos = position[current_section] if current_section == ".text": start_vaddr = vaddr_text + start_pos target_addr = "" try: if itype in ("jmp", "jcc", "short_jmp", "short_jcc"): target = parse_operand(operands[0]) target_addr = hex(target) elif itype == "reg_imm": op_str = operands[1] if op_str in labels: sec = label_sections[op_str] addr = labels[op_str] + (vaddr_text if sec == ".text" else vaddr_data) target_addr = hex(addr) except Exception: target_addr = "ошибка" original_cmd = line_text.split(';')[0].strip() for i, byte in enumerate(code): addr = start_vaddr + i byte_hex = "{:02X}".format(byte) cmd_to_log = original_cmd if i == 0 else "" log_entries.append((hex(addr), byte_hex, target_addr if i == 0 else "", cmd_to_log)) sections[current_section] += code position[current_section] += len(code) else: instr_info = INSTRUCTIONS[mnemonic] itype = instr_info["type"] size_map = { "reg_imm": 10, "cmp_reg_imm": 7, "jmp": 5, "jcc": 6, "none": lambda: len(instr_info["code"]), "short_jmp": 2, "short_jcc": 2, } if itype in size_map: sz = size_map[itype] position[current_section] += sz() if callable(sz) else sz else: raise ValueError("Неподдерживаемый тип инструкции при подсчёте размера: " + itype) def parse_tokens(tokens, line_num, line_text): if not tokens: return if len(tokens) >= 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("ПРОХОД 1: text_size=" + str(text_size_pass1) + " bytes, data_size=" + str(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 vaddr_data = align_up(vaddr_text + text_size_pass1, PAGE_SIZE) comment_content = "Сборщик КВС".encode('utf-8') + b'\x00' comment_size = len(comment_content) offset_comment = align_up(offset_data + data_size_pass1, 1) 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("РАЗМЕЩЕНИЕ: .text at offset=" + hex(offset_text) + ", .data at offset=" + hex(offset_data)) print("ВИРТУАЛЬНЫЕ АДРЕСА: .text at " + hex(vaddr_text) + ", .data at " + hex(vaddr_data)) print("РАССТОЯНИЕ МЕЖДУ СЕКЦИЯМИ: " + hex(vaddr_data - vaddr_text) + " bytes") 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("ПРОХОД 2: text_size=" + str(text_size_pass2) + " bytes, data_size=" + str(data_size_pass2) + " bytes") if text_size_pass1 != text_size_pass2: print("ПРЕДУПРЕЖДЕНИЕ: Размер .text изменился между проходами: " + str(text_size_pass1) + " -> " + str(text_size_pass2)) if data_size_pass1 != data_size_pass2: print("ПРЕДУПРЕЖДЕНИЕ: Размер .data изменился между проходами: " + str(data_size_pass1) + " -> " + str(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) expected_vaddr_data = align_up(vaddr_text + actual_text_size, PAGE_SIZE) if vaddr_data != expected_vaddr_data: print("КОРРЕКЦИЯ: vaddr_data исправлен с " + hex(vaddr_data) + " на " + hex(expected_vaddr_data)) vaddr_data = expected_vaddr_data print("ФАКТИЧЕСКИЕ РАЗМЕРЫ: .text=" + str(actual_text_size) + ", .data=" + str(actual_data_size)) print("КОНЕЧНЫЕ АДРЕСА: .text ends at " + hex(vaddr_text + actual_text_size) + ", .data starts at " + hex(vaddr_data)) print("ПРОВЕРКА ПЕРЕКРЫТИЯ: " + ("ПЕРЕКРЫТИЕ!" if (vaddr_text + actual_text_size) > vaddr_data else "OK")) entry_addr = vaddr_text + labels.get(entry_point, 0) 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) # 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('") 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("Ошибка: файл '" + source_file + "' не найден.") sys.exit(1) except UnicodeDecodeError as e: safe_close(f_in) print("Ошибка кодировки в файле '" + source_file + "': " + str(e)) sys.exit(1) try: parse(source) elf_file = source_file[:-4] + ".elf" create_elf(elf_file) 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(addr + ";" + byte + ";" + target + ";" + cmd + "\n") print("Лог создан: " + log_file) print("ELF-файл создан: " + elf_file) print("Запустить: ./" + elf_file) except SystemExit: raise except Exception as e: print("Внутренняя ошибка ассемблера: " + str(e), file=sys.stderr) sys.exit(1)