From 091244672e9cb571cb7272d491826f85de871ced Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 15 Jan 2008 23:41:28 +0000 Subject: From Andrew Chatham and Craig Silverstein: Add support for version scripts. --- gold/Makefile.in | 2 + gold/configure | 21 +- gold/configure.ac | 2 + gold/dynobj.cc | 64 ++++- gold/dynobj.h | 21 +- gold/layout.cc | 2 +- gold/main.cc | 3 +- gold/options.cc | 21 +- gold/options.h | 5 + gold/script-c.h | 38 +++ gold/script.cc | 475 +++++++++++++++++++++++++++++++----- gold/script.h | 83 +++++++ gold/symtab.cc | 48 ++-- gold/symtab.h | 25 +- gold/testsuite/Makefile.am | 18 +- gold/testsuite/Makefile.in | 23 +- gold/testsuite/ver_matching_def.cc | 69 ++++++ gold/testsuite/ver_matching_test.sh | 80 ++++++ gold/testsuite/version_script.map | 28 +++ gold/yyscript.y | 95 +++++++- 20 files changed, 1017 insertions(+), 106 deletions(-) create mode 100644 gold/testsuite/ver_matching_def.cc create mode 100755 gold/testsuite/ver_matching_test.sh create mode 100644 gold/testsuite/version_script.map diff --git a/gold/Makefile.in b/gold/Makefile.in index 8e02a9c..aa399ba 100644 --- a/gold/Makefile.in +++ b/gold/Makefile.in @@ -198,6 +198,8 @@ MSGMERGE = @MSGMERGE@ NATIVE_LINKER_FALSE = @NATIVE_LINKER_FALSE@ NATIVE_LINKER_TRUE = @NATIVE_LINKER_TRUE@ NO_WERROR = @NO_WERROR@ +OBJDUMP_AND_CPPFILT_FALSE = @OBJDUMP_AND_CPPFILT_FALSE@ +OBJDUMP_AND_CPPFILT_TRUE = @OBJDUMP_AND_CPPFILT_TRUE@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ diff --git a/gold/configure b/gold/configure index 66e6167..deccea5 100755 --- a/gold/configure +++ b/gold/configure @@ -309,7 +309,7 @@ ac_includes_default="\ # include #endif" -ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CYGPATH_W PACKAGE VERSION ACLOCAL AUTOCONF AUTOMAKE AUTOHEADER MAKEINFO install_sh STRIP ac_ct_STRIP INSTALL_STRIP_PROGRAM mkdir_p AWK SET_MAKE am__leading_dot AMTAR am__tar am__untar THREADS_TRUE THREADS_FALSE TARGETOBJS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT DEPDIR am__include am__quote AMDEP_TRUE AMDEP_FALSE AMDEPBACKSLASH CCDEPMODE am__fastdepCC_TRUE am__fastdepCC_FALSE CXX CXXFLAGS ac_ct_CXX CXXDEPMODE am__fastdepCXX_TRUE am__fastdepCXX_FALSE YACC RANLIB ac_ct_RANLIB LN_S USE_NLS LIBINTL LIBINTL_DEP INCINTL XGETTEXT GMSGFMT POSUB CATALOGS DATADIRNAME INSTOBJEXT GENCAT CATOBJEXT MKINSTALLDIRS MSGFMT MSGMERGE NATIVE_LINKER_TRUE NATIVE_LINKER_FALSE GCC_TRUE GCC_FALSE FN_PTRS_IN_SO_WITHOUT_PIC_TRUE FN_PTRS_IN_SO_WITHOUT_PIC_FALSE TLS_TRUE TLS_FALSE STATIC_TLS_TRUE STATIC_TLS_FALSE WARN_CFLAGS NO_WERROR WARN_CXXFLAGS LFS_CXXFLAGS LIBOBJS CPP EGREP CXXCPP MAINTAINER_MODE_TRUE MAINTAINER_MODE_FALSE MAINT LTLIBOBJS' +ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CYGPATH_W PACKAGE VERSION ACLOCAL AUTOCONF AUTOMAKE AUTOHEADER MAKEINFO install_sh STRIP ac_ct_STRIP INSTALL_STRIP_PROGRAM mkdir_p AWK SET_MAKE am__leading_dot AMTAR am__tar am__untar THREADS_TRUE THREADS_FALSE TARGETOBJS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT DEPDIR am__include am__quote AMDEP_TRUE AMDEP_FALSE AMDEPBACKSLASH CCDEPMODE am__fastdepCC_TRUE am__fastdepCC_FALSE CXX CXXFLAGS ac_ct_CXX CXXDEPMODE am__fastdepCXX_TRUE am__fastdepCXX_FALSE YACC RANLIB ac_ct_RANLIB LN_S USE_NLS LIBINTL LIBINTL_DEP INCINTL XGETTEXT GMSGFMT POSUB CATALOGS DATADIRNAME INSTOBJEXT GENCAT CATOBJEXT MKINSTALLDIRS MSGFMT MSGMERGE NATIVE_LINKER_TRUE NATIVE_LINKER_FALSE GCC_TRUE GCC_FALSE OBJDUMP_AND_CPPFILT_TRUE OBJDUMP_AND_CPPFILT_FALSE FN_PTRS_IN_SO_WITHOUT_PIC_TRUE FN_PTRS_IN_SO_WITHOUT_PIC_FALSE TLS_TRUE TLS_FALSE STATIC_TLS_TRUE STATIC_TLS_FALSE WARN_CFLAGS NO_WERROR WARN_CXXFLAGS LFS_CXXFLAGS LIBOBJS CPP EGREP CXXCPP MAINTAINER_MODE_TRUE MAINTAINER_MODE_FALSE MAINT LTLIBOBJS' ac_subst_files='' # Initialize some variables set by options. @@ -4459,6 +4459,16 @@ fi +if objdump --help >/dev/null && c++filt --help >/dev/null; then + OBJDUMP_AND_CPPFILT_TRUE= + OBJDUMP_AND_CPPFILT_FALSE='#' +else + OBJDUMP_AND_CPPFILT_TRUE='#' + OBJDUMP_AND_CPPFILT_FALSE= +fi + + + if case $target_cpu in @@ -6388,6 +6398,13 @@ echo "$as_me: error: conditional \"GCC\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi +if test -z "${OBJDUMP_AND_CPPFILT_TRUE}" && test -z "${OBJDUMP_AND_CPPFILT_FALSE}"; then + { { echo "$as_me:$LINENO: error: conditional \"OBJDUMP_AND_CPPFILT\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +echo "$as_me: error: conditional \"OBJDUMP_AND_CPPFILT\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi if test -z "${FN_PTRS_IN_SO_WITHOUT_PIC_TRUE}" && test -z "${FN_PTRS_IN_SO_WITHOUT_PIC_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"FN_PTRS_IN_SO_WITHOUT_PIC\" was never defined. Usually this means the macro was only invoked conditionally." >&5 @@ -7040,6 +7057,8 @@ s,@NATIVE_LINKER_TRUE@,$NATIVE_LINKER_TRUE,;t t s,@NATIVE_LINKER_FALSE@,$NATIVE_LINKER_FALSE,;t t s,@GCC_TRUE@,$GCC_TRUE,;t t s,@GCC_FALSE@,$GCC_FALSE,;t t +s,@OBJDUMP_AND_CPPFILT_TRUE@,$OBJDUMP_AND_CPPFILT_TRUE,;t t +s,@OBJDUMP_AND_CPPFILT_FALSE@,$OBJDUMP_AND_CPPFILT_FALSE,;t t s,@FN_PTRS_IN_SO_WITHOUT_PIC_TRUE@,$FN_PTRS_IN_SO_WITHOUT_PIC_TRUE,;t t s,@FN_PTRS_IN_SO_WITHOUT_PIC_FALSE@,$FN_PTRS_IN_SO_WITHOUT_PIC_FALSE,;t t s,@TLS_TRUE@,$TLS_TRUE,;t t diff --git a/gold/configure.ac b/gold/configure.ac index ea4fd67..cad5e02 100644 --- a/gold/configure.ac +++ b/gold/configure.ac @@ -157,6 +157,8 @@ AC_EXEEXT AM_CONDITIONAL(NATIVE_LINKER, test "x$target_alias" = "x" -o "x$host_alias" = "x$target_alias") AM_CONDITIONAL(GCC, test "$GCC" = yes) +AM_CONDITIONAL(OBJDUMP_AND_CPPFILT, + [objdump --help >/dev/null && c++filt --help >/dev/null]) dnl Some architectures do not support taking pointers of functions dnl defined in shared libraries except in -fPIC mode. We need to diff --git a/gold/dynobj.cc b/gold/dynobj.cc index 3c3549d..90abbe7 100644 --- a/gold/dynobj.cc +++ b/gold/dynobj.cc @@ -1,6 +1,6 @@ // dynobj.cc -- dynamic object support for gold -// Copyright 2006, 2007 Free Software Foundation, Inc. +// Copyright 2006, 2007, 2008 Free Software Foundation, Inc. // Written by Ian Lance Taylor . // This file is part of gold. @@ -1227,6 +1227,46 @@ Verneed::write(const Stringpool* dynpool, bool is_last, // Versions methods. +Versions::Versions(const General_options& options, Stringpool* dynpool) + : defs_(), needs_(), version_table_(), + is_finalized_(false), version_script_(options.version_script()) +{ + // We always need a base version, so define that first. Nothing + // explicitly declares itself as part of base, so it doesn't need to + // be in version_table_. + // FIXME: Should use soname here when creating a shared object. Is + // this fixme still valid? It looks like it's doing the right thing + // to me. + if (parameters->output_is_shared()) + { + const char* name = dynpool->add(parameters->output_file_name(), + false, NULL); + Verdef* vdbase = new Verdef(name, std::vector(), + true, false, true); + this->defs_.push_back(vdbase); + } + + if (!this->version_script_.empty()) + { + // Parse the version script, and insert each declared version into + // defs_ and version_table_. + std::vector versions = this->version_script_.get_versions(); + for (size_t k = 0; k < versions.size(); ++k) + { + Stringpool::Key version_key; + const char* version = dynpool->add(versions[k].c_str(), + true, &version_key); + Verdef* const vd = new Verdef( + version, + options.version_script().get_dependencies(version), + false, false, false); + this->defs_.push_back(vd); + Key key(version_key, 0); + this->version_table_.insert(std::make_pair(key, vd)); + } + } +} + Versions::~Versions() { for (Defs::iterator p = this->defs_.begin(); @@ -1265,7 +1305,7 @@ Versions::record_version(const Symbol_table* symtab, { gold_assert(!this->is_finalized_); gold_assert(sym->version() != NULL); - + Stringpool::Key version_key; const char* version = dynpool->add(sym->version(), false, &version_key); @@ -1292,7 +1332,7 @@ Versions::add_def(const Symbol* sym, const char* version, Version_base* const vbnull = NULL; std::pair ins = this->version_table_.insert(std::make_pair(k, vbnull)); - + if (!ins.second) { // We already have an entry for this version. @@ -1318,16 +1358,10 @@ Versions::add_def(const Symbol* sym, const char* version, return; } - // If this is the first version we are defining, first define - // the base version. FIXME: Should use soname here when - // creating a shared object. - Verdef* vdbase = new Verdef(parameters->output_file_name(), true, false, - true); - this->defs_.push_back(vdbase); - // When creating a regular executable, automatically define // a new version. - Verdef* vd = new Verdef(version, false, false, false); + Verdef* vd = new Verdef(version, std::vector(), + false, false, false); this->defs_.push_back(vd); ins.first->second = vd; } @@ -1499,10 +1533,14 @@ Versions::symbol_section_contents(const Symbol_table* symtab, const char* version = (*p)->version(); if (version == NULL) version_index = elfcpp::VER_NDX_GLOBAL; - else + else version_index = this->version_index(symtab, dynpool, *p); + // If the symbol was defined as foo@V1 instead of foo@@V1, add + // the hidden bit. + if ((*p)->version() != NULL && !(*p)->is_default()) + version_index |= elfcpp::VERSYM_HIDDEN; elfcpp::Swap<16, big_endian>::writeval(pbuf + (*p)->dynsym_index() * 2, - version_index); + version_index); } *pp = pbuf; diff --git a/gold/dynobj.h b/gold/dynobj.h index cc6ec54..a8c418d 100644 --- a/gold/dynobj.h +++ b/gold/dynobj.h @@ -32,6 +32,7 @@ namespace gold { class General_options; +class Version_script_info; // A dynamic object (ET_DYN). This is an abstract base class itself. // The implementations is the template class Sized_dynobj. @@ -309,8 +310,9 @@ class Version_base class Verdef : public Version_base { public: - Verdef(const char* name, bool is_base, bool is_weak, bool is_symbol_created) - : name_(name), deps_(), is_base_(is_base), is_weak_(is_weak), + Verdef(const char* name, const std::vector& deps, + bool is_base, bool is_weak, bool is_symbol_created) + : name_(name), deps_(deps), is_base_(is_base), is_weak_(is_weak), is_symbol_created_(is_symbol_created) { } @@ -358,7 +360,7 @@ class Verdef : public Version_base // The type of the list of version dependencies. Each dependency // should be canonicalized in the dynamic Stringpool. - typedef std::vector Deps; + typedef std::vector Deps; // The name of this version. This should be canonicalized in the // dynamic Stringpool. @@ -459,9 +461,7 @@ class Verneed class Versions { public: - Versions() - : defs_(), needs_(), version_table_(), is_finalized_(false) - { } + Versions(const General_options&, Stringpool*); ~Versions(); @@ -513,7 +513,14 @@ class Versions unsigned int* psize, unsigned int* pentries ACCEPT_SIZE_ENDIAN) const; + const Version_script_info& + version_script() const + { return this->version_script_; } + private: + Versions(const Versions&); + Versions& operator=(const Versions&); + // The type of the list of version definitions. typedef std::vector Defs; @@ -568,6 +575,8 @@ class Versions Version_table version_table_; // Whether the version indexes have been set. bool is_finalized_; + // Contents of --version-script, if passed, or NULL. + const Version_script_info& version_script_; }; } // End namespace gold. diff --git a/gold/layout.cc b/gold/layout.cc index 7db2e81..63fd2b8 100644 --- a/gold/layout.cc +++ b/gold/layout.cc @@ -683,7 +683,7 @@ Layout::finalize(const Input_objects* input_objects, Symbol_table* symtab, Output_section* dynstr; std::vector dynamic_symbols; unsigned int local_dynamic_count; - Versions versions; + Versions versions(this->options_, &this->dynpool_); this->create_dynamic_symtab(input_objects, target, symtab, &dynstr, &local_dynamic_count, &dynamic_symbols, &versions); diff --git a/gold/main.cc b/gold/main.cc index fb201d7..507e5dd 100644 --- a/gold/main.cc +++ b/gold/main.cc @@ -84,7 +84,8 @@ main(int argc, char** argv) // we're going to see based on the number of input files. Even when // this is off, it means at worse we don't quite optimize hashtable // resizing as well as we could have (perhap using more memory). - Symbol_table symtab(command_line.number_of_input_files() * 1024); + Symbol_table symtab(command_line.number_of_input_files() * 1024, + command_line.options().version_script()); // The layout object. Layout layout(command_line.options(), &script_options); diff --git a/gold/options.cc b/gold/options.cc index 9b5103c..9198136 100644 --- a/gold/options.cc +++ b/gold/options.cc @@ -155,7 +155,23 @@ invoke_script(int argc, char** argv, char* arg, bool long_option, arg, long_option, &ret); if (!read_commandline_script(script_name, cmdline)) - gold::gold_error(_("unable to parse script file %s"), script_name); + gold::gold_fatal(_("unable to parse script file %s"), script_name); + return ret; +} + +// Handle the special --version-script option, which reads a version script. + +int +invoke_version_script(int argc, char** argv, char* arg, bool long_option, + gold::Command_line* cmdline) +{ + int ret; + const char* script_name = cmdline->get_special_argument("version-script", + argc, argv, + arg, long_option, + &ret); + if (!read_version_script(script_name, cmdline)) + gold::gold_fatal(_("unable to parse version script file %s"), script_name); return ret; } @@ -458,6 +474,9 @@ options::Command_line_options::options[] = SPECIAL('T', "script", N_("Read linker script"), N_("-T FILE, --script FILE"), TWO_DASHES, &invoke_script), + SPECIAL('\0', "version-script", N_("Read version script"), + N_("--version-script FILE"), TWO_DASHES, + &invoke_version_script), GENERAL_NOARG('\0', "threads", N_("Run the linker multi-threaded"), NULL, TWO_DASHES, &General_options::set_threads), GENERAL_NOARG('\0', "no-threads", N_("Do not run the linker multi-threaded"), diff --git a/gold/options.h b/gold/options.h index e81856c..47623dc 100644 --- a/gold/options.h +++ b/gold/options.h @@ -233,6 +233,11 @@ class General_options sysroot() const { return this->sysroot_; } + // --version-script: The version script to apply if --shared is true. + const Version_script_info& + version_script() const + { return *this->script_options_->version_script_info(); } + // -Ttext: The address of the .text section uint64_t text_segment_address() const diff --git a/gold/script-c.h b/gold/script-c.h index 95816b7..275f4a2 100644 --- a/gold/script-c.h +++ b/gold/script-c.h @@ -107,6 +107,12 @@ script_parse_option(void* closure, const char*, size_t); extern void script_push_lex_into_expression_mode(void* closure); +/* Called by the bison parser to push the lexer into version + mode. */ + +extern void +script_push_lex_into_version_mode(void* closure); + /* Called by the bison parser to pop the lexer mode. */ extern void @@ -208,6 +214,38 @@ script_exp_function_segment_start(const char*, size_t, Expression_ptr); extern Expression_ptr script_exp_function_assert(Expression_ptr, const char*, size_t); +struct Version_dependency_list; +struct Version_expression_list; +struct Version_tree; + +extern void +script_register_vers_node(void* closure, + const char* tag, + int taglen, + struct Version_tree *, + struct Version_dependency_list *); + +extern struct Version_dependency_list * +script_add_vers_depend(void* closure, + struct Version_dependency_list *existing_dependencies, + const char *depend_to_add, int deplen); + +extern struct Version_expression_list * +script_new_vers_pattern(void* closure, + struct Version_expression_list *, + const char *, int); + +extern struct Version_tree * +script_new_vers_node(void* closure, + struct Version_expression_list *global, + struct Version_expression_list *local); + +extern void +version_script_push_lang(void* closure, const char* lang, int langlen); + +extern void +version_script_pop_lang(void* closure); + #ifdef __cplusplus } #endif diff --git a/gold/script.cc b/gold/script.cc index ae9cb86..16c0cc0 100644 --- a/gold/script.cc +++ b/gold/script.cc @@ -22,6 +22,7 @@ #include "gold.h" +#include #include #include #include @@ -29,6 +30,7 @@ #include "filenames.h" #include "elfcpp.h" +#include "demangle.h" #include "dirsearch.h" #include "options.h" #include "fileread.h" @@ -245,26 +247,32 @@ class Lex inline bool can_start_name(char c, char c2); - // Return whether C can appear in a name which has already started. - inline bool - can_continue_name(char c); + // If C can appear in a name which has already started, return a + // pointer to a character later in the token or just past + // it. Otherwise, return NULL. + inline const char* + can_continue_name(const char* c); // Return whether C, C2, C3 can start a hex number. inline bool can_start_hex(char c, char c2, char c3); - // Return whether C can appear in a hex number. - inline bool - can_continue_hex(char c); + // If C can appear in a hex number which has already started, return + // a pointer to a character later in the token or just past + // it. Otherwise, return NULL. + inline const char* + can_continue_hex(const char* c); // Return whether C can start a non-hex number. static inline bool can_start_number(char c); - // Return whether C can appear in a non-hex number. - inline bool - can_continue_number(char c) - { return Lex::can_start_number(c); } + // If C can appear in a decimal number which has already started, + // return a pointer to a character later in the token or just past + // it. Otherwise, return NULL. + inline const char* + can_continue_number(const char* c) + { return Lex::can_start_number(*c) ? c + 1 : NULL; } // If C1 C2 C3 form a valid three character operator, return the // opcode. Otherwise return 0. @@ -299,7 +307,7 @@ class Lex // MATCH. Set *PP to the character following the token. inline Token gather_token(Token::Classification, - bool (Lex::*can_continue_fn)(char), + const char* (Lex::*can_continue_fn)(const char*), const char* start, const char* match, const char** pp); // Build a token from a quoted string. @@ -382,7 +390,10 @@ Lex::can_start_name(char c, char c2) return this->mode_ == LINKER_SCRIPT; case '~': - return this->mode_ == LINKER_SCRIPT && can_continue_name(c2); + return this->mode_ == LINKER_SCRIPT && can_continue_name(&c2); + + case '*': case '[': + return this->mode_ == VERSION_SCRIPT; default: return false; @@ -395,10 +406,10 @@ Lex::can_start_name(char c, char c2) // script language requires spaces around operators, unless we know // that we are parsing an expression. -inline bool -Lex::can_continue_name(char c) +inline const char* +Lex::can_continue_name(const char* c) { - switch (c) + switch (*c) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': @@ -413,16 +424,38 @@ Lex::can_continue_name(char c) case '_': case '.': case '$': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - return true; + return c + 1; case '/': case '\\': case '~': - case '=': case '+': case '-': - case ':': case '[': case ']': - case ',': case '?': case '*': - return this->mode_ == LINKER_SCRIPT; + case '=': case '+': + case ',': case '?': + if (this->mode_ == LINKER_SCRIPT) + return c + 1; + return NULL; + + case '[': case ']': case '*': case '-': + if (this->mode_ == LINKER_SCRIPT || this->mode_ == VERSION_SCRIPT) + return c + 1; + return NULL; + + case '^': + if (this->mode_ == VERSION_SCRIPT) + return c + 1; + return NULL; + + case ':': + if (this->mode_ == LINKER_SCRIPT) + return c + 1; + else if (this->mode_ == VERSION_SCRIPT && (c[1] == ':')) + { + // A name can have '::' in it, as that's a c++ namespace + // separator. But a single colon is not part of a name. + return c + 2; + } + return NULL; default: - return false; + return NULL; } } @@ -439,25 +472,25 @@ inline bool Lex::can_start_hex(char c1, char c2, char c3) { if (c1 == '0' && (c2 == 'x' || c2 == 'X')) - return this->can_continue_hex(c3); + return this->can_continue_hex(&c3); return false; } // Return whether C can appear in a hex number. -inline bool -Lex::can_continue_hex(char c) +inline const char* +Lex::can_continue_hex(const char* c) { - switch (c) + switch (*c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': - return true; + return c + 1; default: - return false; + return NULL; } } @@ -652,13 +685,14 @@ Lex::skip_line_comment(const char** pp) inline Token Lex::gather_token(Token::Classification classification, - bool (Lex::*can_continue_fn)(char), + const char* (Lex::*can_continue_fn)(const char*), const char* start, const char* match, const char **pp) { - while ((this->*can_continue_fn)(*match)) - ++match; + const char* new_match = NULL; + while ((new_match = (this->*can_continue_fn)(match))) + match = new_match; *pp = match; return this->make_token(classification, start, match - start, start); } @@ -941,8 +975,13 @@ class Parser_closure : filename_(filename), posdep_options_(posdep_options), in_group_(in_group), is_in_sysroot_(is_in_sysroot), command_line_(command_line), script_options_(script_options), + version_script_info_(script_options->version_script_info()), lex_(lex), lineno_(0), charpos_(0), lex_mode_stack_(), inputs_(NULL) - { } + { + // We start out processing C symbols in the default lex mode. + language_stack_.push_back(""); + lex_mode_stack_.push_back(lex->mode()); + } // Return the file name. const char* @@ -978,6 +1017,11 @@ class Parser_closure script_options() { return this->script_options_; } + // Return the object in which version script information should be stored. + Version_script_info* + version_script() + { return this->version_script_info_; } + // Return the next token, and advance. const Token* next_token() @@ -1005,6 +1049,11 @@ class Parser_closure this->lex_mode_stack_.pop_back(); } + // Return the current lexer mode. + Lex::Mode + lex_mode() const + { return this->lex_mode_stack_.back(); } + // Return the line number of the last token. int lineno() const @@ -1030,6 +1079,23 @@ class Parser_closure saw_inputs() const { return this->inputs_ != NULL && !this->inputs_->empty(); } + // Return the current language being processed in a version script + // (eg, "C++"). The empty string represents unmangled C names. + const std::string& + get_current_language() const + { return this->language_stack_.back(); } + + // Push a language onto the stack when entering an extern block. + void push_language(const std::string& lang) + { this->language_stack_.push_back(lang); } + + // Pop a language off of the stack when exiting an extern block. + void pop_language() + { + gold_assert(!this->language_stack_.empty()); + this->language_stack_.pop_back(); + } + private: // The name of the file we are reading. const char* filename_; @@ -1043,6 +1109,8 @@ class Parser_closure Command_line* command_line_; // Options which may be set from any linker script. Script_options* script_options_; + // Information parsed from a version script. + Version_script_info* version_script_info_; // The lexer. Lex* lex_; // The line number of the last token returned by next_token. @@ -1051,6 +1119,9 @@ class Parser_closure int charpos_; // A stack of lexer modes. std::vector lex_mode_stack_; + // A stack of which extern/language block we're inside. Can be C++, + // java, or empty for C. + std::vector language_stack_; // New input files found to add to the link. Input_arguments* inputs_; }; @@ -1119,11 +1190,13 @@ read_input_script(Workqueue* workqueue, const General_options& options, return true; } -// FILENAME was found as an argument to --script (-T). -// Read it as a script, and execute its contents immediately. +// Helper function for read_version_script() and +// read_commandline_script(). Processes the given file in the mode +// indicated by first_token and lex_mode. -bool -read_commandline_script(const char* filename, Command_line* cmdline) +static bool +read_script_file(const char* filename, Command_line* cmdline, + int first_token, Lex::Mode lex_mode) { // TODO: if filename is a relative filename, search for it manually // using "." + cmdline->options()->search_path() -- not dirsearch. @@ -1143,7 +1216,8 @@ read_commandline_script(const char* filename, Command_line* cmdline) std::string input_string; Lex::read_file(&input_file, &input_string); - Lex lex(input_string.c_str(), input_string.length(), PARSING_LINKER_SCRIPT); + Lex lex(input_string.c_str(), input_string.length(), first_token); + lex.set_mode(lex_mode); Parser_closure closure(filename, cmdline->position_dependent_options(), @@ -1165,6 +1239,27 @@ read_commandline_script(const char* filename, Command_line* cmdline) return true; } +// FILENAME was found as an argument to --script (-T). +// Read it as a script, and execute its contents immediately. + +bool +read_commandline_script(const char* filename, Command_line* cmdline) +{ + return read_script_file(filename, cmdline, + PARSING_LINKER_SCRIPT, Lex::LINKER_SCRIPT); +} + +// FILE was found as an argument to --version-script. Read it as a +// version script, and store its contents in +// cmdline->script_options()->version_script_info(). + +bool +read_version_script(const char* filename, Command_line* cmdline) +{ + return read_script_file(filename, cmdline, + PARSING_VERSION_SCRIPT, Lex::VERSION_SCRIPT); +} + // Implement the --defsym option on the command line. Return true if // all is well. @@ -1189,7 +1284,8 @@ Script_options::define_symbol(const char* definition) } // Manage mapping from keywords to the codes expected by the bison -// parser. +// parser. We construct one global object for each lex mode with +// keywords. class Keyword_to_parsecode { @@ -1203,25 +1299,27 @@ class Keyword_to_parsecode int parsecode; }; + Keyword_to_parsecode(const Keyword_parsecode* keywords, + int keyword_count) + : keyword_parsecodes_(keywords), keyword_count_(keyword_count) + { } + // Return the parsecode corresponding KEYWORD, or 0 if it is not a // keyword. - static int - keyword_to_parsecode(const char* keyword, size_t len); + int + keyword_to_parsecode(const char* keyword, size_t len) const; private: - // The array of all keywords. - static const Keyword_parsecode keyword_parsecodes_[]; - - // The number of keywords. - static const int keyword_count; + const Keyword_parsecode* keyword_parsecodes_; + const int keyword_count_; }; // Mapping from keyword string to keyword parsecode. This array must // be kept in sorted order. Parsecodes are looked up using bsearch. // This array must correspond to the list of parsecodes in yyscript.y. -const Keyword_to_parsecode::Keyword_parsecode -Keyword_to_parsecode::keyword_parsecodes_[] = +static const Keyword_to_parsecode::Keyword_parsecode +script_keyword_parsecodes[] = { { "ABSOLUTE", ABSOLUTE }, { "ADDR", ADDR }, @@ -1303,9 +1401,23 @@ Keyword_to_parsecode::keyword_parsecodes_[] = { "sizeof_headers", SIZEOF_HEADERS }, }; -const int Keyword_to_parsecode::keyword_count = - (sizeof(Keyword_to_parsecode::keyword_parsecodes_) - / sizeof(Keyword_to_parsecode::keyword_parsecodes_[0])); +static const Keyword_to_parsecode +script_keywords(&script_keyword_parsecodes[0], + (sizeof(script_keyword_parsecodes) + / sizeof(script_keyword_parsecodes[0]))); + +static const Keyword_to_parsecode::Keyword_parsecode +version_script_keyword_parsecodes[] = +{ + { "extern", EXTERN }, + { "global", GLOBAL }, + { "local", LOCAL }, +}; + +static const Keyword_to_parsecode +version_script_keywords(&version_script_keyword_parsecodes[0], + (sizeof(version_script_keyword_parsecodes) + / sizeof(version_script_keyword_parsecodes[0]))); // Comparison function passed to bsearch. @@ -1335,16 +1447,17 @@ ktt_compare(const void* keyv, const void* kttv) } // End extern "C". int -Keyword_to_parsecode::keyword_to_parsecode(const char* keyword, size_t len) +Keyword_to_parsecode::keyword_to_parsecode(const char* keyword, + size_t len) const { Ktt_key key; key.str = keyword; key.len = len; void* kttv = bsearch(&key, - Keyword_to_parsecode::keyword_parsecodes_, - Keyword_to_parsecode::keyword_count, - sizeof(Keyword_to_parsecode::keyword_parsecodes_[0]), - ktt_compare); + this->keyword_parsecodes_, + this->keyword_count_, + sizeof(this->keyword_parsecodes_[0]), + ktt_compare); if (kttv == NULL) return 0; Keyword_parsecode* ktt = static_cast(kttv); @@ -1383,7 +1496,18 @@ yylex(YYSTYPE* lvalp, void* closurev) // This is either a keyword or a STRING. size_t len; const char* str = token->string_value(&len); - int parsecode = Keyword_to_parsecode::keyword_to_parsecode(str, len); + int parsecode = 0; + switch (closure->lex_mode()) + { + case Lex::LINKER_SCRIPT: + parsecode = script_keywords.keyword_to_parsecode(str, len); + break; + case Lex::VERSION_SCRIPT: + parsecode = version_script_keywords.keyword_to_parsecode(str, len); + break; + default: + break; + } if (parsecode != 0) return parsecode; lvalp->string.value = str; @@ -1561,6 +1685,16 @@ script_push_lex_into_expression_mode(void* closurev) closure->push_lex_mode(Lex::EXPRESSION); } +/* Called by the bison parser to push the lexer into version + mode. */ + +extern void +script_push_lex_into_version_mode(void* closurev) +{ + Parser_closure* closure = static_cast(closurev); + closure->push_lex_mode(Lex::VERSION_SCRIPT); +} + /* Called by the bison parser to pop the lexer mode. */ extern void @@ -1569,3 +1703,234 @@ script_pop_lex_mode(void* closurev) Parser_closure* closure = static_cast(closurev); closure->pop_lex_mode(); } + +// The following structs are used within the VersionInfo class as well +// as in the bison helper functions. They store the information +// parsed from the version script. + +// A single version expression. +// For example, pattern="std::map*" and language="C++". +// pattern and language should be from the stringpool +struct Version_expression { + Version_expression(const std::string& pattern, + const std::string& language) + : pattern(pattern), language(language) {} + + std::string pattern; + std::string language; +}; + + +// A list of expressions. +struct Version_expression_list { + std::vector expressions; +}; + + +// A list of which versions upon which another version depends. +// Strings should be from the Stringpool. +struct Version_dependency_list { + std::vector dependencies; +}; + + +// The total definition of a version. It includes the tag for the +// version, its global and local expressions, and any dependencies. +struct Version_tree { + Version_tree() + : tag(), global(NULL), local(NULL), dependencies(NULL) {} + + std::string tag; + const struct Version_expression_list* global; + const struct Version_expression_list* local; + const struct Version_dependency_list* dependencies; +}; + +Version_script_info::~Version_script_info() +{ + for (size_t k = 0; k < dependency_lists_.size(); ++k) + delete dependency_lists_[k]; + for (size_t k = 0; k < version_trees_.size(); ++k) + delete version_trees_[k]; + for (size_t k = 0; k < expression_lists_.size(); ++k) + delete expression_lists_[k]; +} + +std::vector +Version_script_info::get_versions() const +{ + std::vector ret; + for (size_t j = 0; j < version_trees_.size(); ++j) + ret.push_back(version_trees_[j]->tag); + return ret; +} + +std::vector +Version_script_info::get_dependencies(const char* version) const +{ + std::vector ret; + for (size_t j = 0; j < version_trees_.size(); ++j) + if (version_trees_[j]->tag == version) + { + const struct Version_dependency_list* deps = + version_trees_[j]->dependencies; + if (deps != NULL) + for (size_t k = 0; k < deps->dependencies.size(); ++k) + ret.push_back(deps->dependencies[k]); + return ret; + } + return ret; +} + +const std::string& +Version_script_info::get_symbol_version_helper(const char* symbol_name, + bool check_global) const +{ + for (size_t j = 0; j < version_trees_.size(); ++j) + { + // Is it a global symbol for this version? + const Version_expression_list* exp = + check_global ? version_trees_[j]->global : version_trees_[j]->local; + if (exp != NULL) + for (size_t k = 0; k < exp->expressions.size(); ++k) + { + const char* name_to_match = symbol_name; + char* demangled_name = NULL; + if (exp->expressions[k].language == "C++") + { + demangled_name = cplus_demangle(symbol_name, + DMGL_ANSI | DMGL_PARAMS); + // This isn't a C++ symbol. + if (demangled_name == NULL) + continue; + name_to_match = demangled_name; + } + else if (exp->expressions[k].language == "Java") + { + demangled_name = cplus_demangle(symbol_name, + (DMGL_ANSI | DMGL_PARAMS + | DMGL_JAVA)); + // This isn't a Java symbol. + if (demangled_name == NULL) + continue; + name_to_match = demangled_name; + } + bool matched = fnmatch(exp->expressions[k].pattern.c_str(), + name_to_match, FNM_NOESCAPE) == 0; + if (demangled_name != NULL) + free(demangled_name); + if (matched) + return version_trees_[j]->tag; + } + } + static const std::string empty = ""; + return empty; +} + +struct Version_dependency_list* +Version_script_info::allocate_dependency_list() +{ + dependency_lists_.push_back(new Version_dependency_list); + return dependency_lists_.back(); +} + +struct Version_expression_list* +Version_script_info::allocate_expression_list() +{ + expression_lists_.push_back(new Version_expression_list); + return expression_lists_.back(); +} + +struct Version_tree* +Version_script_info::allocate_version_tree() +{ + version_trees_.push_back(new Version_tree); + return version_trees_.back(); +} + +// Register an entire version node. For example: +// +// GLIBC_2.1 { +// global: foo; +// } GLIBC_2.0; +// +// - tag is "GLIBC_2.1" +// - tree contains the information "global: foo" +// - deps contains "GLIBC_2.0" + +extern "C" void +script_register_vers_node(void*, + const char* tag, + int taglen, + struct Version_tree *tree, + struct Version_dependency_list *deps) +{ + gold_assert(tree != NULL); + gold_assert(tag != NULL); + tree->dependencies = deps; + tree->tag = std::string(tag, taglen); +} + +// Add a dependencies to the list of existing dependencies, if any, +// and return the expanded list. + +extern "C" struct Version_dependency_list * +script_add_vers_depend(void* closurev, + struct Version_dependency_list *all_deps, + const char *depend_to_add, int deplen) +{ + Parser_closure* closure = static_cast(closurev); + if (all_deps == NULL) + all_deps = closure->version_script()->allocate_dependency_list(); + all_deps->dependencies.push_back(std::string(depend_to_add, deplen)); + return all_deps; +} + +// Add a pattern expression to an existing list of expressions, if any. +// TODO: In the old linker, the last argument used to be a bool, but I +// don't know what it meant. + +extern "C" struct Version_expression_list * +script_new_vers_pattern(void* closurev, + struct Version_expression_list *expressions, + const char *pattern, int patlen) +{ + Parser_closure* closure = static_cast(closurev); + if (expressions == NULL) + expressions = closure->version_script()->allocate_expression_list(); + expressions->expressions.push_back( + Version_expression(std::string(pattern, patlen), + closure->get_current_language())); + return expressions; +} + +// Combine the global and local expressions into a a Version_tree. + +extern "C" struct Version_tree * +script_new_vers_node(void* closurev, + struct Version_expression_list *global, + struct Version_expression_list *local) +{ + Parser_closure* closure = static_cast(closurev); + Version_tree* tree = closure->version_script()->allocate_version_tree(); + tree->global = global; + tree->local = local; + return tree; +} + +// Handle a transition in language, such as at the +// start or end of 'extern "C++"' + +extern "C" void +version_script_push_lang(void* closurev, const char* lang, int langlen) +{ + Parser_closure* closure = static_cast(closurev); + closure->push_language(std::string(lang, langlen)); +} + +extern "C" void +version_script_pop_lang(void* closurev) +{ + Parser_closure* closure = static_cast(closurev); + closure->pop_language(); +} diff --git a/gold/script.h b/gold/script.h index 0dfa4bb..69906cb 100644 --- a/gold/script.h +++ b/gold/script.h @@ -32,6 +32,10 @@ #include +struct Version_dependency_list; +struct Version_expression_list; +struct Version_tree; + namespace gold { @@ -80,6 +84,69 @@ class Expression Expression& operator=(const Expression&); }; + +// Version_script_info stores information parsed from the version +// script, either provided by --version-script or as part of a linker +// script. A single Version_script_info object per target is owned by +// Script_options. + +class Version_script_info { + public: + ~Version_script_info(); + + // Return whether any version were defined in the version script. + bool + empty() const + { return this->version_trees_.empty(); } + + // Return the version associated with the given symbol name. + // Strings are allocated out of the stringpool given in the + // constructor. Strings are allocated out of the stringpool given + // in the constructor. + const std::string& + get_symbol_version(const char* symbol) const + { return get_symbol_version_helper(symbol, true); } + + // Return whether this symbol matches the local: section of a + // version script (it doesn't matter which). This test is only + // valid if get_symbol_version() returns the empty string, as we + // don't test that here. + bool + symbol_is_local(const char* symbol) const + { return !get_symbol_version_helper(symbol, false).empty(); } + + // Return the names of versions defined in the version script. + // Strings are allocated out of the stringpool given in the + // constructor. + std::vector + get_versions() const; + + // Return the list of dependencies for this version. + std::vector + get_dependencies(const char* version) const; + + // The following functions should only be used by the bison helper + // functions. They allocate new structs whose memory belongs to + // Version_script_info. The bison functions copy the information + // from the version script into these structs. + struct Version_dependency_list* + allocate_dependency_list(); + + struct Version_expression_list* + allocate_expression_list(); + + struct Version_tree* + allocate_version_tree(); + + private: + const std::string& get_symbol_version_helper(const char* symbol, + bool check_global) const; + + std::vector dependency_lists_; + std::vector expression_lists_; + std::vector version_trees_; +}; + // We can read a linker script in two different contexts: when // initially parsing the command line, and when we find an input file // which is actually a linker script. Also some of the data which can @@ -127,6 +194,12 @@ class Script_options void finalize_symbols(Symbol_table*, const Layout*); + // Version information parsed from a version script. Everything + // else has a pointer to this object. + Version_script_info* + version_script_info() + { return &version_script_info_; } + private: // We keep a list of symbol assignments. struct Symbol_assignment @@ -160,6 +233,8 @@ class Script_options std::string entry_; // Symbols to set. Symbol_assignments symbol_assignments_; + // Version information parsed from a version script. + Version_script_info version_script_info_; }; // FILE was found as an argument on the command line, but was not @@ -181,6 +256,14 @@ read_input_script(Workqueue*, const General_options&, Symbol_table*, Layout*, bool read_commandline_script(const char* filename, Command_line*); +// FILE was found as an argument to --version-script. Read it as a +// version script, and store its contents in +// cmdline->script_options()->version_script_info(). + +bool +read_version_script(const char* filename, Command_line* cmdline); + + } // End namespace gold. #endif // !defined(GOLD_SCRIPT_H) diff --git a/gold/symtab.cc b/gold/symtab.cc index ae8e751..e49178a 100644 --- a/gold/symtab.cc +++ b/gold/symtab.cc @@ -295,9 +295,10 @@ Symbol::final_value_is_known() const // Class Symbol_table. -Symbol_table::Symbol_table(unsigned int count) +Symbol_table::Symbol_table(unsigned int count, + const Version_script_info& version_script) : saw_undefined_(0), offset_(0), table_(count), namepool_(), - forwarders_(), commons_(), warnings_() + forwarders_(), commons_(), warnings_(), version_script_(version_script) { namepool_.reserve(count); } @@ -571,6 +572,7 @@ Symbol_table::add_from_object(Object* object, if (!was_common && ret->is_common()) this->commons_.push_back(ret); + ret->set_is_default(def); return ret; } @@ -626,6 +628,31 @@ Symbol_table::add_from_relobj( // name from the version name. If there are two '@' characters, // this is the default version. const char* ver = strchr(name, '@'); + int namelen = 0; + bool def = false; + + if (ver != NULL) + { + // The symbol name is of the form foo@VERSION or foo@@VERSION + namelen = ver - name; + ++ver; + if (*ver == '@') + { + def = true; + ++ver; + } + } + else if (!version_script_.empty()) + { + // The symbol name did not have a version, but + // the version script may assign a version anyway. + namelen = strlen(name); + def = true; + const std::string& version = + version_script_.get_symbol_version(name); + if (!version.empty()) + ver = version.c_str(); + } Sized_symbol* res; if (ver == NULL) @@ -638,17 +665,8 @@ Symbol_table::add_from_relobj( else { Stringpool::Key name_key; - name = this->namepool_.add_with_length(name, ver - name, true, + name = this->namepool_.add_with_length(name, namelen, true, &name_key); - - bool def = false; - ++ver; - if (*ver == '@') - { - def = true; - ++ver; - } - Stringpool::Key ver_key; ver = this->namepool_.add(ver, true, &ver_key); @@ -1238,7 +1256,7 @@ Symbol_table::do_define_as_constant( if (sym == NULL) return NULL; - gold_assert(version == NULL || oldsym != NULL); + gold_assert(version == NULL || version == name || oldsym != NULL); sym->init(name, value, symsize, type, binding, visibility, nonvis); if (oldsym == NULL) @@ -1392,8 +1410,8 @@ Symbol_table::set_dynsym_indexes(const Target* target, dynpool->add(sym->name(), false, NULL); // Record any version information. - if (sym->version() != NULL) - versions->record_version(this, dynpool, sym); + if (sym->version() != NULL) + versions->record_version(this, dynpool, sym); } } diff --git a/gold/symtab.h b/gold/symtab.h index 59cda2e..43a228d 100644 --- a/gold/symtab.h +++ b/gold/symtab.h @@ -46,6 +46,7 @@ class Dynobj; template class Sized_dynobj; class Versions; +class Version_script_info; class Input_objects; class Output_data; class Output_section; @@ -111,6 +112,21 @@ class Symbol version() const { return this->version_; } + // Return whether this version is the default for this symbol name + // (eg, "foo@@V2" is a default version; "foo@V1" is not). Only + // meaningful for versioned symbols. + bool + is_default() const + { + gold_assert(this->version_ != NULL); + return this->is_def_; + } + + // Set whether this version is the default for this symbol name. + void + set_is_default(bool def) + { this->is_def_ = def; } + // Return the symbol source. Source source() const @@ -953,7 +969,7 @@ class Symbol_table // COUNT is an estimate of how many symbosl will be inserted in the // symbol table. It's ok to put 0 if you don't know; a correct // guess will just save some CPU by reducing hashtable resizes. - Symbol_table(unsigned int count); + Symbol_table(unsigned int count, const Version_script_info& version_script); ~Symbol_table(); @@ -1115,6 +1131,11 @@ class Symbol_table void print_stats() const; + // Return the version script information. + const Version_script_info& + version_script() const + { return version_script_; } + private: Symbol_table(const Symbol_table&); Symbol_table& operator=(const Symbol_table&); @@ -1347,6 +1368,8 @@ class Symbol_table // definition. This maps symbols with COPY relocs to the dynamic // object where they were defined. Copied_symbol_dynobjs copied_symbol_dynobjs_; + // Information parsed from the version script, if any. + const Version_script_info& version_script_; }; // We inline get_sized_symbol for efficiency. diff --git a/gold/testsuite/Makefile.am b/gold/testsuite/Makefile.am index 46e949c..5820038 100644 --- a/gold/testsuite/Makefile.am +++ b/gold/testsuite/Makefile.am @@ -490,10 +490,10 @@ ver_test_LDFLAGS = -Bgcctestdir/ -Wl,-R,. ver_test_LDADD = ver_test_1.so ver_test_2.so ver_test_4.so ver_test_1.so: ver_test_1.o ver_test_2.so ver_test_3.o ver_test_4.so gcctestdir/ld $(CXXLINK) -Bgcctestdir/ -shared ver_test_1.o ver_test_2.so ver_test_3.o ver_test_4.so -ver_test_2.so: ver_test_2.o $(srcdir)/ver_test_2.script ver_test_4.so - $(CXXLINK) -shared -Wl,--version-script,$(srcdir)/ver_test_2.script ver_test_2.o ver_test_4.so -ver_test_4.so: ver_test_4.o $(srcdir)/ver_test_4.script - $(CXXLINK) -shared -Wl,--version-script,$(srcdir)/ver_test_4.script ver_test_4.o +ver_test_2.so: ver_test_2.o $(srcdir)/ver_test_2.script ver_test_4.so gcctestdir/ld + $(CXXLINK) -Bgcctestdir/ -shared -Wl,--version-script,$(srcdir)/ver_test_2.script ver_test_2.o ver_test_4.so +ver_test_4.so: ver_test_4.o $(srcdir)/ver_test_4.script gcctestdir/ld + $(CXXLINK) -Bgcctestdir/ -shared -Wl,--version-script,$(srcdir)/ver_test_4.script ver_test_4.o ver_test_1.o: ver_test_1.cc $(CXXCOMPILE) -c -fpic -o $@ $< ver_test_2.o: ver_test_2.cc @@ -508,5 +508,15 @@ script_test_1_SOURCES = script_test_1.cc script_test_1_DEPENDENCIES = gcctestdir/ld script_test_1.t script_test_1_LDFLAGS = -Bgcctestdir/ -Wl,-R,. -T $(srcdir)/script_test_1.t +if OBJDUMP_AND_CPPFILT +check_SCRIPTS += ver_matching_test.sh +check_DATA += ver_matching_test.stdout +MOSTLYCLEANFILES += ver_matching_test.stdout +ver_matching_def.so: ver_matching_def.cc gcctestdir/ld + $(CXXLINK) -O0 -Bgcctestdir/ -shared $(srcdir)/ver_matching_def.cc -Wl,--version-script=$(srcdir)/version_script.map +ver_matching_test.stdout: ver_matching_def.so + objdump -T ver_matching_def.so | c++filt > ver_matching_test.stdout +endif OBJDUMP_AND_CPPFILT + endif GCC endif NATIVE_LINKER diff --git a/gold/testsuite/Makefile.in b/gold/testsuite/Makefile.in index f61e4e5..8f51d28 100644 --- a/gold/testsuite/Makefile.in +++ b/gold/testsuite/Makefile.in @@ -179,6 +179,9 @@ check_PROGRAMS = object_unittest$(EXEEXT) $(am__EXEEXT_1) \ @NATIVE_LINKER_FALSE@ ../libgold.a ../../libiberty/libiberty.a \ @NATIVE_LINKER_FALSE@ $(am__DEPENDENCIES_1) \ @NATIVE_LINKER_FALSE@ $(am__DEPENDENCIES_1) +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@am__append_11 = ver_matching_test.sh +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@am__append_12 = ver_matching_test.stdout +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@am__append_13 = ver_matching_test.stdout subdir = testsuite DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -633,6 +636,8 @@ MSGMERGE = @MSGMERGE@ NATIVE_LINKER_FALSE = @NATIVE_LINKER_FALSE@ NATIVE_LINKER_TRUE = @NATIVE_LINKER_TRUE@ NO_WERROR = @NO_WERROR@ +OBJDUMP_AND_CPPFILT_FALSE = @OBJDUMP_AND_CPPFILT_FALSE@ +OBJDUMP_AND_CPPFILT_TRUE = @OBJDUMP_AND_CPPFILT_TRUE@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ @@ -722,13 +727,13 @@ INCLUDES = \ # .o's), but not all of them (such as .so's and .err files). We # improve on that here. automake-1.9 info docs say "mostlyclean" is # the right choice for files 'make' builds that people rebuild. -MOSTLYCLEANFILES = *.so $(am__append_9) +MOSTLYCLEANFILES = *.so $(am__append_9) $(am__append_13) # We will add to these later, for each individual test. Note # that we add each test under check_SCRIPTS or check_PROGRAMS; # the TESTS variable is automatically populated from these. -check_SCRIPTS = $(am__append_7) -check_DATA = $(am__append_8) +check_SCRIPTS = $(am__append_7) $(am__append_11) +check_DATA = $(am__append_8) $(am__append_12) TESTS = $(check_SCRIPTS) $(check_PROGRAMS) # --------------------------------------------------------------------- @@ -1561,10 +1566,10 @@ uninstall-am: uninstall-info-am @GCC_TRUE@@NATIVE_LINKER_TRUE@ test -s $@ @GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_1.so: ver_test_1.o ver_test_2.so ver_test_3.o ver_test_4.so gcctestdir/ld @GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXLINK) -Bgcctestdir/ -shared ver_test_1.o ver_test_2.so ver_test_3.o ver_test_4.so -@GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_2.so: ver_test_2.o $(srcdir)/ver_test_2.script ver_test_4.so -@GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXLINK) -shared -Wl,--version-script,$(srcdir)/ver_test_2.script ver_test_2.o ver_test_4.so -@GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_4.so: ver_test_4.o $(srcdir)/ver_test_4.script -@GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXLINK) -shared -Wl,--version-script,$(srcdir)/ver_test_4.script ver_test_4.o +@GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_2.so: ver_test_2.o $(srcdir)/ver_test_2.script ver_test_4.so gcctestdir/ld +@GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXLINK) -Bgcctestdir/ -shared -Wl,--version-script,$(srcdir)/ver_test_2.script ver_test_2.o ver_test_4.so +@GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_4.so: ver_test_4.o $(srcdir)/ver_test_4.script gcctestdir/ld +@GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXLINK) -Bgcctestdir/ -shared -Wl,--version-script,$(srcdir)/ver_test_4.script ver_test_4.o @GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_1.o: ver_test_1.cc @GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXCOMPILE) -c -fpic -o $@ $< @GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_2.o: ver_test_2.cc @@ -1573,6 +1578,10 @@ uninstall-am: uninstall-info-am @GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXCOMPILE) -c -fpic -o $@ $< @GCC_TRUE@@NATIVE_LINKER_TRUE@ver_test_4.o: ver_test_4.cc @GCC_TRUE@@NATIVE_LINKER_TRUE@ $(CXXCOMPILE) -c -fpic -o $@ $< +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@ver_matching_def.so: ver_matching_def.cc gcctestdir/ld +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@ $(CXXLINK) -O0 -Bgcctestdir/ -shared $(srcdir)/ver_matching_def.cc -Wl,--version-script=$(srcdir)/version_script.map +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@ver_matching_test.stdout: ver_matching_def.so +@GCC_TRUE@@NATIVE_LINKER_TRUE@@OBJDUMP_AND_CPPFILT_TRUE@ objdump -T ver_matching_def.so | c++filt > ver_matching_test.stdout # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: diff --git a/gold/testsuite/ver_matching_def.cc b/gold/testsuite/ver_matching_def.cc new file mode 100644 index 0000000..71d8d32 --- /dev/null +++ b/gold/testsuite/ver_matching_def.cc @@ -0,0 +1,69 @@ +// ver_matching_def.cc - test matching rules in version_script.map + +// Copyright 2007 Free Software Foundation, Inc. +// Written by Cary Coutant . + +// This file is part of gold. + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, +// MA 02110-1301, USA. + +extern "C" { +void foo() {} // V1 +void foo1() {} // local +}; + +void bar() {} // V1 +void bar1() {} // global + +extern "C" { +void bar2() {} // V1 +}; + +namespace myns { +void blah() {} // V1 +void bip() {} // V1 + +class Stuff { + public: + Stuff() {} // V1 +}; +} + +class Biz { + public: + Biz() {} // global +}; + +namespace otherns { +Biz biz; // global +myns::Stuff stuff; // V2 +}; + +extern "C" { +void blaza() {} // V1 +void blaza1() {} // V1 + +void original_blaza2() {} // V2 +__asm__(".symver original_blaza2,blaza2@@V2"); // overrides script + +void bla() {} // global +void blaz() {} // V2 +void blazb() {} // V2 + +int globaoeufxstuff = 0; // V1 +int globaoeufostuff = 0; // global +float sizeof_headers = 50.0; // V1 +}; diff --git a/gold/testsuite/ver_matching_test.sh b/gold/testsuite/ver_matching_test.sh new file mode 100755 index 0000000..48dd9a5 --- /dev/null +++ b/gold/testsuite/ver_matching_test.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +# ver_matching_test.sh -- a test case for version script matching + +# Copyright 2008 Free Software Foundation, Inc. +# Written by Ian Lance Taylor . + +# This file is part of gold. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, +# MA 02110-1301, USA. + +# This file goes with ver_matching_def.cc, a C++ source file +# constructed with several symbols mapped via version_script.map. We +# run readelf on the resulting shared object and check that each +# symbol has the correct version. + +check() +{ + if ! grep -q "$2" "$1" + then + echo "Did not find expected symbol in $1:" + echo " $2" + echo "" + echo "Actual output below:" + cat "$1" + exit 1 + fi +} + +check_missing() +{ + if grep -q "$2" "$1" + then + echo "Found unexpected symbol in $1:" + echo " $2" + echo "" + echo "Actual output below:" + cat "$1" + exit 1 + fi +} + +check ver_matching_test.stdout "V1 *sizeof_headers$" +check ver_matching_test.stdout "Base *globaoeufostuff$" +check ver_matching_test.stdout "V1 *globaoeufxstuff$" +check ver_matching_test.stdout "V2 *otherns::stuff$" +check ver_matching_test.stdout "Base *otherns::biz$" +check ver_matching_test.stdout "V1 *foo$" +check ver_matching_test.stdout "V1 *bar()$" +check ver_matching_test.stdout "Base *bar1()$" +check ver_matching_test.stdout "V1 *bar2$" +check ver_matching_test.stdout "V1 *myns::blah()$" +check ver_matching_test.stdout "V1 *myns::bip()$" +check ver_matching_test.stdout "V1 *myns::Stuff::Stuff()$" +check ver_matching_test.stdout "Base *Biz::Biz()$" +check ver_matching_test.stdout "V1 *blaza1$" +check ver_matching_test.stdout "V2 *blaza2$" +check ver_matching_test.stdout "V1 *blaza$" +check ver_matching_test.stdout "Base *bla$" +check ver_matching_test.stdout "V2 *blaz$" +check ver_matching_test.stdout "V2 *blazb$" + +# TODO: foo1 should be a local symbol and not show up in the .dynsym +# dump, but we haven't figured out how to suppress it yet. +# check_missing ver_matching_test.stdout "foo1" + +exit 0 diff --git a/gold/testsuite/version_script.map b/gold/testsuite/version_script.map new file mode 100644 index 0000000..2a17523 --- /dev/null +++ b/gold/testsuite/version_script.map @@ -0,0 +1,28 @@ +V1 { + global: + extern "C++" + { + "bar()"; + myns::*; + }; + foo; + blaza*; + bar*; + # Would be a keyword in a linker script. + SECTIONS; + sizeof_headers; + # Crazy globbiness + glob*f[^A-Zo]stuff; + + local: + *foo*; +}; + +V2 { + global: + extern "C++" { + otherns::stuff; + }; + blaz*; + foo; +} V1; diff --git a/gold/yyscript.y b/gold/yyscript.y index a1f954c..40acb00a2 100644 --- a/gold/yyscript.y +++ b/gold/yyscript.y @@ -55,6 +55,10 @@ uint64_t integer; /* An expression. */ Expression_ptr expr; + // Used for version scripts and within VERSION {} + struct Version_dependency_list* deplist; + struct Version_expression_list* versyms; + struct Version_tree* versnode; } /* Operators, including a precedence table for expressions. */ @@ -178,6 +182,9 @@ /* Non-terminal types, where needed. */ %type parse_exp exp +%type vers_defns +%type vers_tag +%type verdep %% @@ -202,6 +209,10 @@ file_cmd: { script_end_group(closure); } | OPTION '(' STRING ')' { script_parse_option(closure, $3.value, $3.length); } + | VERSIONK '{' + { script_push_lex_into_version_mode(closure); } + version_script '}' + { script_pop_lex_mode(closure); } | file_or_sections_cmd | ignore_cmd ; @@ -412,8 +423,84 @@ defsym_expr: { script_set_symbol(closure, $1.value, $1.length, $3, 0, 0); } ; -/* A version script. Not yet implemented. */ +/* A version script. */ version_script: + vers_nodes + ; + +vers_nodes: + vers_node + | vers_nodes vers_node + ; + +vers_node: + '{' vers_tag '}' ';' + { + script_register_vers_node (closure, NULL, 0, $2, NULL); + } + | STRING '{' vers_tag '}' ';' + { + script_register_vers_node (closure, $1.value, $1.length, $3, + NULL); + } + | STRING '{' vers_tag '}' verdep ';' + { + script_register_vers_node (closure, $1.value, $1.length, $3, $5); + } + ; + +verdep: + STRING + { + $$ = script_add_vers_depend (closure, NULL, $1.value, $1.length); + } + | verdep STRING + { + $$ = script_add_vers_depend (closure, $1, $2.value, $2.length); + } + ; + +vers_tag: + /* empty */ + { $$ = script_new_vers_node (closure, NULL, NULL); } + | vers_defns ';' + { $$ = script_new_vers_node (closure, $1, NULL); } + | GLOBAL ':' vers_defns ';' + { $$ = script_new_vers_node (closure, $3, NULL); } + | LOCAL ':' vers_defns ';' + { $$ = script_new_vers_node (closure, NULL, $3); } + | GLOBAL ':' vers_defns ';' LOCAL ':' vers_defns ';' + { $$ = script_new_vers_node (closure, $3, $7); } + ; + +vers_defns: + STRING + { + $$ = script_new_vers_pattern (closure, NULL, $1.value, + $1.length); + } + | vers_defns ';' STRING + { + $$ = script_new_vers_pattern (closure, $1, $3.value, $3.length); + } + | /* Push STRING on the language stack. */ + EXTERN STRING '{' + { version_script_push_lang(closure, $2.value, $2.length); } + vers_defns opt_semicolon '}' + { + $$ = $5; + version_script_pop_lang(closure); + } + | EXTERN // "extern" as a symbol name + { + $$ = script_new_vers_pattern (closure, NULL, "extern", + sizeof("extern") - 1); + } + | vers_defns ';' EXTERN + { + $$ = script_new_vers_pattern (closure, $1, "extern", + sizeof("extern") - 1); + } ; /* Some statements require a terminator, which may be a semicolon or a @@ -423,6 +510,12 @@ end: | ',' ; +/* An optional semicolon. */ +opt_semicolon: + ';' + | /* empty */ + ; + /* An optional comma. */ opt_comma: ',' -- cgit v1.1