From 1b30aaa56607a563fa263b9d2ee9eba89d79c1b4 Mon Sep 17 00:00:00 2001 From: Yao Qi Date: Fri, 24 Nov 2017 13:04:30 +0000 Subject: [PATCH] regcache::cooked_read unit test This patch adds a unit test to regcache::cooked_read. This unit test is a little different from normal unit test, it is more about conformance test or interaction test. This test pass both raw register number and pseudo register number to regcache::cooked_read, in order to inspect 1) return value of cooked_read, 2) how are target_ops to_xfer_partial, to_{fetch,store}_registers called (because regcache is updated by means of these three target_ops methods). With this test here, we have a clear picture about how each port of GDB get cooked registers. This patch also shares some code on mock target. gdb: 2017-11-24 Yao Qi * gdbarch-selftests.c (test_target_has_registers): Move it to target.c. (test_target_has_stack): Likewise. (test_target_has_memory): Likewise. (test_target_prepare_to_store): Likewise. (test_target_store_registers): Likewise. (test_target_ops): Likewise. * regcache.c: Include selftest-arch.h and gdbthread.h. (target_ops_no_register): New class. (test_target_fetch_registers): New. (test_target_store_registers): New. (test_target_xfer_partial): New. (readwrite_regcache): New. (cooked_read_test): New. (_initialize_regcache): Register the test. * target.c: (test_target_has_registers): Moved from gdbarch-selftests.c. (test_target_has_stack): Likewise. (test_target_has_memory): Likewise. (test_target_prepare_to_store): Likewise. (test_target_store_registers): Likewise. * target.h (test_target_ops): New class. --- gdb/ChangeLog | 25 ++++++ gdb/gdbarch-selftests.c | 50 +---------- gdb/regcache.c | 191 ++++++++++++++++++++++++++++++++++++++++ gdb/target.c | 47 ++++++++++ gdb/target.h | 14 +++ 5 files changed, 278 insertions(+), 49 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index a982caab68b..9947aaa8356 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,28 @@ +2017-11-24 Yao Qi + + * gdbarch-selftests.c (test_target_has_registers): Move it to + target.c. + (test_target_has_stack): Likewise. + (test_target_has_memory): Likewise. + (test_target_prepare_to_store): Likewise. + (test_target_store_registers): Likewise. + (test_target_ops): Likewise. + * regcache.c: Include selftest-arch.h and gdbthread.h. + (target_ops_no_register): New class. + (test_target_fetch_registers): New. + (test_target_store_registers): New. + (test_target_xfer_partial): New. + (readwrite_regcache): New. + (cooked_read_test): New. + (_initialize_regcache): Register the test. + * target.c: (test_target_has_registers): Moved from + gdbarch-selftests.c. + (test_target_has_stack): Likewise. + (test_target_has_memory): Likewise. + (test_target_prepare_to_store): Likewise. + (test_target_store_registers): Likewise. + * target.h (test_target_ops): New class. + 2017-11-24 Alan Hayward * aarch64-tdep.c (_initialize_aarch64_tdep): Add target desc diff --git a/gdb/gdbarch-selftests.c b/gdb/gdbarch-selftests.c index c748fcc6606..49d006f2dc8 100644 --- a/gdb/gdbarch-selftests.c +++ b/gdb/gdbarch-selftests.c @@ -25,58 +25,10 @@ #include "gdbthread.h" #include "target.h" #include "target-float.h" +#include "common/def-vector.h" namespace selftests { -/* A mock process_stratum target_ops that doesn't read/write registers - anywhere. */ - -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: - test_target_ops () - : target_ops {} - { - 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; - - complete_target_initialization (this); - } -}; - /* Test gdbarch methods register_to_value and value_to_register. */ static void diff --git a/gdb/regcache.c b/gdb/regcache.c index 9c68bddbba0..c37a520b3f8 100644 --- a/gdb/regcache.c +++ b/gdb/regcache.c @@ -1611,6 +1611,8 @@ maintenance_print_remote_registers (const char *args, int from_tty) #if GDB_SELF_TEST #include "selftest.h" +#include "selftest-arch.h" +#include "gdbthread.h" namespace selftests { @@ -1679,6 +1681,192 @@ current_regcache_test (void) SELF_CHECK (regcache_access::current_regcache_size () == 2); } +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); +static enum target_xfer_status + test_target_xfer_partial (struct target_ops *ops, + enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, ULONGEST len, + ULONGEST *xfered_len); + +class target_ops_no_register : public test_target_ops +{ +public: + target_ops_no_register () + : test_target_ops {} + { + to_fetch_registers = test_target_fetch_registers; + to_store_registers = test_target_store_registers; + to_xfer_partial = test_target_xfer_partial; + + to_data = this; + } + + void reset () + { + fetch_registers_called = 0; + store_registers_called = 0; + xfer_partial_called = 0; + } + + unsigned int fetch_registers_called = 0; + unsigned int store_registers_called = 0; + unsigned int xfer_partial_called = 0; +}; + +static void +test_target_fetch_registers (target_ops *self, regcache *regs, int regno) +{ + auto ops = static_cast (self->to_data); + + /* Mark register available. */ + regs->raw_supply_zeroed (regno); + ops->fetch_registers_called++; +} + +static void +test_target_store_registers (target_ops *self, regcache *regs, int regno) +{ + auto ops = static_cast (self->to_data); + + ops->store_registers_called++; +} + +static enum target_xfer_status +test_target_xfer_partial (struct target_ops *self, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, ULONGEST len, ULONGEST *xfered_len) +{ + auto ops = static_cast (self->to_data); + + ops->xfer_partial_called++; + + *xfered_len = len; + return TARGET_XFER_OK; +} + +class readwrite_regcache : public regcache +{ +public: + readwrite_regcache (struct gdbarch *gdbarch) + : regcache (gdbarch, nullptr, false) + {} +}; + +/* Test regcache::cooked_read gets registers from raw registers and + memory instead of target to_{fetch,store}_registers. */ + +static void +cooked_read_test (struct 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")); + + /* Create a mock environment. An inferior with a thread, with a + process_stratum target pushed. */ + + target_ops_no_register 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); + + /* Test that read one raw register from regcache_no_target will go + to the target layer. */ + int regnum; + + /* Find a raw register which size isn't zero. */ + for (regnum = 0; regnum < gdbarch_num_regs (gdbarch); regnum++) + { + if (register_size (gdbarch, regnum) != 0) + break; + } + + readwrite_regcache readwrite (gdbarch); + gdb::def_vector buf (register_size (gdbarch, regnum)); + + readwrite.raw_read (regnum, buf.data ()); + + /* raw_read calls target_fetch_registers. */ + SELF_CHECK (mock_target.fetch_registers_called > 0); + mock_target.reset (); + + /* Mark all raw registers valid, so the following raw registers + accesses won't go to target. */ + for (auto i = 0; i < gdbarch_num_regs (gdbarch); i++) + readwrite.raw_update (i); + + mock_target.reset (); + /* Then, read all raw and pseudo registers, and don't expect calling + to_{fetch,store}_registers. */ + for (int regnum = 0; + regnum < gdbarch_num_regs (gdbarch) + gdbarch_num_pseudo_regs (gdbarch); + regnum++) + { + if (register_size (gdbarch, regnum) == 0) + continue; + + gdb::def_vector buf (register_size (gdbarch, regnum)); + + SELF_CHECK (REG_VALID == readwrite.cooked_read (regnum, buf.data ())); + + if (gdbarch_bfd_arch_info (gdbarch)->arch != bfd_arch_mt) + { + /* MT pseudo registers are banked, and different banks are + selected by a raw registers, so GDB needs to write to + that raw register to get different banked pseudo registers. + See mt_select_coprocessor. */ + SELF_CHECK (mock_target.fetch_registers_called == 0); + SELF_CHECK (mock_target.store_registers_called == 0); + } + + /* Some SPU pseudo registers are got via TARGET_OBJECT_SPU. */ + if (gdbarch_bfd_arch_info (gdbarch)->arch != bfd_arch_spu) + SELF_CHECK (mock_target.xfer_partial_called == 0); + + mock_target.reset (); + } +} + } // namespace selftests #endif /* GDB_SELF_TEST */ @@ -1722,5 +1910,8 @@ Takes an optional file parameter."), #if GDB_SELF_TEST selftests::register_test ("current_regcache", selftests::current_regcache_test); + + selftests::register_test_foreach_arch ("regcache::cooked_read_test", + selftests::cooked_read_test); #endif } diff --git a/gdb/target.c b/gdb/target.c index 2e02a774e62..3bfc8b5aefa 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -4018,6 +4018,53 @@ set_write_memory_permission (const char *args, int from_tty, update_observer_mode (); } +#if GDB_SELF_TEST +namespace selftests { + +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) +{ +} + +test_target_ops::test_target_ops () + : target_ops {} +{ + 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; + + complete_target_initialization (this); +} + +} // namespace selftests +#endif /* GDB_SELF_TEST */ void initialize_targets (void) diff --git a/gdb/target.h b/gdb/target.h index 1683af64d56..0d1e7bd6e8d 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -2523,4 +2523,18 @@ extern void target_prepare_to_generate_core (void); /* See to_done_generating_core. */ extern void target_done_generating_core (void); +#if GDB_SELF_TEST +namespace selftests { + +/* A mock process_stratum target_ops that doesn't read/write registers + anywhere. */ + +class test_target_ops : public target_ops +{ +public: + test_target_ops (); +}; +} // namespace selftests +#endif /* GDB_SELF_TEST */ + #endif /* !defined (TARGET_H) */ -- 2.30.2