#!/usr/bin/env python3 """ Отчёт по раскладке памяти образа + проверка лимитов, по .map-файлу sdldz80. Печатается для ЛЮБОЙ модели памяти (не только при --bank): - _CODE — основной код (W1 0x4100 или W2 0x8100), размер, конец; - данные — _DATA/_BSS/_INITIALIZED/… цепочкой за кодом, окно, конец; - статика/куча/стек — где кончается статика и сколько свободно под кучу (до ___sdcc_heap_end) и стек; - _W3CODE — резидентный код окна W3 (--w3), если есть; - _BANKn — банки (big/huge), каждый ≤ 16 КБ (размер окна W3/W1). Раскладка режимов — docs/memory-management.md §4. Линкер сам лимиты не проверяет — ловим здесь пост-линк. Usage: check_banks.py [--mode MODE] [--heap-top 0xNNNN] [--stack 0xNNNN] Выходит с ненулевым кодом, если банк не влез в окно или статика заехала за потолок кучи/окна. """ import argparse import re import sys BANK_LIMIT = 16 * 1024 # окно W3/W1 под банк W_CEILING = 0xC000 # верх W2 (дальше — только W3/банки) # Области .map, составляющие HOME (код + данные), в порядке цепочки линкера. # Всё, что не банк и не резидент W3 и лежит < 0xC000. _BANK_RE = re.compile(r"_BANK\d+$") def win_label(addr): """Окно по адресу (0-based физического образа игнорируем — берём Z80-view).""" a = addr & 0xFFFF if 0x4000 <= a < 0x8000: return "W1" if 0x8000 <= a < 0xC000: return "W2" if 0xC000 <= a <= 0xFFFF: return "W3" return "??" def parse_map(path): """{area_name: (addr, size)} для строк вида `_CODE 00004100 00000313 = ...`.""" line_re = re.compile(r"^\s*(_\w+)\s+([0-9A-Fa-f]{8})\s+([0-9A-Fa-f]{8})\s*=") areas = {} with open(path) as f: for ln in f: m = line_re.match(ln) if m: areas[m.group(1)] = (int(m.group(2), 16), int(m.group(3), 16)) return areas MODE_DESC = { "tiny": "CODE+DATA в W2 (0x8100)", "small": "CODE в W1 (0x4100), DATA→W2", "big": "CODE+DATA в W2 (0x8100), банки в W1", "huge": "CODE в W1 (0x4100), DATA→W2, банки в W3", "manual": "ручная раскладка", } def main(): ap = argparse.ArgumentParser() ap.add_argument("mapfile") ap.add_argument("--mode", default="") ap.add_argument("--heap-top", default="0xBB00") ap.add_argument("--stack", default="0xBFFE") args = ap.parse_args() areas = parse_map(args.mapfile) heap_top = int(args.heap_top, 0) stack_top = int(args.stack, 0) fails = [] desc = MODE_DESC.get(args.mode, "") hdr = f"memory: {args.mode}" if args.mode else "memory:" if desc: hdr += f" — {desc}" print(hdr) # --- HOME: код + данные (всё < 0xC000, кроме банков) -------------- home = {n: v for n, v in areas.items() if not _BANK_RE.match(n) and n != "_W3CODE" and (v[0] & 0xFFFF) < W_CEILING} if "_CODE" in areas: caddr, csize = areas["_CODE"] cend = caddr + csize print(f" _CODE @ 0x{caddr & 0xFFFF:04X} size {csize:>6} " f"({win_label(caddr)}) → 0x{cend & 0xFFFF:04X}") # данные = все HOME-области, кроме _CODE (цепляются за кодом) data = {n: v for n, v in home.items() if n != "_CODE"} if data: dstart = min(v[0] for v in data.values()) dend = max(v[0] + v[1] for v in data.values()) print(f" данные @ 0x{dstart & 0xFFFF:04X} size {dend - dstart:>6} " f"({win_label(dstart)}) → 0x{dend & 0xFFFF:04X}" f" [_DATA/_BSS/_INITIALIZED/…]") else: dend = cend # статика/куча/стек static_end = max(v[0] + v[1] for v in home.values()) heap_free = heap_top - static_end stack_room = stack_top + 1 - heap_top print(f" статика до 0x{static_end & 0xFFFF:04X} — куча " f"0x{static_end & 0xFFFF:04X}..0x{heap_top & 0xFFFF:04X} ({heap_free} Б), " f"стек 0x{heap_top & 0xFFFF:04X}..0x{stack_top & 0xFFFF:04X} ({stack_room} Б)") if static_end > heap_top: print(f" ОШИБКА: статика заехала за heap_top 0x{heap_top & 0xFFFF:04X} " f"(нет места под кучу/стек)") fails.append(("_HOME", static_end)) elif static_end > W_CEILING: print(f" ОШИБКА: статика вышла за 0x{W_CEILING:04X}") fails.append(("_HOME", static_end)) # --- Резидентный код окна W3 (--w3) ------------------------------- if "_W3CODE" in areas: waddr, wsize = areas["_W3CODE"] wend = waddr + wsize wfree = 0x10000 - wend marker = "OK" if wend > 0x10000: marker = "OVERFLOW" fails.append(("_W3CODE", wsize)) print(f" _W3CODE @ 0x{waddr & 0xFFFF:04X} size {wsize:>6} " f"(резидент W3) → 0x{wend & 0xFFFF:04X}, {wfree} Б свободно до 0x10000 {marker}") # --- Банки (big/huge) --------------------------------------------- bank_names = sorted(n for n in areas if _BANK_RE.match(n)) for name in bank_names: addr, size = areas[name] pct = size * 100.0 / BANK_LIMIT marker = "OK" if size > BANK_LIMIT: marker = "OVERFLOW" fails.append((name, size)) # виртуальный адрес: старший байт = номер банка, младшие 16 — окно free = BANK_LIMIT - size print(f" {name:<12} @ 0x{addr:08X} size {size:>6} / {BANK_LIMIT} " f"({pct:4.1f}%) → {free} Б свободно {marker}") if fails: print() for name, size in fails: if name in ("_HOME", "_W3CODE"): print(f" {name}: раскладка не влезла (конец 0x{size & 0xFFFF:04X})") else: print(f" {name} слишком большой: {size} Б (лимит {BANK_LIMIT})") sys.exit(1) if __name__ == "__main__": main()