diff options
Diffstat (limited to 'gdb/python/lib')
-rw-r--r-- | gdb/python/lib/gdb/dap/evaluate.py | 38 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/scopes.py | 61 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/varref.py | 178 | ||||
-rw-r--r-- | gdb/python/lib/gdb/printing.py | 66 |
4 files changed, 322 insertions, 21 deletions
diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py index f01bf0f..d04ac16 100644 --- a/gdb/python/lib/gdb/dap/evaluate.py +++ b/gdb/python/lib/gdb/dap/evaluate.py @@ -14,10 +14,17 @@ # 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. @@ -26,17 +33,32 @@ def _evaluate(expr, frame_id): 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} diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py index 490fa9c..9ab454a 100644 --- a/gdb/python/lib/gdb/dap/scopes.py +++ b/gdb/python/lib/gdb/dap/scopes.py @@ -18,6 +18,7 @@ import gdb 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. @@ -29,17 +30,53 @@ def _safe_block(frame): 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. @@ -49,14 +86,12 @@ def _get_scope(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") diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py new file mode 100644 index 0000000..888415a --- /dev/null +++ b/gdb/python/lib/gdb/dap/varref.py @@ -0,0 +1,178 @@ +# 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] diff --git a/gdb/python/lib/gdb/printing.py b/gdb/python/lib/gdb/printing.py index 1f724ee..5aca2bd 100644 --- a/gdb/python/lib/gdb/printing.py +++ b/gdb/python/lib/gdb/printing.py @@ -269,6 +269,72 @@ class FlagEnumerationPrinter(PrettyPrinter): 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. |