| /* Copyright (C) 2021-2024 Free Software Foundation, Inc. |
| Contributed by Oracle. |
| |
| 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. */ |
| |
| #include "config.h" |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include "util.h" |
| #include "DbeApplication.h" |
| #include "DbeSession.h" |
| #include "Function.h" |
| #include "LoadObject.h" |
| #include "Module.h" |
| #include "DbeView.h" |
| #include "Print.h" |
| #include "DbeFile.h" |
| #include "Command.h" |
| |
| class er_src : public DbeApplication |
| { |
| public: |
| er_src (int argc, char *argv[]); |
| void start (int argc, char *argv[]); |
| |
| private: |
| |
| // override methods in base class |
| void usage (); |
| int check_args (int argc, char *argv[]); |
| void run_args (int argc, char *argv[]); |
| |
| enum Obj_Types |
| { |
| OT_EXE_ELF = 0, OT_JAVA_CLASS, OT_JAR_FILE, OT_UNKNOWN |
| }; |
| |
| void open (char *exe); |
| void dump_annotated (char *name, char* sel, char *src, DbeView *dbev, |
| bool is_dis, bool first); |
| void checkJavaClass (char *exe); |
| void print_header (bool first, const char* text); |
| void proc_cmd (CmdType cmd_type, bool first, char *arg1, |
| const char *arg2, const char *arg3 = NULL); |
| FILE *set_outfile (char *cmd, FILE *&set_file); |
| |
| bool is_java_class () { return obj_type == OT_JAVA_CLASS; } |
| |
| int dbevindex; |
| DbeView *dbev; |
| LoadObject *lo; |
| Obj_Types obj_type; |
| const char *out_fname; |
| FILE *out_file; |
| bool isDisasm; |
| bool isFuncs; |
| bool isDFuncs; |
| bool isSrc; |
| bool v_opt; |
| int multiple; |
| char *str_compcom; |
| }; |
| |
| static int |
| real_main (int argc, char *argv[]) |
| { |
| er_src *src = new er_src (argc, argv); |
| src->start (argc, argv); |
| delete src; |
| return 0; |
| } |
| |
| int |
| main (int argc, char *argv[]) |
| { |
| return catch_out_of_memory (real_main, argc, argv); |
| } |
| |
| er_src::er_src (int argc, char *argv[]) |
| : DbeApplication (argc, argv) |
| { |
| obj_type = OT_UNKNOWN; |
| out_fname = "<stdout>"; |
| out_file = stdout; |
| isDisasm = false; |
| isFuncs = false; |
| isDFuncs = false; |
| isSrc = false; |
| v_opt = false; |
| multiple = 0; |
| lo = NULL; |
| } |
| |
| static int |
| FuncNameCmp (const void *a, const void *b) |
| { |
| Function *item1 = *((Function **) a); |
| Function *item2 = *((Function **) b); |
| return strcmp (item1->get_mangled_name (), item2->get_mangled_name ()); |
| } |
| |
| static int |
| FuncAddrCmp (const void *a, const void *b) |
| { |
| Function *item1 = *((Function **) a); |
| Function *item2 = *((Function **) b); |
| return (item1->img_offset == item2->img_offset) ? |
| FuncNameCmp (a, b) : item1->img_offset > item2->img_offset ? 1 : -1; |
| } |
| |
| void |
| er_src::usage () |
| { |
| |
| /* |
| Ruud - Isolate this line because it has an argument. Otherwise it would be at the |
| end of a long usage list. |
| */ |
| printf ( GTXT ( |
| "Usage: gprofng display src [OPTION(S)] TARGET-OBJECT\n")); |
| |
| printf ( GTXT ( |
| "\n" |
| "Display the source code listing, or source code interleaved with disassembly code,\n" |
| "as extracted from the target object (an executable, shared object, object file, or\n" |
| "a Java .class file).\n" |
| "\n" |
| "Options:\n" |
| "\n" |
| " --version print the version number and exit.\n" |
| " --help print usage information and exit.\n" |
| " --verbose {on|off} enable (on) or disable (off) verbose mode; the default is \"off\".\n" |
| "\n" |
| " -func list all the functions from the given object.\n" |
| "\n" |
| " -source item tag show the source code for item; the tag is used to\n" |
| " differentiate in case of multiple occurences with\n" |
| " the same name; the combination of \"all -1\" selects\n" |
| " all the functions in the object; the default is\n" |
| " \"-source all -1\".\n" |
| "\n" |
| " -disasm item tag show the source code, interleaved with the disassembled\n" |
| " instructions; the same definitions for item and tag apply.\n" |
| "\n" |
| " -outfile <filename> write results to file <filename>; a dash (-) writes to\n" |
| " stdout; this is also the default; note that this only\n" |
| " affects options included to the right of this option.\n" |
| "\n" |
| "Documentation:\n" |
| "\n" |
| "A getting started guide for gprofng is maintained as a Texinfo manual. If the info and\n" |
| "gprofng programs are properly installed at your site, the command \"info gprofng\"\n" |
| "should give you access to this document.\n" |
| "\n" |
| "See also:\n" |
| "\n" |
| "gprofng(1), gp-archive(1), gp-collect-app(1), gp-display-html(1), gp-display-text(1)\n")); |
| /* |
| printf (GTXT ("Usage: %s [OPTION] a.out/.so/.o/.class\n\n"), whoami); |
| printf (GTXT (" -func List all the functions from the given object\n" |
| " -source, -src item tag Show the annotated source for the listed item\n" |
| " -disasm item tag Include the disassembly in the listing\n" |
| " -V Print the current release version of er_src\n" |
| " -cc, -scc, -dcc com_spec Define the compiler commentary classes to show\n" |
| " -outfile filename Open filename for output\n")); |
| */ |
| exit (0); |
| } |
| |
| void |
| er_src::start (int argc, char *argv[]) |
| { |
| dbevindex = dbeSession->createView (0, -1); |
| dbev = dbeSession->getView (dbevindex); |
| |
| // get options |
| check_args (argc, argv); |
| run_args (argc, argv); |
| if (out_file != stdout) |
| fclose (out_file); |
| } |
| |
| FILE * |
| er_src::set_outfile (char *cmd, FILE *&set_file) |
| { |
| FILE *new_file; |
| if (!strcasecmp (cmd, "-")) |
| { |
| new_file = stdout; |
| out_fname = "<stdout>"; |
| } |
| else |
| { |
| char *cmdpath; |
| char *fname = strstr (cmd, "~/"); |
| // Handle ~ in file names |
| char *home = getenv ("HOME"); |
| if (fname != NULL && home != NULL) |
| cmdpath = dbe_sprintf ("%s/%s", home, fname + 2); |
| else if ((fname = strstr (cmd, "~")) != NULL && home != NULL) |
| cmdpath = dbe_sprintf ("/home/%s", fname + 1); |
| else |
| cmdpath = strdup (cmd); |
| new_file = fopen (cmdpath, "w"); |
| if (new_file == NULL) |
| { |
| fprintf (stderr, GTXT ("Unable to open file: %s"), cmdpath); |
| free (cmdpath); |
| return NULL; |
| } |
| out_fname = cmdpath; |
| } |
| if (set_file && (set_file != stdout)) |
| fclose (set_file); |
| |
| set_file = new_file; |
| return set_file; |
| } |
| |
| void |
| er_src::proc_cmd (CmdType cmd_type, bool first, char *arg1, |
| const char *arg2, const char *arg3) |
| { |
| Cmd_status status; |
| Module *module; |
| Function *fitem; |
| int mindex, findex; |
| switch (cmd_type) |
| { |
| case SOURCE: |
| dbev->set_view_mode (VMODE_USER); |
| print_anno_file (arg1, arg2, arg3, false, |
| stdout, stdin, out_file, dbev, false); |
| break; |
| case DISASM: |
| dbev->set_view_mode (VMODE_MACHINE); |
| print_header (first, GTXT ("Annotated disassembly\n")); |
| print_anno_file (arg1, arg2, arg3, true, |
| stdout, stdin, out_file, dbev, false); |
| break; |
| case OUTFILE: |
| if (arg1) |
| set_outfile (arg1, out_file); |
| break; |
| case FUNCS: |
| print_header (false, GTXT ("Function list\n")); |
| fprintf (out_file, GTXT ("Functions sorted in lexicographic order\n")); |
| fprintf (out_file, GTXT ("\nLoad Object: %s\n\n"), lo->get_name ()); |
| if (lo->wsize == W32) |
| fprintf (out_file, GTXT (" Address Size Name\n\n")); |
| else |
| fprintf (out_file, GTXT (" Address Size Name\n\n")); |
| |
| Vec_loop (Module*, lo->seg_modules, mindex, module) |
| { |
| module->functions->sort (FuncNameCmp); |
| const char *fmt = (lo->wsize == W32) ? |
| GTXT (" 0x%08llx %8lld %s\n") : |
| GTXT (" 0x%016llx %16lld %s\n"); |
| Vec_loop (Function*, module->functions, findex, fitem) |
| { |
| fprintf (out_file, fmt, |
| (ull_t) fitem->img_offset, |
| (ull_t) fitem->size, |
| fitem->get_name ()); |
| } |
| } |
| break; |
| case DUMPFUNC: |
| lo->functions->sort (FuncAddrCmp); |
| print_header (first, GTXT ("Dump functions\n")); |
| lo->dump_functions (out_file); |
| first = false; |
| break; |
| case SCOMPCOM: |
| status = dbev->proc_compcom (arg1, true, false); |
| if (status != CMD_OK) |
| fprintf (stderr, GTXT ("Error: %s"), Command::get_err_string (status)); |
| break; |
| case DCOMPCOM: |
| status = dbev->proc_compcom (arg1, false, false); |
| if (status != CMD_OK) |
| fprintf (stderr, GTXT ("Error: %s"), Command::get_err_string (status)); |
| break; |
| case COMPCOM: |
| status = dbev->proc_compcom (arg1, true, false); |
| if (status != CMD_OK) |
| fprintf (stderr, GTXT ("Error: %s: %s"), Command::get_err_string (status), arg1); |
| status = dbev->proc_compcom (arg1, false, false); |
| if (status != CMD_OK) |
| fprintf (stderr, GTXT ("Error: %s: %s"), Command::get_err_string (status), arg1); |
| break; |
| case HELP: |
| usage (); |
| break; |
| case VERSION_cmd: |
| if (out_file != stdout) |
| // Ruud |
| Application::print_version_info (); |
| /* |
| fprintf (out_file, "GNU %s version %s\n", get_basename (prog_name), VERSION); |
| */ |
| break; |
| default: |
| fprintf (stderr, GTXT ("Invalid option")); |
| break; |
| } |
| } |
| |
| void |
| er_src::run_args (int argc, char *argv[]) |
| { |
| CmdType cmd_type; |
| int arg_count, cparam; |
| char *arg; |
| char *arg1; |
| const char *arg2; |
| bool first = true; |
| bool space; |
| Module *module; |
| int mindex; |
| |
| for (int i = 1; i < argc; i++) |
| { |
| if (*argv[i] != '-') |
| { |
| if (!multiple) |
| { // er_src -V exe |
| space = false; |
| dbev->set_view_mode (VMODE_USER); |
| print_header (first, GTXT ("Annotated source\n")); |
| Vec_loop (Module*, lo->seg_modules, mindex, module) |
| { |
| if ((module->flags & MOD_FLAG_UNKNOWN) != 0 || |
| module->lang_code == Sp_lang_unknown) |
| continue; |
| if (space) |
| fprintf (out_file, "\n"); |
| print_anno_file (module->file_name, "1", NULL, false, |
| stdout, stdin, out_file, dbev, false); |
| space = true; |
| } |
| } |
| break; |
| } |
| if (strncmp (argv[i], NTXT ("--whoami="), 9) == 0) |
| { |
| whoami = argv[i] + 9; |
| continue; |
| } |
| switch (cmd_type = Command::get_command (argv[i] + 1, arg_count, cparam)) |
| { |
| case SOURCE: |
| case DISASM: |
| { |
| i += arg_count; |
| multiple++; |
| if (i >= argc || argv[i] == NULL || |
| (*(argv[i]) == '-' && atoi (argv[i]) != -1) || i + 1 == argc) |
| { |
| i--; |
| arg = argv[i]; |
| arg2 = "1"; |
| } |
| else |
| { |
| arg = argv[i - 1]; |
| if (*(argv[i]) == '-' && atoi (argv[i]) == -1 && |
| streq (arg, NTXT ("all"))) |
| { |
| space = false; |
| if (cmd_type == SOURCE) |
| print_header (first, GTXT ("Annotated source\n")); |
| else |
| print_header (first, GTXT ("Annotated disassembly\n")); |
| Vec_loop (Module*, lo->seg_modules, mindex, module) |
| { |
| if ((module->flags & MOD_FLAG_UNKNOWN) != 0 || |
| module->lang_code == Sp_lang_unknown) |
| continue; |
| if (space) |
| fprintf (out_file, "\n"); |
| proc_cmd (cmd_type, first, module->file_name, "1"); |
| space = true; |
| } |
| first = false; |
| break; |
| } |
| arg2 = argv[i]; |
| } |
| char *fcontext = NULL; |
| arg1 = parse_fname (arg, &fcontext); |
| if (arg1 == NULL) |
| { |
| fprintf (stderr, GTXT ("Error: Invalid function/file setting: %s\n"), arg1); |
| free (fcontext); |
| break; |
| } |
| proc_cmd (cmd_type, first, arg1, arg2, fcontext); |
| free (arg1); |
| free (fcontext); |
| first = false; |
| break; |
| } |
| case OUTFILE: |
| case FUNCS: |
| case DUMPFUNC: |
| case COMPCOM: |
| case SCOMPCOM: |
| case DCOMPCOM: |
| case VERSION_cmd: |
| case HELP: |
| proc_cmd (cmd_type, first, (arg_count > 0) ? argv[i + 1] : NULL, |
| (arg_count > 1) ? argv[i + 2] : NULL); |
| i += arg_count; |
| break; |
| default: |
| if (streq (argv[i] + 1, NTXT ("all")) || streq (argv[i] + 1, NTXT ("dall"))) |
| { |
| first = false; |
| multiple++; |
| if (streq (argv[i] + 1, NTXT ("all"))) |
| proc_cmd (FUNCS, first, NULL, NULL); |
| else |
| proc_cmd (DUMPFUNC, first, NULL, NULL); |
| space = false; |
| print_header (first, GTXT ("Annotated source\n")); |
| Vec_loop (Module*, lo->seg_modules, mindex, module) |
| { |
| if ((module->flags & MOD_FLAG_UNKNOWN) != 0 || |
| module->lang_code == Sp_lang_unknown) |
| continue; |
| if (space) |
| fprintf (out_file, "\n"); |
| proc_cmd (SOURCE, first, module->file_name, "1"); |
| space = true; |
| } |
| print_header (first, GTXT ("Annotated disassembly\n")); |
| Vec_loop (Module*, lo->seg_modules, mindex, module) |
| { |
| if ((module->flags & MOD_FLAG_UNKNOWN) != 0 || |
| module->lang_code == Sp_lang_unknown) |
| continue; |
| if (space) |
| fprintf (out_file, "\n"); |
| proc_cmd (DISASM, first, module->file_name, "1"); |
| space = true; |
| } |
| } |
| else |
| { |
| proc_cmd (cmd_type, first, (arg_count > 0) ? argv[i + 1] : NULL, |
| (arg_count > 1) ? argv[i + 2] : NULL); |
| i += arg_count; |
| break; |
| } |
| } |
| } |
| } |
| |
| int |
| er_src::check_args (int argc, char *argv[]) |
| { |
| CmdType cmd_type = UNKNOWN_CMD; |
| int arg_count, cparam; |
| int i; |
| char *exe; |
| bool first = true; |
| if (argc == 1) |
| usage (); |
| |
| // If any comments from the .rc files, log them to stderr |
| Emsg * rcmsg = fetch_comments (); |
| while (rcmsg != NULL) |
| { |
| fprintf (stderr, "%s: %s\n", prog_name, rcmsg->get_msg ()); |
| rcmsg = rcmsg->next; |
| } |
| |
| // Parsing the command line |
| opterr = 0; |
| exe = NULL; |
| for (i = 1; i < argc; i++) |
| { |
| if (*argv[i] != '-') |
| { |
| exe = argv[i]; |
| if (i == 1) |
| { // er_src exe ? |
| if (!exe) |
| usage (); |
| if (argc == 3) // er_src exe file |
| usage (); |
| } |
| else if (v_opt && !multiple && !exe && !str_compcom) // just er_src -V |
| exit (0); |
| if (argc < i + 1 || argc > i + 3) |
| usage (); |
| i++; |
| if (argc > i) |
| usage (); |
| open (exe); |
| return i; |
| } |
| switch (cmd_type = Command::get_command (argv[i] + 1, arg_count, cparam)) |
| { |
| case WHOAMI: |
| whoami = argv[i] + 1 + cparam; |
| break; |
| case HELP: |
| i += arg_count; |
| multiple++; |
| usage (); |
| break; |
| case VERSION_cmd: |
| if (first) |
| { |
| // Ruud |
| Application::print_version_info (); |
| /* |
| printf ("GNU %s version %s\n", get_basename (prog_name), VERSION); |
| */ |
| v_opt = true; |
| first = false; |
| } |
| break; |
| case SOURCE: |
| case DISASM: |
| i += arg_count; |
| multiple++; |
| isDisasm = true; |
| if (i >= argc || argv[i] == NULL || |
| (*(argv[i]) == '-' && atoi (argv[i]) != -1) || (i + 1 == argc)) |
| i--; |
| break; |
| case DUMPFUNC: |
| i += arg_count; |
| multiple++; |
| break; |
| case FUNCS: |
| i += arg_count; |
| multiple++; |
| break; |
| case OUTFILE: |
| case COMPCOM: |
| case SCOMPCOM: |
| case DCOMPCOM: |
| i += arg_count; |
| break; |
| default: |
| if (!(streq (argv[i] + 1, NTXT ("all")) || |
| streq (argv[i] + 1, NTXT ("dall")))) |
| { |
| fprintf (stderr, "Error: invalid option: `%s'\n", argv[i]); |
| exit (1); |
| } |
| } |
| } |
| if (!exe && !(argc == 2 && cmd_type == VERSION_cmd)) |
| usage (); |
| return i; |
| } |
| |
| void |
| er_src::checkJavaClass (char* exe) |
| { |
| unsigned char cf_buf[4]; |
| unsigned int magic_number; |
| int fd = ::open (exe, O_RDONLY | O_LARGEFILE); |
| if (fd == -1) |
| return; |
| if (sizeof (cf_buf) == read_from_file (fd, cf_buf, sizeof (cf_buf))) |
| { |
| magic_number = cf_buf[0] << 24; |
| magic_number |= cf_buf[1] << 16; |
| magic_number |= cf_buf[2] << 8; |
| magic_number |= cf_buf[3]; |
| if (magic_number == 0xcafebabe) |
| obj_type = OT_JAVA_CLASS; |
| } |
| close (fd); |
| } |
| |
| void |
| er_src::print_header (bool first, const char* text) |
| { |
| if (!first) |
| fprintf (out_file, "\n"); |
| if (multiple > 1) |
| { |
| fprintf (out_file, NTXT ("%s"), text); |
| fprintf (out_file, "---------------------------------------\n"); |
| } |
| } |
| |
| void |
| er_src::dump_annotated (char *name, char *sel, char *src, DbeView *dbevr, |
| bool is_dis, bool first) |
| { |
| Module *module; |
| bool space; |
| int mindex; |
| print_header (first, (is_dis) ? ((is_java_class ()) ? |
| GTXT ("Annotated bytecode\n") : |
| GTXT ("Annotated disassembly\n")) : |
| GTXT ("Annotated source\n")); |
| if (!name) |
| { |
| space = false; |
| Vec_loop (Module*, lo->seg_modules, mindex, module) |
| { |
| if ((module->flags & MOD_FLAG_UNKNOWN) != 0 || |
| (!is_dis && module->lang_code == Sp_lang_unknown)) |
| continue; |
| if (space) |
| fprintf (out_file, "\n"); |
| print_anno_file (module->file_name, sel, src, is_dis, |
| stdout, stdin, out_file, dbevr, false); |
| space = true; |
| } |
| } |
| else |
| print_anno_file (name, sel, src, is_dis, stdout, stdin, out_file, dbevr, false); |
| } |
| |
| static bool |
| isFatal (bool isDisasm, LoadObject::Arch_status status) |
| { |
| if (isDisasm) |
| { |
| switch (status) |
| { |
| // non-fatal errors for disassembly |
| case LoadObject::ARCHIVE_BAD_STABS: |
| case LoadObject::ARCHIVE_NO_STABS: |
| return false; |
| default: |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| void |
| er_src::open (char *exe) |
| { |
| LoadObject::Arch_status status; |
| char *errstr; |
| Module *module; |
| Vector<Histable*> *module_lst; |
| |
| // Construct the Segment structure |
| char *path = strdup (exe); |
| lo = dbeSession->createLoadObject (path); |
| if (NULL == lo->dbeFile->find_file (lo->dbeFile->get_name ())) |
| { |
| fprintf (stderr, GTXT ("%s: Error: unable to open file %s\n"), prog_name, lo->dbeFile->get_name ()); |
| exit (1); |
| } |
| checkJavaClass (exe); |
| |
| if (is_java_class ()) |
| { |
| lo->type = LoadObject::SEG_TEXT; |
| lo->set_platform (Java, Wnone); |
| lo->id = (uint64_t) - 1; // see AnalyzerSession::ask_which for details |
| module = dbeSession->createClassFile (dbe_strdup (exe)); |
| module->loadobject = lo; |
| lo->seg_modules->append (module); |
| module->dbeFile->set_location (exe); |
| if (module->readFile () != module->AE_OK) |
| { |
| Emsg *emsg = module->get_error (); |
| if (emsg) |
| { |
| fprintf (stderr, GTXT ("%s: Error: %s\n"), prog_name, emsg->get_msg ()); |
| return; |
| } |
| fprintf (stderr, GTXT ("%s: Error: Could not read class file `%s'\n"), prog_name, exe); |
| return; |
| } |
| status = lo->sync_read_stabs (); |
| if (status != LoadObject::ARCHIVE_SUCCESS) |
| { |
| if (status == LoadObject::ARCHIVE_ERR_OPEN) |
| { |
| fprintf (stderr, GTXT ("%s: Error: Could not read class file `%s'\n"), prog_name, exe); |
| return; |
| } |
| else |
| { |
| if (isDisasm) |
| if (status == LoadObject::ARCHIVE_NO_STABS) |
| { |
| fprintf (stderr, GTXT ("%s: Error: `%s' is interface; disassembly annotation not available\n"), prog_name, exe); |
| return; |
| } |
| } |
| } |
| } |
| else |
| { |
| status = lo->sync_read_stabs (); |
| if (status != LoadObject::ARCHIVE_SUCCESS) |
| { |
| errstr = lo->status_str (status); |
| if (errstr) |
| { |
| fprintf (stderr, "%s: %s\n", prog_name, errstr); |
| free (errstr); |
| } |
| if (isFatal (isDisasm, status)) |
| return; |
| } |
| obj_type = OT_EXE_ELF; |
| |
| // if .o file, then set file as the exe name |
| if (lo->is_relocatable ()) |
| { |
| // find the module, if we can |
| module_lst = new Vector<Histable*>; |
| module = dbeSession->map_NametoModule (path, module_lst, 0); |
| if (module == NULL) |
| // Create a module with the right name |
| module = dbeSession->createModule (lo, path); |
| } |
| } |
| } |