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