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 (at) 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 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 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 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 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 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 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 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 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 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 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 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 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