NationalSecurityAgency/ghidra · error · KeyError

Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack

Error message

Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist

What it means

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]).

Source

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

def find_thread_by_stack_obj(object: TraceObject) -> gdb.InferiorThread:
    return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")


def find_frame_by_level(thread: gdb.InferiorThread,
                        level: int) -> Optional[gdb.Frame]:
    # Because threads don't have any attribute to get at frames
    thread.switch()
    f = util.selected_frame()
    if f is None:
        return None

    # Navigate up or down, because I can't just get by level
    down = level - util.get_level(f)
    while down > 0:
        f = f.older()
        if f is None:
            raise KeyError(
                f"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist")
        down -= 1
    while down < 0:
        f = f.newer()
        if f is None:
            raise KeyError(
                f"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist")
        down += 1
    return f


def find_frame_by_pattern(pattern: re.Pattern, object: TraceObject,
                          err_msg: str) -> Optional[gdb.Frame]:
    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'])

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Bound the requested level to the current frame count (use gdb.execute('bt') length or count via repeated older() before the lookup).
  2. Refresh the Stack subtree of the trace on each stop before method handlers index frames.
  3. In callers, treat the KeyError as 'frame not present' and return None / a not-present result rather than propagating.
  4. Check GDB's backtrace limit (set backtrace limit) is not artificially truncating the walk.

Example fix

// before
f = find_frame_by_level(t, level)
// after
depth = 0
_probe = util.selected_frame()
while _probe is not None:
    depth += 1
    _probe = _probe.older()
if level >= depth:
    return None  # level out of range
f = find_frame_by_level(t, level)
Defensive patterns

Strategy: validation

Validate before calling

depth = 0
_probe = util.selected_frame()
while _probe is not None:
    depth += 1
    _probe = _probe.older()
if level >= depth:
    return None
f = find_frame_by_level(t, level)

Type guard

def frame_level_exists(t: gdb.InferiorThread, level: int) -> bool:
    # walk once to test
    cur = util.selected_frame()
    if cur is None:
        return False
    delta = level - util.get_level(cur)
    cur_level = cur
    for _ in range(abs(delta)):
        cur_level = cur_level.older() if delta > 0 else cur_level.newer()
        if cur_level is None:
            return False
    return True

Try / catch

try:
    f = find_frame_by_level(t, level)
except KeyError:
    # requested level deeper than stack; refresh Stack subtree
    return None

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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