diff --git a/gdb/common/tdesc.c b/gdb/common/tdesc.c
index a0ed7c5115..04f8bc6912 100644
--- a/gdb/common/tdesc.c
+++ b/gdb/common/tdesc.c
@@ -289,3 +289,109 @@ tdesc_add_enum_value (tdesc_type_with_fields *type, int value,
 			     tdesc_predefined_type (TDESC_TYPE_INT32),
 			     value, -1);
 }
+
+void print_xml_feature::visit_pre (const tdesc_feature *e)
+{
+  string_appendf (m_buffer, "<feature name=\"%s\">\n", e->name.c_str ());
+}
+
+void print_xml_feature::visit_post (const tdesc_feature *e)
+{
+  string_appendf (m_buffer, "</feature>\n");
+}
+
+void print_xml_feature::visit (const tdesc_type_builtin *t)
+{
+  error (_("xml output is not supported type \"%s\"."), t->name.c_str ());
+}
+
+void print_xml_feature::visit (const tdesc_type_vector *t)
+{
+  string_appendf (m_buffer, "<vector id=\"%s\" type=\"%s\" count=\"%d\"/>\n",
+		  t->name.c_str (), t->element_type->name.c_str (), t->count);
+}
+
+void print_xml_feature::visit (const tdesc_type_with_fields *t)
+{
+  struct tdesc_type_field *f;
+  const static char *types[] = { "struct", "union", "flags", "enum" };
+
+  gdb_assert (t->kind >= TDESC_TYPE_STRUCT && t->kind <= TDESC_TYPE_ENUM);
+
+  string_appendf (m_buffer, "<%s id=\"%s\"", types[t->kind - TDESC_TYPE_STRUCT],
+		  t->name.c_str ());
+
+  switch (t->kind)
+    {
+    case TDESC_TYPE_STRUCT:
+    case TDESC_TYPE_FLAGS:
+      if (t->size > 0)
+	string_appendf (m_buffer, " size=\"%d\"", t->size);
+      string_appendf (m_buffer, ">\n");
+
+      for (const tdesc_type_field &f : t->fields)
+	{
+	  string_appendf (m_buffer, "  <field name=\"%s\" ", f.name.c_str ());
+	  if (f.start == -1)
+	    string_appendf (m_buffer, "type=\"%s\"/>\n", f.type->name.c_str ());
+	  else
+	    string_appendf (m_buffer, "start=\"%d\" end=\"%d\"/>\n", f.start,
+			    f.end);
+	}
+      break;
+
+    case TDESC_TYPE_ENUM:
+      string_appendf (m_buffer, ">\n");
+      for (const tdesc_type_field &f : t->fields)
+	string_appendf (m_buffer, "  <field name=\"%s\" start=\"%d\"/>\n",
+			f.name.c_str (), f.start);
+      break;
+
+    case TDESC_TYPE_UNION:
+      string_appendf (m_buffer, ">\n");
+      for (const tdesc_type_field &f : t->fields)
+	string_appendf (m_buffer, "  <field name=\"%s\" type=\"%s\"/>\n",
+			f.name.c_str (), f.type->name.c_str ());
+      break;
+
+    default:
+      error (_("xml output is not supported type \"%s\"."), t->name.c_str ());
+    }
+
+  string_appendf (m_buffer, "</%s>\n", types[t->kind - TDESC_TYPE_STRUCT]);
+}
+
+void print_xml_feature::visit (const tdesc_reg *r)
+{
+  string_appendf (m_buffer,
+		  "<reg name=\"%s\" bitsize=\"%d\" type=\"%s\" regnum=\"%ld\"",
+		  r->name.c_str (), r->bitsize, r->type.c_str (),
+		  r->target_regnum);
+
+  if (r->group.length () > 0)
+    string_appendf (m_buffer, " group=\"%s\"", r->group.c_str ());
+
+  if (r->save_restore == 0)
+    string_appendf (m_buffer, " save-restore=\"no\"");
+
+  string_appendf (m_buffer, "/>\n");
+}
+
+void print_xml_feature::visit_pre (const target_desc *e)
+{
+#ifndef IN_PROCESS_AGENT
+  string_appendf (m_buffer, "<?xml version=\"1.0\"?>\n");
+  string_appendf (m_buffer, "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n");
+  string_appendf (m_buffer, "<target>\n<architecture>%s</architecture>\n",
+		  tdesc_architecture_name (e));
+
+  const char *osabi = tdesc_osabi_name (e);
+  if (osabi != nullptr)
+    string_appendf (m_buffer, "<osabi>%s</osabi>", osabi);
+#endif
+}
+
+void print_xml_feature::visit_post (const target_desc *e)
+{
+  string_appendf (m_buffer, "</target>\n");
+}
diff --git a/gdb/common/tdesc.h b/gdb/common/tdesc.h
index 8ab77e365f..45eb24ea2b 100644
--- a/gdb/common/tdesc.h
+++ b/gdb/common/tdesc.h
@@ -371,4 +371,33 @@ void tdesc_create_reg (struct tdesc_feature *feature, const char *name,
 		       int regnum, int save_restore, const char *group,
 		       int bitsize, const char *type);
 
+/* Return the tdesc in string XML format.  */
+
+const char *tdesc_get_features_xml (target_desc *tdesc);
+
+/* Print target description as xml.  */
+
+class print_xml_feature : public tdesc_element_visitor
+{
+public:
+  print_xml_feature (std::string &buffer_)
+    : m_buffer (buffer_)
+  {}
+
+  ~print_xml_feature ()
+  {}
+
+  void visit_pre (const target_desc *e) override;
+  void visit_post (const target_desc *e) override;
+  void visit_pre (const tdesc_feature *e) override;
+  void visit_post (const tdesc_feature *e) override;
+  void visit (const tdesc_type_builtin *type) override;
+  void visit (const tdesc_type_vector *type) override;
+  void visit (const tdesc_type_with_fields *type) override;
+  void visit (const tdesc_reg *reg) override;
+
+private:
+  std::string &m_buffer;
+};
+
 #endif /* ARCH_TDESC_H */
diff --git a/gdb/gdbserver/tdesc.c b/gdb/gdbserver/tdesc.c
index 69bde2ae42..5ec73d9117 100644
--- a/gdb/gdbserver/tdesc.c
+++ b/gdb/gdbserver/tdesc.c
@@ -56,6 +56,18 @@ bool target_desc::operator== (const target_desc &other) const
 
 #endif
 
+void target_desc::accept (tdesc_element_visitor &v) const
+{
+#ifndef IN_PROCESS_AGENT
+  v.visit_pre (this);
+
+  for (const tdesc_feature_up &feature : features)
+    feature->accept (v);
+
+  v.visit_post (this);
+#endif
+}
+
 void
 init_target_desc (struct target_desc *tdesc)
 {
@@ -162,8 +174,7 @@ set_tdesc_osabi (struct target_desc *target_desc, const char *name)
   target_desc->osabi = xstrdup (name);
 }
 
-/* Return a string which is of XML format, including XML target
-   description to be sent to GDB.  */
+/* See common/tdesc.h.  */
 
 const char *
 tdesc_get_features_xml (target_desc *tdesc)
@@ -175,31 +186,9 @@ tdesc_get_features_xml (target_desc *tdesc)
 
   if (tdesc->xmltarget == NULL)
     {
-      std::string buffer ("@<?xml version=\"1.0\"?>");
-
-      buffer += "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">";
-      buffer += "<target>";
-      buffer += "<architecture>";
-      buffer += tdesc_architecture_name (tdesc);
-      buffer += "</architecture>";
-
-      const char *osabi = tdesc_osabi_name (tdesc);
-      if (osabi != nullptr)
-	{
-	  buffer += "<osabi>";
-	  buffer += osabi;
-	  buffer += "</osabi>";
-	}
-
-      for (const tdesc_feature_up &feature : tdesc->features)
-	{
-	  buffer += "<xi:include href=\"";
-	  buffer += feature->name;
-	  buffer += "\"/>";
-	}
-
-      buffer += "</target>";
-
+      std::string buffer ("@");
+      print_xml_feature v (buffer);
+      tdesc->accept (v);
       tdesc->xmltarget = xstrdup (buffer.c_str ());
     }
 
diff --git a/gdb/gdbserver/tdesc.h b/gdb/gdbserver/tdesc.h
index 197fb59127..c00c252f70 100644
--- a/gdb/gdbserver/tdesc.h
+++ b/gdb/gdbserver/tdesc.h
@@ -27,7 +27,7 @@
 /* A target description.  Inherit from tdesc_feature so that target_desc
    can be used as tdesc_feature.  */
 
-struct target_desc
+struct target_desc : tdesc_element
 {
   /* A vector of elements of register definitions that
      describe the inferior's register set.  */
@@ -73,6 +73,8 @@ public:
     return !(*this == other);
   }
 #endif
+
+  void accept (tdesc_element_visitor &v) const;
 };
 
 /* Copy target description SRC to DEST.  */
diff --git a/gdb/regformats/regdat.sh b/gdb/regformats/regdat.sh
index 18108d77eb..8f546fe276 100755
--- a/gdb/regformats/regdat.sh
+++ b/gdb/regformats/regdat.sh
@@ -163,7 +163,9 @@ done
 
 echo
 echo "static const char *expedite_regs_${name}[] = { \"`echo ${expedite} | sed 's/,/", "/g'`\", 0 };"
-if test "${xmltarget}" = x; then
+if test "${feature}" != x; then
+  echo "static const char *xmltarget_${name} = 0;"
+elif test "${xmltarget}" = x; then
   if test "${xmlarch}" = x && test "${xmlosabi}" = x; then
     echo "static const char *xmltarget_${name} = 0;"
   else
diff --git a/gdb/target-descriptions.c b/gdb/target-descriptions.c
index da2c1ce345..467ae94e0c 100644
--- a/gdb/target-descriptions.c
+++ b/gdb/target-descriptions.c
@@ -333,6 +333,8 @@ struct target_desc : tdesc_element
   /* The features associated with this target.  */
   std::vector<tdesc_feature_up> features;
 
+  char *xmltarget = nullptr;
+
   void accept (tdesc_element_visitor &v) const override
   {
     v.visit_pre (this);
@@ -1667,6 +1669,21 @@ private:
   int m_next_regnum = 0;
 };
 
+/* See common/tdesc.h.  */
+
+const char *
+tdesc_get_features_xml (target_desc *tdesc)
+{
+  if (tdesc->xmltarget == nullptr)
+    {
+      std::string buffer ("@");
+      print_xml_feature v (buffer);
+      tdesc->accept (v);
+      tdesc->xmltarget = xstrdup (buffer.c_str ());
+    }
+  return tdesc->xmltarget;
+}
+
 static void
 maint_print_c_tdesc_cmd (const char *args, int from_tty)
 {
@@ -1760,7 +1777,36 @@ maintenance_check_xml_descriptions (const char *dir, int from_tty)
 	= file_read_description_xml (tdesc_xml.data ());
 
       if (tdesc == NULL || *tdesc != *e.second)
-	failed++;
+	{
+	  printf_filtered ( _("Descriptions for %s do not match\n"), e.first);
+	  failed++;
+	  continue;
+	}
+
+      /* Convert both descriptions to xml, and then back again.  Confirm all
+	 descriptions are identical.  */
+
+      const char *xml = tdesc_get_features_xml ((target_desc *) tdesc);
+      const char *xml2 = tdesc_get_features_xml ((target_desc *) e.second);
+      gdb_assert (*xml == '@');
+      gdb_assert (*xml2 == '@');
+      const target_desc *t_trans = target_read_description_xml_string (xml+1);
+      const target_desc *t_trans2 = target_read_description_xml_string (xml2+1);
+
+      if (t_trans == NULL || t_trans2 == NULL)
+	{
+	  printf_filtered (
+	    _("Could not convert descriptions for %s back to xml (%p %p)\n"),
+	    e.first, t_trans, t_trans2);
+	  failed++;
+	}
+      else if (*tdesc != *t_trans || *tdesc != *t_trans2)
+	{
+	  printf_filtered
+	    (_("Translated descriptions for %s do not match (%d %d)\n"),
+	    e.first, *tdesc == *t_trans, *tdesc == *t_trans2);
+	  failed++;
+	}
     }
   printf_filtered (_("Tested %lu XML files, %d failed\n"),
 		   (long) selftests::xml_tdesc.size (), failed);
diff --git a/gdb/xml-tdesc.c b/gdb/xml-tdesc.c
index 9190d5f3c6..f793f07c96 100644
--- a/gdb/xml-tdesc.c
+++ b/gdb/xml-tdesc.c
@@ -752,3 +752,12 @@ target_fetch_description_xml (struct target_ops *ops)
   return output;
 #endif
 }
+
+/* Take an xml string, parse it, and return the parsed description.  Does not
+   handle a string containing includes.  */
+
+const struct target_desc *
+target_read_description_xml_string (const char *xml_str)
+{
+  return tdesc_parse_xml (xml_str, nullptr, nullptr);
+}
diff --git a/gdb/xml-tdesc.h b/gdb/xml-tdesc.h
index 8f0679707a..fee60e86dd 100644
--- a/gdb/xml-tdesc.h
+++ b/gdb/xml-tdesc.h
@@ -44,5 +44,10 @@ const struct target_desc *target_read_description_xml (struct target_ops *);
    otherwise.  */
 gdb::optional<std::string> target_fetch_description_xml (target_ops *ops);
 
+/* Take an xml string, parse it, and return the parsed description.  Does not
+   handle a string containing includes.  */
+
+const struct target_desc *target_read_description_xml_string (const char *);
+
 #endif /* XML_TDESC_H */
 
