Files
Sprinter-SDCC/toolchain/sdbg/build.py
T

114 lines
6.3 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.
"""Сборочные операции sdbg: уникальные debug-символы без изменения инструкций."""
from __future__ import annotations
import hashlib
import json
import re
import subprocess
from pathlib import Path
from .macros import anchor_symbols, extract
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def write_json(path: Path, data) -> None:
temporary = path.with_suffix(path.suffix + '.tmp')
temporary.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n')
temporary.replace(path)
def normalize(assembly: str, adb: str, module: str):
"""Меняет только имена debug-записей; сохраняет число строк .asm."""
match = re.search(r'^\s*\.module\s+(\S+)', assembly, re.M)
if not match:
raise ValueError('В asm отсутствует .module')
old = match.group(1)
# F/XF/L связывают статики, функции и локальные с единицей трансляции.
def rename(text):
text = re.sub(r'\b(X?F)' + re.escape(old) + r'(?=\$)',
lambda m: m[1] + module, text)
return re.sub(r'\bL' + re.escape(old) + r'(?=[.$])', 'L' + module, text)
# Не трогаем .ascii и инструкции: строковый литерал может содержать
# текст, похожий на debug-символ. Переименовываем только определения
# и объявления символов, сгенерированные SDCC.
symbol_line = re.compile(r'^\s*(?:\.globl\s+|(?:X?F|L)' + re.escape(old) + r'[$.])')
assembly = ''.join(rename(line) if symbol_line.match(line) else line
for line in assembly.splitlines(keepends=True))
adb = rename(adb)
assembly = re.sub(r'(?m)^(\s*\.module\s+)\S+', lambda m: m[1] + module, assembly)
adb = re.sub(r'(?m)^M:.*$', 'M:' + module, adb)
markers, active, output = {}, {}, []
for number, line in enumerate(assembly.splitlines(keepends=True), 1):
definition = re.match(r'\s*(C\$[^\s=]+)\s*=\s*\.', line)
if definition:
original = definition[1]
parts = original.split('$')
if len(parts) != 5:
raise ValueError('Неизвестная форма CDB: ' + original)
unique = f'C${module}_{len(markers)}${parts[2]}${parts[3]}${parts[4]}'
active[original] = unique
markers[unique] = {'original': original, 'file': parts[1],
'line': int(parts[2]), 'asm_line': number}
line = line.replace(original, unique)
elif re.match(r'\s*\.globl\s+C\$', line):
original = line.split()[1]
if original not in active:
raise ValueError('CDB .globl без определения: ' + original)
line = line.replace(original, active[original])
output.append(line)
return ''.join(output), adb, markers
def compile_unit(sdcc: Path, assembler: Path, source: Path, output: Path, flags, debug=True):
"""-S и те же опции ассемблера, которые SDCC 4.5 использует с --debug."""
source = source.resolve()
asm, adb = output.with_suffix('.asm'), output.with_suffix('.adb')
compile_flags = [*flags, '-DSPRINTER_SDBG_ANCHORS=1'] if debug else flags
# SDCC -E кодирует внутренний разделитель строк inline asm байтом 0x87;
# для списка #line нужны только ASCII-пути. Само описание логов извлекаем
# отдельным metadata-проходом без asm-развёртки.
preprocessed = subprocess.check_output([str(sdcc), *compile_flags, '-E', str(source)],
text=True, errors='replace')
dependencies = {str(source)}
for filename in re.findall(r'^#(?:line)?\s*\d+\s+"([^"]+)"', preprocessed, re.M):
path = Path(filename).resolve()
if path.is_file(): dependencies.add(str(path))
hashes = {name: digest(Path(name)) for name in dependencies}
if not debug:
subprocess.run([str(sdcc), *flags, '-c', '-o', str(output), str(source)], check=True)
if any(digest(Path(name)) != sha for name, sha in hashes.items()):
raise ValueError('Исходники изменились во время компиляции')
write_json(output.with_suffix('.sdbg-input.json'), {'dependency_hashes': hashes})
return
metadata = subprocess.check_output([str(sdcc), *compile_flags,
'-DSPRINTER_SDBG_METADATA=1', '-E', str(source)], text=True)
macros = extract(metadata, dependencies)
subprocess.run([str(sdcc), *compile_flags, '--debug', '-S', '-o', str(asm), str(source)], check=True)
if any(digest(Path(name)) != sha for name, sha in hashes.items()):
raise ValueError('Исходники изменились во время компиляции')
original = asm.read_text()
# Включаем output stem: один исходник может собираться в разные банки.
module = 'sdbg_' + hashlib.sha256((str(source) + ':' + output.stem).encode()).hexdigest()[:16]
assembly, descriptions, markers = normalize(original, adb.read_text(), module)
assembly, log_macros = anchor_symbols(assembly, module, macros)
asm.write_text(assembly)
adb.write_text(descriptions)
subprocess.run([str(assembler), '-plosgffwy', str(output), str(asm)], check=True)
# Комментарии SDCC сохраняют фактические пути строк, включая заголовки.
sources = {}
for name in dependencies:
sources.setdefault(Path(name).name, set()).add(name)
for filename in re.findall(r'^;(.+?):\d+:', original, re.M):
path = Path(filename).resolve()
if path.is_file():
sources.setdefault(path.name, set()).add(str(path))
write_json(output.with_suffix('.sdbg-unit.json'), {
'module': module, 'source': str(source), 'source_sha256': digest(source),
'asm': asm.name, 'adb': adb.name, 'object': output.name,
'flags': compile_flags, 'markers': markers, 'log_macros': log_macros,
'dependency_hashes': hashes,
'sources': {k: sorted(v) for k, v in sources.items()},
})