Sprinter: добавить отладку C-исходников и интеграцию VS Code
This commit is contained in:
@@ -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()},
|
||||
})
|
||||
Reference in New Issue
Block a user