Проверить штатный выход MAME и загрузку state в DAP
This commit is contained in:
@@ -214,6 +214,8 @@ pyenv exec python tests/sdbg/run_mame_probe.py
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --bridge
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --bridge --banked
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --bridge --banked --server
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --banked --bridge --server --lifecycle-exit
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --banked --bridge --server --lifecycle-load
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher --native-debugger
|
||||
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher --source-step
|
||||
@@ -273,8 +275,18 @@ VS Code запустил сборку SprPoP и остановился в `src/s
|
||||
с `sdbg` и опциональным `osx` debugger provider.
|
||||
Отдельная проба `SIGTERM` остановленному MAME не дала DAP `terminated` за
|
||||
15 секунд; причина (обработка сигнала самим MAME или путь закрытия сессии)
|
||||
ещё не установлена. Штатное закрытие окна и state load не прошли отдельный
|
||||
живой тест.
|
||||
ещё не установлена. Это отдельный OS-signal путь, не равный закрытию окна.
|
||||
|
||||
Живой lifecycle-пробник на банковой программе теперь прошёл с `sdbg` и
|
||||
опциональным `osx` provider. На остановке в `main` Lua вызвал
|
||||
`machine:exit()`: MAME вышел с кодом 0, DAP получил `terminated`, старый
|
||||
session server отверг запрос. Закрытие SDL3-окна в MAME вызывает тот же
|
||||
`schedule_exit()`; физический клик по кнопке окна не автоматизировался.
|
||||
При save/load state пробник после сохранения поставил банковую точку,
|
||||
загрузил сохранённое состояние и получил `terminated`; post-load проверка
|
||||
`bplist()` показала ноль оставшихся точек, старый session server отверг
|
||||
запрос. Для save/load пробник продолжает CPU после планирования операции:
|
||||
эти команды MAME сами не выводят CPU из debugger stop-loop.
|
||||
|
||||
Это репро доступности механизма, а не полный benchmark или проверка всех
|
||||
runtime/mapping/lifecycle. Отдельная публикация debug-библиотек требует
|
||||
@@ -332,7 +344,7 @@ Crash reports в 19:22/19:23 относились к неподдержанно
|
||||
строит проверенное условие через I/O port. После штатных прогонов в 20:43 и
|
||||
позже новых `mame.arm-*.ips` не появилось.
|
||||
|
||||
Далее по плану: проверить graceful exit/state load живым репро, сделать
|
||||
Далее по плану: исследовать отдельно OS-signal путь `SIGTERM`, сделать
|
||||
MCP-адаптер, безопасный restart и расширенные выражения. VSIX уже упакован;
|
||||
для IDE ещё нужны Run-команда, выбор профиля
|
||||
сборки/данных и расширенная диагностика assembler/linker.
|
||||
|
||||
@@ -680,8 +680,13 @@ exit/reset/reload инвалидируют состояние; потеря кл
|
||||
|
||||
Текущая реализация опрашивает backend и при остановленном CPU: потеря MAME
|
||||
переводит DAP в `terminated` (проверено живым пробником с внезапным выходом).
|
||||
Reset/state load обработаны notifier плагина, инвалидация проверена host-тестом;
|
||||
штатное закрытие MAME и загрузка state требуют отдельных живых проверок.
|
||||
Проверенные живые lifecycle-пробники вызывают штатный `schedule_exit()` через
|
||||
Lua `machine:exit()` и реальный save/load state при остановке в `main`:
|
||||
оба завершают DAP, post-load удаляет старые точки. SDL3-событие закрытия
|
||||
окна использует тот же `schedule_exit()`; прямой UI-клик не автоматизировался.
|
||||
Reset по-прежнему требует безопасного отдельного репро. `SIGTERM`
|
||||
остановленному процессу MAME пока не дал DAP `terminated` за 15 секунд;
|
||||
это отдельный OS-signal путь, не проверка штатного выхода.
|
||||
|
||||
### Этап 3 — полезная CLI/MCP-отладка и логи
|
||||
|
||||
|
||||
@@ -101,9 +101,12 @@ service-точка `main` и вводится `a:\\HELLO.EXE`. После сов
|
||||
|
||||
При reset/state load или потере MAME остановленная сессия становится
|
||||
недействительной и DAP завершает её; для новой отладки запускайте F5 заново.
|
||||
Проверен сценарий внезапного завершения собственного MAME во время остановки
|
||||
в `main`. Отдельный живой тест штатного закрытия окна и загрузки state ещё
|
||||
предстоит выполнить.
|
||||
Проверены внезапная потеря собственного MAME, штатный `machine:exit()` и
|
||||
реальная загрузка state при остановке в `main`: каждый раз DAP получает
|
||||
`terminated`, после load старые точки удалены. Закрытие SDL3-окна MAME
|
||||
вызывает тот же `schedule_exit()`, что `machine:exit()`; прямой UI-клик
|
||||
в пробнике не выполнялся. Эти проверки прошли с `sdbg` и опциональным
|
||||
`osx` debugger provider.
|
||||
|
||||
`dssTimeout` задаёт предельное время ожидания prompt (30 эмулируемых секунд).
|
||||
`launchAt` можно задать как необязательную нижнюю границу времени запуска;
|
||||
|
||||
@@ -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