kvs/kvs_pass1.py

400 lines
15 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 -*-
"""
Первый проход КВС
Вычисляет размеры секций и адреса меток
"""
import sys
sys.path.insert(0, '.')
from kvs_data import PAGE_SIZE, text_vaddr_base, align_up, INSTRUCTIONS
from kvs_data import FIXED_SIZE_INSTRUCTIONS, VARIABLE_SIZE_INSTRUCTIONS
# ========== РЕЖИМ ОЦЕНКИ РАЗМЕРОВ ==========
# True — пессимистичный: все неизвестные инструкции = 10 байт (максимум)
# False — оптимистичный: использовать точный анализ операндов
PESSIMISTIC_SIZE_ESTIMATE = False
def unescape_string(s):
"""Преобразует escape-последовательности в реальные символы"""
result = []
i = 0
while i < len(s):
if s[i] == '\\' and i + 1 < len(s):
if s[i + 1] == 'n':
result.append('\n')
elif s[i + 1] == 't':
result.append('\t')
elif s[i + 1] == 'r':
result.append('\r')
else:
result.append(s[i])
result.append(s[i + 1])
i += 2
else:
result.append(s[i])
i += 1
return ''.join(result)
def read_ast(input_file):
ast_lines = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
ast_lines.append(line.strip())
return ast_lines
def is_hex_number(s):
"""Проверяет, является ли строка шестнадцатеричным числом"""
if len(s) < 3:
return False
if not (s[0] == '0' and (s[1] == 'x' or s[1] == 'X')):
return False
for ch in s[2:]:
if not (('0' <= ch <= '9') or ('a' <= ch <= 'f') or ('A' <= ch <= 'F')):
return False
return True
def is_dec_number(s):
"""Проверяет, является ли строка десятичным числом (возможно с минусом)"""
if not s:
return False
start = 0
if s[0] == '-':
if len(s) == 1:
return False
start = 1
for ch in s[start:]:
if ch < '0' or ch > '9':
return False
return True
def is_register(s):
"""Проверяет, является ли строка именем регистра"""
return s in ['раикс', 'рбикс', 'рсикс', 'рдикс', 'рсипи', 'рбипи', 'рсиай', 'рдиай',
'р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15']
def split_by_operators(content):
"""
Разбивает строку по операторам + и - с сохранением операторов.
Аналог re.split(r'([+\-])', content)
Пример: "раикс + рбикс*4 - 8" -> ["раикс ", "+", " рбикс*4 ", "-", " 8"]
"""
parts = []
current = ''
for ch in content:
if ch == '+' or ch == '-':
if current:
parts.append(current)
parts.append(ch)
current = ''
else:
current += ch
if current:
parts.append(current)
return parts
def parse_memory_operand(operand_str):
"""
Анализирует операнд памяти вида [reg + reg*scale + disp]
Возвращает словарь с информацией.
"""
if not operand_str.startswith('[') or not operand_str.endswith(']'):
return None
content = operand_str[1:-1].strip()
if not content:
return None
result = {
'has_base': False,
'base_reg': None,
'has_index': False,
'index_reg': None,
'scale': 1,
'has_disp': False,
'disp_value': 0,
'disp_size': 0
}
if is_dec_number(content) or is_hex_number(content):
result['has_disp'] = True
if content.startswith('0x') or content.startswith('0X'):
result['disp_value'] = int(content, 16)
else:
result['disp_value'] = int(content)
result['disp_size'] = 4
return result
if is_register(content):
result['has_base'] = True
result['base_reg'] = content
return result
parts = split_by_operators(content)
for part in parts:
part = part.strip()
if not part or part == '+' or part == '-':
continue
if is_register(part):
if not result['has_base']:
result['has_base'] = True
result['base_reg'] = part
elif not result['has_index']:
result['has_index'] = True
result['index_reg'] = part
elif is_dec_number(part) or is_hex_number(part):
result['has_disp'] = True
if part.startswith('0x') or part.startswith('0X'):
result['disp_value'] = int(part, 16)
else:
result['disp_value'] = int(part)
elif '*' in part:
star_pos = part.find('*')
if star_pos != -1:
reg_part = part[:star_pos].strip()
scale_part = part[star_pos + 1:].strip()
if is_register(reg_part):
result['has_index'] = True
result['index_reg'] = reg_part
result['scale'] = int(scale_part)
if result['has_disp']:
if -128 <= result['disp_value'] <= 127:
result['disp_size'] = 1
else:
result['disp_size'] = 4
return result
def estimate_memory_operand_size(operand_str):
"""Оценивает размер операнда памяти в байтах (ModR/M + SIB + disp)"""
if not operand_str:
return 0
addr_mode = parse_memory_operand(operand_str)
if addr_mode is None:
return 0
size = 1
if addr_mode['has_index']:
size += 1
if addr_mode['has_disp']:
size += addr_mode['disp_size']
return size
class Pass1:
def __init__(self):
self.labels = {}
self.label_sections = {}
self.symbols = {}
self.position = {".text": 0, ".data": 0, ".бнд": 0}
self.current_section = ".text"
self.entry_point = "_start"
def process_line(self, line):
parts = line.split(':')
line_type = parts[0]
if line_type == "DIRECTIVE":
directive = parts[1]
if directive == '.текст':
self.current_section = ".text"
elif directive == '.данные':
self.current_section = ".data"
elif directive == '.бнд':
self.current_section = ".бнд"
elif directive == '.глобал':
self.entry_point = parts[2]
elif directive in ('.строкауль', '.строка'):
s = parts[3] if len(parts) > 3 else ""
real_s = unescape_string(s)
bstring = real_s.encode('utf-8')
add_null = (directive == '.строкауль')
size = len(bstring) + (1 if add_null else 0)
self.position[".data"] += size
elif directive == '.константа':
name = parts[2]
value_str = parts[3]
if is_dec_number(value_str):
self.symbols[name] = int(value_str)
elif is_hex_number(value_str):
self.symbols[name] = int(value_str, 16)
else:
self.symbols[name] = 0
elif directive == '.байт':
sec = parts[2]
if len(parts) > 3 and parts[3]:
num_bytes = 1
for ch in parts[3]:
if ch == ',':
num_bytes += 1
self.position[sec] += num_bytes
elif directive == '.резб':
count = int(parts[3])
self.position['.бнд'] += count * 1
elif directive == '.резс':
count = int(parts[3])
self.position['.бнд'] += count * 2
elif directive == '.рездс':
count = int(parts[3])
self.position['.бнд'] += count * 4
elif directive == '.резкс':
count = int(parts[3])
self.position['.бнд'] += count * 8
elif line_type == "LABEL":
label_name = parts[1]
sec = parts[2]
self.labels[label_name] = self.position[sec]
self.label_sections[label_name] = sec
elif line_type == "INSTR":
mnemonic = parts[1]
sec = parts[2]
operands_str = parts[3] if len(parts) > 3 else ""
operands = []
if operands_str:
current = ''
for ch in operands_str:
if ch == ',':
operands.append(current)
current = ''
else:
current += ch
if current:
operands.append(current)
size = self.estimate_size(mnemonic, operands)
self.position[sec] += size
def estimate_size(self, mnemonic, operands):
"""Динамический расчёт размера инструкции"""
# Инструкции с фиксированным размером
if mnemonic in FIXED_SIZE_INSTRUCTIONS:
return FIXED_SIZE_INSTRUCTIONS[mnemonic]
# Инструкции с переменным размером — анализируем операнды
if mnemonic in ("загрузить", "сохранить", "загрузить_адрес"):
for op in operands:
if op.startswith('MEM:reg_indirect:'):
return 3 # косвенная: REX + opcode + ModR/M
return 7 # абсолютная: REX + opcode + ModR/M + disp32
if mnemonic in ("втолкнуть", "вытолкнуть"):
if operands:
reg = operands[0]
if reg in ('р8', 'р9', 'р10', 'р11', 'р12', 'р13', 'р14', 'р15'):
return 2 # нужен REX-префикс
return 1 # без REX
if mnemonic in ("прибавить_непосредственно", "вычесть_непосредственно", "сравнить_с"):
return 7 # пессимистично: REX + opcode + ModR/M + imm32
if mnemonic == "переместить_имм":
return 10 # пессимистично: mov reg64, imm64
if mnemonic in ("переместить_сулями", "переместить_со_знаком"):
return 8 # REX + 3-байтный opcode + ModR/M + disp32
if mnemonic == "загрузить_байт":
return 2 # opcode + imm8
if mnemonic == "втолкнуть_непосредственно":
return 5 # пессимистично: opcode + imm32
# Неизвестная инструкция
if PESSIMISTIC_SIZE_ESTIMATE:
return 10
else:
return 3
def calculate_layout(self):
text_size = self.position[".text"]
data_size = self.position[".data"]
bnd_size = self.position[".бнд"]
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, PAGE_SIZE)
vaddr_text = text_vaddr_base
vaddr_data = align_up(vaddr_text + text_size, PAGE_SIZE)
vaddr_bnd = vaddr_data + PAGE_SIZE
comment_size = len("Сборщик КВС".encode('utf-8')) + 1
offset_comment = align_up(offset_data + data_size, 1)
if bnd_size > 0:
shstrtab_content = b"\x00.text\x00.data\x00.bss\x00.comment\x00.shstrtab\x00"
else:
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_offset = align_up(shstrtab_offset + shstrtab_size, 16)
return {
"text_size": text_size,
"data_size": data_size,
"bnd_size": bnd_size,
"offset_text": offset_text,
"offset_data": offset_data,
"vaddr_text": vaddr_text,
"vaddr_data": vaddr_data,
"vaddr_bnd": vaddr_bnd,
"offset_comment": offset_comment,
"comment_size": comment_size,
"shstrtab_offset": shstrtab_offset,
"shstrtab_size": shstrtab_size,
"shdr_offset": shdr_offset,
"entry_point": self.entry_point,
}
def write_pass1(pass1_data, labels, label_sections, symbols, output_file):
with open(output_file, 'w', encoding='utf-8') as f:
for key, value in pass1_data.items():
f.write(f"PARAM:{key}:{value}\n")
for label, pos in labels.items():
sec = label_sections.get(label, ".text")
f.write(f"LABEL:{label}:{sec}:{pos}\n")
for name, value in symbols.items():
f.write(f"SYMBOL:{name}:{value}\n")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python kvs_pass1.py <вход.аст> <выход.проход1>")
sys.exit(1)
ast_lines = read_ast(sys.argv[1])
pass1 = Pass1()
for line in ast_lines:
pass1.process_line(line)
layout = pass1.calculate_layout()
write_pass1(layout, pass1.labels, pass1.label_sections, pass1.symbols, sys.argv[2])
print(f"Проход 1 (режим: {'пессимистичный' if PESSIMISTIC_SIZE_ESTIMATE else 'оптимистичный'}):")
print(f" .text: размер={layout['text_size']} байт, смещение_в_файле=0x{layout['offset_text']:x}, виртуальный_адрес=0x{layout['vaddr_text']:x}")
print(f" .data: размер={layout['data_size']} байт, смещение_в_файле=0x{layout['offset_data']:x}, виртуальный_адрес=0x{layout['vaddr_data']:x}")
print(f" .бнд: размер={layout['bnd_size']} байт, виртуальный_адрес=0x{layout['vaddr_bnd']:x}")
print(f" entry_point: {layout['entry_point']}")
print(f" метки:")
for label, pos in pass1.labels.items():
sec = pass1.label_sections.get(label, ".text")
if sec == ".text":
addr = layout['vaddr_text'] + pos
elif sec == ".data":
addr = layout['vaddr_data'] + pos
elif sec == ".бнд":
addr = layout['vaddr_bnd'] + pos
else:
addr = pos
print(f" {label}: секция={sec}, смещение={pos}, адрес=0x{addr:x}")