Redesign mock environment for gdbarch selftests

A following patch will remove this hack from within regcache's
implementation:

  struct regcache *
  get_thread_arch_regcache (ptid_t ptid, struct gdbarch *gdbarch)
  {
    struct address_space *aspace;

    /* For the benefit of "maint print registers" & co when debugging an
       executable, allow dumping the regcache even when there is no
       thread selected (target_thread_address_space internal-errors if
       no address space is found).  Note that normal user commands will
       fail higher up on the call stack due to no
       target_has_registers.  */
    aspace = (ptid_equal (null_ptid, ptid)
	      ? NULL
	      : target_thread_address_space (ptid));

i.e., it'll no longer be possible to try to build a regcache for
null_ptid.  That change alone would regress the gdbarch self tests
though, causing this:

  (gdb) maintenance selftest
  [...]
  Running selftest register_to_value.
  src/gdb/inferior.c:309: internal-error: inferior* find_inferior_pid(int): Assertion `pid != 0' failed.
  A problem internal to GDB has been detected,
  further debugging may prove unreliable.
  Quit this debugging session? (y or n) FAIL: gdb.gdb/unittest.exp: maintenance selftest (GDB internal error)

The problem is that the way the mocking environment for those unit
tests is written is a bit fragile: it creates a special purpose
regcache (and sentinel's frame), using whatever is the current
inferior_ptid (usually null_ptid), and assumes get_current_regcache
will find that in the regcache::current_regcache list.

This commit changes the way the mock environment is created.  It
eliminates the special regcache and frame and instead creates a fuller
mock environment, with a custom mock target_ops, and then a mock
inferior and thread "running" on that target.

If there's already a running target when you type "maint selftest",
then we error out, instead of pushing a new target on top of the
existing one (and thus killing the debug session).  This results in:

  (gdb) maint selftest
  (...)
  Self test failed: arch i386: target already pushed
  Self test failed: arch i386:x86-64: target already pushed
  Self test failed: arch i386:x64-32: target already pushed
  Self test failed: arch i8086: target already pushed
  Self test failed: arch i386:intel: target already pushed
  Self test failed: arch i386:x86-64:intel: target already pushed
  Self test failed: arch i386:x64-32:intel: target already pushed
  Self test failed: arch i386:nacl: target already pushed
  Self test failed: arch i386:x86-64:nacl: target already pushed
  Self test failed: arch i386:x64-32:nacl: target already pushed
  Self test failed: self-test failed at /home/pedro/gdb/mygit/src/gdb/selftest-arch.c:86
  (...)
  Ran 19 unit tests, 1 failed

I think that's OK, because self tests are really meant to be run from
a clean state right after GDB is started.  I'm adding that erroring
out just as safe measure just in case someone types "maint selftest"
on the command line while already debugging something (as I've done
it).

(In my multi-target branch, where this patch originated from, we don't
actually need to error out, because there each inferior has its own
target stack).

Also, note that the current code was doing:

 current_inferior()->gdbarch = gdbarch;

without taking care to restore the previous gdbarch.  This means that
GDB's state was being left inconsistent after running the self tests,
further supporting the point that there's probably not much
expectation that mixing "maint selftests" and regular debugging in the
same GDB invocation really works.  This patch fixes that, regardless.

gdb/ChangeLog:
2017-10-04  Pedro Alves  <palves@redhat.com>

	* frame.c (create_test_frame): Delete.
	* frame.h (create_test_frame): Delete.
	* gdbarch-selftests.c: Include gdbthread.h and target.h.
	(class regcache_test): Delete.
	(test_target_has_registers, test_target_has_stack)
	(test_target_has_memory, test_target_prepare_to_store)
	(test_target_store_registers): New functions.
	(test_target_ops): New class.
	(register_to_value_test): Error out if there's already a
	process_stratum (or higher) target pushed.  Create a fuller mock
	environment, with mock target_ops, inferior, address space, thread
	and inferior_ptid.
	* progspace.c (struct address_space): Move to ...
	* progspace.h (struct address_space): ... here.
	* regcache.h (regcache::~regcache, regcache::raw_write)
	[GDB_SELF_TEST]: No longer virtual.
This commit is contained in:
Pedro Alves 2017-10-04 18:21:09 +01:00
parent 4c71c1059f
commit 55b11ddf16
7 changed files with 119 additions and 63 deletions

View File

@ -1,3 +1,22 @@
2017-10-04 Pedro Alves <palves@redhat.com>
* frame.c (create_test_frame): Delete.
* frame.h (create_test_frame): Delete.
* gdbarch-selftests.c: Include gdbthread.h and target.h.
(class regcache_test): Delete.
(test_target_has_registers, test_target_has_stack)
(test_target_has_memory, test_target_prepare_to_store)
(test_target_store_registers): New functions.
(test_target_ops): New class.
(register_to_value_test): Error out if there's already a
process_stratum (or higher) target pushed. Create a fuller mock
environment, with mock target_ops, inferior, address space, thread
and inferior_ptid.
* progspace.c (struct address_space): Move to ...
* progspace.h (struct address_space): ... here.
* regcache.h (regcache::~regcache, regcache::raw_write)
[GDB_SELF_TEST]: No longer virtual.
2017-10-04 Simon Marchi <simon.marchi@ericsson.com>
* mi/mi-main.c (list_available_thread_groups): Reverse filter logic.

View File

@ -1716,23 +1716,6 @@ select_frame (struct frame_info *fi)
}
}
#if GDB_SELF_TEST
struct frame_info *
create_test_frame (struct regcache *regcache)
{
struct frame_info *this_frame = XCNEW (struct frame_info);
sentinel_frame = create_sentinel_frame (NULL, regcache);
sentinel_frame->prev = this_frame;
sentinel_frame->prev_p = 1;;
this_frame->prev_arch.p = 1;
this_frame->prev_arch.arch = get_regcache_arch (regcache);
this_frame->next = sentinel_frame;
return this_frame;
}
#endif
/* Create an arbitrary (i.e. address specified by user) or innermost frame.
Always returns a non-NULL value. */

View File

@ -833,14 +833,6 @@ extern struct frame_info *deprecated_safe_get_selected_frame (void);
extern struct frame_info *create_new_frame (CORE_ADDR base, CORE_ADDR pc);
#if GDB_SELF_TEST
/* Create a frame for unit test. Its next frame is sentinel frame,
created from REGCACHE. */
extern struct frame_info *create_test_frame (struct regcache *regcache);
#endif
/* Return true if the frame unwinder for frame FI is UNWINDER; false
otherwise. */

View File

@ -22,25 +22,57 @@
#include "selftest.h"
#include "selftest-arch.h"
#include "inferior.h"
#include "gdbthread.h"
#include "target.h"
namespace selftests {
/* A read-write regcache which doesn't write the target. */
/* A mock process_stratum target_ops that doesn't read/write registers
anywhere. */
class regcache_test : public regcache
static int
test_target_has_registers (target_ops *self)
{
return 1;
}
static int
test_target_has_stack (target_ops *self)
{
return 1;
}
static int
test_target_has_memory (target_ops *self)
{
return 1;
}
static void
test_target_prepare_to_store (target_ops *self, regcache *regs)
{
}
static void
test_target_store_registers (target_ops *self, regcache *regs, int regno)
{
}
class test_target_ops : public target_ops
{
public:
explicit regcache_test (struct gdbarch *gdbarch)
: regcache (gdbarch, NULL, false)
test_target_ops ()
: target_ops {}
{
set_ptid (inferior_ptid);
to_magic = OPS_MAGIC;
to_stratum = process_stratum;
to_has_memory = test_target_has_memory;
to_has_stack = test_target_has_stack;
to_has_registers = test_target_has_registers;
to_prepare_to_store = test_target_prepare_to_store;
to_store_registers = test_target_store_registers;
current_regcache.push_front (this);
}
void raw_write (int regnum, const gdb_byte *buf) override
{
raw_set_cached_value (regnum, buf);
complete_target_initialization (this);
}
};
@ -84,15 +116,56 @@ register_to_value_test (struct gdbarch *gdbarch)
builtin->builtin_char32,
};
current_inferior()->gdbarch = gdbarch;
/* Error out if debugging something, because we're going to push the
test target, which would pop any existing target. */
if (current_target.to_stratum >= process_stratum)
error (_("target already pushed"));
struct regcache *regcache = new regcache_test (gdbarch);
struct frame_info *frame = create_test_frame (regcache);
/* Create a mock environment. An inferior with a thread, with a
process_stratum target pushed. */
test_target_ops mock_target;
ptid_t mock_ptid (1, 1);
inferior mock_inferior (mock_ptid.pid ());
address_space mock_aspace {};
mock_inferior.gdbarch = gdbarch;
mock_inferior.aspace = &mock_aspace;
thread_info mock_thread (&mock_inferior, mock_ptid);
scoped_restore restore_thread_list
= make_scoped_restore (&thread_list, &mock_thread);
/* Add the mock inferior to the inferior list so that look ups by
target+ptid can find it. */
scoped_restore restore_inferior_list
= make_scoped_restore (&inferior_list);
inferior_list = &mock_inferior;
/* Switch to the mock inferior. */
scoped_restore_current_inferior restore_current_inferior;
set_current_inferior (&mock_inferior);
/* Push the process_stratum target so we can mock accessing
registers. */
push_target (&mock_target);
/* Pop it again on exit (return/exception). */
struct on_exit
{
~on_exit ()
{
pop_all_targets_at_and_above (process_stratum);
}
} pop_targets;
/* Switch to the mock thread. */
scoped_restore restore_inferior_ptid
= make_scoped_restore (&inferior_ptid, mock_ptid);
struct frame_info *frame = get_current_frame ();
const int num_regs = (gdbarch_num_regs (gdbarch)
+ gdbarch_num_pseudo_regs (gdbarch));
SELF_CHECK (regcache == get_current_regcache ());
/* Test gdbarch methods register_to_value and value_to_register with
different combinations of register numbers and types. */
for (const auto &type : types)

View File

@ -44,18 +44,6 @@ static int highest_address_space_num;
DEFINE_REGISTRY (program_space, REGISTRY_ACCESS_FIELD)
/* An address space. It is used for comparing if pspaces/inferior/threads
see the same address space and for associating caches to each address
space. */
struct address_space
{
int num;
/* Per aspace data-pointers required by other GDB modules. */
REGISTRY_FIELDS;
};
/* Keep a registry of per-address_space data-pointers required by other GDB
modules. */

View File

@ -210,6 +210,17 @@ struct program_space
REGISTRY_FIELDS;
};
/* An address space. It is used for comparing if
pspaces/inferior/threads see the same address space and for
associating caches to each address space. */
struct address_space
{
int num;
/* Per aspace data-pointers required by other GDB modules. */
REGISTRY_FIELDS;
};
/* The object file that the main symbol table was loaded from (e.g. the
argument to the "symbol-file" or "file" command). */

View File

@ -252,11 +252,6 @@ public:
DISABLE_COPY_AND_ASSIGN (regcache);
/* class regcache is only extended in unit test, so only mark it
virtual when selftest is enabled. */
#if GDB_SELF_TEST
virtual
#endif
~regcache ()
{
xfree (m_registers);
@ -277,11 +272,6 @@ public:
enum register_status raw_read (int regnum, gdb_byte *buf);
/* class regcache is only extended in unit test, so only mark it
virtual when selftest is enabled. */
#if GDB_SELF_TEST
virtual
#endif
void raw_write (int regnum, const gdb_byte *buf);
template<typename T, typename = RequireLongest<T>>