--- /dev/null
+/*
+ * Copyright 2020 Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "sim/proxy_ptr.hh"
+
+struct Access
+{
+ bool read;
+ Addr addr;
+ Addr size;
+
+ Access(bool _read, Addr _addr, Addr _size) :
+ read(_read), addr(_addr), size(_size)
+ {}
+
+ bool
+ operator == (const Access &other) const
+ {
+ return read == other.read &&
+ addr == other.addr &&
+ size == other.size;
+ }
+
+ bool
+ operator != (const Access &other) const
+ {
+ return !(*this == other);
+ }
+};
+
+using Accesses = std::vector<Access>;
+
+class BackingStore
+{
+ public:
+ std::vector<uint8_t> store;
+ Addr base;
+
+ BackingStore(Addr _base, size_t _size) : store(_size, 0), base(_base) {}
+
+ void
+ rangeCheck(Addr addr, Addr size)
+ {
+ panic_if(addr < base || addr + size > base + store.size(),
+ "Range [%#x,%#x) outside of [%#x,%#x).",
+ addr, addr + size, base, base + store.size());
+ }
+
+ mutable Accesses accesses;
+
+ ::testing::AssertionResult
+ expect_access(size_t idx, const Access &other) const
+ {
+ if (idx >= accesses.size()) {
+ return ::testing::AssertionFailure() << "index " << idx <<
+ " out of bounds";
+ }
+
+ if (accesses[idx] != other) {
+ return ::testing::AssertionFailure() << "access[" << idx <<
+ "] was " << accesses[idx] << ", expected " << other;
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ ::testing::AssertionResult
+ expect_accesses(Accesses expected) const
+ {
+ if (accesses.size() != expected.size()) {
+ return ::testing::AssertionFailure() <<
+ "Wrong number of accesses, was " << accesses.size() <<
+ " expected " << expected.size();
+ }
+
+ auto failure = ::testing::AssertionFailure();
+ bool success = true;
+ if (accesses.size() == expected.size()) {
+ for (size_t idx = 0; idx < expected.size(); idx++) {
+ auto result = expect_access(idx, expected[idx]);
+ if (!result) {
+ failure << result.message();
+ success = false;
+ }
+ }
+ }
+
+ if (!success)
+ return failure;
+ else
+ return ::testing::AssertionSuccess();
+ }
+
+ void
+ writeBlob(Addr ptr, const void *data, int size)
+ {
+ rangeCheck(ptr, size);
+ accesses.emplace_back(false, ptr, size);
+ memcpy(store.data() + (ptr - base), data, size);
+ }
+
+ void
+ readBlob(Addr ptr, void *data, int size)
+ {
+ rangeCheck(ptr, size);
+ accesses.emplace_back(true, ptr, size);
+ memcpy(data, store.data() + (ptr - base), size);
+ }
+};
+
+::testing::AssertionResult
+accessed(const char *expr1, const char *expr2,
+ const BackingStore &store, const Accesses &expected)
+{
+ return store.expect_accesses(expected);
+}
+
+#define EXPECT_ACCESSES(store, ...) \
+ do { \
+ Accesses expected({__VA_ARGS__}); \
+ EXPECT_PRED_FORMAT2(accessed, store, expected); \
+ store.accesses.clear(); \
+ } while (false)
+
+std::ostream &
+operator << (std::ostream &os, const Access &access)
+{
+ ccprintf(os, "%s(%#x, %d)", access.read ? "read" : "write",
+ access.addr, access.size);
+ return os;
+}
+
+class TestProxy
+{
+ public:
+ BackingStore &store;
+
+ TestProxy(BackingStore &_store) : store(_store) {}
+ // Sneaky constructor for testing GuestABI integration.
+ TestProxy(ThreadContext *tc) : store(*(BackingStore *)tc) {}
+
+ void
+ writeBlob(Addr ptr, const void *data, int size)
+ {
+ store.writeBlob(ptr, data, size);
+ }
+
+ void
+ readBlob(Addr ptr, void *data, int size)
+ {
+ store.readBlob(ptr, data, size);
+ }
+};
+
+template <typename T>
+using TestPtr = ProxyPtr<T, TestProxy>;
+
+template <typename T>
+using ConstTestPtr = ConstProxyPtr<T, TestProxy>;
+
+TEST(ProxyPtr, Clean)
+{
+ BackingStore store(0x1000, 0x1000);
+
+ EXPECT_ACCESSES(store);
+
+ {
+ ConstTestPtr<uint32_t> test_ptr(0x1100, store);
+
+ EXPECT_ACCESSES(store, { true, test_ptr.addr(), sizeof(uint32_t) });
+ }
+
+ EXPECT_ACCESSES(store);
+
+ {
+ TestPtr<uint32_t> test_ptr(0x1100, store);
+
+ EXPECT_ACCESSES(store, { true, test_ptr.addr(), sizeof(uint32_t) });
+ }
+
+ EXPECT_ACCESSES(store);
+}
+
+TEST(ProxyPtr, Dirty)
+{
+ BackingStore store(0x1000, 0x1100);
+
+ EXPECT_ACCESSES(store);
+
+ {
+ TestPtr<uint32_t> test_ptr(0x1100, store);
+
+ *test_ptr = 0xa5a5a5a5;
+
+ EXPECT_ACCESSES(store, { true, test_ptr.addr(), sizeof(uint32_t) });
+ }
+
+ EXPECT_ACCESSES(store, { false, 0x1100, sizeof(uint32_t) });
+ EXPECT_EQ(store.store[0x100], 0xa5);
+ EXPECT_EQ(store.store[0x101], 0xa5);
+ EXPECT_EQ(store.store[0x102], 0xa5);
+ EXPECT_EQ(store.store[0x103], 0xa5);
+}
+
+
+TEST(ProxyPtr, LoadAndFlush)
+{
+ BackingStore store(0x1000, 0x1100);
+
+ store.store[0x100] = 0xa5;
+ store.store[0x101] = 0xa5;
+ store.store[0x102] = 0xa5;
+ store.store[0x103] = 0xa5;
+
+ TestPtr<uint32_t> test_ptr(0x1100, store);
+
+ // Check that the backing store is unmodified.
+ EXPECT_EQ(store.store[0x100], 0xa5);
+ EXPECT_EQ(store.store[0x101], 0xa5);
+ EXPECT_EQ(store.store[0x102], 0xa5);
+ EXPECT_EQ(store.store[0x103], 0xa5);
+
+ // Change the value in our local buffered copy.
+ *test_ptr = 0x5a5a5a5a;
+
+ // Verify that the backing store hasn't been changed.
+ EXPECT_EQ(store.store[0x100], 0xa5);
+ EXPECT_EQ(store.store[0x101], 0xa5);
+ EXPECT_EQ(store.store[0x102], 0xa5);
+ EXPECT_EQ(store.store[0x103], 0xa5);
+
+ // Flush out our modifications.
+ test_ptr.flush();
+
+ // Verify that they've been written back to the store.
+ EXPECT_EQ(store.store[0x100], 0x5a);
+ EXPECT_EQ(store.store[0x101], 0x5a);
+ EXPECT_EQ(store.store[0x102], 0x5a);
+ EXPECT_EQ(store.store[0x103], 0x5a);
+
+ // Update the store and try to flush again.
+ store.store[0x100] = 0xaa;
+ test_ptr.flush();
+
+ // Verify that no flush happened, since our ptr was "clean".
+ EXPECT_EQ(store.store[0x100], 0xaa);
+
+ // Force a flush.
+ test_ptr.flush(true);
+
+ // Verify that the flush happened even though the ptr was "clean".
+ EXPECT_EQ(store.store[0x100], 0x5a);
+
+ // Update the store.
+ store.store[0x100] = 0xa5;
+ store.store[0x101] = 0xa5;
+ store.store[0x102] = 0xa5;
+ store.store[0x103] = 0xa5;
+
+ // Verify that our local copy hasn't changed.
+ EXPECT_EQ(*(const uint32_t *)test_ptr, 0x5a5a5a5a);
+
+ // Reload the pointer from the store.
+ test_ptr.load();
+ EXPECT_EQ(*(const uint32_t *)test_ptr, 0xa5a5a5a5);
+}
+
+TEST(ProxyPtr, ConstOperators)
+{
+ bool is_same;
+
+ BackingStore store(0x1000, 0x1000);
+
+ const Addr addr1 = 0x1100;
+ const Addr addr2 = 0x1200;
+
+ using PtrType = uint32_t;
+
+ ConstTestPtr<PtrType> test_ptr1(addr1, store);
+ EXPECT_EQ(test_ptr1.addr(), addr1);
+
+ ConstTestPtr<PtrType> test_ptr2(addr2, store);
+ EXPECT_EQ(test_ptr2.addr(), addr2);
+
+ // Pointer +/- integer.
+ auto next_ptr = test_ptr1 + 2;
+ EXPECT_EQ(next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
+
+ auto reverse_next_ptr = 2 + test_ptr1;
+ EXPECT_EQ(reverse_next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
+
+ auto prev_ptr = test_ptr1 - 2;
+ EXPECT_EQ(prev_ptr.addr(), addr1 - 2 * sizeof(PtrType));
+
+ // Pointer-pointer subtraction.
+ auto diff = test_ptr2 - test_ptr1;
+ EXPECT_EQ(diff, (addr2 - addr1) / sizeof(PtrType));
+
+ // Assignment.
+ ConstTestPtr<PtrType> target(addr2, store);
+ EXPECT_EQ(target.addr(), addr2);
+
+ target = test_ptr1;
+ EXPECT_EQ(target.addr(), addr1);
+
+ // Conversions.
+ EXPECT_TRUE(test_ptr1);
+ ConstTestPtr<PtrType> null(0, store);
+ EXPECT_FALSE(null);
+
+ EXPECT_NE((const PtrType *)test_ptr1, nullptr);
+ EXPECT_EQ((const PtrType *)null, nullptr);
+
+ // Dereferences.
+ is_same = std::is_same<decltype(*test_ptr1), const PtrType &>::value;
+ EXPECT_TRUE(is_same);
+
+ store.store[0x100] = 0x55;
+ store.store[0x101] = 0x55;
+ store.store[0x102] = 0x55;
+ store.store[0x103] = 0x55;
+
+ // Force an update since we changed the backing store behind our ptrs back.
+ test_ptr1.load();
+
+ EXPECT_EQ(*test_ptr1, 0x55555555);
+
+ store.store[0x100] = 0x11;
+ store.store[0x101] = 0x22;
+ store.store[0x102] = 0x33;
+ store.store[0x103] = 0x44;
+
+ struct TestStruct
+ {
+ uint8_t a;
+ uint8_t b;
+ uint8_t c;
+ uint8_t d;
+ };
+
+ ConstTestPtr<TestStruct> struct_ptr(addr1, store);
+ EXPECT_EQ(struct_ptr->a, 0x11);
+ EXPECT_EQ(struct_ptr->b, 0x22);
+ EXPECT_EQ(struct_ptr->c, 0x33);
+ EXPECT_EQ(struct_ptr->d, 0x44);
+
+ is_same = std::is_same<decltype((struct_ptr->a)), const uint8_t &>::value;
+ EXPECT_TRUE(is_same);
+}
+
+TEST(ProxyPtr, NonConstOperators)
+{
+ bool is_same;
+
+ BackingStore store(0x1000, 0x1000);
+
+ const Addr addr1 = 0x1100;
+ const Addr addr2 = 0x1200;
+
+ using PtrType = uint32_t;
+
+ TestPtr<PtrType> test_ptr1(addr1, store);
+ EXPECT_EQ(test_ptr1.addr(), addr1);
+
+ TestPtr<PtrType> test_ptr2(addr2, store);
+ EXPECT_EQ(test_ptr2.addr(), addr2);
+
+ // Pointer +/- integer.
+ auto next_ptr = test_ptr1 + 2;
+ EXPECT_EQ(next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
+
+ auto reverse_next_ptr = 2 + test_ptr1;
+ EXPECT_EQ(reverse_next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
+
+ auto prev_ptr = test_ptr1 - 2;
+ EXPECT_EQ(prev_ptr.addr(), addr1 - 2 * sizeof(PtrType));
+
+ // Pointer-pointer subtraction.
+ auto diff = test_ptr2 - test_ptr1;
+ EXPECT_EQ(diff, (addr2 - addr1) / sizeof(PtrType));
+
+ // Assignment.
+ TestPtr<PtrType> target(addr2, store);
+ EXPECT_EQ(target.addr(), addr2);
+
+ target = test_ptr1;
+ EXPECT_EQ(target.addr(), addr1);
+
+ // Conversions.
+ EXPECT_TRUE(test_ptr1);
+ TestPtr<PtrType> null(0, store);
+ EXPECT_FALSE(null);
+
+ EXPECT_NE((PtrType *)test_ptr1, nullptr);
+ EXPECT_EQ((PtrType *)null, nullptr);
+ EXPECT_NE((const PtrType *)test_ptr1, nullptr);
+ EXPECT_EQ((const PtrType *)null, nullptr);
+
+ // Dereferences.
+ is_same = std::is_same<decltype(*test_ptr1), PtrType &>::value;
+ EXPECT_TRUE(is_same);
+
+ // Flush test_ptr1, which has been conservatively marked as dirty.
+ test_ptr1.flush();
+
+ store.store[0x100] = 0x55;
+ store.store[0x101] = 0x55;
+ store.store[0x102] = 0x55;
+ store.store[0x103] = 0x55;
+
+ // Force an update since we changed the backing store behind our ptrs back.
+ test_ptr1.load();
+
+ EXPECT_EQ(*test_ptr1, 0x55555555);
+
+ store.store[0x100] = 0x11;
+ store.store[0x101] = 0x22;
+ store.store[0x102] = 0x33;
+ store.store[0x103] = 0x44;
+
+ struct TestStruct
+ {
+ uint8_t a;
+ uint8_t b;
+ uint8_t c;
+ uint8_t d;
+ };
+
+ TestPtr<TestStruct> struct_ptr(addr1, store);
+ EXPECT_EQ(struct_ptr->a, 0x11);
+ EXPECT_EQ(struct_ptr->b, 0x22);
+ EXPECT_EQ(struct_ptr->c, 0x33);
+ EXPECT_EQ(struct_ptr->d, 0x44);
+
+ is_same = std::is_same<decltype((struct_ptr->a)), uint8_t &>::value;
+ EXPECT_TRUE(is_same);
+}
+
+struct TestABI
+{
+ using State = int;
+};
+
+namespace GuestABI
+{
+
+template <>
+struct Argument<TestABI, Addr>
+{
+ static Addr
+ get(ThreadContext *tc, typename TestABI::State &state)
+ {
+ return 0x1000;
+ }
+};
+
+}
+
+bool abiCalled = false;
+bool abiCalledConst = false;
+
+void
+abiTestFunc(ThreadContext *tc, TestPtr<uint8_t> ptr)
+{
+ abiCalled = true;
+ EXPECT_EQ(ptr.addr(), 0x1000);
+}
+
+void
+abiTestFuncConst(ThreadContext *tc, ConstTestPtr<uint8_t> ptr)
+{
+ abiCalledConst = true;
+ EXPECT_EQ(ptr.addr(), 0x1000);
+}
+
+TEST(ProxyPtr, GuestABI)
+{
+ BackingStore store(0x1000, 0x1000);
+
+ EXPECT_FALSE(abiCalled);
+ EXPECT_FALSE(abiCalledConst);
+
+ invokeSimcall<TestABI>((ThreadContext *)&store, abiTestFunc);
+
+ EXPECT_TRUE(abiCalled);
+ EXPECT_FALSE(abiCalledConst);
+
+ invokeSimcall<TestABI>((ThreadContext *)&store, abiTestFuncConst);
+
+ EXPECT_TRUE(abiCalled);
+ EXPECT_TRUE(abiCalledConst);
+}