GNU Binutils with patches for OS216
Révision | 1cd475528393f4c4c01be7c654990d578247295f (tree) |
---|---|
l'heure | 2006-03-14 22:58:41 |
Auteur | Daniel Jacobowitz <drow@fals...> |
Commiter | Daniel Jacobowitz |
Add RAM caching, checksum, and non-sequential register support.
@@ -609,6 +609,7 @@ gdb_sim_sh_h = $(INCLUDE_DIR)/gdb/sim-sh.h | ||
609 | 609 | splay_tree_h = $(INCLUDE_DIR)/splay-tree.h |
610 | 610 | safe_ctype_h = $(INCLUDE_DIR)/safe-ctype.h |
611 | 611 | hashtab_h = $(INCLUDE_DIR)/hashtab.h |
612 | +filenames_h = $(INCLUDE_DIR)/filenames.h | |
612 | 613 | |
613 | 614 | # |
614 | 615 | # $BUILD/ headers |
@@ -1794,8 +1795,8 @@ auxv.o: auxv.c $(defs_h) $(target_h) $(gdbtypes_h) $(command_h) \ | ||
1794 | 1795 | $(inferior_h) $(valprint_h) $(gdb_assert_h) $(auxv_h) \ |
1795 | 1796 | $(elf_common_h) |
1796 | 1797 | available.o: available.c $(defs_h) $(symfile_h) $(target_h) $(available_h) \ |
1797 | - $(arch_utils_h) $(gdbtypes_h) \ | |
1798 | - $(gdb_string) $(gdb_assert) $(gdb_obstack_h) | |
1798 | + $(arch_utils_h) $(gdbtypes_h) $(sha1_h) \ | |
1799 | + $(gdb_string) $(gdb_assert) $(gdb_obstack_h) $(gdb_stdint_h) | |
1799 | 1800 | avr-tdep.o: avr-tdep.c $(defs_h) $(frame_h) $(frame_unwind_h) \ |
1800 | 1801 | $(frame_base_h) $(trad_frame_h) $(gdbcmd_h) $(gdbcore_h) \ |
1801 | 1802 | $(inferior_h) $(symfile_h) $(arch_utils_h) $(regcache_h) \ |
@@ -2410,8 +2411,8 @@ parse.o: parse.c $(defs_h) $(gdb_string_h) $(symtab_h) $(gdbtypes_h) \ | ||
2410 | 2411 | $(frame_h) $(expression_h) $(value_h) $(command_h) $(language_h) \ |
2411 | 2412 | $(parser_defs_h) $(gdbcmd_h) $(symfile_h) $(inferior_h) \ |
2412 | 2413 | $(doublest_h) $(gdb_assert_h) $(block_h) |
2413 | -parse-avail.o: parse-avail.c $(defs_h) $(available_h) \ | |
2414 | - $(gdb_string) $(gdb_obstack_h) | |
2414 | +parse-avail.o: parse-avail.c $(defs_h) $(available_h) $(gdb_assert_h) \ | |
2415 | + $(filenames_h) $(gdb_string) $(gdb_obstack_h) | |
2415 | 2416 | p-exp.o: p-exp.c $(defs_h) $(gdb_string_h) $(expression_h) $(value_h) \ |
2416 | 2417 | $(parser_defs_h) $(language_h) $(p_lang_h) $(bfd_h) $(symfile_h) \ |
2417 | 2418 | $(objfiles_h) $(block_h) |
@@ -27,12 +27,14 @@ | ||
27 | 27 | #include "gdbtypes.h" |
28 | 28 | #include "symfile.h" |
29 | 29 | #include "target.h" |
30 | +#include "sha1.h" | |
30 | 31 | |
31 | 32 | #include "available.h" |
32 | 33 | |
33 | 34 | #include "gdb_assert.h" |
34 | 35 | #include "gdb_string.h" |
35 | 36 | #include "gdb_obstack.h" |
37 | +#include "gdb_stdint.h" | |
36 | 38 | |
37 | 39 | /* TODO: Remote target "guess" features from g packet size */ |
38 | 40 |
@@ -53,26 +55,184 @@ | ||
53 | 55 | to initialize a gdbarch, which leads to later failures when we expect |
54 | 56 | e.g. current_regcache to have been initialized. */ |
55 | 57 | |
58 | + | |
59 | + | |
60 | +/* Support for caching XML objects read from the target. | |
61 | + | |
62 | + TODO ITEMS: | |
63 | + - Support caching to disk. | |
64 | + - Support compiled-in feature cache. | |
65 | + - Figure out memory management for feature contents strings. | |
66 | +*/ | |
67 | + | |
68 | + | |
69 | +/* Saved information about cached XML objects. Each cache entry | |
70 | + corresponds to a file in the cache, or an object fetched from | |
71 | + the target with one particular annex. */ | |
72 | + | |
73 | +struct xml_cache_entry | |
74 | +{ | |
75 | + const char *annex; | |
76 | + const char *contents; | |
77 | + | |
78 | + union | |
79 | + { | |
80 | + /* We use a union to represent the checksum in order to guarantee | |
81 | + sufficient alignment. */ | |
82 | + uint32_t words[5]; | |
83 | + unsigned char bytes[20]; | |
84 | + } sha1sum; | |
85 | + | |
86 | + struct xml_cache_entry *next; | |
87 | +}; | |
88 | + | |
89 | +/* A list of all the cached objects. */ | |
90 | + | |
91 | +static struct xml_cache_entry *xml_global_cache; | |
92 | + | |
93 | +/* Look for a feature in the cache with ANNEX and CHECKSUM. If no | |
94 | + entry is found, return NULL. */ | |
95 | + | |
96 | +static const char * | |
97 | +find_xml_feature_in_cache (const char *annex, const unsigned char *checksum) | |
98 | +{ | |
99 | + struct xml_cache_entry *ent; | |
100 | + | |
101 | + for (ent = xml_global_cache; ent != NULL; ent = ent->next) | |
102 | + { | |
103 | + if (strcmp (ent->annex, annex) != 0) | |
104 | + continue; | |
105 | + if (memcmp (ent->sha1sum.bytes, checksum, 20) != 0) | |
106 | + continue; | |
107 | + | |
108 | + return ent->contents; | |
109 | + } | |
110 | + | |
111 | + return NULL; | |
112 | +} | |
113 | + | |
114 | +/* Add CONTENTS, which represents the object named ANNEX, to the | |
115 | + cache if it is not already cached. */ | |
116 | + | |
117 | +static void | |
118 | +add_xml_feature_to_cache (const char *annex, const char *contents) | |
119 | +{ | |
120 | + struct xml_cache_entry new_ent; | |
121 | + | |
122 | + /* FIXME: Again, memory allocation? */ | |
123 | + new_ent.annex = xstrdup (annex); | |
124 | + new_ent.contents = xstrdup (contents); | |
125 | + | |
126 | + sha1_buffer (new_ent.contents, strlen (new_ent.contents), | |
127 | + new_ent.sha1sum.bytes); | |
128 | + | |
129 | + /* If this entry is already in the cache, do not add it again. */ | |
130 | + if (find_xml_feature_in_cache (annex, new_ent.sha1sum.bytes)) | |
131 | + return; | |
132 | + | |
133 | + new_ent.next = xml_global_cache; | |
134 | + | |
135 | + xml_global_cache = xmalloc (sizeof (struct xml_cache_entry)); | |
136 | + memcpy (xml_global_cache, &new_ent, sizeof (struct xml_cache_entry)); | |
137 | +} | |
138 | + | |
139 | +/* Convert an ASCII checksum string, CHECKSUM, to a binary blob, | |
140 | + BYTES. Returns 0 for success, or -1 if a bad character is | |
141 | + encountered. CHECKSUM does not need to be NUL terminated. */ | |
142 | + | |
143 | +static int | |
144 | +checksum_to_bytes (char *checksum, unsigned char *bytes) | |
145 | +{ | |
146 | + int i; | |
147 | + | |
148 | + for (i = 0; i < 20; i++) | |
149 | + { | |
150 | + int n; | |
151 | + char c1, c2; | |
152 | + | |
153 | + c1 = checksum[2 * i]; | |
154 | + if (c1 >= '0' && c1 <= '9') | |
155 | + n = c1 - '0'; | |
156 | + else if (c1 >= 'a' && c1 <= 'f') | |
157 | + n = c1 - 'a' + 10; | |
158 | + else if (c1 >= 'A' && c1 <= 'F') | |
159 | + n = c1 - 'A' + 10; | |
160 | + else | |
161 | + return -1; | |
162 | + | |
163 | + n *= 16; | |
164 | + | |
165 | + c2 = checksum[2 * i + 1]; | |
166 | + if (c2 >= '0' && c2 <= '9') | |
167 | + n += c2 - '0'; | |
168 | + else if (c2 >= 'a' && c2 <= 'f') | |
169 | + n += c2 - 'a' + 10; | |
170 | + else if (c2 >= 'A' && c2 <= 'F') | |
171 | + n += c2 - 'A' + 10; | |
172 | + else | |
173 | + return -1; | |
174 | + | |
175 | + bytes[i] = n; | |
176 | + } | |
177 | + | |
178 | + return 0; | |
179 | +} | |
180 | + | |
181 | +/* Baton passed to fetch_available_features_from_target. */ | |
182 | + | |
183 | +struct fetch_features_baton | |
184 | +{ | |
185 | + struct target_ops *ops; | |
186 | + | |
187 | + struct fetch_features_checksum | |
188 | + { | |
189 | + const char *annex; | |
190 | + unsigned char checksum[20]; | |
191 | + } *checksums; | |
192 | +}; | |
193 | + | |
56 | 194 | /* Read a string representation of available features from the target, |
57 | 195 | using TARGET_OBJECT_AVAILABLE_FEATURES. The returned string is |
58 | - malloc allocated and NUL-terminated. If NAME is NULL, the overall | |
59 | - feature set is read; otherwise the specified name is read (e.g. | |
60 | - resolving xi:include). */ | |
196 | + malloc allocated and NUL-terminated. NAME should be a non-NULL | |
197 | + string identifying the XML document we want; the top level document | |
198 | + is "target.xml". Other calls may be performed for the DTD or | |
199 | + for xi:include. */ | |
61 | 200 | |
62 | 201 | static char * |
63 | -fetch_available_features_from_target (const char *name, void *ops_) | |
202 | +fetch_available_features_from_target (const char *name, void *baton_) | |
64 | 203 | { |
65 | - struct target_ops *ops = ops_; | |
204 | + struct fetch_features_baton *baton = baton_; | |
66 | 205 | char *features_str; |
67 | 206 | gdb_byte *features_buf; |
68 | 207 | LONGEST len; |
69 | 208 | |
70 | - struct gdb_feature_set *features; | |
71 | - struct gdb_available_feature **slot; | |
72 | - int ret; | |
209 | + if (baton->checksums) | |
210 | + { | |
211 | + struct fetch_features_checksum *checksum_ent; | |
73 | 212 | |
74 | - len = target_read_whole (ops, TARGET_OBJECT_AVAILABLE_FEATURES, | |
75 | - NULL, &features_buf); | |
213 | + for (checksum_ent = baton->checksums; | |
214 | + checksum_ent->annex != NULL; | |
215 | + checksum_ent++) | |
216 | + if (strcmp (checksum_ent->annex, name) == 0) | |
217 | + break; | |
218 | + | |
219 | + if (checksum_ent) | |
220 | + { | |
221 | + const char *cached_str; | |
222 | + | |
223 | + cached_str = find_xml_feature_in_cache (name, | |
224 | + checksum_ent->checksum); | |
225 | + | |
226 | + /* This function always returns something which the caller is | |
227 | + responsible for freeing. So, if we got a match, return a | |
228 | + copy of it. */ | |
229 | + if (cached_str) | |
230 | + return xstrdup (cached_str); | |
231 | + } | |
232 | + } | |
233 | + | |
234 | + len = target_read_whole (baton->ops, TARGET_OBJECT_AVAILABLE_FEATURES, | |
235 | + name, &features_buf); | |
76 | 236 | if (len <= 0) |
77 | 237 | return NULL; |
78 | 238 |
@@ -85,6 +245,9 @@ fetch_available_features_from_target (const char *name, void *ops_) | ||
85 | 245 | features_str[len] = '\0'; |
86 | 246 | } |
87 | 247 | |
248 | + if (baton->checksums) | |
249 | + add_xml_feature_to_cache (name, features_str); | |
250 | + | |
88 | 251 | return features_str; |
89 | 252 | } |
90 | 253 |
@@ -92,25 +255,120 @@ fetch_available_features_from_target (const char *name, void *ops_) | ||
92 | 255 | to a binary representation. The string representation is fetched using |
93 | 256 | TARGET_OBJECT_AVAILABLE_FEATURES. */ |
94 | 257 | |
258 | +/* TODO: Document \n conventions */ | |
259 | + | |
95 | 260 | struct gdb_feature_set * |
96 | 261 | available_features_from_target_object (struct target_ops *ops, |
97 | 262 | struct obstack *obstack) |
98 | 263 | { |
264 | + struct fetch_features_baton baton; | |
99 | 265 | struct gdb_feature_set *features; |
100 | - char *features_str, *cur; | |
101 | - gdb_byte *features_buf; | |
102 | - LONGEST len; | |
103 | - struct gdb_available_feature **slot; | |
266 | + char *features_str, *checksums_str; | |
104 | 267 | int ret; |
268 | + struct cleanup *back_to = make_cleanup (null_cleanup, NULL); | |
269 | + | |
270 | + /* Initialize the baton. */ | |
271 | + baton.ops = ops; | |
272 | + baton.checksums = NULL; | |
273 | + | |
274 | + /* Attempt to read checksums from the target. */ | |
275 | + checksums_str = fetch_available_features_from_target ("CHECKSUMS", &baton); | |
276 | + if (checksums_str) | |
277 | + { | |
278 | + char *p; | |
279 | + int n_checksums; | |
280 | + | |
281 | + make_cleanup (xfree, checksums_str); | |
282 | + | |
283 | + /* Allow for one checksum in case there is no trailing newline, | |
284 | + and one to serve as a NULL terminator. */ | |
285 | + n_checksums = 2; | |
286 | + | |
287 | + /* Allocate one additional checksum per newline. */ | |
288 | + for (p = checksums_str; *p; p++) | |
289 | + if (*p == '\n') | |
290 | + n_checksums++; | |
105 | 291 | |
106 | - features_str = fetch_available_features_from_target (NULL, ops); | |
292 | + baton.checksums = xmalloc (n_checksums | |
293 | + * sizeof (struct fetch_features_checksum)); | |
294 | + make_cleanup (xfree, baton.checksums); | |
295 | + | |
296 | + n_checksums = 0; | |
297 | + p = checksums_str; | |
298 | + while (*p) | |
299 | + { | |
300 | + char *field_end; | |
301 | + | |
302 | + /* Find the first space on the line, marking the end of the | |
303 | + checksum. */ | |
304 | + field_end = p; | |
305 | + while (*field_end && *field_end != '\n' | |
306 | + && *field_end != ' ') | |
307 | + field_end++; | |
308 | + | |
309 | + /* Check for a malformed checksum. */ | |
310 | + if (*field_end != ' ' | |
311 | + || field_end - p != 40 | |
312 | + || checksum_to_bytes (p, baton.checksums[n_checksums].checksum)) | |
313 | + { | |
314 | + /* Skip this line. */ | |
315 | + p = field_end; | |
316 | + while (*p && *p != '\n') | |
317 | + p++; | |
318 | + if (*p == '\n') | |
319 | + p++; | |
320 | + continue; | |
321 | + } | |
322 | + | |
323 | + *field_end = '\0'; | |
324 | + | |
325 | + /* Skip whitespace after the checksum. */ | |
326 | + p = field_end + 1; | |
327 | + while (*p == ' ') | |
328 | + p++; | |
329 | + | |
330 | + field_end = p; | |
331 | + while (*field_end && *field_end != '\n') | |
332 | + field_end++; | |
333 | + | |
334 | + if (field_end == p) | |
335 | + { | |
336 | + /* Malformed line; skip it. */ | |
337 | + if (*p == '\n') | |
338 | + p++; | |
339 | + continue; | |
340 | + } | |
341 | + | |
342 | + baton.checksums[n_checksums++].annex = p; | |
343 | + | |
344 | + /* Advance to the next line, inserting a NUL for the end of | |
345 | + the annex name if necessary. */ | |
346 | + if (*field_end) | |
347 | + { | |
348 | + *field_end = '\0'; | |
349 | + p = field_end + 1; | |
350 | + } | |
351 | + else | |
352 | + break; | |
353 | + } | |
354 | + | |
355 | + baton.checksums[n_checksums].annex = NULL; | |
356 | + } | |
357 | + | |
358 | + /* FIXME: Memory management: what happens to features_str? */ | |
359 | + | |
360 | + features_str = fetch_available_features_from_target ("target.xml", &baton); | |
361 | + if (features_str == NULL) | |
362 | + return NULL; | |
107 | 363 | |
108 | 364 | features = OBSTACK_ZALLOC (obstack, struct gdb_feature_set); |
109 | 365 | features->obstack = obstack; |
110 | 366 | ret = available_features_from_xml_string (&features->features, obstack, |
111 | 367 | features_str, |
112 | 368 | fetch_available_features_from_target, |
113 | - ops, 0); | |
369 | + &baton, 0); | |
370 | + | |
371 | + do_cleanups (back_to); | |
114 | 372 | |
115 | 373 | if (ret < 0) |
116 | 374 | { |
@@ -83,7 +83,7 @@ struct gdb_available_register | ||
83 | 83 | /* The protocol number used by this target to provide this |
84 | 84 | feature. For instance, the register number used for remote |
85 | 85 | p/P packets to access this register. */ |
86 | - int protocol_number; | |
86 | + long protocol_number; | |
87 | 87 | |
88 | 88 | /* Data private to the architecture associated with this feature. |
89 | 89 | This is a NUL-terminated string. */ |
@@ -158,7 +158,6 @@ const char *available_register_name (struct gdbarch *, int); | ||
158 | 158 | |
159 | 159 | int available_register_target_regnum (struct gdbarch *, int); |
160 | 160 | |
161 | - | |
162 | 161 | /* Internal routines shared by available.c and parse-avail.c. */ |
163 | 162 | |
164 | 163 | typedef char *(*xml_fetch_another) (const char *, void *); |
@@ -0,0 +1,37 @@ | ||
1 | +#!/bin/sh | |
2 | + | |
3 | +input=$1 | |
4 | +output=$2 | |
5 | + | |
6 | +if test -z "$input" || test -z "$output"; then | |
7 | + echo "Usage: $0 INPUTFILE OUTPUTFILE" | |
8 | + exit 1 | |
9 | +fi | |
10 | + | |
11 | +arrayname=xml_feature_`basename $input | sed 's/\..*//g; s/-/_/g'` | |
12 | + | |
13 | +gawk 'BEGIN { n = 0 | |
14 | + print "const char '$arrayname'[] = {" | |
15 | + for (i = 0; i < 255; i++) | |
16 | + _ord_[sprintf("%c", i)] = i | |
17 | +} { | |
18 | + split($0, line, ""); | |
19 | + printf " " | |
20 | + for (i = 1; i <= length(line); i++) { | |
21 | + c = line[i] | |
22 | + if (c == "'\''") { | |
23 | + printf "'\''\\'\'''\'', " | |
24 | + } else if (c == "\\") { | |
25 | + printf "'\''\\\\'\'', " | |
26 | + } else if (match (c, "[[:print:]]") != 0) { | |
27 | + printf "'\''" c "'\'', " | |
28 | + } else { | |
29 | + printf "'\''\\%03o'\'', ", _ord_[c] | |
30 | + } | |
31 | + if (i % 10 == 0) | |
32 | + printf "\n " | |
33 | + } | |
34 | + printf "'\''\\n'\'', \n" | |
35 | +} END { | |
36 | + print " 0 };" | |
37 | +}' < $input > $output |
@@ -30,15 +30,18 @@ | ||
30 | 30 | |
31 | 31 | <!-- TODO: GDB does not yet support descriptions. --> |
32 | 32 | |
33 | -<!-- Registers do not have an explicit register number field; they | |
34 | - are numbered sequentially from the containing feature's base-regnum | |
35 | - when the feature is referenced. --> | |
33 | +<!-- Registers are not required to have an explicit register number field; | |
34 | + they are numbered sequentially starting at zero. If a register does | |
35 | + have an explicit number, the next register will be assigned the next | |
36 | + sequential number by default. When the feature is referenced, register | |
37 | + numbers are adjusted by the reference's base-regnum. --> | |
36 | 38 | <!-- arch_data; see above --> |
37 | 39 | <!-- Kill save-restore in favor of a more complete scheme --> |
38 | 40 | <!ELEMENT reg (description*)> |
39 | 41 | <!ATTLIST reg |
40 | 42 | name CDATA #REQUIRED |
41 | 43 | bitsize CDATA #REQUIRED |
44 | + regnum CDATA #IMPLIED | |
42 | 45 | readonly (yes | no) 'no' |
43 | 46 | save-restore (yes | no) 'yes' |
44 | 47 | type CDATA 'int' |
@@ -328,6 +328,7 @@ xml_start_reg (struct xml_feature_parse_data *data, | ||
328 | 328 | sizeof (struct gdb_available_register)); |
329 | 329 | memset (reg, 0, sizeof (struct gdb_available_register)); |
330 | 330 | |
331 | + reg->protocol_number = -1; | |
331 | 332 | reg->bitsize = -1; |
332 | 333 | reg->readonly = -1; |
333 | 334 | reg->save_restore = -1; |
@@ -343,6 +344,12 @@ xml_start_reg (struct xml_feature_parse_data *data, | ||
343 | 344 | else if (strcmp (name, "name") == 0) |
344 | 345 | reg->name = obstrdup (data->obstack, val); |
345 | 346 | |
347 | + else if (strcmp (name, "regnum") == 0) | |
348 | + { | |
349 | + if (xml_parse_one_integer (val, ®->protocol_number) < 0) | |
350 | + data->unhandled++; | |
351 | + } | |
352 | + | |
346 | 353 | else if (strcmp (name, "bitsize") == 0) |
347 | 354 | { |
348 | 355 | if (xml_parse_one_integer (val, ®->bitsize) < 0) |
@@ -457,10 +464,8 @@ xml_start_feature_ref (struct xml_feature_parse_data *data, | ||
457 | 464 | } |
458 | 465 | |
459 | 466 | /* Set register numbers in the new feature. */ |
460 | - for (i = new_feature->protocol_number, reg = new_feature->registers; | |
461 | - reg != NULL; | |
462 | - i++, reg = reg->next) | |
463 | - reg->protocol_number = i; | |
467 | + for (reg = new_feature->registers; reg != NULL; reg = reg->next) | |
468 | + reg->protocol_number += new_feature->protocol_number; | |
464 | 469 | |
465 | 470 | new_feature->next = data->target_features; |
466 | 471 | data->target_features = new_feature; |
@@ -682,10 +687,11 @@ xml_feature_end_element (void *data_, const XML_Char *name) | ||
682 | 687 | feature->next = data->seen_features; |
683 | 688 | data->seen_features = feature; |
684 | 689 | |
685 | - /* Reverse the list of registers. */ | |
686 | 690 | if (feature->registers) |
687 | 691 | { |
692 | + /* Reverse the list of registers. */ | |
688 | 693 | struct gdb_available_register *reg1, *reg2, *reg3; |
694 | + int next; | |
689 | 695 | |
690 | 696 | reg1 = NULL; |
691 | 697 | reg2 = feature->registers; |
@@ -699,6 +705,13 @@ xml_feature_end_element (void *data_, const XML_Char *name) | ||
699 | 705 | } |
700 | 706 | |
701 | 707 | feature->registers = reg1; |
708 | + | |
709 | + next = 0; | |
710 | + for (reg1 = feature->registers; reg1; reg1 = reg1->next) | |
711 | + if (reg1->protocol_number == -1) | |
712 | + reg1->protocol_number = next++; | |
713 | + else | |
714 | + next = reg1->protocol_number + 1; | |
702 | 715 | } |
703 | 716 | |
704 | 717 | break; |
@@ -765,14 +778,15 @@ xml_parser_cleanup (void *parser) | ||
765 | 778 | struct xml_feature_parse_data *data; |
766 | 779 | |
767 | 780 | data = XML_GetUserData (parser); |
768 | - while (data->state) | |
769 | - { | |
770 | - struct xml_state_stack *prev; | |
781 | + if (data) | |
782 | + while (data->state) | |
783 | + { | |
784 | + struct xml_state_stack *prev; | |
771 | 785 | |
772 | - prev = data->state->prev; | |
773 | - xfree (data->state); | |
774 | - data->state = prev; | |
775 | - } | |
786 | + prev = data->state->prev; | |
787 | + xfree (data->state); | |
788 | + data->state = prev; | |
789 | + } | |
776 | 790 | |
777 | 791 | XML_ParserFree (parser); |
778 | 792 | } |
@@ -801,6 +815,7 @@ xml_feature_external_entity (XML_Parser parser, | ||
801 | 815 | back_to = make_cleanup (xml_parser_cleanup, entity_parser); |
802 | 816 | |
803 | 817 | XML_SetElementHandler (entity_parser, NULL, NULL); |
818 | + XML_SetUserData (entity_parser, NULL); | |
804 | 819 | |
805 | 820 | if (XML_Parse (entity_parser, text, strlen (text), 1) != XML_STATUS_OK) |
806 | 821 | { |