aboutsummaryrefslogtreecommitdiff
path: root/gdb/python
diff options
context:
space:
mode:
authorTom Tromey <tromey@adacore.com>2023-10-06 13:40:39 -0600
committerTom Tromey <tromey@adacore.com>2023-11-17 07:09:36 -0700
commit1920148904fe5ca0035c1addf2376f9ab13ffd3d (patch)
tree69a13a377af5b457744e81be80a2f82aeb5581b5 /gdb/python
parent74affa1bc070ff0530b2a1b92d8d9fbcae6024ec (diff)
downloadgdb-1920148904fe5ca0035c1addf2376f9ab13ffd3d.zip
gdb-1920148904fe5ca0035c1addf2376f9ab13ffd3d.tar.gz
gdb-1920148904fe5ca0035c1addf2376f9ab13ffd3d.tar.bz2
Handle StackFrameFormat in DAP
DAP specifies a StackFrameFormat object that can be used to change how the "name" part of a stack frame is constructed. While this output can already be done in a nicer way (and also letting the client choose the formatting), nevertheless it is in the spec, so I figured I'd implement it. While implementing this, I discovered that the current code does not correctly preserve frame IDs across requests. I rewrote frame iteration to preserve this, and it turned out to be simpler to combine these patches. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30475
Diffstat (limited to 'gdb/python')
-rw-r--r--gdb/python/lib/gdb/dap/bt.py110
-rw-r--r--gdb/python/lib/gdb/dap/frames.py113
-rw-r--r--gdb/python/lib/gdb/dap/scopes.py39
3 files changed, 215 insertions, 47 deletions
diff --git a/gdb/python/lib/gdb/dap/bt.py b/gdb/python/lib/gdb/dap/bt.py
index 982d501..c63ca6d 100644
--- a/gdb/python/lib/gdb/dap/bt.py
+++ b/gdb/python/lib/gdb/dap/bt.py
@@ -16,37 +16,68 @@
import gdb
import os
-from gdb.frames import frame_iterator
-from .frames import frame_id
+# This is deprecated in 3.9, but required in older versions.
+from typing import Optional
+
+from .frames import dap_frame_generator
from .modules import module_id
+from .scopes import symbol_value
from .server import request, capability
from .sources import make_source
from .startup import send_gdb_with_response, in_gdb_thread
from .state import set_thread
+from .typecheck import type_check
from .varref import apply_format
+# Helper function to compute parameter information for a stack frame.
+@in_gdb_thread
+def _compute_parameters(frame, stack_format):
+ arg_iter = frame.frame_args()
+ if arg_iter is None:
+ return ""
+ result = []
+ for arg in arg_iter:
+ desc = []
+ name, val = symbol_value(arg, frame)
+ # We don't try to use any particular language's syntax for the
+ # output here.
+ if stack_format["parameterTypes"]:
+ desc.append("[" + str(val.type) + "]")
+ if stack_format["parameterNames"]:
+ desc.append(name)
+ # If both the name and the value are requested, insert an
+ # '=' for clarity.
+ if stack_format["parameterValues"]:
+ desc.append("=")
+ if stack_format["parameterValues"]:
+ desc.append(val.format_string(summary=True))
+ result.append(" ".join(desc))
+ return ", ".join(result)
+
+
# Helper function to compute a stack trace.
@in_gdb_thread
-def _backtrace(thread_id, levels, startFrame, value_format):
- with apply_format(value_format):
+def _backtrace(thread_id, levels, startFrame, stack_format):
+ with apply_format(stack_format):
set_thread(thread_id)
frames = []
- 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:
- frame_iter = frame_iterator(gdb.newest_frame(), startFrame, high)
- except gdb.error:
- frame_iter = ()
- for current_frame in frame_iter:
+ frame_iter = dap_frame_generator(startFrame, levels, stack_format["includeAll"])
+ for frame_id, current_frame in frame_iter:
pc = current_frame.address()
+ # The stack frame format affects the name, so we build it up
+ # piecemeal and assign it at the end.
+ name = current_frame.function()
+ # The meaning of StackFrameFormat.parameters was clarified
+ # in https://github.com/microsoft/debug-adapter-protocol/issues/411.
+ if stack_format["parameters"] and (
+ stack_format["parameterTypes"]
+ or stack_format["parameterNames"]
+ or stack_format["parameterValues"]
+ ):
+ name += "(" + _compute_parameters(current_frame, stack_format) + ")"
newframe = {
- "id": frame_id(current_frame),
- "name": current_frame.function(),
+ "id": frame_id,
# This must always be supplied, but we will set it
# correctly later if that is possible.
"line": 0,
@@ -54,15 +85,20 @@ def _backtrace(thread_id, levels, startFrame, value_format):
"column": 0,
"instructionPointerReference": hex(pc),
}
- objfile = gdb.current_progspace().objfile_for_address(pc)
- if objfile is not None:
- newframe["moduleId"] = module_id(objfile)
line = current_frame.line()
if line is not None:
newframe["line"] = line
+ if stack_format["line"]:
+ name += ", line " + str(line)
+ objfile = gdb.current_progspace().objfile_for_address(pc)
+ if objfile is not None:
+ newframe["moduleId"] = module_id(objfile)
+ if stack_format["module"]:
+ name += ", module " + objfile.username
filename = current_frame.filename()
if filename is not None:
newframe["source"] = make_source(filename, os.path.basename(filename))
+ newframe["name"] = name
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
@@ -72,11 +108,45 @@ def _backtrace(thread_id, levels, startFrame, value_format):
}
+# A helper function that checks the types of the elements of a
+# StackFrameFormat, and converts this to a dict where all the members
+# are set. This simplifies the implementation code a bit.
+@type_check
+def check_stack_frame(
+ *,
+ # Note that StackFrameFormat extends ValueFormat, which is why
+ # "hex" appears here.
+ hex: Optional[bool] = False,
+ parameters: Optional[bool] = False,
+ parameterTypes: Optional[bool] = False,
+ parameterNames: Optional[bool] = False,
+ parameterValues: Optional[bool] = False,
+ line: Optional[bool] = False,
+ module: Optional[bool] = False,
+ includeAll: Optional[bool] = False,
+ **rest
+):
+ return {
+ "hex": hex,
+ "parameters": parameters,
+ "parameterTypes": parameterTypes,
+ "parameterNames": parameterNames,
+ "parameterValues": parameterValues,
+ "line": line,
+ "module": module,
+ "includeAll": includeAll,
+ }
+
+
@request("stackTrace")
@capability("supportsDelayedStackTraceLoading")
def stacktrace(
*, levels: int = 0, startFrame: int = 0, threadId: int, format=None, **extra
):
+ # It's simpler if the format is always set.
+ if format is None:
+ format = {}
+ format = check_stack_frame(**format)
return send_gdb_with_response(
lambda: _backtrace(threadId, levels, startFrame, format)
)
diff --git a/gdb/python/lib/gdb/dap/frames.py b/gdb/python/lib/gdb/dap/frames.py
index 1d2d137..669e73f 100644
--- a/gdb/python/lib/gdb/dap/frames.py
+++ b/gdb/python/lib/gdb/dap/frames.py
@@ -14,6 +14,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gdb
+import itertools
+
+from gdb.frames import frame_iterator
from .startup import in_gdb_thread
@@ -24,11 +27,17 @@ from .startup import in_gdb_thread
_all_frames = []
+# Map from a global thread ID to a memoizing frame iterator.
+_iter_map = {}
+
+
# Clear all the frame IDs.
@in_gdb_thread
def _clear_frame_ids(evt):
global _all_frames
_all_frames = []
+ global _iter_map
+ _iter_map = {}
# Clear the frame ID map whenever the inferior runs.
@@ -36,18 +45,6 @@ gdb.events.cont.connect(_clear_frame_ids)
@in_gdb_thread
-def frame_id(frame):
- """Return the frame identifier for FRAME."""
- global _all_frames
- for i in range(0, len(_all_frames)):
- if _all_frames[i] == frame:
- return i
- result = len(_all_frames)
- _all_frames.append(frame)
- return result
-
-
-@in_gdb_thread
def frame_for_id(id):
"""Given a frame identifier ID, return the corresponding frame."""
global _all_frames
@@ -59,3 +56,95 @@ def select_frame(id):
"""Given a frame identifier ID, select the corresponding frame."""
frame = frame_for_id(id)
frame.inferior_frame().select()
+
+
+# A simple memoizing iterator. Note that this is not very robust.
+# For example, you can't start two copies of the iterator and then
+# alternate fetching items from each. Instead, it implements just
+# what is needed for the current callers.
+class _MemoizingIterator:
+ def __init__(self, iterator):
+ self.iterator = iterator
+ self.seen = []
+
+ def __iter__(self):
+ # First the memoized items.
+ for item in self.seen:
+ yield item
+ # Now memoize new items.
+ for item in self.iterator:
+ self.seen.append(item)
+ yield item
+
+
+# A generator that fetches frames and pairs them with a frame ID. It
+# yields tuples of the form (ID, ELIDED, FRAME), where ID is the
+# generated ID, ELIDED is a boolean indicating if the frame should be
+# elided, and FRAME is the frame itself. This approach lets us
+# memoize the result and assign consistent IDs, independent of how
+# "includeAll" is set in the request.
+@in_gdb_thread
+def _frame_id_generator():
+ try:
+ base_iterator = frame_iterator(gdb.newest_frame(), 0, -1)
+ except gdb.error:
+ base_iterator = ()
+
+ # Helper function to assign an ID to a frame.
+ def get_id(frame):
+ global _all_frames
+ num = len(_all_frames)
+ _all_frames.append(frame)
+ return num
+
+ def yield_frames(iterator, for_elided):
+ for frame in iterator:
+ # Unfortunately the frame filter docs don't describe
+ # whether the elided frames conceptually come before or
+ # after the eliding frame. Here we choose after.
+ yield (get_id(frame), for_elided, frame)
+
+ elided = frame.elided()
+ if elided is not None:
+ yield from yield_frames(frame.elided(), True)
+
+ yield from yield_frames(base_iterator, False)
+
+
+# Return the memoizing frame iterator for the selected thread.
+@in_gdb_thread
+def _get_frame_iterator():
+ thread_id = gdb.selected_thread().global_num
+ global _iter_map
+ if thread_id not in _iter_map:
+ _iter_map[thread_id] = _MemoizingIterator(_frame_id_generator())
+ return _iter_map[thread_id]
+
+
+# A helper function that creates an iterable that returns (ID, FRAME)
+# pairs. It uses the memoizing frame iterator, but also handles the
+# "includeAll" member of StackFrameFormat.
+@in_gdb_thread
+def dap_frame_generator(frame_low, levels, include_all):
+ """A generator that yields identifiers and frames.
+
+ Each element is a pair of the form (ID, FRAME).
+ ID is the internally-assigned frame ID.
+ FRAME is a FrameDecorator of some kind.
+
+ Arguments are as to the stackTrace request."""
+
+ base_iterator = _get_frame_iterator()
+
+ if not include_all:
+ base_iterator = itertools.filterfalse(lambda item: item[1], base_iterator)
+
+ if levels == 0:
+ # Zero means all remaining frames.
+ frame_high = None
+ else:
+ frame_high = frame_low + levels
+ base_iterator = itertools.islice(base_iterator, frame_low, frame_high)
+
+ for ident, _, frame in base_iterator:
+ yield (ident, frame)
diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py
index 13c3581..e526dfb 100644
--- a/gdb/python/lib/gdb/dap/scopes.py
+++ b/gdb/python/lib/gdb/dap/scopes.py
@@ -36,6 +36,29 @@ def clear_scopes(event):
gdb.events.cont.connect(clear_scopes)
+# A helper function to compute the value of a symbol. SYM is either a
+# gdb.Symbol, or an object implementing the SymValueWrapper interface.
+# FRAME is a frame wrapper, as produced by a frame filter. Returns a
+# tuple of the form (NAME, VALUE), where NAME is the symbol's name and
+# VALUE is a gdb.Value.
+@in_gdb_thread
+def symbol_value(sym, frame):
+ inf_frame = frame.inferior_frame()
+ # Make sure to select the frame first. Ideally this would not
+ # be needed, but this is a way to set the current language
+ # properly so that language-dependent APIs will work.
+ inf_frame.select()
+ 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(inf_frame)
+ elif not isinstance(val, gdb.Value):
+ val = gdb.Value(val)
+ return (name, val)
+
+
class _ScopeReference(BaseReference):
def __init__(self, name, hint, frame, var_list):
super().__init__(name)
@@ -67,21 +90,7 @@ class _ScopeReference(BaseReference):
@in_gdb_thread
def fetch_one_child(self, idx):
- # Make sure to select the frame first. Ideally this would not
- # be needed, but this is a way to set the current language
- # properly so that language-dependent APIs will work.
- self.inf_frame.select()
- # Here SYM will conform to the SymValueWrapper interface.
- sym = self.var_list[idx]
- 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)
+ return symbol_value(self.var_list[idx], self.frame)
class _RegisterReference(_ScopeReference):