| /* Core dump and executable file functions above target vector, for GDB. | |
| Copyright (C) 1986-2024 Free Software Foundation, Inc. | |
| This file is part of GDB. | |
| 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 of the License, 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, see <http://www.gnu.org/licenses/>. */ | |
| #include <signal.h> | |
| #include <fcntl.h> | |
| #include "event-top.h" | |
| #include "extract-store-integer.h" | |
| #include "inferior.h" | |
| #include "symtab.h" | |
| #include "command.h" | |
| #include "cli/cli-cmds.h" | |
| #include "bfd.h" | |
| #include "target.h" | |
| #include "gdbcore.h" | |
| #include "dis-asm.h" | |
| #include <sys/stat.h> | |
| #include "completer.h" | |
| #include "observable.h" | |
| #include "cli/cli-utils.h" | |
| #include "gdbarch.h" | |
| #include "interps.h" | |
| #include "arch-utils.h" | |
| void | |
| reopen_exec_file (void) | |
| { | |
| bfd *exec_bfd = current_program_space->exec_bfd (); | |
| /* Don't do anything if there isn't an exec file. */ | |
| if (exec_bfd == nullptr) | |
| return; | |
| /* The main executable can't be an in-memory BFD object. If it was then | |
| the use of bfd_stat below would not work as expected. */ | |
| gdb_assert ((exec_bfd->flags & BFD_IN_MEMORY) == 0); | |
| /* If the timestamp of the exec file has changed, reopen it. */ | |
| struct stat st; | |
| int res = gdb_bfd_stat (exec_bfd, &st); | |
| if (res == 0 | |
| && current_program_space->ebfd_mtime != 0 | |
| && current_program_space->ebfd_mtime != st.st_mtime) | |
| exec_file_attach (bfd_get_filename (exec_bfd), 0); | |
| } | |
| /* If we have both a core file and an exec file, | |
| print a warning if they don't go together. */ | |
| void | |
| validate_files (void) | |
| { | |
| if (current_program_space->exec_bfd () && current_program_space->core_bfd ()) | |
| { | |
| if (!core_file_matches_executable_p (current_program_space->core_bfd (), | |
| current_program_space->exec_bfd ())) | |
| warning (_("core file may not match specified executable file.")); | |
| else if (gdb_bfd_get_mtime (current_program_space->exec_bfd ()) | |
| > gdb_bfd_get_mtime (current_program_space->core_bfd ())) | |
| warning (_("exec file is newer than core file.")); | |
| } | |
| } | |
| /* See arch-utils.h. */ | |
| core_file_exec_context | |
| default_core_parse_exec_context (struct gdbarch *gdbarch, bfd *cbfd) | |
| { | |
| return {}; | |
| } | |
| std::string | |
| memory_error_message (enum target_xfer_status err, | |
| struct gdbarch *gdbarch, CORE_ADDR memaddr) | |
| { | |
| switch (err) | |
| { | |
| case TARGET_XFER_E_IO: | |
| /* Actually, address between memaddr and memaddr + len was out of | |
| bounds. */ | |
| return string_printf (_("Cannot access memory at address %s"), | |
| paddress (gdbarch, memaddr)); | |
| case TARGET_XFER_UNAVAILABLE: | |
| return string_printf (_("Memory at address %s unavailable."), | |
| paddress (gdbarch, memaddr)); | |
| default: | |
| internal_error ("unhandled target_xfer_status: %s (%s)", | |
| target_xfer_status_to_string (err), | |
| plongest (err)); | |
| } | |
| } | |
| /* Report a memory error by throwing a suitable exception. */ | |
| void | |
| memory_error (enum target_xfer_status err, CORE_ADDR memaddr) | |
| { | |
| enum errors exception = GDB_NO_ERROR; | |
| /* Build error string. */ | |
| std::string str | |
| = memory_error_message (err, current_inferior ()->arch (), memaddr); | |
| /* Choose the right error to throw. */ | |
| switch (err) | |
| { | |
| case TARGET_XFER_E_IO: | |
| exception = MEMORY_ERROR; | |
| break; | |
| case TARGET_XFER_UNAVAILABLE: | |
| exception = NOT_AVAILABLE_ERROR; | |
| break; | |
| } | |
| /* Throw it. */ | |
| throw_error (exception, ("%s"), str.c_str ()); | |
| } | |
| /* Helper function. */ | |
| static void | |
| read_memory_object (enum target_object object, CORE_ADDR memaddr, | |
| gdb_byte *myaddr, ssize_t len) | |
| { | |
| ULONGEST xfered = 0; | |
| while (xfered < len) | |
| { | |
| enum target_xfer_status status; | |
| ULONGEST xfered_len; | |
| status = target_xfer_partial (current_inferior ()->top_target (), object, | |
| NULL, myaddr + xfered, NULL, | |
| memaddr + xfered, len - xfered, | |
| &xfered_len); | |
| if (status != TARGET_XFER_OK) | |
| memory_error (status == TARGET_XFER_EOF ? TARGET_XFER_E_IO : status, | |
| memaddr + xfered); | |
| xfered += xfered_len; | |
| QUIT; | |
| } | |
| } | |
| /* Same as target_read_memory, but report an error if can't read. */ | |
| void | |
| read_memory (CORE_ADDR memaddr, gdb_byte *myaddr, ssize_t len) | |
| { | |
| read_memory_object (TARGET_OBJECT_MEMORY, memaddr, myaddr, len); | |
| } | |
| /* Same as target_read_stack, but report an error if can't read. */ | |
| void | |
| read_stack (CORE_ADDR memaddr, gdb_byte *myaddr, ssize_t len) | |
| { | |
| read_memory_object (TARGET_OBJECT_STACK_MEMORY, memaddr, myaddr, len); | |
| } | |
| /* Same as target_read_code, but report an error if can't read. */ | |
| void | |
| read_code (CORE_ADDR memaddr, gdb_byte *myaddr, ssize_t len) | |
| { | |
| read_memory_object (TARGET_OBJECT_CODE_MEMORY, memaddr, myaddr, len); | |
| } | |
| /* Read memory at MEMADDR of length LEN and put the contents in | |
| RETURN_VALUE. Return 0 if MEMADDR couldn't be read and non-zero | |
| if successful. */ | |
| int | |
| safe_read_memory_integer (CORE_ADDR memaddr, int len, | |
| enum bfd_endian byte_order, | |
| LONGEST *return_value) | |
| { | |
| gdb_byte buf[sizeof (LONGEST)]; | |
| if (target_read_memory (memaddr, buf, len)) | |
| return 0; | |
| *return_value = extract_signed_integer (buf, len, byte_order); | |
| return 1; | |
| } | |
| /* Read memory at MEMADDR of length LEN and put the contents in | |
| RETURN_VALUE. Return 0 if MEMADDR couldn't be read and non-zero | |
| if successful. */ | |
| int | |
| safe_read_memory_unsigned_integer (CORE_ADDR memaddr, int len, | |
| enum bfd_endian byte_order, | |
| ULONGEST *return_value) | |
| { | |
| gdb_byte buf[sizeof (ULONGEST)]; | |
| if (target_read_memory (memaddr, buf, len)) | |
| return 0; | |
| *return_value = extract_unsigned_integer (buf, len, byte_order); | |
| return 1; | |
| } | |
| LONGEST | |
| read_memory_integer (CORE_ADDR memaddr, int len, | |
| enum bfd_endian byte_order) | |
| { | |
| gdb_byte buf[sizeof (LONGEST)]; | |
| read_memory (memaddr, buf, len); | |
| return extract_signed_integer (buf, len, byte_order); | |
| } | |
| ULONGEST | |
| read_memory_unsigned_integer (CORE_ADDR memaddr, int len, | |
| enum bfd_endian byte_order) | |
| { | |
| gdb_byte buf[sizeof (ULONGEST)]; | |
| read_memory (memaddr, buf, len); | |
| return extract_unsigned_integer (buf, len, byte_order); | |
| } | |
| LONGEST | |
| read_code_integer (CORE_ADDR memaddr, int len, | |
| enum bfd_endian byte_order) | |
| { | |
| gdb_byte buf[sizeof (LONGEST)]; | |
| read_code (memaddr, buf, len); | |
| return extract_signed_integer (buf, len, byte_order); | |
| } | |
| ULONGEST | |
| read_code_unsigned_integer (CORE_ADDR memaddr, int len, | |
| enum bfd_endian byte_order) | |
| { | |
| gdb_byte buf[sizeof (ULONGEST)]; | |
| read_code (memaddr, buf, len); | |
| return extract_unsigned_integer (buf, len, byte_order); | |
| } | |
| CORE_ADDR | |
| read_memory_typed_address (CORE_ADDR addr, struct type *type) | |
| { | |
| gdb_byte *buf = (gdb_byte *) alloca (type->length ()); | |
| read_memory (addr, buf, type->length ()); | |
| return extract_typed_address (buf, type); | |
| } | |
| /* See gdbcore.h. */ | |
| void | |
| write_memory (CORE_ADDR memaddr, | |
| const bfd_byte *myaddr, ssize_t len) | |
| { | |
| int status; | |
| status = target_write_memory (memaddr, myaddr, len); | |
| if (status != 0) | |
| memory_error (TARGET_XFER_E_IO, memaddr); | |
| } | |
| /* Notify interpreters and observers that INF's memory was changed. */ | |
| static void | |
| notify_memory_changed (inferior *inf, CORE_ADDR addr, ssize_t len, | |
| const bfd_byte *data) | |
| { | |
| interps_notify_memory_changed (inf, addr, len, data); | |
| gdb::observers::memory_changed.notify (inf, addr, len, data); | |
| } | |
| /* Same as write_memory, but notify 'memory_changed' observers. */ | |
| void | |
| write_memory_with_notification (CORE_ADDR memaddr, const bfd_byte *myaddr, | |
| ssize_t len) | |
| { | |
| write_memory (memaddr, myaddr, len); | |
| notify_memory_changed (current_inferior (), memaddr, len, myaddr); | |
| } | |
| /* Store VALUE at ADDR in the inferior as a LEN-byte unsigned | |
| integer. */ | |
| void | |
| write_memory_unsigned_integer (CORE_ADDR addr, int len, | |
| enum bfd_endian byte_order, | |
| ULONGEST value) | |
| { | |
| gdb_byte *buf = (gdb_byte *) alloca (len); | |
| store_unsigned_integer (buf, len, byte_order, value); | |
| write_memory (addr, buf, len); | |
| } | |
| /* Store VALUE at ADDR in the inferior as a LEN-byte signed | |
| integer. */ | |
| void | |
| write_memory_signed_integer (CORE_ADDR addr, int len, | |
| enum bfd_endian byte_order, | |
| LONGEST value) | |
| { | |
| gdb_byte *buf = (gdb_byte *) alloca (len); | |
| store_signed_integer (buf, len, byte_order, value); | |
| write_memory (addr, buf, len); | |
| } | |
| /* The current default bfd target. Points to storage allocated for | |
| gnutarget_string. */ | |
| const char *gnutarget; | |
| /* Same thing, except it is "auto" not NULL for the default case. */ | |
| static std::string gnutarget_string; | |
| static void | |
| show_gnutarget_string (struct ui_file *file, int from_tty, | |
| struct cmd_list_element *c, | |
| const char *value) | |
| { | |
| gdb_printf (file, | |
| _("The current BFD target is \"%s\".\n"), value); | |
| } | |
| static void | |
| set_gnutarget_command (const char *ignore, int from_tty, | |
| struct cmd_list_element *c) | |
| { | |
| const char *gend = gnutarget_string.c_str () + gnutarget_string.size (); | |
| gend = remove_trailing_whitespace (gnutarget_string.c_str (), gend); | |
| gnutarget_string | |
| = gnutarget_string.substr (0, gend - gnutarget_string.data ()); | |
| if (gnutarget_string == "auto") | |
| gnutarget = NULL; | |
| else | |
| gnutarget = gnutarget_string.c_str (); | |
| } | |
| /* A completion function for "set gnutarget". */ | |
| static void | |
| complete_set_gnutarget (struct cmd_list_element *cmd, | |
| completion_tracker &tracker, | |
| const char *text, const char *word) | |
| { | |
| static const char **bfd_targets; | |
| if (bfd_targets == NULL) | |
| { | |
| int last; | |
| bfd_targets = bfd_target_list (); | |
| for (last = 0; bfd_targets[last] != NULL; ++last) | |
| ; | |
| bfd_targets = XRESIZEVEC (const char *, bfd_targets, last + 2); | |
| bfd_targets[last] = "auto"; | |
| bfd_targets[last + 1] = NULL; | |
| } | |
| complete_on_enum (tracker, bfd_targets, text, word); | |
| } | |
| /* Set the gnutarget. */ | |
| void | |
| set_gnutarget (const char *newtarget) | |
| { | |
| gnutarget_string = newtarget; | |
| set_gnutarget_command (NULL, 0, NULL); | |
| } | |
| void _initialize_core (); | |
| void | |
| _initialize_core () | |
| { | |
| cmd_list_element *core_file_cmd | |
| = add_cmd ("core-file", class_files, core_file_command, _("\ | |
| Use FILE as core dump for examining memory and registers.\n\ | |
| Usage: core-file FILE\n\ | |
| No arg means have no core file. This command has been superseded by the\n\ | |
| `target core' and `detach' commands."), &cmdlist); | |
| set_cmd_completer (core_file_cmd, deprecated_filename_completer); | |
| set_show_commands set_show_gnutarget | |
| = add_setshow_string_noescape_cmd ("gnutarget", class_files, | |
| &gnutarget_string, _("\ | |
| Set the current BFD target."), _("\ | |
| Show the current BFD target."), _("\ | |
| Use `set gnutarget auto' to specify automatic detection."), | |
| set_gnutarget_command, | |
| show_gnutarget_string, | |
| &setlist, &showlist); | |
| set_cmd_completer (set_show_gnutarget.set, complete_set_gnutarget); | |
| add_alias_cmd ("g", set_show_gnutarget.set, class_files, 1, &setlist); | |
| if (getenv ("GNUTARGET")) | |
| set_gnutarget (getenv ("GNUTARGET")); | |
| else | |
| set_gnutarget ("auto"); | |
| } |