From 958be915082e9bd5ccfde2a2f0cf2be2aa024b41 Mon Sep 17 00:00:00 2001 From: Gabe Black Date: Thu, 22 Oct 2020 20:00:44 -0700 Subject: [PATCH] util: Add a unit test for the "addr" call type in the m5 util. This verifies that the slightly more complex --addr command line option behaves as expected. Also, like the inst and semi call type unit tests, it will either attempt to successfully perform a call to the "sum" m5 op if it's told it's running under gem5, or it will attempt to catch itself failing to run that command by using mprotect to block its access to the mmap-ed region and then looks at the siginfo_t to make sure the attempted access was to the right place, etc. It also will attempt to verify the details of the mmap if possible by looking up information about its own mmap-ings in /proc. If the file it would expect to find the mappings in doesn't exist, it prints a warning and gives up. If it does, it looks through it to find the line corresponding to the m5 ops, and then checks some details of the mapping like its size and its offset in the target file. The offset would correspond to the physical address if using the real /dev/mem. Change-Id: Icc14cd9ac02eae93c56f1f2aa78fd67d8540a2f2 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/27751 Reviewed-by: Giacomo Travaglini Maintainer: Gabe Black Tested-by: kokoro --- util/m5/src/call_type/addr.test.cc | 429 +++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) diff --git a/util/m5/src/call_type/addr.test.cc b/util/m5/src/call_type/addr.test.cc index 42b7341e9..60921e411 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); } -- 2.30.2