[RFA] Handle dereferencing Rust trait objects

Message ID 20171115212403.8639-1-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey Nov. 15, 2017, 9:24 p.m. UTC
  In Rust, virtual tables work a bit differently than they do in C++.  In
C++, as you know, they are connected to a particular class hierarchy.
Rust, instead, can generate a virtual table for potentially any type --
in fact, one such virtual table for each trait (a trait is similar to an
abstract class or to a Java interface) that a type implements.

Objects that are referenced via a trait can't currently be inspected by
gdb.  This patch implements the Rust equivalent of "set print object".

gdb relies heavily on the C++ ABI to decode virtual tables; primarily to
make "set print object" work; but also "info vtbl".  However, Rust does
not currently have a specified ABI, so this approach seems unwise to
emulate.

Instead, I've changed the Rust compiler to emit some DWARF that
describes trait objects (previously their internal structure was
opaque), vtables (currently just a size -- but I hope to expand this in
the future), and the concrete type for which a vtable was emitted.

The concrete type is expressed as a DW_AT_containing_type on the
vtable's type.  This is a small extension to DWARF.

This patch adds a new entry to quick_symbol_functions to return the
symtab that holds a data address.  Previously there was no way in gdb to
look up a full (only minimal) non-text symbol by address.  The psymbol
implementation of this method works by lazily filling in a map that is
added to the objfile.  This avoids slowing down psymbol reading for a
feature that is likely to not be used too frequently.

I did not update .gdb_index.  My thinking here is that the DWARF 5
indices will obsolete .gdb_index soon-ish, meaning that adding a new
feature to them is probably wasted work.  If necessary I can update the
DWARF 5 index code when it lands in gdb.

Regression tested on x86-64 Fedora 25.

2017-11-15  Tom Tromey  <tom@tromey.com>

	* symtab.h (struct symbol) <is_rust_vtable>: New member.
	(struct rust_vtable_symbol): New.
	(find_symbol_at_address): Declare.
	* symtab.c (find_symbol_at_address): New function.
	* symfile.h (struct quick_symbol_functions)
	<find_compunit_symtab_by_address>: New member.
	* symfile-debug.c (debug_qf_find_compunit_symtab_by_address): New
	function.
	(debug_sym_quick_functions): Link to
	debug_qf_find_compunit_symtab_by_address.
	* rust-lang.c (rust_get_trait_object_pointer): New function.
	(rust_evaluate_subexp) <case UNOP_IND>: New case.  Call
	rust_get_trait_object_pointer.
	* psymtab.c (psym_relocate): Clear psymbol_map.
	(psym_fill_psymbol_map, psym_find_compunit_symtab_by_address): New
	functions.
	(psym_functions): Link to psym_find_compunit_symtab_by_address.
	* objfiles.h (struct objfile) <psymbol_map>: New member.
	* dwarf2read.c (dwarf2_gdb_index_functions): Update.
	(process_die) <DW_TAG_variable>: New case.  Call read_variable.
	(rust_containing_type, read_variable): New functions.

2017-11-15  Tom Tromey  <tom@tromey.com>

	* gdb.rust/traits.rs: New file.
	* gdb.rust/traits.exp: New file.
---
 gdb/ChangeLog                     | 24 ++++++++++++++++
 gdb/dwarf2read.c                  | 58 +++++++++++++++++++++++++++++++++++++++
 gdb/objfiles.h                    |  6 ++++
 gdb/psymtab.c                     | 53 +++++++++++++++++++++++++++++++++++
 gdb/rust-lang.c                   | 53 +++++++++++++++++++++++++++++++++++
 gdb/symfile-debug.c               | 28 +++++++++++++++++++
 gdb/symfile.h                     |  8 ++++++
 gdb/symtab.c                      | 39 ++++++++++++++++++++++++++
 gdb/symtab.h                      | 19 +++++++++++++
 gdb/testsuite/ChangeLog           |  5 ++++
 gdb/testsuite/gdb.rust/traits.exp | 35 +++++++++++++++++++++++
 gdb/testsuite/gdb.rust/traits.rs  | 37 +++++++++++++++++++++++++
 12 files changed, 365 insertions(+)
 create mode 100644 gdb/testsuite/gdb.rust/traits.exp
 create mode 100644 gdb/testsuite/gdb.rust/traits.rs
  

Comments

Tom Tromey Nov. 15, 2017, 9:28 p.m. UTC | #1
>>>>> "Tom" == Tom Tromey <tom@tromey.com> writes:

Tom> Objects that are referenced via a trait can't currently be inspected by
Tom> gdb.  This patch implements the Rust equivalent of "set print object".

I forgot a couple of details.

The Rust compiler patch is here:

    https://github.com/rust-lang/rust/pull/45897

It should go in soon.

I wasn't sure if I ought to xfail the new tests.  I have the impression
that I'm the only person who runs the Rust gdb tests, but I'm not
actually certain.

Tom
  
Pedro Alves Nov. 16, 2017, 4:34 p.m. UTC | #2
On 11/15/2017 09:24 PM, Tom Tromey wrote:
> In Rust, virtual tables work a bit differently than they do in C++.  In
> C++, as you know, they are connected to a particular class hierarchy.
> Rust, instead, can generate a virtual table for potentially any type --
> in fact, one such virtual table for each trait (a trait is similar to an
> abstract class or to a Java interface) that a type implements.
> 
> Objects that are referenced via a trait can't currently be inspected by
> gdb.  This patch implements the Rust equivalent of "set print object".
> 
> gdb relies heavily on the C++ ABI to decode virtual tables; primarily to
> make "set print object" work; but also "info vtbl".  However, Rust does
> not currently have a specified ABI, so this approach seems unwise to
> emulate.
> 
> Instead, I've changed the Rust compiler to emit some DWARF that
> describes trait objects (previously their internal structure was
> opaque), vtables (currently just a size -- but I hope to expand this in
> the future), and the concrete type for which a vtable was emitted.
> 
> The concrete type is expressed as a DW_AT_containing_type on the
> vtable's type.  This is a small extension to DWARF.
> 
> This patch adds a new entry to quick_symbol_functions to return the
> symtab that holds a data address.  Previously there was no way in gdb to
> look up a full (only minimal) non-text symbol by address.  The psymbol
> implementation of this method works by lazily filling in a map that is
> added to the objfile.  This avoids slowing down psymbol reading for a
> feature that is likely to not be used too frequently.
> 
> I did not update .gdb_index.  My thinking here is that the DWARF 5
> indices will obsolete .gdb_index soon-ish, meaning that adding a new
> feature to them is probably wasted work.  If necessary I can update the
> DWARF 5 index code when it lands in gdb.
> 
> Regression tested on x86-64 Fedora 25.
> 
> 2017-11-15  Tom Tromey  <tom@tromey.com>
> 
> 	* symtab.h (struct symbol) <is_rust_vtable>: New member.
> 	(struct rust_vtable_symbol): New.
> 	(find_symbol_at_address): Declare.
> 	* symtab.c (find_symbol_at_address): New function.
> 	* symfile.h (struct quick_symbol_functions)
> 	<find_compunit_symtab_by_address>: New member.
> 	* symfile-debug.c (debug_qf_find_compunit_symtab_by_address): New
> 	function.
> 	(debug_sym_quick_functions): Link to
> 	debug_qf_find_compunit_symtab_by_address.
> 	* rust-lang.c (rust_get_trait_object_pointer): New function.
> 	(rust_evaluate_subexp) <case UNOP_IND>: New case.  Call
> 	rust_get_trait_object_pointer.
> 	* psymtab.c (psym_relocate): Clear psymbol_map.
> 	(psym_fill_psymbol_map, psym_find_compunit_symtab_by_address): New
> 	functions.
> 	(psym_functions): Link to psym_find_compunit_symtab_by_address.
> 	* objfiles.h (struct objfile) <psymbol_map>: New member.
> 	* dwarf2read.c (dwarf2_gdb_index_functions): Update.
> 	(process_die) <DW_TAG_variable>: New case.  Call read_variable.
> 	(rust_containing_type, read_variable): New functions.
> 
> 2017-11-15  Tom Tromey  <tom@tromey.com>
> 
> 	* gdb.rust/traits.rs: New file.
> 	* gdb.rust/traits.exp: New file.

Looks good to me, with a couple comments below.

> @@ -361,6 +362,11 @@ struct objfile
>  
>    struct psymbol_bcache *psymbol_cache;
>  
> +  /* Map symbol addresses to the partial symtab that defines the
> +     object at that address.  */
> +
> +  std::unordered_map<CORE_ADDR, partial_symtab *> psymbol_map;
> +

std::unordered_map + pointers (or really small objects) makes me
cry.  :-)  That has nasty memory overhead/footprint, with
unordered_map being a node container...

Can you say something about the number of elements that usually
ends up in this map in some reasonably sized Rust-using app (Firefox?)?

I'm assuming that you end up with many contiguous symbols pointing to
the same psymtab?  Would an addrmap (addrmap.h/c) be a good fit here?
Maybe not if we don't have the size of the psymbols handy when
we build this...  :-/

>    /* Vectors of all partial symbols read in from file.  The actual data
>       is stored in the objfile_obstack.  */


> +  CORE_ADDR vtable = value_as_address (value_field (value, vtable_field));
> +  struct symbol *symbol = find_symbol_at_address (vtable);
> +  if (symbol == NULL || !symbol->is_rust_vtable)
> +    return NULL;
> +
> +  struct rust_vtable_symbol *vtable_sym
> +    = reinterpret_cast<struct rust_vtable_symbol *> (symbol);

This reinterpret_cast looks like a big hammer here.  Why not static_cast?

> +  struct type *pointer_type = lookup_pointer_type (vtable_sym->concrete_type);
> +  return value_cast (pointer_type, value_field (value, 1 - vtable_field));
> +}


> --- a/gdb/symtab.h
> +++ b/gdb/symtab.h
> @@ -1061,6 +1061,11 @@ struct symbol
>       In this case the symbol is really a "struct template_symbol".  */
>    unsigned is_cplus_template_function : 1;
>  
> +  /* True if this is a Rust virtual table.  In this case, the symbol
> +     can be downcast to "struct rust_vtable_symbol".  */
> +
> +  unsigned is_rust_vtable : 1;

A symbol can't be both is_cplus_template_function and is_rust_vtable,
I think it'd be better long term if we merged the tag to a single
enum bitfield (with two bits).  But it's not that big a deal and
can always be done later.

So if we don't end up with too many symbols in the map that
could increase gdb's memory significantly, this is fine with me.
Fix the reinterpret_cast and you're good to go.

Thanks,
Pedro Alves
  
Pedro Alves Nov. 16, 2017, 4:40 p.m. UTC | #3
On 11/15/2017 09:28 PM, Tom Tromey wrote:
>>>>>> "Tom" == Tom Tromey <tom@tromey.com> writes:
> 
> Tom> Objects that are referenced via a trait can't currently be inspected by
> Tom> gdb.  This patch implements the Rust equivalent of "set print object".
> 
> I forgot a couple of details.
> 
> The Rust compiler patch is here:
> 
>     https://github.com/rust-lang/rust/pull/45897
> 
> It should go in soon.
> 
> I wasn't sure if I ought to xfail the new tests.  I have the impression
> that I'm the only person who runs the Rust gdb tests, but I'm not
> actually certain.

I run the rust tests with whatever's the rust compiler that comes
with Fedora:

 $ rustc --version
 rustc 1.13.0 (2c6933acc 2016-11-07)

I assume others that run Fedora do the same, since I assume
the instructions to set up for gdb testing on Fedora pull
in rustc [1].  So I guess the bots are testing rust too?
(I'm still on F23, should really upgrade...)

[1] https://sourceware.org/gdb/wiki/BuildBot#Fedora-specific_instructions

Thanks,
Pedro Alves
  
Tom Tromey Nov. 16, 2017, 11:13 p.m. UTC | #4
>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:

Tom> I wasn't sure if I ought to xfail the new tests.  I have the impression
Tom> that I'm the only person who runs the Rust gdb tests, but I'm not
Tom> actually certain.

Pedro> I run the rust tests with whatever's the rust compiler that comes
Pedro> with Fedora:

Thanks for doing that.

I'm not sure what my options are for the test.  I could probably predict
what version of rustc will have the patches -- but this also requires a
patched LLVM, and while both the upstream LLVM and rust-llvm have the
patch, people are free to build against other versions (and I don't know
what the distros do in particular).

Tom
  
Tom Tromey Nov. 16, 2017, 11:26 p.m. UTC | #5
>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:

Pedro> std::unordered_map + pointers (or really small objects) makes me
Pedro> cry.  :-)  That has nasty memory overhead/footprint, with
Pedro> unordered_map being a node container...

I could do something else.  A vector of pairs, sorted by address; or an
htab_t.

Pedro> Can you say something about the number of elements that usually
Pedro> ends up in this map in some reasonably sized Rust-using app (Firefox?)?

I will look at this tomorrow.

Pedro> I'm assuming that you end up with many contiguous symbols pointing to
Pedro> the same psymtab?  Would an addrmap (addrmap.h/c) be a good fit here?
Pedro> Maybe not if we don't have the size of the psymbols handy when
Pedro> we build this...  :-/

Yes, the size isn't known, because psymtab reading doesn't really look
at the types.

Maybe an addrmap wouldn't be too bad though.  Perhaps I'll try a couple
of ideas and try to see what uses the least memory.

Pedro> This reinterpret_cast looks like a big hammer here.  Why not
Pedro> static_cast?

I changed it.  Not sure why I had that .. maybe leftover from before I
used derivation.

Pedro> A symbol can't be both is_cplus_template_function and is_rust_vtable,
Pedro> I think it'd be better long term if we merged the tag to a single
Pedro> enum bitfield (with two bits).  But it's not that big a deal and
Pedro> can always be done later.

I can do this.

Tom
  
Pedro Alves Nov. 17, 2017, 3:39 p.m. UTC | #6
On 11/16/2017 11:13 PM, Tom Tromey wrote:
>>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:
> 
> Tom> I wasn't sure if I ought to xfail the new tests.  I have the impression
> Tom> that I'm the only person who runs the Rust gdb tests, but I'm not
> Tom> actually certain.
> 
> Pedro> I run the rust tests with whatever's the rust compiler that comes
> Pedro> with Fedora:
> 
> Thanks for doing that.
> 
> I'm not sure what my options are for the test.  I could probably predict
> what version of rustc will have the patches -- but this also requires a
> patched LLVM, and while both the upstream LLVM and rust-llvm have the
> patch, people are free to build against other versions (and I don't know
> what the distros do in particular).

Dunno.  Maybe do something like gdb.arch/altivec-abi.exp, using readelf
to look for some DWARf tag:

    if { "$force_abi" == "auto" } {
        # If the toolchain does not record attributes, skip auto-ABI tests.
        set readelf_program [gdb_find_readelf]
        set result [catch "exec $readelf_program -A $binfile" output]

        if {$result == 0 && ![regexp Tag_GNU_Power_ABI_Vector $output]} {
            untested "ABI not marked"
            return
        }
    }

Thanks,
Pedro Alves
  
Tom Tromey Nov. 17, 2017, 5:30 p.m. UTC | #7
Tom> I could do something else.  A vector of pairs, sorted by address; or an
Tom> htab_t.

Pedro> Can you say something about the number of elements that usually
Pedro> ends up in this map in some reasonably sized Rust-using app (Firefox?)?

Tom> I will look at this tomorrow.

I had a bit of a panic when I ran this on libxul and got a count of
501908 symbols.

However, my first attempt counted incorrectly; there are actually only
46488 unique LOC_STATIC psymbols in libxul.  This roughly conforms to
the idea that the psymbol cache is ~90% effective.

Anyway, we discussed it on irc a bit, and I think I'll switch to the
sorted vector of pairs.  This will save some space, and isn't much
slower or harder to code.

Tom
  

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 9b1c335..0569cda 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,27 @@ 
+2017-11-15  Tom Tromey  <tom@tromey.com>
+
+	* symtab.h (struct symbol) <is_rust_vtable>: New member.
+	(struct rust_vtable_symbol): New.
+	(find_symbol_at_address): Declare.
+	* symtab.c (find_symbol_at_address): New function.
+	* symfile.h (struct quick_symbol_functions)
+	<find_compunit_symtab_by_address>: New member.
+	* symfile-debug.c (debug_qf_find_compunit_symtab_by_address): New
+	function.
+	(debug_sym_quick_functions): Link to
+	debug_qf_find_compunit_symtab_by_address.
+	* rust-lang.c (rust_get_trait_object_pointer): New function.
+	(rust_evaluate_subexp) <case UNOP_IND>: New case.  Call
+	rust_get_trait_object_pointer.
+	* psymtab.c (psym_relocate): Clear psymbol_map.
+	(psym_fill_psymbol_map, psym_find_compunit_symtab_by_address): New
+	functions.
+	(psym_functions): Link to psym_find_compunit_symtab_by_address.
+	* objfiles.h (struct objfile) <psymbol_map>: New member.
+	* dwarf2read.c (dwarf2_gdb_index_functions): Update.
+	(process_die) <DW_TAG_variable>: New case.  Call read_variable.
+	(rust_containing_type, read_variable): New functions.
+
 2017-11-15  Simon Marchi  <simon.marchi@ericsson.com>
 
 	* tui/tui-win.c (window_name_completer): Replace VEC with
diff --git a/gdb/dwarf2read.c b/gdb/dwarf2read.c
index b6bd291..8283345 100644
--- a/gdb/dwarf2read.c
+++ b/gdb/dwarf2read.c
@@ -1797,6 +1797,8 @@  static void read_lexical_block_scope (struct die_info *, struct dwarf2_cu *);
 
 static void read_call_site_scope (struct die_info *die, struct dwarf2_cu *cu);
 
+static void read_variable (struct die_info *die, struct dwarf2_cu *cu);
+
 static int dwarf2_ranges_read (unsigned, CORE_ADDR *, CORE_ADDR *,
 			       struct dwarf2_cu *, struct partial_symtab *);
 
@@ -4995,6 +4997,7 @@  const struct quick_symbol_functions dwarf2_gdb_index_functions =
   dw2_map_matching_symbols,
   dw2_expand_symtabs_matching,
   dw2_find_pc_sect_compunit_symtab,
+  NULL,
   dw2_map_symbol_filenames
 };
 
@@ -9230,6 +9233,10 @@  process_die (struct die_info *die, struct dwarf2_cu *cu)
       process_imported_unit_die (die, cu);
       break;
 
+    case DW_TAG_variable:
+      read_variable (die, cu);
+      break;
+
     default:
       new_symbol (die, NULL, cu);
       break;
@@ -12770,6 +12777,57 @@  read_call_site_scope (struct die_info *die, struct dwarf2_cu *cu)
     }
 }
 
+/* Helper function for read_variable.  If DIE represents a virtual
+   table, then return the type of the concrete object that is
+   associated with the virtual table.  Otherwise, return NULL.  */
+
+static struct type *
+rust_containing_type (struct die_info *die, struct dwarf2_cu *cu)
+{
+  struct attribute *attr = dwarf2_attr (die, DW_AT_type, cu);
+  if (attr == NULL)
+    return NULL;
+
+  /* Find the type DIE.  */
+  struct die_info *type_die = NULL;
+  struct dwarf2_cu *type_cu = cu;
+
+  if (attr_form_is_ref (attr))
+    type_die = follow_die_ref (die, attr, &type_cu);
+  if (type_die == NULL)
+    return NULL;
+
+  if (dwarf2_attr (type_die, DW_AT_containing_type, type_cu) == NULL)
+    return NULL;
+  return die_containing_type (type_die, type_cu);
+}
+
+/* Read a variable (DW_TAG_variable) DIE and create a new symbol.  */
+
+static void
+read_variable (struct die_info *die, struct dwarf2_cu *cu)
+{
+  struct rust_vtable_symbol *storage = NULL;
+
+  if (cu->language == language_rust)
+    {
+      struct type *containing_type = rust_containing_type (die, cu);
+
+      if (containing_type != NULL)
+	{
+	  struct objfile *objfile = cu->objfile;
+
+	  storage = OBSTACK_ZALLOC (&objfile->objfile_obstack,
+				    struct rust_vtable_symbol);
+	  initialize_objfile_symbol (storage);
+	  storage->concrete_type = containing_type;
+	  storage->is_rust_vtable = 1;
+	}
+    }
+
+  new_symbol_full (die, NULL, cu, storage);
+}
+
 /* Call CALLBACK from DW_AT_ranges attribute value OFFSET
    reading .debug_rnglists.
    Callback's type should be:
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 12ef5ec..e72fbfe 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -28,6 +28,7 @@ 
 #include "registry.h"
 #include "gdb_bfd.h"
 #include <vector>
+#include <unordered_map>
 
 struct bcache;
 struct htab;
@@ -361,6 +362,11 @@  struct objfile
 
   struct psymbol_bcache *psymbol_cache;
 
+  /* Map symbol addresses to the partial symtab that defines the
+     object at that address.  */
+
+  std::unordered_map<CORE_ADDR, partial_symtab *> psymbol_map;
+
   /* Vectors of all partial symbols read in from file.  The actual data
      is stored in the objfile_obstack.  */
 
diff --git a/gdb/psymtab.c b/gdb/psymtab.c
index 06a65bf..e15a8bc 100644
--- a/gdb/psymtab.c
+++ b/gdb/psymtab.c
@@ -823,6 +823,8 @@  psym_relocate (struct objfile *objfile,
       if (SYMBOL_SECTION (psym) >= 0)
 	SYMBOL_VALUE_ADDRESS (psym) += ANOFFSET (delta, SYMBOL_SECTION (psym));
     }
+
+  objfile->psymbol_map.clear ();
 }
 
 /* Psymtab version of find_last_source_symtab.  See its definition in
@@ -1458,6 +1460,56 @@  psym_has_symbols (struct objfile *objfile)
   return objfile->psymtabs != NULL;
 }
 
+/* Helper function for psym_find_compunit_symtab_by_address that fills
+   in psymbol_map for a given range of psymbols.  */
+
+static void
+psym_fill_psymbol_map (struct objfile *objfile,
+		       struct partial_symtab *psymtab,
+		       const std::vector<partial_symbol *> &symbols,
+		       int start,
+		       int length)
+{
+  for (int i = 0; i < length; ++i)
+    {
+      struct partial_symbol *psym = symbols[start + i];
+
+      if (PSYMBOL_CLASS (psym) == LOC_STATIC)
+	objfile->psymbol_map[SYMBOL_VALUE_ADDRESS (psym)] = psymtab;
+    }
+}
+
+/* See find_compunit_symtab_by_address in quick_symbol_functions, in
+   symfile.h.  */
+
+static compunit_symtab *
+psym_find_compunit_symtab_by_address (struct objfile *objfile,
+				      CORE_ADDR address)
+{
+  if (objfile->psymbol_map.empty ())
+    {
+      struct partial_symtab *pst;
+
+      ALL_OBJFILE_PSYMTABS_REQUIRED (objfile, pst)
+      {
+	psym_fill_psymbol_map (objfile, pst,
+			       objfile->global_psymbols,
+			       pst->globals_offset,
+			       pst->n_global_syms);
+	psym_fill_psymbol_map (objfile, pst,
+			       objfile->static_psymbols,
+			       pst->statics_offset,
+			       pst->n_static_syms);
+      }
+    }
+
+  auto iter = objfile->psymbol_map.find (address);
+  if (iter == objfile->psymbol_map.end ())
+    return NULL;
+
+  return psymtab_to_symtab (objfile, iter->second);
+}
+
 const struct quick_symbol_functions psym_functions =
 {
   psym_has_symbols,
@@ -1474,6 +1526,7 @@  const struct quick_symbol_functions psym_functions =
   psym_map_matching_symbols,
   psym_expand_symtabs_matching,
   psym_find_pc_sect_compunit_symtab,
+  psym_find_compunit_symtab_by_address,
   psym_map_symbol_filenames
 };
 
diff --git a/gdb/rust-lang.c b/gdb/rust-lang.c
index c1b2d6e..701df55 100644
--- a/gdb/rust-lang.c
+++ b/gdb/rust-lang.c
@@ -29,6 +29,7 @@ 
 #include "gdbarch.h"
 #include "infcall.h"
 #include "objfiles.h"
+#include "psymtab.h"
 #include "rust-lang.h"
 #include "valprint.h"
 #include "varobj.h"
@@ -396,6 +397,39 @@  rust_chartype_p (struct type *type)
 	  && TYPE_UNSIGNED (type));
 }
 
+/* If VALUE represents a trait object pointer, return the underlying
+   pointer with the correct (i.e., runtime) type.  Otherwise, return
+   NULL.  */
+
+static struct value *
+rust_get_trait_object_pointer (struct value *value)
+{
+  struct type *type = check_typedef (value_type (value));
+
+  if (TYPE_CODE (type) != TYPE_CODE_STRUCT || TYPE_NFIELDS (type) != 2)
+    return NULL;
+
+  /* Try to be a bit resilient if the ABI changes.  */
+  int vtable_field = 0;
+  for (int i = 0; i < 2; ++i)
+    {
+      if (strcmp (TYPE_FIELD_NAME (type, i), "vtable") == 0)
+	vtable_field = i;
+      else if (strcmp (TYPE_FIELD_NAME (type, i), "pointer") != 0)
+	return NULL;
+    }
+
+  CORE_ADDR vtable = value_as_address (value_field (value, vtable_field));
+  struct symbol *symbol = find_symbol_at_address (vtable);
+  if (symbol == NULL || !symbol->is_rust_vtable)
+    return NULL;
+
+  struct rust_vtable_symbol *vtable_sym
+    = reinterpret_cast<struct rust_vtable_symbol *> (symbol);
+  struct type *pointer_type = lookup_pointer_type (vtable_sym->concrete_type);
+  return value_cast (pointer_type, value_field (value, 1 - vtable_field));
+}
+
 
 
 /* la_emitchar implementation for Rust.  */
@@ -1617,6 +1651,25 @@  rust_evaluate_subexp (struct type *expect_type, struct expression *exp,
 
   switch (exp->elts[*pos].opcode)
     {
+    case UNOP_IND:
+      {
+	if (noside != EVAL_NORMAL)
+	  result = evaluate_subexp_standard (expect_type, exp, pos, noside);
+	else
+	  {
+	    ++*pos;
+	    struct value *value = evaluate_subexp (expect_type, exp, pos,
+						   noside);
+
+	    struct value *trait_ptr = rust_get_trait_object_pointer (value);
+	    if (trait_ptr != NULL)
+	      value = trait_ptr;
+
+	    result = value_ind (value);
+	  }
+      }
+      break;
+
     case UNOP_COMPLEMENT:
       {
 	struct value *value;
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 49c472e0..1754754 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -365,6 +365,33 @@  debug_qf_map_symbol_filenames (struct objfile *objfile,
 						 need_fullname);
 }
 
+static struct compunit_symtab *
+debug_qf_find_compunit_symtab_by_address (struct objfile *objfile,
+					  CORE_ADDR address)
+{
+  const struct debug_sym_fns_data *debug_data
+    = ((const struct debug_sym_fns_data *)
+       objfile_data (objfile, symfile_debug_objfile_data_key));
+  fprintf_filtered (gdb_stdlog,
+		    "qf->find_compunit_symtab_by_address (%s, %s)\n",
+		    objfile_debug_name (objfile),
+		    hex_string (address));
+
+  struct compunit_symtab *result = NULL;
+  if (debug_data->real_sf->qf->map_symbol_filenames != NULL)
+    result
+      = debug_data->real_sf->qf->find_compunit_symtab_by_address (objfile,
+								  address);
+
+  fprintf_filtered (gdb_stdlog,
+		    "qf->find_compunit_symtab_by_address (...) = %s\n",
+		    result
+		    ? debug_symtab_name (compunit_primary_filetab (result))
+		    : "NULL");
+
+  return result;
+}
+
 static const struct quick_symbol_functions debug_sym_quick_functions =
 {
   debug_qf_has_symbols,
@@ -381,6 +408,7 @@  static const struct quick_symbol_functions debug_sym_quick_functions =
   debug_qf_map_matching_symbols,
   debug_qf_expand_symtabs_matching,
   debug_qf_find_pc_sect_compunit_symtab,
+  debug_qf_find_compunit_symtab_by_address,
   debug_qf_map_symbol_filenames
 };
 
diff --git a/gdb/symfile.h b/gdb/symfile.h
index 3472aa0..e742984 100644
--- a/gdb/symfile.h
+++ b/gdb/symfile.h
@@ -270,6 +270,14 @@  struct quick_symbol_functions
     (struct objfile *objfile, struct bound_minimal_symbol msymbol,
      CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
 
+  /* Return the comp unit from OBJFILE that contains a symbol at
+     ADDRESS.  Return NULL if there is no such comp unit.  Unlike
+     find_pc_sect_compunit_symtab, any sort of symbol (not just text
+     symbols) can be considered, and only exact address matches are
+     considered.  This pointer may be NULL.  */
+  struct compunit_symtab *(*find_compunit_symtab_by_address)
+    (struct objfile *objfile, CORE_ADDR address);
+
   /* Call a callback for every file defined in OBJFILE whose symtab is
      not already read in.  FUN is the callback.  It is passed the file's
      FILENAME, the file's FULLNAME (if need_fullname is non-zero), and
diff --git a/gdb/symtab.c b/gdb/symtab.c
index ebb7fbe..38bc713 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -2964,6 +2964,45 @@  find_pc_compunit_symtab (CORE_ADDR pc)
 {
   return find_pc_sect_compunit_symtab (pc, find_pc_mapped_section (pc));
 }
+
+/* See symtab.h.  */
+
+struct symbol *
+find_symbol_at_address (CORE_ADDR address)
+{
+  struct objfile *objfile;
+
+  ALL_OBJFILES (objfile)
+  {
+    if (objfile->sf == NULL
+	|| objfile->sf->qf->find_compunit_symtab_by_address == NULL)
+      continue;
+
+    struct compunit_symtab *symtab
+      = objfile->sf->qf->find_compunit_symtab_by_address (objfile, address);
+    if (symtab != NULL)
+      {
+	const struct blockvector *bv = COMPUNIT_BLOCKVECTOR (symtab);
+
+	for (int i = GLOBAL_BLOCK; i <= STATIC_BLOCK; ++i)
+	  {
+	    struct block *b = BLOCKVECTOR_BLOCK (bv, i);
+	    struct block_iterator iter;
+	    struct symbol *sym;
+
+	    ALL_BLOCK_SYMBOLS (b, iter, sym)
+	    {
+	      if (SYMBOL_CLASS (sym) == LOC_STATIC
+		  && SYMBOL_VALUE_ADDRESS (sym) == address)
+		return sym;
+	    }
+	  }
+      }
+  }
+
+  return NULL;
+}
+
 
 
 /* Find the source file and line number for a given PC value and SECTION.
diff --git a/gdb/symtab.h b/gdb/symtab.h
index 7b8b5cc..0845ae8 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1061,6 +1061,11 @@  struct symbol
      In this case the symbol is really a "struct template_symbol".  */
   unsigned is_cplus_template_function : 1;
 
+  /* True if this is a Rust virtual table.  In this case, the symbol
+     can be downcast to "struct rust_vtable_symbol".  */
+
+  unsigned is_rust_vtable : 1;
+
   /* Line number of this symbol's definition, except for inlined
      functions.  For an inlined function (class LOC_BLOCK and
      SYMBOL_INLINED set) this is the line number of the function's call
@@ -1179,6 +1184,15 @@  struct template_symbol
   struct symbol **template_arguments;
 };
 
+/* A symbol that represents a Rust virtual table object.  */
+
+struct rust_vtable_symbol : public symbol
+{
+  /* The concrete type for which this vtable was created; that is, in
+     "impl Trait for Type", this is "Type".  */
+  struct type *concrete_type;
+};
+
 
 /* Each item represents a line-->pc (or the reverse) mapping.  This is
    somewhat more wasteful of space than one might wish, but since only
@@ -1605,6 +1619,11 @@  extern struct symbol *find_pc_function (CORE_ADDR);
 
 extern struct symbol *find_pc_sect_function (CORE_ADDR, struct obj_section *);
 
+/* Find the symbol at the given address.  Returns NULL if no symbol
+   found.  Only exact matches for ADDRESS are considered.  */
+
+extern struct symbol *find_symbol_at_address (CORE_ADDR);
+
 extern int find_pc_partial_function_gnu_ifunc (CORE_ADDR pc, const char **name,
 					       CORE_ADDR *address,
 					       CORE_ADDR *endaddr,
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index bb8dd79..9c46b74 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,8 @@ 
+2017-11-15  Tom Tromey  <tom@tromey.com>
+
+	* gdb.rust/traits.rs: New file.
+	* gdb.rust/traits.exp: New file.
+
 2017-11-15  Simon Marchi  <simon.marchi@ericsson.com>
 
 	* gdb.tui/completionn.exp (test_tab_completion): Add space in
diff --git a/gdb/testsuite/gdb.rust/traits.exp b/gdb/testsuite/gdb.rust/traits.exp
new file mode 100644
index 0000000..5a3c942
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/traits.exp
@@ -0,0 +1,35 @@ 
+# Copyright (C) 2017 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 trait object printing.
+
+load_lib rust-support.exp
+if {[skip_rust_tests]} {
+    continue
+}
+
+standard_testfile .rs
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug rust}]} {
+    return -1
+}
+
+set line [gdb_get_line_number "set breakpoint here"]
+if {![runto ${srcfile}:$line]} {
+    untested "could not run to breakpoint"
+    return -1
+}
+
+gdb_test "print *td" " = 23.5"
+gdb_test "print *tu" " = 23"
diff --git a/gdb/testsuite/gdb.rust/traits.rs b/gdb/testsuite/gdb.rust/traits.rs
new file mode 100644
index 0000000..98f841b
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/traits.rs
@@ -0,0 +1,37 @@ 
+// Copyright (C) 2017 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/>.
+
+#![allow(dead_code)]
+#![allow(unused_variables)]
+#![allow(unused_assignments)]
+
+pub trait T {
+}
+
+impl T for f64 {
+}
+
+impl T for u8 {
+}
+
+pub fn main() {
+    let d = 23.5f64;
+    let u = 23u8;
+
+    let td = &d as &T;
+    let tu = &u as &T;
+
+    println!("");               // set breakpoint here
+}