[lldb] Make ValueObjectDynamicValue::UpdateValue() point to a host b… (#125143)

…uffer

ValueObjectDynamicValue::UpdateValue() assumes that the dynamic type
found by GetDynamicTypeAndAddress() would return an address in the
inferior. This commit makes it so it can deal with being passed a host
address instead.

This is needed downstream by the Swift fork.

rdar://143357274
This commit is contained in:
Augusto Noronha 2025-02-06 19:04:01 -08:00 committed by GitHub
parent 6f508492d1
commit 0cbc4983ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 342 additions and 41 deletions

View File

@ -105,12 +105,16 @@ public:
"language doesn't support getting vtable information");
}
// this call should return true if it could set the name and/or the type
virtual bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
Address &address,
Value::ValueType &value_type) = 0;
/// This call should return true if it could set the name and/or the type
/// Sets address to the address of the dynamic type if value_type is set to
/// a file or load address. Sets local_buffer to a buffer containing the data
/// of the dynamic type if value_type is set to a host address. Callers should
/// copy local_buffer over into their own buffer if they want to keep the data
/// alive.
virtual bool GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) = 0;
// This call should return a CompilerType given a generic type name and an
// ExecutionContextScope in which one can actually fetch any specialization

View File

@ -865,6 +865,18 @@ public:
virtual void SetLanguageFlags(uint64_t flags) { m_language_flags = flags; }
/// Returns the size of the local buffer if it's available.
/// \return
/// The size of the local buffer if this value object's value points to a
/// host address, and if that size can be determined. Otherwise, returns
/// LLDB_INVALID_ADDRESS.
///
/// TODO: Because a ValueObject's Value can point to any arbitrary memory
/// location, it is possible that the size of the local buffer can't be
/// determined at all. See the comment in Value::m_value for a more thorough
/// explanation of why that is.
uint64_t GetLocalBufferSize();
protected:
typedef ClusterManager<ValueObject> ValueObjectManager;

View File

@ -289,7 +289,7 @@ llvm::Expected<LanguageRuntime::VTableInfo>
bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &dynamic_address,
Value::ValueType &value_type) {
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) {
// For Itanium, if the type has a vtable pointer in the object, it will be at
// offset 0 in the object. That will point to the "address point" within the
// vtable (not the beginning of the vtable.) We can then look up the symbol

View File

@ -54,8 +54,8 @@ public:
bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
Address &address,
Value::ValueType &value_type) override;
Address &address, Value::ValueType &value_type,
llvm::ArrayRef<uint8_t> &local_buffer) override;
TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name,
ValueObject &static_value) override;

View File

@ -276,7 +276,7 @@ bool AppleObjCRuntime::CouldHaveDynamicValue(ValueObject &in_value) {
bool AppleObjCRuntime::GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type) {
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) {
return false;
}

View File

@ -54,8 +54,8 @@ public:
bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
Address &address,
Value::ValueType &value_type) override;
Address &address, Value::ValueType &value_type,
llvm::ArrayRef<uint8_t> &local_buffer) override;
TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name,
ValueObject &static_value) override;

View File

@ -48,7 +48,7 @@ AppleObjCRuntimeV1::AppleObjCRuntimeV1(Process *process)
bool AppleObjCRuntimeV1::GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type) {
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) {
class_type_or_name.Clear();
value_type = Value::ValueType::Scalar;
if (CouldHaveDynamicValue(in_value)) {

View File

@ -100,8 +100,8 @@ public:
bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
Address &address,
Value::ValueType &value_type) override;
Address &address, Value::ValueType &value_type,
llvm::ArrayRef<uint8_t> &local_buffer) override;
llvm::Expected<std::unique_ptr<UtilityFunction>>
CreateObjectChecker(std::string, ExecutionContext &exe_ctx) override;

View File

@ -770,7 +770,7 @@ AppleObjCRuntimeV2::GetPreferredLanguageRuntime(ValueObject &in_value) {
bool AppleObjCRuntimeV2::GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type) {
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) {
// We should never get here with a null process...
assert(m_process != nullptr);

View File

@ -53,8 +53,8 @@ public:
bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
Address &address,
Value::ValueType &value_type) override;
Address &address, Value::ValueType &value_type,
llvm::ArrayRef<uint8_t> &local_buffer) override;
llvm::Expected<std::unique_ptr<UtilityFunction>>
CreateObjectChecker(std::string name, ExecutionContext &exe_ctx) override;

View File

@ -127,7 +127,7 @@ bool GNUstepObjCRuntime::CouldHaveDynamicValue(ValueObject &in_value) {
bool GNUstepObjCRuntime::GetDynamicTypeAndAddress(
ValueObject &in_value, DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type) {
Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) {
return false;
}

View File

@ -67,8 +67,8 @@ public:
bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
Address &address,
Value::ValueType &value_type) override;
Address &address, Value::ValueType &value_type,
llvm::ArrayRef<uint8_t> &local_buffer) override;
TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name,
ValueObject &static_value) override;

View File

@ -849,6 +849,22 @@ bool ValueObject::SetData(DataExtractor &data, Status &error) {
return true;
}
uint64_t ValueObject::GetLocalBufferSize() {
if (m_value.GetValueType() != Value::ValueType::HostAddress)
return LLDB_INVALID_ADDRESS;
auto start = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
if (start == LLDB_INVALID_ADDRESS)
return LLDB_INVALID_ADDRESS;
// Does our pointer point to this value object's m_data buffer?
if ((uint64_t)m_data.GetDataStart() == start)
return m_data.GetByteSize();
// Does our pointer point to the value's buffer?
if ((uint64_t)m_value.GetBuffer().GetBytes() == start)
return m_value.GetBuffer().GetByteSize();
// Our pointer points to something else. We can't know what the size is.
return LLDB_INVALID_ADDRESS;
}
static bool CopyStringDataToBufferSP(const StreamString &source,
lldb::WritableDataBufferSP &destination) {
llvm::StringRef src = source.GetString();

View File

@ -145,6 +145,7 @@ bool ValueObjectDynamicValue::UpdateValue() {
Address dynamic_address;
bool found_dynamic_type = false;
Value::ValueType value_type;
llvm::ArrayRef<uint8_t> local_buffer;
LanguageRuntime *runtime = nullptr;
@ -157,7 +158,7 @@ bool ValueObjectDynamicValue::UpdateValue() {
// Try the preferred runtime first.
found_dynamic_type = preferred_runtime->GetDynamicTypeAndAddress(
*m_parent, m_use_dynamic, class_type_or_name, dynamic_address,
value_type);
value_type, local_buffer);
if (found_dynamic_type)
// Set the operative `runtime` for later use in this function.
runtime = preferred_runtime;
@ -166,20 +167,20 @@ bool ValueObjectDynamicValue::UpdateValue() {
// Fallback to the runtime for `known_type`.
found_dynamic_type = runtime->GetDynamicTypeAndAddress(
*m_parent, m_use_dynamic, class_type_or_name, dynamic_address,
value_type);
value_type, local_buffer);
} else {
runtime = process->GetLanguageRuntime(lldb::eLanguageTypeC_plus_plus);
if (runtime)
found_dynamic_type = runtime->GetDynamicTypeAndAddress(
*m_parent, m_use_dynamic, class_type_or_name, dynamic_address,
value_type);
value_type, local_buffer);
if (!found_dynamic_type) {
runtime = process->GetLanguageRuntime(lldb::eLanguageTypeObjC);
if (runtime)
found_dynamic_type = runtime->GetDynamicTypeAndAddress(
*m_parent, m_use_dynamic, class_type_or_name, dynamic_address,
value_type);
value_type, local_buffer);
}
}
@ -239,11 +240,29 @@ bool ValueObjectDynamicValue::UpdateValue() {
if (m_address.IsValid())
SetValueDidChange(true);
// We've moved, so we should be fine...
m_address = dynamic_address;
lldb::TargetSP target_sp(GetTargetSP());
lldb::addr_t load_address = m_address.GetLoadAddress(target_sp.get());
m_value.GetScalar() = load_address;
// If we found a host address, and the dynamic type fits in the local buffer
// that was found, point to thar buffer. Later on this function will copy
// the buffer over.
if (value_type == Value::ValueType::HostAddress && !local_buffer.empty()) {
auto *exe_scope = exe_ctx.GetBestExecutionContextScope();
// If we found a host address but it doesn't fit in the buffer, there's
// nothing we can do.
if (local_buffer.size() <
m_dynamic_type_info.GetCompilerType().GetByteSize(exe_scope)) {
SetValueIsValid(false);
return false;
}
m_value.GetScalar() = (uint64_t)local_buffer.data();
m_address = LLDB_INVALID_ADDRESS;
} else {
// Otherwise we have a legitimate address on the target. Point to the load
// address.
m_address = dynamic_address;
lldb::TargetSP target_sp(GetTargetSP());
lldb::addr_t load_address = m_address.GetLoadAddress(target_sp.get());
m_value.GetScalar() = load_address;
}
}
if (runtime)
@ -258,7 +277,11 @@ bool ValueObjectDynamicValue::UpdateValue() {
LLDB_LOGF(log, "[%s %p] has a new dynamic type %s", GetName().GetCString(),
static_cast<void *>(this), GetTypeName().GetCString());
if (m_address.IsValid() && m_dynamic_type_info) {
// m_address could be invalid but we could still have a local buffer
// containing the dynamic value.
if ((m_address.IsValid() ||
m_value.GetValueType() == Value::ValueType::HostAddress) &&
m_dynamic_type_info) {
// The variable value is in the Scalar value inside the m_value. We can
// point our m_data right to it.
m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get());

View File

@ -21,20 +21,21 @@ inline clang::DeclarationName getDeclarationName(TypeSystemClang &ast,
return ast.getASTContext().DeclarationNames.getIdentifier(&II);
}
inline CompilerType createRecord(TypeSystemClang &ast, llvm::StringRef name) {
inline CompilerType
createRecord(TypeSystemClang &ast, llvm::StringRef name,
lldb::LanguageType lang = lldb::LanguageType::eLanguageTypeC) {
return ast.CreateRecordType(ast.getASTContext().getTranslationUnitDecl(),
OptionalClangModuleID(),
lldb::AccessType::eAccessPublic, name, 0,
lldb::LanguageType::eLanguageTypeC);
lldb::AccessType::eAccessPublic, name, 0, lang);
}
/// Create a record with the given name and a field with the given type
/// and name.
inline CompilerType createRecordWithField(TypeSystemClang &ast,
llvm::StringRef record_name,
CompilerType field_type,
llvm::StringRef field_name) {
CompilerType t = createRecord(ast, record_name);
inline CompilerType createRecordWithField(
TypeSystemClang &ast, llvm::StringRef record_name, CompilerType field_type,
llvm::StringRef field_name,
lldb::LanguageType lang = lldb::LanguageType::eLanguageTypeC) {
CompilerType t = createRecord(ast, record_name, lang);
TypeSystemClang::StartTagDeclarationDefinition(t);
ast.AddFieldToRecordType(t, field_name, field_type,
@ -63,12 +64,13 @@ struct SourceASTWithRecord {
CompilerType record_type;
clang::RecordDecl *record_decl = nullptr;
clang::FieldDecl *field_decl = nullptr;
SourceASTWithRecord() {
SourceASTWithRecord(
lldb::LanguageType lang = lldb::LanguageType::eLanguageTypeC) {
holder = std::make_unique<TypeSystemClangHolder>("test ASTContext");
ast = holder->GetAST();
record_type = createRecordWithField(
*ast, "Source", ast->GetBasicType(lldb::BasicType::eBasicTypeChar),
"a_field");
"a_field", lang);
record_decl =
llvm::cast<clang::RecordDecl>(ClangUtil::GetAsTagDecl(record_type));
field_decl = *record_decl->fields().begin();

View File

@ -1,6 +1,7 @@
add_lldb_unittest(LLDBValueObjectTests
DumpValueObjectOptionsTests.cpp
DILLexerTests.cpp
DynamicValueObjectLocalBuffer.cpp
LINK_LIBS
lldbValueObject

View File

@ -0,0 +1,243 @@
//===---DynamicValueObjectLocalBuffer.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 "Plugins/Platform/Linux/PlatformLinux.h"
#include "Plugins/ScriptInterpreter/None/ScriptInterpreterNone.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "TestingSupport/SubsystemRAII.h"
#include "TestingSupport/Symbol/ClangTestUtils.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Target/Language.h"
#include "lldb/Target/LanguageRuntime.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/ValueObject/ValueObjectConstResult.h"
#include "gtest/gtest.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::clang_utils;
// This entire class is boilerplate.
struct MockLanguage : public Language {
llvm::StringRef GetPluginName() override { return "MockLanguage"; }
lldb::LanguageType GetLanguageType() const override {
return lldb::eLanguageTypeC_plus_plus;
};
static Language *CreateInstance(lldb::LanguageType language) {
return new MockLanguage();
}
static void Initialize() {
PluginManager::RegisterPlugin("MockLanguage", "Mock Language",
CreateInstance);
};
static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
bool IsSourceFile(llvm::StringRef file_path) const override { return true; }
};
LLDB_PLUGIN_DEFINE(MockLanguage)
struct MockLanguageRuntime : public LanguageRuntime {
// This is the only method in this class that matters for this test.
// This will unconditionally succeed and return a type with size 4,
// a value_type of HostAddress, and a local buffer that points to the parent's
// local buffer.
// The tests will set that buffer to be either be larger or smaller than the
// type we're returning.
bool
GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Value::ValueType &value_type,
llvm::ArrayRef<uint8_t> &local_buffer) override {
auto ast = in_value.GetCompilerType()
.GetTypeSystem()
.dyn_cast_or_null<TypeSystemClang>();
auto int_type = createRecordWithField(
*ast, "TypeWitInt", ast->GetBasicType(lldb::BasicType::eBasicTypeInt),
"theIntField", LanguageType::eLanguageTypeC_plus_plus);
class_type_or_name.SetCompilerType(int_type);
local_buffer = {(uint8_t *)in_value.GetValue().GetScalar().ULongLong(
LLDB_INVALID_ADDRESS),
in_value.GetLocalBufferSize()};
value_type = Value::ValueType::HostAddress;
return true;
}
// All of this is boilerplate.
MockLanguageRuntime(Process *process) : LanguageRuntime(process) {}
llvm::StringRef GetPluginName() override { return "MockLanguageRuntime"; }
lldb::LanguageType GetLanguageType() const override {
return lldb::eLanguageTypeC_plus_plus;
}
llvm::Error GetObjectDescription(Stream &str, ValueObject &object) override {
return llvm::Error::success();
}
llvm::Error GetObjectDescription(Stream &str, Value &value,
ExecutionContextScope *exe_scope) override {
return llvm::Error::success();
}
bool CouldHaveDynamicValue(ValueObject &in_value) override { return true; }
TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name,
ValueObject &static_value) override {
return type_and_or_name;
}
lldb::BreakpointResolverSP
CreateExceptionResolver(const lldb::BreakpointSP &bkpt, bool catch_bp,
bool throw_bp) override {
return lldb::BreakpointResolverSP();
}
lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
bool stop_others) override {
return {};
}
static LanguageRuntime *CreateInstance(Process *process,
LanguageType language) {
return new MockLanguageRuntime(process);
}
static void Initialize() {
PluginManager::RegisterPlugin(
"MockLanguageRuntime", "MockLanguageRuntime", CreateInstance,
[](CommandInterpreter &interpreter) -> lldb::CommandObjectSP {
return {};
},
[](lldb::LanguageType language,
bool throw_bp) -> BreakpointPreconditionSP { return {}; });
}
static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
};
LLDB_PLUGIN_DEFINE(MockLanguageRuntime)
// This entire class is boilerplate.
struct MockProcess : Process {
MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp)
: Process(target_sp, listener_sp) {}
llvm::StringRef GetPluginName() override { return "mock process"; }
bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override {
return false;
};
Status DoDestroy() override { return {}; }
void RefreshStateAfterStop() override {}
bool DoUpdateThreadList(ThreadList &old_thread_list,
ThreadList &new_thread_list) override {
return false;
};
size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error) override {
// No need to read memory in these tests.
return size;
}
};
class DynamicValueObjectLocalBufferTest : public ::testing::Test {
public:
void SetUp() override {
ArchSpec arch("i386-pc-linux");
Platform::SetHostPlatform(
platform_linux::PlatformLinux::CreateInstance(true, &arch));
// std::call_once(TestUtilities::g_debugger_initialize_flag,
// []() { Debugger::Initialize(nullptr); });
m_debugger_sp = Debugger::CreateInstance();
ASSERT_TRUE(m_debugger_sp);
m_debugger_sp->GetTargetList().CreateTarget(*m_debugger_sp, "", arch,
eLoadDependentsNo,
m_platform_sp, m_target_sp);
ASSERT_TRUE(m_target_sp);
ASSERT_TRUE(m_target_sp->GetArchitecture().IsValid());
ASSERT_TRUE(m_platform_sp);
m_listener_sp = Listener::MakeListener("dummy");
m_process_sp = std::make_shared<MockProcess>(m_target_sp, m_listener_sp);
ASSERT_TRUE(m_process_sp);
m_exe_ctx = ExecutionContext(m_process_sp);
m_holder = std::make_unique<clang_utils::TypeSystemClangHolder>("test");
m_type_system = m_holder->GetAST();
LLDB_PLUGIN_INITIALIZE(MockLanguage);
LLDB_PLUGIN_INITIALIZE(MockLanguageRuntime);
}
void TearDown() override {
LLDB_PLUGIN_TERMINATE(MockLanguage);
LLDB_PLUGIN_TERMINATE(MockLanguageRuntime);
}
void TestValueObjectWithLocalBuffer(DataExtractor &data_extractor,
bool should_succeed) {
std::unique_ptr<TypeSystemClangHolder> holder =
std::make_unique<TypeSystemClangHolder>("test ASTContext");
TypeSystemClang *ast = holder->GetAST();
auto char_type = createRecordWithField(
*ast, "TypeWithChar",
ast->GetBasicType(lldb::BasicType::eBasicTypeChar), "theField");
ExecutionContextScope *exe_scope = m_exe_ctx.GetBestExecutionContextScope();
ConstString var_name("test_var");
auto valobj_sp = ValueObjectConstResult::Create(exe_scope, char_type,
var_name, data_extractor);
auto dyn_valobj = valobj_sp->GetDynamicValue(lldb::eDynamicCanRunTarget);
ASSERT_TRUE(dyn_valobj->GetValueIsValid() == should_succeed);
}
SubsystemRAII<FileSystem, HostInfo, platform_linux::PlatformLinux,
ScriptInterpreterNone>
m_subsystems;
std::unique_ptr<clang_utils::TypeSystemClangHolder> m_holder;
lldb::DebuggerSP m_debugger_sp;
lldb::TargetSP m_target_sp;
lldb::PlatformSP m_platform_sp;
lldb::ListenerSP m_listener_sp;
lldb::ProcessSP m_process_sp;
ExecutionContext m_exe_ctx;
TypeSystemClang *m_type_system;
};
TEST_F(DynamicValueObjectLocalBufferTest, BufferTooSmall) {
/// Test that a value object with a buffer to small to fit the
/// "dynamic" type will return an invalid dynamic value object.
u_int8_t value = 1;
ByteOrder endian = endian::InlHostByteOrder();
DataExtractor data_extractor{&value, sizeof(value), endian, 4};
TestValueObjectWithLocalBuffer(data_extractor, false);
}
TEST_F(DynamicValueObjectLocalBufferTest, BufferTooBig) {
/// Test that a value object with a buffer big enough fit the
/// "dynamic" type will return a valid dynamic value object.
uint64_t value = 1;
ByteOrder endian = endian::InlHostByteOrder();
DataExtractor data_extractor{&value, sizeof(value), endian, 4};
TestValueObjectWithLocalBuffer(data_extractor, true);
}
TEST_F(DynamicValueObjectLocalBufferTest, BufferExactlyRight) {
/// Test that a value object with a buffer exactly the size of the
/// "dynamic" type will return a valid dynamic value object.
uint32_t value = 1;
ByteOrder endian = endian::InlHostByteOrder();
DataExtractor data_extractor{&value, sizeof(value), endian, 4};
TestValueObjectWithLocalBuffer(data_extractor, true);
}