NationalSecurityAgency/ghidra · error · KeyError

Inferiors[{inf.num}].Threads[{tnum}] does not exist

Error message

Inferiors[{inf.num}].Threads[{tnum}] does not exist

What it means

Raised by find_thread_by_num when iterating gdb.Inferior.threads() finds no thread whose .num equals the requested tnum. The GDB agent treats thread numbers as lookup keys into the inferior; if the thread has exited, been renumbered, or never existed, the loop completes empty. It surfaces as a KeyError because the indexed entity (Inferiors[i].Threads[t]) is absent — a missing-key condition, not a wrong-type condition.

Source

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

    return find_inf_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")


def find_inf_by_mod_obj(object: TraceObject) -> gdb.Inferior:
    return find_inf_by_pattern(object, MODULE_PATTERN, "a Module")


def find_module_name_by_mod_obj(object: TraceObject) -> str:
    mat = MODULE_PATTERN.fullmatch(object.path)
    if mat is None:
        raise TypeError(f"{object} is not a Module")
    return mat['modname']


def find_thread_by_num(inf: gdb.Inferior, tnum: int) -> gdb.InferiorThread:
    for t in inf.threads():
        if t.num == tnum:
            return t
    raise KeyError(f"Inferiors[{inf.num}].Threads[{tnum}] does not exist")


def find_thread_by_pattern(pattern: re.Pattern, object: TraceObject,
                           err_msg: str) -> gdb.InferiorThread:
    mat = pattern.fullmatch(object.path)
    if mat is None:
        raise TypeError(f"{object} is not {err_msg}")
    infnum = int(mat['infnum'])
    tnum = int(mat['tnum'])
    inf = find_inf_by_num(infnum)
    return find_thread_by_num(inf, tnum)


def find_thread_by_obj(object: TraceObject) -> gdb.InferiorThread:
    return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")


def find_thread_by_stack_obj(object: TraceObject) -> gdb.InferiorThread:

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Re-derive the thread list (inf.threads()) and confirm tnum is present before calling find_thread_by_num.
  2. In the caller, catch KeyError and treat it as 'thread gone' — refresh the Threads subtree of the trace and return a not-present status instead of propagating.
  3. Validate the parsed tnum against the current inferior's threads in find_thread_by_pattern before delegating.
  4. Ensure trace invalidation hooks fire on thread-exit events so stale [tnum] objects are removed before method dispatch.

Example fix

// before
t = find_thread_by_num(inf, tnum)
// after
nums = {t.num for t in inf.threads()}
if tnum not in nums:
    # thread exited; refresh trace Threads subtree
    return None
t = find_thread_by_num(inf, tnum)
Defensive patterns

Strategy: validation

Validate before calling

tnums = {t.num for t in inf.threads()}
if tnum not in tnums:
    # thread gone; refresh trace Threads subtree
    return None
t = find_thread_by_num(inf, tnum)

Type guard

def thread_exists(inf: gdb.Inferior, tnum: int) -> bool:
    return any(t.num == tnum for t in inf.threads())

Try / catch

try:
    t = find_thread_by_num(inf, tnum)
except KeyError:
    # thread exited between event and dispatch; invalidate trace node
    invalidate_thread_obj(inf.num, tnum)
    return None

Prevention

When it happens

Trigger: Calling find_thread_by_num(inf, tnum) after the thread exited (e.g. on stop/cont events where GDB pruned the thread), with a tnum parsed from a stale TraceObject path, or with a tnum that exceeds the current thread range. Reached indirectly via find_thread_by_obj/find_thread_by_pattern which extract tnum from the object path and delegate here.

Common situations: Stale trace objects referencing threads that have since exited (common in long debug sessions, fork/exec, or GDB's scheduler-locking scenarios); race between a 'thread exited' event and a method handler that still holds the old path; mismatched inferior after a re-run where thread numbering restarted.

Related errors


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