]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Add optional filename argument to the linker's --stats option, allowing extra resourc...
authorNick Clifton <nickc@redhat.com>
Wed, 2 Apr 2025 09:56:16 +0000 (10:56 +0100)
committerNick Clifton <nickc@redhat.com>
Wed, 2 Apr 2025 10:06:33 +0000 (11:06 +0100)
14 files changed:
ld/NEWS
ld/config.in
ld/configure
ld/configure.ac
ld/emultempl/ppc64elf.em
ld/ld.h
ld/ld.texi
ld/ldlang.c
ld/ldlex.h
ld/ldmain.c
ld/lexsup.c
ld/testsuite/ld-elf/sec64k.exp
ld/testsuite/ld-scripts/map-address.exp
ld/testsuite/ld-scripts/map-stats.d [new file with mode: 0644]

diff --git a/ld/NEWS b/ld/NEWS
index 494bb83e49b35add96c1b818f7a9cae778953bea..7b5e2e47c07bd842fbdd50a0113a9199e5a76d68 100644 (file)
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -1,5 +1,14 @@
 -*- text -*-
 
+* The linker's --stats option can take an optional argument which if used is
+  interpreted as a filename into which resource usage information should be
+  stored.  As an alternative mechanism the LD_STATS environment variable can
+  also be used to achieve the same results.  Resource usage information for
+  various phases of the linking operation is now included in the report.
+  If a map file is being produced then the information is also included there.
+  The --no-stats option can be used to disable stat reporting, should it have
+  been enabled.
+
 * Remove the linker -taso option for Alpha target, as Linux/Alpha kernel
   support for 32-bit pointers has been removed.
 
index 2d7b6406d2bbc5e48fa8d5e8ea813da67f960830..e10c9e73cc6337cf1f66c21ba9892eeb39247ed8 100644 (file)
 /* Define to 1 if you have the `getpagesize' function. */
 #undef HAVE_GETPAGESIZE
 
+/* Define to 1 if you have the `getrusage' function. */
+#undef HAVE_GETRUSAGE
+
 /* Define if the GNU gettext() function is already present or preinstalled. */
 #undef HAVE_GETTEXT
 
index b7af25d1e5fdfca405ceda36c9370ca96038f6ae..3f745ac883eef5e69dc5aa0a96b9ca9c1ca79993 100755 (executable)
@@ -18753,7 +18753,7 @@ fi
 
 done
 
-for ac_func in close glob lseek mkstemp open realpath waitpid
+for ac_func in close getrusage glob lseek mkstemp open realpath waitpid
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
index 228f2ee4089909e79f17012c285990f29a56a105..1ee0c0c77f9c80c5b50e24669ec111af3fc8f112 100644 (file)
@@ -414,7 +414,7 @@ AC_SUBST(NATIVE_LIB_DIRS)
 AC_CHECK_HEADERS(fcntl.h elf-hints.h limits.h inttypes.h stdint.h \
                 sys/file.h sys/mman.h sys/param.h sys/stat.h sys/time.h \
                 sys/types.h unistd.h)
-AC_CHECK_FUNCS(close glob lseek mkstemp open realpath waitpid)
+AC_CHECK_FUNCS(close getrusage glob lseek mkstemp open realpath waitpid)
 
 BFD_BINARY_FOPEN
 
index f7a8f1eb259d83bd7739a528833ed6c5eb23f5c6..857cf54ad06104d7b25c934944310276af58896d 100644 (file)
@@ -606,14 +606,15 @@ gld${EMULATION_NAME}_finish (void)
     einfo (_("%X%P: can not build stubs: %E\n"));
 
   fflush (stdout);
+  FILE * out = config.stats_file ? config.stats_file : stderr;
   for (line = msg; line != NULL; line = endline)
     {
       endline = strchr (line, '\n');
       if (endline != NULL)
        *endline++ = '\0';
-      fprintf (stderr, "%s: %s\n", program_name, line);
+      fprintf (out, "%s: %s\n", program_name, line);
     }
-  fflush (stderr);
+  fflush (out);
   free (msg);
 
   ldelf_finish ();
diff --git a/ld/ld.h b/ld/ld.h
index 254f0a097bb4c1c01705bf0988f86c521ecc0485..c8688153bd4db64d2afcdc8efe6604f0cbba4e66 100644 (file)
--- a/ld/ld.h
+++ b/ld/ld.h
@@ -295,6 +295,10 @@ typedef struct
   char *map_filename;
   FILE *map_file;
 
+  char *stats_filename;
+  /* If non-NULL then resource use information should be written to this file.  */
+  FILE *stats_file;
+
   char *dependency_file;
 
   unsigned int split_by_reloc;
@@ -330,6 +334,39 @@ typedef struct
   enum compressed_debug_section_type compress_debug;
 } ld_config_type;
 
+/* An enumeration of the linker phases for which resource usage information
+   is recorded.  PHASE_ALL is special as it covers the entire link process.
+
+   Instructions for adding a new phase:
+     1. Add an entry to this enumeration.
+     2. Add an entry for the phase to the phase_data[] structure in ldmain.c.
+     3. Add calls to ld_start_phase(PHASE_xxx) and ld_stop_phase(PHASE_xxx)
+        at the appropriate place(s) in the code.  It does not matter if the
+       new phase overlaps with or is contained by any other phase.
+
+    Instructions for adding a new resource:
+      1. If necessary add a new field to the phase_data structure defined in
+         ldmain.c.
+      2. Add code to initialise the field in ld_main.c:ld_start_phase().
+      3. Add code to finalise the field in ld_main.c:ld_stop_phase().
+      4. Add code to report the field in ld_main.c:report_phases().  */
+typedef enum
+{
+  PHASE_ALL = 0,
+  PHASE_CTF,
+  PHASE_MERGE,
+  PHASE_PARSE,
+  PHASE_PLUGINS,
+  PHASE_PROCESS,
+  PHASE_WRITE,
+
+  NUM_PHASES /* This must be the last entry.  */
+}
+ld_phase;
+
+extern void ld_start_phase (ld_phase);
+extern void ld_stop_phase (ld_phase);
+
 extern ld_config_type config;
 
 extern FILE * saved_script_handle;
index b85d8103b9f47c49985474c2400dd989e2d56744..29bd0e1bb1e65387ae3e4d85cf538e0d2614da38 100644 (file)
@@ -2184,6 +2184,9 @@ Memory region         Used Size  Region Size  %age Used
              RAM:          32 B         2 GB      0.00%
 @end smallexample
 
+Note: if you want to find out about the memory usage of the linker
+itself, then the @option{--stats} option will do this.
+
 @cindex help
 @cindex usage
 @kindex --help
@@ -2706,10 +2709,76 @@ more than @var{count} relocations one output section will contain that
 many relocations.  @var{count} defaults to a value of 32768.
 
 @kindex --stats
-@item --stats
+@item --stats[=@var{filename}]
 Compute and display statistics about the operation of the linker, such
 as execution time and memory usage.
 
+If the optional @var{filename} argument is not supplied then only
+basic information is reported, and it is sent to the standard error
+output stream.  If the @var{filename} argument is supplied then
+extended information is written to the named file.  If @var{filename}
+is set to just the @var{-} symbol, then the extended information is
+sent to the standard output stream.  If the @var{filename} starts with
+@var{+} then the file is opened in append mode rather than overwrite
+mode.
+
+If the @option{-Map} option has been enabled then the information is
+also recorded in the map file as well.  Note: if both the
+@option{--stats} option and the @option{-Map} options have been given
+@var{filename} arguments and they match, then the information will
+only be written out once not twice.
+
+If the @code{LD_STATS} environment variable is defined then this
+behaves likes the @option{--stats} option.  If the variable's value is
+a string then this will used as the name of a file into which the
+information should be recorded.  Otherwise the information
+will be sent to the standard output stream.  Using the environment
+variable allows stats to be recorded without having to alter the
+linker's command line.  Note: if both the environment variable and the
+@option{--stats} option are used then the @option{--stats} option
+takes precedence.
+
+The extended information reported includes the cpu time used and, if
+the @var{getrusage()} system library call is available then memory use
+is recorded as well.  This information is reported for individual
+parts of the linking process which are referred to as @emph{phases}.
+In addition the information is also reported for a special phase
+called @emph{ALL} which covers the entire linking process.  Note that
+individual phases can contain or overlap with each other so it should
+not be assumed that the overall resources used by the linker is the
+sum of the resources used by the individual phases.
+
+In addition when extended information is being reported the linker
+version, command line arguments and linker start time are also
+included.  This makes it easier to handle the situation where multiple
+links are being invoked by a build system and to indentify exactly
+which arguments were responsible for producing the statistics that are
+reported.
+
+The extended output looks something like this:
+
+@smallexample
+Stats: linker version: (GNU Binutils) 2.44.50.20250401
+Stats: linker started: Wed Apr  2 09:36:41 2025
+Stats: args: ld -z norelro -z nomemory-seal -z no-separate-code -o a.out [...]
+
+Stats: phase               cpu time    memory      user time    system time    
+Stats: name                (microsec)   (KiB)      (seconds)      (seconds)    
+Stats: ALL                   390082    217740              0              0    
+Stats: ctf processing            12         0              0              0    
+Stats: string merge            1324         0              0              0    
+Stats: parsing                  349       288              0              0    
+Stats: plugins                    1         0              0              0    
+Stats: processing files      259616    214524              0              0    
+Stats: write                 116493         0              0              0    
+@end smallexample
+
+@kindex --no-stats
+@item --no-stats
+Disables the reporting of usage statistics, should it have been
+enabled via the @option{--stats} command line option or the
+@var{LD_STATS} environment variable.
+
 @kindex --sysroot=@var{directory}
 @item --sysroot=@var{directory}
 Use @var{directory} as the location of the sysroot, overriding the
@@ -4078,6 +4147,15 @@ If the PE/COFF specific @option{--insert-timestamp} is active and the
 timestamp value in this variable will be inserted into the COFF header
 instead of the current time.
 
+@kindex LD_STATS
+@cindex LD_STATS
+If the @code{LD_STATS} environment variable is defined then linker
+resource use information will be recorded, just as if the
+@option{--stats} option had been used.  If the @code{LD_STATS}
+variable has a string value then this will used as the name of a file
+into which the information should be stored.  Otherwise the information
+will be sent to the standard output stream.
+
 @c man end
 @end ifset
 
index 0048dfa4911ceab218d377208b2c4ca172925980..0bb9e17d92798527da071cc93ff6098137aef0f1 100644 (file)
@@ -3807,6 +3807,8 @@ ldlang_open_ctf (void)
   int any_ctf = 0;
   int err;
 
+  ld_start_phase (PHASE_CTF);
+
   LANG_FOR_EACH_INPUT_STATEMENT (file)
     {
       asection *sect;
@@ -3844,17 +3846,23 @@ ldlang_open_ctf (void)
   if (!any_ctf)
     {
       ctf_output = NULL;
+      ld_stop_phase (PHASE_CTF);
       return;
     }
 
   if ((ctf_output = ctf_create (&err)) != NULL)
-    return;
+    {
+      ld_stop_phase (PHASE_CTF);
+      return;
+    }
 
   einfo (_("%P: warning: CTF output not created: `%s'\n"),
         ctf_errmsg (err));
 
   LANG_FOR_EACH_INPUT_STATEMENT (errfile)
     ctf_close (errfile->the_ctf);
+
+  ld_stop_phase (PHASE_CTF);
 }
 
 /* Merge together CTF sections.  After this, only the symtab-dependent
@@ -3869,6 +3877,8 @@ lang_merge_ctf (void)
   if (!ctf_output)
     return;
 
+  ld_start_phase (PHASE_CTF);
+
   output_sect = bfd_get_section_by_name (link_info.output_bfd, ".ctf");
 
   /* If the section was discarded, don't waste time merging.  */
@@ -3882,6 +3892,8 @@ lang_merge_ctf (void)
          ctf_close (file->the_ctf);
          file->the_ctf = NULL;
        }
+
+      ld_stop_phase (PHASE_CTF);
       return;
     }
 
@@ -3924,6 +3936,8 @@ lang_merge_ctf (void)
     }
   /* Output any lingering errors that didn't come from ctf_link.  */
   lang_ctf_errs_warnings (ctf_output);
+
+  ld_stop_phase (PHASE_CTF);
 }
 
 /* Let the emulation acquire strings from the dynamic strtab to help it optimize
@@ -3932,7 +3946,9 @@ lang_merge_ctf (void)
 void
 ldlang_ctf_acquire_strings (struct elf_strtab_hash *dynstrtab)
 {
+  ld_start_phase (PHASE_CTF);
   ldemul_acquire_strings_for_ctf (ctf_output, dynstrtab);
+  ld_stop_phase (PHASE_CTF);
 }
 
 /* Inform the emulation about the addition of a new dynamic symbol, in BFD
@@ -3954,16 +3970,24 @@ lang_write_ctf (int late)
   if (!ctf_output)
     return;
 
+  ld_start_phase (PHASE_CTF);
+
   if (late)
     {
       /* Emit CTF late if this emulation says it can do so.  */
       if (ldemul_emit_ctf_early ())
-       return;
+       {
+         ld_stop_phase (PHASE_CTF);
+         return;
+       }
     }
   else
     {
       if (!ldemul_emit_ctf_early ())
-       return;
+       {
+         ld_stop_phase (PHASE_CTF);
+         return;
+       }
     }
 
   /* Inform the emulation that all the symbols that will be received have
@@ -3998,6 +4022,8 @@ lang_write_ctf (int late)
 
   LANG_FOR_EACH_INPUT_STATEMENT (file)
     file->the_ctf = NULL;
+
+  ld_stop_phase (PHASE_CTF);
 }
 
 /* Write out the CTF section late, if the emulation needs that.  */
@@ -8547,6 +8573,8 @@ lang_process (void)
     {
       asection *found;
 
+      ld_start_phase (PHASE_MERGE);
+
       /* Merge SEC_MERGE sections.  This has to be done after GC of
         sections, so that GCed sections are not merged, but before
         assigning dynamic symbols, since removing whole input sections
@@ -8554,6 +8582,8 @@ lang_process (void)
       if (!bfd_merge_sections (link_info.output_bfd, &link_info))
        fatal (_("%P: bfd_merge_sections failed: %E\n"));
 
+      ld_stop_phase (PHASE_MERGE);
+
       /* Look for a text section and set the readonly attribute in it.  */
       found = bfd_get_section_by_name (link_info.output_bfd, ".text");
 
index 999d0defc6151a3408a86064f0898d14bcb40085..815da76a4c0ee4879db2b9ab45c63c296158a32d 100644 (file)
@@ -46,6 +46,7 @@ enum option_values
   OPTION_MAP,
   OPTION_NO_DEMANGLE,
   OPTION_NO_KEEP_MEMORY,
+  OPTION_NO_STATS,
   OPTION_NO_WARN_MISMATCH,
   OPTION_NO_WARN_SEARCH_MISMATCH,
   OPTION_NOINHIBIT_EXEC,
index 54a834e42a61ff03cbcf8cba3c435b238c91549b..91237a4baad11dc8aea767f81bac54c7df83953f 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "sysdep.h"
 #include "bfd.h"
+#include "bfdver.h"
 #include "safe-ctype.h"
 #include "libiberty.h"
 #include "bfdlink.h"
 
 #include <string.h>
 
+#if defined (HAVE_GETRUSAGE)
+#include <sys/resource.h>
+#endif
+
 #ifndef TARGET_SYSTEM_ROOT
 #define TARGET_SYSTEM_ROOT ""
 #endif
@@ -224,6 +229,10 @@ ld_cleanup (void)
       bfd_close_all_done (ibfd);
     }
 #if BFD_SUPPORTS_PLUGINS
+  /* Note - we do not call ld_plugin_start (PHASE_PLUGINS) here as this
+     function is only called when the linker is exiting - ie after any
+     stats may have been reported, and potentially in the middle of a
+     phase where we have already started recording plugin stats.  */
   plugin_call_cleanup ();
 #endif
   if (output_filename && delete_output_file_on_failure)
@@ -270,11 +279,305 @@ display_external_script (void)
   free (buf);
 }
 
+struct ld_phase_data
+{
+  const char *    name;
+
+  unsigned long   start;
+  unsigned long   duration;
+
+  bool            started;
+  bool            broken;
+
+#if defined (HAVE_GETRUSAGE)
+  struct rusage   begin;
+  struct rusage   use;
+#endif
+};
+
+static struct ld_phase_data phase_data [NUM_PHASES] =
+{
+  [PHASE_ALL]     = { .name = "ALL" },
+  [PHASE_CTF]     = { .name = "ctf processing" },
+  [PHASE_MERGE]   = { .name = "string merge" },
+  [PHASE_PARSE]   = { .name = "parsing" },
+  [PHASE_PLUGINS] = { .name = "plugins" },
+  [PHASE_PROCESS] = { .name = "processing files" },
+  [PHASE_WRITE]   = { .name = "write" },
+};
+
+void
+ld_start_phase (ld_phase phase)
+{
+  struct ld_phase_data * pd = phase_data + phase;
+
+  /* We record data even if config.stats_file is NULL.  This allows
+     us to record data about phases that start before the command line
+     arguments have been parsed.  ie PHASE_ALL and PHASE_PARSE.  */
+
+  /* Do not overwrite the fields if we have already started recording.  */
+  if (pd->started)
+    {
+      /* Since we do not queue phase starts and stops, if a phase is started
+        multiple times there is a likelyhood that it will be stopped multiple
+        times as well.  This is problematic as we will only record the data
+        for the first time the phase stops and ignore all of the other stops.
+
+        So let the user know.  Ideally real users will never actually see
+        this message, and instead only developers who are adding new phase
+        tracking code will ever encounter it.  */
+      einfo ("%P: --stats: phase %s started twice - data may be unreliable\n",
+            pd->name);
+      return;
+    }
+
+  /* It is OK if other phases are also active at this point.
+     It just means that the phases overlap or that one phase is a sub-task
+     of another.  Since we record resources on a per-phase basis, this
+     should not matter.  */
+
+  pd->started = true;
+  pd->start = get_run_time ();
+
+#if defined (HAVE_GETRUSAGE)
+  /* Record the resource usage at the start of the phase.  */
+  struct rusage usage;
+
+  if (getrusage (RUSAGE_SELF, & usage) != 0)
+    /* FIXME: Complain ?  */
+    return;
+  
+  memcpy (& pd->begin, & usage, sizeof usage);
+#endif
+}
+
+void
+ld_stop_phase (ld_phase phase)
+{
+  struct ld_phase_data * pd = phase_data + phase;
+
+  if (!pd->started)
+    {
+      /* We set the broken flag to indicate that the data
+        recorded for this phase is inconsistent.  */
+      pd->broken = true;
+      return;
+    }
+
+  pd->duration += get_run_time () - pd->start;
+  pd->started = false;
+
+#if defined (HAVE_GETRUSAGE)
+  struct rusage usage;
+
+  if (getrusage (RUSAGE_SELF, & usage) != 0)
+    /* FIXME: Complain ?  */
+    return;
+
+  if (phase == PHASE_ALL)
+    memcpy (& pd->use, & usage, sizeof usage);
+  else
+    {
+      struct timeval t;
+
+      /* For sub-phases we record the increase in specific fields.  */
+      /* FIXME: Most rusage{} fields appear to be irrelevent to when considering
+        linker resource usage.  Currently we record maxrss and user and system
+        cpu times.  Are there any other fields that might be useful ?  */
+
+#ifndef timeradd /* Macros copied from <sys/time.h>.  */
+#define timeradd(a, b, result)                                 \
+      do                                                       \
+       {                                                       \
+         (result)->tv_sec = (a)->tv_sec + (b)->tv_sec;         \
+         (result)->tv_usec = (a)->tv_usec + (b)->tv_usec;      \
+         if ((result)->tv_usec >= 1000000)                     \
+           {                                                   \
+             ++(result)->tv_sec;                               \
+             (result)->tv_usec -= 1000000;                     \
+           }                                                   \
+       }                                                       \
+      while (0)
+#endif
+      
+#ifndef timersub
+#define timersub(a, b, result)                                 \
+      do                                                       \
+       {                                                       \
+         (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;         \
+         (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;      \
+         if ((result)->tv_usec < 0)                            \
+           {                                                   \
+             --(result)->tv_sec;                               \
+             (result)->tv_usec += 1000000;                     \
+           }                                                   \
+       }                                                       \
+      while (0)
+#endif
+      
+      timersub (& usage.ru_utime, & pd->begin.ru_utime, & t);
+      timeradd (& pd->use.ru_utime, &t, & pd->use.ru_utime);
+
+      timersub (& usage.ru_stime, & pd->begin.ru_stime, & t);
+      timeradd (& pd->use.ru_stime, &t, & pd->use.ru_stime);
+               
+      if (pd->begin.ru_maxrss < usage.ru_maxrss)
+       pd->use.ru_maxrss += usage.ru_maxrss - pd->begin.ru_maxrss;
+#endif
+    }
+}
+
+static void
+report_phases (FILE * file, time_t * start, char ** argv)
+{
+  unsigned long i;
+
+  if (file == NULL)
+    return;
+
+  /* We might be writing to stdout, so make sure
+     that we do not have any pending error output.  */
+  fflush (stderr);
+
+  /* We do not translate "Stats" as we provide this as a key
+     word that can be searched for by grep and the like.  */
+#define STATS_PREFIX "Stats: "
+
+  fprintf (file, STATS_PREFIX "linker version: %s\n", BFD_VERSION_STRING);
+
+  /* No \n at the end of the string as ctime() provides its own.  */
+  fprintf (file, STATS_PREFIX "linker started: %s", ctime (start));
+
+  /* We include the linker command line arguments since
+     they can be hard to track down by other means.  */
+  if (argv != NULL)
+    {
+      fprintf (file, STATS_PREFIX "args: ");
+      for (i = 0; argv[i] != NULL; i++)
+       fprintf (file, "%s ", argv[i]);
+      fprintf (file, "\n\n");  /* Blank line to separate the args from the stats.  */
+    }
+
+  /* All of this song and dance with the column_info struct and printf
+     formatting is so that we can have a nicely formated table with regular
+     column spacing, whilst allowing for the column headers to be translated,
+     and coping nicely with extra long strings or numbers.  */
+  struct column_info
+  {
+    const char * header;
+    const char * sub_header;
+    int          width;
+    int          pad;
+  } columns[] =
+#define COLUMNS_FIELD(HEADER,SUBHEADER) \
+    { .header = N_( HEADER ), .sub_header = N_( SUBHEADER ) },
+  {
+    COLUMNS_FIELD ("phase", "name")
+    COLUMNS_FIELD ("cpu time", "(microsec)")
+#if defined (HAVE_GETRUSAGE)  
+    /* Note: keep these columns in sync with the
+       information recorded in ld_stop_phase().  */
+    COLUMNS_FIELD ("memory", "(KiB)")
+    COLUMNS_FIELD ("user time", "(seconds)")
+    COLUMNS_FIELD ("system time", "(seconds)")
+#endif
+  };
+
+#ifndef max
+#define max(A,B) ((A) < (B) ? (B) : (A))
+#endif
+
+  size_t maxwidth = 1;
+  for (i = 0; i < NUM_PHASES; i++)
+    maxwidth = max (maxwidth, strlen (phase_data[i].name));
+
+  fprintf (file, "%s", STATS_PREFIX);
+
+  for (i = 0; i < ARRAY_SIZE (columns); i++)
+    {
+      int padding;
+
+      if (i == 0)
+       columns[i].width = fprintf (file, "%-*s", (int) maxwidth, columns[i].header);
+      else
+       columns[i].width = fprintf (file, "%s", columns[i].header);
+      padding = columns[i].width % 8;
+      if (padding < 4)
+       padding = 4;
+      columns[i].pad = fprintf (file, "%*c", padding, ' ');
+    }
+
+  fprintf (file, "\n");
+
+  int bias = 0;
+#define COLUMN_ENTRY(VAL, FORMAT, N)                                   \
+  do                                                                   \
+    {                                                                  \
+      int l;                                                           \
+                                                                       \
+      if (N == 0)                                                      \
+       l = fprintf (file, "%-*" FORMAT, columns[N].width, VAL);        \
+      else                                                             \
+       l = fprintf (file, "%*" FORMAT, columns[N].width - bias, VAL);  \
+      bias = 0;                                                                \
+      if (l < columns[N].width)                                                \
+       l = columns[N].pad;                                             \
+      else if (l < columns[N].width + columns[N].pad)                  \
+       l = columns[N].pad - (l - columns[N].width);                    \
+      else                                                             \
+       {                                                               \
+         bias = l - (columns[N].width + columns[N].pad);               \
+         l = 0;                                                        \
+       }                                                               \
+      if (l)                                                           \
+       fprintf (file, "%*c", l, ' ');                                  \
+    }                                                                  \
+  while (0)
+
+  fprintf (file, "%s", STATS_PREFIX);
+
+  for (i = 0; i < ARRAY_SIZE (columns); i++)
+    COLUMN_ENTRY (columns[i].sub_header, "s", i);
+
+  fprintf (file, "\n");
+
+  for (i = 0; i < NUM_PHASES; i++)
+    {
+      struct ld_phase_data * pd = phase_data + i;
+      /* This should not be needed...  */      
+      const char * name = pd->name ? pd->name : "<unnamed>";
+
+      if (pd->broken)
+       {
+         fprintf (file, "%s %s: %s",
+                  STATS_PREFIX, name, _("WARNING: Data is unreliable!\n"));
+         continue;
+       }
+
+      fprintf (file, "%s", STATS_PREFIX);
+
+      /* Care must be taken to keep the lines below in sync with
+        entries in the columns_info array.
+        FIXME: There ought to be a better way to do this...  */
+      COLUMN_ENTRY (name, "s", 0);
+      COLUMN_ENTRY (pd->duration, "ld", 1);
+#if defined (HAVE_GETRUSAGE)
+      COLUMN_ENTRY (pd->use.ru_maxrss, "ld", 2);
+      COLUMN_ENTRY (pd->use.ru_utime.tv_sec, "ld", 3);
+      COLUMN_ENTRY (pd->use.ru_stime.tv_sec, "ld", 4);
+#endif
+      fprintf (file, "\n");
+    }
+
+  fflush (file);
+}
+
 int
 main (int argc, char **argv)
 {
   char *emulation;
   long start_time = get_run_time ();
+  time_t start_seconds = time (NULL);
 
 #ifdef HAVE_LC_MESSAGES
   setlocale (LC_MESSAGES, "");
@@ -286,7 +589,23 @@ main (int argc, char **argv)
   program_name = argv[0];
   xmalloc_set_program_name (program_name);
 
+  /* Check the LD_STATS environment variable before parsing the command line
+     so that the --stats option, if used, can override the environment variable.  */
+  char * stats_filename;
+  if ((stats_filename = getenv ("LD_STATS")) != NULL)
+    {
+      if (ISPRINT (stats_filename[0]))
+       config.stats_filename = stats_filename;
+      else
+       config.stats_filename = "-";
+      config.stats = true;
+    }
+
+  ld_start_phase (PHASE_ALL);
+  ld_start_phase (PHASE_PARSE);
+  
   expandargv (&argc, &argv);
+  char ** saved_argv = dupargv (argv);
 
   if (bfd_init () != BFD_INIT_MAGIC)
     fatal (_("%P: fatal error: libbfd ABI mismatch\n"));
@@ -404,11 +723,17 @@ main (int argc, char **argv)
   if (config.hash_table_size != 0)
     bfd_hash_set_default_size (config.hash_table_size);
 
+  ld_stop_phase (PHASE_PARSE);
+  
 #if BFD_SUPPORTS_PLUGINS
+  ld_start_phase (PHASE_PLUGINS);
   /* Now all the plugin arguments have been gathered, we can load them.  */
   plugin_load_plugins ();
+  ld_stop_phase (PHASE_PLUGINS);
 #endif /* BFD_SUPPORTS_PLUGINS */
 
+  ld_start_phase (PHASE_PARSE);
+
   ldemul_set_symbols ();
 
   /* If we have not already opened and parsed a linker script,
@@ -531,7 +856,31 @@ main (int argc, char **argv)
       link_info.has_map_file = true;
     }
 
+  if (config.stats_filename != NULL)
+    {
+      if (config.map_filename != NULL
+         && strcmp (config.stats_filename, config.map_filename) == 0)
+       config.stats_file = NULL;
+      else if (strcmp (config.stats_filename, "-") == 0)
+       config.stats_file = stdout;
+      else
+       {
+         if (config.stats_filename[0] == '+')
+           config.stats_file = fopen (config.stats_filename + 1, "a");
+         else
+           config.stats_file = fopen (config.stats_filename, "w");
+
+         if (config.stats_file == NULL)
+           einfo ("%P: Warning: failed to open resource record file: %s\n",
+                  config.stats_filename);
+       }
+    }
+
+  ld_stop_phase (PHASE_PARSE);
+
+  ld_start_phase (PHASE_PROCESS);
   lang_process ();
+  ld_stop_phase (PHASE_PROCESS);
 
   /* Print error messages for any missing symbols, for any warning
      symbols, and possibly multiple definitions.  */
@@ -558,7 +907,11 @@ main (int argc, char **argv)
   link_info.output_bfd->flags
     |= flags & bfd_applicable_file_flags (link_info.output_bfd);
 
+
+  ld_start_phase (PHASE_WRITE);
   ldwrite ();
+  ld_stop_phase (PHASE_WRITE);
+
 
   if (config.map_file != NULL)
     lang_map ();
@@ -653,19 +1006,38 @@ main (int argc, char **argv)
   if (config.emit_gnu_object_only)
     cmdline_emit_object_only_section ();
 
+  ld_stop_phase (PHASE_ALL);
+
   if (config.stats)
     {
-      long run_time = get_run_time () - start_time;
+      report_phases (config.map_file, & start_seconds, saved_argv);
+
+      if (config.stats_filename)
+       {
+         report_phases (config.stats_file, & start_seconds, saved_argv);
+
+         if (config.stats_file != stdout && config.stats_file != stderr)
+           {
+             fclose (config.stats_file);
+             config.stats_file = NULL;
+           }
+       }
+      else /* This is for backwards compatibility.  */
+       {
+         long run_time = get_run_time () - start_time;
 
-      fflush (stdout);
-      fprintf (stderr, _("%s: total time in link: %ld.%06ld\n"),
-              program_name, run_time / 1000000, run_time % 1000000);
-      fflush (stderr);
+         fflush (stdout);
+         fprintf (stderr, _("%s: total time in link: %ld.%06ld\n"),
+                  program_name, run_time / 1000000, run_time % 1000000);
+         fflush (stderr);
+       }
     }
 
   /* Prevent ld_cleanup from deleting the output file.  */
   output_filename = NULL;
 
+  freeargv (saved_argv);
+
   xexit (0);
   return 0;
 }
@@ -942,8 +1314,11 @@ add_archive_element (struct bfd_link_info *info,
       && (!no_more_claiming
          || bfd_get_lto_type (abfd) != lto_fat_ir_object))
     {
+      ld_start_phase (PHASE_PLUGINS);
       /* We must offer this archive member to the plugins to claim.  */
       plugin_maybe_claim (input);
+      ld_stop_phase (PHASE_PLUGINS);
+
       if (input->flags.claimed)
        {
          if (no_more_claiming)
index 7de6e257ad0cd1958ee458a2a8001504926a0181..bde20465835a09c03af0d43023e6c96b5c7ca42e 100644 (file)
@@ -499,8 +499,10 @@ static const struct ld_option ld_options[] =
   { {"split-by-reloc", optional_argument, NULL, OPTION_SPLIT_BY_RELOC},
     '\0', N_("[=COUNT]"), N_("Split output sections every COUNT relocs"),
     TWO_DASHES },
-  { {"stats", no_argument, NULL, OPTION_STATS},
-    '\0', NULL, N_("Print memory usage statistics"), TWO_DASHES },
+  { {"stats", optional_argument, NULL, OPTION_STATS},
+    '\0', NULL, N_("Print resource usage statistics"), TWO_DASHES },
+  { {"no-stats", optional_argument, NULL, OPTION_NO_STATS},
+    '\0', NULL, N_("Do not print resource usage statistics"), TWO_DASHES },
   { {"target-help", no_argument, NULL, OPTION_TARGET_HELP},
     '\0', NULL, N_("Display target specific options"), TWO_DASHES },
   { {"task-link", required_argument, NULL, OPTION_TASK_LINK},
@@ -1412,6 +1414,17 @@ parse_args (unsigned argc, char **argv)
          break;
        case OPTION_STATS:
          config.stats = true;
+         if (optarg)
+           config.stats_filename = optarg;
+         else
+           {
+             config.stats_filename = NULL;
+             config.stats_file = stderr;
+           }
+         break;
+       case OPTION_NO_STATS:
+         config.stats = false;
+         config.stats_filename = NULL;
          break;
        case OPTION_NO_SYMBOLIC:
          opt_symbolic = symbolic_unset;
index 8dcb021c3f9b0769dc42d9f8071a90a1eee7b31a..deb46d3281da8c0b37fea09b1a31bd723b2788d7 100644 (file)
@@ -168,9 +168,9 @@ if [catch { set ofd [open "tmpdir/$test2.d" w] } x] {
     return
 }
 
-# too big for avr, d10v and msp
-# lack of fancy orphan section handling causes overlap on fr30 and iq2000
-# bfin and lm32 complain about relocations in read-only sections
+# Too big for avr, d10v and msp.
+# Lack of fancy orphan section handling causes overlap on fr30 and iq2000.
+# bfin and lm32 complain about relocations in read-only sections.
 if { ![istarget "d10v-*-*"]
      && ![istarget "avr-*-*"]
      && ![istarget "msp*-*-*"]
@@ -179,7 +179,13 @@ if { ![istarget "d10v-*-*"]
      && ![istarget "bfin-*-linux*"]
      && ![istarget "lm32-*-linux*"]
      && ![istarget "pru-*-*"] } {
+
+    # Create a 64ksec.d test control file...
+    
+    # List the input files.
     foreach sfile $sfiles { puts $ofd "#source: $sfile" }
+
+    # Add any needed linker command line options.
     if { [istarget spu*-*-*] } {
        puts $ofd "#ld: --local-store 0:0"
     } elseif { [istarget "i?86-*-linux*"] || [istarget "x86_64-*-linux*"] } {
@@ -187,10 +193,20 @@ if { ![istarget "d10v-*-*"]
     } else {
        puts $ofd "#ld:"
     }
-    #force z80 target to compile for eZ80 in ADL mode
+
+    # Enable the accumulation of internal linker statistics in a separate file.
+    # Enabled this way as you cannot have multiple #ld: options in a .d file.
+    # The + character causes the file to opened in append mode, so that multiple
+    # runs of this test will accumulate data over time.  Thus allowing regular
+    # testers to see changes in the performance of the linker.
+    puts $ofd "#ld_after_inputfiles: --stats=+tmpdir/$test2.stats"
+    
+    # Force z80 target to compile for eZ80 in ADL mode.
     if { [istarget "z80-*-*"] } then {
        puts $ofd "#as: -ez80-adl"
     }
+
+    # Add a test of the linked binary.
     puts $ofd "#readelf: -W -wN -Ss"
     puts $ofd "There are 660.. section headers.*:"
     puts $ofd "#..."
@@ -199,6 +215,7 @@ if { ![istarget "d10v-*-*"]
     puts $ofd "  \\\[65279\\\] \\.foo\\.\[0-9\]+ .*"
     puts $ofd "  \\\[65280\\\] \\.foo\\.\[0-9\]+ .*"
     puts $ofd "#..."
+
     if { [is_elf_unused_section_symbols ] } {
        puts $ofd " 660..: \[0-9a-f\]+\[ \]+0\[ \]+SECTION\[ \]+LOCAL\[ \]+DEFAULT\[ \]+660...*"
        puts $ofd "#..."
@@ -209,6 +226,7 @@ if { ![istarget "d10v-*-*"]
        puts $ofd " 66...: \[0-9a-f\]+\[ \]+0\[ \]+NOTYPE\[ \]+LOCAL\[ \]+DEFAULT\[ \]+660.. bar_66000$"
     }
     puts $ofd "#..."
+
     # Global symbols are not in "alphanumeric" order, so we just check
     # that the first and the last are present in any order (assuming no
     # duplicates).
@@ -217,9 +235,14 @@ if { ![istarget "d10v-*-*"]
     puts $ofd ".* (\[0-9\] foo_1|66... foo_66000)$"
     puts $ofd "#pass"
     close $ofd
+
+    # Now run the constructed test file.
     run_dump_test "tmpdir/$test2"
+
+    # Leave the test file around in case the user wants to examine it.
 }
 
+# Tidy up.
 for { set i 1 } { $i < $max_sec / $secs_per_file } { incr i } {
     catch "exec rm -f tmpdir/dump$i.o" status
 }
index 2291302ae30a16be3ddf90e9e475fbd37deb6791..776fed4357f60a384e141f799208fcd34c597252 100644 (file)
@@ -130,19 +130,38 @@ if { [is_elf_format] } {
              $IMAGE_BASE tmpdir/map-address.o \
              -Map=tmpdir/map-locals.map --print-map-locals"]} {
        fail $testname
-       return
-    }
 
-    if [is_remote host] then {
-       remote_upload host "tmpdir/map-locals.map"
-    }
+    } else {
 
-    # Some ELF targets do not preserve their local symbols.
-    setup_xfail "d30v-*-*" "dlx-*-*" "pj-*-*" "s12z-*-*" "xgate-*-*"
+       if [is_remote host] then {
+           remote_upload host "tmpdir/map-locals.map"
+       }
+
+       # Some ELF targets do not preserve their local symbols.
+       setup_xfail "d30v-*-*" "dlx-*-*" "pj-*-*" "s12z-*-*" "xgate-*-*"
     
+       if {[regexp_diff \
+                "tmpdir/map-locals.map" \
+                "$srcdir/$subdir/map-locals.d"]} {
+           fail $testname
+       } else {
+           pass $testname
+       }
+    }
+}
+
+set testname "map with resource usage"
+
+if {![ld_link $ld tmpdir/map-address \
+         "$LDFLAGS -T $srcdir/$subdir/map-address.t \
+          $IMAGE_BASE tmpdir/map-address.o \
+          -Map=tmpdir/map-locals.map \
+          --stats=tmpdir/map-stats.map"]} {
+    fail $testname
+} else {
     if {[regexp_diff \
-            "tmpdir/map-locals.map" \
-            "$srcdir/$subdir/map-locals.d"]} {
+            "tmpdir/map-stats.map" \
+            "$srcdir/$subdir/map-stats.d"]} {
        fail $testname
     } else {
        pass $testname
diff --git a/ld/testsuite/ld-scripts/map-stats.d b/ld/testsuite/ld-scripts/map-stats.d
new file mode 100644 (file)
index 0000000..ba9adf8
--- /dev/null
@@ -0,0 +1,5 @@
+#...
+Stats: phase.*
+Stats: name.*
+Stats: ALL.*
+#pass