[1/3] Redesign mock environment for gdbarch selftests

Message ID 1506957311-30028-2-git-send-email-palves@redhat.com
State New, archived
Headers

Commit Message

Pedro Alves Oct. 2, 2017, 3:15 p.m. UTC
  A following patch will remove this a 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 bail out, instead of pushing a new target on top of the
existing one (and thus killing the debug session).  I think that's OK,
because self tests are really mean to be run from a clean state right
after GDB is started.  I'm adding that skipping just as safe measure
just in case someone (as I've done it) types "maint selftest" on the
command line while already debugging something.

(In my multi-target branch, where this patch originated from, we don't
actually need to bail 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.

gdb/ChangeLog:
yyyy-mm-dd  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_fetch_registers, test_target_store_registers): New
	functions.
	(test_target_ops): New class.
	(register_to_value_test): Error out if there's execution.  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.
	* target.c (default_thread_address_space)
	(default_thread_architecture): Make extern.
	* target.h (default_thread_architecture)
	(default_thread_address_space): Declare.
---
 gdb/frame.c             |  17 --------
 gdb/frame.h             |   8 ----
 gdb/gdbarch-selftests.c | 105 ++++++++++++++++++++++++++++++++++++++++--------
 gdb/progspace.c         |  12 ------
 gdb/progspace.h         |  11 +++++
 gdb/regcache.h          |  10 -----
 gdb/target.c            |  10 +----
 gdb/target.h            |  10 +++++
 8 files changed, 111 insertions(+), 72 deletions(-)
  

Comments

Yao Qi Oct. 3, 2017, 11:06 a.m. UTC | #1
Pedro Alves <palves@redhat.com> writes:

> If there's already a running target when you type "maint selftest",
> then we bail out, instead of pushing a new target on top of the
> existing one (and thus killing the debug session).  I think that's OK,
> because self tests are really mean to be run from a clean state right
> after GDB is started.  I'm adding that skipping just as safe measure
> just in case someone (as I've done it) types "maint selftest" on the
> command line while already debugging something.

That is a goo catch, but IMO, we shouldn't allow running unit tests
during debugging session.  GDB unit tests touch many things, and some of
them are global states in GDB.  It is difficult to know what global
states we need to restore after tests.

>
> (In my multi-target branch, where this patch originated from, we don't
> actually need to bail 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.

You are right, we should manage the expectation of mixing debugging and
"maint selftests".  My suggestion is that "please do run selftests
with regular debugging".

> -    raw_set_cached_value (regnum, buf);
> +    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_fetch_registers = test_target_fetch_registers;

to_fetch_registers is TARGET_DEFAULT_IGNORE, so we don't need define
test_target_fetch_registers if we disallow running selftests with
regular debugging.

> +    to_store_registers = test_target_store_registers;

> +    to_thread_architecture = default_thread_architecture;
> +    to_thread_address_space = default_thread_address_space;

Any reason to set to_thread_address_space and to_thread_architecture?
Can't we use the default value?
  

Patch

diff --git a/gdb/frame.c b/gdb/frame.c
index a74deef..afdc157 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -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.  */
 
diff --git a/gdb/frame.h b/gdb/frame.h
index 0249857..1b72610 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -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.  */
 
diff --git a/gdb/gdbarch-selftests.c b/gdb/gdbarch-selftests.c
index f0b8d5d..1e9077a 100644
--- a/gdb/gdbarch-selftests.c
+++ b/gdb/gdbarch-selftests.c
@@ -22,25 +22,63 @@ 
 #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)
 {
-public:
-  explicit regcache_test (struct gdbarch *gdbarch)
-    : regcache (gdbarch, NULL, false)
-  {
-    set_ptid (inferior_ptid);
+  return 1;
+}
 
-    current_regcache.push_front (this);
-  }
+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)
+{
+}
 
-  void raw_write (int regnum, const gdb_byte *buf) override
+static void
+test_target_fetch_registers (target_ops *self, regcache *regs, int regno)
+{
+}
+
+static void
+test_target_store_registers (target_ops *self, regcache *regs, int regno)
+{
+}
+
+class test_target_ops : public target_ops
+{
+public:
+  test_target_ops ()
+    : target_ops {}
   {
-    raw_set_cached_value (regnum, buf);
+    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_fetch_registers = test_target_fetch_registers;
+    to_store_registers = test_target_store_registers;
+    to_thread_architecture = default_thread_architecture;
+    to_thread_address_space = default_thread_address_space;
   }
 };
 
@@ -84,15 +122,48 @@  register_to_value_test (struct gdbarch *gdbarch)
       builtin->builtin_char32,
     };
 
-  current_inferior()->gdbarch = gdbarch;
-
-  struct regcache *regcache = new regcache_test (gdbarch);
-  struct frame_info *frame = create_test_frame (regcache);
+  if (target_has_execution)
+    error (_("inferior is running"));
+
+  /* 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);
+  mock_inferior.next = 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);
+  struct on_exit { ~on_exit () { pop_all_targets (); } };
+  on_exit 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)
diff --git a/gdb/progspace.c b/gdb/progspace.c
index 41e8cd0..ea328c8 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -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.  */
 
diff --git a/gdb/progspace.h b/gdb/progspace.h
index f679fe3..85c99a62 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -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).  */
 
diff --git a/gdb/regcache.h b/gdb/regcache.h
index 3ecdb3b..6f42fb93 100644
--- a/gdb/regcache.h
+++ b/gdb/regcache.h
@@ -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>>
diff --git a/gdb/target.c b/gdb/target.c
index aded0ba..733c086 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -81,9 +81,6 @@  static int default_verify_memory (struct target_ops *self,
 				  const gdb_byte *data,
 				  CORE_ADDR memaddr, ULONGEST size);
 
-static struct address_space *default_thread_address_space
-     (struct target_ops *self, ptid_t ptid);
-
 static void tcomplain (void) ATTRIBUTE_NORETURN;
 
 static int return_zero (struct target_ops *);
@@ -94,9 +91,6 @@  static void target_command (char *, int);
 
 static struct target_ops *find_default_run_target (const char *);
 
-static struct gdbarch *default_thread_architecture (struct target_ops *ops,
-						    ptid_t ptid);
-
 static int dummy_find_memory_regions (struct target_ops *self,
 				      find_memory_region_ftype ignore1,
 				      void *ignore2);
@@ -2675,7 +2669,7 @@  target_get_osdata (const char *type)
   return target_read_stralloc (t, TARGET_OBJECT_OSDATA, type);
 }
 
-static struct address_space *
+struct address_space *
 default_thread_address_space (struct target_ops *self, ptid_t ptid)
 {
   struct inferior *inf;
@@ -3134,7 +3128,7 @@  default_watchpoint_addr_within_range (struct target_ops *target,
   return addr >= start && addr < start + length;
 }
 
-static struct gdbarch *
+struct gdbarch *
 default_thread_architecture (struct target_ops *ops, ptid_t ptid)
 {
   return target_gdbarch ();
diff --git a/gdb/target.h b/gdb/target.h
index f5ad1e5..ccb9ffd 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -1789,6 +1789,16 @@  extern int default_child_has_registers (struct target_ops *ops);
 extern int default_child_has_execution (struct target_ops *ops,
 					ptid_t the_ptid);
 
+/* The default target_ops::to_thread_architecture implementation.
+   Simply returns the architecture of the inferior for PTID.  */
+extern struct gdbarch *default_thread_architecture
+  (struct target_ops *ops, ptid_t ptid);
+
+/* The default target_ops::to_thread_address_space implementation.
+   Simply returns the address space of the inferior for PTID.  */
+extern struct address_space *default_thread_address_space
+  (struct target_ops *self, ptid_t ptid);
+
 /* Can the target support the debugger control of thread execution?
    Can it lock the thread scheduler?  */