diff --git a/util/m5/src/call_type/addr.test.cc b/util/m5/src/call_type/addr.test.cc index 42b7341e9f..60921e411a 100644 --- a/util/m5/src/call_type/addr.test.cc +++ b/util/m5/src/call_type/addr.test.cc @@ -26,9 +26,438 @@ */ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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); }