blob: 60921e411a825bdee48c4e0391043fde30b92d25 [file] [log] [blame]
/*
* 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);
}