| /* addr2line.c -- convert addresses to line number and function name | 
 |    Copyright (C) 1997-2024 Free Software Foundation, Inc. | 
 |    Contributed by Ulrich Lauther <Ulrich.Lauther@mchp.siemens.de> | 
 |  | 
 |    This file is part of GNU Binutils. | 
 |  | 
 |    This program is free software; you can redistribute it and/or modify | 
 |    it under the terms of the GNU General Public License as published by | 
 |    the Free Software Foundation; either version 3, or (at your option) | 
 |    any later version. | 
 |  | 
 |    This program is distributed in the hope that it will be useful, | 
 |    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |    GNU General Public License for more details. | 
 |  | 
 |    You should have received a copy of the GNU General Public License | 
 |    along with this program; if not, write to the Free Software | 
 |    Foundation, 51 Franklin Street - Fifth Floor, Boston, | 
 |    MA 02110-1301, USA.  */ | 
 |  | 
 |  | 
 | /* Derived from objdump.c and nm.c by Ulrich.Lauther@mchp.siemens.de | 
 |  | 
 |    Usage: | 
 |    addr2line [options] addr addr ... | 
 |    or | 
 |    addr2line [options] | 
 |  | 
 |    both forms write results to stdout, the second form reads addresses | 
 |    to be converted from stdin.  */ | 
 |  | 
 | #include "sysdep.h" | 
 | #include "bfd.h" | 
 | #include "getopt.h" | 
 | #include "libiberty.h" | 
 | #include "demangle.h" | 
 | #include "bucomm.h" | 
 | #include "elf-bfd.h" | 
 | #include "safe-ctype.h" | 
 |  | 
 | static bool unwind_inlines;	/* -i, unwind inlined functions. */ | 
 | static bool with_addresses;	/* -a, show addresses.  */ | 
 | static bool with_functions;	/* -f, show function names.  */ | 
 | static bool do_demangle;	/* -C, demangle names.  */ | 
 | static bool pretty_print;	/* -p, print on one line.  */ | 
 | static bool base_names;		/* -s, strip directory names.  */ | 
 |  | 
 | /* Flags passed to the name demangler.  */ | 
 | static int demangle_flags = DMGL_PARAMS | DMGL_ANSI; | 
 |  | 
 | static int naddr;		/* Number of addresses to process.  */ | 
 | static char **addr;		/* Hex addresses to process.  */ | 
 |  | 
 | static long symcount; | 
 | static asymbol **syms;		/* Symbol table.  */ | 
 |  | 
 | static struct option long_options[] = | 
 | { | 
 |   {"addresses", no_argument, NULL, 'a'}, | 
 |   {"basenames", no_argument, NULL, 's'}, | 
 |   {"demangle", optional_argument, NULL, 'C'}, | 
 |   {"exe", required_argument, NULL, 'e'}, | 
 |   {"functions", no_argument, NULL, 'f'}, | 
 |   {"inlines", no_argument, NULL, 'i'}, | 
 |   {"pretty-print", no_argument, NULL, 'p'}, | 
 |   {"recurse-limit", no_argument, NULL, 'R'}, | 
 |   {"recursion-limit", no_argument, NULL, 'R'},   | 
 |   {"no-recurse-limit", no_argument, NULL, 'r'}, | 
 |   {"no-recursion-limit", no_argument, NULL, 'r'},   | 
 |   {"section", required_argument, NULL, 'j'}, | 
 |   {"target", required_argument, NULL, 'b'}, | 
 |   {"help", no_argument, NULL, 'H'}, | 
 |   {"version", no_argument, NULL, 'V'}, | 
 |   {0, no_argument, 0, 0} | 
 | }; | 
 |  | 
 | static void usage (FILE *, int); | 
 | static void slurp_symtab (bfd *); | 
 | static void find_address_in_section (bfd *, asection *, void *); | 
 | static void find_offset_in_section (bfd *, asection *); | 
 | static void translate_addresses (bfd *, asection *); | 
 |  | 
 | /* Print a usage message to STREAM and exit with STATUS.  */ | 
 |  | 
 | static void | 
 | usage (FILE *stream, int status) | 
 | { | 
 |   fprintf (stream, _("Usage: %s [option(s)] [addr(s)]\n"), program_name); | 
 |   fprintf (stream, _(" Convert addresses into line number/file name pairs.\n")); | 
 |   fprintf (stream, _(" If no addresses are specified on the command line, they will be read from stdin\n")); | 
 |   fprintf (stream, _(" The options are:\n\ | 
 |   @<file>                Read options from <file>\n\ | 
 |   -a --addresses         Show addresses\n\ | 
 |   -b --target=<bfdname>  Set the binary file format\n\ | 
 |   -e --exe=<executable>  Set the input file name (default is a.out)\n\ | 
 |   -i --inlines           Unwind inlined functions\n\ | 
 |   -j --section=<name>    Read section-relative offsets instead of addresses\n\ | 
 |   -p --pretty-print      Make the output easier to read for humans\n\ | 
 |   -s --basenames         Strip directory names\n\ | 
 |   -f --functions         Show function names\n\ | 
 |   -C --demangle[=style]  Demangle function names\n\ | 
 |   -R --recurse-limit     Enable a limit on recursion whilst demangling.  [Default]\n\ | 
 |   -r --no-recurse-limit  Disable a limit on recursion whilst demangling\n\ | 
 |   -h --help              Display this information\n\ | 
 |   -v --version           Display the program's version\n\ | 
 | \n")); | 
 |  | 
 |   list_supported_targets (program_name, stream); | 
 |   if (REPORT_BUGS_TO[0] && status == 0) | 
 |     fprintf (stream, _("Report bugs to %s\n"), REPORT_BUGS_TO); | 
 |   exit (status); | 
 | } | 
 |  | 
 | /* Read in the symbol table.  */ | 
 |  | 
 | static void | 
 | slurp_symtab (bfd *abfd) | 
 | { | 
 |   long storage; | 
 |   bool dynamic = false; | 
 |  | 
 |   if ((bfd_get_file_flags (abfd) & HAS_SYMS) == 0) | 
 |     return; | 
 |  | 
 |   storage = bfd_get_symtab_upper_bound (abfd); | 
 |   if (storage == 0) | 
 |     { | 
 |       storage = bfd_get_dynamic_symtab_upper_bound (abfd); | 
 |       dynamic = true; | 
 |     } | 
 |   if (storage < 0) | 
 |     { | 
 |       bfd_nonfatal (bfd_get_filename (abfd)); | 
 |       return; | 
 |     } | 
 |  | 
 |   syms = (asymbol **) xmalloc (storage); | 
 |   if (dynamic) | 
 |     symcount = bfd_canonicalize_dynamic_symtab (abfd, syms); | 
 |   else | 
 |     symcount = bfd_canonicalize_symtab (abfd, syms); | 
 |   if (symcount < 0) | 
 |     bfd_nonfatal (bfd_get_filename (abfd)); | 
 |  | 
 |   /* If there are no symbols left after canonicalization and | 
 |      we have not tried the dynamic symbols then give them a go.  */ | 
 |   if (symcount == 0 | 
 |       && ! dynamic | 
 |       && (storage = bfd_get_dynamic_symtab_upper_bound (abfd)) > 0) | 
 |     { | 
 |       free (syms); | 
 |       syms = xmalloc (storage); | 
 |       symcount = bfd_canonicalize_dynamic_symtab (abfd, syms); | 
 |     } | 
 |  | 
 |   /* PR 17512: file: 2a1d3b5b. | 
 |      Do not pretend that we have some symbols when we don't.  */ | 
 |   if (symcount <= 0) | 
 |     { | 
 |       free (syms); | 
 |       syms = NULL; | 
 |     } | 
 | } | 
 |  | 
 | /* These global variables are used to pass information between | 
 |    translate_addresses and find_address_in_section.  */ | 
 |  | 
 | static bfd_vma pc; | 
 | static const char *filename; | 
 | static const char *functionname; | 
 | static unsigned int line; | 
 | static unsigned int discriminator; | 
 | static bool found; | 
 |  | 
 | /* Look for an address in a section.  This is called via | 
 |    bfd_map_over_sections.  */ | 
 |  | 
 | static void | 
 | find_address_in_section (bfd *abfd, asection *section, | 
 | 			 void *data ATTRIBUTE_UNUSED) | 
 | { | 
 |   bfd_vma vma; | 
 |   bfd_size_type size; | 
 |  | 
 |   if (found) | 
 |     return; | 
 |  | 
 |   if ((bfd_section_flags (section) & SEC_ALLOC) == 0) | 
 |     return; | 
 |  | 
 |   vma = bfd_section_vma (section); | 
 |   if (pc < vma) | 
 |     return; | 
 |  | 
 |   size = bfd_section_size (section); | 
 |   if (pc >= vma + size) | 
 |     return; | 
 |  | 
 |   found = bfd_find_nearest_line_discriminator (abfd, section, syms, pc - vma, | 
 |                                                &filename, &functionname, | 
 |                                                &line, &discriminator); | 
 | } | 
 |  | 
 | /* Look for an offset in a section.  This is directly called.  */ | 
 |  | 
 | static void | 
 | find_offset_in_section (bfd *abfd, asection *section) | 
 | { | 
 |   bfd_size_type size; | 
 |  | 
 |   if (found) | 
 |     return; | 
 |  | 
 |   if ((bfd_section_flags (section) & SEC_ALLOC) == 0) | 
 |     return; | 
 |  | 
 |   size = bfd_section_size (section); | 
 |   if (pc >= size) | 
 |     return; | 
 |  | 
 |   found = bfd_find_nearest_line_discriminator (abfd, section, syms, pc, | 
 |                                                &filename, &functionname, | 
 |                                                &line, &discriminator); | 
 | } | 
 |  | 
 | /* Lookup a symbol with offset in symbol table.  */ | 
 |  | 
 | static bfd_vma | 
 | lookup_symbol (bfd *abfd, char *sym, size_t offset) | 
 | { | 
 |   long i; | 
 |  | 
 |   for (i = 0; i < symcount; i++) | 
 |     { | 
 |       if (!strcmp (syms[i]->name, sym)) | 
 | 	return syms[i]->value + offset + bfd_asymbol_section (syms[i])->vma; | 
 |     } | 
 |   /* Try again mangled */ | 
 |   for (i = 0; i < symcount; i++) | 
 |     { | 
 |       char *d = bfd_demangle (abfd, syms[i]->name, demangle_flags); | 
 |       bool match = d && !strcmp (d, sym); | 
 |       free (d); | 
 |  | 
 |       if (match) | 
 | 	return syms[i]->value + offset + bfd_asymbol_section (syms[i])->vma; | 
 |     } | 
 |   return 0; | 
 | } | 
 |  | 
 | /* Split an symbol+offset expression. adr is modified.  */ | 
 |  | 
 | static bool | 
 | is_symbol (char *adr, char **symp, size_t *offset) | 
 | { | 
 |   char *end; | 
 |  | 
 |   while (ISSPACE (*adr)) | 
 |     adr++; | 
 |   if (ISDIGIT (*adr) || *adr == 0) | 
 |     return false; | 
 |   /* Could be either symbol or hex number. Check if it has +.  */ | 
 |   if (TOUPPER(*adr) >= 'A' && TOUPPER(*adr) <= 'F' && !strchr (adr, '+')) | 
 |     return false; | 
 |  | 
 |   *symp = adr; | 
 |   while (*adr && !ISSPACE (*adr) && *adr != '+') | 
 |     adr++; | 
 |   end = adr; | 
 |   while (ISSPACE (*adr)) | 
 |     adr++; | 
 |   *offset = 0; | 
 |   if (*adr == '+') | 
 |     { | 
 |       adr++; | 
 |       *offset = strtoul(adr, NULL, 0); | 
 |     } | 
 |   *end = 0; | 
 |   return true; | 
 | } | 
 |  | 
 | /* Read hexadecimal or symbolic with offset addresses from stdin, translate into | 
 |    file_name:line_number and optionally function name.  */ | 
 |  | 
 | static void | 
 | translate_addresses (bfd *abfd, asection *section) | 
 | { | 
 |   int read_stdin = (naddr == 0); | 
 |   char *adr; | 
 |   char addr_hex[100]; | 
 |   char *symp; | 
 |   size_t offset; | 
 |  | 
 |   for (;;) | 
 |     { | 
 |       if (read_stdin) | 
 | 	{ | 
 | 	  if (fgets (addr_hex, sizeof addr_hex, stdin) == NULL) | 
 | 	    break; | 
 | 	  adr = addr_hex; | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  if (naddr <= 0) | 
 | 	    break; | 
 | 	  --naddr; | 
 | 	  adr = *addr++; | 
 | 	} | 
 |  | 
 |       if (is_symbol (adr, &symp, &offset)) | 
 |         pc = lookup_symbol (abfd, symp, offset); | 
 |       else | 
 |         pc = bfd_scan_vma (adr, NULL, 16); | 
 |       if (bfd_get_flavour (abfd) == bfd_target_elf_flavour) | 
 | 	{ | 
 | 	  const struct elf_backend_data *bed = get_elf_backend_data (abfd); | 
 | 	  bfd_vma sign = (bfd_vma) 1 << (bed->s->arch_size - 1); | 
 |  | 
 | 	  pc &= (sign << 1) - 1; | 
 | 	  if (bed->sign_extend_vma) | 
 | 	    pc = (pc ^ sign) - sign; | 
 | 	} | 
 |  | 
 |       if (with_addresses) | 
 |         { | 
 |           printf ("0x"); | 
 |           bfd_printf_vma (abfd, pc); | 
 |  | 
 |           if (pretty_print) | 
 |             printf (": "); | 
 |           else | 
 |             printf ("\n"); | 
 |         } | 
 |  | 
 |       found = false; | 
 |       if (section) | 
 | 	find_offset_in_section (abfd, section); | 
 |       else | 
 | 	bfd_map_over_sections (abfd, find_address_in_section, NULL); | 
 |  | 
 |       if (! found) | 
 | 	{ | 
 | 	  if (with_functions) | 
 | 	    { | 
 | 	      if (pretty_print) | 
 | 		printf ("?? "); | 
 | 	      else | 
 | 		printf ("??\n"); | 
 | 	    } | 
 | 	  printf ("??:0\n"); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  while (1) | 
 |             { | 
 |               if (with_functions) | 
 |                 { | 
 |                   const char *name; | 
 |                   char *alloc = NULL; | 
 |  | 
 |                   name = functionname; | 
 |                   if (name == NULL || *name == '\0') | 
 |                     name = "??"; | 
 |                   else if (do_demangle) | 
 |                     { | 
 |                       alloc = bfd_demangle (abfd, name, demangle_flags); | 
 |                       if (alloc != NULL) | 
 |                         name = alloc; | 
 |                     } | 
 |  | 
 |                   printf ("%s", name); | 
 |                   if (pretty_print) | 
 | 		    /* Note for translators:  This printf is used to join the | 
 | 		       function name just printed above to the line number/ | 
 | 		       file name pair that is about to be printed below.  Eg: | 
 |  | 
 | 		         foo at 123:bar.c  */ | 
 |                     printf (_(" at ")); | 
 |                   else | 
 |                     printf ("\n"); | 
 |  | 
 | 		  free (alloc); | 
 |                 } | 
 |  | 
 |               if (base_names && filename != NULL) | 
 |                 { | 
 |                   char *h; | 
 |  | 
 |                   h = strrchr (filename, '/'); | 
 |                   if (h != NULL) | 
 |                     filename = h + 1; | 
 |                 } | 
 |  | 
 |               printf ("%s:", filename ? filename : "??"); | 
 | 	      if (line != 0) | 
 |                 { | 
 |                   if (discriminator != 0) | 
 |                     printf ("%u (discriminator %u)\n", line, discriminator); | 
 |                   else | 
 |                     printf ("%u\n", line); | 
 |                 } | 
 | 	      else | 
 | 		printf ("?\n"); | 
 |               if (!unwind_inlines) | 
 |                 found = false; | 
 |               else | 
 |                 found = bfd_find_inliner_info (abfd, &filename, &functionname, | 
 | 					       &line); | 
 |               if (! found) | 
 |                 break; | 
 |               if (pretty_print) | 
 | 		/* Note for translators: This printf is used to join the | 
 | 		   line number/file name pair that has just been printed with | 
 | 		   the line number/file name pair that is going to be printed | 
 | 		   by the next iteration of the while loop.  Eg: | 
 |  | 
 | 		     123:bar.c (inlined by) 456:main.c  */ | 
 |                 printf (_(" (inlined by) ")); | 
 |             } | 
 | 	} | 
 |  | 
 |       /* fflush() is essential for using this command as a server | 
 |          child process that reads addresses from a pipe and responds | 
 |          with line number information, processing one address at a | 
 |          time.  */ | 
 |       fflush (stdout); | 
 |     } | 
 | } | 
 |  | 
 | /* Process a file.  Returns an exit value for main().  */ | 
 |  | 
 | static int | 
 | process_file (const char *file_name, const char *section_name, | 
 | 	      const char *target) | 
 | { | 
 |   bfd *abfd; | 
 |   asection *section; | 
 |   char **matching; | 
 |  | 
 |   if (get_file_size (file_name) < 1) | 
 |     return 1; | 
 |  | 
 |   abfd = bfd_openr (file_name, target); | 
 |   if (abfd == NULL) | 
 |     bfd_fatal (file_name); | 
 |  | 
 |   /* Decompress sections.  */ | 
 |   abfd->flags |= BFD_DECOMPRESS; | 
 |  | 
 |   if (bfd_check_format (abfd, bfd_archive)) | 
 |     { | 
 |       non_fatal (_("%s: cannot get addresses from archive"), file_name); | 
 |       bfd_close (abfd); | 
 |       return 1; | 
 |     } | 
 |  | 
 |   if (! bfd_check_format_matches (abfd, bfd_object, &matching)) | 
 |     { | 
 |       bfd_nonfatal (bfd_get_filename (abfd)); | 
 |       if (bfd_get_error () == bfd_error_file_ambiguously_recognized) | 
 | 	list_matching_formats (matching); | 
 |       bfd_close (abfd); | 
 |       return 1; | 
 |     } | 
 |  | 
 |   if (section_name != NULL) | 
 |     { | 
 |       section = bfd_get_section_by_name (abfd, section_name); | 
 |       if (section == NULL) | 
 | 	{ | 
 | 	  non_fatal (_("%s: cannot find section %s"), file_name, section_name); | 
 | 	  bfd_close (abfd); | 
 | 	  return 1; | 
 | 	} | 
 |     } | 
 |   else | 
 |     section = NULL; | 
 |  | 
 |   slurp_symtab (abfd); | 
 |  | 
 |   translate_addresses (abfd, section); | 
 |  | 
 |   free (syms); | 
 |   syms = NULL; | 
 |  | 
 |   bfd_close (abfd); | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | int | 
 | main (int argc, char **argv) | 
 | { | 
 |   const char *file_name; | 
 |   const char *section_name; | 
 |   char *target; | 
 |   int c; | 
 |  | 
 | #ifdef HAVE_LC_MESSAGES | 
 |   setlocale (LC_MESSAGES, ""); | 
 | #endif | 
 |   setlocale (LC_CTYPE, ""); | 
 |   bindtextdomain (PACKAGE, LOCALEDIR); | 
 |   textdomain (PACKAGE); | 
 |  | 
 |   program_name = *argv; | 
 |   xmalloc_set_program_name (program_name); | 
 |   bfd_set_error_program_name (program_name); | 
 |  | 
 |   expandargv (&argc, &argv); | 
 |  | 
 |   if (bfd_init () != BFD_INIT_MAGIC) | 
 |     fatal (_("fatal error: libbfd ABI mismatch")); | 
 |   set_default_bfd_target (); | 
 |  | 
 |   file_name = NULL; | 
 |   section_name = NULL; | 
 |   target = NULL; | 
 |   while ((c = getopt_long (argc, argv, "ab:Ce:rRsfHhij:pVv", long_options, (int *) 0)) | 
 | 	 != EOF) | 
 |     { | 
 |       switch (c) | 
 | 	{ | 
 | 	case 0: | 
 | 	  break;		/* We've been given a long option.  */ | 
 | 	case 'a': | 
 | 	  with_addresses = true; | 
 | 	  break; | 
 | 	case 'b': | 
 | 	  target = optarg; | 
 | 	  break; | 
 | 	case 'C': | 
 | 	  do_demangle = true; | 
 | 	  if (optarg != NULL) | 
 | 	    { | 
 | 	      enum demangling_styles style; | 
 |  | 
 | 	      style = cplus_demangle_name_to_style (optarg); | 
 | 	      if (style == unknown_demangling) | 
 | 		fatal (_("unknown demangling style `%s'"), | 
 | 		       optarg); | 
 |  | 
 | 	      cplus_demangle_set_style (style); | 
 | 	    } | 
 | 	  break; | 
 | 	case 'r': | 
 | 	  demangle_flags |= DMGL_NO_RECURSE_LIMIT; | 
 | 	  break; | 
 | 	case 'R': | 
 | 	  demangle_flags &= ~ DMGL_NO_RECURSE_LIMIT; | 
 | 	  break; | 
 | 	case 'e': | 
 | 	  file_name = optarg; | 
 | 	  break; | 
 | 	case 's': | 
 | 	  base_names = true; | 
 | 	  break; | 
 | 	case 'f': | 
 | 	  with_functions = true; | 
 | 	  break; | 
 |         case 'p': | 
 |           pretty_print = true; | 
 |           break; | 
 | 	case 'v': | 
 | 	case 'V': | 
 | 	  print_version ("addr2line"); | 
 | 	  break; | 
 | 	case 'h': | 
 | 	case 'H': | 
 | 	  usage (stdout, 0); | 
 | 	  break; | 
 | 	case 'i': | 
 | 	  unwind_inlines = true; | 
 | 	  break; | 
 | 	case 'j': | 
 | 	  section_name = optarg; | 
 | 	  break; | 
 | 	default: | 
 | 	  usage (stderr, 1); | 
 | 	  break; | 
 | 	} | 
 |     } | 
 |  | 
 |   if (file_name == NULL) | 
 |     file_name = "a.out"; | 
 |  | 
 |   addr = argv + optind; | 
 |   naddr = argc - optind; | 
 |  | 
 |   return process_file (file_name, section_name, target); | 
 | } |