binutils-gdb/gdb/elf-none-tdep.c
Simon Marchi 1edb66d856 gdb: make thread_info::suspend private, add getters / setters
A following patch will want to take some action when a pending wait
status is set on or removed from a thread.  Add a getter and a setter on
thread_info for the pending waitstatus, so that we can add some code in
the setter later.

The thing is, the pending wait status field is in the
thread_suspend_state, along with other fields that we need to backup
before and restore after the thread does an inferior function call.
Therefore, make the thread_suspend_state member private
(thread_info::suspend becomes thread_info::m_suspend), and add getters /
setters for all of its fields:

 - pending wait status
 - stop signal
 - stop reason
 - stop pc

For the pending wait status, add the additional has_pending_waitstatus
and clear_pending_waitstatus methods.

I think this makes the thread_info interface a bit nicer, because we
now access the fields as:

  thread->stop_pc ()

rather than

  thread->suspend.stop_pc

The stop_pc field being in the `suspend` structure is an implementation
detail of thread_info that callers don't need to be aware of.

For the backup / restore of the thread_suspend_state structure, add
save_suspend_to and restore_suspend_from methods.  You might wonder why
`save_suspend_to`, as opposed to a simple getter like

  thread_suspend_state &suspend ();

I want to make it clear that this is to be used only for backing up and
restoring the suspend state, _not_ to access fields like:

  thread->suspend ()->stop_pc

Adding some getters / setters allows adding some assertions.  I find
that this helps understand how things are supposed to work.  Add:

 - When getting the pending status (pending_waitstatus method), ensure
   that there is a pending status.
 - When setting a pending status (set_pending_waitstatus method), ensure
   there is no pending status.

There is one case I found where this wasn't true - in
remote_target::process_initial_stop_replies - which needed adjustments
to respect that contract.  I think it's because
process_initial_stop_replies is kind of (ab)using the
thread_info::suspend::waitstatus to store some statuses temporarily, for
its internal use (statuses it doesn't intent on leaving pending).

process_initial_stop_replies pulls out stop replies received during the
initial connection using target_wait.  It always stores the received
event in `evthread->suspend.waitstatus`.  But it only sets
waitstatus_pending_p, if it deems the event interesting enough to leave
pending, to be reported to the core:

      if (ws.kind != TARGET_WAITKIND_STOPPED
	  || ws.value.sig != GDB_SIGNAL_0)
	evthread->suspend.waitstatus_pending_p = 1;

It later uses this flag a bit below, to choose which thread to make the
"selected" one:

      if (selected == NULL
	  && thread->suspend.waitstatus_pending_p)
	selected = thread;

And ultimately that's used if the user-visible mode is all-stop, so that
we print the stop for that interesting thread:

  /* In all-stop, we only print the status of one thread, and leave
     others with their status pending.  */
  if (!non_stop)
    {
      thread_info *thread = selected;
      if (thread == NULL)
	thread = lowest_stopped;
      if (thread == NULL)
	thread = first;

      print_one_stopped_thread (thread);
    }

But in any case (all-stop or non-stop), print_one_stopped_thread needs
to access the waitstatus value of these threads that don't have a
pending waitstatus (those that had TARGET_WAITKIND_STOPPED +
GDB_SIGNAL_0).  This doesn't work with the assertions I've
put.

So, change the code to only set the thread's wait status if it is an
interesting one that we are going to leave pending.  If the thread
stopped due to a non-interesting event (TARGET_WAITKIND_STOPPED +
GDB_SIGNAL_0), don't store it.  Adjust print_one_stopped_thread to
understand that if a thread has no pending waitstatus, it's because it
stopped with TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.

The call to set_last_target_status also uses the pending waitstatus.
However, given that the pending waitstatus for the thread may have been
cleared in print_one_stopped_thread (and that there might not even be a
pending waitstatus in the first place, as explained above), it is no
longer possible to do it at this point.  To fix that, move the call to
set_last_target_status in print_one_stopped_thread.  I think this will
preserve the existing behavior, because set_last_target_status is
currently using the current thread's wait status.  And the current
thread is the last one for which print_one_stopped_thread is called.  So
by calling set_last_target_status in print_one_stopped_thread, we'll get
the same result.  set_last_target_status will possibly be called
multiple times, but only the last call will matter.  It just means
possibly more calls to set_last_target_status, but those are cheap.

Change-Id: Iedab9653238eaf8231abcf0baa20145acc8b77a7
2021-07-12 20:46:53 -04:00

127 lines
3.9 KiB
C

/* Common code for targets with the none ABI (bare-metal), but where the
BFD library is build with ELF support.
Copyright (C) 2020-2021 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 "elf-none-tdep.h"
#include "regset.h"
#include "elf-bfd.h" /* for elfcore_write_* */
#include "inferior.h"
#include "regcache.h"
#include "gdbarch.h"
#include "gcore.h"
#include "gcore-elf.h"
/* Build the note section for a corefile, and return it in a malloc
buffer. Currently this just dumps all available registers for each
thread. */
static gdb::unique_xmalloc_ptr<char>
elf_none_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
int *note_size)
{
gdb::unique_xmalloc_ptr<char> note_data;
/* Add note information about the executable and its arguments. */
std::string fname;
std::string psargs;
static const size_t fname_len = 16;
static const size_t psargs_len = 80;
if (get_exec_file (0))
{
const char *exe = get_exec_file (0);
fname = lbasename (exe);
psargs = std::string (exe);
const char *infargs = get_inferior_args ();
if (infargs != nullptr)
psargs += " " + std::string (infargs);
/* All existing targets that handle writing out prpsinfo expect the
fname and psargs strings to be at least 16 and 80 characters long
respectively, including a null terminator at the end. Resize to
the expected length minus one to ensure there is a null within the
required length. */
fname.resize (fname_len - 1);
psargs.resize (psargs_len - 1);
}
/* Resize the buffers up to their required lengths. This will fill any
remaining space with the null character. */
fname.resize (fname_len);
psargs.resize (psargs_len);
/* Now write out the prpsinfo structure. */
note_data.reset (elfcore_write_prpsinfo (obfd, note_data.release (),
note_size, fname.c_str (),
psargs.c_str ()));
if (note_data == nullptr)
return nullptr;
/* Thread register information. */
try
{
update_thread_list ();
}
catch (const gdb_exception_error &e)
{
exception_print (gdb_stderr, e);
}
/* Like the Linux kernel, prefer dumping the signalled thread first.
"First thread" is what tools use to infer the signalled thread. */
thread_info *signalled_thr = gcore_find_signalled_thread ();
/* All threads are reported as having been stopped by the same signal
that stopped SIGNALLED_THR. */
gdb_signal stop_signal;
if (signalled_thr != nullptr)
stop_signal = signalled_thr->stop_signal ();
else
stop_signal = GDB_SIGNAL_0;
if (signalled_thr != nullptr)
gcore_elf_build_thread_register_notes (gdbarch, signalled_thr,
stop_signal, obfd, &note_data,
note_size);
for (thread_info *thr : current_inferior ()->non_exited_threads ())
{
if (thr == signalled_thr)
continue;
gcore_elf_build_thread_register_notes (gdbarch, thr, stop_signal, obfd,
&note_data, note_size);
}
/* Target description. */
gcore_elf_make_tdesc_note (obfd, &note_data, note_size);
return note_data;
}
/* See none-tdep.h. */
void
elf_none_init_abi (struct gdbarch *gdbarch)
{
/* Default core file support. */
set_gdbarch_make_corefile_notes (gdbarch, elf_none_make_corefile_notes);
}