blob: 162dbe1f0de607e82d731d1bce8f02296ba1d08c [file] [log] [blame] [edit]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/elfldltl/constants.h>
#include <bringup/lib/restricted-machine/machine.h>
#include <zxtest/zxtest.h>
namespace {
class MachineTests : public zxtest::TestWithParam<restricted_machine::MachineType> {
public:
void SetUp() override {
environment_ = fbl::AdoptRef(new restricted_machine::Environment);
machine_type_ = GetParam();
if (!restricted_machine::Environment::HardwareSupported(machine_type_)) {
ZXTEST_SKIP() << "unsupported machine type: " << machine_type_.AsString();
return;
}
ASSERT_TRUE(environment_->Initialize(machine_type_));
}
protected:
restricted_machine::MachineType machine_type_;
fbl::RefPtr<restricted_machine::Environment> environment_{};
};
TEST_P(MachineTests, PingValueArgs) {
restricted_machine::Machine machine(environment_);
if (environment_->address_limit() != 0) {
ZXTEST_SKIP() << "this cannot be run on address limited environments";
}
ASSERT_TRUE(machine.Initialize());
auto r = machine.Call(restricted_machine::Environment::kPingFunctionName, 1, 2, 3, 4);
EXPECT_TRUE(r.is_error());
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, r.error_value());
}
TEST_P(MachineTests, Ping) {
restricted_machine::Machine machine(environment_);
ASSERT_TRUE(machine.Initialize());
auto arg0 = environment_->MakeArgument<uint64_t>(2);
auto arg1 = environment_->MakeArgument<uint64_t>(3);
auto arg2 = environment_->MakeArgument<uint64_t>(4);
auto arg3 = environment_->MakeArgument<uint64_t>(5);
auto r = machine.Call(restricted_machine::Environment::kPingFunctionName, arg0.get(), arg1.get(),
arg2.get(), arg3.get());
ASSERT_TRUE(r.is_ok());
EXPECT_EQ(14, r.value());
}
// Derive a machine to test arg prepping
class ArgMachine : public restricted_machine::Machine {
public:
ArgMachine(fbl::RefPtr<restricted_machine::Environment> e) : restricted_machine::Machine(e) {}
~ArgMachine() = default;
template <typename... Args>
zx::result<> doPrepArgs(std::vector<uint64_t> *args, Args... vargs) {
return prepArgs(args, vargs...);
}
};
TEST_P(MachineTests, PrepArgsAllocationLimit) {
auto e = AdoptRef(new restricted_machine::Environment);
uint64_t limit = 0x4fffffff;
ASSERT_TRUE(e->Initialize(machine_type_, 4096 * 12, limit));
ArgMachine machine(e);
ASSERT_TRUE(machine.Initialize());
auto arg0 = e->MakeArgument<std::string>("Hello World");
auto arg1 = e->MakeArgument<uint64_t>(1);
auto arg2 = e->MakeArgument<uint64_t>(2);
auto arg3 = e->MakeArgument<uint64_t>(3);
std::vector<uint64_t> args;
auto result = machine.doPrepArgs(&args, arg0.get(), arg1.get(), arg2.get(), arg3.get());
EXPECT_OK(result);
ASSERT_EQ(args.size(), 4);
EXPECT_GT(limit, args[0]);
EXPECT_GT(limit, args[1]);
EXPECT_GT(limit, args[2]);
EXPECT_GT(limit, args[3]);
EXPECT_STREQ(reinterpret_cast<std::string *>(args[0])->c_str(), arg0->c_str());
EXPECT_GT(limit, reinterpret_cast<uint64_t>(reinterpret_cast<std::string *>(args[0])->data()));
EXPECT_EQ(*reinterpret_cast<uint64_t *>(args[1]), 1ULL);
EXPECT_EQ(*reinterpret_cast<uint64_t *>(args[2]), 2ULL);
EXPECT_EQ(*reinterpret_cast<uint64_t *>(args[3]), 3ULL);
}
TEST_P(MachineTests, PrepArgsOutOfRange) {
auto e = AdoptRef(new restricted_machine::Environment);
uint64_t limit = 0x4fffffff;
ASSERT_TRUE(e->Initialize(machine_type_, 4096 * 12, limit));
ArgMachine machine(e);
ASSERT_TRUE(machine.Initialize());
auto arg = e->MakeArgument<std::string>("Hello World");
// The stack-based value should be out of range reliably.
std::vector<uint64_t> args;
uint64_t stack_arg = 2;
auto result = machine.doPrepArgs(&args, &stack_arg, arg.get());
EXPECT_TRUE(result.is_error());
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, result.error_value());
EXPECT_EQ(args.size(), 0);
args.clear();
result = machine.doPrepArgs(&args, arg.get(), &stack_arg);
EXPECT_TRUE(result.is_error());
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, result.error_value());
EXPECT_EQ(args.size(), 1);
args.clear();
result = machine.doPrepArgs(&args, arg.get(), arg.get(), &stack_arg);
EXPECT_TRUE(result.is_error());
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, result.error_value());
EXPECT_EQ(args.size(), 2);
args.clear();
result = machine.doPrepArgs(&args, arg.get(), arg.get(), arg.get(), &stack_arg);
EXPECT_TRUE(result.is_error());
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, result.error_value());
EXPECT_EQ(args.size(), 3);
}
// This test validates the FPU registers workflow ensuring a value is returned
// in the FPU registers from the restricted environment.
TEST_P(MachineTests, FpuRegisterPreservation) {
restricted_machine::Machine machine(environment_);
ASSERT_TRUE(machine.Initialize());
machine.enable_fpu_registers(true);
// Make a buffer and fill it with known values
std::vector<char> expected_fpu_buffer;
memset(machine.FpuRegisters()->data(), 0xAA, machine.FpuRegisters()->size());
// Ping will set the first 64-bits of FPU register space to the first
// argument's value.
auto arg0 = environment_->MakeArgument<uint64_t>(2);
// Call ping()
auto arg1 = environment_->MakeArgument<uint64_t>(5);
auto arg2 = environment_->MakeArgument<uint64_t>(4);
auto arg3 = environment_->MakeArgument<uint64_t>(3);
auto r = machine.Call(restricted_machine::Environment::kPingFunctionName, arg0.get(), arg1.get(),
arg2.get(), arg3.get());
ASSERT_TRUE(r.is_ok());
EXPECT_EQ(14, r.value());
// Ping will set the first 64-bits of FPU register space to the first
// argument's value.
uint8_t *value = reinterpret_cast<uint8_t *>(arg0.get());
for (uint16_t i = 0; i < 8; i++) {
EXPECT_EQ(machine.FpuRegisters()->at(i), value[i]);
}
// Now we can see if the other FPU registers survived.
for (uint16_t i = 8; i < machine.FpuRegisters()->size(); i++) {
EXPECT_EQ(0xAA, machine.FpuRegisters()->at(i)) << "i: " << i;
}
}
} // anonymous namespace
INSTANTIATE_TEST_SUITE_P(, MachineTests, zxtest::ValuesIn(restricted_machine::kSupportedMachines),
[](const zxtest::TestParamInfo<MachineTests::ParamType> &info) {
return std::string(info.param.AsString());
});
int main(int argc, char **argv) { return RUN_ALL_TESTS(argc, argv); }