toolchain: предупреждать, когда ISR-стаб W0-страниц остался в W1
Правка пользователя, разобранная по диффу и закоммиченная как есть. Программа может временно маппить свою EMM-страницу в окно 0 (атласы спрайтов, gfx_w0_map). Пока страница там, любое прерывание уходит на #0038 ЭТОЙ страницы, поэтому gfx_w0_page_prepare прошивает туда переход на _gfx_w0_isr. Сам стаб живёт в _CODE, а в режимах small и huge _CODE начинается с 0x4100 — то есть попадает в W1, окно, которое DSS перемаплет на время СВОИХ вызовов. Прерывание в этот момент уходит по адресу, которого сейчас нет: W1 читается как #FF. Ловится тяжело: собирается молча, проявляется недетерминированно — зависанием примерно на каждом третьем холодном старте. Поймано вживую 2026-09-01. check_w0_isr.py берёт адрес __gfx_w0_isr из .map: символа нет — W0-путь не слинкован, молчим; адрес >= 0x8000 — он в W2, всё хорошо; иначе WARNING. Сборку не валит намеренно: свои страницы в W0 кладут не все. Вызов добавлен в app.mk рядом с check_bank_calls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
This commit is contained in:
@@ -92,11 +92,19 @@ RUNTIME_DEPS := $(wildcard $(PROJ_ROOT)/runtime/*.s)
|
|||||||
# bankN_*.asm; обычным небанковым программам ничего не стоит.
|
# bankN_*.asm; обычным небанковым программам ничего не стоит.
|
||||||
BANK_CHECK := $(PROJ_ROOT)/toolchain/check_bank_calls.py
|
BANK_CHECK := $(PROJ_ROOT)/toolchain/check_bank_calls.py
|
||||||
|
|
||||||
|
# ПРОВЕРКА ISR-СТАБА W0-СТРАНИЦ — там же, по свежей карте. Ловит стаб
|
||||||
|
# _gfx_w0_isr, оставшийся в W1: программа, кладущая свои страницы в W0
|
||||||
|
# (атласы спрайтов, gfx_w0_map), получает недетерминированные зависания,
|
||||||
|
# когда прерывание приходит во время вызова DSS и W1 перемаплен. Только
|
||||||
|
# предупреждение: страницы в W0 кладут не все. Разбор — в шапке скрипта.
|
||||||
|
W0ISR_CHECK := $(PROJ_ROOT)/toolchain/check_w0_isr.py
|
||||||
|
|
||||||
$(EXE): $(SOURCES) $(MKEXE) $(LIB) $(RUNTIME_DEPS)
|
$(EXE): $(SOURCES) $(MKEXE) $(LIB) $(RUNTIME_DEPS)
|
||||||
$(if $(strip $(BUILD_DIR)),@mkdir -p $(dir $@))
|
$(if $(strip $(BUILD_DIR)),@mkdir -p $(dir $@))
|
||||||
$(SPRINTER_CC) $(CC_FLAGS) -o $@ $(SOURCES)
|
$(SPRINTER_CC) $(CC_FLAGS) -o $@ $(SOURCES)
|
||||||
@d=$(dir $@).sprinter-cc-$(EXAMPLE); \
|
@d=$(dir $@).sprinter-cc-$(EXAMPLE); \
|
||||||
if ls $$d/bank*_*.asm >/dev/null 2>&1; then python3 $(BANK_CHECK) $$d; fi
|
if ls $$d/bank*_*.asm >/dev/null 2>&1; then python3 $(BANK_CHECK) $$d; fi; \
|
||||||
|
if ls $$d/*.map >/dev/null 2>&1; then python3 $(W0ISR_CHECK) $$d; fi
|
||||||
|
|
||||||
$(MKEXE):
|
$(MKEXE):
|
||||||
$(MAKE) -C $(PROJ_ROOT)/toolchain/mkexe
|
$(MAKE) -C $(PROJ_ROOT)/toolchain/mkexe
|
||||||
|
|||||||
Executable
+83
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
check_w0_isr.py — предупредить, что ISR-стаб W0-страниц оказался в W1.
|
||||||
|
|
||||||
|
Зачем. Программа может временно маппить свою EMM-страницу в W0 (атласы
|
||||||
|
спрайтов в sprite_update, ресурсы через gfx_w0_map). Пока страница там,
|
||||||
|
ЛЮБОЕ прерывание уходит на #0038 ЭТОЙ страницы, поэтому gfx_w0_page_prepare()
|
||||||
|
прошивает туда `JP _gfx_w0_isr`, а стаб возвращает страницу ядра DSS, зовёт
|
||||||
|
честный обработчик и восстанавливает страницу обратно.
|
||||||
|
|
||||||
|
Мина. Сам стаб живёт в _CODE. В режимах small и huge _CODE начинается с
|
||||||
|
0x4100, то есть попадает в W1 — окно, которое DSS перемаппит на время СВОИХ
|
||||||
|
вызовов (docs/new/04-memory.md §4.2, §4.9). Если прерывание придёт, пока
|
||||||
|
наша страница в W0 И идёт вызов DSS, переход уйдёт по адресу, которого в этот
|
||||||
|
момент нет: W1 читается как #FF, процессор уползает в бесконечный пролог
|
||||||
|
системного прерывания. Само libbgi это документирует в common/_gfx_w0_isr.c
|
||||||
|
(«обязан быть в W2»).
|
||||||
|
|
||||||
|
Собирается это МОЛЧА и проявляется НЕДЕТЕРМИНИРОВАННО — зависанием примерно
|
||||||
|
на каждом третьем холодном старте, что легко списать на случайность. Поймано
|
||||||
|
вживую 2026-09-01 в порте Loom (huge): стаб был на 0x6769.
|
||||||
|
|
||||||
|
Как проверяем. По .map берём адрес __gfx_w0_isr. Если символа нет — W0-путь
|
||||||
|
в программу не слинкован, молча выходим. Если адрес >= 0x8000 — он в W2, всё
|
||||||
|
хорошо. Иначе печатаем WARNING: сборку не валим, потому что программа могла
|
||||||
|
и не класть свои страницы в W0.
|
||||||
|
|
||||||
|
Лечение (со стороны приложения): вернуть модули из банков в резидент или
|
||||||
|
разгрузить _DATA так, чтобы _CODE перевалил за 0x8000 и хвост libbgi уехал в
|
||||||
|
W2. Порядок линковки сам по себе не помогает: пока весь _CODE влезает в W1
|
||||||
|
(ёмкость 0x8000-0x4100 = 16128 Б), расстановка модулей ничего не меняет.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
W2_BASE = 0x8000
|
||||||
|
SYM = '__gfx_w0_isr'
|
||||||
|
MAP_RE = re.compile(r'^\s+([0-9A-Fa-f]{4,8})\s+(\S+)\s')
|
||||||
|
|
||||||
|
|
||||||
|
def find_symbol(map_path, name):
|
||||||
|
for line in map_path.read_text(errors='replace').splitlines():
|
||||||
|
m = MAP_RE.match(line)
|
||||||
|
if m and m.group(2) == name:
|
||||||
|
return int(m.group(1), 16)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) != 2:
|
||||||
|
print(__doc__.strip())
|
||||||
|
return 2
|
||||||
|
build = Path(argv[1])
|
||||||
|
maps = list(build.glob('*.map'))
|
||||||
|
if not maps:
|
||||||
|
print(f'check_w0_isr: нет .map в {build}', file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
addr = find_symbol(maps[0], SYM)
|
||||||
|
if addr is None:
|
||||||
|
return 0 # W0-путь не слинкован — проверять нечего
|
||||||
|
if addr >= W2_BASE:
|
||||||
|
print(f'check_w0_isr: {SYM} @ 0x{addr:04X} — в W2, ок')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f'check_w0_isr: ВНИМАНИЕ — {SYM} @ 0x{addr:04X} лежит в W1.',
|
||||||
|
file=sys.stderr)
|
||||||
|
print(' Если программа маппит свои страницы в W0 (атласы спрайтов, '
|
||||||
|
'gfx_w0_map,', file=sys.stderr)
|
||||||
|
print(' atlas_attach), прерывание во время вызова DSS уйдёт в '
|
||||||
|
'перемапленное W1 —', file=sys.stderr)
|
||||||
|
print(' недетерминированные зависания на старте. Стаб обязан быть в W2 '
|
||||||
|
f'(>= 0x{W2_BASE:04X}).', file=sys.stderr)
|
||||||
|
print(' Лечение: увести _CODE за 0x8000 (модули из банков в резидент / '
|
||||||
|
'разгрузить', file=sys.stderr)
|
||||||
|
print(' _DATA). Подробности — в шапке этого скрипта и '
|
||||||
|
'docs/new/04-memory.md §4.2.', file=sys.stderr)
|
||||||
|
return 0 # предупреждение, сборку не валим
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
Reference in New Issue
Block a user