/* addr2line.c -- convert addresses to line number and function name Copyright (C) 1997-2023 Free Software Foundation, Inc. Contributed by Ulrich Lauther 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\ @ Read options from \n\ -a --addresses Show addresses\n\ -b --target= Set the binary file format\n\ -e --exe= Set the input file name (default is a.out)\n\ -i --inlines Unwind inlined functions\n\ -j --section= 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); }