diff --git a/gdb/NEWS b/gdb/NEWS
index 691abff420b..ab252c81ab6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -179,6 +179,9 @@ show unwind-on-signal
   ** The "set debug dap-log-file" command is now documented.  This
      command was available in GDB 14 but not documented.
 
+  ** The "scopes" request will now return a scope holding global
+     variables from the stack frame's compilation unit.
+
 * Guile API
 
   ** New constants SYMBOL_TYPE_DOMAIN, SYMBOL_FUNCTION_DOMAIN, and
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 98a43529b07..f529656ca05 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 00000000000..149c9a8f22f
--- /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 8cd860141d6..ad2f10f4057 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 .startup import in_gdb_thread
 from .varref import BaseReference
@@ -161,4 +162,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 0552c3b9815..236ffae12d5 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 c85568d69ea..d3bd3050c16 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"
