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