NationalSecurityAgency/ghidra · error · KeyError

Processes[{proc.GetProcessID()}].Threads[{tnum}] does not ex

Error message

Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist

What it means

Raised by find_thread_by_num when iterating proc.threads finds no thread whose GetThreadID() matches the requested tnum. The Ghidra LLDB agent resolves trace object paths of the form Processes[<procnum>].Threads[<tnum>] to live LLDB SBThread objects; this error fires when the thread ID extracted from the path does not exist in the current process thread list.

Source

Thrown at Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py:117


def find_proc_by_mem_obj(object: TraceObject) -> lldb.SBProcess:
    return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")


def find_proc_by_modules_obj(object: TraceObject) -> lldb.SBProcess:
    return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")


def find_proc_by_frame(object: TraceObject) -> lldb.SBProcess:
    return find_proc_by_pattern(object, FRAME_PATTERN, "a StaclFrame")


def find_thread_by_num(proc: lldb.SBThread, tnum: int) -> lldb.SBThread:
    for t in proc.threads:
        if t.GetThreadID() == tnum:
            return t
    raise KeyError(
        f"Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist")


def find_thread_by_pattern(pattern: re.Pattern, object: TraceObject,
                           err_msg: str) -> lldb.SBThread:
    mat = pattern.fullmatch(object.path)
    if mat is None:
        raise TypeError(f"{object} is not {err_msg}")
    procnum = int(mat['procnum'])
    tnum = int(mat['tnum'])
    proc = find_proc_by_num(procnum)
    return find_thread_by_num(proc, tnum)


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

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Re-snapshot the trace so object paths reflect the live thread set (e.g. re-run 'ghidra trace tx-start' followed by the appropriate put-* commands).
  2. Verify the thread still exists by checking proc.threads for the matching GetThreadID() before invoking the trace method.
  3. Catch KeyError in the method handler and return a trace-rpc error so Ghidra's UI can re-sync its object tree.

Example fix

# before: stale tnum passed blindly
proc = find_proc_by_num(procnum)
thread = find_thread_by_num(proc, tnum)

# after: verify existence first
tids = [t.GetThreadID() for t in proc.threads]
if tnum not in tids:
    raise KeyError(f"Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist")
thread = next(t for t in proc.threads if t.GetThreadID() == tnum)
Defensive patterns

Strategy: try-catch

Validate before calling

proc = find_proc_by_num(procnum)
tids = {t.GetThreadID() for t in proc.threads}
if tnum not in tids:
    # do not call find_thread_by_num; refresh trace or report error
    pass

Try / catch

try:
    thread = find_thread_by_num(proc, tnum)
except KeyError:
    # thread no longer exists; refresh trace and retry or report
    refresh_trace_threads(proc)
    thread = find_thread_by_num(proc, tnum)

Prevention

When it happens

Trigger: A Ghidra trace method (e.g. activate, read_reg, step) calls find_thread_by_pattern, which extracts tnum from the Threads[<tnum>] segment of the TraceObject path and passes it to find_thread_by_num. The LLDB SBProcess.threads collection contains no SBThread whose GetThreadID() equals that integer.

Common situations: The target thread has exited or been terminated between trace snapshot and operation. A multi-threaded target where threads are dynamically created and destroyed left stale IDs in the trace. The process state changed (e.g. after a continue or kill) without refreshing the trace.

Related errors


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