Add optional filename argument to the linker's --stats option, allowing extra resource use information to be reported.

This commit is contained in:
Nick Clifton
2025-04-02 10:56:16 +01:00
parent 5cb1689a7a
commit c4fce3ef29
14 changed files with 622 additions and 28 deletions

View File

@@ -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.

View File

@@ -122,6 +122,9 @@
/* 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

2
ld/configure vendored
View File

@@ -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"

View 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

View 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 ();

37
ld/ld.h
View File

@@ -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;

View 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

View 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");

View 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,

View File

@@ -21,6 +21,7 @@
#include "sysdep.h"
#include "bfd.h"
#include "bfdver.h"
#include "safe-ctype.h"
#include "libiberty.h"
#include "bfdlink.h"
@@ -51,6 +52,10 @@
#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);
fflush (stdout);
fprintf (stderr, _("%s: total time in link: %ld.%06ld\n"),
program_name, run_time / 1000000, run_time % 1000000);
fflush (stderr);
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);
}
}
/* 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)

View 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;

View 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
}

View 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

View File

@@ -0,0 +1,5 @@
#...
Stats: phase.*
Stats: name.*
Stats: ALL.*
#pass