| /* addr2line.c -- convert addresses to line number and function name |
| Copyright 1997, 1998, 1999, 2000, 2001, 2002 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 2, 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 <string.h> |
| |
| #include "bfd.h" |
| #include "getopt.h" |
| #include "libiberty.h" |
| #include "demangle.h" |
| #include "bucomm.h" |
| #include "budemang.h" |
| |
| static bfd_boolean with_functions; /* -f, show function names. */ |
| static bfd_boolean do_demangle; /* -C, demangle names. */ |
| static bfd_boolean base_names; /* -s, strip directory names. */ |
| |
| static int naddr; /* Number of addresses to process. */ |
| static char **addr; /* Hex addresses to process. */ |
| |
| static asymbol **syms; /* Symbol table. */ |
| |
| static struct option long_options[] = |
| { |
| {"basenames", no_argument, NULL, 's'}, |
| {"demangle", optional_argument, NULL, 'C'}, |
| {"exe", required_argument, NULL, 'e'}, |
| {"functions", no_argument, NULL, 'f'}, |
| {"target", required_argument, NULL, 'b'}, |
| {"help", no_argument, NULL, 'H'}, |
| {"version", no_argument, NULL, 'V'}, |
| {0, no_argument, 0, 0} |
| }; |
| |
| static void usage PARAMS ((FILE *, int)); |
| static void slurp_symtab PARAMS ((bfd *)); |
| static void find_address_in_section PARAMS ((bfd *, asection *, PTR)); |
| static void translate_addresses PARAMS ((bfd *)); |
| static void process_file PARAMS ((const char *, const char *)); |
| |
| /* Print a usage message to STREAM and exit with STATUS. */ |
| |
| static void |
| usage (stream, status) |
| 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\ |
| -b --target=<bfdname> Set the binary file format\n\ |
| -e --exe=<executable> Set the input file name (default is a.out)\n\ |
| -s --basenames Strip directory names\n\ |
| -f --functions Show function names\n\ |
| -C --demangle[=style] Demangle function names\n\ |
| -h --help Display this information\n\ |
| -v --version Display the program's version\n\ |
| \n")); |
| |
| list_supported_targets (program_name, stream); |
| if (status == 0) |
| fprintf (stream, _("Report bugs to %s\n"), REPORT_BUGS_TO); |
| exit (status); |
| } |
| |
| /* Read in the symbol table. */ |
| |
| static void |
| slurp_symtab (abfd) |
| bfd *abfd; |
| { |
| long symcount; |
| unsigned int size; |
| |
| if ((bfd_get_file_flags (abfd) & HAS_SYMS) == 0) |
| return; |
| |
| symcount = bfd_read_minisymbols (abfd, FALSE, (PTR) &syms, &size); |
| if (symcount == 0) |
| symcount = bfd_read_minisymbols (abfd, TRUE /* dynamic */, (PTR) &syms, &size); |
| |
| if (symcount < 0) |
| bfd_fatal (bfd_get_filename (abfd)); |
| } |
| |
| /* 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 bfd_boolean found; |
| |
| /* Look for an address in a section. This is called via |
| bfd_map_over_sections. */ |
| |
| static void |
| find_address_in_section (abfd, section, data) |
| bfd *abfd; |
| asection *section; |
| PTR data ATTRIBUTE_UNUSED; |
| { |
| bfd_vma vma; |
| bfd_size_type size; |
| |
| if (found) |
| return; |
| |
| if ((bfd_get_section_flags (abfd, section) & SEC_ALLOC) == 0) |
| return; |
| |
| vma = bfd_get_section_vma (abfd, section); |
| if (pc < vma) |
| return; |
| |
| size = bfd_get_section_size_before_reloc (section); |
| if (pc >= vma + size) |
| return; |
| |
| found = bfd_find_nearest_line (abfd, section, syms, pc - vma, |
| &filename, &functionname, &line); |
| } |
| |
| /* Read hexadecimal addresses from stdin, translate into |
| file_name:line_number and optionally function name. */ |
| |
| static void |
| translate_addresses (abfd) |
| bfd *abfd; |
| { |
| int read_stdin = (naddr == 0); |
| |
| for (;;) |
| { |
| if (read_stdin) |
| { |
| char addr_hex[100]; |
| |
| if (fgets (addr_hex, sizeof addr_hex, stdin) == NULL) |
| break; |
| pc = bfd_scan_vma (addr_hex, NULL, 16); |
| } |
| else |
| { |
| if (naddr <= 0) |
| break; |
| --naddr; |
| pc = bfd_scan_vma (*addr++, NULL, 16); |
| } |
| |
| found = FALSE; |
| bfd_map_over_sections (abfd, find_address_in_section, (PTR) NULL); |
| |
| if (! found) |
| { |
| if (with_functions) |
| printf ("??\n"); |
| printf ("??:0\n"); |
| } |
| else |
| { |
| if (with_functions) |
| { |
| const char *name; |
| char *alloc = NULL; |
| |
| name = functionname; |
| if (name == NULL || *name == '\0') |
| name = "??"; |
| else if (do_demangle) |
| { |
| alloc = demangle (abfd, name); |
| name = alloc; |
| } |
| |
| printf ("%s\n", name); |
| |
| if (alloc != NULL) |
| free (alloc); |
| } |
| |
| if (base_names && filename != NULL) |
| { |
| char *h; |
| |
| h = strrchr (filename, '/'); |
| if (h != NULL) |
| filename = h + 1; |
| } |
| |
| printf ("%s:%u\n", filename ? filename : "??", line); |
| } |
| |
| /* 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. */ |
| |
| static void |
| process_file (file_name, target) |
| const char *file_name; |
| const char *target; |
| { |
| bfd *abfd; |
| char **matching; |
| |
| abfd = bfd_openr (file_name, target); |
| if (abfd == NULL) |
| bfd_fatal (file_name); |
| |
| if (bfd_check_format (abfd, bfd_archive)) |
| fatal (_("%s: can not get addresses from archive"), file_name); |
| |
| 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); |
| free (matching); |
| } |
| xexit (1); |
| } |
| |
| slurp_symtab (abfd); |
| |
| translate_addresses (abfd); |
| |
| if (syms != NULL) |
| { |
| free (syms); |
| syms = NULL; |
| } |
| |
| bfd_close (abfd); |
| } |
| |
| int main PARAMS ((int, char **)); |
| |
| int |
| main (argc, argv) |
| int argc; |
| char **argv; |
| { |
| const char *file_name; |
| char *target; |
| int c; |
| |
| #if defined (HAVE_SETLOCALE) && defined (HAVE_LC_MESSAGES) |
| setlocale (LC_MESSAGES, ""); |
| #endif |
| #if defined (HAVE_SETLOCALE) |
| setlocale (LC_CTYPE, ""); |
| #endif |
| bindtextdomain (PACKAGE, LOCALEDIR); |
| textdomain (PACKAGE); |
| |
| program_name = *argv; |
| xmalloc_set_program_name (program_name); |
| |
| bfd_init (); |
| set_default_bfd_target (); |
| |
| file_name = NULL; |
| target = NULL; |
| while ((c = getopt_long (argc, argv, "b:Ce:sfHhVv", long_options, (int *) 0)) |
| != EOF) |
| { |
| switch (c) |
| { |
| case 0: |
| break; /* We've been given a long option. */ |
| 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 'e': |
| file_name = optarg; |
| break; |
| case 's': |
| base_names = TRUE; |
| break; |
| case 'f': |
| with_functions = TRUE; |
| break; |
| case 'v': |
| case 'V': |
| print_version ("addr2line"); |
| break; |
| case 'h': |
| case 'H': |
| usage (stdout, 0); |
| break; |
| default: |
| usage (stderr, 1); |
| break; |
| } |
| } |
| |
| if (file_name == NULL) |
| file_name = "a.out"; |
| |
| addr = argv + optind; |
| naddr = argc - optind; |
| |
| process_file (file_name, target); |
| |
| return 0; |
| } |