# zero_nobits.py - Zero-fill NOBITS/BSZ sections after `load` in GDB
#
# PURPOSE
# -------
# GDB's `load` command only writes bytes for ELF sections that carry file
# content (PROGBITS). It never touches SHT_NOBITS sections (what VisualDSP/
# CrossCore linkers emit as "BSZ" - Block Storage Zero - regions, the
# equivalent of .bss) since that's standard GDB behavior for every target,
# not a bug: normally the target's own C runtime startup code zeroes that
# memory before main() runs. When you `load` and jump straight into the
# application without running crt0/startup, any variable expected to start
# at zero can instead hold whatever was left in memory, causing intermittent,
# hard-to-diagnose failures.
#
# This script closes that gap without hardcoding any addresses: after each
# `load`, it asks GDB's own section introspection (`maint info sections`)
# for the currently loaded ELF's regions, finds every ALLOC section that
# has no LOAD/HAS_CONTENTS flag (the BFD-level signature of a NOBITS/BSZ
# section - i.e. exactly the ones `load` skipped), and zero-fills each one
# over the target's memory-write remote interface. Because it re-derives
# the region list from the binary on every run, it stays correct across
# relinks with no manual address bookkeeping.
#
# USAGE
# -----
# 1. Source this file from GDB (interactively or from .gdbinit):
#
#        source /path/to/zero_nobits.py
#
# 2. Run it manually whenever you want:
#
#        (gdb) zero-nobits            # zero-fill now
#        (gdb) zero-nobits dry-run    # list what would be zeroed, no writes
#
# 3. Recommended: wire it to run automatically after every `load`, by adding
#    to your .gdbinit (after the `source` line above):
#
#        define hookpost-load
#          zero-nobits
#        end
#
# Requires a GDB build with Python scripting support (`gdb --configuration`
# should list `--with-python`). No target-, vendor-, or architecture-specific
# assumptions - works on any GDB remote target where "maint info sections"
# reports ALLOC/LOAD flags for the loaded objfile.

import gdb
import re

_SECTION_RE = re.compile(
    r"0x([0-9a-fA-F]+)->0x([0-9a-fA-F]+)\s+at\s+0x[0-9a-fA-F]+:\s+(\S+)\s+(.*)"
)


def find_nobits_sections():
    """Return [(name, start, size), ...] for every ALLOC section GDB's
    load will NOT write data into (i.e. no LOAD/HAS_CONTENTS flag - the
    BFD-level signal for an SHT_NOBITS / VisualDSP 'BSZ' section)."""
    out = gdb.execute("maint info sections ALLOC", to_string=True)
    regions = []
    for line in out.splitlines():
        m = _SECTION_RE.search(line)
        if not m:
            continue
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        name = m.group(3)
        flags = m.group(4)
        if "LOAD" in flags.split():
            continue
        size = end - start
        if size > 0:
            regions.append((name, start, size))
    return regions


def zero_nobits_sections(dry_run=False):
    regions = find_nobits_sections()
    if not regions:
        print("zero-nobits: no NOBITS/BSZ sections found")
        return
    inferior = None if dry_run else gdb.selected_inferior()
    total = 0
    for name, start, size in regions:
        if not dry_run:
            inferior.write_memory(start, b"\x00" * size)
        print(f"zero-nobits: cleared {name:<20s} at 0x{start:08x}, {size} bytes"
              + ("  [dry-run]" if dry_run else ""))
        total += size
    print(f"zero-nobits: {len(regions)} section(s), {total} bytes total")


class ZeroNobits(gdb.Command):
    """Zero-fill every ALLOC section that `load` does not initialize
    (ELF NOBITS / VisualDSP BSZ regions). Usage: zero-nobits [dry-run]"""

    def __init__(self):
        super().__init__("zero-nobits", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        zero_nobits_sections(dry_run=(arg.strip() == "dry-run"))


ZeroNobits()
