1/* Print size information from ELF file.
2   Copyright (C) 2000-2007,2009,2012 Red Hat, Inc.
3   This file is part of Red Hat elfutils.
4   Written by Ulrich Drepper <drepper@redhat.com>, 2000.
5
6   Red Hat elfutils is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 2 of the License.
9
10   Red Hat elfutils is distributed in the hope that it will be useful, but
11   WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   General Public License for more details.
14
15   You should have received a copy of the GNU General Public License along
16   with Red Hat elfutils; if not, write to the Free Software Foundation,
17   Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
18
19   Red Hat elfutils is an included package of the Open Invention Network.
20   An included package of the Open Invention Network is a package for which
21   Open Invention Network licensees cross-license their patents.  No patent
22   license is granted, either expressly or impliedly, by designation as an
23   included package.  Should you wish to participate in the Open Invention
24   Network licensing program, please visit www.openinventionnetwork.com
25   <http://www.openinventionnetwork.com>.  */
26
27#ifdef HAVE_CONFIG_H
28# include <config.h>
29#endif
30
31#include <argp.h>
32#include <error.h>
33#include <fcntl.h>
34#include <gelf.h>
35#include <inttypes.h>
36#include <libelf.h>
37#include <libintl.h>
38#include <locale.h>
39#include <mcheck.h>
40#include <stdbool.h>
41#include <stdio.h>
42#include <stdio_ext.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <sys/param.h>
47
48#include <system.h>
49
50
51/* Name and version of program.  */
52static void print_version (FILE *stream, struct argp_state *state);
53ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
54
55/* Bug report address.  */
56ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
57
58
59/* Values for the parameters which have no short form.  */
60#define OPT_FORMAT	0x100
61#define OPT_RADIX	0x101
62
63/* Definitions of arguments for argp functions.  */
64static const struct argp_option options[] =
65{
66  { NULL, 0, NULL, 0, N_("Output format:"), 0 },
67  { "format", OPT_FORMAT, "FORMAT", 0,
68    N_("Use the output format FORMAT.  FORMAT can be `bsd' or `sysv'.  "
69       "The default is `bsd'"), 0 },
70  { NULL, 'A', NULL, 0, N_("Same as `--format=sysv'"), 0 },
71  { NULL, 'B', NULL, 0, N_("Same as `--format=bsd'"), 0 },
72  { "radix", OPT_RADIX, "RADIX", 0, N_("Use RADIX for printing symbol values"),
73    0},
74  { NULL, 'd', NULL, 0, N_("Same as `--radix=10'"), 0 },
75  { NULL, 'o', NULL, 0, N_("Same as `--radix=8'"), 0 },
76  { NULL, 'x', NULL, 0, N_("Same as `--radix=16'"), 0 },
77  { NULL, 'f', NULL, 0,
78    N_("Similar to `--format=sysv' output but in one line"), 0 },
79
80  { NULL, 0, NULL, 0, N_("Output options:"), 0 },
81  { NULL, 'F', NULL, 0,
82    N_("Print size and permission flags for loadable segments"), 0 },
83  { "totals", 't', NULL, 0, N_("Display the total sizes (bsd only)"), 0 },
84  { NULL, 0, NULL, 0, NULL, 0 }
85};
86
87/* Short description of program.  */
88static const char doc[] = N_("\
89List section sizes of FILEs (a.out by default).");
90
91/* Strings for arguments in help texts.  */
92static const char args_doc[] = N_("[FILE...]");
93
94/* Prototype for option handler.  */
95static error_t parse_opt (int key, char *arg, struct argp_state *state);
96
97/* Data structure to communicate with argp functions.  */
98static struct argp argp =
99{
100  options, parse_opt, args_doc, doc, NULL, NULL, NULL
101};
102
103
104/* Print symbols in file named FNAME.  */
105static int process_file (const char *fname);
106
107/* Handle content of archive.  */
108static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname);
109
110/* Handle ELF file.  */
111static void handle_elf (Elf *elf, const char *fullname, const char *fname);
112
113/* Show total size.  */
114static void show_bsd_totals (void);
115
116#define INTERNAL_ERROR(fname) \
117  error (EXIT_FAILURE, 0, gettext ("%s: INTERNAL ERROR %d (%s-%s): %s"),      \
118	 fname, __LINE__, PACKAGE_VERSION, __DATE__, elf_errmsg (-1))
119
120
121/* User-selectable options.  */
122
123/* The selected output format.  */
124static enum
125{
126  format_bsd = 0,
127  format_sysv,
128  format_sysv_one_line,
129  format_segments
130} format;
131
132/* Radix for printed numbers.  */
133static enum
134{
135  radix_decimal = 0,
136  radix_hex,
137  radix_octal
138} radix;
139
140
141/* Mapping of radix and binary class to length.  */
142static const int length_map[2][3] =
143{
144  [ELFCLASS32 - 1] =
145  {
146    [radix_hex] = 8,
147    [radix_decimal] = 10,
148    [radix_octal] = 11
149  },
150  [ELFCLASS64 - 1] =
151  {
152    [radix_hex] = 16,
153    [radix_decimal] = 20,
154    [radix_octal] = 22
155  }
156};
157
158/* True if total sizes should be printed.  */
159static bool totals;
160/* To print the total sizes in a reasonable format remember the higest
161   "class" of ELF binaries processed.  */
162static int totals_class;
163
164
165int
166main (int argc, char *argv[])
167{
168  int remaining;
169  int result = 0;
170
171  /* Make memory leak detection possible.  */
172  mtrace ();
173
174  /* We use no threads here which can interfere with handling a stream.  */
175  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
176  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
177  __fsetlocking (stderr, FSETLOCKING_BYCALLER);
178
179  /* Set locale.  */
180  setlocale (LC_ALL, "");
181
182  /* Make sure the message catalog can be found.  */
183  bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
184
185  /* Initialize the message catalog.  */
186  textdomain (PACKAGE_TARNAME);
187
188  /* Parse and process arguments.  */
189  argp_parse (&argp, argc, argv, 0, &remaining, NULL);
190
191
192  /* Tell the library which version we are expecting.  */
193  elf_version (EV_CURRENT);
194
195  if (remaining == argc)
196    /* The user didn't specify a name so we use a.out.  */
197    result = process_file ("a.out");
198  else
199    /* Process all the remaining files.  */
200    do
201      result |= process_file (argv[remaining]);
202    while (++remaining < argc);
203
204  /* Print the total sizes but only if the output format is BSD and at
205     least one file has been correctly read (i.e., we recognized the
206     class).  */
207  if (totals && format == format_bsd && totals_class != 0)
208    show_bsd_totals ();
209
210  return result;
211}
212
213
214/* Print the version information.  */
215static void
216print_version (FILE *stream, struct argp_state *state __attribute__ ((unused)))
217{
218  fprintf (stream, "size (%s) %s\n", PACKAGE_NAME, PACKAGE_VERSION);
219  fprintf (stream, gettext ("\
220Copyright (C) %s Red Hat, Inc.\n\
221This is free software; see the source for copying conditions.  There is NO\n\
222warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
223"), "2012");
224  fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
225}
226
227
228/* Handle program arguments.  */
229static error_t
230parse_opt (int key, char *arg,
231	   struct argp_state *state __attribute__ ((unused)))
232{
233  switch (key)
234    {
235    case 'd':
236      radix = radix_decimal;
237      break;
238
239    case 'f':
240      format = format_sysv_one_line;
241      break;
242
243    case 'o':
244      radix = radix_octal;
245      break;
246
247    case 'x':
248      radix = radix_hex;
249      break;
250
251    case 'A':
252      format = format_sysv;
253      break;
254
255    case 'B':
256      format = format_bsd;
257      break;
258
259    case 'F':
260      format = format_segments;
261      break;
262
263    case OPT_FORMAT:
264      if (strcmp (arg, "bsd") == 0 || strcmp (arg, "berkeley") == 0)
265	format = format_bsd;
266      else if (likely (strcmp (arg, "sysv") == 0))
267	format = format_sysv;
268      else
269	error (EXIT_FAILURE, 0, gettext ("Invalid format: %s"), arg);
270      break;
271
272    case OPT_RADIX:
273      if (strcmp (arg, "x") == 0 || strcmp (arg, "16") == 0)
274	radix = radix_hex;
275      else if (strcmp (arg, "d") == 0 || strcmp (arg, "10") == 0)
276	radix = radix_decimal;
277      else if (strcmp (arg, "o") == 0 || strcmp (arg, "8") == 0)
278	radix = radix_octal;
279      else
280	error (EXIT_FAILURE, 0, gettext ("Invalid radix: %s"), arg);
281      break;
282
283    case 't':
284      totals = true;
285      break;
286
287    default:
288      return ARGP_ERR_UNKNOWN;
289    }
290  return 0;
291}
292
293
294/* Open the file and determine the type.  */
295static int
296process_file (const char *fname)
297{
298  int fd = open (fname, O_RDONLY);
299  if (unlikely (fd == -1))
300    {
301      error (0, errno, gettext ("cannot open '%s'"), fname);
302      return 1;
303    }
304
305  /* Now get the ELF descriptor.  */
306  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
307  if (likely (elf != NULL))
308    {
309      if (elf_kind (elf) == ELF_K_ELF)
310	{
311	  handle_elf (elf, NULL, fname);
312
313	  if (unlikely (elf_end (elf) != 0))
314	    INTERNAL_ERROR (fname);
315
316	  if (unlikely (close (fd) != 0))
317	    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
318
319	  return 0;
320	}
321      else if (likely (elf_kind (elf) == ELF_K_AR))
322	{
323	  int result = handle_ar (fd, elf, NULL, fname);
324
325	  if (unlikely  (close (fd) != 0))
326	    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
327
328	  return result;
329	}
330
331      /* We cannot handle this type.  Close the descriptor anyway.  */
332      if (unlikely (elf_end (elf) != 0))
333	INTERNAL_ERROR (fname);
334    }
335
336  if (unlikely (close (fd) != 0))
337    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
338
339  error (0, 0, gettext ("%s: file format not recognized"), fname);
340
341  return 1;
342}
343
344
345/* Print the BSD-style header.  This is done exactly once.  */
346static void
347print_header (Elf *elf)
348{
349  static int done;
350
351  if (! done)
352    {
353      int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
354      int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
355
356      printf ("%*s %*s %*s %*s %*s %s\n",
357	      ddigits - 2, sgettext ("bsd|text"),
358	      ddigits - 2, sgettext ("bsd|data"),
359	      ddigits - 2, sgettext ("bsd|bss"),
360	      ddigits - 2, sgettext ("bsd|dec"),
361	      xdigits - 2, sgettext ("bsd|hex"),
362	      sgettext ("bsd|filename"));
363
364      done = 1;
365    }
366}
367
368
369static int
370handle_ar (int fd, Elf *elf, const char *prefix, const char *fname)
371{
372  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
373  size_t fname_len = strlen (fname) + 1;
374  char new_prefix[prefix_len + 1 + fname_len];
375  char *cp = new_prefix;
376
377  /* Create the full name of the file.  */
378  if (prefix != NULL)
379    {
380      cp = mempcpy (cp, prefix, prefix_len);
381      *cp++ = ':';
382    }
383  memcpy (cp, fname, fname_len);
384
385  /* Process all the files contained in the archive.  */
386  int result = 0;
387  Elf *subelf;
388  Elf_Cmd cmd = ELF_C_READ_MMAP;
389  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
390    {
391      /* The the header for this element.  */
392      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
393
394      if (elf_kind (subelf) == ELF_K_ELF)
395	handle_elf (subelf, new_prefix, arhdr->ar_name);
396      else if (likely (elf_kind (subelf) == ELF_K_AR))
397	result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name);
398      /* else signal error??? */
399
400      /* Get next archive element.  */
401      cmd = elf_next (subelf);
402      if (unlikely (elf_end (subelf) != 0))
403	INTERNAL_ERROR (fname);
404    }
405
406  if (unlikely (elf_end (elf) != 0))
407    INTERNAL_ERROR (fname);
408
409  return result;
410}
411
412
413/* Show sizes in SysV format.  */
414static void
415show_sysv (Elf *elf, const char *prefix, const char *fname,
416	   const char *fullname)
417{
418  int maxlen = 10;
419  const int digits = length_map[gelf_getclass (elf) - 1][radix];
420
421  /* Get the section header string table index.  */
422  size_t shstrndx;
423  if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
424    error (EXIT_FAILURE, 0,
425	   gettext ("cannot get section header string table index"));
426
427  /* First round over the sections: determine the longest section name.  */
428  Elf_Scn *scn = NULL;
429  while ((scn = elf_nextscn (elf, scn)) != NULL)
430    {
431      GElf_Shdr shdr_mem;
432      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
433
434      if (shdr == NULL)
435	INTERNAL_ERROR (fullname);
436
437      /* Ignore all sections which are not used at runtime.  */
438      if ((shdr->sh_flags & SHF_ALLOC) != 0)
439	maxlen = MAX (maxlen,
440		      (int) strlen (elf_strptr (elf, shstrndx,
441						shdr->sh_name)));
442    }
443
444  fputs_unlocked (fname, stdout);
445  if (prefix != NULL)
446    printf (gettext (" (ex %s)"), prefix);
447  printf (":\n%-*s %*s %*s\n",
448	  maxlen, sgettext ("sysv|section"),
449	  digits - 2, sgettext ("sysv|size"),
450	  digits, sgettext ("sysv|addr"));
451
452  const char *fmtstr;
453  if (radix == radix_hex)
454    fmtstr = "%-*s %*" PRIx64 " %*" PRIx64 "\n";
455  else if (radix == radix_decimal)
456    fmtstr = "%-*s %*" PRId64 " %*" PRId64 "\n";
457  else
458    fmtstr = "%-*s %*" PRIo64 " %*" PRIo64 "\n";
459
460  /* Iterate over all sections.  */
461  GElf_Off total = 0;
462  while ((scn = elf_nextscn (elf, scn)) != NULL)
463    {
464      GElf_Shdr shdr_mem;
465      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
466
467      /* Ignore all sections which are not used at runtime.  */
468      if ((shdr->sh_flags & SHF_ALLOC) != 0)
469	{
470	  printf (fmtstr,
471		  maxlen, elf_strptr (elf, shstrndx, shdr->sh_name),
472		  digits - 2, shdr->sh_size,
473		  digits, shdr->sh_addr);
474
475	  total += shdr->sh_size;
476	}
477    }
478
479  if (radix == radix_hex)
480    printf ("%-*s %*" PRIx64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
481	    digits - 2, total);
482  else if (radix == radix_decimal)
483    printf ("%-*s %*" PRId64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
484	    digits - 2, total);
485  else
486    printf ("%-*s %*" PRIo64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
487	    digits - 2, total);
488}
489
490
491/* Show sizes in SysV format in one line.  */
492static void
493show_sysv_one_line (Elf *elf)
494{
495  /* Get the section header string table index.  */
496  size_t shstrndx;
497  if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
498    error (EXIT_FAILURE, 0,
499	   gettext ("cannot get section header string table index"));
500
501  const char *fmtstr;
502  if (radix == radix_hex)
503    fmtstr = "%" PRIx64 "(%s)";
504  else if (radix == radix_decimal)
505    fmtstr = "%" PRId64 "(%s)";
506  else
507    fmtstr = "%" PRIo64 "(%s)";
508
509  /* Iterate over all sections.  */
510  GElf_Off total = 0;
511  bool first = true;
512  Elf_Scn *scn = NULL;
513  while ((scn = elf_nextscn (elf, scn)) != NULL)
514    {
515      GElf_Shdr shdr_mem;
516      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
517
518      /* Ignore all sections which are not used at runtime.  */
519      if ((shdr->sh_flags & SHF_ALLOC) == 0)
520	continue;
521
522      if (! first)
523	fputs_unlocked (" + ", stdout);
524      first = false;
525
526      printf (fmtstr, shdr->sh_size,
527	      elf_strptr (elf, shstrndx, shdr->sh_name));
528
529      total += shdr->sh_size;
530    }
531
532  if (radix == radix_hex)
533    printf (" = %#" PRIx64 "\n", total);
534  else if (radix == radix_decimal)
535    printf (" = %" PRId64 "\n", total);
536  else
537    printf (" = %" PRIo64 "\n", total);
538}
539
540
541/* Variables to add up the sizes of all files.  */
542static uintmax_t total_textsize;
543static uintmax_t total_datasize;
544static uintmax_t total_bsssize;
545
546
547/* Show sizes in BSD format.  */
548static void
549show_bsd (Elf *elf, const char *prefix, const char *fname,
550	  const char *fullname)
551{
552  GElf_Off textsize = 0;
553  GElf_Off datasize = 0;
554  GElf_Off bsssize = 0;
555  const int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
556  const int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
557
558  /* Iterate over all sections.  */
559  Elf_Scn *scn = NULL;
560  while ((scn = elf_nextscn (elf, scn)) != NULL)
561    {
562      GElf_Shdr shdr_mem;
563      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
564
565      if (shdr == NULL)
566	INTERNAL_ERROR (fullname);
567
568      /* Ignore all sections which are not marked as loaded.  */
569      if ((shdr->sh_flags & SHF_ALLOC) == 0)
570	continue;
571
572      if ((shdr->sh_flags & SHF_WRITE) == 0)
573	textsize += shdr->sh_size;
574      else if (shdr->sh_type == SHT_NOBITS)
575	bsssize += shdr->sh_size;
576      else
577	datasize += shdr->sh_size;
578    }
579
580  printf ("%*" PRId64 " %*" PRId64 " %*" PRId64 " %*" PRId64 " %*"
581	  PRIx64 " %s",
582	  ddigits - 2, textsize,
583	  ddigits - 2, datasize,
584	  ddigits - 2, bsssize,
585	  ddigits - 2, textsize + datasize + bsssize,
586	  xdigits - 2, textsize + datasize + bsssize,
587	  fname);
588  if (prefix != NULL)
589    printf (gettext (" (ex %s)"), prefix);
590  fputs_unlocked ("\n", stdout);
591
592  total_textsize += textsize;
593  total_datasize += datasize;
594  total_bsssize += bsssize;
595
596  totals_class = MAX (totals_class, gelf_getclass (elf));
597}
598
599
600/* Show total size.  */
601static void
602show_bsd_totals (void)
603{
604  int ddigits = length_map[totals_class - 1][radix_decimal];
605  int xdigits = length_map[totals_class - 1][radix_hex];
606
607  printf ("%*" PRIuMAX " %*" PRIuMAX " %*" PRIuMAX " %*" PRIuMAX " %*"
608	  PRIxMAX " %s",
609	  ddigits - 2, total_textsize,
610	  ddigits - 2, total_datasize,
611	  ddigits - 2, total_bsssize,
612	  ddigits - 2, total_textsize + total_datasize + total_bsssize,
613	  xdigits - 2, total_textsize + total_datasize + total_bsssize,
614	  gettext ("(TOTALS)\n"));
615}
616
617
618/* Show size and permission of loadable segments.  */
619static void
620show_segments (Elf *elf, const char *fullname)
621{
622  GElf_Ehdr ehdr_mem;
623  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
624  if (ehdr == NULL)
625    INTERNAL_ERROR (fullname);
626
627  GElf_Off total = 0;
628  bool first = true;
629  for (size_t cnt = 0; cnt < ehdr->e_phnum; ++cnt)
630    {
631      GElf_Phdr phdr_mem;
632      GElf_Phdr *phdr;
633
634      phdr = gelf_getphdr (elf, cnt, &phdr_mem);
635      if (phdr == NULL)
636	INTERNAL_ERROR (fullname);
637
638      if (phdr->p_type != PT_LOAD)
639	/* Only load segments.  */
640	continue;
641
642      if (! first)
643	fputs_unlocked (" + ", stdout);
644      first = false;
645
646      printf (radix == radix_hex ? "%" PRIx64 "(%c%c%c)"
647	      : (radix == radix_decimal ? "%" PRId64 "(%c%c%c)"
648		 : "%" PRIo64 "(%c%c%c)"),
649	      phdr->p_memsz,
650	      (phdr->p_flags & PF_R) == 0 ? '-' : 'r',
651	      (phdr->p_flags & PF_W) == 0 ? '-' : 'w',
652	      (phdr->p_flags & PF_X) == 0 ? '-' : 'x');
653
654      total += phdr->p_memsz;
655    }
656
657  if (radix == radix_hex)
658    printf (" = %#" PRIx64 "\n", total);
659  else if (radix == radix_decimal)
660    printf (" = %" PRId64 "\n", total);
661  else
662    printf (" = %" PRIo64 "\n", total);
663}
664
665
666static void
667handle_elf (Elf *elf, const char *prefix, const char *fname)
668{
669  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
670  size_t fname_len = strlen (fname) + 1;
671  char fullname[prefix_len + 1 + fname_len];
672  char *cp = fullname;
673
674  /* Create the full name of the file.  */
675  if (prefix != NULL)
676    {
677      cp = mempcpy (cp, prefix, prefix_len);
678      *cp++ = ':';
679    }
680  memcpy (cp, fname, fname_len);
681
682  if (format == format_sysv)
683    show_sysv (elf, prefix, fname, fullname);
684  else if (format == format_sysv_one_line)
685    show_sysv_one_line (elf);
686  else if (format == format_segments)
687    show_segments (elf, fullname);
688  else
689    {
690      print_header (elf);
691
692      show_bsd (elf, prefix, fname, fullname);
693    }
694}
695
696
697#include "debugpred.h"
698