Improve the linker's --stats option to record memory use information provided by mallinfo().

This commit is contained in:
Nick Clifton
2026-01-07 10:45:22 +00:00
parent d81b2679c8
commit 7be6efd7d4
6 changed files with 132 additions and 31 deletions

View File

@@ -215,6 +215,12 @@
/* Define to 1 if you have the `waitpid' function. */
#undef HAVE_WAITPID
/* Define to 1 if you have the `mallinfo' function. */
#undef HAVE_MALLINFO
/* Define to 1 if you have the `mallinfo2' function. */
#undef HAVE_MALLINFO2
/* Define to 1 if you have the <windows.h> header file. */
#undef HAVE_WINDOWS_H

2
ld/configure vendored
View File

@@ -18670,7 +18670,7 @@ fi
done
for ac_func in close getrusage glob lseek mkstemp open realpath waitpid
for ac_func in close getrusage glob lseek mkstemp open realpath waitpid mallinfo mallinfo2
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

@@ -447,7 +447,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 getrusage glob lseek mkstemp open realpath waitpid)
AC_CHECK_FUNCS(close getrusage glob lseek mkstemp open realpath waitpid mallinfo mallinfo2)
BFD_BINARY_FOPEN

26
ld/ld.h
View File

@@ -336,20 +336,38 @@ typedef struct
/* An enumeration of the linker phases for which resource usage information
is recorded. PHASE_ALL is special as it covers the entire link process.
PHASE_DEBUG is special as it causes an instant resource report to be
displayed each time ld_stop_phase(PHASE_DEBUG) is called. If there has
been a previous ld_start_phase(PHASE_DEBUG) then the report just covers
the resource usage between the two calls. Otherwise it reports the
resource usage in total so far. Unfortunately the two types of use
cannot be mixed.
Note: ld_set_phase_name() can be used to change the name displayed when
PHASE_DEBUG is reported. Possibly helping to identify specific resource
reports. Typical code usage would look like this:
ld_start_phase (PHASE_DEBUG);
<code to be investigated>
ld_set_phase_name (PHASE_DEBUG, "description of code");
ld_stop_phase (PHASE_DEBUG);
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.
new phase overlaps with or is contained by other phases, but it must
not overlap with or contain itself.
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(). */
4. Add code to report the field in ld_main.c:report_phases().
*/
typedef enum
{
PHASE_ALL = 0,
@@ -360,12 +378,16 @@ typedef enum
PHASE_PROCESS,
PHASE_WRITE,
PHASE_DEBUG, /* Not a real phase. Used to help debug linker resource usage. */
NUM_PHASES /* This must be the last entry. */
}
ld_phase;
extern void ld_start_phase (ld_phase);
extern void ld_stop_phase (ld_phase);
/* Change the name of a phase. Only really useful for PHASE_DEBUG. */
extern void ld_set_phase_name (ld_phase, const char *);
extern ld_config_type config;

View File

@@ -2769,21 +2769,25 @@ linker's command line. Note: if both the environment variable and the
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
the @var{getrusage()} system library call is available then maximum
set size and the user and system run times as well. In addition if
the @var{mallinfo} or @var{mallinfo2} system library calls are
available then the total memory usage is reported,
The information is displayed for individual parts of the linking
process which are referred to as @emph{phases}. 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.
In addition the information is also reported for a special phase
called @emph{ALL} which covers the entire linking process.
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:
@@ -2792,15 +2796,15 @@ 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
Stats: phase cpu time rss user time system time memory
Stats: name (microsec) (KiB) (seconds) (seconds) (KiB)
Stats: ALL 390082 217740 0 0 25219440
Stats: ctf processing 12 0 0 0 0
Stats: string merge 1324 0 0 0 406544
Stats: parsing 349 288 0 0 119792
Stats: plugins 1 0 0 0 0
Stats: processing files 259616 214524 0 0 31569120
Stats: write 116493 0 0 0 48096
@end smallexample
@kindex --no-stats

View File

@@ -53,6 +53,10 @@
#include <sys/resource.h>
#endif
#if defined (HAVE_MALLINFO2) || defined (HAVE_MALLINFO)
#include <malloc.h>
#endif
#ifndef TARGET_SYSTEM_ROOT
#define TARGET_SYSTEM_ROOT ""
#endif
@@ -289,6 +293,11 @@ struct ld_phase_data
struct rusage begin;
struct rusage use;
#endif
#if defined (HAVE_MALLINFO2) || defined (HAVE_MALLINFO)
size_t begin_blks;
size_t used_blks;
#endif
};
static struct ld_phase_data phase_data [NUM_PHASES] =
@@ -300,8 +309,15 @@ static struct ld_phase_data phase_data [NUM_PHASES] =
[PHASE_PLUGINS] = { .name = "plugins" },
[PHASE_PROCESS] = { .name = "processing files" },
[PHASE_WRITE] = { .name = "write" },
[PHASE_DEBUG] = { .name = "debug" }
};
void
ld_set_phase_name (ld_phase phase, const char * name)
{
phase_data[phase].name = name ? name : "<unnamed>";
}
void
ld_start_phase (ld_phase phase)
{
@@ -345,6 +361,14 @@ ld_start_phase (ld_phase phase)
memcpy (& pd->begin, & usage, sizeof usage);
#endif
#if defined (HAVE_MALLINFO2)
struct mallinfo2 mi2 = mallinfo2 ();
pd->begin_blks = mi2.uordblks;
#elif defined (HAVE_MALLINFO)
struct mallinfo mi = mallinfo ();
pd->begin_blks = mi.uordblks;
#endif
}
void
@@ -354,10 +378,14 @@ ld_stop_phase (ld_phase phase)
if (!pd->started)
{
/* We set the broken flag to indicate that the data
recorded for this phase is inconsistent. */
pd->broken = true;
return;
/* It does not matter if the debug phase has not been started. */
if (phase != PHASE_DEBUG)
{
/* 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;
@@ -421,6 +449,33 @@ ld_stop_phase (ld_phase phase)
pd->use.ru_maxrss += usage.ru_maxrss - pd->begin.ru_maxrss;
}
#endif
#if defined (HAVE_MALLINFO2)
/* FIXME: How do we know if mallinfo2() has failed ? */
struct mallinfo2 mi2 = mallinfo2 ();
pd->used_blks += mi2.uordblks - pd->begin_blks;
#elif defined (HAVE_MALLINFO)
struct mallinfo mi = mallinfo ();
pd->used_blks += mi.uordblks - pd->begin_blks;
#endif
if (phase == PHASE_DEBUG)
{
/* FIXME: Should we report other resources as well ? */
/* FIXME: Can we integrate this code with report_phases() ? */
fprintf (stderr, "stats: %s: cpu time: %ld ", pd->name, pd->duration);
#if defined (HAVE_GETRUSAGE)
fprintf (stderr, "rss: %ld ", pd->use.ru_maxrss);
#endif
#if defined (HAVE_MALLINFO2) || defined (HAVE_MALLINFO)
fprintf (stderr, "memory: %ld", (long) pd->used_blks);
#endif
fprintf (stderr, "\n");
/* Reset the counters to zero. */
memset (((char *) pd) + sizeof (pd->name), 0, (sizeof (* pd)) - sizeof (pd->name));
}
}
static void
@@ -473,9 +528,12 @@ report_phases (FILE * file, time_t * start, char ** argv)
#if defined (HAVE_GETRUSAGE)
/* Note: keep these columns in sync with the
information recorded in ld_stop_phase(). */
COLUMNS_FIELD ("memory", "(KiB)")
COLUMNS_FIELD ("rss", "(KiB)")
COLUMNS_FIELD ("user time", "(seconds)")
COLUMNS_FIELD ("system time", "(seconds)")
#endif
#if defined (HAVE_MALLINFO2) || defined (HAVE_MALLINFO)
COLUMNS_FIELD ("memory", "(KiB)")
#endif
};
@@ -485,7 +543,12 @@ report_phases (FILE * file, time_t * start, char ** argv)
size_t maxwidth = 1;
for (i = 0; i < NUM_PHASES; i++)
maxwidth = max (maxwidth, strlen (phase_data[i].name));
{
struct ld_phase_data * pd = phase_data + i;
if (pd->name != NULL)
maxwidth = max (maxwidth, strlen (pd->name));
}
fprintf (file, "%s", STATS_PREFIX);
@@ -543,6 +606,9 @@ report_phases (FILE * file, time_t * start, char ** argv)
/* This should not be needed... */
const char * name = pd->name ? pd->name : "<unnamed>";
if (i == PHASE_DEBUG)
continue;
if (pd->broken)
{
fprintf (file, "%s %s: %s",
@@ -552,7 +618,7 @@ report_phases (FILE * file, time_t * start, char ** argv)
fprintf (file, "%s", STATS_PREFIX);
/* Care must be taken to keep the lines below in sync with
/* Care must be taken to keep the numbers 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);
@@ -561,6 +627,9 @@ report_phases (FILE * file, time_t * start, char ** argv)
COLUMN_ENTRY (pd->use.ru_maxrss, "ld", 2);
COLUMN_ENTRY ((int64_t) pd->use.ru_utime.tv_sec, PRId64, 3);
COLUMN_ENTRY ((int64_t) pd->use.ru_stime.tv_sec, PRId64, 4);
#endif
#if defined (HAVE_MALLINFO2) || defined (HAVE_MALLINFO)
COLUMN_ENTRY ((int64_t) pd->used_blks / 1024, PRId64, 5);
#endif
fprintf (file, "\n");
}