*/
#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <csetjmp>
+#include <csignal>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <gem5/asm/generic/m5ops.h>
+
+#include "args.hh"
#include "call_type.hh"
+#include "dispatch_table.hh"
+#include "m5_mmap.h"
+
+class DefaultCallType : public CallType
+{
+ private:
+ DispatchTable dt;
+
+ public:
+ DefaultCallType() : CallType("default") {}
+
+ bool initCalled = false;
+ void init() override { initCalled = true; }
+
+ bool isDefault() const override { return true; }
+ void printDesc(std::ostream &os) const override {}
+ const DispatchTable &getDispatch() const override { return dt; }
+};
+
+DefaultCallType defaultCallType;
+
+#if defined(M5OP_ADDR)
+const bool DefaultAddrDefined = true;
+constexpr uint64_t DefaultAddress = M5OP_ADDR;
+#else
+const bool DefaultAddrDefined = false;
+constexpr uint64_t DefaultAddress = 0;
+#endif
+
+class AddrCallTypeTest : public testing::Test
+{
+ protected:
+ CallType *ct = nullptr;
+
+ void
+ SetUp() override
+ {
+ m5_mmap_dev = "/dev/zero";
+ m5op_addr = 2;
+ }
+
+ void
+ TearDown() override
+ {
+ unmap_m5_mem();
+ }
+};
+
+TEST_F(AddrCallTypeTest, EmptyArgs)
+{
+ // Addr should not be selected if there are no arguments.
+ Args empty({});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(empty);
+ EXPECT_EQ(ct, &defaultCallType);
+ EXPECT_TRUE(defaultCallType.initCalled);
+}
+
+TEST_F(AddrCallTypeTest, OneArgMismatch)
+{
+ // Addr should not be selected if --addr isn't the first argument.
+ Args one_arg({"one"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(one_arg);
+ EXPECT_EQ(ct, &defaultCallType);
+ EXPECT_TRUE(defaultCallType.initCalled);
+ EXPECT_EQ(one_arg.size(), 1);
+}
+
+TEST_F(AddrCallTypeTest, OneArgSelected)
+{
+ // Addr should be selected if --addr is the first argument.
+ Args selected({"--addr=3"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(selected);
+ EXPECT_NE(ct, &defaultCallType);
+ EXPECT_NE(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, 3);
+}
+
+TEST_F(AddrCallTypeTest, SplitSelected)
+{
+ Args split({"--addr", "3"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(split);
+ EXPECT_NE(ct, &defaultCallType);
+ EXPECT_NE(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, 3);
+}
+
+TEST_F(AddrCallTypeTest, OneArgSelectedExtra)
+{
+ Args selected_extra({"--addr=3", "foo"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(selected_extra);
+ EXPECT_NE(ct, &defaultCallType);
+ EXPECT_NE(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, 3);
+}
+
+TEST_F(AddrCallTypeTest, SplitSelectedExtra)
+{
+ Args split_extra({"--addr", "3", "foo"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(split_extra);
+ EXPECT_NE(ct, &defaultCallType);
+ EXPECT_NE(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, 3);
+}
+
+TEST_F(AddrCallTypeTest, SupersetOneArg)
+{
+ // Nothing should be selected if an argument starts with --addr which is
+ // followed by something other than '=' and then a number.
+ Args no_equal({"--address"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(no_equal);
+ EXPECT_EQ(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, 2);
+}
+
+TEST_F(AddrCallTypeTest, NonNumberAddr)
+{
+ Args no_number({"--addr=foo"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(no_number);
+ EXPECT_EQ(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, 2);
+}
+
+TEST_F(AddrCallTypeTest, DetectDefaultAddr)
+{
+ if (!DefaultAddrDefined)
+ return;
+
+ // Verify that the default address is set up in m5op_addr.
+ Args noaddr({"--addr"});
+ defaultCallType.initCalled = false;
+ m5op_addr = DefaultAddress;
+ ct = CallType::detect(noaddr);
+ EXPECT_NE(ct, &defaultCallType);
+ EXPECT_NE(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, DefaultAddress);
+}
+
+TEST_F(AddrCallTypeTest, DetectDefaultAddrExtra)
+{
+ if (!DefaultAddrDefined)
+ return;
+
+ Args noaddr_foo({"--addr", "foo"});
+ defaultCallType.initCalled = false;
+ m5op_addr = DefaultAddress;
+ ct = CallType::detect(noaddr_foo);
+ EXPECT_NE(ct, &defaultCallType);
+ EXPECT_NE(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, DefaultAddress);
+}
+
+TEST_F(AddrCallTypeTest, DetectNoDefault)
+{
+ if (DefaultAddrDefined)
+ return;
+
+ // Verify that the address must be specified since there's no default.
+ Args noaddr({"--addr"});
+ defaultCallType.initCalled = false;
+ m5op_addr = DefaultAddress + 1;
+ ct = CallType::detect(noaddr);
+ EXPECT_EQ(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, DefaultAddress + 1);
+}
+
+TEST_F(AddrCallTypeTest, DetectNoDefaultExtra)
+{
+ if (DefaultAddrDefined)
+ return;
+
+ Args noaddr_foo({"--addr", "foo"});
+ defaultCallType.initCalled = false;
+ m5op_addr = DefaultAddress + 1;
+ ct = CallType::detect(noaddr_foo);
+ EXPECT_EQ(ct, nullptr);
+ EXPECT_FALSE(defaultCallType.initCalled);
+ EXPECT_EQ(m5op_addr, DefaultAddress + 1);
+}
+
+TEST_F(AddrCallTypeTest, NotFirstArg)
+{
+ // Addr should not be selected if --addr isn't first.
+ Args not_first({"foo", "--addr"});
+ defaultCallType.initCalled = false;
+ ct = CallType::detect(not_first);
+ EXPECT_EQ(ct, &defaultCallType);
+ EXPECT_TRUE(defaultCallType.initCalled);
+ EXPECT_EQ(not_first.size(), 2);
+}
+
+sigjmp_buf interceptEnv;
+siginfo_t interceptSiginfo;
+
+void
+sigsegv_handler(int sig, siginfo_t *info, void *ucontext)
+{
+ std::memcpy(&interceptSiginfo, info, sizeof(interceptSiginfo));
+ siglongjmp(interceptEnv, 1);
+}
+
+const uint64_t MmapPhysAddr = 0x1000000;
+
+// A class to create and clean up a sparse temporary file of a given size.
+class TempFile
+{
+ private:
+ size_t _size;
+ int fd;
+ std::string _path;
+
+ public:
+ TempFile(size_t _size) : _size(_size)
+ {
+ // Generate a temporary filename.
+ char *tmp_name = strdup("/tmp/addr.test.XXXXXXXX");
+ fd = mkstemp(tmp_name);
+ _path = tmp_name;
+ free(tmp_name);
+
+ // Make the file the appropriate length.
+ assert(!ftruncate(fd, _size));
+ };
+
+ ~TempFile()
+ {
+ unlink(path().c_str());
+ close(fd);
+ }
+
+ const std::string &path() const { return _path; }
+};
+
+// Sparse dummy mmap file if we're not in gem5.
+TempFile mmapDummyFile(MmapPhysAddr * 2);
+
+void
+verify_mmap()
+{
+ // Look for the proc file that lists all our mmap-ed files.
+ pid_t pid = getpid();
+
+ std::ostringstream os;
+ os << "/proc/" << pid << "/maps";
+ auto maps_path = os.str();
+
+ if (access(maps_path.c_str(), R_OK) == -1) {
+ std::cout << "Unable to access " << maps_path <<
+ ", can't verify mmap." << std::endl;
+ return;
+ }
+
+ // Verify that the right area is mmap-ed.
+ std::ifstream maps(maps_path);
+ EXPECT_TRUE(maps);
+
+ uint64_t start, end, offset, inode;
+ std::string path, permissions, device;
+
+ std::istringstream line_ss;
+ bool found = false;
+ while (!maps.eof()) {
+ std::string line;
+ if (getline(maps, line).fail()) {
+ std::cout << "Error reading from \"" << maps_path << "\"." <<
+ std::endl;
+ return;
+ }
+
+ line_ss.str(line);
+
+ line_ss >> std::hex >> start >> std::dec;
+
+ char c;
+ line_ss.get(c);
+ if (c != '-') {
+ std::cout << "Badly formatted maps line." << std::endl;
+ continue;
+ }
+
+ // Is this the mapping we're interested in?
+ if (start == (uintptr_t)m5_mem) {
+ found = true;
+ break;
+ }
+ }
+
+ if (maps.eof() && !found) {
+ std::cout << "Did not find entry for temp file \"" <<
+ mmapDummyFile.path() << "\" in \"" << maps_path <<
+ "\"." << std::endl;
+ ADD_FAILURE() << "No mapping for our mmapped file.";
+ return;
+ }
+
+ // We found our mapping. Try to extract the remaining fields.
+ line_ss >> std::hex >> end >> std::dec;
+ line_ss >> permissions;
+ line_ss >> std::hex >> offset >> std::dec;
+ line_ss >> device;
+ line_ss >> inode;
+
+ // Everything left on the line goes into "path".
+ getline(line_ss, path);
+
+ // Strip off whitespace on either end of the path.
+ const char *ws = " \t\n\r\f\v";
+ // If nothing would be left, don't bother.
+ if (path.find_first_not_of(ws) != std::string::npos) {
+ path.erase(path.find_last_not_of(ws) + 1);
+ path.erase(0, path.find_first_not_of(ws));
+ }
+
+ // Use stat to make sure this is the right file, in case the path
+ // strings are immaterially different.
+ struct stat stata, statb;
+ EXPECT_EQ(stat(path.c_str(), &stata), 0);
+ EXPECT_EQ(stat(mmapDummyFile.path().c_str(), &statb), 0);
+ EXPECT_EQ(stata.st_dev, statb.st_dev);
+ EXPECT_EQ(stata.st_ino, statb.st_ino);
+
+ // Make sure the mapping is in the right place and the right size.
+ EXPECT_EQ(end - start, 0x10000);
+ EXPECT_EQ(offset, MmapPhysAddr);
+}
TEST(AddrCallType, Sum)
{
+ // Determine if we're running within gem5 by checking whether a flag is
+ // set in the environment.
+ bool in_gem5 = (std::getenv("RUNNING_IN_GEM5") != nullptr);
+ if (in_gem5)
+ std::cout << "In gem5, m5 ops should work." << std::endl;
+ else
+ std::cout << "Not in gem5, m5 ops won't work." << std::endl;
+
+ // Get the addr call type, which is in an anonymous namespace. Set the
+ // address to a well known constant that's nicely aligned.
+ Args args({"--addr=0x1000000"});
+ if (!in_gem5) {
+ // Change the file to be mmap-ed to something not dangerous, but only
+ // if we're not in gem5. Otherwise we'll need this to really work.
+ m5_mmap_dev = mmapDummyFile.path().c_str();
+ }
+ CallType *addr_call_type = CallType::detect(args);
+ EXPECT_NE(addr_call_type, nullptr);
+ EXPECT_EQ(m5op_addr, MmapPhysAddr);
+
+ verify_mmap();
+
+ // Get the dispatch table associated with it.
+ const auto &dt = addr_call_type->getDispatch();
+
+ // If we're in gem5, then we should be able to run the "sum" command.
+ if (in_gem5) {
+ EXPECT_EQ((*dt.m5_sum)(2, 2, 0, 0, 0, 0), 4);
+ return;
+ }
+
+ // If not, then we'll need to try to catch the fall out from trying to run
+ // an m5 op and verify that what we were trying looks correct.
+
+ // Block access to the page that was mapped.
+ mprotect(m5_mem, 0x10000, 0);
+
+ struct sigaction sigsegv_action;
+ std::memset(&sigsegv_action, 0, sizeof(sigsegv_action));
+ sigsegv_action.sa_sigaction = &sigsegv_handler;
+ sigsegv_action.sa_flags = SA_SIGINFO | SA_RESETHAND;
+
+ struct sigaction old_sigsegv_action;
+
+ sigaction(SIGSEGV, &sigsegv_action, &old_sigsegv_action);
+
+ if (!sigsetjmp(interceptEnv, 1)) {
+ (*dt.m5_sum)(2, 2, 0, 0, 0, 0);
+ sigaction(SIGSEGV, &old_sigsegv_action, nullptr);
+ ADD_FAILURE() << "Didn't die when attempting to run \"sum\".";
+ return;
+ }
+
+ // Restore access to the page that was mapped.
+ mprotect(m5_mem, 0x10000, PROT_READ | PROT_WRITE);
+
+ // Back from siglongjump.
+ auto &info = interceptSiginfo;
+
+ EXPECT_EQ(info.si_signo, SIGSEGV);
+ EXPECT_EQ(info.si_code, SEGV_ACCERR);
+
+ uintptr_t access_addr = (uintptr_t)info.si_addr;
+ uintptr_t virt_addr = (uintptr_t)m5_mem;
+
+ // Verify that the address was in the right area.
+ EXPECT_LT(access_addr, virt_addr + 0x10000);
+ EXPECT_GE(access_addr, virt_addr);
+
+ // Extract the func number.
+ uintptr_t offset = access_addr - virt_addr;
+ int func = (offset & 0xff00) >> 8;
+ EXPECT_EQ(func, M5OP_SUM);
}