Home | History | Annotate | Line # | Download | only in binutils
      1 /* size.c -- report size of various sections of an executable file.
      2    Copyright (C) 1991-2025 Free Software Foundation, Inc.
      3 
      4    This file is part of GNU Binutils.
      5 
      6    This program is free software; you can redistribute it and/or modify
      7    it under the terms of the GNU General Public License as published by
      8    the Free Software Foundation; either version 3 of the License, or
      9    (at your option) any later version.
     10 
     11    This program is distributed in the hope that it will be useful,
     12    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14    GNU General Public License for more details.
     15 
     16    You should have received a copy of the GNU General Public License
     17    along with this program; if not, write to the Free Software
     18    Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
     19    MA 02110-1301, USA.  */
     20 
     21 /* Extensions/incompatibilities:
     23    o - BSD output has filenames at the end.
     24    o - BSD output can appear in different radicies.
     25    o - SysV output has less redundant whitespace.  Filename comes at end.
     26    o - SysV output doesn't show VMA which is always the same as the PMA.
     27    o - We also handle core files.
     28    o - We also handle archives.
     29    If you write shell scripts which manipulate this info then you may be
     30    out of luck; there's no --compatibility or --pedantic option.  */
     31 
     32 #include "sysdep.h"
     33 #include "bfd.h"
     34 #include "libiberty.h"
     35 #include "getopt.h"
     36 #include "bucomm.h"
     37 
     38 #ifndef BSD_DEFAULT
     39 #define BSD_DEFAULT 1
     40 #endif
     41 
     42 /* Program options.  */
     43 
     44 static enum
     45   {
     46     decimal, octal, hex
     47   }
     48 radix = decimal;
     49 
     50 /* Select the desired output format.  */
     51 enum output_format
     52   {
     53    FORMAT_BERKLEY,
     54    FORMAT_SYSV,
     55    FORMAT_GNU
     56   };
     57 static enum output_format selected_output_format =
     58 #if BSD_DEFAULT
     59   FORMAT_BERKLEY
     60 #else
     61   FORMAT_SYSV
     62 #endif
     63   ;
     64 
     65 static int show_version = 0;
     66 static int show_help = 0;
     67 static int show_totals = 0;
     68 static int show_common = 0;
     69 
     70 static bfd_size_type common_size;
     71 static bfd_size_type total_bsssize;
     72 static bfd_size_type total_datasize;
     73 static bfd_size_type total_textsize;
     74 
     75 /* Program exit status.  */
     76 static int return_code = 0;
     77 
     78 static char *target = NULL;
     79 
     80 /* Forward declarations.  */
     81 
     82 static void display_file (char *);
     83 static void rprint_number (int, bfd_size_type);
     84 static void print_sizes (bfd * file);
     85 
     86 static void
     88 usage (FILE *stream, int status)
     89 {
     90   fprintf (stream, _("Usage: %s [option(s)] [file(s)]\n"), program_name);
     91   fprintf (stream, _(" Displays the sizes of sections inside binary files\n"));
     92   fprintf (stream, _(" If no input file(s) are specified, a.out is assumed\n"));
     93   fprintf (stream, _(" The options are:\n\
     94   -A|-B|-G  --format={sysv|berkeley|gnu}  Select output style (default is %s)\n\
     95   -o|-d|-x  --radix={8|10|16}         Display numbers in octal, decimal or hex\n\
     96   -t        --totals                  Display the total sizes (Berkeley only)\n\
     97   -f                                  Ignored.\n\
     98             --common                  Display total size for *COM* syms\n\
     99             --target=<bfdname>        Set the binary file format\n\
    100             @<file>                   Read options from <file>\n\
    101   -h|-H|-?  --help                    Display this information\n\
    102   -v|-V     --version                 Display the program's version\n\
    103 \n"),
    104 #if BSD_DEFAULT
    105   "berkeley"
    106 #else
    107   "sysv"
    108 #endif
    109 );
    110   list_supported_targets (program_name, stream);
    111   if (REPORT_BUGS_TO[0] && status == 0)
    112     fprintf (stream, _("Report bugs to %s\n"), REPORT_BUGS_TO);
    113   exit (status);
    114 }
    115 
    116 #define OPTION_FORMAT (200)
    117 #define OPTION_RADIX (OPTION_FORMAT + 1)
    118 #define OPTION_TARGET (OPTION_RADIX + 1)
    119 
    120 static struct option long_options[] =
    121 {
    122   {"common", no_argument, &show_common, 1},
    123   {"format", required_argument, 0, OPTION_FORMAT},
    124   {"radix", required_argument, 0, OPTION_RADIX},
    125   {"target", required_argument, 0, OPTION_TARGET},
    126   {"totals", no_argument, &show_totals, 1},
    127   {"version", no_argument, &show_version, 1},
    128   {"help", no_argument, &show_help, 1},
    129   {0, no_argument, 0, 0}
    130 };
    131 
    132 int main (int, char **);
    133 
    134 int
    135 main (int argc, char **argv)
    136 {
    137   int temp;
    138   int c;
    139 
    140 #ifdef HAVE_LC_MESSAGES
    141   setlocale (LC_MESSAGES, "");
    142 #endif
    143   setlocale (LC_CTYPE, "");
    144   bindtextdomain (PACKAGE, LOCALEDIR);
    145   textdomain (PACKAGE);
    146 
    147   program_name = *argv;
    148   xmalloc_set_program_name (program_name);
    149   bfd_set_error_program_name (program_name);
    150 
    151   expandargv (&argc, &argv);
    152 
    153   if (bfd_init () != BFD_INIT_MAGIC)
    154     fatal (_("fatal error: libbfd ABI mismatch"));
    155   set_default_bfd_target ();
    156 
    157   while ((c = getopt_long (argc, argv, "ABGHhVvdfotx", long_options,
    158 			   (int *) 0)) != EOF)
    159     switch (c)
    160       {
    161       case OPTION_FORMAT:
    162 	switch (*optarg)
    163 	  {
    164 	  case 'B':
    165 	  case 'b':
    166 	    selected_output_format = FORMAT_BERKLEY;
    167 	    break;
    168 	  case 'S':
    169 	  case 's':
    170 	    selected_output_format = FORMAT_SYSV;
    171 	    break;
    172 	  case 'G':
    173 	  case 'g':
    174 	    selected_output_format = FORMAT_GNU;
    175 	    break;
    176 	  default:
    177 	    non_fatal (_("invalid argument to --format: %s"), optarg);
    178 	    usage (stderr, 1);
    179 	  }
    180 	break;
    181 
    182       case OPTION_TARGET:
    183 	target = optarg;
    184 	break;
    185 
    186       case OPTION_RADIX:
    187 #ifdef ANSI_LIBRARIES
    188 	temp = strtol (optarg, NULL, 10);
    189 #else
    190 	temp = atol (optarg);
    191 #endif
    192 	switch (temp)
    193 	  {
    194 	  case 10:
    195 	    radix = decimal;
    196 	    break;
    197 	  case 8:
    198 	    radix = octal;
    199 	    break;
    200 	  case 16:
    201 	    radix = hex;
    202 	    break;
    203 	  default:
    204 	    non_fatal (_("Invalid radix: %s\n"), optarg);
    205 	    usage (stderr, 1);
    206 	  }
    207 	break;
    208 
    209       case 'A':
    210 	selected_output_format = FORMAT_SYSV;
    211 	break;
    212       case 'B':
    213 	selected_output_format = FORMAT_BERKLEY;
    214 	break;
    215       case 'G':
    216 	selected_output_format = FORMAT_GNU;
    217 	break;
    218       case 'v':
    219       case 'V':
    220 	show_version = 1;
    221 	break;
    222       case 'd':
    223 	radix = decimal;
    224 	break;
    225       case 'x':
    226 	radix = hex;
    227 	break;
    228       case 'o':
    229 	radix = octal;
    230 	break;
    231       case 't':
    232 	show_totals = 1;
    233 	break;
    234       case 'f': /* FIXME : For sysv68, `-f' means `full format', i.e.
    235 		   `[fname:] M(.text) + N(.data) + O(.bss) + P(.comment) = Q'
    236 		   where `fname: ' appears only if there are >= 2 input files,
    237 		   and M, N, O, P, Q are expressed in decimal by default,
    238 		   hexa or octal if requested by `-x' or `-o'.
    239 		   Just to make things interesting, Solaris also accepts -f,
    240 		   which prints out the size of each allocatable section, the
    241 		   name of the section, and the total of the section sizes.  */
    242 		/* For the moment, accept `-f' silently, and ignore it.  */
    243 	break;
    244       case 0:
    245 	break;
    246       case 'h':
    247       case 'H':
    248       case '?':
    249 	usage (stderr, 1);
    250       }
    251 
    252   if (show_version)
    253     print_version ("size");
    254   if (show_help)
    255     usage (stdout, 0);
    256 
    257   if (optind == argc)
    258     display_file ("a.out");
    259   else
    260     for (; optind < argc;)
    261       display_file (argv[optind++]);
    262 
    263   if (show_totals && (selected_output_format == FORMAT_BERKLEY
    264 		      || selected_output_format == FORMAT_GNU))
    265     {
    266       bfd_size_type total = total_textsize + total_datasize + total_bsssize;
    267       int col_width = (selected_output_format == FORMAT_BERKLEY) ? 7 : 10;
    268       char sep_char = (selected_output_format == FORMAT_BERKLEY) ? '\t' : ' ';
    269 
    270       rprint_number (col_width, total_textsize);
    271       putchar(sep_char);
    272       rprint_number (col_width, total_datasize);
    273       putchar(sep_char);
    274       rprint_number (col_width, total_bsssize);
    275       putchar(sep_char);
    276       if (selected_output_format == FORMAT_BERKLEY)
    277 	printf (((radix == octal) ? "%7lo\t%7lx" : "%7lu\t%7lx"),
    278 		(unsigned long) total, (unsigned long) total);
    279       else
    280 	rprint_number (col_width, total);
    281       putchar(sep_char);
    282       fputs ("(TOTALS)\n", stdout);
    283     }
    284 
    285   return return_code;
    286 }
    287 
    288 /* Total size required for common symbols in ABFD.  */
    290 
    291 static void
    292 calculate_common_size (bfd *abfd)
    293 {
    294   asymbol **syms = NULL;
    295   long storage, symcount;
    296 
    297   common_size = 0;
    298   if ((bfd_get_file_flags (abfd) & (EXEC_P | DYNAMIC | HAS_SYMS)) != HAS_SYMS)
    299     return;
    300 
    301   storage = bfd_get_symtab_upper_bound (abfd);
    302   if (storage < 0)
    303     bfd_fatal (bfd_get_filename (abfd));
    304   if (storage)
    305     syms = (asymbol **) xmalloc (storage);
    306 
    307   symcount = bfd_canonicalize_symtab (abfd, syms);
    308   if (symcount < 0)
    309     bfd_fatal (bfd_get_filename (abfd));
    310 
    311   while (--symcount >= 0)
    312     {
    313       asymbol *sym = syms[symcount];
    314 
    315       if (bfd_is_com_section (sym->section)
    316 	  && (sym->flags & BSF_SECTION_SYM) == 0)
    317 	common_size += sym->value;
    318     }
    319   free (syms);
    320 }
    321 
    322 /* Display stats on file or archive member ABFD.  */
    323 
    324 static void
    325 display_bfd (bfd *abfd)
    326 {
    327   char **matching;
    328 
    329   if (bfd_check_format (abfd, bfd_archive))
    330     /* An archive within an archive.  */
    331     return;
    332 
    333   if (bfd_check_format_matches (abfd, bfd_object, &matching))
    334     {
    335       print_sizes (abfd);
    336       printf ("\n");
    337       return;
    338     }
    339 
    340   if (bfd_get_error () == bfd_error_file_ambiguously_recognized)
    341     {
    342       bfd_nonfatal (bfd_get_filename (abfd));
    343       list_matching_formats (matching);
    344       return_code = 3;
    345       return;
    346     }
    347 
    348   if (bfd_check_format_matches (abfd, bfd_core, &matching))
    349     {
    350       const char *core_cmd;
    351 
    352       print_sizes (abfd);
    353       fputs (" (core file", stdout);
    354 
    355       core_cmd = bfd_core_file_failing_command (abfd);
    356       if (core_cmd)
    357 	printf (" invoked as %s", core_cmd);
    358 
    359       puts (")\n");
    360       return;
    361     }
    362 
    363   bfd_nonfatal (bfd_get_filename (abfd));
    364 
    365   if (bfd_get_error () == bfd_error_file_ambiguously_recognized)
    366     list_matching_formats (matching);
    367 
    368   return_code = 3;
    369 }
    370 
    371 static void
    372 display_archive (bfd *file)
    373 {
    374   bfd *last_arfile = NULL;
    375   for (;;)
    376     {
    377       bfd *arfile = bfd_openr_next_archived_file (file, last_arfile);
    378       if (arfile == NULL
    379 	  || arfile == last_arfile)
    380 	{
    381 	  if (arfile != NULL)
    382 	    bfd_set_error (bfd_error_malformed_archive);
    383 	  if (bfd_get_error () != bfd_error_no_more_archived_files)
    384 	    {
    385 	      bfd_nonfatal (bfd_get_filename (file));
    386 	      return_code = 2;
    387 	    }
    388 	  break;
    389 	}
    390 
    391       if (last_arfile != NULL)
    392 	bfd_close (last_arfile);
    393 
    394       display_bfd (arfile);
    395       last_arfile = arfile;
    396     }
    397 
    398   if (last_arfile != NULL)
    399     bfd_close (last_arfile);
    400 }
    401 
    402 static void
    403 display_file (char *filename)
    404 {
    405   bfd *file;
    406 
    407   if (get_file_size (filename) < 1)
    408     {
    409       return_code = 1;
    410       return;
    411     }
    412 
    413   file = bfd_openr (filename, target);
    414   if (file == NULL)
    415     {
    416       bfd_nonfatal (filename);
    417       return_code = 1;
    418       return;
    419     }
    420 
    421   if (bfd_check_format (file, bfd_archive))
    422     display_archive (file);
    423   else
    424     display_bfd (file);
    425 
    426   if (!bfd_close (file))
    427     {
    428       bfd_nonfatal (filename);
    429       return_code = 1;
    430       return;
    431     }
    432 }
    433 
    434 static int
    436 size_number (bfd_size_type num)
    437 {
    438   char buffer[40];
    439 
    440   return sprintf (buffer, (radix == decimal ? "%" PRIu64
    441 			   : radix == octal ? "0%" PRIo64 : "0x%" PRIx64),
    442 		  (uint64_t) num);
    443 }
    444 
    445 static void
    446 rprint_number (int width, bfd_size_type num)
    447 {
    448   char buffer[40];
    449 
    450   sprintf (buffer, (radix == decimal ? "%" PRIu64
    451 		    : radix == octal ? "0%" PRIo64 : "0x%" PRIx64),
    452 	   (uint64_t) num);
    453 
    454   printf ("%*s", width, buffer);
    455 }
    456 
    457 static bfd_size_type bsssize;
    458 static bfd_size_type datasize;
    459 static bfd_size_type textsize;
    460 
    461 static void
    462 berkeley_or_gnu_sum (bfd *abfd ATTRIBUTE_UNUSED, sec_ptr sec,
    463 		     void *ignore ATTRIBUTE_UNUSED)
    464 {
    465   flagword flags;
    466   bfd_size_type size;
    467 
    468   flags = bfd_section_flags (sec);
    469   if ((flags & SEC_ALLOC) == 0)
    470     return;
    471 
    472   size = bfd_section_size (sec);
    473   if ((flags & SEC_CODE) != 0
    474       || (selected_output_format == FORMAT_BERKLEY
    475 	  && (flags & SEC_READONLY) != 0))
    476     textsize += size;
    477   else if ((flags & SEC_HAS_CONTENTS) != 0)
    478     datasize += size;
    479   else
    480     bsssize += size;
    481 }
    482 
    483 static void
    484 print_berkeley_or_gnu_format (bfd *abfd)
    485 {
    486   static int files_seen = 0;
    487   bfd_size_type total;
    488   int col_width = (selected_output_format == FORMAT_BERKLEY) ? 7 : 10;
    489   char sep_char = (selected_output_format == FORMAT_BERKLEY) ? '\t' : ' ';
    490 
    491   bsssize = 0;
    492   datasize = 0;
    493   textsize = 0;
    494 
    495   bfd_map_over_sections (abfd, berkeley_or_gnu_sum, NULL);
    496 
    497   bsssize += common_size;
    498   if (files_seen++ == 0)
    499     {
    500       if (selected_output_format == FORMAT_BERKLEY)
    501 	puts ((radix == octal) ? "   text\t   data\t    bss\t    oct\t    hex\tfilename" :
    502 	      "   text\t   data\t    bss\t    dec\t    hex\tfilename");
    503       else
    504 	puts ("      text       data        bss      total filename");
    505     }
    506 
    507   total = textsize + datasize + bsssize;
    508 
    509   if (show_totals)
    510     {
    511       total_textsize += textsize;
    512       total_datasize += datasize;
    513       total_bsssize  += bsssize;
    514     }
    515 
    516   rprint_number (col_width, textsize);
    517   putchar (sep_char);
    518   rprint_number (col_width, datasize);
    519   putchar (sep_char);
    520   rprint_number (col_width, bsssize);
    521   putchar (sep_char);
    522 
    523   if (selected_output_format == FORMAT_BERKLEY)
    524     printf (((radix == octal) ? "%7lo\t%7lx" : "%7lu\t%7lx"),
    525 	    (unsigned long) total, (unsigned long) total);
    526   else
    527     rprint_number (col_width, total);
    528 
    529   putchar (sep_char);
    530   fputs (bfd_get_filename (abfd), stdout);
    531 
    532   if (abfd->my_archive)
    533     printf (" (ex %s)", bfd_get_filename (abfd->my_archive));
    534 }
    535 
    536 /* I REALLY miss lexical functions! */
    537 bfd_size_type svi_total = 0;
    538 bfd_vma svi_maxvma = 0;
    539 int svi_namelen = 0;
    540 int svi_vmalen = 0;
    541 int svi_sizelen = 0;
    542 
    543 static void
    544 sysv_internal_sizer (bfd *file ATTRIBUTE_UNUSED, sec_ptr sec,
    545 		     void *ignore ATTRIBUTE_UNUSED)
    546 {
    547   flagword flags = bfd_section_flags (sec);
    548   /* Exclude sections with no flags set.  This is to omit som spaces.  */
    549   if (flags == 0)
    550     return;
    551 
    552   if (   ! bfd_is_abs_section (sec)
    553       && ! bfd_is_com_section (sec)
    554       && ! bfd_is_und_section (sec))
    555     {
    556       bfd_size_type size = bfd_section_size (sec);
    557       int namelen = strlen (bfd_section_name (sec));
    558 
    559       if (namelen > svi_namelen)
    560 	svi_namelen = namelen;
    561 
    562       svi_total += size;
    563 
    564       if (bfd_section_vma (sec) > svi_maxvma)
    565 	svi_maxvma = bfd_section_vma (sec);
    566     }
    567 }
    568 
    569 static void
    570 sysv_one_line (const char *name, bfd_size_type size, bfd_vma vma)
    571 {
    572   printf ("%-*s   ", svi_namelen, name);
    573   rprint_number (svi_sizelen, size);
    574   printf ("   ");
    575   rprint_number (svi_vmalen, vma);
    576   printf ("\n");
    577 }
    578 
    579 static void
    580 sysv_internal_printer (bfd *file ATTRIBUTE_UNUSED, sec_ptr sec,
    581 		       void *ignore ATTRIBUTE_UNUSED)
    582 {
    583   flagword flags = bfd_section_flags (sec);
    584   if (flags == 0)
    585     return;
    586 
    587   if (   ! bfd_is_abs_section (sec)
    588       && ! bfd_is_com_section (sec)
    589       && ! bfd_is_und_section (sec))
    590     {
    591       bfd_size_type size = bfd_section_size (sec);
    592 
    593       svi_total += size;
    594 
    595       sysv_one_line (bfd_section_name (sec),
    596 		     size,
    597 		     bfd_section_vma (sec));
    598     }
    599 }
    600 
    601 static void
    602 print_sysv_format (bfd *file)
    603 {
    604   /* Size all of the columns.  */
    605   svi_total = 0;
    606   svi_maxvma = 0;
    607   svi_namelen = 0;
    608   bfd_map_over_sections (file, sysv_internal_sizer, NULL);
    609   if (show_common)
    610     {
    611       if (svi_namelen < (int) sizeof ("*COM*") - 1)
    612 	svi_namelen = sizeof ("*COM*") - 1;
    613       svi_total += common_size;
    614     }
    615 
    616   svi_vmalen = size_number ((bfd_size_type)svi_maxvma);
    617 
    618   if ((size_t) svi_vmalen < sizeof ("addr") - 1)
    619     svi_vmalen = sizeof ("addr")-1;
    620 
    621   svi_sizelen = size_number (svi_total);
    622   if ((size_t) svi_sizelen < sizeof ("size") - 1)
    623     svi_sizelen = sizeof ("size")-1;
    624 
    625   svi_total = 0;
    626   printf ("%s  ", bfd_get_filename (file));
    627 
    628   if (file->my_archive)
    629     printf (" (ex %s)", bfd_get_filename (file->my_archive));
    630 
    631   printf (":\n%-*s   %*s   %*s\n", svi_namelen, "section",
    632 	  svi_sizelen, "size", svi_vmalen, "addr");
    633 
    634   bfd_map_over_sections (file, sysv_internal_printer, NULL);
    635   if (show_common)
    636     {
    637       svi_total += common_size;
    638       sysv_one_line ("*COM*", common_size, 0);
    639     }
    640 
    641   printf ("%-*s   ", svi_namelen, "Total");
    642   rprint_number (svi_sizelen, svi_total);
    643   printf ("\n\n");
    644 }
    645 
    646 static void
    647 print_sizes (bfd *file)
    648 {
    649   if (show_common)
    650     calculate_common_size (file);
    651   if (selected_output_format == FORMAT_SYSV)
    652     print_sysv_format (file);
    653   else
    654     print_berkeley_or_gnu_format (file);
    655 }
    656