Current File : //kunden/usr/share/gdb/python/gdb/dap/varref.py |
# 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 .server import client_bool_capability
from abc import ABC, abstractmethod
from collections import defaultdict
from contextlib import contextmanager
# 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)
# A null context manager. Python supplies one, starting in 3.7.
@contextmanager
def _null(**ignore):
yield
@in_gdb_thread
def apply_format(value_format):
"""Temporarily apply the DAP ValueFormat.
This returns a new context manager that applies the given DAP
ValueFormat object globally, then restores gdb's state when finished."""
if value_format is not None and "hex" in value_format and value_format["hex"]:
return gdb.with_parameter("output-radix", 16)
return _null()
class BaseReference(ABC):
"""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.reset_children()
@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.has_children() else 0}
if self.name is not None:
result["name"] = str(self.name)
return result
@abstractmethod
def has_children(self):
"""Return True if this object has children."""
return False
def reset_children(self):
"""Reset any cached information about the children of this object."""
# A list of all the children. Each child is a BaseReference
# of some kind.
self.children = None
# Map from the name of a child to a BaseReference.
self.by_name = {}
# Keep track of how many duplicates there are of a given name,
# so that unique names can be generated. Map from base name
# to a count.
self.name_counts = defaultdict(lambda: 1)
@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
# Helper method to compute the final name for a child whose base
# name is given. Updates the name_counts map. This is used to
# handle shadowing -- in DAP, the adapter is responsible for
# making sure that all the variables in a a given container have
# unique names. See
# https://github.com/microsoft/debug-adapter-protocol/issues/141
# and
# https://github.com/microsoft/debug-adapter-protocol/issues/149
def _compute_name(self, name):
if name in self.by_name:
self.name_counts[name] += 1
# In theory there's no safe way to compute a name, because
# a pretty-printer might already be generating names of
# that form. In practice I think we should not worry too
# much.
name = name + " #" + str(self.name_counts[name])
return name
@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.
Returns an iterable of some kind."""
if count == 0:
count = self.child_count()
if self.children is None:
self.children = [None] * self.child_count()
for idx in range(start, start + count):
if self.children[idx] is None:
(name, value) = self.fetch_one_child(idx)
name = self._compute_name(name)
var = VariableReference(name, value)
self.children[idx] = var
self.by_name[name] = var
yield self.children[idx]
@in_gdb_thread
def find_child_by_name(self, name):
"""Find a child of this variable, given its name.
Returns the value of the child, or throws if not found."""
# A lookup by name can only be done using names previously
# provided to the client, so we can simply rely on the by-name
# map here.
if name in self.by_name:
return self.by_name[name]
raise Exception("no variable named '" + name + "'")
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.result_name = result_name
self.value = value
self._update_value()
# Internal method to update local data when the value changes.
def _update_value(self):
self.reset_children()
self.printer = gdb.printing.make_visualizer(self.value)
self.child_cache = None
if self.has_children():
self.count = -1
else:
self.count = None
def assign(self, value):
"""Assign VALUE to this object and update."""
self.value.assign(value)
self._update_value()
def has_children(self):
return hasattr(self.printer, "children")
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. Note that
# this is only needed with legacy (non-ValuePrinter)
# printers.
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:
num_children = None
if isinstance(self.printer, gdb.ValuePrinter) and hasattr(
self.printer, "num_children"
):
num_children = self.printer.num_children()
if num_children is None:
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] = str(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
if client_bool_capability("supportsMemoryReferences"):
# https://github.com/microsoft/debug-adapter-protocol/issues/414
# changed DAP to allow memory references for any of the
# variable response requests, and to lift the restriction
# to pointer-to-function from Variable.
if self.value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR:
result["memoryReference"] = hex(int(self.value))
if client_bool_capability("supportsVariableType"):
result["type"] = str(self.value.type)
return result
@in_gdb_thread
def fetch_one_child(self, idx):
if isinstance(self.printer, gdb.ValuePrinter) and hasattr(
self.printer, "child"
):
(name, val) = self.printer.child(idx)
else:
(name, val) = self.cache_children()[idx]
# A pretty-printer can return something other than a
# gdb.Value, but it must be convertible.
if not isinstance(val, gdb.Value):
val = gdb.Value(val)
return (name, val)
@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]