X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=ld%2Fldlang.c;h=f4eb703b71da0e8cfb2b2e7c1823f2323e585deb;hb=df7b86aa4cb63ce86e60949b8160438bc0f9e389;hp=e804a53b84a0fe4e5905b4ad14aaf841b2fe914f;hpb=b7f95647fd61692f386a504b17a32326259d54cf;p=deliverable%2Fbinutils-gdb.git diff --git a/ld/ldlang.c b/ld/ldlang.c index e804a53b84..f4eb703b71 100644 --- a/ld/ldlang.c +++ b/ld/ldlang.c @@ -1,6 +1,6 @@ /* Linker command language support. Copyright 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, - 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc. This file is part of the GNU Binutils. @@ -23,6 +23,7 @@ #include "sysdep.h" #include "bfd.h" #include "libiberty.h" +#include "filenames.h" #include "safe-ctype.h" #include "obstack.h" #include "bfdlink.h" @@ -55,7 +56,6 @@ static struct obstack map_obstack; #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free -static const char *startup_file; static const char *entry_symbol_default = "start"; static bfd_boolean placed_commons = FALSE; static bfd_boolean stripped_excluded_sections = FALSE; @@ -69,7 +69,6 @@ static struct bfd_hash_table lang_definedness_table; static lang_statement_list_type *stat_save[10]; static lang_statement_list_type **stat_save_ptr = &stat_save[0]; static struct unique_sections *unique_section_list; -static bfd_boolean ldlang_sysrooted_script = FALSE; /* Forward declarations. */ static void exp_init_os (etree_type *); @@ -100,6 +99,7 @@ lang_statement_list_type file_chain = { NULL, NULL }; lang_statement_list_type input_file_chain; struct bfd_sym_chain entry_symbol = { NULL, NULL }; const char *entry_section = ".text"; +struct lang_input_statement_flags input_flags; bfd_boolean entry_from_cmdline; bfd_boolean undef_from_cmdline; bfd_boolean lang_has_input_file = FALSE; @@ -108,7 +108,6 @@ bfd_boolean lang_float_flag = FALSE; bfd_boolean delete_output_file_on_failure = FALSE; struct lang_phdr *lang_phdr_list; struct lang_nocrossrefs *nocrossref_list; -bfd_boolean missing_file = FALSE; /* Functions that traverse the linker script and might evaluate DEFINED() need to increment this. */ @@ -263,7 +262,7 @@ walk_wild_consider_section (lang_wild_statement_type *ptr, return; } - (*callback) (ptr, sec, s, file, data); + (*callback) (ptr, sec, s, ptr->section_flag_list, file, data); } /* Lowest common denominator routine that can handle everything correctly, @@ -282,7 +281,7 @@ walk_wild_section_general (lang_wild_statement_type *ptr, { sec = ptr->section_list; if (sec == NULL) - (*callback) (ptr, sec, s, file, data); + (*callback) (ptr, sec, s, ptr->section_flag_list, file, data); while (sec != NULL) { @@ -371,18 +370,70 @@ match_simple_wild (const char *pattern, const char *name) return TRUE; } +/* Return the numerical value of the init_priority attribute from + section name NAME. */ + +static unsigned long +get_init_priority (const char *name) +{ + char *end; + unsigned long init_priority; + + /* GCC uses the following section names for the init_priority + attribute with numerical values 101 and 65535 inclusive. A + lower value means a higher priority. + + 1: .init_array.NNNN/.fini_array.NNNN: Where NNNN is the + decimal numerical value of the init_priority attribute. + The order of execution in .init_array is forward and + .fini_array is backward. + 2: .ctors.NNNN/.ctors.NNNN: Where NNNN is 65535 minus the + decimal numerical value of the init_priority attribute. + The order of execution in .ctors is backward and .dtors + is forward. + */ + if (strncmp (name, ".init_array.", 12) == 0 + || strncmp (name, ".fini_array.", 12) == 0) + { + init_priority = strtoul (name + 12, &end, 10); + return *end ? 0 : init_priority; + } + else if (strncmp (name, ".ctors.", 7) == 0 + || strncmp (name, ".dtors.", 7) == 0) + { + init_priority = strtoul (name + 7, &end, 10); + return *end ? 0 : 65535 - init_priority; + } + + return 0; +} + /* Compare sections ASEC and BSEC according to SORT. */ static int compare_section (sort_type sort, asection *asec, asection *bsec) { int ret; + unsigned long ainit_priority, binit_priority; switch (sort) { default: abort (); + case by_init_priority: + ainit_priority + = get_init_priority (bfd_get_section_name (asec->owner, asec)); + binit_priority + = get_init_priority (bfd_get_section_name (bsec->owner, bsec)); + if (ainit_priority == 0 || binit_priority == 0) + goto sort_by_name; + ret = ainit_priority - binit_priority; + if (ret) + break; + else + goto sort_by_name; + case by_alignment_name: ret = (bfd_section_alignment (bsec->owner, bsec) - bfd_section_alignment (asec->owner, asec)); @@ -391,6 +442,7 @@ compare_section (sort_type sort, asection *asec, asection *bsec) /* Fall through. */ case by_name: +sort_by_name: ret = strcmp (bfd_get_section_name (asec->owner, asec), bfd_get_section_name (bsec->owner, bsec)); break; @@ -451,6 +503,7 @@ static void output_section_callback_fast (lang_wild_statement_type *ptr, struct wildcard_list *sec, asection *section, + struct flag_info *sflag_list ATTRIBUTE_UNUSED, lang_input_statement_type *file, void *output) { @@ -483,7 +536,7 @@ output_section_callback_tree_to_list (lang_wild_statement_type *ptr, if (tree->left) output_section_callback_tree_to_list (ptr, tree->left, output); - lang_add_section (&ptr->children, tree->section, + lang_add_section (&ptr->children, tree->section, NULL, (lang_output_section_statement_type *) output); if (tree->right) @@ -675,7 +728,7 @@ walk_wild_section (lang_wild_statement_type *ptr, callback_t callback, void *data) { - if (file->just_syms_flag) + if (file->flags.just_syms) return; (*ptr->walk_wild_section_handler) (ptr, file, callback, data); @@ -990,6 +1043,8 @@ new_afile (const char *name, { lang_input_statement_type *p; + lang_has_input_file = TRUE; + if (add_to_list) p = (lang_input_statement_type *) new_stat (lang_input_statement, stat_ptr); else @@ -1000,9 +1055,14 @@ new_afile (const char *name, p->header.next = NULL; } - lang_has_input_file = TRUE; + memset (&p->the_bfd, 0, + sizeof (*p) - offsetof (lang_input_statement_type, the_bfd)); p->target = target; - p->sysrooted = FALSE; + p->flags.dynamic = input_flags.dynamic; + p->flags.add_DT_NEEDED_for_dynamic = input_flags.add_DT_NEEDED_for_dynamic; + p->flags.add_DT_NEEDED_for_regular = input_flags.add_DT_NEEDED_for_regular; + p->flags.whole_archive = input_flags.whole_archive; + p->flags.sysrooted = input_flags.sysrooted; if (file_type == lang_input_file_is_l_enum && name[0] == ':' && name[1] != '\0') @@ -1015,65 +1075,40 @@ new_afile (const char *name, { case lang_input_file_is_symbols_only_enum: p->filename = name; - p->maybe_archive = FALSE; - p->real = TRUE; p->local_sym_name = name; - p->just_syms_flag = TRUE; - p->search_dirs_flag = FALSE; + p->flags.real = TRUE; + p->flags.just_syms = TRUE; break; case lang_input_file_is_fake_enum: p->filename = name; - p->maybe_archive = FALSE; - p->real = FALSE; p->local_sym_name = name; - p->just_syms_flag = FALSE; - p->search_dirs_flag = FALSE; break; case lang_input_file_is_l_enum: - p->maybe_archive = TRUE; p->filename = name; - p->real = TRUE; p->local_sym_name = concat ("-l", name, (const char *) NULL); - p->just_syms_flag = FALSE; - p->search_dirs_flag = TRUE; + p->flags.maybe_archive = TRUE; + p->flags.real = TRUE; + p->flags.search_dirs = TRUE; break; case lang_input_file_is_marker_enum: p->filename = name; - p->maybe_archive = FALSE; - p->real = FALSE; p->local_sym_name = name; - p->just_syms_flag = FALSE; - p->search_dirs_flag = TRUE; + p->flags.search_dirs = TRUE; break; case lang_input_file_is_search_file_enum: - p->sysrooted = ldlang_sysrooted_script; p->filename = name; - p->maybe_archive = FALSE; - p->real = TRUE; p->local_sym_name = name; - p->just_syms_flag = FALSE; - p->search_dirs_flag = TRUE; + p->flags.real = TRUE; + p->flags.search_dirs = TRUE; break; case lang_input_file_is_file_enum: p->filename = name; - p->maybe_archive = FALSE; - p->real = TRUE; p->local_sym_name = name; - p->just_syms_flag = FALSE; - p->search_dirs_flag = FALSE; + p->flags.real = TRUE; break; default: FAIL (); } - p->the_bfd = NULL; - p->next_real_file = NULL; - p->next = NULL; - p->dynamic = config.dynamic_link; - p->add_DT_NEEDED_for_dynamic = add_DT_NEEDED_for_dynamic; - p->add_DT_NEEDED_for_regular = add_DT_NEEDED_for_regular; - p->whole_archive = whole_archive; - p->loaded = FALSE; - p->missing_file = FALSE; lang_statement_append (&input_file_chain, (lang_statement_union_type *) p, @@ -1251,12 +1286,13 @@ lang_memory_region_lookup (const char *const name, bfd_boolean create) { if (create) einfo (_("%P:%S: warning: redeclaration of memory region `%s'\n"), - name); + NULL, name); return r; } if (!create && strcmp (name, DEFAULT_MEMORY_REGION)) - einfo (_("%P:%S: warning: memory region `%s' not declared\n"), name); + einfo (_("%P:%S: warning: memory region `%s' not declared\n"), + NULL, name); new_region = (lang_memory_region_type *) stat_alloc (sizeof (lang_memory_region_type)); @@ -1290,7 +1326,7 @@ lang_memory_region_alias (const char * alias, const char * region_name) the default memory region. */ if (strcmp (region_name, DEFAULT_MEMORY_REGION) == 0 || strcmp (alias, DEFAULT_MEMORY_REGION) == 0) - einfo (_("%F%P:%S: error: alias for default memory region\n")); + einfo (_("%F%P:%S: error: alias for default memory region\n"), NULL); /* Look for the target region and check if the alias is not already in use. */ @@ -1303,15 +1339,14 @@ lang_memory_region_alias (const char * alias, const char * region_name) if (strcmp (n->name, alias) == 0) einfo (_("%F%P:%S: error: redefinition of memory region " "alias `%s'\n"), - alias); + NULL, alias); } /* Check if the target region exists. */ if (region == NULL) einfo (_("%F%P:%S: error: memory region `%s' " "for alias `%s' does not exist\n"), - region_name, - alias); + NULL, region_name, alias); /* Add alias to region name list. */ n = (lang_memory_region_name *) stat_alloc (sizeof (lang_memory_region_name)); @@ -1522,8 +1557,14 @@ lang_output_section_find_by_flags (const asection *sec, } flags ^= sec->flags; if (!(flags & (SEC_HAS_CONTENTS | SEC_ALLOC | SEC_LOAD - | SEC_READONLY)) - && !(look->flags & (SEC_SMALL_DATA | SEC_THREAD_LOCAL))) + | SEC_READONLY | SEC_SMALL_DATA)) + || (!(flags & (SEC_HAS_CONTENTS | SEC_ALLOC | SEC_LOAD + | SEC_READONLY)) + && !(look->flags & SEC_SMALL_DATA)) + || (!(flags & (SEC_THREAD_LOCAL | SEC_ALLOC)) + && (look->flags & SEC_THREAD_LOCAL) + && (!(flags & SEC_LOAD) + || (look->flags & SEC_LOAD)))) found = look; } } @@ -1756,7 +1797,7 @@ lang_insert_orphan (asection *s, sprintf (symname + (symname[0] != 0), "__start_%s", secname); e_align = exp_unop (ALIGN_K, exp_intop ((bfd_vma) 1 << s->alignment_power)); - lang_add_assignment (exp_assop ('=', ".", e_align)); + lang_add_assignment (exp_assign (".", e_align)); lang_add_assignment (exp_provide (symname, exp_unop (ABSOLUTE, exp_nameop (NAME, ".")), @@ -1766,7 +1807,7 @@ lang_insert_orphan (asection *s, if (add_child == NULL) add_child = &os->children; - lang_add_section (add_child, s, os); + lang_add_section (add_child, s, NULL, os); if (after && (s->flags & (SEC_LOAD | SEC_ALLOC)) != 0) { @@ -1945,7 +1986,7 @@ lang_map (void) asection *s; if ((file->the_bfd->flags & (BFD_LINKER_CREATED | DYNAMIC)) != 0 - || file->just_syms_flag) + || file->flags.just_syms) continue; for (s = file->the_bfd->sections; s != NULL; s = s->next) @@ -2167,7 +2208,7 @@ section_already_linked (bfd *abfd, asection *sec, void *data) /* If we are only reading symbols from this object, then we want to discard all sections. */ - if (entry->just_syms_flag) + if (entry->flags.just_syms) { bfd_link_just_syms (abfd, sec, &link_info); return; @@ -2184,17 +2225,19 @@ section_already_linked (bfd *abfd, asection *sec, void *data) foo.o(.text, .data). */ /* Add SECTION to the output section OUTPUT. Do this by creating a - lang_input_section statement which is placed at PTR. FILE is the - input file which holds SECTION. */ + lang_input_section statement which is placed at PTR. */ void lang_add_section (lang_statement_list_type *ptr, asection *section, + struct flag_info *sflag_info, lang_output_section_statement_type *output) { flagword flags = section->flags; + bfd_boolean discard; lang_input_section_type *new_section; + bfd *abfd = link_info.output_bfd; /* Discard sections marked with SEC_EXCLUDE. */ discard = (flags & SEC_EXCLUDE) != 0; @@ -2220,6 +2263,15 @@ lang_add_section (lang_statement_list_type *ptr, return; } + if (sflag_info) + { + bfd_boolean keep; + + keep = bfd_lookup_section_flags (&link_info, sflag_info, section); + if (!keep) + return; + } + if (section->output_section != NULL) return; @@ -2391,7 +2443,7 @@ wild_sort (lang_wild_statement_type *wild, la = FALSE; } - i = strcmp (fn, ln); + i = filename_cmp (fn, ln); if (i > 0) continue; else if (i < 0) @@ -2404,7 +2456,7 @@ wild_sort (lang_wild_statement_type *wild, if (la) ln = ls->section->owner->filename; - i = strcmp (fn, ln); + i = filename_cmp (fn, ln); if (i > 0) continue; else if (i < 0) @@ -2430,6 +2482,7 @@ static void output_section_callback (lang_wild_statement_type *ptr, struct wildcard_list *sec, asection *section, + struct flag_info *sflag_info, lang_input_statement_type *file, void *output) { @@ -2450,14 +2503,14 @@ output_section_callback (lang_wild_statement_type *ptr, of the current list. */ if (before == NULL) - lang_add_section (&ptr->children, section, os); + lang_add_section (&ptr->children, section, sflag_info, os); else { lang_statement_list_type list; lang_statement_union_type **pp; lang_list_init (&list); - lang_add_section (&list, section, os); + lang_add_section (&list, section, sflag_info, os); /* If we are discarding the section, LIST.HEAD will be NULL. */ @@ -2483,6 +2536,7 @@ static void check_section_callback (lang_wild_statement_type *ptr ATTRIBUTE_UNUSED, struct wildcard_list *sec ATTRIBUTE_UNUSED, asection *section, + struct flag_info *sflag_info ATTRIBUTE_UNUSED, lang_input_statement_type *file ATTRIBUTE_UNUSED, void *output) { @@ -2517,7 +2571,7 @@ lookup_name (const char *name) const char *filename = search->local_sym_name; if (filename != NULL - && strcmp (filename, name) == 0) + && filename_cmp (filename, name) == 0) break; } @@ -2527,7 +2581,7 @@ lookup_name (const char *name) /* If we have already added this file, or this file is not real don't add this file. */ - if (search->loaded || !search->real) + if (search->flags.loaded || !search->flags.real) return search; if (! load_symbols (search, NULL)) @@ -2584,7 +2638,7 @@ check_excluded_libs (bfd *abfd) return; } - if (strncmp (lib->name, filename, len) == 0 + if (filename_ncmp (lib->name, filename, len) == 0 && (filename[len] == '\0' || (filename[len] == '.' && filename[len + 1] == 'a' && filename[len + 2] == '\0'))) @@ -2605,23 +2659,20 @@ load_symbols (lang_input_statement_type *entry, { char **matching; - if (entry->loaded) + if (entry->flags.loaded) return TRUE; ldfile_open_file (entry); /* Do not process further if the file was missing. */ - if (entry->missing_file) + if (entry->flags.missing_file) return TRUE; if (! bfd_check_format (entry->the_bfd, bfd_archive) && ! bfd_check_format_matches (entry->the_bfd, bfd_object, &matching)) { bfd_error_type err; - bfd_boolean save_ldlang_sysrooted_script; - bfd_boolean save_add_DT_NEEDED_for_regular; - bfd_boolean save_add_DT_NEEDED_for_dynamic; - bfd_boolean save_whole_archive; + struct lang_input_statement_flags save_flags; err = bfd_get_error (); @@ -2647,30 +2698,27 @@ load_symbols (lang_input_statement_type *entry, entry->the_bfd = NULL; /* Try to interpret the file as a linker script. */ + save_flags = input_flags; ldfile_open_command_file (entry->filename); push_stat_ptr (place); - save_ldlang_sysrooted_script = ldlang_sysrooted_script; - ldlang_sysrooted_script = entry->sysrooted; - save_add_DT_NEEDED_for_regular = add_DT_NEEDED_for_regular; - add_DT_NEEDED_for_regular = entry->add_DT_NEEDED_for_regular; - save_add_DT_NEEDED_for_dynamic = add_DT_NEEDED_for_dynamic; - add_DT_NEEDED_for_dynamic = entry->add_DT_NEEDED_for_dynamic; - save_whole_archive = whole_archive; - whole_archive = entry->whole_archive; + input_flags.add_DT_NEEDED_for_regular + = entry->flags.add_DT_NEEDED_for_regular; + input_flags.add_DT_NEEDED_for_dynamic + = entry->flags.add_DT_NEEDED_for_dynamic; + input_flags.whole_archive = entry->flags.whole_archive; + input_flags.dynamic = entry->flags.dynamic; ldfile_assumed_script = TRUE; parser_input = input_script; - /* We want to use the same -Bdynamic/-Bstatic as the one for - ENTRY. */ - config.dynamic_link = entry->dynamic; yyparse (); ldfile_assumed_script = FALSE; - ldlang_sysrooted_script = save_ldlang_sysrooted_script; - add_DT_NEEDED_for_regular = save_add_DT_NEEDED_for_regular; - add_DT_NEEDED_for_dynamic = save_add_DT_NEEDED_for_dynamic; - whole_archive = save_whole_archive; + /* missing_file is sticky. sysrooted will already have been + restored when seeing EOF in yyparse, but no harm to restore + again. */ + save_flags.missing_file |= input_flags.missing_file; + input_flags = save_flags; pop_stat_ptr (); return TRUE; @@ -2689,7 +2737,10 @@ load_symbols (lang_input_statement_type *entry, break; case bfd_object: - ldlang_add_file (entry); +#ifdef ENABLE_PLUGINS + if (!entry->flags.reload) +#endif + ldlang_add_file (entry); if (trace_files || trace_file_tries) info_msg ("%I\n", entry); break; @@ -2697,7 +2748,7 @@ load_symbols (lang_input_statement_type *entry, case bfd_archive: check_excluded_libs (entry->the_bfd); - if (entry->whole_archive) + if (entry->flags.whole_archive) { bfd *member = NULL; bfd_boolean loaded = TRUE; @@ -2717,33 +2768,33 @@ load_symbols (lang_input_statement_type *entry, loaded = FALSE; } - subsbfd = NULL; - if (! ((*link_info.callbacks->add_archive_element) - (&link_info, member, "--whole-archive", &subsbfd))) + subsbfd = member; + if (!(*link_info.callbacks + ->add_archive_element) (&link_info, member, + "--whole-archive", &subsbfd)) abort (); /* Potentially, the add_archive_element hook may have set a substitute BFD for us. */ - if (! bfd_link_add_symbols (subsbfd ? subsbfd : member, - &link_info)) + if (!bfd_link_add_symbols (subsbfd, &link_info)) { einfo (_("%F%B: could not read symbols: %E\n"), member); loaded = FALSE; } } - entry->loaded = loaded; + entry->flags.loaded = loaded; return loaded; } break; } if (bfd_link_add_symbols (entry->the_bfd, &link_info)) - entry->loaded = TRUE; + entry->flags.loaded = TRUE; else einfo (_("%F%B: could not read symbols: %E\n"), entry->the_bfd); - return entry->loaded; + return entry->flags.loaded; } /* Handle a wild statement. S->FILENAME or S->SECTION_LIST or both @@ -2927,7 +2978,7 @@ get_first_input_target (void) LANG_FOR_EACH_INPUT_STATEMENT (s) { if (s->header.type == lang_input_statement_enum - && s->real) + && s->flags.real) { ldfile_open_file (s); @@ -2956,7 +3007,7 @@ lang_get_output_target (void) /* No - has the current target been set to something other than the default? */ - if (current_target != default_target) + if (current_target != default_target && current_target != NULL) return current_target; /* No - can we determine the format of the first input file? */ @@ -3107,26 +3158,37 @@ init_opb (void) /* Open all the input files. */ +enum open_bfd_mode + { + OPEN_BFD_NORMAL = 0, + OPEN_BFD_FORCE = 1, + OPEN_BFD_RESCAN = 2 + }; +#ifdef ENABLE_PLUGINS +static lang_input_statement_type *plugin_insert = NULL; +#endif + static void -open_input_bfds (lang_statement_union_type *s, bfd_boolean force) +open_input_bfds (lang_statement_union_type *s, enum open_bfd_mode mode) { for (; s != NULL; s = s->header.next) { switch (s->header.type) { case lang_constructors_statement_enum: - open_input_bfds (constructor_list.head, force); + open_input_bfds (constructor_list.head, mode); break; case lang_output_section_statement_enum: - open_input_bfds (s->output_section_statement.children.head, force); + open_input_bfds (s->output_section_statement.children.head, mode); break; case lang_wild_statement_enum: /* Maybe we should load the file's symbols. */ - if (s->wild_statement.filename + if ((mode & OPEN_BFD_RESCAN) == 0 + && s->wild_statement.filename && !wildcardp (s->wild_statement.filename) && !archive_path (s->wild_statement.filename)) lookup_name (s->wild_statement.filename); - open_input_bfds (s->wild_statement.children.head, force); + open_input_bfds (s->wild_statement.children.head, mode); break; case lang_group_statement_enum: { @@ -3139,7 +3201,8 @@ open_input_bfds (lang_statement_union_type *s, bfd_boolean force) do { undefs = link_info.hash->undefs_tail; - open_input_bfds (s->group_statement.children.head, TRUE); + open_input_bfds (s->group_statement.children.head, + mode | OPEN_BFD_FORCE); } while (undefs != link_info.hash->undefs_tail); } @@ -3148,7 +3211,7 @@ open_input_bfds (lang_statement_union_type *s, bfd_boolean force) current_target = s->target_statement.target; break; case lang_input_statement_enum: - if (s->input_statement.real) + if (s->input_statement.flags.real) { lang_statement_union_type **os_tail; lang_statement_list_type add; @@ -3158,13 +3221,30 @@ open_input_bfds (lang_statement_union_type *s, bfd_boolean force) /* If we are being called from within a group, and this is an archive which has already been searched, then force it to be researched unless the whole archive - has been loaded already. */ - if (force - && !s->input_statement.whole_archive - && s->input_statement.loaded + has been loaded already. Do the same for a rescan. */ + if (mode != OPEN_BFD_NORMAL +#ifdef ENABLE_PLUGINS + && ((mode & OPEN_BFD_RESCAN) == 0 + || plugin_insert == NULL) +#endif + && !s->input_statement.flags.whole_archive + && s->input_statement.flags.loaded && bfd_check_format (s->input_statement.the_bfd, bfd_archive)) - s->input_statement.loaded = FALSE; + s->input_statement.flags.loaded = FALSE; +#ifdef ENABLE_PLUGINS + /* When rescanning, reload --as-needed shared libs. */ + else if ((mode & OPEN_BFD_RESCAN) != 0 + && plugin_insert == NULL + && s->input_statement.flags.loaded + && s->input_statement.flags.add_DT_NEEDED_for_regular + && ((s->input_statement.the_bfd->flags) & DYNAMIC) != 0 + && plugin_should_reload (s->input_statement.the_bfd)) + { + s->input_statement.flags.loaded = FALSE; + s->input_statement.flags.reload = TRUE; + } +#endif os_tail = lang_output_section_statement.tail; lang_list_init (&add); @@ -3195,6 +3275,17 @@ open_input_bfds (lang_statement_union_type *s, bfd_boolean force) } } } +#ifdef ENABLE_PLUGINS + /* If we have found the point at which a plugin added new + files, clear plugin_insert to enable archive rescan. */ + if (&s->input_statement == plugin_insert) + plugin_insert = NULL; +#endif + break; + case lang_assignment_statement_enum: + if (s->assignment_statement.exp->assign.hidden) + /* This is from a --defsym on the command line. */ + exp_fold_tree_no_dot (s->assignment_statement.exp); break; default: break; @@ -3202,7 +3293,7 @@ open_input_bfds (lang_statement_union_type *s, bfd_boolean force) } /* Exit if any of the files were missing. */ - if (missing_file) + if (input_flags.missing_file) einfo ("%F"); } @@ -3342,65 +3433,6 @@ lang_place_undefineds (void) insert_undefined (ptr->name); } -typedef struct bfd_sym_chain ldlang_def_chain_list_type; - -static ldlang_def_chain_list_type ldlang_def_chain_list_head; - -/* Insert NAME as defined in the symbol table. */ - -static void -insert_defined (const char *name) -{ - struct bfd_link_hash_entry *h; - - h = bfd_link_hash_lookup (link_info.hash, name, TRUE, FALSE, TRUE); - if (h == NULL) - einfo (_("%P%F: bfd_link_hash_lookup failed: %E\n")); - if (h->type == bfd_link_hash_new - || h->type == bfd_link_hash_undefined - || h->type == bfd_link_hash_undefweak) - { - h->type = bfd_link_hash_defined; - h->u.def.section = bfd_abs_section_ptr; - h->u.def.value = 0; - } -} - -/* Like lang_add_undef, but this time for symbols defined on the - command line. */ - -static void -ldlang_add_def (const char *const name) -{ - if (link_info.output_bfd != NULL) - insert_defined (xstrdup (name)); - else - { - ldlang_def_chain_list_type *new_def; - - new_def = (ldlang_def_chain_list_type *) stat_alloc (sizeof (*new_def)); - new_def->next = ldlang_def_chain_list_head.next; - ldlang_def_chain_list_head.next = new_def; - - new_def->name = xstrdup (name); - } -} - -/* Run through the list of defineds created above and place them - into the linker hash table as defined symbols belonging to the - script file. */ - -static void -lang_place_defineds (void) -{ - ldlang_def_chain_list_type *ptr; - - for (ptr = ldlang_def_chain_list_head.next; - ptr != NULL; - ptr = ptr->next) - insert_defined (ptr->name); -} - /* Check for all readonly or some readwrite sections. */ static void @@ -3840,8 +3872,9 @@ strip_excluded_output_sections (void) asection *s; for (s = output_section->map_head.s; s != NULL; s = s->map_head.s) - if ((s->flags & SEC_LINKER_CREATED) != 0 - && (s->flags & SEC_EXCLUDE) == 0) + if ((s->flags & SEC_EXCLUDE) == 0 + && ((s->flags & SEC_LINKER_CREATED) != 0 + || link_info.emitrelocations)) { exclude = FALSE; break; @@ -3929,12 +3962,12 @@ scan_for_self_assignment (const char * dst, etree_type * rhs) switch (rhs->type.node_class) { case etree_binary: - return scan_for_self_assignment (dst, rhs->binary.lhs) - || scan_for_self_assignment (dst, rhs->binary.rhs); + return (scan_for_self_assignment (dst, rhs->binary.lhs) + || scan_for_self_assignment (dst, rhs->binary.rhs)); case etree_trinary: - return scan_for_self_assignment (dst, rhs->trinary.lhs) - || scan_for_self_assignment (dst, rhs->trinary.rhs); + return (scan_for_self_assignment (dst, rhs->trinary.lhs) + || scan_for_self_assignment (dst, rhs->trinary.rhs)); case etree_assign: case etree_provided: @@ -3989,7 +4022,7 @@ print_assignment (lang_assignment_statement_type *assignment, is_dot = (dst[0] == '.' && dst[1] == 0); tree = assignment->exp->assign.src; - computation_is_valid = is_dot || (scan_for_self_assignment (dst, tree) == FALSE); + computation_is_valid = is_dot || !scan_for_self_assignment (dst, tree); } osec = output_section->bfd_section; @@ -4020,9 +4053,8 @@ print_assignment (lang_assignment_statement_type *assignment, if (h) { value = h->u.def.value; - - if (expld.result.section != NULL) - value += expld.result.section->vma; + value += h->u.def.section->output_section->vma; + value += h->u.def.section->output_offset; minfo ("[0x%V]", value); } @@ -4521,11 +4553,11 @@ dprint_statement (lang_statement_union_type *s, int n) static void insert_pad (lang_statement_union_type **ptr, fill_type *fill, - unsigned int alignment_needed, + bfd_size_type alignment_needed, asection *output_section, bfd_vma dot) { - static fill_type zero_fill = { 1, { 0 } }; + static fill_type zero_fill; lang_statement_union_type *pad = NULL; if (ptr != &statement_list.head) @@ -4573,10 +4605,10 @@ size_input_section lang_input_section_type *is = &((*this_ptr)->input_section); asection *i = is->section; - if (!((lang_input_statement_type *) i->owner->usrdata)->just_syms_flag + if (i->sec_info_type != SEC_INFO_TYPE_JUST_SYMS && (i->flags & SEC_EXCLUDE) == 0) { - unsigned int alignment_needed; + bfd_size_type alignment_needed; asection *o; /* Align this section first to the input sections requirement, @@ -4816,7 +4848,7 @@ lang_size_sections_1 else if (expld.phase != lang_mark_phase_enum) einfo (_("%F%S: non constant or forward reference" " address expression for section %s\n"), - os->name); + os->addr_tree, os->name); } if (os->bfd_section == NULL) @@ -5428,9 +5460,11 @@ lang_size_sections (bfd_boolean *relax, bfd_boolean check_regions) lang_reset_memory_regions (); one_lang_size_sections_pass (relax, check_regions); } + else + expld.dataseg.phase = exp_dataseg_done; } - - expld.phase = lang_final_phase_enum; + else + expld.dataseg.phase = exp_dataseg_done; } /* Worker function for lang_do_assignments. Recursiveness goes here. */ @@ -5576,8 +5610,9 @@ lang_do_assignments_1 (lang_statement_union_type *s, } void -lang_do_assignments (void) +lang_do_assignments (lang_phase_type phase) { + expld.phase = phase; lang_statement_iteration++; lang_do_assignments_1 (statement_list.head, abs_output_section, NULL, 0); } @@ -5739,6 +5774,11 @@ lang_check (void) for (file = file_chain.head; file != NULL; file = file->input_statement.next) { +#ifdef ENABLE_PLUGINS + /* Don't check format of files claimed by plugin. */ + if (file->input_statement.flags.claimed) + continue; +#endif /* ENABLE_PLUGINS */ input_bfd = file->input_statement.the_bfd; compatible = bfd_arch_get_compatible (input_bfd, link_info.output_bfd, @@ -5939,7 +5979,7 @@ lang_place_orphans (void) /* This section of the file is not attached, root around for a sensible place for it to go. */ - if (file->just_syms_flag) + if (file->flags.just_syms) bfd_link_just_syms (file->the_bfd, s, &link_info); else if ((s->flags & SEC_EXCLUDE) != 0) s->output_section = bfd_abs_section_ptr; @@ -5956,7 +5996,7 @@ lang_place_orphans (void) = lang_output_section_statement_lookup (".bss", 0, TRUE); lang_add_section (&default_common_section->children, s, - default_common_section); + NULL, default_common_section); } } else @@ -5978,7 +6018,7 @@ lang_place_orphans (void) && (link_info.relocatable || (s->flags & (SEC_LOAD | SEC_ALLOC)) == 0)) os->addr_tree = exp_intop (0); - lang_add_section (&os->children, s, os); + lang_add_section (&os->children, s, NULL, os); } } } @@ -6199,6 +6239,7 @@ static void gc_section_callback (lang_wild_statement_type *ptr, struct wildcard_list *sec ATTRIBUTE_UNUSED, asection *section, + struct flag_info *sflag_info ATTRIBUTE_UNUSED, lang_input_statement_type *file ATTRIBUTE_UNUSED, void *data ATTRIBUTE_UNUSED) { @@ -6250,6 +6291,10 @@ lang_gc_sections (void) LANG_FOR_EACH_INPUT_STATEMENT (f) { asection *sec; +#ifdef ENABLE_PLUGINS + if (f->flags.claimed) + continue; +#endif for (sec = f->the_bfd->sections; sec != NULL; sec = sec->next) if ((sec->flags & SEC_DEBUGGING) == 0) sec->flags &= ~SEC_EXCLUDE; @@ -6266,6 +6311,7 @@ static void find_relro_section_callback (lang_wild_statement_type *ptr ATTRIBUTE_UNUSED, struct wildcard_list *sec ATTRIBUTE_UNUSED, asection *section, + struct flag_info *sflag_info ATTRIBUTE_UNUSED, lang_input_statement_type *file ATTRIBUTE_UNUSED, void *data) { @@ -6364,7 +6410,7 @@ lang_relax_sections (bfd_boolean need_layout) /* Do all the assignments with our current guesses as to section sizes. */ - lang_do_assignments (); + lang_do_assignments (lang_assigning_phase_enum); /* We must do this after lang_do_assignments, because it uses size. */ @@ -6385,12 +6431,78 @@ lang_relax_sections (bfd_boolean need_layout) if (need_layout) { /* Final extra sizing to report errors. */ - lang_do_assignments (); + lang_do_assignments (lang_assigning_phase_enum); lang_reset_memory_regions (); lang_size_sections (NULL, TRUE); } } +#ifdef ENABLE_PLUGINS +/* Find the insert point for the plugin's replacement files. We + place them after the first claimed real object file, or if the + first claimed object is an archive member, after the last real + object file immediately preceding the archive. In the event + no objects have been claimed at all, we return the first dummy + object file on the list as the insert point; that works, but + the callee must be careful when relinking the file_chain as it + is not actually on that chain, only the statement_list and the + input_file list; in that case, the replacement files must be + inserted at the head of the file_chain. */ + +static lang_input_statement_type * +find_replacements_insert_point (void) +{ + lang_input_statement_type *claim1, *lastobject; + lastobject = &input_file_chain.head->input_statement; + for (claim1 = &file_chain.head->input_statement; + claim1 != NULL; + claim1 = &claim1->next->input_statement) + { + if (claim1->flags.claimed) + return claim1->flags.claim_archive ? lastobject : claim1; + /* Update lastobject if this is a real object file. */ + if (claim1->the_bfd && (claim1->the_bfd->my_archive == NULL)) + lastobject = claim1; + } + /* No files were claimed by the plugin. Choose the last object + file found on the list (maybe the first, dummy entry) as the + insert point. */ + return lastobject; +} + +/* Insert SRCLIST into DESTLIST after given element by chaining + on FIELD as the next-pointer. (Counterintuitively does not need + a pointer to the actual after-node itself, just its chain field.) */ + +static void +lang_list_insert_after (lang_statement_list_type *destlist, + lang_statement_list_type *srclist, + lang_statement_union_type **field) +{ + *(srclist->tail) = *field; + *field = srclist->head; + if (destlist->tail == field) + destlist->tail = srclist->tail; +} + +/* Detach new nodes added to DESTLIST since the time ORIGLIST + was taken as a copy of it and leave them in ORIGLIST. */ + +static void +lang_list_remove_tail (lang_statement_list_type *destlist, + lang_statement_list_type *origlist) +{ + union lang_statement_union **savetail; + /* Check that ORIGLIST really is an earlier state of DESTLIST. */ + ASSERT (origlist->head == destlist->head); + savetail = origlist->tail; + origlist->head = *(savetail); + origlist->tail = destlist->tail; + destlist->tail = savetail; + *savetail = NULL; +} +#endif /* ENABLE_PLUGINS */ + void lang_process (void) { @@ -6408,31 +6520,69 @@ lang_process (void) /* Add to the hash table all undefineds on the command line. */ lang_place_undefineds (); - lang_place_defineds (); if (!bfd_section_already_linked_table_init ()) einfo (_("%P%F: Failed to create hash table\n")); /* Create a bfd for each input file. */ current_target = default_target; - open_input_bfds (statement_list.head, FALSE); + open_input_bfds (statement_list.head, OPEN_BFD_NORMAL); #ifdef ENABLE_PLUGINS + if (plugin_active_plugins_p ()) { - union lang_statement_union **listend; + lang_statement_list_type added; + lang_statement_list_type files, inputfiles; + /* Now all files are read, let the plugin(s) decide if there are any more to be added to the link before we call the - emulation's after_open hook. */ - listend = statement_list.tail; - ASSERT (!*listend); + emulation's after_open hook. We create a private list of + input statements for this purpose, which we will eventually + insert into the global statment list after the first claimed + file. */ + added = *stat_ptr; + /* We need to manipulate all three chains in synchrony. */ + files = file_chain; + inputfiles = input_file_chain; if (plugin_call_all_symbols_read ()) einfo (_("%P%F: %s: plugin reported error after all symbols read\n"), plugin_error_plugin ()); - /* If any new files were added, they will be on the end of the - statement list, and we can open them now by getting open_input_bfds - to carry on from where it ended last time. */ - if (*listend) - open_input_bfds (*listend, FALSE); + /* Open any newly added files, updating the file chains. */ + link_info.loading_lto_outputs = TRUE; + open_input_bfds (*added.tail, OPEN_BFD_NORMAL); + /* Restore the global list pointer now they have all been added. */ + lang_list_remove_tail (stat_ptr, &added); + /* And detach the fresh ends of the file lists. */ + lang_list_remove_tail (&file_chain, &files); + lang_list_remove_tail (&input_file_chain, &inputfiles); + /* Were any new files added? */ + if (added.head != NULL) + { + /* If so, we will insert them into the statement list immediately + after the first input file that was claimed by the plugin. */ + plugin_insert = find_replacements_insert_point (); + /* If a plugin adds input files without having claimed any, we + don't really have a good idea where to place them. Just putting + them at the start or end of the list is liable to leave them + outside the crtbegin...crtend range. */ + ASSERT (plugin_insert != NULL); + /* Splice the new statement list into the old one. */ + lang_list_insert_after (stat_ptr, &added, + &plugin_insert->header.next); + /* Likewise for the file chains. */ + lang_list_insert_after (&input_file_chain, &inputfiles, + &plugin_insert->next_real_file); + /* We must be careful when relinking file_chain; we may need to + insert the new files at the head of the list if the insert + point chosen is the dummy first input file. */ + if (plugin_insert->filename) + lang_list_insert_after (&file_chain, &files, &plugin_insert->next); + else + lang_list_insert_after (&file_chain, &files, &file_chain.head); + + /* Rescan archives in case new undefined symbols have appeared. */ + open_input_bfds (statement_list.head, OPEN_BFD_RESCAN); + } } #endif /* ENABLE_PLUGINS */ @@ -6459,6 +6609,11 @@ lang_process (void) files. */ ldctor_build_sets (); + /* PR 13683: We must rerun the assignments prior to running garbage + collection in order to make sure that all symbol aliases are resolved. */ + lang_do_assignments (lang_mark_phase_enum); + expld.phase = lang_first_phase_enum; + /* Remove unreferenced sections if asked to. */ lang_gc_sections (); @@ -6470,6 +6625,7 @@ lang_process (void) /* Run through the contours of the script and attach input sections to the correct output sections. */ + lang_statement_iteration++; map_input_to_output_sections (statement_list.head, NULL, NULL); process_insert_statements (); @@ -6523,8 +6679,7 @@ lang_process (void) /* Do all the assignments, now that we know the final resting places of all the symbols. */ - - lang_do_assignments (); + lang_do_assignments (lang_final_phase_enum); ldemul_finish (); @@ -6568,10 +6723,12 @@ lang_add_wild (struct wildcard_spec *filespec, new_stmt = new_stat (lang_wild_statement, stat_ptr); new_stmt->filename = NULL; new_stmt->filenames_sorted = FALSE; + new_stmt->section_flag_list = NULL; if (filespec != NULL) { new_stmt->filename = filespec->name; new_stmt->filenames_sorted = filespec->sorted == by_name; + new_stmt->section_flag_list = filespec->section_flag_list; } new_stmt->section_list = section_list; new_stmt->keep_sections = keep_sections; @@ -6693,10 +6850,6 @@ lang_add_assignment (etree_type *exp) { lang_assignment_statement_type *new_stmt; - extern int parsing_defsym; - if (parsing_defsym) - ldlang_add_def (exp->assign.dst); - new_stmt = new_stat (lang_assignment_statement, stat_ptr); new_stmt->exp = exp; return new_stmt; @@ -6711,15 +6864,13 @@ lang_add_attribute (enum statement_enum attribute) void lang_startup (const char *name) { - if (startup_file != NULL) + if (first_file->filename != NULL) { einfo (_("%P%F: multiple STARTUP files\n")); } first_file->filename = name; first_file->local_sym_name = name; - first_file->real = TRUE; - - startup_file = name; + first_file->flags.real = TRUE; } void @@ -6761,7 +6912,8 @@ lang_get_regions (lang_memory_region_type **region, *region = lang_memory_region_lookup (memspec, FALSE); if (have_lma && lma_memspec != 0) - einfo (_("%X%P:%S: section has both a load address and a load region\n")); + einfo (_("%X%P:%S: section has both a load address and a load region\n"), + NULL); } void @@ -6775,11 +6927,13 @@ lang_leave_output_section_statement (fill_type *fill, const char *memspec, current_section->load_base != NULL, current_section->addr_tree != NULL); - /* If this section has no load region or base, but has the same + /* If this section has no load region or base, but uses the same region as the previous section, then propagate the previous section's load region. */ - if (!current_section->lma_region && !current_section->load_base + if (current_section->lma_region == NULL + && current_section->load_base == NULL + && current_section->addr_tree == NULL && current_section->region == current_section->prev->region) current_section->lma_region = current_section->prev->lma_region; @@ -6946,7 +7100,8 @@ lang_new_phdr (const char *name, && (*pp)->type == 1 && !((*pp)->filehdr || (*pp)->phdrs)) { - einfo (_("%X%P:%S: PHDRS and FILEHDR are not supported when prior PT_LOAD headers lack them\n")); + einfo (_("%X%P:%S: PHDRS and FILEHDR are not supported" + " when prior PT_LOAD headers lack them\n"), NULL); hdrs = FALSE; } @@ -7236,7 +7391,7 @@ lang_leave_overlay (etree_type *lma_expr, overlay region. */ if (overlay_list != NULL) overlay_list->os->update_dot_tree - = exp_assop ('=', ".", exp_binop ('+', overlay_vma, overlay_max)); + = exp_assign (".", exp_binop ('+', overlay_vma, overlay_max)); l = overlay_list; while (l != NULL) @@ -7286,10 +7441,6 @@ lang_leave_overlay (etree_type *lma_expr, /* Version handling. This is only useful for ELF. */ -/* This global variable holds the version tree that we build. */ - -struct bfd_elf_version_tree *lang_elf_version_info; - /* If PREV is NULL, return first version pattern matching particular symbol. If PREV is non-NULL, return first version pattern matching particular symbol after PREV (previously returned by lang_vers_match). */ @@ -7299,19 +7450,29 @@ lang_vers_match (struct bfd_elf_version_expr_head *head, struct bfd_elf_version_expr *prev, const char *sym) { + const char *c_sym; const char *cxx_sym = sym; const char *java_sym = sym; struct bfd_elf_version_expr *expr = NULL; + enum demangling_styles curr_style; + + curr_style = CURRENT_DEMANGLING_STYLE; + cplus_demangle_set_style (no_demangling); + c_sym = bfd_demangle (link_info.output_bfd, sym, DMGL_NO_OPTS); + if (!c_sym) + c_sym = sym; + cplus_demangle_set_style (curr_style); if (head->mask & BFD_ELF_VERSION_CXX_TYPE) { - cxx_sym = cplus_demangle (sym, DMGL_PARAMS | DMGL_ANSI); + cxx_sym = bfd_demangle (link_info.output_bfd, sym, + DMGL_PARAMS | DMGL_ANSI); if (!cxx_sym) cxx_sym = sym; } if (head->mask & BFD_ELF_VERSION_JAVA_TYPE) { - java_sym = cplus_demangle (sym, DMGL_JAVA); + java_sym = bfd_demangle (link_info.output_bfd, sym, DMGL_JAVA); if (!java_sym) java_sym = sym; } @@ -7325,10 +7486,10 @@ lang_vers_match (struct bfd_elf_version_expr_head *head, case 0: if (head->mask & BFD_ELF_VERSION_C_TYPE) { - e.pattern = sym; + e.pattern = c_sym; expr = (struct bfd_elf_version_expr *) htab_find ((htab_t) head->htab, &e); - while (expr && strcmp (expr->pattern, sym) == 0) + while (expr && strcmp (expr->pattern, c_sym) == 0) if (expr->mask == BFD_ELF_VERSION_C_TYPE) goto out_ret; else @@ -7386,12 +7547,14 @@ lang_vers_match (struct bfd_elf_version_expr_head *head, else if (expr->mask == BFD_ELF_VERSION_CXX_TYPE) s = cxx_sym; else - s = sym; + s = c_sym; if (fnmatch (expr->pattern, s, 0) == 0) break; } out_ret: + if (c_sym != sym) + free ((char *) c_sym); if (cxx_sym != sym) free ((char *) cxx_sym); if (java_sym != sym) @@ -7619,8 +7782,8 @@ lang_register_vers_node (const char *name, if (name == NULL) name = ""; - if ((name[0] == '\0' && lang_elf_version_info != NULL) - || (lang_elf_version_info && lang_elf_version_info->name[0] == '\0')) + if (link_info.version_info != NULL + && (name[0] == '\0' || link_info.version_info->name[0] == '\0')) { einfo (_("%X%P: anonymous version tag cannot be combined" " with other version tags\n")); @@ -7629,7 +7792,7 @@ lang_register_vers_node (const char *name, } /* Make sure this node has a unique name. */ - for (t = lang_elf_version_info; t != NULL; t = t->next) + for (t = link_info.version_info; t != NULL; t = t->next) if (strcmp (t->name, name) == 0) einfo (_("%X%P: duplicate version tag `%s'\n"), name); @@ -7641,7 +7804,7 @@ lang_register_vers_node (const char *name, for (e1 = version->globals.list; e1 != NULL; e1 = e1->next) { - for (t = lang_elf_version_info; t != NULL; t = t->next) + for (t = link_info.version_info; t != NULL; t = t->next) { struct bfd_elf_version_expr *e2; @@ -7668,7 +7831,7 @@ lang_register_vers_node (const char *name, for (e1 = version->locals.list; e1 != NULL; e1 = e1->next) { - for (t = lang_elf_version_info; t != NULL; t = t->next) + for (t = link_info.version_info; t != NULL; t = t->next) { struct bfd_elf_version_expr *e2; @@ -7704,7 +7867,7 @@ lang_register_vers_node (const char *name, else version->vernum = 0; - for (pp = &lang_elf_version_info; *pp != NULL; pp = &(*pp)->next) + for (pp = &link_info.version_info; *pp != NULL; pp = &(*pp)->next) ; *pp = version; } @@ -7720,7 +7883,7 @@ lang_add_vers_depend (struct bfd_elf_version_deps *list, const char *name) ret = (struct bfd_elf_version_deps *) xmalloc (sizeof *ret); ret->next = list; - for (t = lang_elf_version_info; t != NULL; t = t->next) + for (t = link_info.version_info; t != NULL; t = t->next) { if (strcmp (t->name, name) == 0) { @@ -7852,3 +8015,32 @@ lang_append_dynamic_list_cpp_new (void) lang_append_dynamic_list (dynamic); } + +/* Scan a space and/or comma separated string of features. */ + +void +lang_ld_feature (char *str) +{ + char *p, *q; + + p = str; + while (*p) + { + char sep; + while (*p == ',' || ISSPACE (*p)) + ++p; + if (!*p) + break; + q = p + 1; + while (*q && *q != ',' && !ISSPACE (*q)) + ++q; + sep = *q; + *q = 0; + if (strcasecmp (p, "SANE_EXPR") == 0) + config.sane_expr = TRUE; + else + einfo (_("%X%P: unknown feature `%s'\n"), p); + *q = sep; + p = q; + } +}