diff options
author | Tom Tromey <tromey@adacore.com> | 2024-05-16 07:58:07 -0600 |
---|---|---|
committer | Tom Tromey <tromey@adacore.com> | 2024-06-04 11:12:42 -0600 |
commit | d856ef4fc2b09d88bf66e59badde9b4c884de811 (patch) | |
tree | a01cf0aa80ba5cf7c91f4af8a7dc83904917c05e | |
parent | 4dd38c398331c2fdb8bfc7ffd78d836bae0d9303 (diff) | |
download | gdb-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/NEWS | 5 | ||||
-rw-r--r-- | gdb/data-directory/Makefile.in | 1 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/globalvars.py | 97 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/scopes.py | 4 | ||||
-rw-r--r-- | gdb/testsuite/gdb.dap/ptrref.exp | 8 | ||||
-rw-r--r-- | gdb/testsuite/gdb.dap/rust-slices.exp | 4 |
6 files changed, 115 insertions, 4 deletions
@@ -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" |