Reimplement DAP stack traces using frame filters

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
This commit is contained in:
Tom Tromey
2023-06-14 06:09:23 -06:00
parent f921fe9318
commit 5b86f10883
5 changed files with 79 additions and 102 deletions

View File

@ -15,67 +15,56 @@
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.

View 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,

View 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()

View 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)
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):
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]