forked from Imagelibrary/binutils-gdb
Add optional filename argument to the linker's --stats option, allowing extra resource use information to be reported.
This commit is contained in:
9
ld/NEWS
9
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.
|
||||
|
||||
|
||||
@@ -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
2
ld/configure
vendored
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
37
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;
|
||||
|
||||
80
ld/ld.texi
80
ld/ld.texi
@@ -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
|
||||
|
||||
|
||||
36
ld/ldlang.c
36
ld/ldlang.c
@@ -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");
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
385
ld/ldmain.c
385
ld/ldmain.c
@@ -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)
|
||||
|
||||
17
ld/lexsup.c
17
ld/lexsup.c
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
5
ld/testsuite/ld-scripts/map-stats.d
Normal file
5
ld/testsuite/ld-scripts/map-stats.d
Normal file
@@ -0,0 +1,5 @@
|
||||
#...
|
||||
Stats: phase.*
|
||||
Stats: name.*
|
||||
Stats: ALL.*
|
||||
#pass
|
||||
Reference in New Issue
Block a user