kvs/jumps/kvs_5.py
2025-11-08 20:42:59 +03:00

653 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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, "р": 9, "р10д": 10, "р11д": 11,
"р12д": 12, "р13д": 13, "р14д": 14, "р15д": 15,
# 16-битные
"аикс": 0, "сикс": 1, "дикс": 2, "бикс": 3,
"эсп": 4, "бипи": 5, "эс": 6, "ди": 7,
"р": 8, "р": 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('<Q', imm))
elif 8 <= reg <= 15:
code.extend(b'\x49')
code.append(0xB8 + (reg - 8))
code.extend(struct.pack('<Q', imm))
else:
raise ValueError("Недопустимый регистр в '" + mnemonic + "'")
elif itype == "cmp_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 -0x80000000 <= imm <= 0x7FFFFFFF:
rex = 0x48
if reg >= 8:
rex |= 0x01
code.append(rex)
code.append(0x81)
modrm = 0xF8 | (reg & 7)
code.append(modrm)
code.extend(struct.pack('<i', imm))
else:
raise ValueError("Непосредственное значение слишком велико для сравнения: " + str(imm))
elif itype == "jmp":
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 + 5)
if offset < -0x80000000 or offset > 0x7FFFFFFF:
raise ValueError("Цель слишком далеко для 32-битного смещения в инструкции '" + mnemonic + "': " + str(offset))
code.extend(struct.pack('<i', offset))
elif itype == "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 + 6)
if offset < -0x80000000 or offset > 0x7FFFFFFF:
raise ValueError("Цель условного перехода слишком далеко: " + str(offset))
code.extend(struct.pack('<i', offset))
# === НОВОЕ: короткие переходы ===
elif itype == "short_jmp":
if len(operands) != 1:
raise ValueError(error_invalid_operand_count(mnemonic, 1, len(operands)))
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(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('<IIQQQQQQ',
p_type, p_flags,
p_offset, p_vaddr, p_vaddr,
p_filesz, p_memsz,
PAGE_SIZE
)
ph0 = phdr(1, 4, 0, elf_base_vaddr, elf_header_size + ph_num * ph_size, PAGE_SIZE)
ph1 = phdr(1, 5, offset_text, vaddr_text, len(text), align_up(len(text), PAGE_SIZE))
ph2 = phdr(1, 6, offset_data, vaddr_data, len(data), align_up(len(data), PAGE_SIZE))
phdrs = ph0 + ph1 + ph2
elf_data[elf_header_size : elf_header_size + len(phdrs)] = phdrs
elf_data[offset_text : offset_text + len(text)] = text
elf_data[offset_data : offset_data + len(data)] = data
elf_data[offset_comment : offset_comment + len(comment)] = comment
elf_data[shstrtab_offset : shstrtab_offset + shstrtab_size] = shstrtab_content
def shdr(name_idx, sh_type, flags, addr, offset, size, addralign=1):
return struct.pack('<IIQQQQIIQQ',
name_idx, sh_type, flags, addr, offset, size,
0, 0, addralign, 0
)
sh0 = shdr(0, 0, 0, 0, 0, 0)
sh1 = shdr(1, 1, 6, vaddr_text, offset_text, len(text), 16)
sh2 = shdr(7, 1, 3, vaddr_data, offset_data, len(data), 8)
sh3 = shdr(13, 1, 0, 0, offset_comment, len(comment), 1)
sh4 = shdr(22, 3, 0, 0, shstrtab_offset, shstrtab_size, 1)
shdrs = sh0 + sh1 + sh2 + sh3 + sh4
elf_data[shdr_offset : shdr_offset + len(shdrs)] = shdrs
with open(filename, "wb") as f:
f.write(elf_data)
import os
os.chmod(filename, 0o755)
# === Основной запуск ===
if len(sys.argv) != 2:
print("Использование: python kvs_4.py <файл.квс>")
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)