| /* addr2line.c -- convert addresses to line number and function name | |
| Copyright (C) 1997-2023 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); | |
| } |