Reimplement DAP stack traces using frame filters
authorTom Tromey <tromey@adacore.com>
Wed, 14 Jun 2023 12:09:23 +0000 (06:09 -0600)
committerTom Tromey <tromey@adacore.com>
Mon, 10 Jul 2023 19:17:30 +0000 (13:17 -0600)
This reimplements DAP stack traces using frame filters.  This slightly
simplifies the code, because frame filters and DAP were already doing
some similar work.  This also renames RegisterReference and
ScopeReference to make it clear that these are private (and so changes
don't have to worry about other files).

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30468

gdb/python/lib/gdb/dap/bt.py
gdb/python/lib/gdb/dap/evaluate.py
gdb/python/lib/gdb/dap/frames.py
gdb/python/lib/gdb/dap/scopes.py
gdb/testsuite/gdb.dap/scopes.c

index 4439b428926e69205b5109475a2db409468df520..0350a3bb6d55f7085bd876a07e8e25b18fbfb888 100644 (file)
 
 import gdb
 import os
+import itertools
 
+from gdb.frames import frame_iterator
+from gdb.FrameIterator import FrameIterator
+from gdb.FrameDecorator import FrameDecorator
 from .frames import frame_id
 from .server import request, capability
 from .startup import send_gdb_with_response, in_gdb_thread
 from .state import set_thread
 
 
-# Helper function to safely get the name of a frame as a string.
-@in_gdb_thread
-def _frame_name(frame):
-    name = frame.name()
-    if name is None:
-        name = "???"
-    return name
-
-
-# Helper function to get a frame's SAL without an error.
-@in_gdb_thread
-def _safe_sal(frame):
-    try:
-        return frame.find_sal()
-    except gdb.error:
-        return None
-
-
 # Helper function to compute a stack trace.
 @in_gdb_thread
 def _backtrace(thread_id, levels, startFrame):
     set_thread(thread_id)
     frames = []
-    current_number = 0
+    if levels == 0:
+        # Zero means all remaining frames.
+        high = -1
+    else:
+        # frame_iterator uses an inclusive range, so subtract one.
+        high = startFrame + levels - 1
     try:
-        current_frame = gdb.newest_frame()
+        frame_iter = frame_iterator(gdb.newest_frame(), startFrame, high)
     except gdb.error:
-        current_frame = None
-    while current_frame is not None and (levels == 0 or len(frames) < levels):
-        # This condition handles the startFrame==0 case as well.
-        if current_number >= startFrame:
-            newframe = {
-                "id": frame_id(current_frame),
-                "name": _frame_name(current_frame),
-                # This must always be supplied, but we will set it
-                # correctly later if that is possible.
-                "line": 0,
-                # GDB doesn't support columns.
-                "column": 0,
-                "instructionPointerReference": hex(current_frame.pc()),
+        frame_iter = ()
+    for current_frame in frame_iter:
+        newframe = {
+            "id": frame_id(current_frame),
+            "name": current_frame.function(),
+            # This must always be supplied, but we will set it
+            # correctly later if that is possible.
+            "line": 0,
+            # GDB doesn't support columns.
+            "column": 0,
+            "instructionPointerReference": hex(current_frame.address()),
+        }
+        line = current_frame.line()
+        if line is not None:
+            newframe["line"] = line
+        filename = current_frame.filename()
+        if filename is not None:
+            newframe["source"] = {
+                "name": os.path.basename(filename),
+                "path": filename,
+                # We probably don't need this but it doesn't hurt
+                # to be explicit.
+                "sourceReference": 0,
             }
-            sal = _safe_sal(current_frame)
-            if sal is not None and sal.symtab is not None:
-                newframe["source"] = {
-                    "name": os.path.basename(sal.symtab.filename),
-                    "path": sal.symtab.filename,
-                    # We probably don't need this but it doesn't hurt
-                    # to be explicit.
-                    "sourceReference": 0,
-                }
-                newframe["line"] = sal.line
-            frames.append(newframe)
-        current_number = current_number + 1
-        current_frame = current_frame.older()
+        frames.append(newframe)
     # Note that we do not calculate totalFrames here.  Its absence
     # tells the client that it may simply ask for frames until a
     # response yields fewer frames than requested.
index 5e3ebf7fa24aaab0e6a11f483b220f42c24631e2..635a05fcdcd91dd48ccaf9b7b2cae8b7801fac87 100644 (file)
@@ -19,7 +19,7 @@ import gdb.printing
 # This is deprecated in 3.9, but required in older versions.
 from typing import Optional
 
-from .frames import frame_for_id
+from .frames import select_frame
 from .server import capability, request, client_bool_capability
 from .startup import send_gdb_with_response, in_gdb_thread
 from .varref import find_variable, VariableReference
@@ -35,8 +35,7 @@ class EvaluateResult(VariableReference):
 def _evaluate(expr, frame_id):
     global_context = True
     if frame_id is not None:
-        frame = frame_for_id(frame_id)
-        frame.select()
+        select_frame(frame_id)
         global_context = False
     val = gdb.parse_and_eval(expr, global_context=global_context)
     ref = EvaluateResult(val)
@@ -70,8 +69,7 @@ class _SetResult(VariableReference):
 def _set_expression(expression, value, frame_id):
     global_context = True
     if frame_id is not None:
-        frame = frame_for_id(frame_id)
-        frame.select()
+        select_frame(frame_id)
         global_context = False
     lhs = gdb.parse_and_eval(expression, global_context=global_context)
     rhs = gdb.parse_and_eval(value, global_context=global_context)
@@ -83,8 +81,7 @@ def _set_expression(expression, value, frame_id):
 @in_gdb_thread
 def _repl(command, frame_id):
     if frame_id is not None:
-        frame = frame_for_id(frame_id)
-        frame.select()
+        select_frame(frame_id)
     val = gdb.execute(command, from_tty=True, to_string=True)
     return {
         "result": val,
index 08209d0b361e75de6f79e39ee175df08c523c268..1d2d1371354da6288c1e66f035f1c765037871d3 100644 (file)
@@ -52,3 +52,10 @@ def frame_for_id(id):
     """Given a frame identifier ID, return the corresponding frame."""
     global _all_frames
     return _all_frames[id]
+
+
+@in_gdb_thread
+def select_frame(id):
+    """Given a frame identifier ID, select the corresponding frame."""
+    frame = frame_for_id(id)
+    frame.inferior_frame().select()
index 9b80dd9ce806baf0dc74cabcd1f03e157b68110b..1687094c4ceb27631e9da7e08f8e6d624c0e3154 100644 (file)
@@ -21,41 +21,17 @@ from .server import request
 from .varref import BaseReference
 
 
-# Helper function to return a frame's block without error.
-@in_gdb_thread
-def _safe_block(frame):
-    try:
-        return frame.block()
-    except gdb.error:
-        return None
-
-
-# Helper function to return two lists of variables of block, up to the
-# enclosing function.  The result is of the form (ARGS, LOCALS), where
-# each element is itself a list.
-@in_gdb_thread
-def _block_vars(block):
-    args = []
-    locs = []
-    while True:
-        for var in block:
-            if var.is_argument:
-                args.append(var)
-            elif var.is_variable or var.is_constant:
-                locs.append(var)
-        if block.function is not None:
-            break
-        block = block.superblock
-    return (args, locs)
-
-
-class ScopeReference(BaseReference):
+class _ScopeReference(BaseReference):
     def __init__(self, name, hint, frame, var_list):
         super().__init__(name)
         self.hint = hint
         self.frame = frame
+        self.inf_frame = frame.inferior_frame()
         self.func = frame.function()
-        self.var_list = var_list
+        self.line = frame.line()
+        # 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)
 
     def to_object(self):
         result = super().to_object()
@@ -63,8 +39,8 @@ class ScopeReference(BaseReference):
         # How would we know?
         result["expensive"] = False
         result["namedVariables"] = len(self.var_list)
-        if self.func is not None:
-            result["line"] = self.func.line
+        if self.line is not None:
+            result["line"] = self.line
             # FIXME construct a Source object
         return result
 
@@ -73,38 +49,45 @@ class ScopeReference(BaseReference):
 
     @in_gdb_thread
     def fetch_one_child(self, idx):
+        # Here SYM will conform to the SymValueWrapper interface.
         sym = self.var_list[idx]
-        if sym.needs_frame:
-            val = sym.value(self.frame)
-        else:
-            val = sym.value()
-        return (sym.print_name, val)
-
-
-class RegisterReference(ScopeReference):
+        name = str(sym.symbol())
+        val = sym.value()
+        if val is None:
+            # No synthetic value, so must read the symbol value
+            # ourselves.
+            val = sym.symbol().value(self.inf_frame)
+        elif not isinstance(val, gdb.Value):
+            val = gdb.Value(val)
+        return (name, val)
+
+
+class _RegisterReference(_ScopeReference):
     def __init__(self, name, frame):
         super().__init__(
-            name, "registers", frame, list(frame.architecture().registers())
+            name, "registers", frame, frame.inferior_frame().architecture().registers()
         )
 
     @in_gdb_thread
     def fetch_one_child(self, idx):
-        return (self.var_list[idx].name, self.frame.read_register(self.var_list[idx]))
+        return (
+            self.var_list[idx].name,
+            self.inf_frame.read_register(self.var_list[idx]),
+        )
 
 
 # Helper function to create a DAP scopes for a given frame ID.
 @in_gdb_thread
 def _get_scope(id):
     frame = frame_for_id(id)
-    block = _safe_block(frame)
     scopes = []
-    if block is not None:
-        (args, locs) = _block_vars(block)
-        if args:
-            scopes.append(ScopeReference("Arguments", "arguments", frame, args))
-        if locs:
-            scopes.append(ScopeReference("Locals", "locals", frame, locs))
-    scopes.append(RegisterReference("Registers", frame))
+    args = frame.frame_args()
+    if args:
+        scopes.append(_ScopeReference("Arguments", "arguments", frame, args))
+    locs = frame.frame_locals()
+    if locs:
+        scopes.append(_ScopeReference("Locals", "locals", frame, locs))
+    scopes.append(_RegisterReference("Registers", frame))
     return [x.to_object() for x in scopes]
 
 
index ce87db1f13d0254271f06e351b84be9cd157fd67..a9ad4d8a53bbe27bdeb8262e3825f4def95f8a4b 100644 (file)
@@ -30,6 +30,7 @@ int main ()
   {
     const char *inner = "inner block";
 
-    return 0;                  /* BREAK */
+    /* Make sure to use 'scalar'.  */
+    return scalar - 23;                        /* BREAK */
   }
 }