From f80524335dd75fd8c609602ce3334d27f12dbc3f Mon Sep 17 00:00:00 2001 From: artradeskz Date: Mon, 3 Nov 2025 14:23:23 +0300 Subject: [PATCH] Add files via upload --- objdump_readelf/kvs_4.py | 678 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 objdump_readelf/kvs_4.py diff --git a/objdump_readelf/kvs_4.py b/objdump_readelf/kvs_4.py new file mode 100644 index 0000000..8655ee0 --- /dev/null +++ b/objdump_readelf/kvs_4.py @@ -0,0 +1,678 @@ +#!/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) \ No newline at end of file