1/* Print size information from ELF file.
2   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 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);
53void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
54
55/* Bug report address.  */
56const char *argp_program_bug_address = 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"), "2008");
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	return handle_ar (fd, elf, NULL, fname);
323
324      /* We cannot handle this type.  Close the descriptor anyway.  */
325      if (unlikely (elf_end (elf) != 0))
326	INTERNAL_ERROR (fname);
327    }
328
329  error (0, 0, gettext ("%s: file format not recognized"), fname);
330
331  return 1;
332}
333
334
335/* Print the BSD-style header.  This is done exactly once.  */
336static void
337print_header (Elf *elf)
338{
339  static int done;
340
341  if (! done)
342    {
343      int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
344      int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
345
346      printf ("%*s %*s %*s %*s %*s %s\n",
347	      ddigits - 2, sgettext ("bsd|text"),
348	      ddigits - 2, sgettext ("bsd|data"),
349	      ddigits - 2, sgettext ("bsd|bss"),
350	      ddigits - 2, sgettext ("bsd|dec"),
351	      xdigits - 2, sgettext ("bsd|hex"),
352	      sgettext ("bsd|filename"));
353
354      done = 1;
355    }
356}
357
358
359static int
360handle_ar (int fd, Elf *elf, const char *prefix, const char *fname)
361{
362  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
363  size_t fname_len = strlen (fname) + 1;
364  char new_prefix[prefix_len + 1 + fname_len];
365  char *cp = new_prefix;
366
367  /* Create the full name of the file.  */
368  if (prefix != NULL)
369    {
370      cp = mempcpy (cp, prefix, prefix_len);
371      *cp++ = ':';
372    }
373  memcpy (cp, fname, fname_len);
374
375  /* Process all the files contained in the archive.  */
376  int result = 0;
377  Elf *subelf;
378  Elf_Cmd cmd = ELF_C_READ_MMAP;
379  while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
380    {
381      /* The the header for this element.  */
382      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
383
384      if (elf_kind (subelf) == ELF_K_ELF)
385	handle_elf (subelf, new_prefix, arhdr->ar_name);
386      else if (likely (elf_kind (subelf) == ELF_K_AR))
387	result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name);
388      /* else signal error??? */
389
390      /* Get next archive element.  */
391      cmd = elf_next (subelf);
392      if (unlikely (elf_end (subelf) != 0))
393	INTERNAL_ERROR (fname);
394    }
395
396  if (unlikely (elf_end (elf) != 0))
397    INTERNAL_ERROR (fname);
398
399  if (unlikely  (close (fd) != 0))
400    error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
401
402  return result;
403}
404
405
406/* Show sizes in SysV format.  */
407static void
408show_sysv (Elf *elf, const char *prefix, const char *fname,
409	   const char *fullname)
410{
411  int maxlen = 10;
412  const int digits = length_map[gelf_getclass (elf) - 1][radix];
413
414  /* Get the section header string table index.  */
415  size_t shstrndx;
416  if (unlikely (elf_getshstrndx (elf, &shstrndx) < 0))
417    error (EXIT_FAILURE, 0,
418	   gettext ("cannot get section header string table index"));
419
420  /* First round over the sections: determine the longest section name.  */
421  Elf_Scn *scn = NULL;
422  while ((scn = elf_nextscn (elf, scn)) != NULL)
423    {
424      GElf_Shdr shdr_mem;
425      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
426
427      if (shdr == NULL)
428	INTERNAL_ERROR (fullname);
429
430      /* Ignore all sections which are not used at runtime.  */
431      if ((shdr->sh_flags & SHF_ALLOC) != 0)
432	maxlen = MAX (maxlen,
433		      (int) strlen (elf_strptr (elf, shstrndx,
434						shdr->sh_name)));
435    }
436
437  fputs_unlocked (fname, stdout);
438  if (prefix != NULL)
439    printf (gettext (" (ex %s)"), prefix);
440  printf (":\n%-*s %*s %*s\n",
441	  maxlen, sgettext ("sysv|section"),
442	  digits - 2, sgettext ("sysv|size"),
443	  digits, sgettext ("sysv|addr"));
444
445  const char *fmtstr;
446  if (radix == radix_hex)
447    fmtstr = "%-*s %*" PRIx64 " %*" PRIx64 "\n";
448  else if (radix == radix_decimal)
449    fmtstr = "%-*s %*" PRId64 " %*" PRId64 "\n";
450  else
451    fmtstr = "%-*s %*" PRIo64 " %*" PRIo64 "\n";
452
453  /* Iterate over all sections.  */
454  GElf_Off total = 0;
455  while ((scn = elf_nextscn (elf, scn)) != NULL)
456    {
457      GElf_Shdr shdr_mem;
458      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
459
460      /* Ignore all sections which are not used at runtime.  */
461      if ((shdr->sh_flags & SHF_ALLOC) != 0)
462	{
463	  printf (fmtstr,
464		  maxlen, elf_strptr (elf, shstrndx, shdr->sh_name),
465		  digits - 2, shdr->sh_size,
466		  digits, shdr->sh_addr);
467
468	  total += shdr->sh_size;
469	}
470    }
471
472  if (radix == radix_hex)
473    printf ("%-*s %*" PRIx64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
474	    digits - 2, total);
475  else if (radix == radix_decimal)
476    printf ("%-*s %*" PRId64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
477	    digits - 2, total);
478  else
479    printf ("%-*s %*" PRIo64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
480	    digits - 2, total);
481}
482
483
484/* Show sizes in SysV format in one line.  */
485static void
486show_sysv_one_line (Elf *elf)
487{
488  /* Get the section header string table index.  */
489  size_t shstrndx;
490  if (unlikely (elf_getshstrndx (elf, &shstrndx) < 0))
491    error (EXIT_FAILURE, 0,
492	   gettext ("cannot get section header string table index"));
493
494  const char *fmtstr;
495  if (radix == radix_hex)
496    fmtstr = "%" PRIx64 "(%s)";
497  else if (radix == radix_decimal)
498    fmtstr = "%" PRId64 "(%s)";
499  else
500    fmtstr = "%" PRIo64 "(%s)";
501
502  /* Iterate over all sections.  */
503  GElf_Off total = 0;
504  bool first = true;
505  Elf_Scn *scn = NULL;
506  while ((scn = elf_nextscn (elf, scn)) != NULL)
507    {
508      GElf_Shdr shdr_mem;
509      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
510
511      /* Ignore all sections which are not used at runtime.  */
512      if ((shdr->sh_flags & SHF_ALLOC) == 0)
513	continue;
514
515      if (! first)
516	fputs_unlocked (" + ", stdout);
517      first = false;
518
519      printf (fmtstr, shdr->sh_size,
520	      elf_strptr (elf, shstrndx, shdr->sh_name));
521
522      total += shdr->sh_size;
523    }
524
525  if (radix == radix_hex)
526    printf (" = %#" PRIx64 "\n", total);
527  else if (radix == radix_decimal)
528    printf (" = %" PRId64 "\n", total);
529  else
530    printf (" = %" PRIo64 "\n", total);
531}
532
533
534/* Variables to add up the sizes of all files.  */
535static uintmax_t total_textsize;
536static uintmax_t total_datasize;
537static uintmax_t total_bsssize;
538
539
540/* Show sizes in BSD format.  */
541static void
542show_bsd (Elf *elf, const char *prefix, const char *fname,
543	  const char *fullname)
544{
545  GElf_Off textsize = 0;
546  GElf_Off datasize = 0;
547  GElf_Off bsssize = 0;
548  const int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
549  const int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
550
551  /* Iterate over all sections.  */
552  Elf_Scn *scn = NULL;
553  while ((scn = elf_nextscn (elf, scn)) != NULL)
554    {
555      GElf_Shdr shdr_mem;
556      GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
557
558      if (shdr == NULL)
559	INTERNAL_ERROR (fullname);
560
561      /* Ignore all sections which are not marked as loaded.  */
562      if ((shdr->sh_flags & SHF_ALLOC) == 0)
563	continue;
564
565      if ((shdr->sh_flags & SHF_WRITE) == 0)
566	textsize += shdr->sh_size;
567      else if (shdr->sh_type == SHT_NOBITS)
568	bsssize += shdr->sh_size;
569      else
570	datasize += shdr->sh_size;
571    }
572
573  printf ("%*" PRId64 " %*" PRId64 " %*" PRId64 " %*" PRId64 " %*"
574	  PRIx64 " %s",
575	  ddigits - 2, textsize,
576	  ddigits - 2, datasize,
577	  ddigits - 2, bsssize,
578	  ddigits - 2, textsize + datasize + bsssize,
579	  xdigits - 2, textsize + datasize + bsssize,
580	  fname);
581  if (prefix != NULL)
582    printf (gettext (" (ex %s)"), prefix);
583  fputs_unlocked ("\n", stdout);
584
585  total_textsize += textsize;
586  total_datasize += datasize;
587  total_bsssize += bsssize;
588
589  totals_class = MAX (totals_class, gelf_getclass (elf));
590}
591
592
593/* Show total size.  */
594static void
595show_bsd_totals (void)
596{
597  int ddigits = length_map[totals_class - 1][radix_decimal];
598  int xdigits = length_map[totals_class - 1][radix_hex];
599
600  printf ("%*" PRIuMAX " %*" PRIuMAX " %*" PRIuMAX " %*" PRIuMAX " %*"
601	  PRIxMAX " %s",
602	  ddigits - 2, total_textsize,
603	  ddigits - 2, total_datasize,
604	  ddigits - 2, total_bsssize,
605	  ddigits - 2, total_textsize + total_datasize + total_bsssize,
606	  xdigits - 2, total_textsize + total_datasize + total_bsssize,
607	  gettext ("(TOTALS)\n"));
608}
609
610
611/* Show size and permission of loadable segments.  */
612static void
613show_segments (Elf *elf, const char *fullname)
614{
615  GElf_Ehdr ehdr_mem;
616  GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
617  if (ehdr == NULL)
618    INTERNAL_ERROR (fullname);
619
620  GElf_Off total = 0;
621  bool first = true;
622  for (size_t cnt = 0; cnt < ehdr->e_phnum; ++cnt)
623    {
624      GElf_Phdr phdr_mem;
625      GElf_Phdr *phdr;
626
627      phdr = gelf_getphdr (elf, cnt, &phdr_mem);
628      if (phdr == NULL)
629	INTERNAL_ERROR (fullname);
630
631      if (phdr->p_type != PT_LOAD)
632	/* Only load segments.  */
633	continue;
634
635      if (! first)
636	fputs_unlocked (" + ", stdout);
637      first = false;
638
639      printf (radix == radix_hex ? "%" PRIx64 "(%c%c%c)"
640	      : (radix == radix_decimal ? "%" PRId64 "(%c%c%c)"
641		 : "%" PRIo64 "(%c%c%c)"),
642	      phdr->p_memsz,
643	      (phdr->p_flags & PF_R) == 0 ? '-' : 'r',
644	      (phdr->p_flags & PF_W) == 0 ? '-' : 'w',
645	      (phdr->p_flags & PF_X) == 0 ? '-' : 'x');
646
647      total += phdr->p_memsz;
648    }
649
650  if (radix == radix_hex)
651    printf (" = %#" PRIx64 "\n", total);
652  else if (radix == radix_decimal)
653    printf (" = %" PRId64 "\n", total);
654  else
655    printf (" = %" PRIo64 "\n", total);
656}
657
658
659static void
660handle_elf (Elf *elf, const char *prefix, const char *fname)
661{
662  size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
663  size_t fname_len = strlen (fname) + 1;
664  char fullname[prefix_len + 1 + fname_len];
665  char *cp = fullname;
666
667  /* Create the full name of the file.  */
668  if (prefix != NULL)
669    {
670      cp = mempcpy (cp, prefix, prefix_len);
671      *cp++ = ':';
672    }
673  memcpy (cp, fname, fname_len);
674
675  if (format == format_sysv)
676    show_sysv (elf, prefix, fname, fullname);
677  else if (format == format_sysv_one_line)
678    show_sysv_one_line (elf);
679  else if (format == format_segments)
680    show_segments (elf, fullname);
681  else
682    {
683      print_header (elf);
684
685      show_bsd (elf, prefix, fname, fullname);
686    }
687}
688
689
690#include "debugpred.h"
691