]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Improve the linker's --stats option to record memory use information provided by...
authorNick Clifton <nickc@redhat.com>
Wed, 7 Jan 2026 10:45:22 +0000 (10:45 +0000)
committerNick Clifton <nickc@redhat.com>
Wed, 7 Jan 2026 10:45:22 +0000 (10:45 +0000)
ld/config.in
ld/configure
ld/configure.ac
ld/ld.h
ld/ld.texi
ld/ldmain.c

index 81146b84bc36dd1990a2b9ac43d68ee67c0eac58..d7a3ba0ed6f27e4ed713304a5665dbb3a9af0c37 100644 (file)
 /* 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
 
index f557d53b1c33c7a3b5258a52b2e198c3b16772fd..3dcd0aaca723f38e488a6f07bef15b017da18dfe 100755 (executable)
@@ -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"
index 40ca00907e0419695a4cf30f40ede9a03d058b1a..a0a1361a0eb92273bcf4f9ab9c0fbb1b74e814b3 100644 (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
 
diff --git a/ld/ld.h b/ld/ld.h
index 7ce4b7bef08d5dca680cae081d0d48aa1572b794..76825e4253c9aad7592ab99da0fdf63678d92e2c 100644 (file)
--- 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);
+       <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;
 
index 85272e4b6f6f877b7f61ceb4d966ca867976d095..1ec56c08337b9443f2374bf302ecda7398b53140 100644 (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
index 3d1de65a736955e4de46ae11bfc70c61a31a88be..a2711d230ea06d5576e697f15d26ecccc5c2e82b 100644 (file)
 #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");
     }