--- /dev/null
+# Copyright 2024 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+
+from .sources import make_source
+from .startup import in_gdb_thread
+from .varref import BaseReference
+
+# Map a block identifier to a scope object.
+_id_to_scope = {}
+
+
+# Arrange to clear the scope references when the inferior runs.
+@in_gdb_thread
+def clear(event):
+ global _id_to_scope
+ _id_to_scope = {}
+
+
+gdb.events.cont.connect(clear)
+
+
+# A scope that holds static and/or global variables.
+class _Globals(BaseReference):
+ def __init__(self, filename, var_list):
+ super().__init__("Globals")
+ self.filename = filename
+ self.var_list = var_list
+
+ def to_object(self):
+ result = super().to_object()
+ result["presentationHint"] = "globals"
+ # How would we know?
+ result["expensive"] = False
+ result["namedVariables"] = self.child_count()
+ if self.filename is not None:
+ result["source"] = make_source(self.filename)
+ return result
+
+ def has_children(self):
+ # This object won't even be created if there are no variables
+ # to return.
+ return True
+
+ def child_count(self):
+ return len(self.var_list)
+
+ @in_gdb_thread
+ def fetch_one_child(self, idx):
+ return self.var_list[idx].value()
+
+
+@in_gdb_thread
+def get_global_scope(frame):
+ """Given a frame decorator, return the corresponding global scope
+ object.
+
+ If the frame does not have a block, or if the CU does not have
+ globals (that is, empty static and global blocks), return None."""
+ inf_frame = frame.inferior_frame()
+ # It's unfortunate that this API throws instead of returning None.
+ try:
+ block = inf_frame.block()
+ except RuntimeError:
+ return None
+
+ global _id_to_scope
+ block = block.static_block
+ if block in _id_to_scope:
+ return _id_to_scope[block]
+
+ syms = []
+ block_iter = block
+ while block_iter is not None:
+ syms += [sym for sym in block_iter if sym.is_variable]
+ block_iter = block_iter.superblock
+
+ if len(syms) == 0:
+ return None
+
+ result = _Globals(frame.filename(), syms)
+ _id_to_scope[block] = result
+
+ return result
import gdb
from .frames import frame_for_id
+from .globalvars import get_global_scope
from .server import request
from .sources import make_source
from .startup import in_gdb_thread
scopes.append(_ScopeReference("Locals", "locals", frame, locs))
scopes.append(_RegisterReference("Registers", frame))
frame_to_scope[frameId] = scopes
+ global_scope = get_global_scope(frame)
+ if global_scope is not None:
+ scopes.append(global_scope)
return {"scopes": [x.to_object() for x in scopes]}
[format {o frameId [i %d]} $frame_id]]
set scopes [dict get [lindex $scopes 0] body scopes]
-gdb_assert {[llength $scopes] == 2} "two scopes"
+gdb_assert {[llength $scopes] == 3} "three scopes"
-lassign $scopes scope reg_scope
+lassign $scopes scope reg_scope global_scope
gdb_assert {[dict get $scope name] == "Locals"} "scope is locals"
+gdb_assert {[dict get $global_scope name] == "Globals"} "scope is globals"
-gdb_assert {[dict get $scope namedVariables] == 4} "three vars in scope"
+gdb_assert {[dict get $scope namedVariables] == 4} "four vars in locals"
+gdb_assert {[dict get $global_scope namedVariables] == 1} "one var in globals"
set num [dict get $scope variablesReference]
set refs [lindex [dap_check_request_and_response "fetch variables" \