1 /* Print size information from ELF file.
2 Copyright (C) 2000-2007,2009,2012 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. */
52 static void print_version (FILE *stream, struct argp_state *state);
53 ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
54
55 /* Bug report address. */
56 ARGP_PROGRAM_BUG_ADDRESS_DEF = 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. */
64 static 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. */
88 static const char doc[] = N_("\
89 List section sizes of FILEs (a.out by default).");
90
91 /* Strings for arguments in help texts. */
92 static const char args_doc[] = N_("[FILE...]");
93
94 /* Prototype for option handler. */
95 static error_t parse_opt (int key, char *arg, struct argp_state *state);
96
97 /* Data structure to communicate with argp functions. */
98 static struct argp argp =
99 {
100 options, parse_opt, args_doc, doc, NULL, NULL, NULL
101 };
102
103
104 /* Print symbols in file named FNAME. */
105 static int process_file (const char *fname);
106
107 /* Handle content of archive. */
108 static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname);
109
110 /* Handle ELF file. */
111 static void handle_elf (Elf *elf, const char *fullname, const char *fname);
112
113 /* Show total size. */
114 static 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. */
124 static 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. */
133 static 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. */
142 static 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. */
159 static bool totals;
160 /* To print the total sizes in a reasonable format remember the higest
161 "class" of ELF binaries processed. */
162 static int totals_class;
163
164
165 int
main(int argc,char * argv[])166 main (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. */
215 static void
print_version(FILE * stream,struct argp_state * state)216 print_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 ("\
220 Copyright (C) %s Red Hat, Inc.\n\
221 This is free software; see the source for copying conditions. There is NO\n\
222 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
223 "), "2012");
224 fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
225 }
226
227
228 /* Handle program arguments. */
229 static error_t
parse_opt(int key,char * arg,struct argp_state * state)230 parse_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. */
295 static int
process_file(const char * fname)296 process_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 {
323 int result = handle_ar (fd, elf, NULL, fname);
324
325 if (unlikely (close (fd) != 0))
326 error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
327
328 return result;
329 }
330
331 /* We cannot handle this type. Close the descriptor anyway. */
332 if (unlikely (elf_end (elf) != 0))
333 INTERNAL_ERROR (fname);
334 }
335
336 if (unlikely (close (fd) != 0))
337 error (EXIT_FAILURE, errno, gettext ("while closing '%s'"), fname);
338
339 error (0, 0, gettext ("%s: file format not recognized"), fname);
340
341 return 1;
342 }
343
344
345 /* Print the BSD-style header. This is done exactly once. */
346 static void
print_header(Elf * elf)347 print_header (Elf *elf)
348 {
349 static int done;
350
351 if (! done)
352 {
353 int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
354 int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
355
356 printf ("%*s %*s %*s %*s %*s %s\n",
357 ddigits - 2, sgettext ("bsd|text"),
358 ddigits - 2, sgettext ("bsd|data"),
359 ddigits - 2, sgettext ("bsd|bss"),
360 ddigits - 2, sgettext ("bsd|dec"),
361 xdigits - 2, sgettext ("bsd|hex"),
362 sgettext ("bsd|filename"));
363
364 done = 1;
365 }
366 }
367
368
369 static int
handle_ar(int fd,Elf * elf,const char * prefix,const char * fname)370 handle_ar (int fd, Elf *elf, const char *prefix, const char *fname)
371 {
372 size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
373 size_t fname_len = strlen (fname) + 1;
374 char new_prefix[prefix_len + 1 + fname_len];
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 int result = 0;
387 Elf *subelf;
388 Elf_Cmd cmd = ELF_C_READ_MMAP;
389 while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
390 {
391 /* The the header for this element. */
392 Elf_Arhdr *arhdr = elf_getarhdr (subelf);
393
394 if (elf_kind (subelf) == ELF_K_ELF)
395 handle_elf (subelf, new_prefix, arhdr->ar_name);
396 else if (likely (elf_kind (subelf) == ELF_K_AR))
397 result |= handle_ar (fd, subelf, new_prefix, arhdr->ar_name);
398 /* else signal error??? */
399
400 /* Get next archive element. */
401 cmd = elf_next (subelf);
402 if (unlikely (elf_end (subelf) != 0))
403 INTERNAL_ERROR (fname);
404 }
405
406 if (unlikely (elf_end (elf) != 0))
407 INTERNAL_ERROR (fname);
408
409 return result;
410 }
411
412
413 /* Show sizes in SysV format. */
414 static void
show_sysv(Elf * elf,const char * prefix,const char * fname,const char * fullname)415 show_sysv (Elf *elf, const char *prefix, const char *fname,
416 const char *fullname)
417 {
418 int maxlen = 10;
419 const int digits = length_map[gelf_getclass (elf) - 1][radix];
420
421 /* Get the section header string table index. */
422 size_t shstrndx;
423 if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
424 error (EXIT_FAILURE, 0,
425 gettext ("cannot get section header string table index"));
426
427 /* First round over the sections: determine the longest section name. */
428 Elf_Scn *scn = NULL;
429 while ((scn = elf_nextscn (elf, scn)) != NULL)
430 {
431 GElf_Shdr shdr_mem;
432 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
433
434 if (shdr == NULL)
435 INTERNAL_ERROR (fullname);
436
437 /* Ignore all sections which are not used at runtime. */
438 if ((shdr->sh_flags & SHF_ALLOC) != 0)
439 maxlen = MAX (maxlen,
440 (int) strlen (elf_strptr (elf, shstrndx,
441 shdr->sh_name)));
442 }
443
444 fputs_unlocked (fname, stdout);
445 if (prefix != NULL)
446 printf (gettext (" (ex %s)"), prefix);
447 printf (":\n%-*s %*s %*s\n",
448 maxlen, sgettext ("sysv|section"),
449 digits - 2, sgettext ("sysv|size"),
450 digits, sgettext ("sysv|addr"));
451
452 const char *fmtstr;
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 GElf_Off total = 0;
462 while ((scn = elf_nextscn (elf, scn)) != NULL)
463 {
464 GElf_Shdr shdr_mem;
465 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
466
467 /* Ignore all sections which are not used at runtime. */
468 if ((shdr->sh_flags & SHF_ALLOC) != 0)
469 {
470 printf (fmtstr,
471 maxlen, elf_strptr (elf, shstrndx, shdr->sh_name),
472 digits - 2, shdr->sh_size,
473 digits, shdr->sh_addr);
474
475 total += shdr->sh_size;
476 }
477 }
478
479 if (radix == radix_hex)
480 printf ("%-*s %*" PRIx64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
481 digits - 2, total);
482 else if (radix == radix_decimal)
483 printf ("%-*s %*" PRId64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
484 digits - 2, total);
485 else
486 printf ("%-*s %*" PRIo64 "\n\n\n", maxlen, sgettext ("sysv|Total"),
487 digits - 2, total);
488 }
489
490
491 /* Show sizes in SysV format in one line. */
492 static void
show_sysv_one_line(Elf * elf)493 show_sysv_one_line (Elf *elf)
494 {
495 /* Get the section header string table index. */
496 size_t shstrndx;
497 if (unlikely (elf_getshdrstrndx (elf, &shstrndx) < 0))
498 error (EXIT_FAILURE, 0,
499 gettext ("cannot get section header string table index"));
500
501 const char *fmtstr;
502 if (radix == radix_hex)
503 fmtstr = "%" PRIx64 "(%s)";
504 else if (radix == radix_decimal)
505 fmtstr = "%" PRId64 "(%s)";
506 else
507 fmtstr = "%" PRIo64 "(%s)";
508
509 /* Iterate over all sections. */
510 GElf_Off total = 0;
511 bool first = true;
512 Elf_Scn *scn = NULL;
513 while ((scn = elf_nextscn (elf, scn)) != NULL)
514 {
515 GElf_Shdr shdr_mem;
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 = false;
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. */
542 static uintmax_t total_textsize;
543 static uintmax_t total_datasize;
544 static uintmax_t total_bsssize;
545
546
547 /* Show sizes in BSD format. */
548 static void
show_bsd(Elf * elf,const char * prefix,const char * fname,const char * fullname)549 show_bsd (Elf *elf, const char *prefix, const char *fname,
550 const char *fullname)
551 {
552 GElf_Off textsize = 0;
553 GElf_Off datasize = 0;
554 GElf_Off bsssize = 0;
555 const int ddigits = length_map[gelf_getclass (elf) - 1][radix_decimal];
556 const int xdigits = length_map[gelf_getclass (elf) - 1][radix_hex];
557
558 /* Iterate over all sections. */
559 Elf_Scn *scn = NULL;
560 while ((scn = elf_nextscn (elf, scn)) != NULL)
561 {
562 GElf_Shdr shdr_mem;
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. */
601 static void
show_bsd_totals(void)602 show_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. */
619 static void
show_segments(Elf * elf,const char * fullname)620 show_segments (Elf *elf, const char *fullname)
621 {
622 GElf_Ehdr ehdr_mem;
623 GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
624 if (ehdr == NULL)
625 INTERNAL_ERROR (fullname);
626
627 GElf_Off total = 0;
628 bool first = true;
629 for (size_t cnt = 0; cnt < ehdr->e_phnum; ++cnt)
630 {
631 GElf_Phdr phdr_mem;
632 GElf_Phdr *phdr;
633
634 phdr = gelf_getphdr (elf, cnt, &phdr_mem);
635 if (phdr == NULL)
636 INTERNAL_ERROR (fullname);
637
638 if (phdr->p_type != PT_LOAD)
639 /* Only load segments. */
640 continue;
641
642 if (! first)
643 fputs_unlocked (" + ", stdout);
644 first = false;
645
646 printf (radix == radix_hex ? "%" PRIx64 "(%c%c%c)"
647 : (radix == radix_decimal ? "%" PRId64 "(%c%c%c)"
648 : "%" PRIo64 "(%c%c%c)"),
649 phdr->p_memsz,
650 (phdr->p_flags & PF_R) == 0 ? '-' : 'r',
651 (phdr->p_flags & PF_W) == 0 ? '-' : 'w',
652 (phdr->p_flags & PF_X) == 0 ? '-' : 'x');
653
654 total += phdr->p_memsz;
655 }
656
657 if (radix == radix_hex)
658 printf (" = %#" PRIx64 "\n", total);
659 else if (radix == radix_decimal)
660 printf (" = %" PRId64 "\n", total);
661 else
662 printf (" = %" PRIo64 "\n", total);
663 }
664
665
666 static void
handle_elf(Elf * elf,const char * prefix,const char * fname)667 handle_elf (Elf *elf, const char *prefix, const char *fname)
668 {
669 size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
670 size_t fname_len = strlen (fname) + 1;
671 char fullname[prefix_len + 1 + fname_len];
672 char *cp = fullname;
673
674 /* Create the full name of the file. */
675 if (prefix != NULL)
676 {
677 cp = mempcpy (cp, prefix, prefix_len);
678 *cp++ = ':';
679 }
680 memcpy (cp, fname, fname_len);
681
682 if (format == format_sysv)
683 show_sysv (elf, prefix, fname, fullname);
684 else if (format == format_sysv_one_line)
685 show_sysv_one_line (elf);
686 else if (format == format_segments)
687 show_segments (elf, fullname);
688 else
689 {
690 print_header (elf);
691
692 show_bsd (elf, prefix, fname, fullname);
693 }
694 }
695
696
697 #include "debugpred.h"
698