Expand shared Sprinter MCP with managed launch, raw reads and key input
This commit is contained in:
@@ -19,7 +19,7 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
PYTHON = Path.home()/'.pyenv/shims/python'
|
||||
sys.path.insert(0, str(ROOT/'toolchain'))
|
||||
from mame_interactive import resolve
|
||||
from sdbg.server import rpc_call
|
||||
from sdbg.server import RPC_GENERATION_MUTATIONS, rpc_call as raw_rpc_call
|
||||
from sdbg.session import SessionError
|
||||
|
||||
|
||||
@@ -98,6 +98,10 @@ def main():
|
||||
help='Python 3.12 с MCP SDK 2.x для совместной живой пробы DAP/MCP')
|
||||
parser.add_argument('--orphan-expiry', action='store_true',
|
||||
help='проверить очистку точки владельца MCP без heartbeat через 30 с')
|
||||
parser.add_argument('--screen-mcp-python', default=None,
|
||||
help='Python с MCP SDK 2.x для снимка hello во время getchar')
|
||||
parser.add_argument('--screen-proof', default=None,
|
||||
help='сохранить PNG работающего hello вне временной сессии')
|
||||
parser.add_argument('--clear-while-running', action='store_true',
|
||||
help='во время WAITKEY удалить личную точку; использовать с --waitkey --emulated-key')
|
||||
parser.add_argument('--exit-while-stopped', action='store_true',
|
||||
@@ -128,6 +132,14 @@ def main():
|
||||
launch['launchPath'] = options.launch_path
|
||||
send(process, 2, 'launch', launch)
|
||||
launched, buffer, events = wait_response(process, buffer, 'launch', 80)
|
||||
session_identity = raw_rpc_call(launched['body']['socket'], 'status')
|
||||
def session_rpc(path, method, arguments=None, timeout=10):
|
||||
identity = {'session_id': session_identity['session_id'],
|
||||
'build_id': session_identity['build_id']}
|
||||
if method in RPC_GENERATION_MUTATIONS:
|
||||
current = raw_rpc_call(path, 'status', **identity)
|
||||
identity['generation'] = current['generation']
|
||||
return raw_rpc_call(path, method, arguments, timeout, **identity)
|
||||
if not any(item.get('event') == 'initialized' for item in events):
|
||||
initialized, buffer = receive(process, buffer, 5)
|
||||
if initialized.get('event') != 'initialized':
|
||||
@@ -156,13 +168,13 @@ def main():
|
||||
raise RuntimeError('MCP SDK probe: '+probe.stderr[-4000:])
|
||||
if options.orphan_expiry:
|
||||
owner = 'mcp:orphan-expiry-probe'
|
||||
orphan = rpc_call(launched['body']['socket'], 'break_line', {
|
||||
orphan = session_rpc(launched['body']['socket'], 'break_line', {
|
||||
'file': source, 'line': 62, 'owner': owner})
|
||||
rpc_call(launched['body']['socket'], 'claim_control', {'owner': owner})
|
||||
session_rpc(launched['body']['socket'], 'claim_control', {'owner': owner})
|
||||
print('Ожидание истечения MCP owner и очистки его точки...', flush=True)
|
||||
deadline = time.monotonic()+36
|
||||
while time.monotonic() < deadline:
|
||||
events = rpc_call(launched['body']['socket'], 'events', {'after': 0})
|
||||
events = session_rpc(launched['body']['socket'], 'events', {'after': 0})
|
||||
if any(event['event'] == 'owner_expired' and
|
||||
event['body']['owner'] == owner for event in events['events']):
|
||||
break
|
||||
@@ -170,7 +182,7 @@ def main():
|
||||
else:
|
||||
raise RuntimeError('MCP owner не истёк за 36 с')
|
||||
try:
|
||||
rpc_call(launched['body']['socket'], 'clear_breakpoint', {
|
||||
session_rpc(launched['body']['socket'], 'clear_breakpoint', {
|
||||
'id': orphan['id'], 'owner': owner})
|
||||
except SessionError as error:
|
||||
if 'Неизвестная логическая точка' not in str(error):
|
||||
@@ -284,37 +296,49 @@ def main():
|
||||
socket_path = launched['body']['socket']
|
||||
clear_point = None
|
||||
if options.clear_while_running:
|
||||
clear_point = rpc_call(socket_path, 'break_line', {
|
||||
clear_point = session_rpc(socket_path, 'break_line', {
|
||||
'file': str(ROOT/'tests/hello/hello.c'), 'line': 63,
|
||||
'owner': 'probe:running-clear'})
|
||||
started = time.monotonic()
|
||||
send(process, 8, 'next')
|
||||
_, buffer, _ = wait_response(process, buffer, 'next', 3)
|
||||
response_ms = round((time.monotonic()-started)*1000, 2)
|
||||
if options.screen_mcp_python:
|
||||
time.sleep(.6)
|
||||
screen_command = [options.screen_mcp_python,
|
||||
str(ROOT/'tests/sdbg/run_mcp_screen_probe.py'),
|
||||
'--socket', socket_path]
|
||||
if options.screen_proof:
|
||||
screen_command += ['--proof', options.screen_proof]
|
||||
screen_probe = subprocess.run(screen_command, capture_output=True,
|
||||
text=True, timeout=20, check=False)
|
||||
if screen_probe.returncode:
|
||||
raise RuntimeError('MCP screen probe: '+screen_probe.stderr[-3000:])
|
||||
print(screen_probe.stdout.strip(), flush=True)
|
||||
if clear_point:
|
||||
snapshot = rpc_call(socket_path, 'snapshot', timeout=5)
|
||||
snapshot = session_rpc(socket_path, 'snapshot', timeout=5)
|
||||
if snapshot['state'] != 'running':
|
||||
raise RuntimeError('CPU не выполняется перед очисткой точки')
|
||||
try:
|
||||
rpc_call(socket_path, 'read_memory', {'address': 0, 'length': 1})
|
||||
session_rpc(socket_path, 'read_memory', {'address': 0, 'length': 1})
|
||||
raise RuntimeError('Чтение памяти разрешено при running CPU')
|
||||
except SessionError as error:
|
||||
if 'остановленного CPU' not in str(error):
|
||||
raise
|
||||
rpc_call(socket_path, 'clear_breakpoint', {
|
||||
session_rpc(socket_path, 'clear_breakpoint', {
|
||||
'id': clear_point['id'], 'owner': 'probe:running-clear'})
|
||||
if '--emulated-key' in sys.argv:
|
||||
first = rpc_call(socket_path, 'snapshot', timeout=5)
|
||||
first = session_rpc(socket_path, 'snapshot', timeout=5)
|
||||
time.sleep(1)
|
||||
second = rpc_call(socket_path, 'snapshot', timeout=5)
|
||||
second = session_rpc(socket_path, 'snapshot', timeout=5)
|
||||
if second['paused'] or second['time'] <= first['time']:
|
||||
raise RuntimeError('MAME не обновляет input frames: '+
|
||||
str((first, second)))
|
||||
tag, mask, _ = resolve('x')
|
||||
rpc_call(socket_path, 'input_key',
|
||||
session_rpc(socket_path, 'input_key',
|
||||
{'tag': tag, 'mask': mask, 'down': True}, timeout=5)
|
||||
time.sleep(.15)
|
||||
rpc_call(socket_path, 'input_key',
|
||||
session_rpc(socket_path, 'input_key',
|
||||
{'tag': tag, 'mask': mask, 'down': False}, timeout=5)
|
||||
key_stop, buffer = wait_event(process, buffer, 'stopped', 10)
|
||||
if not key_stop:
|
||||
@@ -324,7 +348,7 @@ def main():
|
||||
after = after_response['body']['stackFrames'][0]
|
||||
if after['line'] != 63:
|
||||
raise RuntimeError('После клавиши ожидалась строка 63: '+str(after))
|
||||
ascii_code = rpc_call(socket_path, 'registers', timeout=5)
|
||||
ascii_code = session_rpc(socket_path, 'registers', timeout=5)
|
||||
ascii_code = ascii_code['registers']['DE'] & 0xff
|
||||
if ascii_code != ord('x'):
|
||||
raise RuntimeError(f'WAITKEY вернул {ascii_code:#x}, ожидался x (0x78)')
|
||||
@@ -340,7 +364,7 @@ def main():
|
||||
return 0
|
||||
if '--manual-key' in sys.argv:
|
||||
socket_path = launched['body']['socket']
|
||||
keyboard_state = rpc_call(socket_path, 'snapshot', timeout=5)['keyboards']
|
||||
keyboard_state = session_rpc(socket_path, 'snapshot', timeout=5)['keyboards']
|
||||
if keyboard_state.get(':kbd:ms_naturl') is not True:
|
||||
raise RuntimeError('Физическая PC-клавиатура MAME выключена: '+
|
||||
str(keyboard_state))
|
||||
@@ -357,7 +381,7 @@ def main():
|
||||
after = after_response['body']['stackFrames'][0]
|
||||
if after['line'] != 63:
|
||||
raise RuntimeError('После клавиши ожидалась строка 63: '+str(after))
|
||||
ascii_code = rpc_call(socket_path, 'registers', timeout=5)
|
||||
ascii_code = session_rpc(socket_path, 'registers', timeout=5)
|
||||
ascii_code = ascii_code['registers']['DE'] & 0xff
|
||||
if ascii_code != ord('x'):
|
||||
raise RuntimeError(f'WAITKEY вернул {ascii_code:#x}, ожидался x (0x78)')
|
||||
|
||||
Reference in New Issue
Block a user