blob: dbcca51f1cf980b44c9adabc837dcc57fbc9dd69 [file] [log] [blame]
// Copyright 2021 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 "src/developer/debug/zxdb/expr/return_value.h"
#include <gtest/gtest.h>
#include "src/developer/debug/zxdb/common/test_with_loop.h"
#include "src/developer/debug/zxdb/expr/abi_arm64.h"
#include "src/developer/debug/zxdb/expr/abi_x64.h"
#include "src/developer/debug/zxdb/expr/mock_eval_context.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/collection.h"
#include "src/developer/debug/zxdb/symbols/enumeration.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/modified_type.h"
#include "src/developer/debug/zxdb/symbols/type_test_support.h"
namespace zxdb {
namespace {
using debug::RegisterID;
// Returns a new vector consisting of the first |len| bytes of the given vector.
std::vector<uint8_t> DataPrefix(const std::vector<uint8_t>& source, size_t len) {
return std::vector<uint8_t>(source.begin(), source.begin() + len);
}
class ReturnValue : public TestWithLoop {
public:
ErrOrValue GetReturnValueSync(const fxl::RefPtr<EvalContext>& context, const Function* func) {
std::optional<ErrOrValue> result;
GetReturnValue(context, func, [&result](ErrOrValue val) { result = std::move(val); });
loop().RunUntilNoTasks();
EXPECT_TRUE(result); // Should always get the callback issued.
if (!result)
return Err("No result");
return *result;
}
void TestStructureByValue(const fxl::RefPtr<EvalContext>& context, const std::string& name,
std::initializer_list<NameAndType> members,
std::vector<uint8_t> expected_contents) {
auto coll = MakeCollectionType(DwarfTag::kStructureType, name, members);
coll->set_calling_convention(Collection::kPassByValue);
auto fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
fn->set_return_type(coll);
ErrOrValue result = GetReturnValueSync(context, fn.get());
EXPECT_TRUE(result.ok()) << "Error evaluating return value for " << name;
if (!result.ok())
return;
EXPECT_EQ(name, result.value().type()->GetFullName()) << " for " << name;
EXPECT_EQ(expected_contents, result.value().data().bytes()) << " for " << name;
}
// Ensures that we can't compute the given structure's return-by-value value.
void TestStructureByValueFails(const fxl::RefPtr<EvalContext>& context, const std::string& name,
std::initializer_list<NameAndType> members) {
auto coll = MakeCollectionType(DwarfTag::kStructureType, name, members);
coll->set_calling_convention(Collection::kPassByValue);
auto fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
fn->set_return_type(coll);
ErrOrValue result = GetReturnValueSync(context, fn.get());
EXPECT_FALSE(result.ok()) << "Expecting failure for " << name;
}
};
fxl::RefPtr<Function> MakeFunctionReturningBaseType(int base_type, uint32_t byte_size) {
auto base = fxl::MakeRefCounted<BaseType>(base_type, byte_size, "ReturnType");
auto fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
fn->set_return_type(base);
return fn;
}
} // namespace
TEST_F(ReturnValue, BaseTypeX64) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
context->set_abi(std::make_shared<AbiX64>());
// Integer return value.
constexpr uint64_t kIntValue = 42;
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, kIntValue);
// Returning an 8-byte integer.
auto int_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeSigned, 8);
ErrOrValue result = GetReturnValueSync(context, int_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kIntValue, result.value().GetAs<uint64_t>());
// Returning a 1-byte bool. The mock data provider doesn't know how to extract "al" from "rax"
// so we have to supply a mapping for that explicitly.
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, 1);
auto bool_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeBoolean, 1);
result = GetReturnValueSync(context, bool_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(1u, result.value().GetAs<uint8_t>());
// Provide a floating-point return register value. The xmm0 register holds two doubles but we
// only use the first one.
constexpr double kDoubleValue = 3.14;
std::vector<uint8_t> double_data(16); // 128-bit register.
memcpy(double_data.data(), &kDoubleValue, sizeof(kDoubleValue));
context->data_provider()->AddRegisterValue(RegisterID::kX64_xmm0, false, double_data);
// Returning an 8-byte double.
auto double_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeFloat, 8);
result = GetReturnValueSync(context, double_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kDoubleValue, result.value().GetAs<double>());
// Provide a 4-byte float in the low bits of xmm0.
constexpr float kFloatValue = 2.17;
std::vector<uint8_t> float_data(16); // 128-bit register.
memcpy(float_data.data(), &kFloatValue, sizeof(kFloatValue));
context->data_provider()->AddRegisterValue(RegisterID::kX64_xmm0, false, float_data);
// Returning a 4-byte float. This returns the low 4 bytes of xmm0.
auto float_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeFloat, 4);
result = GetReturnValueSync(context, float_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kFloatValue, result.value().GetAs<float>());
}
TEST_F(ReturnValue, BaseTypeARM64) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
context->set_abi(std::make_shared<AbiArm64>());
// Integer return value.
constexpr uint64_t kIntValue = 42;
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_x0, true, kIntValue);
// Returning an 8-byte integer.
auto int_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeSigned, 8);
ErrOrValue result = GetReturnValueSync(context, int_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kIntValue, result.value().GetAs<uint64_t>());
// Returning a 1-byte bool.
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_x0, true, 1);
auto bool_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeBoolean, 1);
result = GetReturnValueSync(context, bool_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(1u, result.value().GetAs<uint8_t>());
// Provide a floating-point return register value. The v0 register holds two doubles but we
// only fill in the first.
constexpr double kDoubleValue = 3.14;
std::vector<uint8_t> vector_data(16); // 128-bit register.
memcpy(vector_data.data(), &kDoubleValue, sizeof(kDoubleValue));
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_v0, false, vector_data);
// Returning an 8-byte double.
auto double_fn = MakeFunctionReturningBaseType(BaseType::kBaseTypeFloat, 8);
result = GetReturnValueSync(context, double_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kDoubleValue, result.value().GetAs<double>());
}
TEST_F(ReturnValue, Pointer) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Make a function that returns "const int32_t*".
auto int32_type = MakeInt32Type();
auto const_int32_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, int32_type);
auto const_int32_ptr_type =
fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, const_int32_type);
auto fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
fn->set_return_type(const_int32_ptr_type);
// Set up x64 values.
context->set_abi(std::make_shared<AbiX64>());
constexpr uint64_t kPtrValue = 0x123456789abc;
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, kPtrValue);
auto result = GetReturnValueSync(context, fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kPtrValue, result.value().GetAs<uint64_t>());
EXPECT_EQ("const int32_t*", result.value().type()->GetFullName());
// Set up ARM64 values.
context->set_abi(std::make_shared<AbiArm64>());
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, 99); // Poison old x86.
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_x0, true, kPtrValue);
result = GetReturnValueSync(context, fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kPtrValue, result.value().GetAs<uint64_t>());
EXPECT_EQ("const int32_t*", result.value().type()->GetFullName());
}
TEST_F(ReturnValue, Enum) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Old-style untyped enumeration type.
Enumeration::Map map;
map[0] = "kZero";
map[1] = "kOne";
map[2] = "kTwo";
auto untyped_enum = fxl::MakeRefCounted<Enumeration>("MyEnum", LazySymbol(), 4, true, map);
auto untyped_fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
untyped_fn->set_return_type(untyped_enum);
// New-style C++ typed enumeration (use int16).
auto int16_type = MakeInt16Type();
auto typed_enum = fxl::MakeRefCounted<Enumeration>("MyEnum", int16_type, 2, true, map);
auto typed_fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
typed_fn->set_return_type(typed_enum);
// Set up calls for x64 tests.
constexpr uint64_t kEnumValue = 2;
context->set_abi(std::make_shared<AbiX64>());
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, kEnumValue);
// Untyped x64 enumeration.
auto result = GetReturnValueSync(context, untyped_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kEnumValue, result.value().GetAs<uint32_t>()); // Declared a 4-byte enum size.
EXPECT_EQ("MyEnum", result.value().type()->GetFullName());
// Typed x64 enumeration.
result = GetReturnValueSync(context, typed_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kEnumValue, result.value().GetAs<uint16_t>());
// Set up calls for ARM64.
context->set_abi(std::make_shared<AbiArm64>());
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, 99); // Poison old x86.
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_x0, true, kEnumValue);
// Untyped ARM64 enumeration.
result = GetReturnValueSync(context, untyped_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kEnumValue, result.value().GetAs<uint32_t>()); // Declared a 4-byte enum size.
// Typed ARM64 enumeration.
result = GetReturnValueSync(context, typed_fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(kEnumValue, result.value().GetAs<uint16_t>());
}
TEST_F(ReturnValue, CollectionByRefX64) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
context->set_abi(std::make_shared<AbiX64>());
// Make a pass-by-ref struct type.
auto int64_type = MakeInt64Type();
auto coll = MakeCollectionType(DwarfTag::kStructureType, "MyStruct",
{{"a", int64_type}, {"b", int64_type}, {"c", int64_type}});
coll->set_calling_convention(Collection::kPassByReference);
// Struct location is returned in rax.
constexpr uint64_t kAddress = 0x2384f576a230;
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, kAddress);
// Struct data.
std::vector<uint8_t> data{0x42, 0, 0, 0, 0, 0, 0, 0, // a
0x99, 0, 0, 0, 0, 0, 0, 0, // b
0xff, 0, 0, 0, 0, 0, 0, 0}; // c
context->data_provider()->AddMemory(kAddress, data);
auto fn = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
fn->set_return_type(coll);
auto result = GetReturnValueSync(context, fn.get());
ASSERT_TRUE(result.ok()) << result.err().msg();
EXPECT_EQ(data, result.value().data().bytes());
EXPECT_EQ(kAddress, result.value().source().address());
}
TEST_F(ReturnValue, CollectionByValueX64) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
context->set_abi(std::make_shared<AbiX64>());
auto int32_type = MakeInt32Type();
auto int64_type = MakeInt64Type();
auto float_type = MakeFloatType();
auto char_type = MakeSignedChar8Type();
auto char_pointer_type = MakeCharPointerType();
auto double_type = MakeDoubleType();
std::vector<uint8_t> rax_data{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
std::vector<uint8_t> rdx_data{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};
context->data_provider()->AddRegisterValue(RegisterID::kX64_rax, true, rax_data);
context->data_provider()->AddRegisterValue(RegisterID::kX64_rdx, true, rdx_data);
std::vector<uint8_t> xmm0_data{0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87};
std::vector<uint8_t> xmm1_data{0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97};
context->data_provider()->AddRegisterValue(RegisterID::kX64_xmm0, false, xmm0_data);
context->data_provider()->AddRegisterValue(RegisterID::kX64_xmm1, false, xmm1_data);
// Structures consisting of floating-point data should use xmm0 followed by xmm1 registers, 8
// bytes each. Going beyond 16 bytes should fail. All purely floating-point outputs should be
// prefixes of this data.
std::vector<uint8_t> float_data;
float_data.insert(float_data.end(), xmm0_data.begin(), xmm0_data.end());
float_data.insert(float_data.end(), xmm1_data.begin(), xmm1_data.end());
TestStructureByValue(context, "OneFloat", {{"f", float_type}}, DataPrefix(float_data, 4));
TestStructureByValue(context, "TwoFloat", {{"f", float_type}, {"g", float_type}},
DataPrefix(float_data, 8));
TestStructureByValue(context, "TwoFloatDouble",
{{"f", float_type}, {"g", float_type}, {"d", double_type}},
DataPrefix(float_data, 16));
TestStructureByValueFails(
context, "TwoFloatDoubleFloat",
{{"f", float_type}, {"g", float_type}, {"d", double_type}, {"e", float_type}});
// Structures consisting of only integer and pointer data should use rax followed by rdx.
std::vector<uint8_t> int_data;
int_data.insert(int_data.end(), rax_data.begin(), rax_data.end());
int_data.insert(int_data.end(), rdx_data.begin(), rdx_data.end());
TestStructureByValue(context, "OneChar", {{"c", char_type}}, DataPrefix(int_data, 1));
TestStructureByValue(context, "Int64OneChar", {{"i", int64_type}, {"c", char_type}},
DataPrefix(int_data, 9));
TestStructureByValue(context, "Int64TwoChar",
{{"i", int64_type}, {"c", char_type}, {"c2", char_type}},
DataPrefix(int_data, 10));
TestStructureByValue(context, "Int64Pointer", {{"i", int64_type}, {"c", char_pointer_type}},
DataPrefix(int_data, 16));
TestStructureByValueFails(context, "ThreeInt64",
{{"i", int64_type}, {"j", int64_type}, {"k", int64_type}});
// Combining an integer and a float in the same 8-byte word will pack the result as an integer.
TestStructureByValue(context, "FloatChar", {{"f", float_type}, {"c", char_type}},
DataPrefix(int_data, 5));
// For structures returned in an integer and a floating-point register.
std::vector<uint8_t> int_float_data;
int_float_data.insert(int_float_data.end(), rax_data.begin(), rax_data.end());
int_float_data.insert(int_float_data.end(), xmm0_data.begin(), xmm0_data.end());
TestStructureByValue(context, "FloatInt32Double",
{{"f", float_type}, {"i", int32_type}, {"d", double_type}},
DataPrefix(int_float_data, 16));
}
TEST_F(ReturnValue, CollectionByValueARM64) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
context->set_abi(std::make_shared<AbiArm64>());
auto int64_type = MakeInt64Type();
auto float_type = MakeFloatType();
auto char_type = MakeSignedChar8Type();
auto char_pointer_type = MakeCharPointerType();
auto double_type = MakeDoubleType();
std::vector<uint8_t> x0_data{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
std::vector<uint8_t> x1_data{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_x0, true, x0_data);
context->data_provider()->AddRegisterValue(RegisterID::kARMv8_x1, true, x1_data);
// The data is just packed linrarly for all structures.
std::vector<uint8_t> data;
data.insert(data.end(), x0_data.begin(), x0_data.end());
data.insert(data.end(), x1_data.begin(), x1_data.end());
TestStructureByValue(context, "OneChar", {{"c", char_type}}, DataPrefix(data, 1));
TestStructureByValue(context, "OneFloat", {{"f", float_type}}, DataPrefix(data, 4));
TestStructureByValue(context, "TwoFloatDouble",
{{"f", float_type}, {"g", float_type}, {"d", double_type}},
DataPrefix(data, 16));
TestStructureByValue(context, "Int64Pointer", {{"i", int64_type}, {"c", char_pointer_type}},
DataPrefix(data, 16));
TestStructureByValueFails(
context, "TwoFloatDoubleFloat",
{{"f", float_type}, {"g", float_type}, {"d", double_type}, {"e", float_type}});
}
} // namespace zxdb