NationalSecurityAgency/ghidra · error · KeyError

Breakpoints[{breaknum}] does not exist

Error message

Breakpoints[{breaknum}] does not exist

What it means

Raised by find_bpt_by_number when scanning gdb.breakpoints() finds no breakpoint whose .number equals breaknum. The GDB Python API offers no direct get-breakpoint-by-number, so the agent iterates the list; a deleted, conditional, or never-existent breakpoint number leaves the loop empty. KeyError signals Breakpoints[N] does not exist.

Source

Thrown at Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py:241

# Because there's no method to get a register by name....
def find_reg_by_name(f: gdb.Frame, name: str) -> Union[gdb.RegisterDescriptor,
                                                       util.RegisterDesc]:
    for reg in util.get_register_descs(f.architecture()):
        # TODO: gdb appears to be case sensitive, but until we encounter a
        # situation where case matters, we'll be insensitive
        if reg.name.lower() == name.lower():
            return reg
    raise KeyError(f"No such register: {name}")


# Oof. no gdb/Python method to get breakpoint by number
# I could keep my own cache in a dict, but why?
def find_bpt_by_number(breaknum: int) -> gdb.Breakpoint:
    # TODO: If len exceeds some threshold, use binary search?
    for b in gdb.breakpoints():
        if b.number == breaknum:
            return b
    raise KeyError(f"Breakpoints[{breaknum}] does not exist")


def find_bpt_by_pattern(pattern: re.Pattern, object: TraceObject,
                        err_msg: str) -> gdb.Breakpoint:
    mat = pattern.fullmatch(object.path)
    if mat is None:
        raise TypeError(f"{object} is not {err_msg}")
    breaknum = int(mat['breaknum'])
    return find_bpt_by_number(breaknum)


def find_bpt_by_obj(object: TraceObject) -> gdb.Breakpoint:
    return find_bpt_by_pattern(BREAKPOINT_PATTERN, object, "a BreakpointSpec")


def find_bptlocnum_by_pattern(pattern: re.Pattern, object: TraceObject,
                              err_msg: str) -> Tuple[int, int]:
    mat = pattern.fullmatch(object.path)

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Refresh gdb.breakpoints() and confirm breaknum is present before lookup.
  2. Catch KeyError in callers and invalidate the corresponding Breakpoints[N] trace object, returning 'not present'.
  3. Ensure breakpoint lifecycle hooks (delete/insert) update the trace so stale numbers cannot be queried.
  4. Validate the parsed breaknum is within the range of current breakpoint numbers before delegating.

Example fix

// before
b = find_bpt_by_number(breaknum)
// after
nums = {b.number for b in gdb.breakpoints()}
if breaknum not in nums:
    return None  # breakpoint gone; invalidate trace node
b = find_bpt_by_number(breaknum)
Defensive patterns

Strategy: validation

Validate before calling

nums = {b.number for b in gdb.breakpoints()}
if breaknum not in nums:
    return None  # breakpoint gone
b = find_bpt_by_number(breaknum)

Type guard

def breakpoint_exists(breaknum: int) -> bool:
    return any(b.number == breaknum for b in gdb.breakpoints())

Try / catch

try:
    b = find_bpt_by_number(breaknum)
except KeyError:
    invalidate_bpt_obj(breaknum)
    return None

Prevention

When it happens

Trigger: Calling find_bpt_by_number with a number parsed from a stale Breakpoints[N] trace object after the breakpoint was deleted or disabled-and-removed; a number from a different GDB session; a breakpoint whose location count changed. Reached via breakpoint method handlers and find_bpt_by_pattern/find_bpt_by_obj.

Common situations: Stale trace breakpoint objects referencing deleted breakpoints (very common after 'delete' commands); race between a breakpoint-removed event and a method handler still holding the old number; re-run of the inferior where breakpoint numbers reset.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/52e5fc7b3df9da4c. Report an issue: GitHub.