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. */
52 static void print_version (FILE *stream, struct argp_state *state);
53 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
54
55 /* Bug report address. */
56 const 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. */
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 "), "2008");
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 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. */
336 static void
print_header(Elf * elf)337 print_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
359 static int
handle_ar(int fd,Elf * elf,const char * prefix,const char * fname)360 handle_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. */
407 static void
show_sysv(Elf * elf,const char * prefix,const char * fname,const char * fullname)408 show_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. */
485 static void
show_sysv_one_line(Elf * elf)486 show_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. */
535 static uintmax_t total_textsize;
536 static uintmax_t total_datasize;
537 static uintmax_t total_bsssize;
538
539
540 /* Show sizes in BSD format. */
541 static void
show_bsd(Elf * elf,const char * prefix,const char * fname,const char * fullname)542 show_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. */
594 static void
show_bsd_totals(void)595 show_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. */
612 static void
show_segments(Elf * elf,const char * fullname)613 show_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
659 static void
handle_elf(Elf * elf,const char * prefix,const char * fname)660 handle_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