From: Nick Clifton Date: Wed, 7 Jan 2026 10:45:22 +0000 (+0000) Subject: Improve the linker's --stats option to record memory use information provided by... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7be6efd7d4bf9eb93a876fffddb34ced3bceb546;p=thirdparty%2Fbinutils-gdb.git Improve the linker's --stats option to record memory use information provided by mallinfo(). --- diff --git a/ld/config.in b/ld/config.in index 81146b84bc3..d7a3ba0ed6f 100644 --- a/ld/config.in +++ b/ld/config.in @@ -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 header file. */ #undef HAVE_WINDOWS_H diff --git a/ld/configure b/ld/configure index f557d53b1c3..3dcd0aaca72 100755 --- a/ld/configure +++ b/ld/configure @@ -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" diff --git a/ld/configure.ac b/ld/configure.ac index 40ca00907e0..a0a1361a0eb 100644 --- a/ld/configure.ac +++ b/ld/configure.ac @@ -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 diff --git a/ld/ld.h b/ld/ld.h index 7ce4b7bef08..76825e4253c 100644 --- a/ld/ld.h +++ b/ld/ld.h @@ -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); + + 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; diff --git a/ld/ld.texi b/ld/ld.texi index 85272e4b6f6..1ec56c08337 100644 --- a/ld/ld.texi +++ b/ld/ld.texi @@ -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 diff --git a/ld/ldmain.c b/ld/ldmain.c index 3d1de65a736..a2711d230ea 100644 --- a/ld/ldmain.c +++ b/ld/ldmain.c @@ -53,6 +53,10 @@ #include #endif +#if defined (HAVE_MALLINFO2) || defined (HAVE_MALLINFO) +#include +#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 : ""; +} + 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 : ""; + 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"); }