{"record":{"id":"837e0ecfa5793b2c","repo":"NationalSecurityAgency/ghidra","slug":"inferiors-inf-num-threads-tnum-does-not-exis","errorCode":null,"errorMessage":"Inferiors[{inf.num}].Threads[{tnum}] does not exist","messagePattern":"Inferiors\\[(.+?)\\]\\.Threads\\[(.+?)\\] does not exist","errorType":"validation","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py","lineNumber":154,"sourceCode":"    return find_inf_by_pattern(object, MODULES_PATTERN, \"a ModuleContainer\")\n\n\ndef find_inf_by_mod_obj(object: TraceObject) -> gdb.Inferior:\n    return find_inf_by_pattern(object, MODULE_PATTERN, \"a Module\")\n\n\ndef find_module_name_by_mod_obj(object: TraceObject) -> str:\n    mat = MODULE_PATTERN.fullmatch(object.path)\n    if mat is None:\n        raise TypeError(f\"{object} is not a Module\")\n    return mat['modname']\n\n\ndef find_thread_by_num(inf: gdb.Inferior, tnum: int) -> gdb.InferiorThread:\n    for t in inf.threads():\n        if t.num == tnum:\n            return t\n    raise KeyError(f\"Inferiors[{inf.num}].Threads[{tnum}] does not exist\")\n\n\ndef find_thread_by_pattern(pattern: re.Pattern, object: TraceObject,\n                           err_msg: str) -> gdb.InferiorThread:\n    mat = pattern.fullmatch(object.path)\n    if mat is None:\n        raise TypeError(f\"{object} is not {err_msg}\")\n    infnum = int(mat['infnum'])\n    tnum = int(mat['tnum'])\n    inf = find_inf_by_num(infnum)\n    return find_thread_by_num(inf, tnum)\n\n\ndef find_thread_by_obj(object: TraceObject) -> gdb.InferiorThread:\n    return find_thread_by_pattern(THREAD_PATTERN, object, \"a Thread\")\n\n\ndef find_thread_by_stack_obj(object: TraceObject) -> gdb.InferiorThread:","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py#L136-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-derive the thread list (inf.threads()) and confirm tnum is present before calling find_thread_by_num.","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.","Validate the parsed tnum against the current inferior's threads in find_thread_by_pattern before delegating.","Ensure trace invalidation hooks fire on thread-exit events so stale [tnum] objects are removed before method dispatch."],"exampleFix":"// before\nt = find_thread_by_num(inf, tnum)\n// after\nnums = {t.num for t in inf.threads()}\nif tnum not in nums:\n    # thread exited; refresh trace Threads subtree\n    return None\nt = find_thread_by_num(inf, tnum)","handlingStrategy":"validation","validationCode":"tnums = {t.num for t in inf.threads()}\nif tnum not in tnums:\n    # thread gone; refresh trace Threads subtree\n    return None\nt = find_thread_by_num(inf, tnum)","typeGuard":"def thread_exists(inf: gdb.Inferior, tnum: int) -> bool:\n    return any(t.num == tnum for t in inf.threads())","tryCatchPattern":"try:\n    t = find_thread_by_num(inf, tnum)\nexcept KeyError:\n    # thread exited between event and dispatch; invalidate trace node\n    invalidate_thread_obj(inf.num, tnum)\n    return None","preventionTips":["re-query inf.threads() before lookup in long sessions","wire thread-exit hooks to remove stale Threads[t] trace objects","treat thread lookups as inherently racy and handle absence"],"tags":["ghidra","gdb","trace-rpc","key-error","thread-lifecycle"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}