Add bounded logical Z80 disassembly to shared MCP
This commit is contained in:
@@ -38,7 +38,7 @@ async def probe() -> None:
|
||||
|
||||
names = {tool.name for tool in (await client.list_tools()).tools}
|
||||
if not {'start_session', 'stop_session', 'where', 'read_variable',
|
||||
'press_key', 'list_breakpoints'} <= names:
|
||||
'press_key', 'list_breakpoints', 'disassemble_logical'} <= names:
|
||||
raise RuntimeError('Нет инструментов автономного запуска')
|
||||
initial = await call('session_status')
|
||||
if initial['phase'] != 'idle':
|
||||
@@ -67,6 +67,18 @@ async def probe() -> None:
|
||||
location = await call('where')
|
||||
if location.get('function', {}).get('name') != 'main':
|
||||
raise RuntimeError('MCP не остановился в main: ' + repr(location))
|
||||
bytes_before = await call('read_memory',
|
||||
{'address': hex(location['pc']), 'length': 32})
|
||||
disassembly = await call('disassemble_logical',
|
||||
{'address': hex(location['pc']), 'length': 32})
|
||||
bytes_after = await call('read_memory',
|
||||
{'address': hex(location['pc']), 'length': 32})
|
||||
if disassembly['space'] != 'logical_z80' or \
|
||||
disassembly['address'] != location['pc'] or \
|
||||
not disassembly['text'].upper().startswith(
|
||||
f"{location['pc']:04X}:") or \
|
||||
bytes_before['hex'] != bytes_after['hex']:
|
||||
raise RuntimeError('Нет дизассемблирования main: ' + repr(disassembly))
|
||||
await call('read_variable', {'name': 'errno'})
|
||||
shot = await call('screenshot')
|
||||
if Path(shot['path']).read_bytes()[:8] != b'\x89PNG\r\n\x1a\n':
|
||||
@@ -145,6 +157,7 @@ async def probe() -> None:
|
||||
'tools': len(names), 'start_ms': start_ms,
|
||||
'session_id': status['session_id'],
|
||||
'socket': socket, 'pc': location['pc'],
|
||||
'disassembly_head': disassembly['text'][:90],
|
||||
'key': key['key'], 'after_getchar_line': 63},
|
||||
ensure_ascii=False), flush=True)
|
||||
finally:
|
||||
|
||||
@@ -29,6 +29,10 @@ class DummyBridge:
|
||||
if command == 'snapshot': return {'state': self.state}
|
||||
if command == 'console_print': return {'printed': True}
|
||||
if command == 'memory': return {'hex': '00' * arguments['length']}
|
||||
if command == 'disassemble_logical':
|
||||
return {'space': 'logical_z80', 'address': arguments['address'],
|
||||
'length': arguments['length'], 'text': '8100: NOP',
|
||||
'generation': self.generation}
|
||||
if command == 'key': return {'accepted': True}
|
||||
raise AssertionError(command)
|
||||
|
||||
@@ -243,6 +247,22 @@ class ServerTests(unittest.TestCase):
|
||||
finally:
|
||||
controller.close()
|
||||
|
||||
def test_disassemble_logical_is_bounded_and_labels_bank_mapping(self):
|
||||
controller = SessionController(DummySession())
|
||||
try:
|
||||
result = controller.call('disassemble_logical',
|
||||
{'address': 0x8100, 'length': 32})
|
||||
self.assertEqual(result['space'], 'logical_z80')
|
||||
self.assertEqual(result['address'], 0x8100)
|
||||
self.assertEqual(result['bank_pages'], {})
|
||||
with self.assertRaisesRegex(SessionError, 'Дизассемблирование'):
|
||||
controller.call('disassemble_logical',
|
||||
{'address': 0xffff, 'length': 2})
|
||||
self.assertEqual(len([call for call in controller.session.bridge.calls
|
||||
if call[0] == 'disassemble_logical']), 1)
|
||||
finally:
|
||||
controller.close()
|
||||
|
||||
def test_control_lease_blocks_other_client_and_can_be_released_or_expire(self):
|
||||
controller = SessionController(DummySession())
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user