Откат bank-data для pop_trob: указатель на его статику уходил наружу

Симптом: через несколько комнат живого прохода перезагружался DSS.

pop_trob_modif() возвращает указатель на room_modif[24][30], а зовут её из
банков 2, 3, 7 и резидента.  После переноса массив лежит по 0xC000+ в
странице банка 6, но разыменовывает указатель ЧУЖОЙ код — когда замаплена
его собственная страница.  Значит чтение и запись идут поверх кода соседнего
банка.

Анализ Def/Ref такое не ловит: снаружи нет ссылки на символ, есть ссылка на
функцию, которая отдаёт его адрес.  Условий для кандидата два, и второе
проверяется только чтением кода — ни один указатель на статику не должен
уходить наружу.

pop_room оба условия проходит (_mobs не читает никто; bake_copy статическая;
atlas_load(&pop_env[i]) берёт адрес глобала из резидентного pop_tile.c).
Остаётся −1620 Б: данные в W2 6774 -> 5157, свободно 129 -> 1746 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 21:21:44 +03:00
parent 354582662c
commit 90255737c2
2 changed files with 46 additions and 13 deletions
+31 -5
View File
@@ -42,19 +42,45 @@ re.match(r'A\s+(\S+)\s+size\s+([0-9A-Fa-f]+)\s+flags', line)
глобал, который читает соседний банк или резидент, обязан остаться
замапленным всегда, иначе чтение придёт из чужой страницы.
Включено для `pop_room.c` и `pop_trob.c` — единственных модулей, у которых
НИ ОДИН символ не Ref снаружи. Результат:
Включено для `pop_room.c`. Результат:
| | было | стало |
|---|---:|---:|
| данные в W2 | 6774 | **4228** |
| свободно до стека | 129 | **2675** |
| BANK6 | 4199 | 5128 (31 %) |
| данные в W2 | 6774 | **5157** |
| свободно до стека | 129 | **1746** |
| BANK7 | 12684 | 14301 (87 %) |
**Проверено в MAME**: уровень 1, комнаты 1 и 2 — фон, факелы, решётки,
проваливающиеся полы и переход между комнатами работают.
## ГЛАВНАЯ ЛОВУШКА: указатель на банковую статику
Первый заход включал ещё и `pop_trob.c` — по Def/Ref он выглядел ИДЕАЛЬНЫМ
кандидатом (экспортируемых данных нет вообще). Через несколько комнат
живого прохода **перезагрузился DSS**.
Причина: `pop_trob_modif()` ВОЗВРАЩАЕТ УКАЗАТЕЛЬ на `room_modif[24][30]`, а
зовут её из банков 2, 3, 7 и резидента. После переноса массив лежит по
0xC000+ в странице банка 6, но разыменовывает указатель чужой код — когда
замаплена ЕГО страница. То есть чтение и запись идут по тем же смещениям
поверх кода соседнего банка. Отсюда и уход системы.
**Анализ Def/Ref этого не видит**: снаружи нет ссылки на символ, есть
ссылка на функцию, которая отдаёт его адрес. Поэтому условий для
кандидата ДВА, и второе проверяется только чтением кода:
1. ни один чужой модуль не Ref его глобалы (по `.rel`);
2. **ни один указатель на его статику не уходит наружу** — ни возвратом, ни
через out-параметр, ни записью в общий глобал.
У `pop_room` второе выполнено: единственные `&`-выражения наружу —
`atlas_load(&pop_env[i])`, но `pop_env` определён в `pop_tile.c`, то есть в
резиденте; `bake_copy` статическая.
Внутри своей страницы указатель безопасен всегда: любой вызов резидентной
функции, которая перемапливает W3 (`atlas_load`, `pop_page_read`),
восстанавливает окно перед возвратом.
### Два условия, без которых это молча ломается
1. **Нулевая инициализация.** crt0 зануляет только резидентный `_DATA`, до
+15 -8
View File
@@ -35,14 +35,21 @@ MEMORY ?= huge # small-раскладка + банки кода в W
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
ALLOCS ?= 3000
# Писучие данные ЭТИХ банковых модулей живут в их же странице, а не в
# резиденте W1/W2 — это 2549 Б, которые иначе съедала бы база. Список
# короткий не по лени: у остальных банковых модулей часть глобалов читают
# соседние банки и резидент (hitp_*, pop_upside, pop_loose_modif, pop_cd),
# и такие данные ОБЯЗАНЫ остаться замапленными всегда. Проверять новичка в
# списке — по объектным файлам: символ должен быть Def только в своём .rel и
# нигде не Ref (см. ../docs/resident_budget.md).
BANK_DATA := --bank-data=pop_room.c --bank-data=pop_trob.c
# Писучие данные ЭТОГО банкового модуля живут в его же странице, а не в
# резиденте W1/W2 (1620 Б базы). Список из одного файла — не лень, а
# результат проверки; ДВА условия, и второе стоило перезагрузки DSS:
#
# 1) ни один чужой модуль не ссылается на его глобалы (проверяется по
# .rel: символ Def только в своём объекте и нигде не Ref);
# 2) НИ ОДИН УКАЗАТЕЛЬ на его статику не уходит наружу. Def/Ref этого не
# видит. Так провалился pop_trob: pop_trob_modif() возвращает указатель
# на room_modif[24][30], и его разыменовывают банки 2/3/7 и резидент —
# то есть пишут по 0xC000+ в СВОЮ страницу, поверх чужого кода.
#
# У pop_room оба условия выполнены: _mobs не читает никто, bake_copy
# статическая, а atlas_load(&pop_env[i]) берёт адрес глобала из pop_tile.c
# (резидент), не своего. Подробности — ../docs/resident_budget.md.
BANK_DATA := --bank-data=pop_room.c
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 4=pop_kdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c --bank 8=pop_kboot.c --bank 8=pop_guard_cold.c --bank 8=pop_shadow.c --bank 8=pop_sfx_cold.c $(BANK_DATA) $(PROF_FLAGS)
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по