gdb/dap/startup.py \
gdb/dap/state.py \
gdb/dap/threads.py \
+ gdb/dap/varref.py \
gdb/function/__init__.py \
gdb/function/as_string.py \
gdb/function/caller_is.py \
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gdb
+import gdb.printing
from .frames import frame_for_id
from .server import request
from .startup import send_gdb_with_response, in_gdb_thread
+from .varref import find_variable, VariableReference
+
+
+class EvaluateResult(VariableReference):
+ def __init__(self, value):
+ super().__init__(None, value, "result")
# Helper function to evaluate an expression in a certain frame.
if frame_id is not None:
frame = frame_for_id(frame_id)
frame.select()
- return str(gdb.parse_and_eval(expr))
+ val = gdb.parse_and_eval(expr)
+ ref = EvaluateResult(val)
+ return ref.to_object()
# FIXME 'format' & hex
-# FIXME return a structured response using pretty-printers / varobj
# FIXME supportsVariableType handling
+# FIXME "repl"
@request("evaluate")
def eval_request(*, expression, frameId=None, **args):
- result = send_gdb_with_response(lambda: _evaluate(expression, frameId))
- return {
- "result": result,
- # FIXME
- "variablesReference": -1,
- }
+ return send_gdb_with_response(lambda: _evaluate(expression, frameId))
+
+
+@in_gdb_thread
+def _variables(ref, start, count):
+ var = find_variable(ref)
+ children = var.fetch_children(start, count)
+ return [x.to_object() for x in children]
+
+
+@request("variables")
+# Note that we ignore the 'filter' field. That seems to be
+# specific to javascript.
+# FIXME: implement format
+def variables(*, variablesReference, start=0, count=0, **args):
+ result = send_gdb_with_response(
+ lambda: _variables(variablesReference, start, count)
+ )
+ return {"variables": result}
from .frames import frame_for_id
from .startup import send_gdb_with_response, in_gdb_thread
from .server import request
+from .varref import BaseReference
# Helper function to return a frame's block without error.
return None
-# Helper function to return a list of variables of block, up to the
-# enclosing function.
+# 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):
- result = []
+ args = []
+ locs = []
while True:
- result += list(block)
+ for var in block:
+ if var.is_argument:
+ args.append(var)
+ else:
+ locs.append(var)
if block.function is not None:
break
block = block.superblock
- return result
+ return (args, locs)
+
+
+class ScopeReference(BaseReference):
+ def __init__(self, name, frame, var_list):
+ super().__init__(name)
+ self.frame = frame
+ self.func = frame.function()
+ self.var_list = var_list
+
+ def to_object(self):
+ result = super().to_object()
+ # How would we know?
+ result["expensive"] = False
+ result["namedVariables"] = len(self.var_list)
+ if self.func is not None:
+ result["line"] = self.func.line
+ # FIXME construct a Source object
+ return result
+
+ def child_count(self):
+ return len(self.var_list)
+
+ @in_gdb_thread
+ def fetch_one_child(self, idx):
+ sym = self.var_list[idx]
+ if sym.needs_frame:
+ val = sym.value(self.frame)
+ else:
+ val = sym.value()
+ return (sym.print_name, val)
# Helper function to create a DAP scopes for a given frame ID.
block = _safe_block(frame)
scopes = []
if block is not None:
- new_scope = {
- # FIXME
- "name": "Locals",
- "expensive": False,
- "namedVariables": len(_block_vars(block)),
- }
- scopes.append(new_scope)
- return scopes
+ (args, locs) = _block_vars(block)
+ if args:
+ scopes.append(ScopeReference("Arguments", frame, args))
+ if locs:
+ scopes.append(ScopeReference("Locals", frame, locs))
+ return [x.to_object() for x in scopes]
@request("scopes")
--- /dev/null
+# Copyright 2023 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 .startup import in_gdb_thread
+from abc import abstractmethod
+
+
+# A list of all the variable references created during this pause.
+all_variables = []
+
+
+# When the inferior is re-started, we erase all variable references.
+# See the section "Lifetime of Objects References" in the spec.
+@in_gdb_thread
+def clear_vars(event):
+ global all_variables
+ all_variables = []
+
+
+gdb.events.cont.connect(clear_vars)
+
+
+class BaseReference:
+ """Represent a variable or a scope.
+
+ This class is just a base class, some methods must be implemented in
+ subclasses.
+
+ The 'ref' field can be used as the variablesReference in the protocol.
+ """
+
+ @in_gdb_thread
+ def __init__(self, name):
+ """Create a new variable reference with the given name.
+
+ NAME is a string or None. None means this does not have a
+ name, e.g., the result of expression evaluation."""
+
+ global all_variables
+ all_variables.append(self)
+ self.ref = len(all_variables)
+ self.name = name
+ self.children = None
+
+ @in_gdb_thread
+ def to_object(self):
+ """Return a dictionary that describes this object for DAP.
+
+ The resulting object is a starting point that can be filled in
+ further. See the Scope or Variable types in the spec"""
+ result = {
+ "variablesReference": self.ref,
+ }
+ if self.name is not None:
+ result["name"] = self.name
+ return result
+
+ def no_children(self):
+ """Call this to declare that this variable or scope has no children."""
+ self.ref = 0
+
+ @abstractmethod
+ def fetch_one_child(self, index):
+ """Fetch one child of this variable.
+
+ INDEX is the index of the child to fetch.
+ This should return a tuple of the form (NAME, VALUE), where
+ NAME is the name of the variable, and VALUE is a gdb.Value."""
+ return
+
+ @abstractmethod
+ def child_count(self):
+ """Return the number of children of this variable."""
+ return
+
+ @in_gdb_thread
+ def fetch_children(self, start, count):
+ """Fetch children of this variable.
+
+ START is the starting index.
+ COUNT is the number to return, with 0 meaning return all."""
+ if count == 0:
+ count = self.child_count()
+ if self.children is None:
+ self.children = [None] * self.child_count()
+ result = []
+ for idx in range(start, start + count):
+ if self.children[idx] is None:
+ (name, value) = self.fetch_one_child(idx)
+ self.children[idx] = VariableReference(name, value)
+ result.append(self.children[idx])
+ return result
+
+
+class VariableReference(BaseReference):
+ """Concrete subclass of BaseReference that handles gdb.Value."""
+
+ def __init__(self, name, value, result_name="value"):
+ """Initializer.
+
+ NAME is the name of this reference, see superclass.
+ VALUE is a gdb.Value that holds the value.
+ RESULT_NAME can be used to change how the simple string result
+ is emitted in the result dictionary."""
+ super().__init__(name)
+ self.printer = gdb.printing.make_visualizer(value)
+ self.result_name = result_name
+ # We cache all the children we create.
+ self.child_cache = None
+ if not hasattr(self.printer, "children"):
+ self.no_children()
+ self.count = None
+ else:
+ self.count = -1
+
+ def cache_children(self):
+ if self.child_cache is None:
+ # This discards all laziness. This could be improved
+ # slightly by lazily evaluating children, but because this
+ # code also generally needs to know the number of
+ # children, it probably wouldn't help much. A real fix
+ # would require an update to gdb's pretty-printer protocol
+ # (though of course that is probably also inadvisable).
+ self.child_cache = list(self.printer.children())
+ return self.child_cache
+
+ def child_count(self):
+ if self.count is None:
+ return None
+ if self.count == -1:
+ if hasattr(self.printer, "num_children"):
+ num_children = self.printer.num_children
+ else:
+ num_children = len(self.cache_children())
+ self.count = num_children
+ return self.count
+
+ def to_object(self):
+ result = super().to_object()
+ result[self.result_name] = self.printer.to_string()
+ num_children = self.child_count()
+ if num_children is not None:
+ if (
+ hasattr(self.printer, "display_hint")
+ and self.printer.display_hint() == "array"
+ ):
+ result["indexedVariables"] = num_children
+ else:
+ result["namedVariables"] = num_children
+ return result
+
+ @in_gdb_thread
+ def fetch_one_child(self, idx):
+ return self.cache_children()[idx]
+
+
+@in_gdb_thread
+def find_variable(ref):
+ """Given a variable reference, return the corresponding variable object."""
+ global all_variables
+ # Variable references are offset by 1.
+ ref = ref - 1
+ if ref < 0 or ref > len(all_variables):
+ raise Exception("invalid variablesReference")
+ return all_variables[ref]
return None
+class NoOpScalarPrinter:
+ """A no-op pretty printer that wraps a scalar value."""
+ def __init__(self, value):
+ self.value = value
+
+ def to_string(self):
+ return self.value.format_string(raw=True)
+
+
+class NoOpArrayPrinter:
+ """A no-op pretty printer that wraps an array value."""
+ def __init__(self, value):
+ self.value = value
+ (low, high) = self.value.type.range()
+ self.low = low
+ self.high = high
+ # This is a convenience to the DAP code and perhaps other
+ # users.
+ self.num_children = high - low + 1
+
+ def to_string(self):
+ return ""
+
+ def display_hint(self):
+ return "array"
+
+ def children(self):
+ for i in range(self.low, self.high):
+ yield (i, self.value[i])
+
+
+class NoOpStructPrinter:
+ """A no-op pretty printer that wraps a struct or union value."""
+ def __init__(self, value):
+ self.value = value
+
+ def to_string(self):
+ return ""
+
+ def children(self):
+ for field in self.value.type.fields():
+ if field.name is not None:
+ yield (field.name, self.value[field])
+
+
+def make_visualizer(value):
+ """Given a gdb.Value, wrap it in a pretty-printer.
+
+ If a pretty-printer is found by the usual means, it is returned.
+ Otherwise, VALUE will be wrapped in a no-op visualizer."""
+
+ result = gdb.default_visualizer(value)
+ if result is not None:
+ # Found a pretty-printer.
+ pass
+ elif value.type.code == gdb.TYPE_CODE_ARRAY:
+ result = gdb.printing.NoOpArrayPrinter(value)
+ (low, high) = value.type.range()
+ result.n_children = high - low + 1
+ elif value.type.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION):
+ result = gdb.printing.NoOpStructPrinter(value)
+ else:
+ result = gdb.printing.NoOpScalarPrinter(value)
+ return result
+
+
# Builtin pretty-printers.
# The set is defined as empty, and files in printing/*.py add their printers
# to this with add_builtin_pretty_printer.
--- /dev/null
+/* Copyright 2023 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+struct dei_struct
+{
+ int x;
+ int more[5];
+};
+
+int main ()
+{
+ struct dei_struct dei = { 2, { 3, 5, 7, 11, 13 } };
+
+ static int scalar = 23;
+
+ {
+ const char *inner = "inner block";
+
+ return 0; /* BREAK */
+ }
+}
--- /dev/null
+# Copyright 2023 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/>.
+
+# Test "scopes" and "variables".
+
+require allow_python_tests
+
+load_lib dap-support.exp
+
+standard_testfile
+
+if {[build_executable ${testfile}.exp $testfile] == -1} {
+ return
+}
+
+if {[dap_launch $testfile] == ""} {
+ return
+}
+
+set line [gdb_get_line_number "BREAK"]
+set obj [dap_check_request_and_response "set breakpoint by line number" \
+ setBreakpoints \
+ [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
+ [list s $srcfile] $line]]
+set line_bpno [dap_get_breakpoint_number $obj]
+
+dap_check_request_and_response "start inferior" configurationDone
+dap_wait_for_event_and_check "inferior started" thread "body reason" started
+
+dap_wait_for_event_and_check "stopped at line breakpoint" stopped \
+ "body reason" breakpoint \
+ "body hitBreakpointIds" $line_bpno
+
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+ {o threadId [i 1]}] \
+ 0]
+set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
+
+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] == 1} "single scope"
+
+set scope [lindex $scopes 0]
+gdb_assert {[dict get $scope name] == "Locals"} "scope is locals"
+gdb_assert {[dict get $scope namedVariables] == 3} "three vars in scope"
+
+set num [dict get $scope variablesReference]
+set refs [lindex [dap_check_request_and_response "fetch variables" \
+ "variables" \
+ [format {o variablesReference [i %d] count [i 3]} \
+ $num]] \
+ 0]
+
+foreach var [dict get $refs body variables] {
+ set name [dict get $var name]
+
+ if {$name != "dei"} {
+ gdb_assert {[dict get $var variablesReference] == 0} \
+ "$name has no structure"
+ }
+
+ switch $name {
+ "inner" {
+ gdb_assert {[string match "*inner block*" [dict get $var value]]} \
+ "check value of inner"
+ }
+ "dei" {
+ gdb_assert {[dict get $var value] == ""} "check value of dei"
+ set dei_ref [dict get $var variablesReference]
+ }
+ "scalar" {
+ gdb_assert {[dict get $var value] == 23} "check value of scalar"
+ }
+ default {
+ fail "unknown variable $name"
+ }
+ }
+}
+
+set refs [lindex [dap_check_request_and_response "fetch contents of dei" \
+ "variables" \
+ [format {o variablesReference [i %d]} $dei_ref]] \
+ 0]
+set deivals [dict get $refs body variables]
+gdb_assert {[llength $deivals] == 2} "dei has two members"
+
+dap_shutdown