util: Add a unit test for the "addr" call type in the m5 util.
authorGabe Black <gabe.black@gmail.com>
Fri, 23 Oct 2020 03:00:44 +0000 (20:00 -0700)
committerGabe Black <gabe.black@gmail.com>
Tue, 3 Nov 2020 01:59:30 +0000 (01:59 +0000)
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 <giacomo.travaglini@arm.com>
Maintainer: Gabe Black <gabe.black@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
util/m5/src/call_type/addr.test.cc

index 42b7341e9f8e6fb7569b4b1223297bdfc832370e..60921e411a825bdee48c4e0391043fde30b92d25 100644 (file)
  */
 
 #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);
 }