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