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
+143
View File
@@ -0,0 +1,143 @@
"""Source-session: identity, банковские условия и безопасные переменные."""
from pathlib import Path
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / 'toolchain'))
from sdbg.image import read_ihx
from sdbg.session import DebugSession, SessionError
def record(kind, address, data=b''):
body = bytes((len(data), address >> 8, address & 0xff, kind)) + data
return ':' + (body + bytes((-sum(body) & 0xff,))).hex().upper()
class FakeModel:
def __init__(self, directory):
self.directory = Path(directory)
self.manifest = {'executable': 'test.exe', 'build_id': 'build'}
self.stale_sources = []
self.symbols = {'_bank_pages': 0x8200}
self.instructions = {
0x8100: {'link_address': 0x8100, 'logical_address': 0x8100,
'size': 2, 'bank': None, 'window': 2},
0x1c000: {'link_address': 0x1c000, 'logical_address': 0xc000,
'size': 2, 'bank': 1, 'window': 3},
}
self.functions = [
{'name': 'main', 'link_address': 0x8100, 'logical_address': 0x8100,
'start': 0x8100, 'end': 0x8102, 'bank': None, 'window': 2},
{'name': 'worker', 'link_address': 0x1c000, 'logical_address': 0xc000,
'start': 0x1c000, 'end': 0x1c002, 'bank': 1, 'window': 3},
]
self.variables = [
{'name': 'total', 'module': None, 'size': 2, 'type': 'SI:S',
'signed': True, 'supported': True, 'link_address': 0x8210,
'logical_address': 0x8210, 'bank': None, 'window': 2},
]
def verify_executable(self):
return None
def addr2line(self, address):
return {'status': 'mapped', 'address': address}
def line_locations(self, filename, line):
return {'stale_source': bool(self.stale_sources),
'locations': [self.functions[1]] if line == 7 else []}
class FakeBridge:
def __init__(self, memory, pc=0x8100, pg3=5):
self.memory = memory
self.generation = 1
self.registers = {'PC': pc, 'PG0': 0, 'PG1': 1, 'PG2': 2, 'PG3': pg3}
self.calls = []
self.next_id = 10
def handshake(self):
return {'protocol': 1, 'capabilities': {'bank_guard': True}}
def wait_stopped(self, timeout=5):
return {'state': 'stopped', 'registers': dict(self.registers)}
def request(self, command, **args):
self.calls.append((command, args))
if command == 'memory':
data = bytes(self.memory.get(args['address'] + i, 0)
for i in range(args['length']))
return {'hex': data.hex()}
if command == 'breakpoint':
value = {'id': self.next_id}
self.next_id += 1
return value
if command == 'clear':
return {'cleared': args['id']}
raise AssertionError(command)
class SessionTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
directory = Path(self.temp.name)
ihx = '\n'.join((
record(0, 0x8100, b'\x21\x2a'),
record(4, 0, b'\x00\x01'),
record(0, 0xc000, b'\x3e\x07'),
record(1, 0),
)) + '\n'
(directory / 'test.ihx').write_text(ihx)
self.model = FakeModel(directory)
self.memory = {0x8100: 0x21, 0x8101: 0x2a,
0xc000: 0x3e, 0xc001: 0x07,
0x8200: 0, 0x8201: 7,
0x8210: 0xfe, 0x8211: 0xff}
def tearDown(self):
self.temp.cleanup()
def test_ihx_rejects_checksum(self):
path = Path(self.temp.name) / 'bad.ihx'
path.write_text(':00000001FE\n')
with self.assertRaisesRegex(ValueError, 'сумм'):
read_ihx(path)
def test_attach_and_read_signed_variable(self):
session = DebugSession(self.model, FakeBridge(self.memory))
attached = session.attach()
self.assertEqual(attached['pc'], 0x8100)
self.assertEqual(attached['bank_pages'], {1: 7})
self.assertEqual(session.read_variable('total')['value'], -2)
def test_loaded_image_mismatch_is_rejected(self):
self.memory[0x8100] = 0
with self.assertRaisesRegex(SessionError, 'не соответствует'):
DebugSession(self.model, FakeBridge(self.memory)).attach()
def test_banked_breakpoint_has_page_condition(self):
bridge = FakeBridge(self.memory)
session = DebugSession(self.model, bridge)
session.attach()
result = session.break_line('worker.c', 7)
self.assertEqual(result['backend_ids'], [10])
self.assertIn(('breakpoint', {'address': 0xc000, 'window': 3, 'page': 7}),
bridge.calls)
session.clear_breakpoint(result['id'])
def test_stale_source_and_unmapped_bank_are_rejected(self):
session = DebugSession(self.model, FakeBridge(self.memory))
session.attach()
self.model.stale_sources = ['worker.c']
with self.assertRaisesRegex(SessionError, 'пересоберите'):
session.break_line('worker.c', 7)
bridge = FakeBridge(self.memory, pc=0xc000, pg3=7)
session = DebugSession(self.model, bridge)
attached = session.attach()
self.assertEqual(attached['location']['link_address'], 0x1c000)
if __name__ == '__main__':
unittest.main()