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.  */
44 static void print_version (FILE *stream, struct argp_state *state);
45 ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
46 
47 /* Bug report address.  */
48 ARGP_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.  */
56 static 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.  */
80 static const char doc[] = N_("\
81 List section sizes of FILEs (a.out by default).");
82 
83 /* Strings for arguments in help texts.  */
84 static const char args_doc[] = N_("[FILE...]");
85 
86 /* Prototype for option handler.  */
87 static error_t parse_opt (int key, char *arg, struct argp_state *state);
88 
89 /* Data structure to communicate with argp functions.  */
90 static struct argp argp =
91 {
92   options, parse_opt, args_doc, doc, NULL, NULL, NULL
93 };
94 
95 
96 /* Print symbols in file named FNAME.  */
97 static int process_file (const char *fname);
98 
99 /* Handle content of archive.  */
100 static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname);
101 
102 /* Handle ELF file.  */
103 static void handle_elf (Elf *elf, const char *fullname, const char *fname);
104 
105 /* Show total size.  */
106 static 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.  */
116 static 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.  */
125 static 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.  */
134 static 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.  */
151 static bool totals;
152 /* To print the total sizes in a reasonable format remember the higest
153    "class" of ELF binaries processed.  */
154 static int totals_class;
155 
156 
157 int
main(int argc,char * argv[])158 main (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.  */
207 static void
print_version(FILE * stream,struct argp_state * state)208 print_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 ("\
212 Copyright (C) %s Red Hat, Inc.\n\
213 This is free software; see the source for copying conditions.  There is NO\n\
214 warranty; 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.  */
221 static error_t
parse_opt(int key,char * arg,struct argp_state * state)222 parse_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.  */
287 static int
process_file(const char * fname)288 process_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.  */
338 static void
print_header(Elf * elf)339 print_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 
361 static int
handle_ar(int fd,Elf * elf,const char * prefix,const char * fname)362 handle_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.  */
406 static void
show_sysv(Elf * elf,const char * prefix,const char * fname,const char * fullname)407 show_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.  */
479 static void
show_sysv_one_line(Elf * elf)480 show_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.  */
523 static uintmax_t total_textsize;
524 static uintmax_t total_datasize;
525 static uintmax_t total_bsssize;
526 
527 
528 /* Show sizes in BSD format.  */
529 static void
show_bsd(Elf * elf,const char * prefix,const char * fname,const char * fullname)530 show_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.  */
582 static void
show_bsd_totals(void)583 show_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.  */
600 static void
show_segments(Elf * elf,const char * fullname)601 show_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 
646 static void
handle_elf(Elf * elf,const char * prefix,const char * fname)647 handle_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