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. */
40 static void print_version (FILE *stream, struct argp_state *state);
41 void (*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. */
49 static 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. */
68 static const char doc[] = N_("\
69 List section sizes of FILEs (a.out by default).");
70
71 /* Strings for arguments in help texts. */
72 static const char args_doc[] = N_("[FILE...]");
73
74 /* Prototype for option handler. */
75 static error_t parse_opt (int key, char *arg, struct argp_state *state);
76
77 /* Function to print some extra text in the help message. */
78 static char *more_help (int key, const char *text, void *input);
79
80 /* Data structure to communicate with argp functions. */
81 static struct argp argp =
82 {
83 options, parse_opt, args_doc, doc, NULL, more_help
84 };
85
86
87 /* Print symbols in file named FNAME. */
88 static int process_file (const char *fname);
89
90 /* Handle content of archive. */
91 static int handle_ar (int fd, Elf *elf, const char *prefix, const char *fname);
92
93 /* Handle ELF file. */
94 static void handle_elf (Elf *elf, const char *fullname, const char *fname);
95
96 /* Show total size. */
97 static 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. */
107 static 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. */
116 static 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. */
125 static 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. */
142 static bool totals;
143 /* To print the total sizes in a reasonable format remember the higest
144 "class" of ELF binaries processed. */
145 static int totals_class;
146
147
148 int
main(int argc,char * argv[])149 main (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. */
198 static void
print_version(FILE * stream,struct argp_state * state)199 print_version (FILE *stream, struct argp_state *state)
200 {
201 fprintf (stream, "size (%s) %s\n", PACKAGE_NAME, VERSION);
202 fprintf (stream, gettext ("\
203 Copyright (C) %s Red Hat, Inc.\n\
204 This is free software; see the source for copying conditions. There is NO\n\
205 warranty; 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. */
212 static error_t
parse_opt(int key,char * arg,struct argp_state * state)213 parse_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
276 static char *
more_help(int key,const char * text,void * input)277 more_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
297 static int
process_file(const char * fname)298 process_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. */
343 static void
print_header(Elf * elf)344 print_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
366 static int
handle_ar(int fd,Elf * elf,const char * prefix,const char * fname)367 handle_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. */
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 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. */
490 static void
show_sysv_one_line(Elf * elf,const char * prefix,const char * fname,const char * fullname)491 show_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. */
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 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. */
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 * prefix,const char * fname,const char * fullname)620 show_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
670 static void
handle_elf(Elf * elf,const char * prefix,const char * fname)671 handle_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