From: Oleg Tolmatcev Date: Mon, 26 Aug 2024 21:11:36 +0000 (+0200) Subject: gdb-dap: fix gdb.error: Frame is invalid. X-Git-Tag: gdb-16-branchpoint~106 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=83c1269833c88608dac27edf78afc335ca8dbff1;p=thirdparty%2Fbinutils-gdb.git gdb-dap: fix gdb.error: Frame is invalid. When you try to use a frame on one thread and it was created on another you get an error. I fixed it by creating a map from a frame ID to a thread ID. When a frame is created it is added to the map. When you try to find a frame for an id it checks if it is on the correct thread and if not switches to that thread. I had to store the frame id instead of the frame itself in a "_ScopeReference". Signed-off-by: Oleg Tolmatcev Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32133 Approved-By: Tom Tromey --- diff --git a/gdb/python/lib/gdb/dap/frames.py b/gdb/python/lib/gdb/dap/frames.py index 07a4e3ea793..12113961386 100644 --- a/gdb/python/lib/gdb/dap/frames.py +++ b/gdb/python/lib/gdb/dap/frames.py @@ -19,6 +19,7 @@ import gdb from gdb.frames import frame_iterator from .startup import in_gdb_thread +from .state import set_thread # A list of all the frames we've reported. A frame's index in the # list is its ID. We don't use a hash here because frames are not @@ -29,6 +30,9 @@ _all_frames = [] # Map from a global thread ID to a memoizing frame iterator. _iter_map = {} +# Map from a global frame ID to a thread ID. +thread_ids: dict[int, int] = {} + # Clear all the frame IDs. @in_gdb_thread @@ -37,6 +41,8 @@ def _clear_frame_ids(evt): _all_frames = [] global _iter_map _iter_map = {} + global thread_ids + thread_ids = {} # Clear the frame ID map whenever the inferior runs. @@ -46,6 +52,11 @@ gdb.events.cont.connect(_clear_frame_ids) @in_gdb_thread def frame_for_id(id): """Given a frame identifier ID, return the corresponding frame.""" + global thread_ids + if id in thread_ids: + thread_id = thread_ids[id] + if thread_id != gdb.selected_thread().global_num: + set_thread(thread_id) global _all_frames return _all_frames[id] @@ -94,6 +105,8 @@ def _frame_id_generator(): global _all_frames num = len(_all_frames) _all_frames.append(frame) + global thread_ids + thread_ids[num] = gdb.selected_thread().global_num return num def yield_frames(iterator, for_elided): diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py index fb90f647a0f..2fb3f77f1e3 100644 --- a/gdb/python/lib/gdb/dap/scopes.py +++ b/gdb/python/lib/gdb/dap/scopes.py @@ -76,13 +76,10 @@ def symbol_value(sym, frame): class _ScopeReference(BaseReference): - def __init__(self, name, hint, frame, var_list): + def __init__(self, name, hint, frameId: int, var_list): super().__init__(name) self.hint = hint - self.frame = frame - self.inf_frame = frame.inferior_frame() - self.func = frame.function() - self.line = frame.line() + self.frameId = frameId # VAR_LIST might be any kind of iterator, but it's convenient # here if it is just a collection. self.var_list = tuple(var_list) @@ -93,9 +90,10 @@ class _ScopeReference(BaseReference): # How would we know? result["expensive"] = False result["namedVariables"] = self.child_count() - if self.line is not None: - result["line"] = self.line - filename = self.frame.filename() + frame = frame_for_id(self.frameId) + if frame.line() is not None: + result["line"] = frame.line() + filename = frame.filename() if filename is not None: result["source"] = make_source(filename) return result @@ -108,14 +106,14 @@ class _ScopeReference(BaseReference): @in_gdb_thread def fetch_one_child(self, idx): - return symbol_value(self.var_list[idx], self.frame) + return symbol_value(self.var_list[idx], frame_for_id(self.frameId)) # A _ScopeReference that wraps the 'finish' value. Note that this # object is only created if such a value actually exists. class _FinishScopeReference(_ScopeReference): - def __init__(self, frame): - super().__init__("Return", "returnValue", frame, ()) + def __init__(self, frameId): + super().__init__("Return", "returnValue", frameId, ()) def child_count(self): return 1 @@ -127,16 +125,21 @@ class _FinishScopeReference(_ScopeReference): class _RegisterReference(_ScopeReference): - def __init__(self, name, frame): + def __init__(self, name, frameId): super().__init__( - name, "registers", frame, frame.inferior_frame().architecture().registers() + name, + "registers", + frameId, + frame_for_id(frameId).inferior_frame().architecture().registers(), ) @in_gdb_thread def fetch_one_child(self, idx): return ( self.var_list[idx].name, - self.inf_frame.read_register(self.var_list[idx]), + frame_for_id(self.frameId) + .inferior_frame() + .read_register(self.var_list[idx]), ) @@ -153,16 +156,16 @@ def scopes(*, frameId: int, **extra): # iterator case. args = tuple(frame.frame_args() or ()) if args: - scopes.append(_ScopeReference("Arguments", "arguments", frame, args)) + scopes.append(_ScopeReference("Arguments", "arguments", frameId, args)) has_return_value = frameId == 0 and _last_return_value is not None # Make sure to handle the None case as well as the empty # iterator case. locs = tuple(frame.frame_locals() or ()) if locs: - scopes.append(_ScopeReference("Locals", "locals", frame, locs)) - scopes.append(_RegisterReference("Registers", frame)) + scopes.append(_ScopeReference("Locals", "locals", frameId, locs)) + scopes.append(_RegisterReference("Registers", frameId)) if has_return_value: - scopes.append(_FinishScopeReference(frame)) + scopes.append(_FinishScopeReference(frameId)) frame_to_scope[frameId] = scopes global_scope = get_global_scope(frame) if global_scope is not None: