#!/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))