This is the mail archive of the binutils@sourceware.org mailing list for the binutils project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Add plugin interface to LD [1/4] Infrastructure.


  This is the first patch in the series: it adds the infrastructure for
parsing plugin-related command line options, and for loading, initialising,
and cleaning up the plugins.  Notes, in no particular order:

- I added a --enable-plugins command to ld's configure before I remembered
that there's one in the binutils/ subdir (for nm and ar) as well.  Should I
perhaps change it to --enable-ld-plugins (or something else) so that it can be
controlled separately?  Should I rewrite it to use config/plugins.m4?

- Is it OK to put an unguarded "#include <stdarg.h>" in ldmisc.h?

- There are a couple of ever-so-slightly long lines and not enough comments;
both of these points get fixed in the later patches.  I've split them up for
ease of review but would like to apply them either in one, or in rapid
succession, so that it'll be corrected immediately.

    include/ChangeLog:

	* plugin-api.h (LDPT_GNU_LD_VERSION): New ld_plugin_tag enum member.

    ld/ChangeLog:

	* configure.in: Add --enable-plugins option and additional AC_CHECKs
	for required headers and functions.
	(ENABLE_PLUGINS): Add related automake conditional.
	* configure: Regenerate.
	* config.in: Likewise.
	* Makefile.am (PLUGIN_C): Declare plugin C source file, conditional
	on ENABLE_PLUGINS being defined.
	(PLUGIN_H): Likewise for header file.
	(PLUGIN_OBJEXT): Likewise for object file.
	(PLUGIN_CFLAGS): Likewise -D flag required to compile plugin support.
	(AM_CFLAGS): Use PLUGIN_CFLAGS.
	(CFILES): Use PLUGIN_C.
	(HFILES): Use PLUGIN_H.
	(OFILES): Use PLUGIN_OBJEXT.
	(ld_new_SOURCES): Use PLUGIN_C.
	(noinst_LTLIBRARIES)[ENABLE_PLUGINS]: Declare test plugin.
	(libldtestplug_la_SOURCES)[ENABLE_PLUGINS]: Add automake definition
	for test plugin.
	(libldtestplug_la_CFLAGS)[ENABLE_PLUGINS]: Likewise.
	(libldtestplug_la_LDFLAGS)[ENABLE_PLUGINS]: Likewise.
	* Makefile.in: Regenerate.
	* sysdep.h: Include unistd.h and one of fcntl.h or sys/file.h where
	available.
	(O_RDONLY): Supply default definition likewise to bfd's sysdep.h
	(O_WRONLY): Likewise.
	(O_RDWR): Likewise.
	(O_ACCMODE): Likewise.
	(SEEK_SET): Likewise.
	(SEEK_CUR): Likewise.
	(SEEK_END): Likewise.
	* ldmisc.c (vfinfo): Make non-static.
	* ldmisc.h: Include stdarg.h for va_list type.
	(vfinfo): Declare extern prototype.
	* lexsup.c (enum option_values)[ENABLE_PLUGINS]: Add new entries for
	OPTION_PLUGIN and OPTION_PLUGIN_ARG.
	(ld_options[])[ENABLE_PLUGINS]: Add option data for the above two.
	(parse_args)[ENABLE_PLUGINS]: Handle them, and load all plugins once
	option parsing is complete.
	* ldmain.c (main)[ENABLE_PLUGINS]: Call plugin cleanup hooks just
	after lang_finish.
	* plugin.c: New source file.
	* plugin.h: Likewise new header.
	* testplug.c: New source file.

    ld/testsuite/ChangeLog:

	* lib/ld-lib.exp (proc regexp_diff): Extend verbose debug output.
	(proc set_file_contents): Write a file with the supplied content.
	(run_ld_link_tests): Add new 'ld' action to test linker output.
	(proc check_plugin_api_available): Return true if linker under test
	supports the plugin API.
	* ld-plugin/func.c: New test source file.
	* ld-plugin/main.c: Likewise.
	* ld-plugin/text.c: Likewise.
	* ld-plugin/plugin-1.d: New dump test output pattern script.
	* ld-plugin/plugin-2.d: Likewise.
	* ld-plugin/plugin-3.d: Likewise.
	* ld-plugin/plugin-4.d: Likewise.
	* ld-plugin/plugin-5.d: Likewise.
	* ld-plugin/plugin.exp: New test control script.

    cheers,
      DaveK
>From 4f6d92e075279cca74abd64cc00878197127d6e9 Mon Sep 17 00:00:00 2001
From: Dave Korn <dave.korn.cygwin@gmail.com>
Date: Fri, 10 Sep 2010 11:02:24 +0100
Subject: [PATCH] Add infrastructure for plugin API; functionality to follow.

    include/ChangeLog:

	* plugin-api.h (LDPT_GNU_LD_VERSION): New ld_plugin_tag enum member.

    ld/ChangeLog:

	* configure.in: Add --enable-plugins option and additional AC_CHECKs
	for required headers and functions.
	(ENABLE_PLUGINS): Add related automake conditional.
	* configure: Regenerate.
	* config.in: Likewise.
	* Makefile.am (PLUGIN_C): Declare plugin C source file, conditional
	on ENABLE_PLUGINS being defined.
	(PLUGIN_H): Likewise for header file.
	(PLUGIN_OBJEXT): Likewise for object file.
	(PLUGIN_CFLAGS): Likewise -D flag required to compile plugin support.
	(AM_CFLAGS): Use PLUGIN_CFLAGS.
	(CFILES): Use PLUGIN_C.
	(HFILES): Use PLUGIN_H.
	(OFILES): Use PLUGIN_OBJEXT.
	(ld_new_SOURCES): Use PLUGIN_C.
	(noinst_LTLIBRARIES)[ENABLE_PLUGINS]: Declare test plugin.
	(libldtestplug_la_SOURCES)[ENABLE_PLUGINS]: Add automake definition
	for test plugin.
	(libldtestplug_la_CFLAGS)[ENABLE_PLUGINS]: Likewise.
	(libldtestplug_la_LDFLAGS)[ENABLE_PLUGINS]: Likewise.
	* Makefile.in: Regenerate.
	* sysdep.h: Include unistd.h and one of fcntl.h or sys/file.h where
	available.
	(O_RDONLY): Supply default definition likewise to bfd's sysdep.h
	(O_WRONLY): Likewise.
	(O_RDWR): Likewise.
	(O_ACCMODE): Likewise.
	(SEEK_SET): Likewise.
	(SEEK_CUR): Likewise.
	(SEEK_END): Likewise.
	* ldmisc.c (vfinfo): Make non-static.
	* ldmisc.h: Include stdarg.h for va_list type.
	(vfinfo): Declare extern prototype.
	* lexsup.c (enum option_values)[ENABLE_PLUGINS]: Add new entries for
	OPTION_PLUGIN and OPTION_PLUGIN_ARG.
	(ld_options[])[ENABLE_PLUGINS]: Add option data for the above two.
	(parse_args)[ENABLE_PLUGINS]: Handle them, and load all plugins once
	option parsing is complete.
	* ldmain.c (main)[ENABLE_PLUGINS]: Call plugin cleanup hooks just
	after lang_finish.
	* plugin.c: New source file.
	* plugin.h: Likewise new header.
	* testplug.c: New source file.

    ld/testsuite/ChangeLog:

	* lib/ld-lib.exp (proc regexp_diff): Extend verbose debug output.
	(proc set_file_contents): Write a file with the supplied content.
	(run_ld_link_tests): Add new 'ld' action to test linker output.
	(proc check_plugin_api_available): Return true if linker under test
	supports the plugin API.
	* ld-plugin/func.c: New test source file.
	* ld-plugin/main.c: Likewise.
	* ld-plugin/text.c: Likewise.
	* ld-plugin/plugin-1.d: New dump test output pattern script.
	* ld-plugin/plugin-2.d: Likewise.
	* ld-plugin/plugin-3.d: Likewise.
	* ld-plugin/plugin-4.d: Likewise.
	* ld-plugin/plugin-5.d: Likewise.
	* ld-plugin/plugin.exp: New test control script.
---
 include/plugin-api.h              |    3 +-
 ld/Makefile.am                    |   36 +++-
 ld/Makefile.in                    |  149 ++++++++----
 ld/config.in                      |   18 ++
 ld/configure                      |   59 ++++-
 ld/configure.in                   |   12 +
 ld/ldmain.c                       |    9 +
 ld/ldmisc.c                       |    2 +-
 ld/ldmisc.h                       |    3 +
 ld/lexsup.c                       |   31 +++-
 ld/plugin.c                       |  507 +++++++++++++++++++++++++++++++++++++
 ld/plugin.h                       |   46 ++++
 ld/sysdep.h                       |   35 +++
 ld/testplug.c                     |  344 +++++++++++++++++++++++++
 ld/testsuite/ld-plugin/func.c     |    7 +
 ld/testsuite/ld-plugin/main.c     |   13 +
 ld/testsuite/ld-plugin/plugin-1.d |   18 ++
 ld/testsuite/ld-plugin/plugin-2.d |   21 ++
 ld/testsuite/ld-plugin/plugin-3.d |   23 ++
 ld/testsuite/ld-plugin/plugin-4.d |   23 ++
 ld/testsuite/ld-plugin/plugin-5.d |   31 +++
 ld/testsuite/ld-plugin/plugin.exp |   76 ++++++
 ld/testsuite/ld-plugin/text.c     |    3 +
 ld/testsuite/lib/ld-lib.exp       |   52 ++++-
 24 files changed, 1461 insertions(+), 60 deletions(-)

diff --git a/include/plugin-api.h b/include/plugin-api.h
index a0cf5f4..9d58fef 100644
--- a/include/plugin-api.h
+++ b/include/plugin-api.h
@@ -268,7 +268,8 @@ enum ld_plugin_tag
   LDPT_RELEASE_INPUT_FILE,
   LDPT_ADD_INPUT_LIBRARY,
   LDPT_OUTPUT_NAME,
-  LDPT_SET_EXTRA_LIBRARY_PATH
+  LDPT_SET_EXTRA_LIBRARY_PATH,
+  LDPT_GNU_LD_VERSION
 };
 
 /* The plugin transfer vector.  */
diff --git a/ld/Makefile.am b/ld/Makefile.am
index 9eaf0e2..5b0c74c 100644
--- a/ld/Makefile.am
+++ b/ld/Makefile.am
@@ -21,6 +21,21 @@ WARN_CFLAGS = @WARN_CFLAGS@
 NO_WERROR = @NO_WERROR@
 AM_CFLAGS = $(WARN_CFLAGS)
 
+# Conditionally enable the plugin interface.
+if ENABLE_PLUGINS
+PLUGIN_C = plugin.c
+PLUGIN_H = plugin.h
+PLUGIN_OBJEXT = plugin.@OBJEXT@
+PLUGIN_CFLAGS = -DENABLE_PLUGINS
+else
+PLUGIN_C =
+PLUGIN_H =
+PLUGIN_OBJEXT =
+PLUGIN_CFLAGS =
+endif
+
+AM_CFLAGS += $(PLUGIN_CFLAGS)
+
 # We put the scripts in the directory $(scriptdir)/ldscripts.
 # We can't put the scripts in $(datadir) because the SEARCH_DIR
 # directives need to be different for native and cross linkers.
@@ -455,11 +470,13 @@ ALL_EMUL_EXTRA_OFILES = \
 
 CFILES = ldctor.c ldemul.c ldexp.c ldfile.c ldlang.c \
 	ldmain.c ldmisc.c ldver.c ldwrite.c lexsup.c \
-	mri.c ldcref.c pe-dll.c pep-dll.c ldlex-wrapper.c
+	mri.c ldcref.c pe-dll.c pep-dll.c ldlex-wrapper.c \
+	$(PLUGIN_C)
 
 HFILES = ld.h ldctor.h ldemul.h ldexp.h ldfile.h \
 	ldlang.h ldlex.h ldmain.h ldmisc.h ldver.h \
-	ldwrite.h mri.h deffile.h pe-dll.h pep-dll.h elf-hints-local.h
+	ldwrite.h mri.h deffile.h pe-dll.h pep-dll.h \
+	elf-hints-local.h $(PLUGIN_H)
 
 GENERATED_CFILES = ldgram.c ldlex.c deffilep.c
 GENERATED_HFILES = ldgram.h ldemul-list.h deffilep.h
@@ -468,7 +485,8 @@ GENERATED_HFILES = ldgram.h ldemul-list.h deffilep.h
 # tracking will not cause them to be built beforehand.
 BUILT_SOURCES = $(GENERATED_HFILES)
 
-OFILES = ldgram.@OBJEXT@ ldlex-wrapper.@OBJEXT@ lexsup.@OBJEXT@ ldlang.@OBJEXT@ mri.@OBJEXT@ ldctor.@OBJEXT@ ldmain.@OBJEXT@ \
+OFILES = ldgram.@OBJEXT@ ldlex-wrapper.@OBJEXT@ lexsup.@OBJEXT@ ldlang.@OBJEXT@ \
+	mri.@OBJEXT@ ldctor.@OBJEXT@ ldmain.@OBJEXT@ $(PLUGIN_OBJEXT) \
 	ldwrite.@OBJEXT@ ldexp.@OBJEXT@  ldemul.@OBJEXT@ ldver.@OBJEXT@ ldmisc.@OBJEXT@ \
 	ldfile.@OBJEXT@ ldcref.@OBJEXT@ ${EMULATION_OFILES} ${EMUL_EXTRA_OFILES}
 
@@ -1882,7 +1900,7 @@ EXTRA_ld_new_SOURCES = deffilep.y ldlex.l
 EXTRA_ld_new_SOURCES += pep-dll.c pe-dll.c
 
 ld_new_SOURCES = ldgram.y ldlex-wrapper.c lexsup.c ldlang.c mri.c ldctor.c ldmain.c \
-	ldwrite.c ldexp.c ldemul.c ldver.c ldmisc.c ldfile.c ldcref.c
+	ldwrite.c ldexp.c ldemul.c ldver.c ldmisc.c ldfile.c ldcref.c $(PLUGIN_C)
 ld_new_DEPENDENCIES = $(EMULATION_OFILES) $(EMUL_EXTRA_OFILES) $(BFDLIB) $(LIBIBERTY) $(LIBINTL_DEP)
 ld_new_LDADD = $(EMULATION_OFILES) $(EMUL_EXTRA_OFILES) $(BFDLIB) $(LIBIBERTY) $(LIBINTL)
 
@@ -1964,6 +1982,16 @@ bootstrap: ld3$(EXEEXT)
 
 # END OF CHECK TARGETS
 
+# 
+# Build a dummy plugin using libtool.
+#
+if ENABLE_PLUGINS
+noinst_LTLIBRARIES = libldtestplug.la
+libldtestplug_la_SOURCES = testplug.c
+libldtestplug_la_CFLAGS= -g -O2
+libldtestplug_la_LDFLAGS = -no-undefined -rpath /nowhere
+endif
+
 # DOCUMENTATION TARGETS
 # Manual configuration file; not usually attached to normal configuration,
 # because almost all configs use "gen" version of manual.
diff --git a/ld/configure.in b/ld/configure.in
index 90baeca..cfc312b 100644
--- a/ld/configure.in
+++ b/ld/configure.in
@@ -117,6 +117,16 @@ case "${got_handling}" in
   *)  AC_MSG_ERROR(bad value ${got_handling} for --enable-got option) ;;
 esac
 
+AC_ARG_ENABLE(plugins,
+[[  --enable-plugins        enable plugin interface]],
+[case "${enableval}" in
+  yes | "")  enable_plugins=true  ;;
+  no)   enable_plugins=false ;;
+  *)    AC_MSG_ERROR(bad value ${enableval} for --enable-plugins option) ;;
+esac],
+[enable_plugins=false])dnl
+AM_CONDITIONAL([ENABLE_PLUGINS], [test x$enable_plugins = xtrue])
+
 AM_BINUTILS_WARNINGS
 
 AC_CONFIG_HEADERS([config.h:config.in])
@@ -159,7 +169,9 @@ AC_SUBST(HOSTING_LIBS)
 AC_SUBST(NATIVE_LIB_DIRS)
 
 AC_CHECK_HEADERS(string.h strings.h stdlib.h unistd.h elf-hints.h limits.h sys/param.h)
+AC_CHECK_HEADERS(fcntl.h sys/file.h sys/time.h sys/stat.h)
 AC_CHECK_FUNCS(glob mkstemp realpath sbrk waitpid)
+AC_CHECK_FUNCS(open lseek close)
 AC_HEADER_DIRENT
 
 AC_MSG_CHECKING(for a known getopt prototype in unistd.h)
diff --git a/ld/ldmain.c b/ld/ldmain.c
index 08679ec..85236ed 100644
--- a/ld/ldmain.c
+++ b/ld/ldmain.c
@@ -40,6 +40,9 @@
 #include "ldfile.h"
 #include "ldemul.h"
 #include "ldctor.h"
+#ifdef ENABLE_PLUGINS
+#include "plugin.h"
+#endif /* ENABLE_PLUGINS */
 
 /* Somewhere above, sys/stat.h got included.  */
 #if !defined(S_ISDIR) && defined(S_IFDIR)
@@ -474,6 +477,12 @@ main (int argc, char **argv)
 
   lang_finish ();
 
+#ifdef ENABLE_PLUGINS
+  /* Now everything is finished, we can tell the plugins to clean up.  */
+  if (plugin_call_cleanup ())
+    info_msg (_("%P: %s: error in plugin cleanup (ignored)\n"), plugin_error_plugin ());
+#endif /* ENABLE_PLUGINS */
+
   /* Even if we're producing relocatable output, some non-fatal errors should
      be reported in the exit status.  (What non-fatal errors, if any, do we
      want to ignore for relocatable output?)  */
diff --git a/ld/ldmisc.c b/ld/ldmisc.c
index d49cf17..31c46a9 100644
--- a/ld/ldmisc.c
+++ b/ld/ldmisc.c
@@ -62,7 +62,7 @@
  %v hex bfd_vma, no leading zeros
 */
 
-static void
+void
 vfinfo (FILE *fp, const char *fmt, va_list arg, bfd_boolean is_warning)
 {
   bfd_boolean fatal = FALSE;
diff --git a/ld/ldmisc.h b/ld/ldmisc.h
index ba7f0c6..b29ce5a 100644
--- a/ld/ldmisc.h
+++ b/ld/ldmisc.h
@@ -22,6 +22,9 @@
 #ifndef LDMISC_H
 #define LDMISC_H
 
+#include <stdarg.h>
+
+extern void vfinfo (FILE *fp, const char *fmt, va_list arg, bfd_boolean is_warning);
 extern void einfo (const char *, ...);
 extern void minfo (const char *, ...);
 extern void info_msg (const char *, ...);
diff --git a/ld/lexsup.c b/ld/lexsup.c
index b992fca..b9dd3e8 100644
--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -40,6 +40,9 @@
 #include "ldver.h"
 #include "ldemul.h"
 #include "demangle.h"
+#ifdef ENABLE_PLUGINS
+#include "plugin.h"
+#endif /* ENABLE_PLUGINS */
 
 #ifndef PATH_SEPARATOR
 #if defined (__MSDOS__) || (defined (_WIN32) && ! defined (__CYGWIN32__))
@@ -167,7 +170,11 @@ enum option_values
   OPTION_WARN_SHARED_TEXTREL,
   OPTION_WARN_ALTERNATE_EM,
   OPTION_REDUCE_MEMORY_OVERHEADS,
-  OPTION_DEFAULT_SCRIPT
+#ifdef ENABLE_PLUGINS
+  OPTION_PLUGIN,
+  OPTION_PLUGIN_ARG,
+#endif /* ENABLE_PLUGINS */
+  OPTION_DEFAULT_SCRIPT,
 };
 
 /* The long options.  This structure is used for both the option
@@ -271,6 +278,12 @@ static const struct ld_option ld_options[] =
     'o', N_("FILE"), N_("Set output file name"), EXACTLY_TWO_DASHES },
   { {NULL, required_argument, NULL, '\0'},
     'O', NULL, N_("Optimize output file"), ONE_DASH },
+#ifdef ENABLE_PLUGINS
+  { {"plugin", required_argument, NULL, OPTION_PLUGIN},
+    '\0', N_("PLUGIN"), N_("Load named plugin"), ONE_DASH },
+  { {"plugin-arg", required_argument, NULL, OPTION_PLUGIN_ARG},
+    '\0', N_("ARG"), N_("Send arg to last-loaded plugin"), ONE_DASH },
+#endif /* ENABLE_PLUGINS */
   { {"Qy", no_argument, NULL, OPTION_IGNORE},
     '\0', NULL, N_("Ignored for SVR4 compatibility"), ONE_DASH },
   { {"emit-relocs", no_argument, NULL, 'q'},
@@ -1040,6 +1053,16 @@ parse_args (unsigned argc, char **argv)
 	case OPTION_OFORMAT:
 	  lang_add_output_format (optarg, NULL, NULL, 0);
 	  break;
+#ifdef ENABLE_PLUGINS
+	case OPTION_PLUGIN:
+	  if (plugin_opt_plugin (optarg))
+	    einfo(_("%P%F: bad -plugin option\n"));
+	  break;
+	case OPTION_PLUGIN_ARG:
+	  if (plugin_opt_plugin_arg (optarg))
+	    einfo(_("%P%F: bad -plugin-arg option\n"));
+	  break;
+#endif /* ENABLE_PLUGINS */
 	case 'q':
 	  link_info.emitrelocations = TRUE;
 	  break;
@@ -1517,6 +1540,12 @@ parse_args (unsigned argc, char **argv)
   if (link_info.unresolved_syms_in_shared_libs == RM_NOT_YET_SET)
     /* FIXME: Should we allow emulations a chance to set this ?  */
     link_info.unresolved_syms_in_shared_libs = how_to_report_unresolved_symbols;
+
+#ifdef ENABLE_PLUGINS
+  /* Now all the plugin arguments have been gathered, we can load them.  */
+  if (plugin_load_plugins ())
+    einfo (_("%P%F: %s: error loading plugin\n"), plugin_error_plugin ());
+#endif /* ENABLE_PLUGINS */
 }
 
 /* Add the (colon-separated) elements of DIRLIST_PTR to the
diff --git a/ld/plugin.c b/ld/plugin.c
new file mode 100644
index 0000000..e9f3b81
--- /dev/null
+++ b/ld/plugin.c
@@ -0,0 +1,507 @@
+/* Plugin control for the GNU linker.
+   Copyright 2010 Free Software Foundation, Inc.
+
+   This file is part of the GNU Binutils.
+
+   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.  */
+
+#include <dlfcn.h>
+
+#include "sysdep.h"
+#include "libiberty.h"
+#include "bfd.h"
+#include "bfdlink.h"
+#include "bfdver.h"
+#include "ld.h"
+#include "ldmain.h"
+#include "ldmisc.h"
+#include "plugin.h"
+#include "plugin-api.h"
+
+/* Alias to shorten static function prototype lines.  */
+#define PLUGAPIFUNC static enum ld_plugin_status
+
+PLUGAPIFUNC message (int level, const char *format, ...);
+PLUGAPIFUNC register_claim_file (ld_plugin_claim_file_handler handler);
+PLUGAPIFUNC register_all_symbols_read (
+		ld_plugin_all_symbols_read_handler handler);
+PLUGAPIFUNC register_cleanup (ld_plugin_cleanup_handler handler);
+PLUGAPIFUNC add_symbols (void *handle, int nsyms,
+		const struct ld_plugin_symbol *syms);
+PLUGAPIFUNC get_input_file (const void *handle,
+		struct ld_plugin_input_file *file);
+PLUGAPIFUNC release_input_file (const void *handle);
+PLUGAPIFUNC get_symbols (const void *handle, int nsyms,
+		struct ld_plugin_symbol *syms);
+PLUGAPIFUNC add_input_file (const char *pathname);
+PLUGAPIFUNC add_input_library (const char *pathname);
+PLUGAPIFUNC set_extra_library_path (const char *path);
+
+/* Always use this macro when invoking a plugin function.  */
+#define INVOKE_PLUGIN_FN(plugin, retval, fn, args)	\
+	called_plugin = plugin;				\
+	retval = (*fn) args;				\
+	called_plugin = NULL;
+
+/* Stores a single argument passed to a plugin.  */
+typedef struct plugin_arg
+{
+  struct plugin_arg *next;
+  const char *arg;
+} plugin_arg_t;
+
+/* Holds all details of a single plugin.  */
+typedef struct plugin
+{
+  /* Next on the list of plugins, or NULL at end of chain.  */
+  struct plugin *next;
+  /* The argument string given to --plugin.  */
+  const char *name;
+  /* The shared library handle returned by dlopen.  */
+  void *dlhandle;
+  /* The list of argument string given to --plugin-opt.  */
+  plugin_arg_t *args;
+  /* Number of args in the list, for convenience.  */
+  size_t n_args;
+  /* The plugin's event handlers.  */
+  ld_plugin_claim_file_handler claim_file_handler;
+  ld_plugin_all_symbols_read_handler all_symbols_read_handler;
+  ld_plugin_cleanup_handler cleanup_handler;
+  /* TRUE if the cleanup handlers have been called.  */
+  bfd_boolean cleanup_done;
+} plugin_t;
+
+/* The master list of all plugins.  */
+static plugin_t *plugins_list = NULL;
+
+/* We keep a tail pointer for easy linking on the end.  */
+static plugin_t **plugins_tail_chain_ptr = &plugins_list;
+
+/* The last plugin added to the list, for receiving args.  */
+static plugin_t *last_plugin = NULL;
+
+/* The tail of the arg chain of the last plugin added to the list.  */
+static plugin_arg_t **last_plugin_args_tail_chain_ptr = NULL;
+
+/* The plugin which is currently having a callback executed.  */
+static plugin_t *called_plugin = NULL;
+
+/* Last plugin to cause an error, if any.  */
+static const char *error_plugin = NULL;
+
+/* Helper to size leading part of tv array and set it up. */
+static size_t
+set_tv_header (struct ld_plugin_tv *tv)
+{
+  size_t i;
+  /* List of tags to set in the constant leading part of the tv array. */
+  static const enum ld_plugin_tag tv_header_tags[] =
+  {
+    LDPT_MESSAGE,
+    LDPT_API_VERSION,
+    LDPT_GNU_LD_VERSION,
+    LDPT_LINKER_OUTPUT,
+    LDPT_OUTPUT_NAME,
+    LDPT_REGISTER_CLAIM_FILE_HOOK,
+    LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
+    LDPT_REGISTER_CLEANUP_HOOK,
+    LDPT_ADD_SYMBOLS,
+    LDPT_GET_INPUT_FILE,
+    LDPT_RELEASE_INPUT_FILE,
+    LDPT_GET_SYMBOLS,
+    LDPT_ADD_INPUT_FILE,
+    LDPT_ADD_INPUT_LIBRARY,
+    LDPT_SET_EXTRA_LIBRARY_PATH
+  };
+  /* How many entries.  */
+  static const size_t tv_header_size = ARRAY_SIZE (tv_header_tags);
+
+  /* Version info.  */
+  static const unsigned int major = (unsigned)(BFD_VERSION / 100000000UL);
+  static const unsigned int minor = (unsigned)(BFD_VERSION / 1000000UL) % 100;
+
+  if (!tv)
+    return tv_header_size;
+
+  for (i = 0; i < tv_header_size; i++)
+    {
+      tv[i].tv_tag = tv_header_tags[i];
+#define TVU(x) tv[i].tv_u.tv_ ## x
+      switch (tv[i].tv_tag)
+	{
+	  case LDPT_MESSAGE:
+	    TVU(message) = message;
+	    break;
+	  case LDPT_API_VERSION:
+	    TVU(val) = LD_PLUGIN_API_VERSION;
+	    break;
+	  case LDPT_GNU_LD_VERSION:
+	    TVU(val) = major * 100 + minor;
+	    break;
+	  case LDPT_LINKER_OUTPUT:
+	    TVU(val) = link_info.relocatable ? LDPO_REL
+			: (link_info.shared ? LDPO_DYN : LDPO_EXEC);
+	    break;
+	  case LDPT_OUTPUT_NAME:
+	    TVU(string) = output_filename;
+	    break;
+	  case LDPT_REGISTER_CLAIM_FILE_HOOK:
+	    TVU(register_claim_file) = register_claim_file;
+	    break;
+	  case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
+	    TVU(register_all_symbols_read) = register_all_symbols_read;
+	    break;
+	  case LDPT_REGISTER_CLEANUP_HOOK:
+	    TVU(register_cleanup) = register_cleanup;
+	    break;
+	  case LDPT_ADD_SYMBOLS:
+	    TVU(add_symbols) = add_symbols;
+	    break;
+	  case LDPT_GET_INPUT_FILE:
+	    TVU(get_input_file) = get_input_file;
+	    break;
+	  case LDPT_RELEASE_INPUT_FILE:
+	    TVU(release_input_file) = release_input_file;
+	    break;
+	  case LDPT_GET_SYMBOLS:
+	    TVU(get_symbols) = get_symbols;
+	    break;
+	  case LDPT_ADD_INPUT_FILE:
+	    TVU(add_input_file) = add_input_file;
+	    break;
+	  case LDPT_ADD_INPUT_LIBRARY:
+	    TVU(add_input_library) = add_input_library;
+	    break;
+	  case LDPT_SET_EXTRA_LIBRARY_PATH:
+	    TVU(set_extra_library_path) = set_extra_library_path;
+	    break;
+	  default:
+	    /* Added a new entry to the array without adding
+	       a new case to set up its value is a bug.  */
+	    FAIL ();
+	}
+#undef TVU
+    }
+  return tv_header_size;
+}
+
+/* Append the per-plugin args list and trailing LDPT_NULL to tv.  */
+static void
+set_tv_plugin_args (plugin_t *plugin, struct ld_plugin_tv *tv)
+{
+  plugin_arg_t *arg = plugin->args;
+  while (arg)
+    {
+      tv->tv_tag = LDPT_OPTION;
+      tv->tv_u.tv_string = arg->arg;
+      arg = arg->next;
+      tv++;
+    }
+  tv->tv_tag = LDPT_NULL;
+  tv->tv_u.tv_val = 0;
+}
+
+/* Helper function for exiting with error status.  */
+static int
+set_plugin_error (const char *plugin)
+{
+  error_plugin = plugin;
+  return -1;
+}
+
+/* Test if an error occurred.  */
+static bfd_boolean
+plugin_error_p (void)
+{
+  return error_plugin != NULL;
+}
+
+/* Return name of plugin which caused an error if any.  */
+const char *plugin_error_plugin (void)
+{
+  return error_plugin ? error_plugin : _("<no plugin>");
+}
+
+/* Handle -plugin arg: find and load plugin, or return error.  */
+int plugin_opt_plugin (const char *plugin)
+{
+  plugin_t *newplug;
+
+  newplug = xmalloc (sizeof *newplug);
+  memset (newplug, 0, sizeof *newplug);
+  newplug->name = plugin;
+  newplug->dlhandle = dlopen (plugin, RTLD_NOW);
+  if (!newplug->dlhandle)
+    return set_plugin_error (plugin);
+
+  /* Chain on end, so when we run list it is in command-line order.  */
+  *plugins_tail_chain_ptr = newplug;
+  plugins_tail_chain_ptr = &newplug->next;
+
+  /* Record it as current plugin for receiving args.  */
+  last_plugin = newplug;
+  last_plugin_args_tail_chain_ptr = &newplug->args;
+  return 0;
+}
+
+/* Accumulate option arguments for last-loaded plugin, or return
+   error if none.  */
+int plugin_opt_plugin_arg (const char *arg)
+{
+  plugin_arg_t *newarg;
+
+  if (!last_plugin)
+    return set_plugin_error (_("<no plugin>"));
+
+  newarg = xmalloc (sizeof *newarg);
+  newarg->arg = arg;
+  newarg->next = NULL;
+
+  /* Chain on end to preserve command-line order.  */
+  *last_plugin_args_tail_chain_ptr = newarg;
+  last_plugin_args_tail_chain_ptr = &newarg->next;
+  last_plugin->n_args++;
+  return 0;
+}
+
+/* Load up and initialise all plugins after argument parsing.  */
+int plugin_load_plugins (void)
+{
+  struct ld_plugin_tv *my_tv;
+  unsigned int max_args = 0;
+  plugin_t *curplug = plugins_list;
+
+  /* First pass over plugins to find max # args needed so that we
+     can size and allocate the tv array.  */
+  while (curplug)
+    {
+      if (curplug->n_args > max_args)
+	max_args = curplug->n_args;
+      curplug = curplug->next;
+    }
+  /* Allocate tv array and initialise constant part.  */
+  my_tv = xmalloc ((max_args + 1 + set_tv_header (NULL)) * sizeof *my_tv);
+  set_tv_header (my_tv);
+
+  curplug = plugins_list;
+  while (curplug)
+    {
+      enum ld_plugin_status rv;
+      ld_plugin_onload onloadfn = dlsym (curplug->dlhandle, "onload");
+      if (!onloadfn)
+	onloadfn = dlsym (curplug->dlhandle, "_onload");
+      if (!onloadfn)
+        return set_plugin_error (curplug->name);
+      set_tv_plugin_args (curplug, &my_tv[set_tv_header (NULL)]);
+      INVOKE_PLUGIN_FN (curplug, rv, onloadfn, (my_tv));
+      if (rv != LDPS_OK)
+        return set_plugin_error (curplug->name);
+      curplug = curplug->next;
+    }
+  return 0;
+}
+
+/* Call 'claim file' hook for all plugins.  */
+int
+plugin_call_claim_file (const struct ld_plugin_input_file *file, int *claimed)
+{
+  plugin_t *curplug = plugins_list;
+  *claimed = FALSE;
+  while (curplug && !*claimed)
+    {
+      if (curplug->claim_file_handler)
+	{
+	  enum ld_plugin_status rv;
+	  INVOKE_PLUGIN_FN (curplug, rv, curplug->claim_file_handler, \
+				(file, claimed));
+	  if (rv != LDPS_OK)
+	    set_plugin_error (curplug->name);
+	}
+      curplug = curplug->next;
+    }
+  return plugin_error_p () ? -1 : 0;
+}
+
+/* Call 'all symbols read' hook for all plugins.  */
+int
+plugin_call_all_symbols_read (void)
+{
+  plugin_t *curplug = plugins_list;
+  while (curplug)
+    {
+      if (curplug->all_symbols_read_handler)
+	{
+	  enum ld_plugin_status rv;
+	  INVOKE_PLUGIN_FN (curplug, rv, curplug->all_symbols_read_handler, \
+				());
+	  if (rv != LDPS_OK)
+	    set_plugin_error (curplug->name);
+	}
+      curplug = curplug->next;
+    }
+  return plugin_error_p () ? -1 : 0;
+}
+
+/* Call 'cleanup' hook for all plugins.  */
+int
+plugin_call_cleanup (void)
+{
+  plugin_t *curplug = plugins_list;
+  while (curplug)
+    {
+      if (curplug->cleanup_handler && !curplug->cleanup_done)
+	{
+	  enum ld_plugin_status rv;
+	  curplug->cleanup_done = TRUE;
+	  INVOKE_PLUGIN_FN (curplug, rv, curplug->cleanup_handler, ());
+	  if (rv != LDPS_OK)
+	    set_plugin_error (curplug->name);
+	  dlclose (curplug->dlhandle);
+	}
+      curplug = curplug->next;
+    }
+  return plugin_error_p () ? -1 : 0;
+}
+
+/* Register a claim-file handler.  */
+static enum ld_plugin_status
+register_claim_file (ld_plugin_claim_file_handler handler)
+{
+  ASSERT (called_plugin);
+  called_plugin->claim_file_handler = handler;
+  return LDPS_OK;
+}
+
+/* Register an all-symbols-read handler.  */
+static enum ld_plugin_status
+register_all_symbols_read (ld_plugin_all_symbols_read_handler handler)
+{
+  ASSERT (called_plugin);
+  called_plugin->all_symbols_read_handler = handler;
+  return LDPS_OK;
+}
+
+/* Register a cleanup handler.  */
+static enum ld_plugin_status
+register_cleanup (ld_plugin_cleanup_handler handler)
+{
+  ASSERT (called_plugin);
+  called_plugin->cleanup_handler = handler;
+  return LDPS_OK;
+}
+
+/* Add symbols from a plugin-claimed input file.  */
+static enum ld_plugin_status
+add_symbols (void *handle, int nsyms, const struct ld_plugin_symbol *syms)
+{
+  ASSERT (called_plugin);
+  handle = handle;
+  nsyms = nsyms;
+  syms = syms;
+  return LDPS_ERR;
+}
+
+/* Get the input file information with an open (possibly re-opened)
+   file descriptor.  */
+static enum ld_plugin_status
+get_input_file (const void *handle, struct ld_plugin_input_file *file)
+{
+  ASSERT (called_plugin);
+  handle = handle;
+  file = file;
+  return LDPS_ERR;
+}
+
+/* Release the input file.  */
+static enum ld_plugin_status
+release_input_file (const void *handle)
+{
+  ASSERT (called_plugin);
+  handle = handle;
+  return LDPS_ERR;
+}
+
+/* Get the symbol resolution info for a plugin-claimed input file.  */
+static enum ld_plugin_status
+get_symbols (const void *handle, int nsyms, struct ld_plugin_symbol *syms)
+{
+  ASSERT (called_plugin);
+  handle = handle;
+  nsyms = nsyms;
+  syms = syms;
+  return LDPS_ERR;
+}
+
+/* Add a new (real) input file generated by a plugin.  */
+static enum ld_plugin_status
+add_input_file (const char *pathname)
+{
+  ASSERT (called_plugin);
+  pathname = pathname;
+  return LDPS_ERR;
+}
+
+/* Add a new (real) library required by a plugin.  */
+static enum ld_plugin_status
+add_input_library (const char *pathname)
+{
+  ASSERT (called_plugin);
+  pathname = pathname;
+  return LDPS_ERR;
+}
+
+/* Set the extra library path to be used by libraries added via
+   add_input_library.  */
+static enum ld_plugin_status
+set_extra_library_path (const char *path)
+{
+  ASSERT (called_plugin);
+  path = path;
+  return LDPS_ERR;
+}
+
+/* Issue a diagnostic message from a plugin.  */
+static enum ld_plugin_status
+message (int level, const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+
+  switch (level)
+    {
+    case LDPL_INFO:
+      vfinfo (stdout, format, args, FALSE);
+      break;
+    case LDPL_WARNING:
+      vfinfo (stdout, format, args, TRUE);
+      break;
+    case LDPL_FATAL:
+    case LDPL_ERROR:
+    default:
+      {
+	char *newfmt = xmalloc (strlen (format) + 3);
+	newfmt[0] = '%';
+	newfmt[1] = (level == LDPL_FATAL) ? 'F' : 'X';
+	strcpy (&newfmt[2], format);
+	vfinfo (stderr, newfmt, args, TRUE);
+      }
+      break;
+    }
+
+  va_end (args);
+  return LDPS_OK;
+}
+
diff --git a/ld/plugin.h b/ld/plugin.h
new file mode 100644
index 0000000..fe3d148
--- /dev/null
+++ b/ld/plugin.h
@@ -0,0 +1,46 @@
+/* Plugin control for the GNU linker.
+   Copyright 2010 Free Software Foundation, Inc.
+
+   This file is part of the GNU Binutils.
+
+   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 is the only forward declaration we need to avoid having
+   to include the plugin-api.h header in order to use this file.  */
+struct ld_plugin_input_file;
+
+/* Handle -plugin arg: find and load plugin, or return error.  */
+extern int plugin_opt_plugin (const char *plugin);
+
+/* Accumulate option arguments for last-loaded plugin, or return
+   error if none.  */
+extern int plugin_opt_plugin_arg (const char *arg);
+
+/* Load up and initialise all plugins after argument parsing.  */
+extern int plugin_load_plugins (void);
+
+/* Return name of plugin which caused an error in any of the above.  */
+extern const char *plugin_error_plugin (void);
+
+/* Call 'claim file' hook for all plugins.  */
+extern int plugin_call_claim_file (const struct ld_plugin_input_file *file, int *claimed);
+
+/* Call 'all symbols read' hook for all plugins.  */
+extern int plugin_call_all_symbols_read (void);
+
+/* Call 'cleanup' hook for all plugins.  */
+extern int plugin_call_cleanup (void);
+
diff --git a/ld/sysdep.h b/ld/sysdep.h
index 97fe17d..929a5b4 100644
--- a/ld/sysdep.h
+++ b/ld/sysdep.h
@@ -71,12 +71,47 @@ extern char *strrchr ();
 # define REALPATH(a,b) NULL
 #endif
 
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
 #ifdef USE_BINARY_FOPEN
 #include "fopen-bin.h"
 #else
 #include "fopen-same.h"
 #endif
 
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#endif
+
+#ifndef O_RDONLY
+#define O_RDONLY 0
+#endif
+#ifndef O_WRONLY
+#define O_WRONLY 1
+#endif
+#ifndef O_RDWR
+#define O_RDWR 2
+#endif
+#ifndef O_ACCMODE
+#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
+#endif
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#endif
+#ifndef SEEK_CUR
+#define SEEK_CUR 1
+#endif
+#ifndef SEEK_END
+#define SEEK_END 2
+#endif
+
 #if !HAVE_DECL_STRSTR
 extern char *strstr ();
 #endif
diff --git a/ld/testplug.c b/ld/testplug.c
new file mode 100644
index 0000000..46d7c0f
--- /dev/null
+++ b/ld/testplug.c
@@ -0,0 +1,344 @@
+/* Test plugin for the GNU linker.
+   Copyright 2010 Free Software Foundation, Inc.
+
+   This file is part of the GNU Binutils.
+
+   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.  */
+
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+#include "bfd.h"
+#include "libiberty.h"
+#include "plugin-api.h"
+
+extern enum ld_plugin_status onload (struct ld_plugin_tv *tv);
+enum ld_plugin_status onclaim_file (const struct ld_plugin_input_file *file, int *claimed);
+enum ld_plugin_status onall_symbols_read (void);
+enum ld_plugin_status oncleanup (void);
+
+#define ADDENTRY(tag) { tag, #tag }
+
+typedef struct tag_name
+{
+  enum ld_plugin_tag tag;
+  const char *name;
+} tag_name_t;
+
+tag_name_t tag_names[] =
+{
+  ADDENTRY(LDPT_NULL),
+  ADDENTRY(LDPT_API_VERSION),
+  ADDENTRY(LDPT_GOLD_VERSION),
+  ADDENTRY(LDPT_LINKER_OUTPUT),
+  ADDENTRY(LDPT_OPTION),
+  ADDENTRY(LDPT_REGISTER_CLAIM_FILE_HOOK),
+  ADDENTRY(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK),
+  ADDENTRY(LDPT_REGISTER_CLEANUP_HOOK),
+  ADDENTRY(LDPT_ADD_SYMBOLS),
+  ADDENTRY(LDPT_GET_SYMBOLS),
+  ADDENTRY(LDPT_ADD_INPUT_FILE),
+  ADDENTRY(LDPT_MESSAGE),
+  ADDENTRY(LDPT_GET_INPUT_FILE),
+  ADDENTRY(LDPT_RELEASE_INPUT_FILE),
+  ADDENTRY(LDPT_ADD_INPUT_LIBRARY),
+  ADDENTRY(LDPT_OUTPUT_NAME),
+  ADDENTRY(LDPT_SET_EXTRA_LIBRARY_PATH),
+  ADDENTRY(LDPT_GNU_LD_VERSION)
+};
+
+/* Function pointers to cache hooks passed at onload time.  */
+static ld_plugin_register_claim_file tv_register_claim_file = 0;
+static ld_plugin_register_all_symbols_read tv_register_all_symbols_read = 0;
+static ld_plugin_register_cleanup tv_register_cleanup = 0;
+static ld_plugin_add_symbols tv_add_symbols = 0;
+static ld_plugin_get_symbols tv_get_symbols = 0;
+static ld_plugin_add_input_file tv_add_input_file = 0;
+static ld_plugin_message tv_message = 0;
+static ld_plugin_get_input_file tv_get_input_file = 0;
+static ld_plugin_release_input_file tv_release_input_file = 0;
+static ld_plugin_add_input_library tv_add_input_library = 0;
+static ld_plugin_set_extra_library_path tv_set_extra_library_path = 0;
+
+/* Other cached info from the transfer vector.  */
+static enum ld_plugin_output_file_type linker_output;
+static const char *output_name;
+
+/* Behaviour control flags set by plugin options.  */
+static enum ld_plugin_status onload_ret = LDPS_OK;
+static enum ld_plugin_status claim_file_ret = LDPS_OK;
+static enum ld_plugin_status all_symbols_read_ret = LDPS_OK;
+static enum ld_plugin_status cleanup_ret = LDPS_OK;
+static bfd_boolean register_claimfile_hook = FALSE;
+static bfd_boolean register_allsymbolsread_hook = FALSE;
+static bfd_boolean register_cleanup_hook = FALSE;
+
+static enum ld_plugin_status
+set_ret_val (const char *whichval, enum ld_plugin_status retval)
+{
+  if (!strcmp ("onload", whichval))
+    onload_ret = retval;
+  else if (!strcmp ("claimfile", whichval))
+    claim_file_ret = retval;
+  else if (!strcmp ("allsymbolsread", whichval))
+    all_symbols_read_ret = retval;
+  else if (!strcmp ("cleanup", whichval))
+    cleanup_ret = retval;
+  else
+    return LDPS_ERR;
+  return LDPS_OK;
+}
+
+static enum ld_plugin_status
+set_register_hook (const char *whichhook, bfd_boolean yesno)
+{
+  if (!strcmp ("claimfile", whichhook))
+    register_claimfile_hook = yesno;
+  else if (!strcmp ("allsymbolsread", whichhook))
+    register_allsymbolsread_hook = yesno;
+  else if (!strcmp ("cleanup", whichhook))
+    register_cleanup_hook = yesno;
+  else
+    return LDPS_ERR;
+  return LDPS_OK;
+}
+
+static enum ld_plugin_status
+parse_option (const char *opt)
+{
+  if (!strncmp ("fail", opt, 4))
+    return set_ret_val (opt + 4, LDPS_ERR);
+  else if (!strncmp ("pass", opt, 4))
+    return set_ret_val (opt + 4, LDPS_OK);
+  else if (!strncmp ("register", opt, 8))
+    return set_register_hook (opt + 8, TRUE);
+  else if (!strncmp ("noregister", opt, 10))
+    return set_register_hook (opt + 10, FALSE);
+  else
+    return LDPS_ERR;
+  return LDPS_OK;
+}
+
+static void
+dump_tv_tag (size_t n, struct ld_plugin_tv *tv)
+{
+  size_t tag;
+  char unknownbuf[40];
+  const char *name;
+
+  for (tag = 0; tag < ARRAY_SIZE (tag_names); tag++)
+    if (tag_names[tag].tag == tv->tv_tag)
+      break;
+  sprintf (unknownbuf, "unknown tag #%d", tv->tv_tag);
+  name = (tag < ARRAY_SIZE (tag_names)) ? tag_names[tag].name : unknownbuf;
+  if (tv_message)
+    (*tv_message) (LDPL_INFO, "tv[%d]: %s ", n, name);
+  switch (tv->tv_tag)
+    {
+      case LDPT_OPTION:
+      case LDPT_OUTPUT_NAME:
+	if (tv_message)
+	  (*tv_message) (LDPL_INFO, "'%s'\n", tv->tv_u.tv_string);
+        break;
+      case LDPT_REGISTER_CLAIM_FILE_HOOK:
+      case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
+      case LDPT_REGISTER_CLEANUP_HOOK:
+      case LDPT_ADD_SYMBOLS:
+      case LDPT_GET_SYMBOLS:
+      case LDPT_ADD_INPUT_FILE:
+      case LDPT_MESSAGE:
+      case LDPT_GET_INPUT_FILE:
+      case LDPT_RELEASE_INPUT_FILE:
+      case LDPT_ADD_INPUT_LIBRARY:
+      case LDPT_SET_EXTRA_LIBRARY_PATH:
+	if (tv_message)
+	  (*tv_message) (LDPL_INFO, "func@0x%v\n",
+			(bfd_vma)(tv->tv_u.tv_message));
+        break;
+      case LDPT_NULL:
+      case LDPT_API_VERSION:
+      case LDPT_GOLD_VERSION:
+      case LDPT_LINKER_OUTPUT:
+      case LDPT_GNU_LD_VERSION:
+      default:
+	if (tv_message)
+	  (*tv_message) (LDPL_INFO, "value %W (%d)\n",
+			(bfd_vma)tv->tv_u.tv_val, tv->tv_u.tv_val);
+	break;
+    }
+}
+
+static enum ld_plugin_status
+parse_tv_tag (struct ld_plugin_tv *tv)
+{
+#define SETVAR(x) x = tv->tv_u.x
+  switch (tv->tv_tag)
+    {
+      case LDPT_OPTION:
+	return parse_option (tv->tv_u.tv_string);
+      case LDPT_NULL:
+      case LDPT_GOLD_VERSION:
+      case LDPT_GNU_LD_VERSION:
+      case LDPT_API_VERSION:
+      default:
+	break;
+      case LDPT_OUTPUT_NAME:
+	output_name = tv->tv_u.tv_string;
+	break;
+      case LDPT_LINKER_OUTPUT:
+	linker_output = tv->tv_u.tv_val;
+	break;
+      case LDPT_REGISTER_CLAIM_FILE_HOOK:
+	SETVAR(tv_register_claim_file);
+	break;
+      case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
+	SETVAR(tv_register_all_symbols_read);
+	break;
+      case LDPT_REGISTER_CLEANUP_HOOK:
+	SETVAR(tv_register_cleanup);
+	break;
+      case LDPT_ADD_SYMBOLS:
+	SETVAR(tv_add_symbols);
+	break;
+      case LDPT_GET_SYMBOLS:
+	SETVAR(tv_get_symbols);
+	break;
+      case LDPT_ADD_INPUT_FILE:
+	SETVAR(tv_add_input_file);
+	break;
+      case LDPT_MESSAGE:
+	SETVAR(tv_message);
+	break;
+      case LDPT_GET_INPUT_FILE:
+	SETVAR(tv_get_input_file);
+	break;
+      case LDPT_RELEASE_INPUT_FILE:
+	SETVAR(tv_release_input_file);
+	break;
+      case LDPT_ADD_INPUT_LIBRARY:
+	SETVAR(tv_add_input_library);
+	break;
+      case LDPT_SET_EXTRA_LIBRARY_PATH:
+	SETVAR(tv_set_extra_library_path);
+	break;
+    }
+#undef SETVAR
+  return LDPS_OK;
+}
+
+enum ld_plugin_status
+parse_and_dump_tv_tag (size_t n, struct ld_plugin_tv *tv)
+{
+  enum ld_plugin_status rv = parse_tv_tag (tv);
+  dump_tv_tag (n, tv);
+  return rv;
+}
+
+enum ld_plugin_status
+onload (struct ld_plugin_tv *tv)
+{
+  size_t n = 0;
+  enum ld_plugin_status rv;
+
+  /* This plugin does nothing but dump the tv array.  It would
+     be an error if this function was called without one.  */
+  if (!tv)
+    return LDPS_ERR;
+
+  /* First entry should always be LDPT_MESSAGE, letting us get
+     hold of it easily so we can send output straight away.  */
+  if (tv[0].tv_tag == LDPT_MESSAGE)
+    tv_message = tv[0].tv_u.tv_message;
+
+  fflush (NULL);
+  if (tv_message)
+    (*tv_message) (LDPL_INFO, "Hello from testplugin.\n");
+
+  do
+    if ((rv = parse_and_dump_tv_tag (n++, tv)) != LDPS_OK)
+      return rv;
+  while ((tv++)->tv_tag != LDPT_NULL);
+
+  if (tv_message)
+    (*tv_message) (LDPL_INFO, "\n");
+
+  /* Register hooks only if instructed by options.  */
+  if (register_claimfile_hook)
+    {
+      if (!tv_register_claim_file)
+	{
+	  if (tv_message)
+	    (*tv_message) (LDPL_FATAL, "No register_claim_file hook\n");
+	  fflush (NULL);
+	  return LDPS_ERR;
+	}
+      (*tv_register_claim_file) (onclaim_file);
+    }
+  if (register_allsymbolsread_hook)
+    {
+      if (!tv_register_all_symbols_read)
+	{
+	  if (tv_message)
+	    (*tv_message) (LDPL_FATAL, "No register_all_symbols_read hook\n");
+	  fflush (NULL);
+	  return LDPS_ERR;
+	}
+      (*tv_register_all_symbols_read) (onall_symbols_read);
+    }
+  if (register_cleanup_hook)
+    {
+      if (!tv_register_cleanup)
+	{
+	  if (tv_message)
+	    (*tv_message) (LDPL_FATAL, "No register_cleanup hook\n");
+	  fflush (NULL);
+	  return LDPS_ERR;
+	}
+      (*tv_register_cleanup) (oncleanup);
+    }
+  fflush (NULL);
+  return onload_ret;
+}
+
+
+enum ld_plugin_status
+onclaim_file (const struct ld_plugin_input_file *file, int *claimed)
+{
+  if (tv_message)
+    (*tv_message)(LDPL_INFO, "hook called: claim_file %s [@%ld/%ld]\n",
+      file->name, (long)file->offset, (long)file->filesize);
+  fflush (NULL);
+  return claim_file_ret;
+}
+
+enum ld_plugin_status
+onall_symbols_read (void)
+{
+  if (tv_message)
+    (*tv_message)(LDPL_INFO, "hook called: all symbols read.\n");
+  fflush (NULL);
+  return all_symbols_read_ret;
+}
+
+enum ld_plugin_status
+oncleanup (void)
+{
+  if (tv_message)
+    (*tv_message)(LDPL_INFO, "hook called: cleanup.\n");
+  fflush (NULL);
+  return cleanup_ret;
+}
+
diff --git a/ld/testsuite/ld-plugin/func.c b/ld/testsuite/ld-plugin/func.c
new file mode 100644
index 0000000..8c668db
--- /dev/null
+++ b/ld/testsuite/ld-plugin/func.c
@@ -0,0 +1,7 @@
+
+extern int retval;
+
+int func (void)
+{
+  return retval;
+}
diff --git a/ld/testsuite/ld-plugin/main.c b/ld/testsuite/ld-plugin/main.c
new file mode 100644
index 0000000..2d64617
--- /dev/null
+++ b/ld/testsuite/ld-plugin/main.c
@@ -0,0 +1,13 @@
+
+extern int printf (const char *fmt, ...);
+
+extern const char *text;
+extern int func (void);
+
+int retval = 0;
+
+int main (int argc, const char **argv)
+{
+  printf ("%s\n", text);
+  return func ();
+}
diff --git a/ld/testsuite/ld-plugin/plugin-1.d b/ld/testsuite/ld-plugin/plugin-1.d
new file mode 100644
index 0000000..0ce0794
--- /dev/null
+++ b/ld/testsuite/ld-plugin/plugin-1.d
@@ -0,0 +1,18 @@
+Hello from testplugin.
+tv\[0\]: LDPT_MESSAGE func@0x.*
+tv\[1\]: LDPT_API_VERSION value        0x1 \(1\)
+tv\[2\]: LDPT_GNU_LD_VERSION value       0x.*
+tv\[3\]: LDPT_LINKER_OUTPUT value        0x1 \(1\)
+tv\[4\]: LDPT_OUTPUT_NAME 'tmpdir/main.x'
+tv\[5\]: LDPT_REGISTER_CLAIM_FILE_HOOK func@0x.*
+tv\[6\]: LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK func@0x.*
+tv\[7\]: LDPT_REGISTER_CLEANUP_HOOK func@0x.*
+tv\[8\]: LDPT_ADD_SYMBOLS func@0x.*
+tv\[9\]: LDPT_GET_INPUT_FILE func@0x.*
+tv\[10\]: LDPT_RELEASE_INPUT_FILE func@0x.*
+tv\[11\]: LDPT_GET_SYMBOLS func@0x.*
+tv\[12\]: LDPT_ADD_INPUT_FILE func@0x.*
+tv\[13\]: LDPT_ADD_INPUT_LIBRARY func@0x.*
+tv\[14\]: LDPT_SET_EXTRA_LIBRARY_PATH func@0x.*
+tv\[15\]: LDPT_NULL value        0x0 \(0\)
+#...
diff --git a/ld/testsuite/ld-plugin/plugin-2.d b/ld/testsuite/ld-plugin/plugin-2.d
new file mode 100644
index 0000000..677f8fb
--- /dev/null
+++ b/ld/testsuite/ld-plugin/plugin-2.d
@@ -0,0 +1,21 @@
+Hello from testplugin.
+tv\[0\]: LDPT_MESSAGE func@0x.*
+tv\[1\]: LDPT_API_VERSION value        0x1 \(1\)
+tv\[2\]: LDPT_GNU_LD_VERSION value       0x.*
+tv\[3\]: LDPT_LINKER_OUTPUT value        0x1 \(1\)
+tv\[4\]: LDPT_OUTPUT_NAME 'tmpdir/main.x'
+tv\[5\]: LDPT_REGISTER_CLAIM_FILE_HOOK func@0x.*
+tv\[6\]: LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK func@0x.*
+tv\[7\]: LDPT_REGISTER_CLEANUP_HOOK func@0x.*
+tv\[8\]: LDPT_ADD_SYMBOLS func@0x.*
+tv\[9\]: LDPT_GET_INPUT_FILE func@0x.*
+tv\[10\]: LDPT_RELEASE_INPUT_FILE func@0x.*
+tv\[11\]: LDPT_GET_SYMBOLS func@0x.*
+tv\[12\]: LDPT_ADD_INPUT_FILE func@0x.*
+tv\[13\]: LDPT_ADD_INPUT_LIBRARY func@0x.*
+tv\[14\]: LDPT_SET_EXTRA_LIBRARY_PATH func@0x.*
+tv\[15\]: LDPT_OPTION 'failonload'
+tv\[16\]: LDPT_NULL value        0x0 \(0\)
+#...
+.*ld.*:.*ldtestplug.*: error loading plugin
+#...
diff --git a/ld/testsuite/ld-plugin/plugin-3.d b/ld/testsuite/ld-plugin/plugin-3.d
new file mode 100644
index 0000000..9014870
--- /dev/null
+++ b/ld/testsuite/ld-plugin/plugin-3.d
@@ -0,0 +1,23 @@
+Hello from testplugin.
+tv\[0\]: LDPT_MESSAGE func@0x.*
+tv\[1\]: LDPT_API_VERSION value        0x1 \(1\)
+tv\[2\]: LDPT_GNU_LD_VERSION value       0x.*
+tv\[3\]: LDPT_LINKER_OUTPUT value        0x1 \(1\)
+tv\[4\]: LDPT_OUTPUT_NAME 'tmpdir/main.x'
+tv\[5\]: LDPT_REGISTER_CLAIM_FILE_HOOK func@0x.*
+tv\[6\]: LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK func@0x.*
+tv\[7\]: LDPT_REGISTER_CLEANUP_HOOK func@0x.*
+tv\[8\]: LDPT_ADD_SYMBOLS func@0x.*
+tv\[9\]: LDPT_GET_INPUT_FILE func@0x.*
+tv\[10\]: LDPT_RELEASE_INPUT_FILE func@0x.*
+tv\[11\]: LDPT_GET_SYMBOLS func@0x.*
+tv\[12\]: LDPT_ADD_INPUT_FILE func@0x.*
+tv\[13\]: LDPT_ADD_INPUT_LIBRARY func@0x.*
+tv\[14\]: LDPT_SET_EXTRA_LIBRARY_PATH func@0x.*
+tv\[15\]: LDPT_OPTION 'registerallsymbolsread'
+tv\[16\]: LDPT_OPTION 'failallsymbolsread'
+tv\[17\]: LDPT_NULL value        0x0 \(0\)
+# Enable these next two lines when all symbols read hook is being called by infrastructure.
+##...
+#.*ld.*:.*ldtestplug.*: plugin reported error after all symbols read
+#...
diff --git a/ld/testsuite/ld-plugin/plugin-4.d b/ld/testsuite/ld-plugin/plugin-4.d
new file mode 100644
index 0000000..580cbac
--- /dev/null
+++ b/ld/testsuite/ld-plugin/plugin-4.d
@@ -0,0 +1,23 @@
+Hello from testplugin.
+tv\[0\]: LDPT_MESSAGE func@0x.*
+tv\[1\]: LDPT_API_VERSION value        0x1 \(1\)
+tv\[2\]: LDPT_GNU_LD_VERSION value       0x.*
+tv\[3\]: LDPT_LINKER_OUTPUT value        0x1 \(1\)
+tv\[4\]: LDPT_OUTPUT_NAME 'tmpdir/main.x'
+tv\[5\]: LDPT_REGISTER_CLAIM_FILE_HOOK func@0x.*
+tv\[6\]: LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK func@0x.*
+tv\[7\]: LDPT_REGISTER_CLEANUP_HOOK func@0x.*
+tv\[8\]: LDPT_ADD_SYMBOLS func@0x.*
+tv\[9\]: LDPT_GET_INPUT_FILE func@0x.*
+tv\[10\]: LDPT_RELEASE_INPUT_FILE func@0x.*
+tv\[11\]: LDPT_GET_SYMBOLS func@0x.*
+tv\[12\]: LDPT_ADD_INPUT_FILE func@0x.*
+tv\[13\]: LDPT_ADD_INPUT_LIBRARY func@0x.*
+tv\[14\]: LDPT_SET_EXTRA_LIBRARY_PATH func@0x.*
+tv\[15\]: LDPT_OPTION 'failcleanup'
+tv\[16\]: LDPT_OPTION 'registercleanup'
+tv\[17\]: LDPT_NULL value        0x0 \(0\)
+#...
+hook called: cleanup.
+.*ld.*:.*ldtestplug.*: error in plugin cleanup \(ignored\)
+#...
diff --git a/ld/testsuite/ld-plugin/plugin-5.d b/ld/testsuite/ld-plugin/plugin-5.d
new file mode 100644
index 0000000..b9b02ab
--- /dev/null
+++ b/ld/testsuite/ld-plugin/plugin-5.d
@@ -0,0 +1,31 @@
+Hello from testplugin.
+tv\[0\]: LDPT_MESSAGE func@0x.*
+tv\[1\]: LDPT_API_VERSION value        0x1 \(1\)
+tv\[2\]: LDPT_GNU_LD_VERSION value       0x.*
+tv\[3\]: LDPT_LINKER_OUTPUT value        0x1 \(1\)
+tv\[4\]: LDPT_OUTPUT_NAME 'tmpdir/main.x'
+tv\[5\]: LDPT_REGISTER_CLAIM_FILE_HOOK func@0x.*
+tv\[6\]: LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK func@0x.*
+tv\[7\]: LDPT_REGISTER_CLEANUP_HOOK func@0x.*
+tv\[8\]: LDPT_ADD_SYMBOLS func@0x.*
+tv\[9\]: LDPT_GET_INPUT_FILE func@0x.*
+tv\[10\]: LDPT_RELEASE_INPUT_FILE func@0x.*
+tv\[11\]: LDPT_GET_SYMBOLS func@0x.*
+tv\[12\]: LDPT_ADD_INPUT_FILE func@0x.*
+tv\[13\]: LDPT_ADD_INPUT_LIBRARY func@0x.*
+tv\[14\]: LDPT_SET_EXTRA_LIBRARY_PATH func@0x.*
+tv\[15\]: LDPT_OPTION 'registerclaimfile'
+tv\[16\]: LDPT_OPTION 'registerallsymbolsread'
+tv\[17\]: LDPT_OPTION 'registercleanup'
+tv\[18\]: LDPT_NULL value        0x0 \(0\)
+# Enable these next four lines when claim hook is being called by infrastructure.
+##...
+#hook called: claim_file tmpdir/main.o \[@0/.*
+#hook called: claim_file tmpdir/func.o \[@0/.*
+#hook called: claim_file tmpdir/text.o \[@0/.*
+# Enable these next two lines when all symbols read hook is being called by infrastructure.
+##...
+#hook called: all symbols read.
+#...
+hook called: cleanup.
+#...
diff --git a/ld/testsuite/ld-plugin/plugin.exp b/ld/testsuite/ld-plugin/plugin.exp
new file mode 100644
index 0000000..1150d93
--- /dev/null
+++ b/ld/testsuite/ld-plugin/plugin.exp
@@ -0,0 +1,76 @@
+# Expect script for ld-plugin tests
+#   Copyright 2010
+#   Free Software Foundation, Inc.
+#
+# This file is part of the GNU Binutils.
+#
+# 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.
+
+# These tests require the plugin API to be configured in.
+if ![check_plugin_api_available] {
+    return
+}
+
+pass "plugin API enabled"
+
+global base_dir
+
+# Look for the name we can dlopen in the test plugin's libtool control script.
+set plugin_name [file_contents "$base_dir/libldtestplug.la"]
+set plugin_name [regsub "'.*" [regsub ".*dlname='" "$plugin_name" ""] ""]
+verbose "plugin name is '$plugin_name'"
+
+# Use libtool to find full path to plugin rather than worrying
+# about run paths or anything like that.
+catch "exec $base_dir/libtool --config" lt_config
+verbose "Full lt config: $lt_config" 3
+# Look for "objdir=.libs"
+regexp -line "^objdir=.*$" "$lt_config" lt_objdir
+verbose "lt_objdir line is '$lt_objdir'" 3
+set lt_objdir [regsub "objdir=" "$lt_objdir" ""]
+set plugin_path "$base_dir/$lt_objdir/$plugin_name"
+verbose "Full plugin path $plugin_path" 2
+
+set testobjfiles "$HOSTING_CRT0 tmpdir/main.o tmpdir/func.o tmpdir/text.o"
+set libs "$LIBS $HOSTING_LIBS"
+
+set regclm "-plugin-arg registerclaimfile"
+set regas "-plugin-arg registerallsymbolsread"
+set regcln "-plugin-arg registercleanup"
+
+set plugin_tests [list \
+    [list "load plugin" "-plugin $plugin_path \
+    $testobjfiles $libs" "" "" {{ld plugin-1.d}} "main.x" ] \
+    [list "fail plugin onload" "-plugin $plugin_path -plugin-arg failonload \
+    $testobjfiles $libs" "" "" {{ld plugin-2.d}} "main.x" ] \
+    [list "fail plugin allsymbolsread" "-plugin $plugin_path $regas -plugin-arg failallsymbolsread \
+    $testobjfiles $libs" "" "" {{ld plugin-3.d}} "main.x" ] \
+    [list "fail plugin cleanup" "-plugin $plugin_path -plugin-arg failcleanup $regcln \
+    $testobjfiles $libs" "" "" {{ld plugin-4.d}} "main.x" ] \
+    [list "plugin all hooks" "-plugin $plugin_path $regclm $regas $regcln \
+    $testobjfiles $libs" "" "" {{ld plugin-5.d}} "main.x" ] \
+]
+
+if { ![ld_compile "$CC $CFLAGS" $srcdir/$subdir/main.c tmpdir/main.o]
+	|| ![ld_compile "$CC $CFLAGS" $srcdir/$subdir/func.c tmpdir/func.o]
+	|| ![ld_compile "$CC $CFLAGS" $srcdir/$subdir/text.c tmpdir/text.o] } {
+    foreach testitem $plugin_tests {
+	unresolved [lindex $testitem 0]
+    }
+    return
+}
+
+run_ld_link_tests $plugin_tests
diff --git a/ld/testsuite/ld-plugin/text.c b/ld/testsuite/ld-plugin/text.c
new file mode 100644
index 0000000..6d02114
--- /dev/null
+++ b/ld/testsuite/ld-plugin/text.c
@@ -0,0 +1,3 @@
+
+const char *text = "Hello world!\n";
+
diff --git a/ld/testsuite/lib/ld-lib.exp b/ld/testsuite/lib/ld-lib.exp
index 709702a..9803076 100644
--- a/ld/testsuite/lib/ld-lib.exp
+++ b/ld/testsuite/lib/ld-lib.exp
@@ -1125,6 +1125,7 @@ proc regexp_diff { file_1 file_2 } {
         } else {
             verbose "regexp \"^$line_b$\"\nline   \"$line_a\"" 3
             if ![regexp "^$line_b$" "$line_a"] {
+		verbose "regexp_diff match failure\n" 3
 		send_log "regexp_diff match failure\n"
 		send_log "regexp \"^$line_b$\"\nline   \"$line_a\"\n"
 		set differences 1
@@ -1159,6 +1160,12 @@ proc file_contents { filename } {
     return $contents
 }
 
+proc set_file_contents { filename contents } {
+    set file [open $filename w]
+    puts $file "$contents"
+    close $file
+}
+
 # Create an archive using ar
 #
 proc ar_simple_create { ar aropts target objects } {
@@ -1185,6 +1192,10 @@ proc ar_simple_create { ar aropts target objects } {
 # objdump: Apply objdump options on result.  Compare with regex (last arg).
 # nm: Apply nm options on result.  Compare with regex (last arg).
 # readelf: Apply readelf options on result.  Compare with regex (last arg).
+# ld: Don't apply anything on result.  Compare output during linking with 
+#     regex (second arg).  Note that this *must* be the first action if it
+#     is to be used at all; in all other cases, any output from the linker
+#     during linking is treated as a sign of an error and FAILs the test.
 #
 proc run_ld_link_tests { ldtests } {
     global ld
@@ -1199,6 +1210,7 @@ proc run_ld_link_tests { ldtests } {
     global CC
     global CFLAGS
     global runtests
+    global exec_output
 
     foreach testitem $ldtests {
 	set testname [lindex $testitem 0]
@@ -1216,6 +1228,8 @@ proc run_ld_link_tests { ldtests } {
 	set objfiles {}
 	set is_unresolved 0
 	set failed 0
+	set maybe_failed 0
+	set ld_output ""
 
 #	verbose -log "Testname is $testname"
 #	verbose -log "ld_options is $ld_options"
@@ -1258,8 +1272,8 @@ proc run_ld_link_tests { ldtests } {
 		set failed 0
 	    }
 	} elseif { ![ld_simple_link $ld $binfile "-L$srcdir/$subdir $ld_options $objfiles"] } {
-	    fail $testname
-	    set failed 1
+	    set maybe_failed 1
+	    set ld_output "$exec_output"
 	} else {
 	    set failed 0
 	}
@@ -1280,6 +1294,8 @@ proc run_ld_link_tests { ldtests } {
 		        { set dump_prog $nm }
 		    readelf
 		        { set dump_prog $READELF }
+		    ld
+		        { set dump_prog "ld" }
 		    default
 			{
 			    perror "Unrecognized action $action"
@@ -1288,7 +1304,21 @@ proc run_ld_link_tests { ldtests } {
 			}
 		    }
 
-		if { $dump_prog != "" } {
+		if { $action == "ld" } {
+		    set dumpfile [lindex $actionlist 1]
+		    verbose "dumpfile is $dumpfile"
+		    set_file_contents "tmpdir/ld.messages" "$ld_output"
+		    verbose "ld.messages has '[file_contents tmpdir/ld.messages]'"
+		    if { [regexp_diff "tmpdir/ld.messages" "$srcdir/$subdir/$dumpfile"] } then {
+			verbose "output is $ld_output" 2
+			set failed 1
+			break
+		    }
+		    set maybe_failed 0
+		} elseif { $maybe_failed != 0 } {
+		    set failed 1
+		    break
+		} elseif { $dump_prog != "" } {
 		    set dumpfile [lindex $actionlist 2]
 		    set binary $dump_prog
 
@@ -1684,6 +1714,22 @@ proc check_gc_sections_available { } {
     return $gc_sections_available_saved
 }
 
+# Returns true if the target ld supports the plugin API.
+proc check_plugin_api_available { } {
+    global plugin_api_available_saved
+    global ld
+    if {![info exists plugin_api_available_saved]} {
+	# Check if the ld used by gcc supports --plugin.
+	set ld_output [remote_exec host $ld "--help"]
+	if { [ string first "-plugin" $ld_output ] >= 0 } {
+	    set plugin_api_available_saved 1
+	} else {
+	    set plugin_api_available_saved 0
+	}
+    }
+    return $plugin_api_available_saved
+}
+
 # Check if the assembler supports CFI statements.
 
 proc check_as_cfi { } {

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]