XML XInclude support

Daniel Jacobowitz
2007-01-29T21:32:44Z

This patch adds support for a limited subset (I confess, very limited)
of XInclude to GDB's target description parser. It's a simple textual
transform. A file that looks like this:

<target>
<xi:include href="other.xml"/>
</target>

is expanded to include the contents of other.xml instead of the
xi:include element, before parsing it as a target description.
For "set tdesc filename" other.xml should be in the same directory
as the original description. For qXfer:features, other.xml is
a new annex to read from the target.

This is really handy, because it allows descriptions to be broken up
into pieces. One place where we use it is when most of a description
is static, but part is determined at runtime by the remote debug stub -
it can include the correct elements for e.g. a target with or without
floating point registers once it knows which one it's got. This limits
the amount of XML the debug stub has to know about or generate.

It's also handy for reuseable bits of descriptions, like separating
the bit that describes architecture core registers from the bit that
describes whatever motivated you to write an XML description in the
first place.

Also included are documentation and test cases. I've tested this on
arm-none-linux-gnueabi and x86_64-pc-linux-gnu. The patch is pretty
uninteresting; if the documentation is OK, I plan to apply it when
the next couple of things I'm posting are ready.

Oh, if anyone has a better portable way to convert a blob of text into
a C array than my shell/awk hybrid, do let me know, thanks :-)

--
Daniel Jacobowitz
CodeSourcery

2007-01-29 Daniel Jacobowitz <dan@codesourcery.com>

* Makefile.in (XMLFILES): New.
(COMMON_OBS): Add xml-builtin.o.
(xml-builtin.c, stamp-xml): New rules.
(xml-tdesc.o): Update.
* features/feature_to_c.sh: New file.
* xml-support.c (MAX_XINCLUDE_DEPTH): Define.
(struct gdb_xml_parser): Add dtd_name and is_xinclude.
(gdb_xml_start_element): Initialize scope after possibly reallocating
scopes. Move cleanup later. Handle the XInclude description
specially.
(gdb_xml_end_element): Only parse the body if there is a current element.
Call XML_DefaultCurrent if there is no element.
(gdb_xml_fetch_external_entity, gdb_xml_use_dtd): New.
(struct xinclude_parsing_data, xinclude_start_include)
(xinclude_end_include, xml_xinclude_default)
(xml_xinclude_start_doctype, xml_xinclude_end_doctype)
(xml_xinclude_xml_decl, xml_xinclude_cleanup, xinclude_attributes)
(xinclude_elements, xml_process_xincludes, fetch_xml_builtin): New.
* xml-support.h (xml_fetch_another, xml_process_xincludes)
(fetch_xml_builtin, xml_builtin, gdb_xml_use_dtd): New declarations.
* xml-tdesc.c (tdesc_parse_xml): Add fetcher_baton argument. Expand
XInclude directives. Use the compiled in DTD.
(fetch_xml_from_file): Add baton argument. Treat it as a containing
directory name. Do not warn here.
(file_read_description_xml): Update call. Warn here instead. Pass
a dirname as baton.
(fetch_available_features_from_target): New.
(target_read_description_xml): Use it.
* features/gdb-target.dtd: Add copyright notice. Use xinclude.dtd
to handle XInclude.
* features/xinclude.dtd: New file.

* gdb.xml/bad-include.xml, gdb.xml/inc-2.xml, gdb.xml/inc-body.xml,
gdb.xml/includes.xml, gdb.xml/tdesc-xinclude.exp: New files.

* gdb.texinfo (Target Description Format): Add section on XInclude.

---
gdb/Makefile.in | 20 +
gdb/doc/gdb.texinfo | 21 +
gdb/features/feature_to_c.sh | 77 ++++++
gdb/features/gdb-target.dtd | 11
gdb/features/xinclude.dtd | 13 +
gdb/testsuite/gdb.xml/bad-include.xml | 1
gdb/testsuite/gdb.xml/inc-2.xml | 1
gdb/testsuite/gdb.xml/inc-body.xml | 1
gdb/testsuite/gdb.xml/includes.xml | 3
gdb/testsuite/gdb.xml/tdesc-xinclude.exp | 54 ++++
gdb/xml-support.c | 372 +++++++++++++++++++++++++++++--
gdb/xml-support.h | 34 ++
gdb/xml-tdesc.c | 98 ++++++--
13 files changed, 661 insertions(+), 45 deletions(-)

Index: src/gdb/Makefile.in
===================================================================
--- src.orig/gdb/Makefile.in 2007-01-29 12:37:14.000000000 -0500
+++ src/gdb/Makefile.in 2007-01-29 12:39:10.000000000 -0500
@@ -400,6 +400,9 @@ LINTFLAGS= $(GDB_CFLAGS) $(OPCODES_CFLAG
RUNTEST = runtest
RUNTESTFLAGS=

+# XML files to build in to GDB.
+XMLFILES = $(srcdir)/features/gdb-target.dtd $(srcdir)/features/xinclude.dtd
+
# This is ser-unix.o for any system which supports a v7/BSD/SYSV/POSIX
# interface to the serial port. Hopefully if get ported to OS/2, VMS,
# etc., then there will be (as part of the C library or perhaps as
@@ -972,7 +975,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $
tramp-frame.o \
solib.o solib-null.o \
prologue-value.o memory-map.o xml-support.o \
- target-descriptions.o target-memory.o xml-tdesc.o
+ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o

TSOBS = inflow.o

@@ -1681,6 +1684,19 @@ po/$(PACKAGE).pot: force
.PRECIOUS: objc-exp.c
.PRECIOUS: p-exp.c

+# XML rules
+
+xml-builtin.c: stamp-xml; @true
+stamp-xml: $(srcdir)/features/feature_to_c.sh Makefile $(XMLFILES)
+ rm -f xml-builtin.tmp
+ AWK="$(AWK)" \
+ $(SHELL) $(srcdir)/features/feature_to_c.sh \
+ xml-builtin.tmp $(XMLFILES)
+ $(SHELL) $(srcdir)/../move-if-change xml-builtin.tmp xml-builtin.c
+ echo stamp > stamp-xml
+
+.PRECIOUS: xml-builtin.c
+
#
# gdb/ dependencies
#
@@ -2876,7 +2892,7 @@ xcoffread.o: xcoffread.c $(defs_h) $(bfd
xcoffsolib.o: xcoffsolib.c $(defs_h) $(bfd_h) $(xcoffsolib_h) $(inferior_h) \
$(gdbcmd_h) $(symfile_h) $(frame_h) $(gdb_regex_h)
xml-tdesc.o: xml-tdesc.c $(defs_h) $(target_h) $(target_descriptions_h) \
- $(xml_tdesc_h) $(xml_support_h) $(gdb_assert_h)
+ $(xml_tdesc_h) $(xml_support_h) $(filenames_h) $(gdb_assert_h)
xml-support.o: xml-support.c $(defs_h) $(xml_support_h) $(exceptions_h) \
$(gdbcmd_h) $(gdb_string_h) $(gdb_expat_h) $(safe_ctype_h)
xstormy16-tdep.o: xstormy16-tdep.c $(defs_h) $(frame_h) $(frame_base_h) \
Index: src/gdb/features/feature_to_c.sh
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/features/feature_to_c.sh 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Convert text files to compilable C arrays.
+#
+# Copyright (C) 2007 Free Software Foundation, Inc.
+#
+# This file is part of GDB.
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+output=$1
+shift
+
+if test -z "$output" || test -z "$1"; then
+ echo "Usage: $0 OUTPUTFILE INPUTFILE..."
+ exit 1
+fi
+
+if test -e "$output"; then
+ echo "Output file \"$output\" already exists; refusing to overwrite."
+ exit 1
+fi
+
+for input; do
+ arrayname=xml_feature_`echo $input | sed 's,.*/,,; s/[-.]/_/g'`
+
+ ${AWK:-awk} 'BEGIN { n = 0
+ print "static const char '$arrayname'[] = {"
+ for (i = 0; i < 255; i++)
+ _ord_[sprintf("%c", i)] = i
+ } {
+ split($0, line, "");
+ printf " "
+ for (i = 1; i <= length($0); i++) {
+ c = line[i]
+ if (c == "'\''") {
+ printf "'\''\\'\'''\'', "
+ } else if (c == "\\") {
+ printf "'\''\\\\'\'', "
+ } else if (_ord_[c] >= 32 && _ord_[c] < 127) {
+ printf "'\''" c "'\'', "
+ } else {
+ printf "'\''\\%03o'\'', ", _ord_[c]
+ }
+ if (i % 10 == 0)
+ printf "\n "
+ }
+ printf "'\''\\n'\'', \n"
+ } END {
+ print " 0 };"
+ }' < $input >> $output
+done
+
+echo >> $output
+echo "const char *const xml_builtin[][2] = {" >> $output
+
+for input; do
+ basename=`echo $input | sed 's,.*/,,'`
+ arrayname=xml_feature_`echo $input | sed 's,.*/,,; s/[-.]/_/g'`
+ echo " { \"$basename\", $arrayname }," >> $output
+done
+
+echo " { 0, 0 }" >> $output
+echo "};" >> $output
Index: src/gdb/xml-support.c
===================================================================
--- src.orig/gdb/xml-support.c 2007-01-29 12:37:14.000000000 -0500
+++ src/gdb/xml-support.c 2007-01-29 12:39:10.000000000 -0500
@@ -36,6 +36,10 @@ static int debug_xml;
#include "gdb_string.h"
#include "safe-ctype.h"

+/* The maximum depth of <xi:include> nesting. No need to be miserly,
+ we just want to avoid running out of stack on loops. */
+#define MAX_XINCLUDE_DEPTH 30
+
/* A parsing level -- used to keep track of the current element
nesting. */
struct scope_level
@@ -68,6 +72,10 @@ struct gdb_xml_parser

struct gdb_exception error; /* A thrown error, if any. */
int last_line; /* The line of the thrown error, or 0. */
+
+ const char *dtd_name; /* The name of the expected / default DTD,
+ if specified. */
+ int is_xinclude; /* Are we the special <xi:include> parser? */
};

/* Process some body text. We accumulate the text for later use; it's
@@ -152,7 +160,7 @@ gdb_xml_start_element (void *data, const
const XML_Char **attrs)
{
struct gdb_xml_parser *parser = data;
- struct scope_level *scope = VEC_last (scope_level_s, parser->scopes);
+ struct scope_level *scope;
struct scope_level new_scope;
const struct gdb_xml_element *element;
const struct gdb_xml_attribute *attribute;
@@ -160,13 +168,13 @@ gdb_xml_start_element (void *data, const
unsigned int seen;
struct cleanup *back_to;

- back_to = make_cleanup (gdb_xml_values_cleanup, &attributes);
-
/* Push an error scope. If we return or throw an exception before
filling this in, it will tell us to ignore children of this
element. */
+ VEC_reserve (scope_level_s, parser->scopes, 1);
+ scope = VEC_last (scope_level_s, parser->scopes);
memset (&new_scope, 0, sizeof (new_scope));
- VEC_safe_push (scope_level_s, parser->scopes, &new_scope);
+ VEC_quick_push (scope_level_s, parser->scopes, &new_scope);

gdb_xml_debug (parser, _("Entering element <%s>"), name);

@@ -181,8 +189,21 @@ gdb_xml_start_element (void *data, const

if (element == NULL || element->name == NULL)
{
+ /* If we're working on XInclude, <xi:include> can be the child
+ of absolutely anything. Copy the previous scope's element
+ list into the new scope even if there was no match. */
+ if (parser->is_xinclude)
+ {
+ struct scope_level *unknown_scope;
+
+ XML_DefaultCurrent (parser->expat_parser);
+
+ unknown_scope = VEC_last (scope_level_s, parser->scopes);
+ unknown_scope->elements = scope->elements;
+ return;
+ }
+
gdb_xml_debug (parser, _("Element <%s> unknown"), name);
- do_cleanups (back_to);
return;
}

@@ -191,6 +212,8 @@ gdb_xml_start_element (void *data, const

scope->seen |= seen;

+ back_to = make_cleanup (gdb_xml_values_cleanup, &attributes);
+
for (attribute = element->attributes;
attribute != NULL && attribute->name != NULL;
attribute++)
@@ -304,7 +327,6 @@ gdb_xml_end_element (void *data, const X
struct scope_level *scope = VEC_last (scope_level_s, parser->scopes);
const struct gdb_xml_element *element;
unsigned int seen;
- char *body;

gdb_xml_debug (parser, _("Leaving element <%s>"), name);

@@ -317,26 +339,32 @@ gdb_xml_end_element (void *data, const X
element->name);

/* Call the element processor. */
- if (scope->body == NULL)
- body = "";
- else
+ if (scope->element != NULL && scope->element->end_handler)
{
- int length;
+ char *body;

- length = obstack_object_size (scope->body);
- obstack_1grow (scope->body, '\0');
- body = obstack_finish (scope->body);
-
- /* Strip leading and trailing whitespace. */
- while (length > 0 && ISSPACE (body[length-1]))
- body[--length] = '\0';
- while (*body && ISSPACE (*body))
- body++;
- }
+ if (scope->body == NULL)
+ body = "";
+ else
+ {
+ int length;

- if (scope->element != NULL && scope->element->end_handler)
- scope->element->end_handler (parser, scope->element, parser->user_data,
- body);
+ length = obstack_object_size (scope->body);
+ obstack_1grow (scope->body, '\0');
+ body = obstack_finish (scope->body);
+
+ /* Strip leading and trailing whitespace. */
+ while (length > 0 && ISSPACE (body[length-1]))
+ body[--length] = '\0';
+ while (*body && ISSPACE (*body))
+ body++;
+ }
+
+ scope->element->end_handler (parser, scope->element, parser->user_data,
+ body);
+ }
+ else if (scope->element == NULL)
+ XML_DefaultCurrent (parser->expat_parser);

/* Pop the scope level. */
if (scope->body)
@@ -435,6 +463,74 @@ gdb_xml_create_parser_and_cleanup (const
return parser;
}

+/* External entity handler. The only external entities we support
+ are those compiled into GDB (we do not fetch entities from the
+ target). */
+
+static int XMLCALL
+gdb_xml_fetch_external_entity (XML_Parser expat_parser,
+ const XML_Char *context,
+ const XML_Char *base,
+ const XML_Char *systemId,
+ const XML_Char *publicId)
+{
+ struct gdb_xml_parser *parser = XML_GetUserData (expat_parser);
+ XML_Parser entity_parser;
+ const char *text;
+ enum XML_Status status;
+
+ if (systemId == NULL)
+ {
+ text = fetch_xml_builtin (parser->dtd_name);
+ if (text == NULL)
+ internal_error (__FILE__, __LINE__, "could not locate built-in DTD %s",
+ parser->dtd_name);
+ }
+ else
+ {
+ text = fetch_xml_builtin (systemId);
+ if (text == NULL)
+ return XML_STATUS_ERROR;
+ }
+
+ entity_parser = XML_ExternalEntityParserCreate (expat_parser, context, NULL);
+
+ /* Don't use our handlers for the contents of the DTD. Just let expat
+ process it. */
+ XML_SetElementHandler (entity_parser, NULL, NULL);
+ XML_SetDoctypeDeclHandler (entity_parser, NULL, NULL);
+ XML_SetXmlDeclHandler (entity_parser, NULL);
+ XML_SetDefaultHandler (entity_parser, NULL);
+ XML_SetUserData (entity_parser, NULL);
+
+ status = XML_Parse (entity_parser, text, strlen (text), 1);
+
+ XML_ParserFree (entity_parser);
+ return status;
+}
+
+/* Associate DTD_NAME, which must be the name of a compiled-in DTD,
+ with PARSER. */
+
+void
+gdb_xml_use_dtd (struct gdb_xml_parser *parser, const char *dtd_name)
+{
+ enum XML_Error err;
+
+ parser->dtd_name = dtd_name;
+
+ XML_SetParamEntityParsing (parser->expat_parser,
+ XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE);
+ XML_SetExternalEntityRefHandler (parser->expat_parser,
+ gdb_xml_fetch_external_entity);
+
+ /* Even if no DTD is provided, use the built-in DTD anyway. */
+ err = XML_UseForeignDTD (parser->expat_parser, XML_TRUE);
+ if (err != XML_ERROR_NONE)
+ internal_error (__FILE__, __LINE__,
+ "XML_UseForeignDTD failed: %s", XML_ErrorString (err));
+}
+
/* Invoke PARSER on BUFFER. BUFFER is the data to parse, which
should be NUL-terminated.

@@ -560,6 +656,236 @@ gdb_xml_parse_attr_enum (struct gdb_xml_
memcpy (ret, &enums->value, sizeof (enums->value));
return ret;
}
+\f
+
+/* XInclude processing. This is done as a separate step from actually
+ parsing the document, so that we can produce a single combined XML
+ document - e.g. to hand to a front end or to simplify comparing two
+ documents. We make extensive use of XML_DefaultCurrent, to pass
+ input text directly into the output without reformatting or
+ requoting it.
+
+ We output the DOCTYPE declaration for the first document unchanged,
+ if present, and discard DOCTYPEs from included documents. Only the
+ one we pass through here is used when we feed the result back to
+ expat. The XInclude standard explicitly does not discuss
+ validation of the result; we choose to apply the same DTD applied
+ to the outermost document.
+
+ We can not simply include the external DTD subset in the document
+ as an internal subset, because <!IGNORE> and <!INCLUDE> are valid
+ only in external subsets. But if we do not pass the DTD into the
+ output at all, default values will not be filled in.
+
+ We don't pass through any <?xml> declaration because we generate
+ UTF-8, not whatever the input encoding was. */
+
+struct xinclude_parsing_data
+{
+ /* The obstack to build the output in. */
+ struct obstack obstack;
+
+ /* A count indicating whether we are in an element whose
+ children should not be copied to the output, and if so,
+ how deep we are nested. This is used for anything inside
+ an xi:include, and for the DTD. */
+ int skip_depth;
+
+ /* The number of <xi:include> elements currently being processed,
+ to detect loops. */
+ int include_depth;
+
+ /* A function to call to obtain additional features, and its
+ baton. */
+ xml_fetch_another fetcher;
+ void *fetcher_baton;
+};
+
+static void
+xinclude_start_include (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+ struct xinclude_parsing_data *data = user_data;
+ char *href = VEC_index (gdb_xml_value_s, attributes, 0)->value;
+ struct cleanup *back_to;
+ char *text, *output;
+ int ret;
+
+ gdb_xml_debug (parser, _("Processing XInclude of \"%s\""), href);
+
+ if (data->include_depth > MAX_XINCLUDE_DEPTH)
+ gdb_xml_error (parser, _("Maximum XInclude depth (%d) exceeded"),
+ MAX_XINCLUDE_DEPTH);
+
+ text = data->fetcher (href, data->fetcher_baton);
+ if (text == NULL)
+ gdb_xml_error (parser, _("Could not load XML document \"%s\""), href);
+ back_to = make_cleanup (xfree, text);
+
+ output = xml_process_xincludes (parser->name, text, data->fetcher,
+ data->fetcher_baton,
+ data->include_depth + 1);
+ if (output == NULL)
+ gdb_xml_error (parser, _("Parsing \"%s\" failed"), href);
+
+ obstack_grow (&data->obstack, output, strlen (output));
+ xfree (output);
+
+ do_cleanups (back_to);
+
+ data->skip_depth++;
+}
+
+static void
+xinclude_end_include (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, const char *body_text)
+{
+ struct xinclude_parsing_data *data = user_data;
+
+ data->skip_depth--;
+}
+
+static void XMLCALL
+xml_xinclude_default (void *data_, const XML_Char *s, int len)
+{
+ struct gdb_xml_parser *parser = data_;
+ struct xinclude_parsing_data *data = parser->user_data;
+
+ /* If we are inside of e.g. xi:include or the DTD, don't save this
+ string. */
+ if (data->skip_depth)
+ return;
+
+ /* Otherwise just add it to the end of the document we're building
+ up. */
+ obstack_grow (&data->obstack, s, len);
+}
+
+static void XMLCALL
+xml_xinclude_start_doctype (void *data_, const XML_Char *doctypeName,
+ const XML_Char *sysid, const XML_Char *pubid,
+ int has_internal_subset)
+{
+ struct gdb_xml_parser *parser = data_;
+ struct xinclude_parsing_data *data = parser->user_data;
+
+ /* Don't print out the doctype, or the contents of the DTD internal
+ subset, if any. */
+ data->skip_depth++;
+}
+
+static void XMLCALL
+xml_xinclude_end_doctype (void *data_)
+{
+ struct gdb_xml_parser *parser = data_;
+ struct xinclude_parsing_data *data = parser->user_data;
+
+ data->skip_depth--;
+}
+
+static void XMLCALL
+xml_xinclude_xml_decl (void *data_, const XML_Char *version,
+ const XML_Char *encoding, int standalone)
+{
+ /* Do nothing - this function prevents the default handler from
+ being called, thus suppressing the XML declaration from the
+ output. */
+}
+
+static void
+xml_xinclude_cleanup (void *data_)
+{
+ struct xinclude_parsing_data *data = data_;
+
+ obstack_free (&data->obstack, NULL);
+ xfree (data);
+}
+
+const struct gdb_xml_attribute xinclude_attributes[] = {
+ { "href", GDB_XML_AF_NONE, NULL, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element xinclude_elements[] = {
+ { "http://www.w3.org/2001/XInclude!include", xinclude_attributes, NULL,
+ GDB_XML_EF_OPTIONAL | GDB_XML_EF_REPEATABLE,
+ xinclude_start_include, xinclude_end_include },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+/* The main entry point for <xi:include> processing. */
+
+char *
+xml_process_xincludes (const char *name, const char *text,
+ xml_fetch_another fetcher, void *fetcher_baton,
+ int depth)
+{
+ enum XML_Error err;
+ struct gdb_xml_parser *parser;
+ struct xinclude_parsing_data *data;
+ struct cleanup *back_to;
+ char *result = NULL;
+
+ data = XZALLOC (struct xinclude_parsing_data);
+ obstack_init (&data->obstack);
+ back_to = make_cleanup (xml_xinclude_cleanup, data);
+
+ parser = gdb_xml_create_parser_and_cleanup (name, xinclude_elements, data);
+ parser->is_xinclude = 1;
+
+ data->include_depth = depth;
+ data->fetcher = fetcher;
+ data->fetcher_baton = fetcher_baton;
+
+ XML_SetCharacterDataHandler (parser->expat_parser, NULL);
+ XML_SetDefaultHandler (parser->expat_parser, xml_xinclude_default);
+
+ /* Always discard the XML version declarations; the only important
+ thing this provides is encoding, and our result will have been
+ converted to UTF-8. */
+ XML_SetXmlDeclHandler (parser->expat_parser, xml_xinclude_xml_decl);
+
+ if (depth > 0)
+ /* Discard the doctype for included documents. */
+ XML_SetDoctypeDeclHandler (parser->expat_parser,
+ xml_xinclude_start_doctype,
+ xml_xinclude_end_doctype);
+
+ gdb_xml_use_dtd (parser, "xinclude.dtd");
+
+ if (gdb_xml_parse (parser, text) == 0)
+ {
+ obstack_1grow (&data->obstack, '\0');
+ result = xstrdup (obstack_finish (&data->obstack));
+
+ if (depth == 0)
+ gdb_xml_debug (parser, _("XInclude processing succeeded:\n%s"),
+ result);
+ }
+ else
+ result = NULL;
+
+ do_cleanups (back_to);
+ return result;
+}
+\f
+
+/* Return an XML document which was compiled into GDB, from
+ the given FILENAME, or NULL if the file was not compiled in. */
+
+const char *
+fetch_xml_builtin (const char *filename)
+{
+ const char *(*p)[2];
+
+ for (p = xml_builtin; (*p)[0]; p++)
+ if (strcmp ((*p)[0], filename) == 0)
+ return (*p)[1];
+
+ return NULL;
+}

#endif /* HAVE_LIBEXPAT */

Index: src/gdb/xml-support.h
===================================================================
--- src.orig/gdb/xml-support.h 2007-01-29 12:37:14.000000000 -0500
+++ src/gdb/xml-support.h 2007-01-29 12:39:10.000000000 -0500
@@ -30,6 +30,35 @@ struct gdb_xml_parser;
struct gdb_xml_element;
struct gdb_xml_attribute;

+/* Support for XInclude. */
+
+/* Callback to fetch a new XML file, based on the provided HREF. */
+
+typedef char *(*xml_fetch_another) (const char *href, void *baton);
+
+/* Return a new string which is the expansion of TEXT after processing
+ <xi:include> tags. FETCHER will be called (with FETCHER_BATON) to
+ retrieve any new files. DEPTH should be zero on the initial call.
+
+ On failure, this function uses NAME in a warning and returns NULL.
+ It may throw an exception, but does not for XML parsing
+ problems. */
+
+char *xml_process_xincludes (const char *name, const char *text,
+ xml_fetch_another fetcher, void *fetcher_baton,
+ int depth);
+
+/* Return an XML document which was compiled into GDB, from
+ the given FILENAME, or NULL if the file was not compiled in. */
+
+const char *fetch_xml_builtin (const char *filename);
+
+/* The text of compiled-in XML documents, from xml-builtin.c
+ (generated). */
+extern const char *xml_builtin[][2];
+
+/* Simplified XML parser infrastructure. */
+
/* A name and value pair, used to record parsed attributes. */

struct gdb_xml_value
@@ -140,6 +169,11 @@ struct gdb_xml_parser *gdb_xml_create_pa
(const char *name, const struct gdb_xml_element *elements,
void *user_data);

+/* Associate DTD_NAME, which must be the name of a compiled-in DTD,
+ with PARSER. */
+
+void gdb_xml_use_dtd (struct gdb_xml_parser *parser, const char *dtd_name);
+
/* Invoke PARSER on BUFFER. BUFFER is the data to parse, which
should be NUL-terminated.

Index: src/gdb/xml-tdesc.c
===================================================================
--- src.orig/gdb/xml-tdesc.c 2007-01-29 12:37:14.000000000 -0500
+++ src/gdb/xml-tdesc.c 2007-01-29 12:39:10.000000000 -0500
@@ -28,6 +28,8 @@
#include "xml-support.h"
#include "xml-tdesc.h"

+#include "filenames.h"
+
#include "gdb_assert.h"

#if !defined(HAVE_LIBEXPAT)
@@ -36,7 +38,8 @@
an XML parser. */

static struct target_desc *
-tdesc_parse_xml (const char *document)
+tdesc_parse_xml (const char *document, xml_fetch_another fetcher,
+ void *fetcher_baton)
{
static int have_warned;

@@ -94,22 +97,33 @@ const struct gdb_xml_element tdesc_eleme
/* Parse DOCUMENT into a target description and return it. */

static struct target_desc *
-tdesc_parse_xml (const char *document)
+tdesc_parse_xml (const char *document, xml_fetch_another fetcher,
+ void *fetcher_baton)
{
struct cleanup *back_to, *result_cleanup;
struct gdb_xml_parser *parser;
struct tdesc_parsing_data data;
+ char *expanded_text;

- memset (&data, 0, sizeof (struct tdesc_parsing_data));
+ /* Expand all XInclude directives. */
+ expanded_text = xml_process_xincludes (_("target description"),
+ document, fetcher, fetcher_baton, 0);
+ if (expanded_text == NULL)
+ {
+ warning (_("Could not load XML target description; ignoring"));
+ return NULL;
+ }
+ back_to = make_cleanup (xfree, expanded_text);

- back_to = make_cleanup (null_cleanup, NULL);
parser = gdb_xml_create_parser_and_cleanup (_("target description"),
tdesc_elements, &data);
+ gdb_xml_use_dtd (parser, "gdb-target.dtd");

+ memset (&data, 0, sizeof (struct tdesc_parsing_data));
data.tdesc = allocate_target_description ();
result_cleanup = make_cleanup_free_target_description (data.tdesc);

- if (gdb_xml_parse (parser, document) == 0)
+ if (gdb_xml_parse (parser, expanded_text) == 0)
{
/* Parsed successfully. */
discard_cleanups (result_cleanup);
@@ -123,7 +137,6 @@ tdesc_parse_xml (const char *document)
return NULL;
}
}
-
#endif /* HAVE_LIBEXPAT */
\f

@@ -139,19 +152,28 @@ do_cleanup_fclose (void *file)
the text. If something goes wrong, return NULL and warn. */

static char *
-fetch_xml_from_file (const char *filename)
+fetch_xml_from_file (const char *filename, void *baton)
{
+ const char *dirname = baton;
FILE *file;
struct cleanup *back_to;
char *text;
size_t len, offset;

- file = fopen (filename, FOPEN_RT);
- if (file == NULL)
+ if (dirname && *dirname)
{
- warning (_("Could not open \"%s\""), filename);
- return NULL;
+ char *fullname = concat (dirname, "/", filename, NULL);
+ if (fullname == NULL)
+ nomem (0);
+ file = fopen (fullname, FOPEN_RT);
+ xfree (fullname);
}
+ else
+ file = fopen (filename, FOPEN_RT);
+
+ if (file == NULL)
+ return NULL;
+
back_to = make_cleanup (do_cleanup_fclose, file);

/* Read in the whole file, one chunk at a time. */
@@ -198,18 +220,59 @@ file_read_description_xml (const char *f
struct target_desc *tdesc;
char *tdesc_str;
struct cleanup *back_to;
+ const char *base;
+ char *dirname;

- tdesc_str = fetch_xml_from_file (filename);
+ tdesc_str = fetch_xml_from_file (filename, NULL);
if (tdesc_str == NULL)
- return NULL;
+ {
+ warning (_("Could not open \"%s\""), filename);
+ return NULL;
+ }

back_to = make_cleanup (xfree, tdesc_str);
- tdesc = tdesc_parse_xml (tdesc_str);
+
+ /* Simple, portable version of dirname that does not modify its
+ argument. */
+ base = lbasename (filename);
+ while (base > filename && IS_DIR_SEPARATOR (base[-1]))
+ --base;
+ if (base > filename)
+ {
+ dirname = xmalloc (base - filename + 1);
+ memcpy (dirname, filename, base - filename);
+ dirname[base - filename] = '\0';
+ make_cleanup (xfree, dirname);
+ }
+ else
+ dirname = NULL;
+
+ tdesc = tdesc_parse_xml (tdesc_str, fetch_xml_from_file, dirname);
do_cleanups (back_to);

return tdesc;
}

+/* Read a string representation of available features from the target,
+ using TARGET_OBJECT_AVAILABLE_FEATURES. The returned string is
+ malloc allocated and NUL-terminated. NAME should be a non-NULL
+ string identifying the XML document we want; the top level document
+ is "target.xml". Other calls may be performed for the DTD or
+ for <xi:include>. */
+
+static char *
+fetch_available_features_from_target (const char *name, void *baton_)
+{
+ struct target_ops *ops = baton_;
+
+ /* Read this object as a string. This ensures that a NUL
+ terminator is added. */
+ return target_read_stralloc (ops,
+ TARGET_OBJECT_AVAILABLE_FEATURES,
+ name);
+}
+\f
+
/* Read an XML target description using OPS. Parse it, and return the
parsed description. */

@@ -220,13 +283,14 @@ target_read_description_xml (struct targ
char *tdesc_str;
struct cleanup *back_to;

- tdesc_str = target_read_stralloc (ops, TARGET_OBJECT_AVAILABLE_FEATURES,
- "target.xml");
+ tdesc_str = fetch_available_features_from_target ("target.xml", ops);
if (tdesc_str == NULL)
return NULL;

back_to = make_cleanup (xfree, tdesc_str);
- tdesc = tdesc_parse_xml (tdesc_str);
+ tdesc = tdesc_parse_xml (tdesc_str,
+ fetch_available_features_from_target,
+ ops);
do_cleanups (back_to);

return tdesc;
Index: src/gdb/features/gdb-target.dtd
===================================================================
--- src.orig/gdb/features/gdb-target.dtd 2007-01-29 12:37:14.000000000 -0500
+++ src/gdb/features/gdb-target.dtd 2007-01-29 12:39:10.000000000 -0500
@@ -1,9 +1,14 @@
+<!-- Copyright (C) 2007 Free Software Foundation, Inc.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
<!-- The root element of a GDB target description is <target>. -->

<!ELEMENT target (architecture?)>
-<!ATTLIST target
- xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude">

<!ELEMENT architecture (#PCDATA)>

-
+<!ENTITY % xinclude SYSTEM "xinclude.dtd">
+%xinclude;
Index: src/gdb/features/xinclude.dtd
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/features/xinclude.dtd 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1,13 @@
+<!-- Copyright (C) 2007 Free Software Foundation, Inc.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
+<!-- GDB supports a subset of XInclude. Only whole documents can
+ be included, and only as XML. -->
+
+<!ELEMENT xi:include (EMPTY)>
+<!ATTLIST xi:include
+ xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude"
+ href CDATA #REQUIRED>
Index: src/gdb/testsuite/gdb.xml/bad-include.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.xml/bad-include.xml 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1 @@
+<xi:include href="nonexistant.xml"/>
Index: src/gdb/testsuite/gdb.xml/inc-2.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.xml/inc-2.xml 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1 @@
+<xi:include href="inc-body.xml"/>
Index: src/gdb/testsuite/gdb.xml/inc-body.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.xml/inc-body.xml 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1 @@
+<architecture>invalid-body</architecture>
Index: src/gdb/testsuite/gdb.xml/includes.xml
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.xml/includes.xml 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1,3 @@
+<target>
+ <xi:include href="inc-2.xml"/>
+</target>
Index: src/gdb/testsuite/gdb.xml/tdesc-xinclude.exp
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.xml/tdesc-xinclude.exp 2007-01-29 12:39:10.000000000 -0500
@@ -0,0 +1,54 @@
+# Copyright 2007 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+if {[gdb_skip_xml_test]} {
+ unsupported "tdesc-errors.exp"
+ return -1
+}
+
+gdb_start
+
+proc set_arch { srcfile errmsg } {
+ global gdb_prompt
+ global srcdir
+ global subdir
+
+ # Anchor the test output, so that error messages are detected.
+ set cmd "set tdesc filename $srcdir/$subdir/$srcfile"
+ set msg $cmd
+ set cmd_regex [string_to_regexp $cmd]
+ gdb_test_multiple $cmd $msg {
+ -re "^$cmd_regex\r\n$errmsg$gdb_prompt $" {
+ pass $msg
+ }
+ }
+}
+
+set common_warn "\r\nwarning: Could not load XML target description; ignoring\r\n"
+
+# This file is valid, but specifies an unknown architecture.
+# We should get a warning, if we process the includes correctly.
+set_arch "includes.xml" \
+ "warning:.*specified unknown architecture.* \"invalid-body\"$common_warn"
+
+# This file contains a missing include. We should warn the user about
+# it.
+set_arch "bad-include.xml" \
+ "warning:.*Could not load XML document \"nonexistant.xml\"$common_warn"
+
+# Make sure we detect infinite loops, eventually.
+set_arch "loop.xml" \
+ "warning:.*Maximum XInclude depth.*$common_warn"
Index: src/gdb/doc/gdb.texinfo
===================================================================
--- src.orig/gdb/doc/gdb.texinfo 2007-01-29 12:37:52.000000000 -0500
+++ src/gdb/doc/gdb.texinfo 2007-01-29 12:39:10.000000000 -0500
@@ -25781,6 +25781,27 @@ The content of the @samp{<architecture>}
name, from the same selection accepted by @code{set architecture}
(@pxref{Targets, ,Specifying a Debugging Target}).

+@subsection Inclusion
+
+It can sometimes be valuable to split a target description up into
+several different annexes, either for organizational purposes, or to
+share files between different possible target descriptions. You can
+divide a description into multiple files by replacing any element of
+the target description with an inclusion directive of the form:
+
+@example
+<xi:include href="@var{name}"/>
+@end example
+
+@noindent
+When @value{GDBN} encounters an element of this form, it will retrieve
+the XML document named @var{name}, and replace the inclusion directive
+with the contents of that annex. If the current description was read
+using @samp{qXfer}, then the new document will be also, using
+@var{name} as the annex. If the current description was read from a
+file, @value{GDBN} will look for a file named @var{name} in the same
+directory as the original file.
+

@include gpl.texi