Skip to content

Commit

Permalink
debuginfo: Create common debugger pretty printer module.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
michaelwoerister committed May 30, 2015
1 parent 474c6e0 commit d136714
Show file tree
Hide file tree
Showing 6 changed files with 737 additions and 421 deletions.
6 changes: 4 additions & 2 deletions mk/debuggers.mk
Expand Up @@ -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))
Expand All @@ -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))
Expand Down
328 changes: 328 additions & 0 deletions src/etc/debugger_pretty_printers_common.py
@@ -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)

0 comments on commit d136714

Please sign in to comment.