1 /* Print size information from ELF file. 2 Copyright (C) 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. 3 Written by Ulrich Drepper <drepper (at) 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 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 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 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 * 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 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 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 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 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 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 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 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 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 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