aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Tromey <tromey@adacore.com>2024-05-16 07:58:07 -0600
committerTom Tromey <tromey@adacore.com>2024-06-04 11:12:42 -0600
commitd856ef4fc2b09d88bf66e59badde9b4c884de811 (patch)
treea01cf0aa80ba5cf7c91f4af8a7dc83904917c05e
parent4dd38c398331c2fdb8bfc7ffd78d836bae0d9303 (diff)
downloadgdb-d856ef4fc2b09d88bf66e59badde9b4c884de811.zip
gdb-d856ef4fc2b09d88bf66e59badde9b4c884de811.tar.gz
gdb-d856ef4fc2b09d88bf66e59badde9b4c884de811.tar.bz2
Return global scope from DAP scopes request
A co-worker requested that the DAP code emit a scope for global variables. It's not really practical to do this for all globals, but it seemed reasonable to do this for globals coming from the frame's compilation unit. For Ada in particular, this is convenient as it exposes package-scoped variables. Reviewed-By: Eli Zaretskii <eliz@gnu.org>
-rw-r--r--gdb/NEWS5
-rw-r--r--gdb/data-directory/Makefile.in1
-rw-r--r--gdb/python/lib/gdb/dap/globalvars.py97
-rw-r--r--gdb/python/lib/gdb/dap/scopes.py4
-rw-r--r--gdb/testsuite/gdb.dap/ptrref.exp8
-rw-r--r--gdb/testsuite/gdb.dap/rust-slices.exp4
6 files changed, 115 insertions, 4 deletions
diff --git a/gdb/NEWS b/gdb/NEWS
index 408d150..bb7c4a6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,11 @@
*** Changes since GDB 15
+* Debugger Adapter Protocol changes
+
+ ** The "scopes" request will now return a scope holding global
+ variables from the stack frame's compilation unit.
+
*** Changes in GDB 15
* The MPX commands "show/set mpx bound" have been deprecated, as Intel
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 98a4352..f529656 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -98,6 +98,7 @@ PYTHON_FILE_LIST = \
gdb/dap/evaluate.py \
gdb/dap/events.py \
gdb/dap/frames.py \
+ gdb/dap/globalvars.py \
gdb/dap/__init__.py \
gdb/dap/io.py \
gdb/dap/launch.py \
diff --git a/gdb/python/lib/gdb/dap/globalvars.py b/gdb/python/lib/gdb/dap/globalvars.py
new file mode 100644
index 0000000..149c9a8
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/globalvars.py
@@ -0,0 +1,97 @@
+# 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
diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py
index 09f440e..d0e9115 100644
--- a/gdb/python/lib/gdb/dap/scopes.py
+++ b/gdb/python/lib/gdb/dap/scopes.py
@@ -16,6 +16,7 @@
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
@@ -164,4 +165,7 @@ def scopes(*, frameId: int, **extra):
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]}
diff --git a/gdb/testsuite/gdb.dap/ptrref.exp b/gdb/testsuite/gdb.dap/ptrref.exp
index 0552c3b..236ffae 100644
--- a/gdb/testsuite/gdb.dap/ptrref.exp
+++ b/gdb/testsuite/gdb.dap/ptrref.exp
@@ -54,12 +54,14 @@ set scopes [dap_check_request_and_response "get scopes" 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" \
diff --git a/gdb/testsuite/gdb.dap/rust-slices.exp b/gdb/testsuite/gdb.dap/rust-slices.exp
index c85568d..d3bd305 100644
--- a/gdb/testsuite/gdb.dap/rust-slices.exp
+++ b/gdb/testsuite/gdb.dap/rust-slices.exp
@@ -59,7 +59,9 @@ set scopes [dap_check_request_and_response "get scopes" scopes \
[format {o frameId [i %d]} $frame_id]]
set scopes [dict get [lindex $scopes 0] body scopes]
-gdb_assert {[llength $scopes] == 2} "two scopes"
+# There are three scopes because an artificial symbol ends up in the
+# DWARF. See https://github.com/rust-lang/rust/issues/125126.
+gdb_assert {[llength $scopes] == 3} "three scopes"
lassign $scopes scope ignore
gdb_assert {[dict get $scope name] == "Locals"} "scope is locals"