Sprinter: добавить отладку C-исходников и интеграцию VS Code

This commit is contained in:
2026-09-15 17:58:41 +03:00
parent 50c6e56b7b
commit e4695b8281
62 changed files with 7147 additions and 27 deletions
+113
View File
@@ -0,0 +1,113 @@
"""Сборочные операции 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()},
})