{"record":{"id":"2d6154f7c4afc0f3","repo":"NationalSecurityAgency/ghidra","slug":"inferiors-thread-inferior-num-threads-thread-n","errorCode":null,"errorMessage":"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist","messagePattern":"Inferiors\\[(.+?)\\]\\.Threads\\[(.+?)\\]\\.Stack\\[(.+?)\\] 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":189,"sourceCode":"\ndef find_thread_by_stack_obj(object: TraceObject) -> gdb.InferiorThread:\n    return find_thread_by_pattern(STACK_PATTERN, object, \"a Stack\")\n\n\ndef find_frame_by_level(thread: gdb.InferiorThread,\n                        level: int) -> Optional[gdb.Frame]:\n    # Because threads don't have any attribute to get at frames\n    thread.switch()\n    f = util.selected_frame()\n    if f is None:\n        return None\n\n    # Navigate up or down, because I can't just get by level\n    down = level - util.get_level(f)\n    while down > 0:\n        f = f.older()\n        if f is None:\n            raise KeyError(\n                f\"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist\")\n        down -= 1\n    while down < 0:\n        f = f.newer()\n        if f is None:\n            raise KeyError(\n                f\"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist\")\n        down += 1\n    return f\n\n\ndef find_frame_by_pattern(pattern: re.Pattern, object: TraceObject,\n                          err_msg: str) -> Optional[gdb.Frame]:\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'])","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py#L171-L207","documentation":"Raised in find_frame_by_level while walking the stack upward via frame.older(): the older frame is None, meaning the requested level is deeper than the oldest (root) frame on the current thread's stack. Because GDB's Python API exposes no direct 'get frame by level', the agent walks frame.older() decrementing a counter; hitting None before the counter reaches zero means the level is out of range. KeyError signals a missing indexed entity (Stack[level]).","triggerScenarios":"Calling find_frame_by_level with a level larger than the deepest frame index that exists on the thread. Reached when a trace-rpc handler requests a frame by an absolute level parsed from a FRAME_PATTERN object path while the thread's actual stack is shallower (e.g. after a stack unwind truncated frames, or the level was computed from a previous deeper stack).","commonSituations":"Stale Stack[level] trace objects left after the stack unwound/shrank; backtrace limits (GDB backtrace depth settings) hiding deeper frames; thread resumed and re-stopped with a shallower stack; cross-architecture stub frames that terminate the chain early.","solutions":["Bound the requested level to the current frame count (use gdb.execute('bt') length or count via repeated older() before the lookup).","Refresh the Stack subtree of the trace on each stop before method handlers index frames.","In callers, treat the KeyError as 'frame not present' and return None / a not-present result rather than propagating.","Check GDB's backtrace limit (set backtrace limit) is not artificially truncating the walk."],"exampleFix":"// before\nf = find_frame_by_level(t, level)\n// after\ndepth = 0\n_probe = util.selected_frame()\nwhile _probe is not None:\n    depth += 1\n    _probe = _probe.older()\nif level >= depth:\n    return None  # level out of range\nf = find_frame_by_level(t, level)","handlingStrategy":"validation","validationCode":"depth = 0\n_probe = util.selected_frame()\nwhile _probe is not None:\n    depth += 1\n    _probe = _probe.older()\nif level >= depth:\n    return None\nf = find_frame_by_level(t, level)","typeGuard":"def frame_level_exists(t: gdb.InferiorThread, level: int) -> bool:\n    # walk once to test\n    cur = util.selected_frame()\n    if cur is None:\n        return False\n    delta = level - util.get_level(cur)\n    cur_level = cur\n    for _ in range(abs(delta)):\n        cur_level = cur_level.older() if delta > 0 else cur_level.newer()\n        if cur_level is None:\n            return False\n    return True","tryCatchPattern":"try:\n    f = find_frame_by_level(t, level)\nexcept KeyError:\n    # requested level deeper than stack; refresh Stack subtree\n    return None","preventionTips":["recompute stack depth on each stop before indexing frames","refresh the Stack subtree of the trace before method dispatch","check GDB backtrace limit is not truncating the walk"],"tags":["ghidra","gdb","trace-rpc","key-error","stack-walk"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}