Проверить штатный выход MAME и загрузку state в DAP
This commit is contained in:
@@ -23,7 +23,7 @@ os.environ['SPRINTER_PYTHON'] = sys.executable
|
||||
sys.path.insert(0,str(ROOT/'toolchain'))
|
||||
from sdbg.model import DebugMap
|
||||
from sdbg.image import read_ihx
|
||||
from sdbg.session import DebugSession
|
||||
from sdbg.session import DebugSession, SessionError
|
||||
from mame_interactive import build_events
|
||||
from make_disk import create_floppy_image
|
||||
from mame_profile import MameProfile, write_keyboard_config
|
||||
@@ -42,6 +42,12 @@ def wait_dap_stop(engine, timeout=15):
|
||||
|
||||
def main():
|
||||
banked_mode = '--banked' in sys.argv
|
||||
if '--lifecycle-exit' in sys.argv and '--lifecycle-load' in sys.argv:
|
||||
raise ValueError('Выберите один lifecycle-сценарий')
|
||||
lifecycle = 'exit' if '--lifecycle-exit' in sys.argv else (
|
||||
'load' if '--lifecycle-load' in sys.argv else None)
|
||||
if lifecycle and not {'--banked', '--bridge', '--server'} <= set(sys.argv):
|
||||
raise ValueError('Lifecycle-пробник требует --banked --bridge --server')
|
||||
work=ROOT/'build/sdbg-live'
|
||||
work.mkdir(parents=True,exist_ok=True)
|
||||
source=work/'probe.c'
|
||||
@@ -161,6 +167,8 @@ void worker(void) __banked {
|
||||
ev=',\n'.join('{'+f'{t},"{tag}",{mask},{value}'+'}' for t,tag,mask,value in events)
|
||||
report=work/'result.jsonl'
|
||||
report.write_text('')
|
||||
control=work/('control-'+uuid.uuid4().hex)
|
||||
state_file=work/('state-'+uuid.uuid4().hex+'.sta')
|
||||
lua=work/'probe.lua'
|
||||
lua.write_text('''if _G.sdbg_probe_loaded then return end
|
||||
_G.sdbg_probe_loaded=true
|
||||
@@ -180,14 +188,39 @@ local bp=nil
|
||||
local false_hits=0
|
||||
local launch_reported=false
|
||||
local active=true
|
||||
local state_loaded=false
|
||||
local state_load_reported=false
|
||||
_G.sdbg_probe_reset_subscription=emu.add_machine_reset_notifier(function()
|
||||
-- Первый notifier относится к начальному machine start. После arm это
|
||||
-- уже настоящий reset сессии, и старый debugger object трогать нельзя.
|
||||
if phase~="booting" then active=false end
|
||||
end)
|
||||
_G.sdbg_probe_load_subscription=emu.add_machine_post_load_notifier(function()
|
||||
state_loaded=true
|
||||
end)
|
||||
debug.execution_state="run"
|
||||
emu.register_periodic(function()
|
||||
if not active then return end
|
||||
if state_loaded and not state_load_reported then
|
||||
local count=0
|
||||
for _ in pairs(cpu.debug:bplist()) do count=count+1 end
|
||||
emit('{"event":"state_loaded","breakpoints":' .. count .. '}')
|
||||
state_load_reported=true
|
||||
end
|
||||
if phase=="bridge_wait" then
|
||||
local control=io.open(CONTROL,"rb")
|
||||
if control then
|
||||
local action=control:read("*a");control:close();os.remove(CONTROL)
|
||||
emit('{"event":"control","action":"' .. action .. '"}')
|
||||
if action=="exit" then machine:exit()
|
||||
-- save/load не входят в scheduled_event_pending(): остановленный
|
||||
-- debugger надо отпустить, иначе операция не дойдёт до timeslice.
|
||||
elseif action=="save" then machine:save(STATE);debug.execution_state="run"
|
||||
elseif action=="load" then machine:load(STATE);debug.execution_state="run"
|
||||
else error("Unknown lifecycle action: " .. action) end
|
||||
end
|
||||
return
|
||||
end
|
||||
ticks=ticks+1
|
||||
-- До окна запуска приложения DSS работает вообще без наших точек.
|
||||
-- Первая клавиша launch sequence придёт через полсекунды после arm.
|
||||
@@ -234,11 +267,13 @@ emu.register_periodic(function()
|
||||
if now()>35 then emit('{"event":"timeout"}');report:close();machine:exit() end
|
||||
end)
|
||||
'''.replace('REPORT',json.dumps(str(report))).replace('EVENTS',ev)
|
||||
.replace('SIGNATURE',','.join(map(str,signature))).replace('MAIN',str(main_address)))
|
||||
.replace('SIGNATURE',','.join(map(str,signature))).replace('MAIN',str(main_address))
|
||||
.replace('CONTROL',json.dumps(str(control))).replace('STATE',json.dumps(str(state_file))))
|
||||
command=[str(profile.binary),'sprinter','-noreadconfig',
|
||||
'-rompath',str(profile.rompath),'-bios',profile.bios,'-kbd','ms_naturl,bios=sp2k',
|
||||
'-video','none','-sound','none','-nothrottle','-skip_gameinfo',
|
||||
'-beta:wd179x:0','35hd','-flop1',str(work/'probe.img'),
|
||||
'-beta:wd179x:0','35hd','-beta:wd179x:1','35hd',
|
||||
'-flop1',str(work/'probe.img'),
|
||||
'-flop2',str(profile.dss_image),
|
||||
'-hard1',str(system),'-debug','-debugger','none',
|
||||
'-autoboot_delay','0','-autoboot_script',str(lua)]
|
||||
@@ -296,6 +331,56 @@ end)
|
||||
dap=DapEngine()
|
||||
attached,_=dap.handle('attach',{'socket':str(socket_path)})
|
||||
assert attached['buildId']==model.manifest['build_id']
|
||||
if lifecycle:
|
||||
def send_control(action):
|
||||
pending=control.with_suffix('.tmp')
|
||||
pending.write_text(action)
|
||||
pending.replace(control)
|
||||
if lifecycle=='load':
|
||||
send_control('save')
|
||||
deadline=time.monotonic()+20
|
||||
while (not state_file.is_file() or state_file.stat().st_size==0):
|
||||
if process.poll() is not None or time.monotonic()>deadline:
|
||||
raise RuntimeError('MAME не сохранил state; лог: '+str(work/'mame.log'))
|
||||
time.sleep(.05)
|
||||
time.sleep(.25)
|
||||
dap.handle('pause',{})
|
||||
points=dap.rpc('set_function_breakpoints',
|
||||
{'names':['worker']})['breakpoints']
|
||||
assert points[0]['verified'] and points[0]['backend_ids'],points
|
||||
send_control(lifecycle)
|
||||
deadline=time.monotonic()+20
|
||||
dap_events=[]
|
||||
while time.monotonic()<deadline:
|
||||
found,closed=dap.poll_events(.5)
|
||||
dap_events.extend(found)
|
||||
if closed: break
|
||||
assert closed and any(name=='terminated' for name,_ in dap_events),dap_events
|
||||
try:
|
||||
dap.rpc('status')
|
||||
raise AssertionError('Старая DAP-сессия осталась доступной')
|
||||
except SessionError as error:
|
||||
assert 'закрыта' in str(error) or 'инвалидирована' in str(error),error
|
||||
if lifecycle=='exit':
|
||||
assert process.wait(timeout=5)==0
|
||||
else:
|
||||
deadline=time.monotonic()+5
|
||||
while '"event":"state_loaded"' not in report.read_text():
|
||||
if process.poll() is not None or time.monotonic()>deadline:
|
||||
raise RuntimeError('Нет post-load события; лог: '+str(work/'mame.log'))
|
||||
time.sleep(.05)
|
||||
loaded=next(json.loads(line) for line in report.read_text().splitlines()
|
||||
if '"event":"state_loaded"' in line)
|
||||
assert loaded['breakpoints']==0,loaded
|
||||
with report.open('a') as file:
|
||||
file.write(json.dumps({'event':'lifecycle_verified',
|
||||
'action':lifecycle,'terminated':True,
|
||||
'backend_breakpoints':points[0]['backend_ids']})+'\n')
|
||||
print(json.dumps({'event':'lifecycle_verified',
|
||||
'action':lifecycle,'dap_terminated':True,
|
||||
'state_breakpoints':0 if lifecycle=='load' else None}))
|
||||
state_file.unlink(missing_ok=True)
|
||||
return 0
|
||||
started=time.monotonic()
|
||||
points,_=dap.handle('setFunctionBreakpoints',
|
||||
{'breakpoints':[{'name':'worker'}]})
|
||||
|
||||
Reference in New Issue
Block a user