| /* |
| * 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 <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); |
| } |