Files
binutils-gdb/gdb/corefile.c
Andrew Burgess 96619f154a gdb: move all bfd_cache_close_all calls in gdb_bfd.c
In the following commit I ran into a problem.  The next commit aims to
improve GDB's handling of the main executable being a file on a remote
target (i.e. one with a 'target:' prefix).

To do this I have replaced a system 'stat' call with a bfd_stat call.

However, doing this caused a regression in gdb.base/attach.exp.

The problem is that the bfd library caches open FILE* handles for bfd
objects that it has accessed, which is great for short-lived, non
interactive programs (e.g. the assembler, or objcopy, etc), however,
for GDB this caching causes us a problem.

If we open the main executable as a bfd then the bfd library will
cache the open FILE*.  If some time passes, maybe just sat at the GDB
prompt, or with the inferior running, and then later we use bfd_stat
to check if the underlying, on-disk file has changed, then the bfd
library will actually use fstat on the underlying file descriptor.
This is of course slightly different than using system stat on with
the on-disk file name.

If the on-disk file has changed then system stat will give results for
the current on-disk file.  But, if the bfd cache is still holding open
the file descriptor for the original on-disk file (from before the
change) then fstat will return a result based on the original file,
and so show no change as having happened.

This is a known problem in GDB, and so far this has been solved by
scattering bfd_cache_close_all() calls throughout GDB.  But, as I
said, in the next commit I've made a change and run into a
problem (gdb.base/attach.exp) where we are apparently missing a
bfd_cache_close_all() call.

Now I could solve this problem by adding a bfd_cache_close_all() call
before the bfd_stat call that I plan to add in the next commit, that
would for sure solve the problem, but feels a little crude.

Better I think would be to track down where the bfd is being opened
and add a corresponding bfd_cache_close_all() call elsewhere in GDB
once we've finished doing whatever it is that caused us to open the
bfd in the first place.

This second solution felt like the better choice, so I tracked the
problem down to elf_locate_base and fixed that.  But that just exposed
another problem in gdb_bfd_map_section which was also re-opening the
bfd, so I fixed this (with another bfd_cache_close_all() call), and
that exposed another issue in gdbarch_lookup_osabi... and at this
point I wondered if I was approaching this problem the wrong way...

.... And so, I wonder, is there a _better_ way to handle these
bfd_cache_close_all() calls?

I see two problems with the current approach:

  1. It's fragile.  Folk aren't always aware that they need to clear
  the bfd cache, and this feels like something that is easy to
  overlook in review.  So adding new code to GDB can innocently touch
  a bfd, which populates the cache, which will then be a bug that can
  lie hidden until an on-disk file just happens to change at the wrong
  time ... and GDB fails to spot the change.  Additionally,

  2. It's in efficient.  The caching is intended to stop the bfd
  library from continually having to re-open the on-disk file.  If we
  have a function that touches a bfd then often that function is the
  obvious place to call bfd_cache_close_all.  But if a single GDB
  command calls multiple functions, each of which touch the bfd, then
  we will end up opening and closing the same on-disk file multiple
  times.  It feels like we would be better postponing the
  bfd_cache_close_all call until some later point, then we can benefit
  from the bfd cache.

So, in this commit I propose a new approach.  We now clear the bfd
cache in two places:

  (a) Just before we display a GDB prompt.  We display a prompt after
  completing a command, and GDB is about to enter an idle state
  waiting for further input from the user (or in async mode, for an
  inferior event).  If while we are in this idle state the user
  changes the on-disk file(s) then we would like GDB to notice this
  the next time it leaves its idle state, e.g. the next time the user
  executes a command, or when an inferior event arrives,

  (b) When we resume the inferior.  In synchronous mode, resuming the
  inferior is another time when GDB is blocked and sitting idle, but
  in this case we don't display a prompt.  As with (a) above, when an
  inferior event arrives we want GDB to notice any changes to on-disk
  files.

It turns out that there are existing observers for both of these
cases (before_prompt and target_resumed respectively), so my initial
thought was that I should attach to these observers in gdb_bfd.c, and
in both cases call bfd_cache_close_all().

And this does indeed solve the gdb.base/attach.exp problem that I see
with the following commit.

However, I see a problem with this solution.

Both of the observers I'm using are exposed through the Python API as
events that a user can hook into.  The user can potentially run any
GDB command (using gdb.execute), so Python code might end up causing
some bfds to be reopened, and inserted into the cache.

To solve this one solution would be to add a bfd_cache_close_all()
call into gdbpy_enter::~gdbpy_enter().  Unfortunately, there's no
similar enter/exit object for Guile, though right now Guile doesn't
offer the same event API, so maybe we could just ignore that
problem... but this doesn't feel great.

So instead, I think a better solution might be to not use observers
for the bfd_cache_close_all() calls.  Instead, I'll call
bfd_cache_close_all() directly from core GDB after we've notified the
before_prompt and target_resumed observers, this was we can be sure
that the cache is cleared after the observers have run, and before GDB
enters an idle state.

This commit also removes all of the other bfd_cache_close_all() calls
from GDB.  My claim is that these are no longer needed.

Approved-By: Tom Tromey <tom@tromey.com>
2023-11-20 10:54:17 +00:00

493 lines
13 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Core dump and executable file functions above target vector, for GDB.
Copyright (C) 1986-2023 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 "defs.h"
#include <signal.h>
#include <fcntl.h>
#include "inferior.h"
#include "symtab.h"
#include "command.h"
#include "gdbcmd.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"
/* You can have any number of hooks for `exec_file_command' command to
call. If there's only one hook, it is set in exec_file_display
hook. If there are two or more hooks, they are set in
exec_file_extra_hooks[], and deprecated_exec_file_display_hook is
set to a function that calls all of them. This extra complexity is
needed to preserve compatibility with old code that assumed that
only one hook could be set, and which called
deprecated_exec_file_display_hook directly. */
typedef void (*hook_type) (const char *);
hook_type deprecated_exec_file_display_hook; /* The original hook. */
static hook_type *exec_file_extra_hooks; /* Array of additional
hooks. */
static int exec_file_hook_count = 0; /* Size of array. */
/* If there are two or more functions that wish to hook into
exec_file_command, this function will call all of the hook
functions. */
static void
call_extra_exec_file_hooks (const char *filename)
{
int i;
for (i = 0; i < exec_file_hook_count; i++)
(*exec_file_extra_hooks[i]) (filename);
}
/* Call this to specify the hook for exec_file_command to call back.
This is called from the x-window display code. */
void
specify_exec_file_hook (void (*hook) (const char *))
{
hook_type *new_array;
if (deprecated_exec_file_display_hook != NULL)
{
/* There's already a hook installed. Arrange to have both it
and the subsequent hooks called. */
if (exec_file_hook_count == 0)
{
/* If this is the first extra hook, initialize the hook
array. */
exec_file_extra_hooks = XNEW (hook_type);
exec_file_extra_hooks[0] = deprecated_exec_file_display_hook;
deprecated_exec_file_display_hook = call_extra_exec_file_hooks;
exec_file_hook_count = 1;
}
/* Grow the hook array by one and add the new hook to the end.
Yes, it's inefficient to grow it by one each time but since
this is hardly ever called it's not a big deal. */
exec_file_hook_count++;
new_array = (hook_type *)
xrealloc (exec_file_extra_hooks,
exec_file_hook_count * sizeof (hook_type));
exec_file_extra_hooks = new_array;
exec_file_extra_hooks[exec_file_hook_count - 1] = hook;
}
else
deprecated_exec_file_display_hook = hook;
}
void
reopen_exec_file (void)
{
int res;
struct stat st;
/* Don't do anything if there isn't an exec file. */
if (current_program_space->exec_bfd () == NULL)
return;
/* If the timestamp of the exec file has changed, reopen it. */
std::string filename = bfd_get_filename (current_program_space->exec_bfd ());
res = stat (filename.c_str (), &st);
if (res == 0
&& current_program_space->ebfd_mtime
&& current_program_space->ebfd_mtime != st.st_mtime)
exec_file_attach (filename.c_str (), 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 () && core_bfd)
{
if (!core_file_matches_executable_p (core_bfd,
current_program_space->exec_bfd ()))
warning (_("core file may not match specified executable file."));
else if (bfd_get_mtime (current_program_space->exec_bfd ())
> bfd_get_mtime (core_bfd))
warning (_("exec file is newer than core file."));
}
}
/* See gdbsupport/common-inferior.h. */
const char *
get_exec_file (int err)
{
if (current_program_space->exec_filename != nullptr)
return current_program_space->exec_filename.get ();
if (!err)
return NULL;
error (_("No executable file specified.\n\
Use the \"file\" or \"exec-file\" command."));
}
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, 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");
}