diff --git a/src/processorhandler.h b/src/processorhandler.h index 19f7b087b..b2b5f7ca4 100644 --- a/src/processorhandler.h +++ b/src/processorhandler.h @@ -62,11 +62,16 @@ class ProcessorHandler : public QObject { return get()->_fullISA(); } - /// Returns a reference to the system call manager. + /// Returns a const reference to the system call manager. static const SyscallManager &getSyscallManager() { return get()->_getSyscallManager(); } + /// Returns a non-const reference to the system call manager. + static SyscallManager &getSyscallManagerNonConst() { + return get()->_getSyscallManagerNonConst(); + } + /// Sets the program p as the currently instantiated program. static void loadProgram(const std::shared_ptr &p) { get()->_loadProgram(p); @@ -294,6 +299,9 @@ private slots: return m_currentProcessor->fullISA(); } const SyscallManager &_getSyscallManager() const { return *m_syscallManager; } + SyscallManager &_getSyscallManagerNonConst() const { + return *m_syscallManager; + } void _loadProcessorToWidget(vsrtl::VSRTLWidget *widget, bool doPlaceAndRoute = false); void _selectProcessor( diff --git a/src/statusmanager.h b/src/statusmanager.h index 0637c1dd7..0af9b5527 100644 --- a/src/statusmanager.h +++ b/src/statusmanager.h @@ -12,15 +12,17 @@ namespace Ripes { /** * @brief postToGUIThread - * Schedules the execution of @param fun in the GUI thread. + * Schedules the execution of @param fun in the GUI thread if it exists. * @param connection type. */ template static void postToGUIThread(F &&fun, Qt::ConnectionType type = Qt::QueuedConnection) { - auto *obj = QAbstractEventDispatcher::instance(qApp->thread()); - Q_ASSERT(obj); - QMetaObject::invokeMethod(obj, std::forward(fun), type); + if (qApp) { + auto *obj = QAbstractEventDispatcher::instance(qApp->thread()); + Q_ASSERT(obj); + QMetaObject::invokeMethod(obj, std::forward(fun), type); + } } class StatusEmitter : public QObject { diff --git a/src/syscall/file.h b/src/syscall/file.h index 65be8c856..786445b91 100644 --- a/src/syscall/file.h +++ b/src/syscall/file.h @@ -14,10 +14,11 @@ class OpenSyscall : public BaseSyscall { public: OpenSyscall() - : BaseSyscall("Open", "Opens a file from a path", - {{0, "Pointer to null terminated string for the path"}, - {1, "flags"}}, - {{0, "the file decriptor or -1 if an error occurred"}}) {} + : BaseSyscall( + "Open", "Opens a file from a path", + {{0, "Pointer to null terminated string for the path"}, + {1, "flags" /* TODO(raccog): Add descriptions for each flag */}}, + {{0, "the file decriptor or -1 if an error occurred"}}) {} void execute() { const AInt arg0 = BaseSyscall::getArg(BaseSyscall::REG_FILE, 0); const AInt arg1 = BaseSyscall::getArg(BaseSyscall::REG_FILE, 1); @@ -29,6 +30,8 @@ class OpenSyscall : public BaseSyscall { ProcessorHandler::getMemory().readMemConst(address++, 1) & 0xFF); string.append(byte); } while (byte != '\0'); + if (string.endsWith('\0')) + string.removeLast(); // Remove null-byte int ret = SystemIO::openFile(QString::fromUtf8(string), arg1); diff --git a/src/syscall/systemio.h b/src/syscall/systemio.h index aefa9d439..6fbb9d80b 100644 --- a/src/syscall/systemio.h +++ b/src/syscall/systemio.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -76,13 +77,17 @@ class SystemIO : public QObject { // Maximum number of files that can be open static constexpr int SYSCALL_MAXFILES = 32; - static constexpr int O_RDONLY = 0x00000000; - static constexpr int O_WRONLY = 0x00000001; - static constexpr int O_RDWR = 0x00000002; - static constexpr int O_APPEND = 0x00000008; - static constexpr int O_CREAT = 0x00000200; // 512 - static constexpr int O_TRUNC = 0x00000400; // 1024 - static constexpr int O_EXCL = 0x00000800; // 2048 + /// Flags used in the open syscall + enum Flags : unsigned { + O_RDONLY = 0x00000000, + O_WRONLY = 0x00000001, + O_RDWR = 0x00000002, + O_ACCMODE = 0x00000003, + O_CREAT = 0x00000100, + O_EXCL = 0x00000200, + O_TRUNC = 0x00001000, + O_APPEND = 0x00002000 + }; // ////////////////////////////////////////////////////////////////////////////// // Maintain information on files in use. The index to the arrays is the "file @@ -91,8 +96,7 @@ class SystemIO : public QObject { struct FileIOData { // The filenames in use. Null if file descriptor i is not in use. static std::map fileNames; - // The flags of this file, 0=READ, 1=WRITE. Invalid if this file descriptor - // is not in use. + // The flags of this file. Invalid if this file descriptor is not in use. static std::map fileFlags; // The streams in use, associated with the filenames static std::map streams; @@ -143,31 +147,37 @@ class SystemIO : public QObject { // Open a file stream assigned to the given file descriptor static void openFilestream(int fd, const QString &filename) { - files.emplace(fd, filename); - + // Ensure flags are valid const auto flags = fileFlags[fd]; - const auto qtOpenFlags = // Translate from stdlib file flags to Qt flags - (flags & O_RDONLY ? QIODevice::ReadOnly : QIODevice::NotOpen) | + if ((flags & O_ACCMODE) == O_ACCMODE) { + throw std::runtime_error( + "Tried to open file with incompatible read/write mode flags"); + } + + // Translate from stdlib file flags to Qt flags + auto qtOpenFlags = + ((flags & O_RDONLY) == O_RDONLY ? QIODevice::ReadOnly + : QIODevice::NotOpen) | (flags & O_WRONLY ? QIODevice::WriteOnly : QIODevice::NotOpen) | (flags & O_RDWR ? QIODevice::ReadWrite : QIODevice::NotOpen) | - (flags & O_TRUNC ? QIODevice::Truncate : QIODevice::Append) | - (flags & O_EXCL ? QIODevice::NewOnly : QIODevice::NotOpen); + (flags & O_EXCL ? QIODevice::NewOnly : QIODevice::NotOpen) | + (flags & O_TRUNC ? QIODevice::Truncate : QIODevice::NotOpen) | + (flags & O_APPEND ? QIODevice::Append : QIODevice::NotOpen); // Try to open file with the given flags - files[fd].open(qtOpenFlags); + files.emplace(fd, filename); + auto &file = files[fd]; + file.open(qtOpenFlags); - if (!files[fd].exists() && flags & O_CREAT) { - files.erase(fd); + if (!file.exists() && flags & O_CREAT) { throw std::runtime_error("Could not create file"); } - if (!files[fd].exists()) { - files.erase(fd); + if (!file.exists()) { throw std::runtime_error("File not found"); } - if (!files[fd].isOpen()) { - files.erase(fd); + if (!file.isOpen()) { throw std::runtime_error("File could not be opened"); } @@ -185,14 +195,13 @@ class SystemIO : public QObject { } // Determine whether a given fd is already in use with the given flag. - static bool fdInUse(int fd, int flag) { + static bool fdInUse(int fd, unsigned flag) { if (fd < 0 || fd >= SYSCALL_MAXFILES) { return false; } else if (fileNames[fd].isEmpty()) { return false; - } else if ((fileFlags[fd] & flag) == - static_cast( - flag) /* also compares ie. O_RDONLY (0x0) */) { + } else if ((flag == O_RDONLY) ? (fileFlags[fd] & O_ACCMODE) == flag + : (fileFlags[fd] & flag) == flag) { return true; } return false; @@ -205,7 +214,7 @@ class SystemIO : public QObject { if (fd < STDIO_END || fd >= SYSCALL_MAXFILES) return; - fileFlags[fd] = -1; + fileFlags[fd] = O_ACCMODE; // set flag to invalid read/write mode files[fd].close(); streams.erase(fd); files.erase(fd); @@ -216,7 +225,7 @@ class SystemIO : public QObject { // available file descriptor. Check that filename is not in use, flag is // reasonable, and there is an available file descriptor. Return: file // descriptor in 0...(SYSCALL_MAXFILES-1), or -1 if error - static int nowOpening(const QString &filename, int flag) { + static int nowOpening(const QString &filename, unsigned flag) { int i = 0; if (filenameInUse(filename)) { s_fileErrorString = "File name " + filename + " is already open."; @@ -248,11 +257,11 @@ class SystemIO : public QObject { * Open a file for either reading or writing. * * @param filename string containing filename - * @param flags 0 for read, 1 for write - * @return file descriptor in the range 0 to SYSCALL_MAXFILES-1, or -1 if + * @param flags see SystemIO::Flags enum for all possible flags + * @return file descriptor in the range 0 to SYSCALL_MAXFILES-1, or if * error */ - static int openFile(QString filename, int flags) { + static int openFile(QString filename, unsigned flags) { SystemIO::get(); // Ensure that SystemIO is constructed // Internally, a "file descriptor" is an index into a table // of the filename, flag, and the File???putStream associated with @@ -270,8 +279,10 @@ class SystemIO : public QObject { try { FileIOData::openFilestream(fdToUse, filename); - } catch (int) { - s_fileErrorString = "File " + filename + " could not be opened."; + } catch (const std::runtime_error &error) { + FileIOData::files.erase(fdToUse); + s_fileErrorString = + "File " + filename + " could not be opened: " + error.what(); retValue = -1; } @@ -288,8 +299,10 @@ class SystemIO : public QObject { * @return -1 on error */ static int seek(int fd, int offset, int base) { - SystemIO::get(); // Ensure that SystemIO is constructed - if (!FileIOData::fdInUse(fd, 0)) // Check the existence of the "read" fd + SystemIO::get(); // Ensure that SystemIO is constructed + if (!(FileIOData::fdInUse(fd, O_RDONLY) || + FileIOData::fdInUse(fd, + O_RDWR))) // Check the existence of the "read" fd { s_fileErrorString = "File descriptor " + QString::number(fd) + " is not open for reading"; @@ -330,8 +343,9 @@ class SystemIO : public QObject { ///////////////////////////////////////////////////// /// Read from STDIN file descriptor while using IDE - get input from /// Messages pane. - if (!FileIOData::fdInUse(fd, - O_RDONLY)) // Check the existence of the "read" fd + if (!(FileIOData::fdInUse(fd, O_RDONLY) || + FileIOData::fdInUse(fd, + O_RDWR))) // Check the existence of the "read" fd { s_fileErrorString = "File descriptor " + QString::number(fd) + " is not open for reading"; @@ -405,8 +419,9 @@ class SystemIO : public QObject { return myBuffer.size(); } - if (!FileIOData::fdInUse( - fd, O_WRONLY | O_RDWR)) // Check the existence of the "write" fd + if (!(FileIOData::fdInUse(fd, O_WRONLY) || + FileIOData::fdInUse(fd, + O_RDWR))) // Check the existence of the "write" fd { s_fileErrorString = "File descriptor " + QString::number(fd) + " is not open for writing"; diff --git a/test/riscv-tests-64/ecall_file.S b/test/riscv-tests-64/ecall_file.S new file mode 120000 index 000000000..d4b534f6e --- /dev/null +++ b/test/riscv-tests-64/ecall_file.S @@ -0,0 +1 @@ +../riscv-tests/ecall_file.s \ No newline at end of file diff --git a/test/riscv-tests/ecall_file.s b/test/riscv-tests/ecall_file.s new file mode 100644 index 000000000..b6bf47a6a --- /dev/null +++ b/test/riscv-tests/ecall_file.s @@ -0,0 +1,128 @@ +.text + +#--------------------------------------------------------------- +# Program should write "BBAABBAABBAABBAABBAA" to /tmp/test.txt +# and then read back the file +#--------------------------------------------------------------- + +test_0: # Test open +la a0, FILENAME # a0: Pointer to filename +lw a1, O_RDWR # a1: Open flags +lw t0, O_TRUNC # Truncate file +or a1, a1, t0 # OR flags +lw t0, O_CREAT # Create file if it doesnt exist +or a1, a1, t0 # OR flags +lw a7, OPEN # a7: "Open" ecall +ecall # Returns: File descriptor to a0 +mv s0, a0 # Save file descriptor in s0 +li t0, 3 # File descriptor should be 3 +bne a0, t0, fail + +test_1: # Test write +lw s1, LOOPS # Number of times to duplicate strings +lw s2, BUF_START # Buffer to use for string duplication + +L1: +la a1, AA # a1: Pointer to 'AA' string +andi t0, s1, 1 # Write 'AA' if even and 'BB' if odd +bne t0, zero, 1f # Skip 'BB' string if even +la a1, BB # a1: Pointer to 'BB' string +1: + +lw t1, 0(a1) # Load 'AA' or 'BB' +sw t1, 0(s2), t0 # Store to buffer +lw a2, STRLEN # a2: Buffer size +add s2, s2, a2 # Next index in buffer + +addi s1, s1, -1 # Subtract from loop counter +bne s1, zero, L1 # Write again + +mv a0, s0 # a0: File descriptor +lw a1, BUF_START # a1: Buffer start +lw a2, STRLEN +lw s1, LOOPS +mul a2, a2, s1 # a2: Buffer size = (STRLEN * LOOPS) +mv s1, a2 # Bytes that should have been written +lw a7, WRITE # a7: "Write" ecall +ecall # Returns to a0: Number of bytes written +bne a0, s1, fail # Ensure write returned correct number of bytes + +test_2: # Seek file to start +mv a0, s0 # a0: File descriptor +mv a1, zero # a1: Offset to seek +mv a2, zero # a2: Base of seek +lw a7, LSEEK # a7: LSeek syscall +ecall # Returns to a0: 0 if successful +bne a0, zero, fail + +test_3: # Read from file +mv a0, s0 # a0: File descriptor +lw a1, BUF_START # a1: Buffer start +lw a2, STRLEN # a2: Number of bytes to read +lw t0, LOOPS # Number of times written +mul a2, a2, t0 # Multiply by number of times written +mv s1, a2 # Save number of bytes written to check if successful +lw a7, READ # a7: Read syscall +ecall # Returns to a0: (STRLEN * LOOPS) if successful +bne a0, s1, fail + +j success + +cleanup: # Close file +mv a0, s0 # a0: File descriptor +lw a7, CLOSE # a7: Close syscall +ecall +ret # Return to fail/success +fail: +jal cleanup +li a0, 0 +li a7, 93 +ecall +success: +jal cleanup +li a0, 42 +li a7, 93 +ecall + +.data +.align 4 + +# Data to write to temp file +STRLEN: .word 2 +AA: .string "AA" +BB: .string "BB" + +# File name to use for this test +FILENAME: .string "./test.txt" + +# Error messages +OPEN_FAILED: .string "Failed to open file" +WRITE_FAILED: .string "Failed to write to file" +READ_FAILED: .string "Failed to read from file" +SEEK_FAILED: .string "Failed to seek file to beginning" + +# File open modes +O_RDONLY: .word 0x0000 +O_WRONLY: .word 0x0001 +O_RDWR: .word 0x0002 +O_ACCMODE: .word 0x0003 +# Additional file flags +O_CREAT: .word 0x0100 +O_EXCL: .word 0x0200 +O_TRUNC: .word 0x1000 +O_APPEND: .word 0x2000 + +# Syscalls +PRINT_STR: .word 4 +EXIT: .word 10 +CLOSE: .word 57 +LSEEK: .word 62 +READ: .word 63 +WRITE: .word 64 +EXIT2: .word 93 +OPEN: .word 1024 + +# Other data +BUF_START: .word 0x2000 +LOOPS: .word 10 + diff --git a/test/tst_riscv.cpp b/test/tst_riscv.cpp index b7c9141b1..89c339a9b 100644 --- a/test/tst_riscv.cpp +++ b/test/tst_riscv.cpp @@ -7,6 +7,7 @@ #include "processorregistry.h" #include "ripessettings.h" #include "rvisainfo_common.h" +#include "systemio.h" #if !defined(RISCV32_TEST_DIR) || !defined(RISCV64_TEST_DIR) || \ !defined(RISCV32_C_TEST_DIR) || !defined(RISCV64_C_TEST_DIR) @@ -33,6 +34,8 @@ static constexpr unsigned s_success = 42; static constexpr unsigned s_statusreg = 3; // Current test stored in the gp(3) register static constexpr unsigned s_ecallreg = 10; // a0 +// Register containing the ecall operation +static constexpr unsigned s_ecallopreg = 17; // a7 // Maximum cycle count static constexpr unsigned s_maxCycles = 10000; @@ -183,7 +186,7 @@ QString tst_RISCV::executeSimulator() { void tst_RISCV::runTests(const ProcessorID &id, const QStringList &extensions, const QStringList &testDirs) { - for (auto testDir : testDirs) { + for (const auto &testDir : testDirs) { const auto dir = QDir(testDir); const auto testFiles = dir.entryList({"*.s"}); ProcessorHandler::selectProcessor(id, extensions); @@ -211,10 +214,18 @@ void tst_RISCV::runTests(const ProcessorID &id, const QStringList &extensions, } auto spProgram = std::make_shared(program.program); - // Override the ProcessorHandler's ECALL handling. In doing so, we verify - // whether the correct test value was reached. + // Override the ProcessorHandler's ECALL Exit2 handling. In doing so, we + // verify whether the correct test value was reached. ProcessorHandler::getProcessorNonConst()->trapHandler = [=] { - trapHandler(); + if (ProcessorHandler::getProcessor()->getRegister( + RVISA::GPR, s_ecallopreg) == RVABI::Exit2) { + trapHandler(); + } else { + const unsigned int function = + ProcessorHandler::getProcessor()->getRegister(RVISA::GPR, + s_ecallopreg); + ProcessorHandler::getSyscallManagerNonConst().execute(function); + } }; ProcessorHandler::get()->loadProgram(spProgram); RipesSettings::getObserver(RIPES_GLOBALSIGNAL_REQRESET)->trigger(); @@ -225,6 +236,8 @@ void tst_RISCV::runTests(const ProcessorID &id, const QStringList &extensions, } qInfo() << "Test '" << m_currentTest << "' succeeded."; + + SystemIO::reset(); // Close open files in between tests } } }