163 lines
6.3 KiB
Python
163 lines
6.3 KiB
Python
"""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_partial_breakpoint_clear_retains_remaining_backend_id(self):
|
|
class FlakyBridge(FakeBridge):
|
|
failed = False
|
|
|
|
def request(self, command, **args):
|
|
if command == 'clear' and args['id'] == 11 and not self.failed:
|
|
self.failed = True
|
|
raise SessionError('временная ошибка удаления')
|
|
return super().request(command, **args)
|
|
|
|
bridge = FlakyBridge(self.memory)
|
|
session = DebugSession(self.model, bridge)
|
|
session.breakpoints[1] = [10, 11]
|
|
with self.assertRaisesRegex(SessionError, 'временная ошибка'):
|
|
session.clear_breakpoint(1)
|
|
self.assertEqual(session.breakpoints[1], [11])
|
|
self.assertEqual(session.clear_breakpoint(1)['backend_ids'], [11])
|
|
self.assertNotIn(1, session.breakpoints)
|
|
|
|
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()
|