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