David Spickett b3396c5e96
[lldb] Account for registers being host endian when casting values (#150011)
Fixes https://github.com/llvm/llvm-project/issues/135707

Follow up to https://github.com/llvm/llvm-project/pull/148836
which fixed some of this issue but not all of it. 

Our Value/ValueObject system does not store the endian directly
in the values. Instead it assumes that the endian of the result
of a cast can be assumed to be the target's endian, or the host
but only as a fallback. It assumes the place it is copying from
is also that endian.

This breaks down when you have register values. These are always
host endian and continue to be when cast. Casting them to big 
endian when on a little endian host breaks certain calls like
GetValueAsUnsigned.

To fix this, check the context of the value. If it has a register
context, always treat it as host endian and make the result host
endian.

I had an alternative where I passed an "is_register" flag into
all calls to this, but it felt like a layering violation and changed
many more lines.

This solution isn't much more robust, but it works for all the test
cases I know of. Perhaps you can create a register value without
a RegisterInfo backing it, but I don't know of a way myself.

For testing, I had to add a minimal program file for each arch
so that there is a type system to support the casting. This is
generated from YAML since we only need the machine and endian
to be set.
2025-08-13 14:58:29 +01:00

703 lines
22 KiB
C++

//===-- Value.cpp ---------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Core/Value.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Module.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Symbol/Type.h"
#include "lldb/Symbol/Variable.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/Endian.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
#include "lldb/Utility/Stream.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-forward.h"
#include "lldb/lldb-types.h"
#include <memory>
#include <optional>
#include <string>
#include <cinttypes>
using namespace lldb;
using namespace lldb_private;
Value::Value() : m_value(), m_compiler_type(), m_data_buffer() {}
Value::Value(const Scalar &scalar)
: m_value(scalar), m_compiler_type(), m_data_buffer() {}
Value::Value(const void *bytes, int len)
: m_value(), m_compiler_type(), m_value_type(ValueType::HostAddress),
m_data_buffer() {
SetBytes(bytes, len);
}
Value::Value(const Value &v)
: m_value(v.m_value), m_compiler_type(v.m_compiler_type),
m_context(v.m_context), m_value_type(v.m_value_type),
m_context_type(v.m_context_type), m_data_buffer() {
const uintptr_t rhs_value =
(uintptr_t)v.m_value.ULongLong(LLDB_INVALID_ADDRESS);
if ((rhs_value != 0) &&
(rhs_value == (uintptr_t)v.m_data_buffer.GetBytes())) {
m_data_buffer.CopyData(v.m_data_buffer.GetBytes(),
v.m_data_buffer.GetByteSize());
m_value = (uintptr_t)m_data_buffer.GetBytes();
}
}
Value &Value::operator=(const Value &rhs) {
if (this != &rhs) {
m_value = rhs.m_value;
m_compiler_type = rhs.m_compiler_type;
m_context = rhs.m_context;
m_value_type = rhs.m_value_type;
m_context_type = rhs.m_context_type;
const uintptr_t rhs_value =
(uintptr_t)rhs.m_value.ULongLong(LLDB_INVALID_ADDRESS);
if ((rhs_value != 0) &&
(rhs_value == (uintptr_t)rhs.m_data_buffer.GetBytes())) {
m_data_buffer.CopyData(rhs.m_data_buffer.GetBytes(),
rhs.m_data_buffer.GetByteSize());
m_value = (uintptr_t)m_data_buffer.GetBytes();
}
}
return *this;
}
void Value::SetBytes(const void *bytes, int len) {
m_value_type = ValueType::HostAddress;
m_data_buffer.CopyData(bytes, len);
m_value = (uintptr_t)m_data_buffer.GetBytes();
}
void Value::AppendBytes(const void *bytes, int len) {
m_value_type = ValueType::HostAddress;
m_data_buffer.AppendData(bytes, len);
m_value = (uintptr_t)m_data_buffer.GetBytes();
}
void Value::Dump(Stream *strm) {
if (!strm)
return;
m_value.GetValue(*strm, true);
strm->Printf(", value_type = %s, context = %p, context_type = %s",
Value::GetValueTypeAsCString(m_value_type), m_context,
Value::GetContextTypeAsCString(m_context_type));
}
Value::ValueType Value::GetValueType() const { return m_value_type; }
AddressType Value::GetValueAddressType() const {
switch (m_value_type) {
case ValueType::Invalid:
case ValueType::Scalar:
break;
case ValueType::LoadAddress:
return eAddressTypeLoad;
case ValueType::FileAddress:
return eAddressTypeFile;
case ValueType::HostAddress:
return eAddressTypeHost;
}
return eAddressTypeInvalid;
}
Value::ValueType Value::GetValueTypeFromAddressType(AddressType address_type) {
switch (address_type) {
case eAddressTypeFile:
return Value::ValueType::FileAddress;
case eAddressTypeLoad:
return Value::ValueType::LoadAddress;
case eAddressTypeHost:
return Value::ValueType::HostAddress;
case eAddressTypeInvalid:
return Value::ValueType::Invalid;
}
llvm_unreachable("Unexpected address type!");
}
RegisterInfo *Value::GetRegisterInfo() const {
if (m_context_type == ContextType::RegisterInfo)
return static_cast<RegisterInfo *>(m_context);
return nullptr;
}
Type *Value::GetType() {
if (m_context_type == ContextType::LLDBType)
return static_cast<Type *>(m_context);
return nullptr;
}
size_t Value::AppendDataToHostBuffer(const Value &rhs) {
if (this == &rhs)
return 0;
size_t curr_size = m_data_buffer.GetByteSize();
Status error;
switch (rhs.GetValueType()) {
case ValueType::Invalid:
return 0;
case ValueType::Scalar: {
const size_t scalar_size = rhs.m_value.GetByteSize();
if (scalar_size > 0) {
const size_t new_size = curr_size + scalar_size;
if (ResizeData(new_size) == new_size) {
rhs.m_value.GetAsMemoryData(m_data_buffer.GetBytes() + curr_size,
scalar_size, endian::InlHostByteOrder(),
error);
return scalar_size;
}
}
} break;
case ValueType::FileAddress:
case ValueType::LoadAddress:
case ValueType::HostAddress: {
const uint8_t *src = rhs.GetBuffer().GetBytes();
const size_t src_len = rhs.GetBuffer().GetByteSize();
if (src && src_len > 0) {
const size_t new_size = curr_size + src_len;
if (ResizeData(new_size) == new_size) {
::memcpy(m_data_buffer.GetBytes() + curr_size, src, src_len);
return src_len;
}
}
} break;
}
return 0;
}
size_t Value::ResizeData(size_t len) {
m_value_type = ValueType::HostAddress;
m_data_buffer.SetByteSize(len);
m_value = (uintptr_t)m_data_buffer.GetBytes();
return m_data_buffer.GetByteSize();
}
bool Value::ValueOf(ExecutionContext *exe_ctx) {
switch (m_context_type) {
case ContextType::Invalid:
case ContextType::RegisterInfo: // RegisterInfo *
case ContextType::LLDBType: // Type *
break;
case ContextType::Variable: // Variable *
ResolveValue(exe_ctx);
return true;
}
return false;
}
uint64_t Value::GetValueByteSize(Status *error_ptr, ExecutionContext *exe_ctx) {
switch (m_context_type) {
case ContextType::RegisterInfo: // RegisterInfo *
if (GetRegisterInfo()) {
if (error_ptr)
error_ptr->Clear();
return GetRegisterInfo()->byte_size;
}
break;
case ContextType::Invalid:
case ContextType::LLDBType: // Type *
case ContextType::Variable: // Variable *
{
auto *scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr;
auto size_or_err = GetCompilerType().GetByteSize(scope);
if (!size_or_err) {
if (error_ptr && error_ptr->Success())
*error_ptr = Status::FromError(size_or_err.takeError());
else
LLDB_LOG_ERRORV(GetLog(LLDBLog::Types), size_or_err.takeError(), "{0}");
} else {
if (error_ptr)
error_ptr->Clear();
return *size_or_err;
}
break;
}
}
if (error_ptr && error_ptr->Success())
*error_ptr = Status::FromErrorString("Unable to determine byte size.");
return 0;
}
const CompilerType &Value::GetCompilerType() {
if (!m_compiler_type.IsValid()) {
switch (m_context_type) {
case ContextType::Invalid:
break;
case ContextType::RegisterInfo:
break; // TODO: Eventually convert into a compiler type?
case ContextType::LLDBType: {
Type *lldb_type = GetType();
if (lldb_type)
m_compiler_type = lldb_type->GetForwardCompilerType();
} break;
case ContextType::Variable: {
Variable *variable = GetVariable();
if (variable) {
Type *variable_type = variable->GetType();
if (variable_type)
m_compiler_type = variable_type->GetForwardCompilerType();
}
} break;
}
}
return m_compiler_type;
}
void Value::SetCompilerType(const CompilerType &compiler_type) {
m_compiler_type = compiler_type;
}
lldb::Format Value::GetValueDefaultFormat() {
switch (m_context_type) {
case ContextType::RegisterInfo:
if (GetRegisterInfo())
return GetRegisterInfo()->format;
break;
case ContextType::Invalid:
case ContextType::LLDBType:
case ContextType::Variable: {
const CompilerType &ast_type = GetCompilerType();
if (ast_type.IsValid())
return ast_type.GetFormat();
} break;
}
// Return a good default in case we can't figure anything out
return eFormatHex;
}
bool Value::GetData(DataExtractor &data) {
switch (m_value_type) {
case ValueType::Invalid:
return false;
case ValueType::Scalar:
if (m_value.GetData(data))
return true;
break;
case ValueType::LoadAddress:
case ValueType::FileAddress:
case ValueType::HostAddress:
if (m_data_buffer.GetByteSize()) {
data.SetData(m_data_buffer.GetBytes(), m_data_buffer.GetByteSize(),
data.GetByteOrder());
return true;
}
break;
}
return false;
}
Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
Module *module) {
data.Clear();
Status error;
lldb::addr_t address = LLDB_INVALID_ADDRESS;
AddressType address_type = eAddressTypeFile;
Address file_so_addr;
const CompilerType &ast_type = GetCompilerType();
std::optional<uint64_t> type_size =
llvm::expectedToOptional(ast_type.GetByteSize(
exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr));
// Nothing to be done for a zero-sized type.
if (type_size && *type_size == 0)
return error;
switch (m_value_type) {
case ValueType::Invalid:
error = Status::FromErrorString("invalid value");
break;
case ValueType::Scalar: {
data.SetByteOrder(endian::InlHostByteOrder());
if (ast_type.IsValid())
data.SetAddressByteSize(ast_type.GetPointerByteSize());
else
data.SetAddressByteSize(sizeof(void *));
uint32_t result_byte_size = *type_size;
if (m_value.GetData(data, result_byte_size))
return error; // Success;
error = Status::FromErrorString("extracting data from value failed");
break;
}
case ValueType::LoadAddress:
if (exe_ctx == nullptr) {
error = Status::FromErrorString(
"can't read load address (no execution context)");
} else {
Process *process = exe_ctx->GetProcessPtr();
if (process == nullptr || !process->IsAlive()) {
Target *target = exe_ctx->GetTargetPtr();
if (target) {
// Allow expressions to run and evaluate things when the target has
// memory sections loaded. This allows you to use "target modules
// load" to load your executable and any shared libraries, then
// execute commands where you can look at types in data sections.
if (target->HasLoadedSections()) {
address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
if (target->ResolveLoadAddress(address, file_so_addr)) {
address_type = eAddressTypeLoad;
data.SetByteOrder(target->GetArchitecture().GetByteOrder());
data.SetAddressByteSize(
target->GetArchitecture().GetAddressByteSize());
} else
address = LLDB_INVALID_ADDRESS;
}
} else {
error = Status::FromErrorString(
"can't read load address (invalid process)");
}
} else {
address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
address_type = eAddressTypeLoad;
data.SetByteOrder(
process->GetTarget().GetArchitecture().GetByteOrder());
data.SetAddressByteSize(
process->GetTarget().GetArchitecture().GetAddressByteSize());
}
}
break;
case ValueType::FileAddress:
if (exe_ctx == nullptr) {
error = Status::FromErrorString(
"can't read file address (no execution context)");
} else if (exe_ctx->GetTargetPtr() == nullptr) {
error =
Status::FromErrorString("can't read file address (invalid target)");
} else {
address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
if (address == LLDB_INVALID_ADDRESS) {
error = Status::FromErrorString("invalid file address");
} else {
if (module == nullptr) {
// The only thing we can currently lock down to a module so that we
// can resolve a file address, is a variable.
Variable *variable = GetVariable();
if (variable) {
SymbolContext var_sc;
variable->CalculateSymbolContext(&var_sc);
module = var_sc.module_sp.get();
}
}
if (module) {
bool resolved = false;
ObjectFile *objfile = module->GetObjectFile();
if (objfile) {
Address so_addr(address, objfile->GetSectionList());
addr_t load_address =
so_addr.GetLoadAddress(exe_ctx->GetTargetPtr());
bool process_launched_and_stopped =
exe_ctx->GetProcessPtr()
? StateIsStoppedState(exe_ctx->GetProcessPtr()->GetState(),
true /* must_exist */)
: false;
// Don't use the load address if the process has exited.
if (load_address != LLDB_INVALID_ADDRESS &&
process_launched_and_stopped) {
resolved = true;
address = load_address;
address_type = eAddressTypeLoad;
data.SetByteOrder(
exe_ctx->GetTargetRef().GetArchitecture().GetByteOrder());
data.SetAddressByteSize(exe_ctx->GetTargetRef()
.GetArchitecture()
.GetAddressByteSize());
} else {
if (so_addr.IsSectionOffset()) {
resolved = true;
file_so_addr = so_addr;
data.SetByteOrder(objfile->GetByteOrder());
data.SetAddressByteSize(objfile->GetAddressByteSize());
}
}
}
if (!resolved) {
Variable *variable = GetVariable();
if (module) {
if (variable)
error = Status::FromErrorStringWithFormat(
"unable to resolve the module for file address 0x%" PRIx64
" for variable '%s' in %s",
address, variable->GetName().AsCString(""),
module->GetFileSpec().GetPath().c_str());
else
error = Status::FromErrorStringWithFormat(
"unable to resolve the module for file address 0x%" PRIx64
" in %s",
address, module->GetFileSpec().GetPath().c_str());
} else {
if (variable)
error = Status::FromErrorStringWithFormat(
"unable to resolve the module for file address 0x%" PRIx64
" for variable '%s'",
address, variable->GetName().AsCString(""));
else
error = Status::FromErrorStringWithFormat(
"unable to resolve the module for file address 0x%" PRIx64,
address);
}
}
} else {
// Can't convert a file address to anything valid without more
// context (which Module it came from)
error = Status::FromErrorString(
"can't read memory from file address without more context");
}
}
}
break;
case ValueType::HostAddress:
address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
address_type = eAddressTypeHost;
if (exe_ctx) {
if (Target *target = exe_ctx->GetTargetPtr()) {
// Registers are always stored in host endian.
data.SetByteOrder(m_context_type == ContextType::RegisterInfo
? endian::InlHostByteOrder()
: target->GetArchitecture().GetByteOrder());
data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize());
break;
}
}
// fallback to host settings
data.SetByteOrder(endian::InlHostByteOrder());
data.SetAddressByteSize(sizeof(void *));
break;
}
// Bail if we encountered any errors
if (error.Fail())
return error;
if (address == LLDB_INVALID_ADDRESS) {
error = Status::FromErrorStringWithFormat(
"invalid %s address",
address_type == eAddressTypeHost ? "host" : "load");
return error;
}
// If we got here, we need to read the value from memory.
size_t byte_size = GetValueByteSize(&error, exe_ctx);
// Bail if we encountered any errors getting the byte size.
if (error.Fail())
return error;
// No memory to read for zero-sized types.
if (byte_size == 0)
return error;
// Make sure we have enough room within "data", and if we don't make
// something large enough that does
if (!data.ValidOffsetForDataOfSize(0, byte_size)) {
auto data_sp = std::make_shared<DataBufferHeap>(byte_size, '\0');
data.SetData(data_sp);
}
uint8_t *dst = const_cast<uint8_t *>(data.PeekData(0, byte_size));
if (dst != nullptr) {
if (address_type == eAddressTypeHost) {
// The address is an address in this process, so just copy it.
if (address == 0) {
error =
Status::FromErrorString("trying to read from host address of 0.");
return error;
}
memcpy(dst, reinterpret_cast<uint8_t *>(address), byte_size);
} else if ((address_type == eAddressTypeLoad) ||
(address_type == eAddressTypeFile)) {
if (file_so_addr.IsValid()) {
const bool force_live_memory = true;
if (exe_ctx->GetTargetRef().ReadMemory(file_so_addr, dst, byte_size,
error, force_live_memory) !=
byte_size) {
error = Status::FromErrorStringWithFormat(
"read memory from 0x%" PRIx64 " failed", (uint64_t)address);
}
} else {
// The execution context might have a NULL process, but it might have a
// valid process in the exe_ctx->target, so use the
// ExecutionContext::GetProcess accessor to ensure we get the process
// if there is one.
Process *process = exe_ctx->GetProcessPtr();
if (process) {
const size_t bytes_read =
process->ReadMemory(address, dst, byte_size, error);
if (bytes_read != byte_size)
error = Status::FromErrorStringWithFormat(
"read memory from 0x%" PRIx64 " failed (%u of %u bytes read)",
(uint64_t)address, (uint32_t)bytes_read, (uint32_t)byte_size);
} else {
error = Status::FromErrorStringWithFormat(
"read memory from 0x%" PRIx64 " failed (invalid process)",
(uint64_t)address);
}
}
} else {
error = Status::FromErrorStringWithFormat(
"unsupported AddressType value (%i)", address_type);
}
} else {
error = Status::FromErrorString("out of memory");
}
return error;
}
Scalar &Value::ResolveValue(ExecutionContext *exe_ctx, Module *module) {
const CompilerType &compiler_type = GetCompilerType();
if (compiler_type.IsValid()) {
switch (m_value_type) {
case ValueType::Invalid:
case ValueType::Scalar: // raw scalar value
break;
case ValueType::FileAddress:
case ValueType::LoadAddress: // load address value
case ValueType::HostAddress: // host address value (for memory in the process
// that is using liblldb)
{
DataExtractor data;
lldb::addr_t addr = m_value.ULongLong(LLDB_INVALID_ADDRESS);
Status error(GetValueAsData(exe_ctx, data, module));
if (error.Success()) {
Scalar scalar;
if (compiler_type.GetValueAsScalar(
data, 0, data.GetByteSize(), scalar,
exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr)) {
m_value = scalar;
m_value_type = ValueType::Scalar;
} else {
if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) {
m_value.Clear();
m_value_type = ValueType::Scalar;
}
}
} else {
if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) {
m_value.Clear();
m_value_type = ValueType::Scalar;
}
}
} break;
}
}
return m_value;
}
Variable *Value::GetVariable() {
if (m_context_type == ContextType::Variable)
return static_cast<Variable *>(m_context);
return nullptr;
}
void Value::Clear() {
m_value.Clear();
m_compiler_type.Clear();
m_value_type = ValueType::Scalar;
m_context = nullptr;
m_context_type = ContextType::Invalid;
m_data_buffer.Clear();
}
const char *Value::GetValueTypeAsCString(ValueType value_type) {
switch (value_type) {
case ValueType::Invalid:
return "invalid";
case ValueType::Scalar:
return "scalar";
case ValueType::FileAddress:
return "file address";
case ValueType::LoadAddress:
return "load address";
case ValueType::HostAddress:
return "host address";
};
llvm_unreachable("enum cases exhausted.");
}
const char *Value::GetContextTypeAsCString(ContextType context_type) {
switch (context_type) {
case ContextType::Invalid:
return "invalid";
case ContextType::RegisterInfo:
return "RegisterInfo *";
case ContextType::LLDBType:
return "Type *";
case ContextType::Variable:
return "Variable *";
};
llvm_unreachable("enum cases exhausted.");
}
void Value::ConvertToLoadAddress(Module *module, Target *target) {
if (!module || !target || (GetValueType() != ValueType::FileAddress))
return;
lldb::addr_t file_addr = GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
if (file_addr == LLDB_INVALID_ADDRESS)
return;
Address so_addr;
if (!module->ResolveFileAddress(file_addr, so_addr))
return;
lldb::addr_t load_addr = so_addr.GetLoadAddress(target);
if (load_addr == LLDB_INVALID_ADDRESS)
return;
SetValueType(Value::ValueType::LoadAddress);
GetScalar() = load_addr;
}
void ValueList::PushValue(const Value &value) { m_values.push_back(value); }
size_t ValueList::GetSize() { return m_values.size(); }
Value *ValueList::GetValueAtIndex(size_t idx) {
if (idx < GetSize()) {
return &(m_values[idx]);
} else
return nullptr;
}
void ValueList::Clear() { m_values.clear(); }