debuginfo: Create common debugger pretty printer module.

GDB and LLDB pretty printers have some common functionality
and also access some common information, such as the layout of
standard library types. So far, this information has been
duplicated in the two pretty printing python modules. This
commit introduces a common module used by both debuggers.
This commit is contained in:
Michael Woerister 2015-05-09 16:48:36 +02:00
parent 474c6e0ae4
commit d136714e04
6 changed files with 742 additions and 426 deletions

View file

@ -15,7 +15,8 @@
## GDB ##
DEBUGGER_RUSTLIB_ETC_SCRIPTS_GDB=gdb_load_rust_pretty_printers.py \
gdb_rust_pretty_printing.py
gdb_rust_pretty_printing.py \
debugger_pretty_printers_common.py
DEBUGGER_RUSTLIB_ETC_SCRIPTS_GDB_ABS=\
$(foreach script,$(DEBUGGER_RUSTLIB_ETC_SCRIPTS_GDB), \
$(CFG_SRC_DIR)src/etc/$(script))
@ -27,7 +28,8 @@ DEBUGGER_BIN_SCRIPTS_GDB_ABS=\
## LLDB ##
DEBUGGER_RUSTLIB_ETC_SCRIPTS_LLDB=lldb_rust_formatters.py
DEBUGGER_RUSTLIB_ETC_SCRIPTS_LLDB=lldb_rust_formatters.py \
debugger_pretty_printers_common.py
DEBUGGER_RUSTLIB_ETC_SCRIPTS_LLDB_ABS=\
$(foreach script,$(DEBUGGER_RUSTLIB_ETC_SCRIPTS_LLDB), \
$(CFG_SRC_DIR)src/etc/$(script))

View file

@ -0,0 +1,328 @@
# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution and at
# http://rust-lang.org/COPYRIGHT.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
"""
This module provides an abstraction layer over common Rust pretty printing
functionality needed by both GDB and LLDB.
"""
import re
# Type codes that indicate the kind of type as it appears in DWARF debug
# information. This code alone is not sufficient to determine the Rust type.
# For example structs, tuples, fat pointers, or enum variants will all have
# DWARF_TYPE_CODE_STRUCT.
DWARF_TYPE_CODE_STRUCT = 1
DWARF_TYPE_CODE_UNION = 2
DWARF_TYPE_CODE_PTR = 3
DWARF_TYPE_CODE_ARRAY = 4
DWARF_TYPE_CODE_ENUM = 5
# These constants specify the most specific kind of type that could be
# determined for a given value.
TYPE_KIND_UNKNOWN = -1
TYPE_KIND_EMPTY = 0
TYPE_KIND_SLICE = 1
TYPE_KIND_REGULAR_STRUCT = 2
TYPE_KIND_TUPLE = 3
TYPE_KIND_TUPLE_STRUCT = 4
TYPE_KIND_CSTYLE_VARIANT = 5
TYPE_KIND_TUPLE_VARIANT = 6
TYPE_KIND_STRUCT_VARIANT = 7
TYPE_KIND_STR_SLICE = 8
TYPE_KIND_STD_VEC = 9
TYPE_KIND_STD_STRING = 10
TYPE_KIND_REGULAR_ENUM = 11
TYPE_KIND_COMPRESSED_ENUM = 12
TYPE_KIND_SINGLETON_ENUM = 13
TYPE_KIND_CSTYLE_ENUM = 14
TYPE_KIND_PTR = 15
TYPE_KIND_FIXED_SIZE_VEC = 16
ENCODED_ENUM_PREFIX = "RUST$ENCODED$ENUM$"
ENUM_DISR_FIELD_NAME = "RUST$ENUM$DISR"
# Slice related constants
SLICE_FIELD_NAME_DATA_PTR = "data_ptr"
SLICE_FIELD_NAME_LENGTH = "length"
SLICE_FIELD_NAMES = [SLICE_FIELD_NAME_DATA_PTR, SLICE_FIELD_NAME_LENGTH]
# std::Vec<> related constants
STD_VEC_FIELD_NAME_DATA_PTR = "ptr"
STD_VEC_FIELD_NAME_LENGTH = "len"
STD_VEC_FIELD_NAME_CAPACITY = "cap"
STD_VEC_FIELD_NAMES = [STD_VEC_FIELD_NAME_DATA_PTR,
STD_VEC_FIELD_NAME_LENGTH,
STD_VEC_FIELD_NAME_CAPACITY]
# std::String related constants
STD_STRING_FIELD_NAMES = ["vec"]
class Type(object):
"""
This class provides a common interface for type-oriented operations.
Sub-classes are supposed to wrap a debugger-specific type-object and
provide implementations for the abstract methods in this class.
"""
def __init__(self):
self.__type_kind = None
def get_unqualified_type_name(self):
"""
Implementations of this method should return the unqualified name of the
type-object they are wrapping. Some examples:
'int' -> 'int'
'std::vec::Vec<std::string::String>' -> 'Vec<std::string::String>'
'&std::option::Option<std::string::String>' -> '&std::option::Option<std::string::String>'
As you can see, type arguments stay fully qualified.
"""
raise NotImplementedError("Override this method")
def get_dwarf_type_kind(self):
"""
Implementations of this method should return the correct
DWARF_TYPE_CODE_* value for the wrapped type-object.
"""
raise NotImplementedError("Override this method")
def get_fields(self):
"""
Implementations of this method should return a list of field-objects of
this type. For Rust-enums (i.e. with DWARF_TYPE_CODE_UNION) these field-
objects represent the variants of the enum. Field-objects must have a
`name` attribute that gives their name as specified in DWARF.
"""
assert ((self.get_dwarf_type_kind() == DWARF_TYPE_CODE_STRUCT) or
(self.get_dwarf_type_kind() == DWARF_TYPE_CODE_UNION))
raise NotImplementedError("Override this method")
def get_wrapped_value(self):
"""
Returns the debugger-specific type-object wrapped by this object. This
is sometimes needed for doing things like pointer-arithmetic in GDB.
"""
raise NotImplementedError("Override this method")
def get_type_kind(self):
"""This method returns the TYPE_KIND_* value for this type-object."""
if self.__type_kind is None:
dwarf_type_code = self.get_dwarf_type_kind()
if dwarf_type_code == DWARF_TYPE_CODE_STRUCT:
self.__type_kind = self.__classify_struct()
elif dwarf_type_code == DWARF_TYPE_CODE_UNION:
self.__type_kind = self.__classify_union()
elif dwarf_type_code == DWARF_TYPE_CODE_PTR:
self.__type_kind = TYPE_KIND_PTR
elif dwarf_type_code == DWARF_TYPE_CODE_ARRAY:
self.__type_kind = TYPE_KIND_FIXED_SIZE_VEC
else:
self.__type_kind = TYPE_KIND_UNKNOWN
return self.__type_kind
def __classify_struct(self):
assert self.get_dwarf_type_kind() == DWARF_TYPE_CODE_STRUCT
unqualified_type_name = self.get_unqualified_type_name()
# STR SLICE
if unqualified_type_name == "&str":
return TYPE_KIND_STR_SLICE
# REGULAR SLICE
if (unqualified_type_name.startswith("&[") and
unqualified_type_name.endswith("]") and
self.__conforms_to_field_layout(SLICE_FIELD_NAMES)):
return TYPE_KIND_SLICE
fields = self.get_fields()
field_count = len(fields)
# EMPTY STRUCT
if field_count == 0:
return TYPE_KIND_EMPTY
# STD VEC
if (unqualified_type_name.startswith("Vec<") and
self.__conforms_to_field_layout(STD_VEC_FIELD_NAMES)):
return TYPE_KIND_STD_VEC
# STD STRING
if (unqualified_type_name.startswith("String") and
self.__conforms_to_field_layout(STD_STRING_FIELD_NAMES)):
return TYPE_KIND_STD_STRING
# ENUM VARIANTS
if fields[0].name == ENUM_DISR_FIELD_NAME:
if field_count == 1:
return TYPE_KIND_CSTYLE_VARIANT
elif self.__all_fields_conform_to_tuple_field_naming(1):
return TYPE_KIND_TUPLE_VARIANT
else:
return TYPE_KIND_STRUCT_VARIANT
# TUPLE
if self.__all_fields_conform_to_tuple_field_naming(0):
if unqualified_type_name.startswith("("):
return TYPE_KIND_TUPLE
else:
return TYPE_KIND_TUPLE_STRUCT
# REGULAR STRUCT
return TYPE_KIND_REGULAR_STRUCT
def __classify_union(self):
assert self.get_dwarf_type_kind() == DWARF_TYPE_CODE_UNION
union_members = self.get_fields()
union_member_count = len(union_members)
if union_member_count == 0:
return TYPE_KIND_EMPTY
elif union_member_count == 1:
first_variant_name = union_members[0].name
if first_variant_name is None:
return TYPE_KIND_SINGLETON_ENUM
else:
assert first_variant_name.startswith(ENCODED_ENUM_PREFIX)
return TYPE_KIND_COMPRESSED_ENUM
else:
return TYPE_KIND_REGULAR_ENUM
def __conforms_to_field_layout(self, expected_fields):
actual_fields = self.get_fields()
actual_field_count = len(actual_fields)
if actual_field_count != len(expected_fields):
return False
for i in range(0, actual_field_count):
if actual_fields[i].name != expected_fields[i]:
return False
return True
def __all_fields_conform_to_tuple_field_naming(self, start_index):
fields = self.get_fields()
field_count = len(fields)
for i in range(start_index, field_count):
field_name = fields[i].name
if (field_name is None) or (re.match(r"__\d+$", field_name) is None):
return False
return True
class Value(object):
"""
This class provides a common interface for value-oriented operations.
Sub-classes are supposed to wrap a debugger-specific value-object and
provide implementations for the abstract methods in this class.
"""
def __init__(self, ty):
self.type = ty
def get_child_at_index(self, index):
"""Returns the value of the field, array element or variant at the given index"""
raise NotImplementedError("Override this method")
def as_integer(self):
"""
Try to convert the wrapped value into a Python integer. This should
always succeed for values that are pointers or actual integers.
"""
raise NotImplementedError("Override this method")
def get_wrapped_value(self):
"""
Returns the debugger-specific value-object wrapped by this object. This
is sometimes needed for doing things like pointer-arithmetic in GDB.
"""
raise NotImplementedError("Override this method")
class EncodedEnumInfo(object):
"""
This class provides facilities for handling enum values with compressed
encoding where a non-null field in one variant doubles as the discriminant.
"""
def __init__(self, enum_val):
assert enum_val.type.get_type_kind() == TYPE_KIND_COMPRESSED_ENUM
variant_name = enum_val.type.get_fields()[0].name
last_separator_index = variant_name.rfind("$")
start_index = len(ENCODED_ENUM_PREFIX)
indices_substring = variant_name[start_index:last_separator_index].split("$")
self.__enum_val = enum_val
self.__disr_field_indices = [int(index) for index in indices_substring]
self.__null_variant_name = variant_name[last_separator_index + 1:]
def is_null_variant(self):
ty = self.__enum_val.type
sole_variant_val = self.__enum_val.get_child_at_index(0)
discriminant_val = sole_variant_val
for disr_field_index in self.__disr_field_indices:
discriminant_val = discriminant_val.get_child_at_index(disr_field_index)
# If the discriminant field is a fat pointer we have to consider the
# first word as the true discriminant
if discriminant_val.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_STRUCT:
discriminant_val = discriminant_val.get_child_at_index(0)
return discriminant_val.as_integer() == 0
def get_non_null_variant_val(self):
return self.__enum_val.get_child_at_index(0)
def get_null_variant_name(self):
return self.__null_variant_name
def get_discriminant_value_as_integer(enum_val):
assert enum_val.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_UNION
# we can take any variant here because the discriminant has to be the same
# for all of them.
variant_val = enum_val.get_child_at_index(0)
disr_val = variant_val.get_child_at_index(0)
return disr_val.as_integer()
def extract_length_ptr_and_cap_from_std_vec(vec_val):
assert vec_val.type.get_type_kind() == TYPE_KIND_STD_VEC
length_field_index = STD_VEC_FIELD_NAMES.index(STD_VEC_FIELD_NAME_LENGTH)
ptr_field_index = STD_VEC_FIELD_NAMES.index(STD_VEC_FIELD_NAME_DATA_PTR)
cap_field_index = STD_VEC_FIELD_NAMES.index(STD_VEC_FIELD_NAME_CAPACITY)
length = vec_val.get_child_at_index(length_field_index).as_integer()
vec_ptr_val = vec_val.get_child_at_index(ptr_field_index)
capacity = vec_val.get_child_at_index(cap_field_index).as_integer()
unique_ptr_val = vec_ptr_val.get_child_at_index(0)
data_ptr = unique_ptr_val.get_child_at_index(0)
assert data_ptr.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_PTR
return (length, data_ptr, capacity)
def extract_length_and_ptr_from_slice(slice_val):
assert (slice_val.type.get_type_kind() == TYPE_KIND_SLICE or
slice_val.type.get_type_kind() == TYPE_KIND_STR_SLICE)
length_field_index = SLICE_FIELD_NAMES.index(SLICE_FIELD_NAME_LENGTH)
ptr_field_index = SLICE_FIELD_NAMES.index(SLICE_FIELD_NAME_DATA_PTR)
length = slice_val.get_child_at_index(length_field_index).as_integer()
data_ptr = slice_val.get_child_at_index(ptr_field_index)
assert data_ptr.type.get_dwarf_type_kind() == DWARF_TYPE_CODE_PTR
return (length, data_ptr)

View file

@ -10,246 +10,265 @@
import gdb
import re
import debugger_pretty_printers_common as rustpp
#===============================================================================
# GDB Pretty Printing Module for Rust
#===============================================================================
class GdbType(rustpp.Type):
def __init__(self, ty):
super(GdbType, self).__init__()
self.ty = ty
self.fields = None
def get_unqualified_type_name(self):
tag = self.ty.tag
if tag is None:
return tag
return tag.replace("&'static ", "&")
def get_dwarf_type_kind(self):
if self.ty.code == gdb.TYPE_CODE_STRUCT:
return rustpp.DWARF_TYPE_CODE_STRUCT
if self.ty.code == gdb.TYPE_CODE_UNION:
return rustpp.DWARF_TYPE_CODE_UNION
if self.ty.code == gdb.TYPE_CODE_PTR:
return rustpp.DWARF_TYPE_CODE_PTR
if self.ty.code == gdb.TYPE_CODE_ENUM:
return rustpp.DWARF_TYPE_CODE_ENUM
def get_fields(self):
assert ((self.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_STRUCT) or
(self.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_UNION))
if self.fields is None:
self.fields = list(self.ty.fields())
return self.fields
def get_wrapped_value(self):
return self.ty
class GdbValue(rustpp.Value):
def __init__(self, gdb_val):
super(GdbValue, self).__init__(GdbType(gdb_val.type))
self.gdb_val = gdb_val
self.children = {}
def get_child_at_index(self, index):
child = self.children.get(index)
if child is None:
gdb_field = get_field_at_index(self.gdb_val, index)
child = GdbValue(self.gdb_val[gdb_field])
self.children[index] = child
return child
def as_integer(self):
return int(self.gdb_val)
def get_wrapped_value(self):
return self.gdb_val
def register_printers(objfile):
"Registers Rust pretty printers for the given objfile"
"""Registers Rust pretty printers for the given objfile"""
objfile.pretty_printers.append(rust_pretty_printer_lookup_function)
def rust_pretty_printer_lookup_function(val):
"Returns the correct Rust pretty printer for the given value if there is one"
type_code = val.type.code
def rust_pretty_printer_lookup_function(gdb_val):
"""
Returns the correct Rust pretty printer for the given value
if there is one
"""
if type_code == gdb.TYPE_CODE_STRUCT:
struct_kind = classify_struct(val.type)
val = GdbValue(gdb_val)
type_kind = val.type.get_type_kind()
if struct_kind == STRUCT_KIND_SLICE:
return RustSlicePrinter(val)
if (type_kind == rustpp.TYPE_KIND_REGULAR_STRUCT or
type_kind == rustpp.TYPE_KIND_EMPTY):
return RustStructPrinter(val,
omit_first_field = False,
omit_type_name = False,
is_tuple_like = False)
if struct_kind == STRUCT_KIND_STR_SLICE:
return RustStringSlicePrinter(val)
if type_kind == rustpp.TYPE_KIND_STRUCT_VARIANT:
return RustStructPrinter(val,
omit_first_field = True,
omit_type_name = False,
is_tuple_like = False)
if struct_kind == STRUCT_KIND_STD_VEC:
return RustStdVecPrinter(val)
if type_kind == rustpp.TYPE_KIND_SLICE:
return RustSlicePrinter(val)
if struct_kind == STRUCT_KIND_STD_STRING:
return RustStdStringPrinter(val)
if type_kind == rustpp.TYPE_KIND_STR_SLICE:
return RustStringSlicePrinter(val)
if struct_kind == STRUCT_KIND_TUPLE:
return RustTuplePrinter(val)
if type_kind == rustpp.TYPE_KIND_STD_VEC:
return RustStdVecPrinter(val)
if struct_kind == STRUCT_KIND_TUPLE_STRUCT:
return RustTupleStructPrinter(val, False)
if type_kind == rustpp.TYPE_KIND_STD_STRING:
return RustStdStringPrinter(val)
if struct_kind == STRUCT_KIND_CSTYLE_VARIANT:
return RustCStyleEnumPrinter(val[get_field_at_index(val, 0)])
if type_kind == rustpp.TYPE_KIND_TUPLE:
return RustStructPrinter(val,
omit_first_field = False,
omit_type_name = True,
is_tuple_like = True)
if struct_kind == STRUCT_KIND_TUPLE_VARIANT:
return RustTupleStructPrinter(val, True)
if type_kind == rustpp.TYPE_KIND_TUPLE_STRUCT:
return RustStructPrinter(val,
omit_first_field = False,
omit_type_name = False,
is_tuple_like = True)
if struct_kind == STRUCT_KIND_STRUCT_VARIANT:
return RustStructPrinter(val, True)
if type_kind == rustpp.TYPE_KIND_CSTYLE_VARIANT:
return RustCStyleVariantPrinter(val.get_child_at_index(0))
return RustStructPrinter(val, False)
if type_kind == rustpp.TYPE_KIND_TUPLE_VARIANT:
return RustStructPrinter(val,
omit_first_field = True,
omit_type_name = False,
is_tuple_like = True)
# Enum handling
if type_code == gdb.TYPE_CODE_UNION:
enum_members = list(val.type.fields())
enum_member_count = len(enum_members)
if enum_member_count == 0:
return RustStructPrinter(val, False)
if enum_member_count == 1:
first_variant_name = enum_members[0].name
if first_variant_name is None:
# This is a singleton enum
return rust_pretty_printer_lookup_function(val[enum_members[0]])
else:
assert first_variant_name.startswith("RUST$ENCODED$ENUM$")
# This is a space-optimized enum.
# This means this enum has only two states, and Rust uses one
# of the fields somewhere in the struct to determine which of
# the two states it's in. The location of the field is encoded
# in the name as something like
# RUST$ENCODED$ENUM$(num$)*name_of_zero_state
last_separator_index = first_variant_name.rfind("$")
start_index = len("RUST$ENCODED$ENUM$")
disr_field_indices = first_variant_name[start_index:last_separator_index].split("$")
disr_field_indices = [int(index) for index in disr_field_indices]
sole_variant_val = val[enum_members[0]]
discriminant = sole_variant_val
for disr_field_index in disr_field_indices:
disr_field = get_field_at_index(discriminant, disr_field_index)
discriminant = discriminant[disr_field]
# If the discriminant field is a fat pointer we have to consider the
# first word as the true discriminant
if discriminant.type.code == gdb.TYPE_CODE_STRUCT:
discriminant = discriminant[get_field_at_index(discriminant, 0)]
if discriminant == 0:
null_variant_name = first_variant_name[last_separator_index + 1:]
return IdentityPrinter(null_variant_name)
return rust_pretty_printer_lookup_function(sole_variant_val)
if type_kind == rustpp.TYPE_KIND_SINGLETON_ENUM:
variant = get_field_at_index(gdb_val, 0)
return rust_pretty_printer_lookup_function(gdb_val[variant])
if type_kind == rustpp.TYPE_KIND_REGULAR_ENUM:
# This is a regular enum, extract the discriminant
discriminant_name, discriminant_val = extract_discriminant_value(val)
return rust_pretty_printer_lookup_function(val[enum_members[discriminant_val]])
discriminant_val = rustpp.get_discriminant_value_as_integer(val)
variant = get_field_at_index(gdb_val, discriminant_val)
return rust_pretty_printer_lookup_function(gdb_val[variant])
if type_kind == rustpp.TYPE_KIND_COMPRESSED_ENUM:
encoded_enum_info = rustpp.EncodedEnumInfo(val)
if encoded_enum_info.is_null_variant():
return IdentityPrinter(encoded_enum_info.get_null_variant_name())
non_null_val = encoded_enum_info.get_non_null_variant_val()
return rust_pretty_printer_lookup_function(non_null_val.get_wrapped_value())
# No pretty printer has been found
return None
#=------------------------------------------------------------------------------
# Pretty Printer Classes
#=------------------------------------------------------------------------------
class RustStructPrinter:
def __init__(self, val, hide_first_field):
self.val = val
self.hide_first_field = hide_first_field
def __init__(self, val, omit_first_field, omit_type_name, is_tuple_like):
self.__val = val
self.__omit_first_field = omit_first_field
self.__omit_type_name = omit_type_name
self.__is_tuple_like = is_tuple_like
def to_string(self):
return self.val.type.tag
if self.__omit_type_name:
return None
return self.__val.type.get_unqualified_type_name()
def children(self):
cs = []
for field in self.val.type.fields():
field_name = field.name
# Normally the field name is used as a key to access the field
# value, because that's also supported in older versions of GDB...
field_key = field_name
if field_name is None:
field_name = ""
# ... but for fields without a name (as in tuples), we have to
# fall back to the newer method of using the field object
# directly as key. In older versions of GDB, this will just
# fail.
field_key = field
name_value_tuple = (field_name, self.val[field_key])
cs.append(name_value_tuple)
wrapped_value = self.__val.get_wrapped_value()
if self.hide_first_field:
cs = cs[1:]
for field in self.__val.type.get_fields():
field_value = wrapped_value[field.name]
if self.__is_tuple_like:
cs.append(("", field_value))
else:
cs.append((field.name, field_value))
return cs
class RustTuplePrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return None
def children(self):
cs = []
for field in self.val.type.fields():
cs.append(("", self.val[field]))
return cs
def display_hint(self):
return "array"
class RustTupleStructPrinter:
def __init__(self, val, hide_first_field):
self.val = val
self.hide_first_field = hide_first_field
def to_string(self):
return self.val.type.tag
def children(self):
cs = []
for field in self.val.type.fields():
cs.append(("", self.val[field]))
if self.hide_first_field:
if self.__omit_first_field:
cs = cs[1:]
return cs
def display_hint(self):
return "array"
if self.__is_tuple_like:
return "array"
else:
return ""
class RustSlicePrinter:
def __init__(self, val):
self.val = val
self.__val = val
def display_hint(self):
return "array"
def to_string(self):
length = int(self.val["length"])
return self.val.type.tag + ("(len: %i)" % length)
(length, data_ptr) = rustpp.extract_length_and_ptr_from_slice(self.__val)
return (self.__val.type.get_unqualified_type_name() +
("(len: %i)" % length))
def children(self):
cs = []
length = int(self.val["length"])
data_ptr = self.val["data_ptr"]
assert data_ptr.type.code == gdb.TYPE_CODE_PTR
pointee_type = data_ptr.type.target()
(length, data_ptr) = rustpp.extract_length_and_ptr_from_slice(self.__val)
assert data_ptr.type.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_PTR
raw_ptr = data_ptr.get_wrapped_value()
for index in range(0, length):
cs.append((str(index), (data_ptr + index).dereference()))
cs.append((str(index), (raw_ptr + index).dereference()))
return cs
class RustStringSlicePrinter:
def __init__(self, val):
self.val = val
self.__val = val
def to_string(self):
slice_byte_len = self.val["length"]
return '"%s"' % self.val["data_ptr"].string(encoding="utf-8", length=slice_byte_len)
(length, data_ptr) = rustpp.extract_length_and_ptr_from_slice(self.__val)
raw_ptr = data_ptr.get_wrapped_value()
return '"%s"' % raw_ptr.string(encoding="utf-8", length=length)
class RustStdVecPrinter:
def __init__(self, val):
self.val = val
self.__val = val
def display_hint(self):
return "array"
def to_string(self):
length = int(self.val["len"])
cap = int(self.val["cap"])
return self.val.type.tag + ("(len: %i, cap: %i)" % (length, cap))
(length, data_ptr, cap) = rustpp.extract_length_ptr_and_cap_from_std_vec(self.__val)
return (self.__val.type.get_unqualified_type_name() +
("(len: %i, cap: %i)" % (length, cap)))
def children(self):
cs = []
(length, data_ptr) = extract_length_and_data_ptr_from_std_vec(self.val)
pointee_type = data_ptr.type.target()
(length, data_ptr, cap) = rustpp.extract_length_ptr_and_cap_from_std_vec(self.__val)
gdb_ptr = data_ptr.get_wrapped_value()
for index in range(0, length):
cs.append((str(index), (data_ptr + index).dereference()))
cs.append((str(index), (gdb_ptr + index).dereference()))
return cs
class RustStdStringPrinter:
def __init__(self, val):
self.val = val
self.__val = val
def to_string(self):
(length, data_ptr) = extract_length_and_data_ptr_from_std_vec(self.val["vec"])
return '"%s"' % data_ptr.string(encoding="utf-8", length=length)
vec = self.__val.get_child_at_index(0)
(length, data_ptr, cap) = rustpp.extract_length_ptr_and_cap_from_std_vec(vec)
return '"%s"' % data_ptr.get_wrapped_value().string(encoding="utf-8",
length=length)
class RustCStyleEnumPrinter:
class RustCStyleVariantPrinter:
def __init__(self, val):
assert val.type.code == gdb.TYPE_CODE_ENUM
self.val = val
assert val.type.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_ENUM
self.__val = val
def to_string(self):
return str(self.val)
return str(self.__val.get_wrapped_value())
class IdentityPrinter:
@ -259,91 +278,11 @@ class IdentityPrinter:
def to_string(self):
return self.string
STRUCT_KIND_REGULAR_STRUCT = 0
STRUCT_KIND_TUPLE_STRUCT = 1
STRUCT_KIND_TUPLE = 2
STRUCT_KIND_TUPLE_VARIANT = 3
STRUCT_KIND_STRUCT_VARIANT = 4
STRUCT_KIND_CSTYLE_VARIANT = 5
STRUCT_KIND_SLICE = 6
STRUCT_KIND_STR_SLICE = 7
STRUCT_KIND_STD_VEC = 8
STRUCT_KIND_STD_STRING = 9
def classify_struct(type):
# print("\nclassify_struct: tag=%s\n" % type.tag)
if type.tag == "&str":
return STRUCT_KIND_STR_SLICE
if type.tag.startswith("&[") and type.tag.endswith("]"):
return STRUCT_KIND_SLICE
fields = list(type.fields())
field_count = len(fields)
if field_count == 0:
return STRUCT_KIND_REGULAR_STRUCT
if (field_count == 3 and
fields[0].name == "ptr" and
fields[1].name == "len" and
fields[2].name == "cap" and
type.tag.startswith("Vec<")):
return STRUCT_KIND_STD_VEC
if (field_count == 1 and
fields[0].name == "vec" and
type.tag == "String"):
return STRUCT_KIND_STD_STRING
if fields[0].name == "RUST$ENUM$DISR":
if field_count == 1:
return STRUCT_KIND_CSTYLE_VARIANT
elif all_fields_conform_to_tuple_field_naming(fields, 1):
return STRUCT_KIND_TUPLE_VARIANT
else:
return STRUCT_KIND_STRUCT_VARIANT
if all_fields_conform_to_tuple_field_naming(fields, 0):
if type.tag.startswith("("):
return STRUCT_KIND_TUPLE
else:
return STRUCT_KIND_TUPLE_STRUCT
return STRUCT_KIND_REGULAR_STRUCT
def extract_discriminant_value(enum_val):
assert enum_val.type.code == gdb.TYPE_CODE_UNION
for variant_descriptor in enum_val.type.fields():
variant_val = enum_val[variant_descriptor]
for field in variant_val.type.fields():
return (field.name, int(variant_val[field]))
def first_field(val):
for field in val.type.fields():
return field
def get_field_at_index(val, index):
def get_field_at_index(gdb_val, index):
i = 0
for field in val.type.fields():
for field in gdb_val.type.fields():
if i == index:
return field
i += 1
return None
def all_fields_conform_to_tuple_field_naming(fields, start_index):
for i in range(start_index, len(fields)):
if (fields[i].name is None) or (re.match(r"__\d+$", fields[i].name) is None):
return False
return True
def extract_length_and_data_ptr_from_std_vec(vec_val):
length = int(vec_val["len"])
vec_ptr_val = vec_val["ptr"]
unique_ptr_val = vec_ptr_val[first_field(vec_ptr_val)]
data_ptr = unique_ptr_val[first_field(unique_ptr_val)]
assert data_ptr.type.code == gdb.TYPE_CODE_PTR
return (length, data_ptr)

View file

@ -10,58 +10,177 @@
import lldb
import re
import debugger_pretty_printers_common as rustpp
def print_val(val, internal_dict):
'''Prints the given value with Rust syntax'''
type_class = val.GetType().GetTypeClass()
#===============================================================================
# LLDB Pretty Printing Module for Rust
#===============================================================================
if type_class == lldb.eTypeClassStruct:
return print_struct_val(val, internal_dict)
class LldbType(rustpp.Type):
if type_class == lldb.eTypeClassUnion:
return print_enum_val(val, internal_dict)
def __init__(self, ty):
super(LldbType, self).__init__()
self.ty = ty
self.fields = None
if type_class == lldb.eTypeClassPointer:
def get_unqualified_type_name(self):
qualified_name = self.ty.GetName()
if qualified_name is None:
return qualified_name
return extract_type_name(qualified_name).replace("&'static ", "&")
def get_dwarf_type_kind(self):
type_class = self.ty.GetTypeClass()
if type_class == lldb.eTypeClassStruct:
return rustpp.DWARF_TYPE_CODE_STRUCT
if type_class == lldb.eTypeClassUnion:
return rustpp.DWARF_TYPE_CODE_UNION
if type_class == lldb.eTypeClassPointer:
return rustpp.DWARF_TYPE_CODE_PTR
if type_class == lldb.eTypeClassArray:
return rustpp.DWARF_TYPE_CODE_ARRAY
if type_class == lldb.eTypeClassEnumeration:
return rustpp.DWARF_TYPE_CODE_ENUM
return None
def get_fields(self):
assert ((self.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_STRUCT) or
(self.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_UNION))
if self.fields is None:
self.fields = list(self.ty.fields)
return self.fields
def get_wrapped_value(self):
return self.ty
class LldbValue(rustpp.Value):
def __init__(self, lldb_val):
ty = lldb_val.type
wty = LldbType(ty)
super(LldbValue, self).__init__(wty)
self.lldb_val = lldb_val
self.children = {}
def get_child_at_index(self, index):
child = self.children.get(index)
if child is None:
lldb_field = self.lldb_val.GetChildAtIndex(index)
child = LldbValue(lldb_field)
self.children[index] = child
return child
def as_integer(self):
return self.lldb_val.GetValueAsUnsigned()
def get_wrapped_value(self):
return self.lldb_val
def print_val(lldb_val, internal_dict):
val = LldbValue(lldb_val)
type_kind = val.type.get_type_kind()
if (type_kind == rustpp.TYPE_KIND_REGULAR_STRUCT or
type_kind == rustpp.TYPE_KIND_EMPTY):
return print_struct_val(val,
internal_dict,
omit_first_field = False,
omit_type_name = False,
is_tuple_like = False)
if type_kind == rustpp.TYPE_KIND_STRUCT_VARIANT:
return print_struct_val(val,
internal_dict,
omit_first_field = True,
omit_type_name = False,
is_tuple_like = False)
if type_kind == rustpp.TYPE_KIND_SLICE:
return print_vec_slice_val(val, internal_dict)
if type_kind == rustpp.TYPE_KIND_STR_SLICE:
return print_str_slice_val(val, internal_dict)
if type_kind == rustpp.TYPE_KIND_STD_VEC:
return print_std_vec_val(val, internal_dict)
if type_kind == rustpp.TYPE_KIND_STD_STRING:
return print_std_string_val(val, internal_dict)
if type_kind == rustpp.TYPE_KIND_TUPLE:
return print_struct_val(val,
internal_dict,
omit_first_field = False,
omit_type_name = True,
is_tuple_like = True)
if type_kind == rustpp.TYPE_KIND_TUPLE_STRUCT:
return print_struct_val(val,
internal_dict,
omit_first_field = False,
omit_type_name = False,
is_tuple_like = True)
if type_kind == rustpp.TYPE_KIND_CSTYLE_VARIANT:
return val.type.get_unqualified_type_name()
if type_kind == rustpp.TYPE_KIND_TUPLE_VARIANT:
return print_struct_val(val,
internal_dict,
omit_first_field = True,
omit_type_name = False,
is_tuple_like = True)
if type_kind == rustpp.TYPE_KIND_SINGLETON_ENUM:
return print_val(lldb_val.GetChildAtIndex(0), internal_dict)
if type_kind == rustpp.TYPE_KIND_PTR:
return print_pointer_val(val, internal_dict)
if type_class == lldb.eTypeClassArray:
if type_kind == rustpp.TYPE_KIND_FIXED_SIZE_VEC:
return print_fixed_size_vec_val(val, internal_dict)
return val.GetValue()
if type_kind == rustpp.TYPE_KIND_REGULAR_ENUM:
# This is a regular enum, extract the discriminant
discriminant_val = rustpp.get_discriminant_value_as_integer(val)
return print_val(lldb_val.GetChildAtIndex(discriminant_val), internal_dict)
if type_kind == rustpp.TYPE_KIND_COMPRESSED_ENUM:
encoded_enum_info = rustpp.EncodedEnumInfo(val)
if encoded_enum_info.is_null_variant():
return encoded_enum_info.get_null_variant_name()
non_null_val = encoded_enum_info.get_non_null_variant_val()
return print_val(non_null_val.get_wrapped_value(), internal_dict)
# No pretty printer has been found
return lldb_val.GetValue()
#=--------------------------------------------------------------------------------------------------
# Type-Specialized Printing Functions
#=--------------------------------------------------------------------------------------------------
def print_struct_val(val, internal_dict):
'''Prints a struct, tuple, or tuple struct value with Rust syntax'''
assert val.GetType().GetTypeClass() == lldb.eTypeClassStruct
if is_vec_slice(val):
return print_vec_slice_val(val, internal_dict)
elif is_std_vec(val):
return print_std_vec_val(val, internal_dict)
else:
return print_struct_val_starting_from(0, val, internal_dict)
def print_struct_val_starting_from(field_start_index, val, internal_dict):
def print_struct_val(val, internal_dict, omit_first_field, omit_type_name, is_tuple_like):
'''
Prints a struct, tuple, or tuple struct value with Rust syntax.
Ignores any fields before field_start_index.
'''
assert val.GetType().GetTypeClass() == lldb.eTypeClassStruct
assert val.type.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_STRUCT
t = val.GetType()
type_name = extract_type_name(t.GetName())
num_children = val.num_children
if (num_children - field_start_index) == 0:
# The only field of this struct is the enum discriminant
return type_name
is_tuple_like = type_is_tuple_like(t)
if omit_type_name:
type_name = ""
else:
type_name = val.type.get_unqualified_type_name()
if is_tuple_like:
template = "%(type_name)s(%(body)s)"
@ -70,20 +189,18 @@ def print_struct_val_starting_from(field_start_index, val, internal_dict):
template = "%(type_name)s {\n%(body)s\n}"
separator = ", \n"
if type_name.startswith("("):
# this is a tuple, so don't print the type name
type_name = ""
fields = val.type.get_fields()
def render_child(child_index):
this = ""
if not is_tuple_like:
field_name = t.GetFieldAtIndex(child_index).GetName()
field_name = fields[child_index].name
this += field_name + ": "
field_val = val.GetChildAtIndex(child_index)
field_val = val.get_child_at_index(child_index)
if not field_val.IsValid():
field = t.GetFieldAtIndex(child_index)
if not field_val.get_wrapped_value().IsValid():
field = fields[child_index]
# LLDB is not good at handling zero-sized values, so we have to help
# it a little
if field.GetType().GetByteSize() == 0:
@ -91,95 +208,38 @@ def print_struct_val_starting_from(field_start_index, val, internal_dict):
else:
return this + "<invalid value>"
return this + print_val(field_val, internal_dict)
return this + print_val(field_val.get_wrapped_value(), internal_dict)
body = separator.join([render_child(idx) for idx in range(field_start_index, num_children)])
if omit_first_field:
field_start_index = 1
else:
field_start_index = 0
body = separator.join([render_child(idx) for idx in range(field_start_index, len(fields))])
return template % {"type_name": type_name,
"body": body}
def print_enum_val(val, internal_dict):
'''Prints an enum value with Rust syntax'''
assert val.GetType().GetTypeClass() == lldb.eTypeClassUnion
if val.num_children == 1:
# This is either an enum with just one variant, or it is an Option-like
# enum where the discriminant is encoded in a non-nullable pointer
# field. We find out which one it is by looking at the member name of
# the sole union variant. If it starts with "RUST$ENCODED$ENUM$" then
# we have an Option-like enum.
first_variant_name = val.GetChildAtIndex(0).GetName()
if first_variant_name and first_variant_name.startswith("RUST$ENCODED$ENUM$"):
# This is an Option-like enum. The position of the discriminator field is
# encoded in the name which has the format:
# RUST$ENCODED$ENUM$<index of discriminator field>$<name of null variant>
last_separator_index = first_variant_name.rfind("$")
if last_separator_index == -1:
return "<invalid enum encoding: %s>" % first_variant_name
start_index = len("RUST$ENCODED$ENUM$")
# Extract indices of the discriminator field
try:
disr_field_indices = first_variant_name[start_index:last_separator_index].split("$")
disr_field_indices = [int(index) for index in disr_field_indices]
except:
return "<invalid enum encoding: %s>" % first_variant_name
# Read the discriminant
disr_val = val.GetChildAtIndex(0)
for index in disr_field_indices:
disr_val = disr_val.GetChildAtIndex(index)
# If the discriminant field is a fat pointer we have to consider the
# first word as the true discriminant
if disr_val.GetType().GetTypeClass() == lldb.eTypeClassStruct:
disr_val = disr_val.GetChildAtIndex(0)
if disr_val.GetValueAsUnsigned() == 0:
# Null case: Print the name of the null-variant
null_variant_name = first_variant_name[last_separator_index + 1:]
return null_variant_name
else:
# Non-null case: Interpret the data as a value of the non-null variant type
return print_struct_val_starting_from(0, val.GetChildAtIndex(0), internal_dict)
else:
# This is just a regular uni-variant enum without discriminator field
return print_struct_val_starting_from(0, val.GetChildAtIndex(0), internal_dict)
# If we are here, this is a regular enum with more than one variant
disr_val = val.GetChildAtIndex(0).GetChildMemberWithName("RUST$ENUM$DISR")
disr_type = disr_val.GetType()
if disr_type.GetTypeClass() != lldb.eTypeClassEnumeration:
return "<Invalid enum value encountered: Discriminator is not an enum>"
variant_index = disr_val.GetValueAsUnsigned()
return print_struct_val_starting_from(1, val.GetChildAtIndex(variant_index), internal_dict)
def print_pointer_val(val, internal_dict):
'''Prints a pointer value with Rust syntax'''
assert val.GetType().IsPointerType()
assert val.type.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_PTR
sigil = "&"
type_name = extract_type_name(val.GetType().GetName())
if type_name and type_name[0:1] in ["&", "~", "*"]:
type_name = val.type.get_unqualified_type_name()
if type_name and type_name[0:1] in ["&", "*"]:
sigil = type_name[0:1]
return sigil + hex(val.GetValueAsUnsigned()) #print_val(val.Dereference(), internal_dict)
return sigil + hex(val.as_integer())
def print_fixed_size_vec_val(val, internal_dict):
assert val.GetType().GetTypeClass() == lldb.eTypeClassArray
assert val.type.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_ARRAY
lldb_val = val.get_wrapped_value()
output = "["
for i in range(val.num_children):
output += print_val(val.GetChildAtIndex(i), internal_dict)
if i != val.num_children - 1:
for i in range(lldb_val.num_children):
output += print_val(lldb_val.GetChildAtIndex(i), internal_dict)
if i != lldb_val.num_children - 1:
output += ", "
output += "]"
@ -187,39 +247,38 @@ def print_fixed_size_vec_val(val, internal_dict):
def print_vec_slice_val(val, internal_dict):
length = val.GetChildAtIndex(1).GetValueAsUnsigned()
data_ptr_val = val.GetChildAtIndex(0)
data_ptr_type = data_ptr_val.GetType()
return "&[%s]" % print_array_of_values(val.GetName(),
data_ptr_val,
(length, data_ptr) = rustpp.extract_length_and_ptr_from_slice(val)
return "&[%s]" % print_array_of_values(val.get_wrapped_value().GetName(),
data_ptr,
length,
internal_dict)
def print_std_vec_val(val, internal_dict):
length = val.GetChildAtIndex(1).GetValueAsUnsigned()
# Vec<> -> Unique<> -> NonZero<> -> *T
data_ptr_val = val.GetChildAtIndex(0).GetChildAtIndex(0).GetChildAtIndex(0)
data_ptr_type = data_ptr_val.GetType()
return "vec![%s]" % print_array_of_values(val.GetName(),
data_ptr_val,
(length, data_ptr, cap) = rustpp.extract_length_ptr_and_cap_from_std_vec(val)
return "vec![%s]" % print_array_of_values(val.get_wrapped_value().GetName(),
data_ptr,
length,
internal_dict)
def print_str_slice_val(val, internal_dict):
(length, data_ptr) = rustpp.extract_length_and_ptr_from_slice(val)
return read_utf8_string(data_ptr, length)
def print_std_string_val(val, internal_dict):
vec = val.get_child_at_index(0)
(length, data_ptr, cap) = rustpp.extract_length_ptr_and_cap_from_std_vec(vec)
return read_utf8_string(data_ptr, length)
#=--------------------------------------------------------------------------------------------------
# Helper Functions
#=--------------------------------------------------------------------------------------------------
unqualified_type_markers = frozenset(["(", "[", "&", "*"])
UNQUALIFIED_TYPE_MARKERS = frozenset(["(", "[", "&", "*"])
def extract_type_name(qualified_type_name):
'''Extracts the type name from a fully qualified path'''
if qualified_type_name[0] in unqualified_type_markers:
if qualified_type_name[0] in UNQUALIFIED_TYPE_MARKERS:
return qualified_type_name
end_of_search = qualified_type_name.find("<")
@ -232,72 +291,34 @@ def extract_type_name(qualified_type_name):
else:
return qualified_type_name[index + 2:]
def type_is_tuple_like(ty):
'''Returns true of this is a type with field names (struct, struct-like enum variant)'''
for field in ty.fields:
if field.GetName() == "RUST$ENUM$DISR":
# Ignore the enum discriminant field if there is one.
continue
if (field.GetName() is None) or (re.match(r"__\d+$", field.GetName()) is None):
return False
return True
def is_vec_slice(val):
ty = val.GetType()
if ty.GetTypeClass() != lldb.eTypeClassStruct:
return False
if ty.GetNumberOfFields() != 2:
return False
if ty.GetFieldAtIndex(0).GetName() != "data_ptr":
return False
if ty.GetFieldAtIndex(1).GetName() != "length":
return False
type_name = extract_type_name(ty.GetName()).replace("&'static", "&").replace(" ", "")
return type_name.startswith("&[") and type_name.endswith("]")
def is_std_vec(val):
ty = val.GetType()
if ty.GetTypeClass() != lldb.eTypeClassStruct:
return False
if ty.GetNumberOfFields() != 3:
return False
if ty.GetFieldAtIndex(0).GetName() != "ptr":
return False
if ty.GetFieldAtIndex(1).GetName() != "len":
return False
if ty.GetFieldAtIndex(2).GetName() != "cap":
return False
return ty.GetName().startswith("collections::vec::Vec<")
def print_array_of_values(array_name, data_ptr_val, length, internal_dict):
'''Prints a contigous memory range, interpreting it as values of the
pointee-type of data_ptr_val.'''
data_ptr_type = data_ptr_val.GetType()
assert data_ptr_type.IsPointerType()
data_ptr_type = data_ptr_val.type
assert data_ptr_type.get_dwarf_type_kind() == rustpp.DWARF_TYPE_CODE_PTR
element_type = data_ptr_type.GetPointeeType()
element_type = data_ptr_type.get_wrapped_value().GetPointeeType()
element_type_size = element_type.GetByteSize()
start_address = data_ptr_val.GetValueAsUnsigned()
start_address = data_ptr_val.as_integer()
raw_value = data_ptr_val.get_wrapped_value()
def render_element(i):
address = start_address + i * element_type_size
element_val = data_ptr_val.CreateValueFromAddress(array_name + ("[%s]" % i),
address,
element_type)
element_val = raw_value.CreateValueFromAddress(array_name + ("[%s]" % i),
address,
element_type)
return print_val(element_val, internal_dict)
return ', '.join([render_element(i) for i in range(length)])
def read_utf8_string(ptr_val, byte_count):
error = lldb.SBError()
process = ptr_val.get_wrapped_value().GetProcess()
data = process.ReadMemory(ptr_val.as_integer(), byte_count, error)
if error.Success():
return '"%s"' % data.decode(encoding='UTF-8')
else:
return '<error: %s>' % error.GetCString()

View file

@ -67,7 +67,7 @@
// lldb-check:[...]$5 = Void
// lldb-command:print some_str
// lldb-check:[...]$6 = Some(&str { data_ptr: [...], length: 3 })
// lldb-check:[...]$6 = Some("abc")
// lldb-command:print none_str
// lldb-check:[...]$7 = None

View file

@ -10,10 +10,12 @@
// ignore-windows failing on win32 bot
// ignore-freebsd: gdb package too new
// ignore-lldb
// ignore-android: FIXME(#10381)
// compile-flags:-g
// min-gdb-version 7.7
// min-lldb-version: 310
// === GDB TESTS ===================================================================================
// gdb-command: run
@ -35,6 +37,30 @@
// gdb-command: print none
// gdb-check:$6 = None
// === LLDB TESTS ==================================================================================
// lldb-command: run
// lldb-command: print slice
// lldb-check:[...]$0 = &[0, 1, 2, 3]
// lldb-command: print vec
// lldb-check:[...]$1 = vec![4, 5, 6, 7]
// lldb-command: print str_slice
// lldb-check:[...]$2 = "IAMA string slice!"
// lldb-command: print string
// lldb-check:[...]$3 = "IAMA string!"
// lldb-command: print some
// lldb-check:[...]$4 = Some(8)
// lldb-command: print none
// lldb-check:[...]$5 = None
#![allow(unused_variables)]
fn main() {