blob: 999c325de0b19ea84d37f16df16b4a098947d861 [file]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "riscv/riscv_arm_semihost.h"
#include <time.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include "absl/log/check.h"
#include "googlemock/include/gmock/gmock.h"
#include "mpact/sim/generic/data_buffer.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/util/memory/flat_demand_memory.h"
#include "mpact/sim/util/memory/memory_interface.h"
#include "riscv/riscv_register.h"
#include "riscv/riscv_state.h"
// This file implements the tests for the class that implements the ARM style
// semihosting for the RiscV architecture. The test is done for both 32 bit and
// 64 bit versions of the architecture.
namespace {
// Test input file to use for some of the file operations.
constexpr char kFileName[] = "test_file.txt";
// The depot path to the test directory.
constexpr char kDepotPath[] = "riscv/test/";
using ::mpact::sim::generic::DataBufferFactory;
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::riscv::RiscVArmSemihost;
using ::mpact::sim::riscv::RiscVState;
using ::mpact::sim::riscv::RiscVXlen;
using ::mpact::sim::riscv::RV32Register;
using ::mpact::sim::riscv::RV64Register;
constexpr char kHelloWorld[] = "Hello World\n";
constexpr uint64_t kSemihostCallAddress = 0x1004;
constexpr uint64_t kSemihostNonCallAddress = 0x2004;
constexpr uint64_t kParameterAddress = 0x3000;
constexpr uint64_t kStringAddress = 0x4000;
constexpr uint64_t kBufferAddress = 0x5000;
constexpr int kTmpNamLength = L_tmpnam * 3 / 2;
constexpr int kBufferSize = 128;
// Opcode sequence that idenfifies the semihosting call. This consists
// of a shift immediate on the x0 (constant 0 register), followed by
// an ebreak instruction, followed by a different shift immediate on x0.
constexpr uint32_t kSemihostCallSequence[] = {0x01f01013, 0x00100073,
0x40705013};
// A sequence that does not match the semihosting call.
constexpr uint32_t kSemihostNonCallSequence[] = {0x0e01013, 0x00100073,
0x40605013};
// Exception code;
constexpr int kAdpStoppedApplicationExit = 0x20026;
// Semihosting function call codes.
constexpr int kSysException = 0x18;
constexpr int kSysTime = 0x11;
constexpr int kSysWrite = 0x05;
constexpr int kSysTickFreq = 0x31;
constexpr int kSysHeapInfo = 0x16;
constexpr int kSysClose = 0x02;
constexpr int kSysFlen = 0x0c;
constexpr int kSysOpen = 0x01;
constexpr int kSysSeek = 0x0a;
constexpr int kSysRead = 0x06;
constexpr int kSysTmpnam = 0x0d;
// Helper template to get register type from integer type.
template <typename T>
struct RegisterType {};
template <>
struct RegisterType<uint32_t> {
using type = RV32Register;
};
template <>
struct RegisterType<uint64_t> {
using type = RV64Register;
};
// Test fixture for RiscV Arm Semihosting.
class RiscVArmSemihostTest : public testing::Test {
public:
void SetTrue(bool *value) { *value = true; }
RiscVArmSemihost *semi32() const { return semi32_; }
RiscVArmSemihost *semi64() const { return semi64_; }
RV32Register *a0_32() const { return a0_32_; }
RV32Register *a1_32() const { return a1_32_; }
RV64Register *a0_64() const { return a0_64_; }
RV64Register *a1_64() const { return a1_64_; }
Instruction *semihost_inst_32() const { return semihost_inst_32_; }
Instruction *semihost_inst_64() const { return semihost_inst_64_; }
Instruction *non_semihost_inst_32() const { return non_semihost_inst_32_; }
Instruction *non_semihost_inst_64() const { return non_semihost_inst_64_; }
DataBufferFactory *db_factory() { return &db_factory_; }
mpact::sim::util::MemoryInterface *memory() const { return memory_; }
protected:
RiscVArmSemihostTest() {
memory_ = new mpact::sim::util::FlatDemandMemory(0);
state32_ = new RiscVState("test", RiscVXlen::RV32, memory_);
state64_ = new RiscVState("test", RiscVXlen::RV64, memory_);
// Store the semihost call sequence to memory.
auto db = db_factory_.Allocate<uint32_t>(3);
for (int i = 0; i < 3; i++) {
db->Set<uint32_t>(i, kSemihostCallSequence[i]);
}
memory_->Store(kSemihostCallAddress - 4, db);
// Store the non-semihost call sequence to memory.
for (int i = 0; i < 3; i++) {
db->Set<uint32_t>(i, kSemihostNonCallSequence[i]);
}
memory_->Store(kSemihostNonCallAddress - 4, db);
db->DecRef();
// Create instruction objects for semihost and non-semihost for 32 and 64
// bit architectures.
semihost_inst_32_ = new Instruction(kSemihostCallAddress, state32_);
semihost_inst_64_ = new Instruction(kSemihostCallAddress, state64_);
non_semihost_inst_32_ = new Instruction(kSemihostNonCallAddress, state32_);
non_semihost_inst_64_ = new Instruction(kSemihostNonCallAddress, state64_);
// Create registers used in the calls. Just use the alias names as opposed
// to the x register names.
a0_32_ = state32_->GetRegister<RV32Register>("x10").first;
a1_32_ = state32_->GetRegister<RV32Register>("x11").first;
a0_64_ = state64_->GetRegister<RV64Register>("x10").first;
a1_64_ = state64_->GetRegister<RV64Register>("x11").first;
// Create the semihosting class instances.
semi32_ = new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord32, memory_,
memory_);
semi64_ = new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord64, memory_,
memory_);
}
~RiscVArmSemihostTest() override {
delete memory_;
delete state32_;
delete state64_;
semihost_inst_32_->DecRef();
semihost_inst_64_->DecRef();
non_semihost_inst_32_->DecRef();
non_semihost_inst_64_->DecRef();
delete semi32_;
delete semi64_;
}
DataBufferFactory db_factory_;
mpact::sim::util::FlatDemandMemory *memory_;
RiscVState *state32_;
RiscVState *state64_;
Instruction *semihost_inst_32_;
Instruction *semihost_inst_64_;
Instruction *non_semihost_inst_32_;
Instruction *non_semihost_inst_64_;
RiscVArmSemihost *semi32_;
RiscVArmSemihost *semi64_;
RV32Register *a0_32_, *a1_32_;
RV64Register *a0_64_, *a1_64_;
};
// Templated helper functions to select members from the test fixture by type.
// Semihosting instance.
template <typename T>
RiscVArmSemihost *SemiHost(RiscVArmSemihostTest *test) {}
template <>
RiscVArmSemihost *SemiHost<uint32_t>(RiscVArmSemihostTest *test) {
return test->semi32();
}
template <>
RiscVArmSemihost *SemiHost<uint64_t>(RiscVArmSemihostTest *test) {
return test->semi64();
}
// Register.
template <typename T>
typename RegisterType<T>::type *AReg(RiscVArmSemihostTest *test, T num) {}
template <>
typename RegisterType<uint32_t>::type *AReg(RiscVArmSemihostTest *test,
uint32_t num) {
if (num == 0) return test->a0_32();
if (num == 1) return test->a1_32();
return nullptr;
}
template <>
typename RegisterType<uint64_t>::type *AReg(RiscVArmSemihostTest *test,
uint64_t num) {
if (num == 0) return test->a0_64();
if (num == 1) return test->a1_64();
return nullptr;
}
// Semihosting instruction instance.
template <typename T>
Instruction *SemihostInst(RiscVArmSemihostTest *test, T num = 0) {}
template <>
Instruction *SemihostInst<uint32_t>(RiscVArmSemihostTest *test, uint32_t) {
return test->semihost_inst_32();
}
template <>
Instruction *SemihostInst<uint64_t>(RiscVArmSemihostTest *test, uint64_t) {
return test->semihost_inst_64();
}
// Non-semihosting instruction instance.
template <typename T>
Instruction *NonSemihostInst(RiscVArmSemihostTest *test, T num = 0) {}
template <>
Instruction *NonSemihostInst<uint32_t>(RiscVArmSemihostTest *test, uint32_t) {
return test->non_semihost_inst_32();
}
template <>
Instruction *NonSemihostInst<uint64_t>(RiscVArmSemihostTest *test, uint64_t) {
return test->non_semihost_inst_64();
}
// Verify that the semihosting call is recognized properly by using the time
// call.
template <typename T>
void CallRecognitionTest(RiscVArmSemihostTest *test) {
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysTime);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, 0);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
EXPECT_NEAR(AReg<T>(test, 0)->data_buffer()->template Get<T>(0),
time(nullptr), 2);
EXPECT_TRUE(SemiHost<T>(test)->IsSemihostingCall(SemihostInst<T>(test)));
}
TEST_F(RiscVArmSemihostTest, CallRecognition32) {
CallRecognitionTest<uint32_t>(this);
}
TEST_F(RiscVArmSemihostTest, CallRecognition64) {
CallRecognitionTest<uint64_t>(this);
}
// Verify that a sequence that doesn't match doesn't trigger semihosting call.
template <typename T>
void CallNonRecognitionTest(RiscVArmSemihostTest *test) {
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysTime);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, 0);
SemiHost<T>(test)->OnEBreak(NonSemihostInst<T>(test));
EXPECT_EQ(AReg<T>(test, 0)->data_buffer()->template Get<uint32_t>(0),
kSysTime);
EXPECT_FALSE(SemiHost<T>(test)->IsSemihostingCall(NonSemihostInst<T>(test)));
}
TEST_F(RiscVArmSemihostTest, CallNonRecognition32) {
CallNonRecognitionTest<uint32_t>(this);
}
TEST_F(RiscVArmSemihostTest, CallNonRecognition64) {
CallNonRecognitionTest<uint64_t>(this);
}
// RiscV32 puts appliation exit code into A1.
TEST_F(RiscVArmSemihostTest, ApplicationExit32) {
bool detected = false;
SemiHost<uint32_t>(this)->set_exit_callback(
[&detected]() { detected = true; });
// Set up register values.
AReg<uint32_t>(this, 0)->data_buffer()->template Set<uint32_t>(0,
kSysException);
AReg<uint32_t>(this, 1)->data_buffer()->template Set<uint32_t>(
0, kAdpStoppedApplicationExit);
// Make the callback.
SemiHost<uint32_t>(this)->OnEBreak(SemihostInst<uint32_t>(this));
EXPECT_TRUE(detected);
}
// RiscV64 puts application exit code into memory, with the pointer in A1.
TEST_F(RiscVArmSemihostTest, ApplicationExit64) {
bool detected = false;
SemiHost<uint64_t>(this)->set_exit_callback(
[&detected]() { detected = true; });
// Write exception code to memory.
auto *db = db_factory()->Allocate<uint64_t>(1);
db->template Set<uint64_t>(0, kAdpStoppedApplicationExit);
memory_->Store(kParameterAddress, db);
db->DecRef();
// Set up parameter block.
AReg<uint64_t>(this, 0)->data_buffer()->template Set<uint64_t>(0,
kSysException);
AReg<uint64_t>(this, 1)->data_buffer()->template Set<uint64_t>(
0, kParameterAddress);
// Make the callback.
SemiHost<uint64_t>(this)->OnEBreak(SemihostInst<uint64_t>(this));
EXPECT_TRUE(detected);
}
// Test the Write system call (printf to stderr).
template <typename T>
void SysWriteTest(RiscVArmSemihostTest *test) {
testing::internal::CaptureStderr();
// Write string to memory.
auto *str_db = test->db_factory()->Allocate<uint8_t>(strlen(kHelloWorld));
std::memcpy(str_db->raw_ptr(), kHelloWorld, strlen(kHelloWorld));
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, 2);
db->template Set<T>(1, kStringAddress);
db->template Set<T>(2, strlen(kHelloWorld));
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysWrite);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
std::string capture_stderr = testing::internal::GetCapturedStderr();
EXPECT_STREQ(kHelloWorld, capture_stderr.c_str());
}
TEST_F(RiscVArmSemihostTest, SysWrite32) { SysWriteTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysWrite64) { SysWriteTest<uint64_t>(this); }
// Systick should just return -1 for now.
template <typename T>
void SysTickTest(RiscVArmSemihostTest *test) {
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysTickFreq);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, 0);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
EXPECT_EQ(AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0),
-1);
}
TEST_F(RiscVArmSemihostTest, SysTick32) { SysTickTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysTick64) { SysTickTest<uint64_t>(this); }
// Test SysOpen
template <typename T>
void SysOpenTest(RiscVArmSemihostTest *test) {
std::string input_file = absl::StrCat(kDepotPath, "testfiles/", kFileName);
auto *str_db = test->db_factory()->Allocate<uint8_t>(input_file.length() + 1);
std::memcpy(str_db->raw_ptr(), input_file.c_str(), input_file.length() + 1);
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kStringAddress);
db->template Set<T>(1, 0);
db->template Set<T>(2, input_file.length());
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysOpen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
EXPECT_GT(AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0),
0);
// Remove a character from the end of the file name and try again. This time
// it should fail.
input_file = input_file.substr(0, input_file.length() - 1);
str_db = test->db_factory()->Allocate<uint8_t>(input_file.length() + 1);
std::memcpy(str_db->raw_ptr(), input_file.c_str(), input_file.length() + 1);
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kStringAddress);
db->template Set<T>(1, 0);
db->template Set<T>(2, input_file.length());
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysOpen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
EXPECT_LT(AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0),
0);
}
TEST_F(RiscVArmSemihostTest, SysOpen32) { SysOpenTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysOpen64) { SysOpenTest<uint64_t>(this); }
// Test sys open and close.
template <typename T>
void SysOpenCloseTest(RiscVArmSemihostTest *test) {
std::string input_file = absl::StrCat(kDepotPath, "testfiles/", kFileName);
auto *str_db = test->db_factory()->Allocate<uint8_t>(input_file.length() + 1);
std::memcpy(str_db->raw_ptr(), input_file.c_str(), input_file.length() + 1);
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kStringAddress);
db->template Set<T>(1, 0);
db->template Set<T>(2, input_file.length());
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysOpen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int fd = AReg<T>(test, 0)->data_buffer()->template Get<int32_t>(0);
CHECK_GT(fd, 0);
// Now close that file descriptor.
db = test->db_factory()->Allocate<T>(1);
db->template Set<T>(0, fd);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysClose);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
EXPECT_EQ(AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0),
0);
// Try closing again this should be an error.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysClose);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
EXPECT_EQ(AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0),
-1);
}
// Test SysOpen and SysClose
TEST_F(RiscVArmSemihostTest, SysOpenClose32) {
SysOpenCloseTest<uint32_t>(this);
}
TEST_F(RiscVArmSemihostTest, SysOpenClose64) {
SysOpenCloseTest<uint64_t>(this);
}
// Test open-read-close.
template <typename T>
void SysOpenReadClose(RiscVArmSemihostTest *test) {
// Open the file.
std::string input_file = absl::StrCat(kDepotPath, "testfiles/", kFileName);
auto *str_db = test->db_factory()->Allocate<uint8_t>(input_file.length() + 1);
std::memcpy(str_db->raw_ptr(), input_file.c_str(), input_file.length() + 1);
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kStringAddress);
db->template Set<T>(1, 0);
db->template Set<T>(2, input_file.length());
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysOpen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int fd = AReg<T>(test, 0)->data_buffer()->template Get<int32_t>(0);
CHECK_GT(fd, 0) << "Invalid file descriptor: " << fd;
// Read from the file.
db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, fd);
db->template Set<T>(1, kBufferAddress);
db->template Set<T>(2, kBufferSize);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysRead);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int read_ret = AReg<T>(test, 0)->data_buffer()->template Get<int32_t>(0);
db = test->db_factory()->Allocate<uint8_t>(6);
test->memory()->Load(kBufferAddress, db, nullptr, nullptr);
std::string file_value(reinterpret_cast<char *>(db->raw_ptr()), 6);
db->DecRef();
// Verify the number of characters read and that the characters are correct.
EXPECT_STREQ("abcdef", file_value.c_str());
EXPECT_EQ(read_ret, 7);
// Now close that file descriptor.
db = test->db_factory()->Allocate<T>(1);
db->template Set<T>(0, fd);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysClose);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
CHECK_EQ(AReg<T>(test, 0)->data_buffer()->template Get<int32_t>(0), 0)
<< "Failed to close file";
}
TEST_F(RiscVArmSemihostTest, SysOpenReadClose32) {
SysOpenReadClose<uint32_t>(this);
}
TEST_F(RiscVArmSemihostTest, SysOpenReadClose64) {
SysOpenReadClose<uint64_t>(this);
}
// Test flen (file length).
template <typename T>
void SysFlenTest(RiscVArmSemihostTest *test) { // Open the file.
std::string input_file = absl::StrCat(kDepotPath, "testfiles/", kFileName);
auto *str_db = test->db_factory()->Allocate<uint8_t>(input_file.length() + 1);
std::memcpy(str_db->raw_ptr(), input_file.c_str(), input_file.length() + 1);
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kStringAddress);
db->template Set<T>(1, 0);
db->template Set<T>(2, input_file.length());
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysOpen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int fd = AReg<T>(test, 0)->data_buffer()->template Get<int32_t>(0);
CHECK_GT(fd, 0) << "Invalid file descriptor: " << fd;
// Get the length of the file (7).
db = test->db_factory()->Allocate<T>(1);
db->template Set<T>(0, fd);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysFlen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int flen_ret = AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0);
EXPECT_EQ(flen_ret, 7);
}
TEST_F(RiscVArmSemihostTest, SysFlen32) { SysFlenTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysFlen64) { SysFlenTest<uint64_t>(this); }
// Test tmpnam call.
template <typename T>
void SysTmpnamTest(RiscVArmSemihostTest *test) {
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kBufferAddress);
db->template Set<T>(1, 1);
db->template Set<T>(2, kTmpNamLength);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysTmpnam);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
// Check return value.
EXPECT_EQ(AReg<T>(test, 0)->data_buffer()->template Get<T>(0), 0);
// Fetch the string and check the length.
auto tmpnam_db = test->db_factory()->Allocate<char>(kTmpNamLength);
test->memory()->Load(kBufferAddress, tmpnam_db, nullptr, nullptr);
char *tmp_name = static_cast<char *>(tmpnam_db->raw_ptr());
auto length = strlen(tmp_name);
EXPECT_LE(length, kTmpNamLength);
EXPECT_GT(length, 0);
tmpnam_db->DecRef();
}
TEST_F(RiscVArmSemihostTest, SysTmpnam32) { SysTmpnamTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysTmpnam64) { SysTmpnamTest<uint64_t>(this); }
// Test that HeapInfo returns a struct with all zeros.
template <typename T>
void SysHeapInfoTest(RiscVArmSemihostTest *test) {
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysHeapInfo);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kBufferAddress);
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
// Fetch the returned values.
auto *db = test->db_factory()->Allocate<T>(4);
test->memory()->Load(kBufferAddress, db, nullptr, nullptr);
EXPECT_EQ(db->template Get<T>(0), 0);
EXPECT_EQ(db->template Get<T>(1), 0);
EXPECT_EQ(db->template Get<T>(2), 0);
EXPECT_EQ(db->template Get<T>(3), 0);
db->DecRef();
}
TEST_F(RiscVArmSemihostTest, SysHeapInfo32) { SysHeapInfoTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysHeapInfo64) { SysHeapInfoTest<uint64_t>(this); }
// Test that seek works by opening a file, seeking, then reading from the file.
template <typename T>
void SysSeekTest(RiscVArmSemihostTest *test) {
std::string input_file = absl::StrCat(kDepotPath, "testfiles/", kFileName);
auto *str_db = test->db_factory()->Allocate<uint8_t>(input_file.length() + 1);
std::memcpy(str_db->raw_ptr(), input_file.c_str(), input_file.length() + 1);
test->memory()->Store(kStringAddress, str_db);
str_db->DecRef();
// Set up parameter block.
auto *db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, kStringAddress);
db->template Set<T>(1, 0);
db->template Set<T>(2, input_file.length());
test->memory()->Store(kParameterAddress, db);
db->DecRef();
// Set up register values.
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysOpen);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int fd = AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0);
CHECK_GT(fd, 0) << "Invalid file descriptor: " << fd;
// Seek to position 2 (file length 7).
db = test->db_factory()->Allocate<T>(2);
db->template Set<T>(0, fd);
db->template Set<T>(1, 2);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysSeek);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int seek_ret = AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0);
EXPECT_EQ(seek_ret, 0);
// Now read from that file descriptor to verify that the file is in the
// right position.
// Read from the file.
db = test->db_factory()->Allocate<T>(3);
db->template Set<T>(0, fd);
db->template Set<T>(1, kBufferAddress);
db->template Set<T>(2, kBufferSize);
test->memory()->Store(kParameterAddress, db);
db->DecRef();
AReg<T>(test, 0)->data_buffer()->template Set<T>(0, kSysRead);
AReg<T>(test, 1)->data_buffer()->template Set<T>(0, kParameterAddress);
// Make the callback.
SemiHost<T>(test)->OnEBreak(SemihostInst<T>(test));
int read_ret = AReg<T>(test, 0)
->data_buffer()
->template Get<typename std::make_signed<T>::type>(0);
db = test->db_factory()->Allocate<uint8_t>(6);
test->memory()->Load(kBufferAddress, db, nullptr, nullptr);
std::string file_value(reinterpret_cast<char *>(db->raw_ptr()), 4);
db->DecRef();
// Verify the number of characters read and that the characters are correct.
EXPECT_STREQ("cdef", file_value.c_str());
EXPECT_EQ(read_ret, 5);
}
TEST_F(RiscVArmSemihostTest, SysSeek32) { SysSeekTest<uint32_t>(this); }
TEST_F(RiscVArmSemihostTest, SysSeek64) { SysSeekTest<uint64_t>(this); }
} // namespace