| //===-- Materializer.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/Expression/Materializer.h" | |
| #include "lldb/Core/DumpDataExtractor.h" | |
| #include "lldb/Expression/ExpressionVariable.h" | |
| #include "lldb/Symbol/Symbol.h" | |
| #include "lldb/Symbol/Type.h" | |
| #include "lldb/Symbol/Variable.h" | |
| #include "lldb/Target/ExecutionContext.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/StackFrame.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/RegisterValue.h" | |
| #include "lldb/ValueObject/ValueObjectConstResult.h" | |
| #include "lldb/ValueObject/ValueObjectVariable.h" | |
| #include "lldb/lldb-forward.h" | |
| #include <memory> | |
| #include <optional> | |
| using namespace lldb_private; | |
| // FIXME: these should be retrieved from the target | |
| // instead of being hard-coded. Currently we | |
| // assume that persistent vars are materialized | |
| // as references, and thus pick the size of a | |
| // 64-bit pointer. | |
| static constexpr uint32_t g_default_var_alignment = 8; | |
| static constexpr uint32_t g_default_var_byte_size = 8; | |
| uint32_t Materializer::AddStructMember(Entity &entity) { | |
| uint32_t size = entity.GetSize(); | |
| uint32_t alignment = entity.GetAlignment(); | |
| uint32_t ret; | |
| if (m_current_offset == 0) | |
| m_struct_alignment = alignment; | |
| if (m_current_offset % alignment) | |
| m_current_offset += (alignment - (m_current_offset % alignment)); | |
| ret = m_current_offset; | |
| m_current_offset += size; | |
| return ret; | |
| } | |
| class EntityPersistentVariable : public Materializer::Entity { | |
| public: | |
| EntityPersistentVariable(lldb::ExpressionVariableSP &persistent_variable_sp, | |
| Materializer::PersistentVariableDelegate *delegate) | |
| : Entity(), m_persistent_variable_sp(persistent_variable_sp), | |
| m_delegate(delegate) { | |
| // Hard-coding to maximum size of a pointer since persistent variables are | |
| // materialized by reference | |
| m_size = g_default_var_byte_size; | |
| m_alignment = g_default_var_alignment; | |
| } | |
| void MakeAllocation(IRMemoryMap &map, Status &err) { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| // Allocate a spare memory area to store the persistent variable's | |
| // contents. | |
| const bool zero_memory = false; | |
| IRMemoryMap::AllocationPolicy used_policy; | |
| const uint64_t malloc_size = | |
| llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0); | |
| auto address_or_error = map.Malloc( | |
| malloc_size, 8, lldb::ePermissionsReadable | lldb::ePermissionsWritable, | |
| IRMemoryMap::eAllocationPolicyMirror, zero_memory, &used_policy); | |
| if (!address_or_error) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't allocate a memory area to store %s: %s", | |
| m_persistent_variable_sp->GetName().GetCString(), | |
| toString(address_or_error.takeError()).c_str()); | |
| return; | |
| } | |
| lldb::addr_t mem = *address_or_error; | |
| LLDB_LOGF( | |
| log, "Allocated 0x%" PRIx64 "bytes for %s (0x%" PRIx64 ") successfully", | |
| malloc_size, m_persistent_variable_sp->GetName().GetCString(), mem); | |
| // Put the location of the spare memory into the live data of the | |
| // ValueObject. | |
| m_persistent_variable_sp->m_live_sp = ValueObjectConstResult::Create( | |
| map.GetBestExecutionContextScope(), | |
| m_persistent_variable_sp->GetCompilerType(), | |
| m_persistent_variable_sp->GetName(), mem, eAddressTypeLoad, | |
| map.GetAddressByteSize()); | |
| if (used_policy == IRMemoryMap::eAllocationPolicyMirror) { | |
| if (m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVKeepInTarget) { | |
| // Clear the flag if the variable will never be deallocated. | |
| Status leak_error; | |
| map.Leak(mem, leak_error); | |
| m_persistent_variable_sp->m_flags &= | |
| ~ExpressionVariable::EVNeedsAllocation; | |
| } | |
| } else { | |
| // If we cannot allocate memory in the process, | |
| // - clear the 'EVKeepInTarget' flag to ensure that 'm_live_sp' is reset | |
| // during dematerialization, | |
| m_persistent_variable_sp->m_flags &= ~ExpressionVariable::EVKeepInTarget; | |
| // - set the 'EVNeedsFreezeDry' flag so that the value is copied to | |
| // 'm_frozen_sp' during dematerialization. | |
| m_persistent_variable_sp->m_flags |= ExpressionVariable::EVNeedsFreezeDry; | |
| } | |
| // Write the contents of the variable to the area. | |
| Status write_error; | |
| map.WriteMemory( | |
| mem, m_persistent_variable_sp->GetValueBytes(), | |
| llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0), | |
| write_error); | |
| if (!write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write {0} to the target: {1}", | |
| m_persistent_variable_sp->GetName(), write_error.AsCString()); | |
| return; | |
| } | |
| } | |
| void DestroyAllocation(IRMemoryMap &map, Status &err) { | |
| Status deallocate_error; | |
| lldb::ValueObjectSP live_valobj_sp = | |
| m_persistent_variable_sp->GetLiveObject(); | |
| map.Free((lldb::addr_t)live_valobj_sp->GetValue().GetScalar().ULongLong(), | |
| deallocate_error); | |
| live_valobj_sp.reset(); | |
| if (!deallocate_error.Success()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't deallocate memory for %s: %s", | |
| m_persistent_variable_sp->GetName().GetCString(), | |
| deallocate_error.AsCString()); | |
| } | |
| } | |
| void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOG(log, | |
| "EntityPersistentVariable::Materialize [address = {0:x}, m_name = " | |
| "{1}, m_flags = {2:x}]", | |
| (uint64_t)load_addr, m_persistent_variable_sp->GetName(), | |
| m_persistent_variable_sp->m_flags); | |
| if (m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVNeedsAllocation) { | |
| MakeAllocation(map, err); | |
| m_persistent_variable_sp->m_flags |= | |
| ExpressionVariable::EVIsLLDBAllocated; | |
| if (!err.Success()) | |
| return; | |
| } | |
| lldb::ValueObjectSP live_valobj_sp = | |
| m_persistent_variable_sp->GetLiveObject(); | |
| if ((m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVIsProgramReference && | |
| live_valobj_sp) || | |
| m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVIsLLDBAllocated) { | |
| Status write_error; | |
| map.WriteScalarToMemory(load_addr, live_valobj_sp->GetValue().GetScalar(), | |
| map.GetAddressByteSize(), write_error); | |
| if (!write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write the location of {0} to memory: {1}", | |
| m_persistent_variable_sp->GetName(), write_error.AsCString()); | |
| } | |
| } else { | |
| err = Status::FromErrorStringWithFormatv( | |
| "no materialization happened for persistent variable {0}", | |
| m_persistent_variable_sp->GetName()); | |
| return; | |
| } | |
| } | |
| void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, lldb::addr_t frame_top, | |
| lldb::addr_t frame_bottom, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOG(log, | |
| "EntityPersistentVariable::Dematerialize [address = {0:x}, m_name " | |
| "= {1}, m_flags = {2}]", | |
| (uint64_t)process_address + m_offset, | |
| m_persistent_variable_sp->GetName(), | |
| m_persistent_variable_sp->m_flags); | |
| if (m_delegate) { | |
| m_delegate->DidDematerialize(m_persistent_variable_sp); | |
| } | |
| lldb::ValueObjectSP live_valobj_sp = | |
| m_persistent_variable_sp->GetLiveObject(); | |
| if ((m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVIsLLDBAllocated) || | |
| (m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVIsProgramReference)) { | |
| if (m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVIsProgramReference && | |
| !live_valobj_sp) { | |
| // If the reference comes from the program, then the | |
| // ClangExpressionVariable's live variable data hasn't been set up yet. | |
| // Do this now. | |
| lldb::addr_t location; | |
| Status read_error; | |
| map.ReadPointerFromMemory(&location, load_addr, read_error); | |
| if (!read_error.Success()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't read the address of program-allocated variable %s: %s", | |
| m_persistent_variable_sp->GetName().GetCString(), | |
| read_error.AsCString()); | |
| return; | |
| } | |
| m_persistent_variable_sp->m_live_sp = ValueObjectConstResult::Create( | |
| map.GetBestExecutionContextScope(), | |
| m_persistent_variable_sp->GetCompilerType(), | |
| m_persistent_variable_sp->GetName(), location, eAddressTypeLoad, | |
| llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0)); | |
| if (frame_top != LLDB_INVALID_ADDRESS && | |
| frame_bottom != LLDB_INVALID_ADDRESS && location >= frame_bottom && | |
| location <= frame_top) { | |
| // If the variable is resident in the stack frame created by the | |
| // expression, then it cannot be relied upon to stay around. We | |
| // treat it as needing reallocation. | |
| m_persistent_variable_sp->m_flags |= | |
| ExpressionVariable::EVIsLLDBAllocated; | |
| m_persistent_variable_sp->m_flags |= | |
| ExpressionVariable::EVNeedsAllocation; | |
| m_persistent_variable_sp->m_flags |= | |
| ExpressionVariable::EVNeedsFreezeDry; | |
| m_persistent_variable_sp->m_flags &= | |
| ~ExpressionVariable::EVIsProgramReference; | |
| } | |
| } | |
| if (!live_valobj_sp) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't find the memory area used to store %s", | |
| m_persistent_variable_sp->GetName().GetCString()); | |
| return; | |
| } | |
| lldb::addr_t mem = live_valobj_sp->GetValue().GetScalar().ULongLong(); | |
| if (live_valobj_sp->GetValue().GetValueAddressType() != | |
| eAddressTypeLoad) { | |
| err = Status::FromErrorStringWithFormat( | |
| "the address of the memory area for %s is in an incorrect format", | |
| m_persistent_variable_sp->GetName().GetCString()); | |
| return; | |
| } | |
| if (m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVNeedsFreezeDry || | |
| m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVKeepInTarget) { | |
| LLDB_LOGF(log, "Dematerializing %s from 0x%" PRIx64 " (size = %llu)", | |
| m_persistent_variable_sp->GetName().GetCString(), | |
| (uint64_t)mem, | |
| (unsigned long long)llvm::expectedToOptional( | |
| m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0)); | |
| // Read the contents of the spare memory area | |
| m_persistent_variable_sp->ValueUpdated(); | |
| Status read_error; | |
| map.ReadMemory( | |
| m_persistent_variable_sp->GetValueBytes(), mem, | |
| llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0), | |
| read_error); | |
| if (!read_error.Success()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't read the contents of %s from memory: %s", | |
| m_persistent_variable_sp->GetName().GetCString(), | |
| read_error.AsCString()); | |
| return; | |
| } | |
| m_persistent_variable_sp->m_flags &= | |
| ~ExpressionVariable::EVNeedsFreezeDry; | |
| } | |
| } else { | |
| err = Status::FromErrorStringWithFormatv( | |
| "no dematerialization happened for persistent variable {0}", | |
| m_persistent_variable_sp->GetName()); | |
| return; | |
| } | |
| if (m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVNeedsAllocation && | |
| !(m_persistent_variable_sp->m_flags & | |
| ExpressionVariable::EVKeepInTarget)) { | |
| DestroyAllocation(map, err); | |
| if (!err.Success()) | |
| return; | |
| } | |
| } | |
| void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address, | |
| Log *log) override { | |
| StreamString dump_stream; | |
| Status err; | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| dump_stream.Format("{0:x}: EntityPersistentVariable ({1})\n", load_addr, | |
| m_persistent_variable_sp->GetName()); | |
| { | |
| dump_stream.PutCString("Pointer:\n"); | |
| DataBufferHeap data(m_size, 0); | |
| map.ReadMemory(data.GetBytes(), load_addr, m_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| { | |
| dump_stream.PutCString("Target:\n"); | |
| lldb::addr_t target_address; | |
| map.ReadPointerFromMemory(&target_address, load_addr, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DataBufferHeap data( | |
| llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0), | |
| 0); | |
| map.ReadMemory( | |
| data.GetBytes(), target_address, | |
| llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize()) | |
| .value_or(0), | |
| err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| target_address); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| } | |
| log->PutString(dump_stream.GetString()); | |
| } | |
| void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {} | |
| private: | |
| lldb::ExpressionVariableSP m_persistent_variable_sp; | |
| Materializer::PersistentVariableDelegate *m_delegate; | |
| }; | |
| uint32_t Materializer::AddPersistentVariable( | |
| lldb::ExpressionVariableSP &persistent_variable_sp, | |
| PersistentVariableDelegate *delegate, Status &err) { | |
| EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); | |
| *iter = std::make_unique<EntityPersistentVariable>(persistent_variable_sp, | |
| delegate); | |
| uint32_t ret = AddStructMember(**iter); | |
| (*iter)->SetOffset(ret); | |
| return ret; | |
| } | |
| /// Base class for materialization of Variables and ValueObjects. | |
| /// | |
| /// Subclasses specify how to obtain the Value which is to be | |
| /// materialized. | |
| class EntityVariableBase : public Materializer::Entity { | |
| public: | |
| virtual ~EntityVariableBase() = default; | |
| EntityVariableBase() { | |
| // Hard-coding to maximum size of a pointer since all variables are | |
| // materialized by reference | |
| m_size = g_default_var_byte_size; | |
| m_alignment = g_default_var_alignment; | |
| } | |
| void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOGF(log, | |
| "EntityVariable::Materialize [address = 0x%" PRIx64 | |
| ", m_variable_sp = %s]", | |
| (uint64_t)load_addr, GetName().GetCString()); | |
| ExecutionContextScope *scope = frame_sp.get(); | |
| if (!scope) | |
| scope = map.GetBestExecutionContextScope(); | |
| lldb::ValueObjectSP valobj_sp = SetupValueObject(scope); | |
| if (!valobj_sp) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't get a value object for variable {0}", GetName()); | |
| return; | |
| } | |
| Status valobj_error = valobj_sp->GetError().Clone(); | |
| if (valobj_error.Fail()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't get the value of variable {0}: {1}", GetName(), | |
| valobj_error.AsCString()); | |
| return; | |
| } | |
| if (m_is_reference) { | |
| DataExtractor valobj_extractor; | |
| Status extract_error; | |
| valobj_sp->GetData(valobj_extractor, extract_error); | |
| if (!extract_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't read contents of reference variable {0}: {1}", GetName(), | |
| extract_error.AsCString()); | |
| return; | |
| } | |
| lldb::offset_t offset = 0; | |
| lldb::addr_t reference_addr = valobj_extractor.GetAddress(&offset); | |
| Status write_error; | |
| map.WritePointerToMemory(load_addr, reference_addr, write_error); | |
| if (!write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write the contents of reference variable {} to memory: " | |
| "{}", | |
| GetName(), write_error.AsCString()); | |
| return; | |
| } | |
| } else { | |
| lldb::addr_t addr_of_valobj = | |
| valobj_sp->GetAddressOf(/*scalar_is_load_address=*/false).address; | |
| if (addr_of_valobj != LLDB_INVALID_ADDRESS) { | |
| Status write_error; | |
| map.WritePointerToMemory(load_addr, addr_of_valobj, write_error); | |
| if (!write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write the address of variable {0} to memory: {1}", | |
| GetName(), write_error.AsCString()); | |
| return; | |
| } | |
| } else { | |
| DataExtractor data; | |
| Status extract_error; | |
| valobj_sp->GetData(data, extract_error); | |
| if (!extract_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't get the value of {0}: {1}", GetName(), | |
| extract_error.AsCString()); | |
| return; | |
| } | |
| if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "trying to create a temporary region for {0} but one exists", | |
| GetName()); | |
| return; | |
| } | |
| if (data.GetByteSize() < | |
| llvm::expectedToOptional(GetByteSize(scope)).value_or(0)) { | |
| if (data.GetByteSize() == 0 && !LocationExpressionIsValid()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "the variable '{0}' has no location, " | |
| "it may have been optimized out", | |
| GetName()); | |
| } else { | |
| err = Status::FromErrorStringWithFormatv( | |
| "size of variable {0} ({1}) is larger than the ValueObject's " | |
| "size ({2})", | |
| GetName(), | |
| llvm::expectedToOptional(GetByteSize(scope)).value_or(0), | |
| data.GetByteSize()); | |
| } | |
| return; | |
| } | |
| std::optional<size_t> opt_bit_align = GetTypeBitAlign(scope); | |
| if (!opt_bit_align) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "can't get the type alignment for {0}", GetName()); | |
| return; | |
| } | |
| size_t byte_align = (*opt_bit_align + 7) / 8; | |
| const bool zero_memory = false; | |
| if (auto address_or_error = map.Malloc( | |
| data.GetByteSize(), byte_align, | |
| lldb::ePermissionsReadable | lldb::ePermissionsWritable, | |
| IRMemoryMap::eAllocationPolicyMirror, zero_memory)) { | |
| m_temporary_allocation = *address_or_error; | |
| } else { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't allocate a temporary region for {0}: {1}", GetName(), | |
| toString(address_or_error.takeError())); | |
| return; | |
| } | |
| m_temporary_allocation_size = data.GetByteSize(); | |
| m_original_data = std::make_shared<DataBufferHeap>(data.GetDataStart(), | |
| data.GetByteSize()); | |
| Status write_error; | |
| map.WriteMemory(m_temporary_allocation, data.GetDataStart(), | |
| data.GetByteSize(), write_error); | |
| if (!write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write to the temporary region for {0}: {1}", GetName(), | |
| write_error.AsCString()); | |
| return; | |
| } | |
| Status pointer_write_error; | |
| map.WritePointerToMemory(load_addr, m_temporary_allocation, | |
| pointer_write_error); | |
| if (!pointer_write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write the address of the temporary region for {0}: {1}", | |
| GetName(), pointer_write_error.AsCString()); | |
| } | |
| } | |
| } | |
| } | |
| void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, lldb::addr_t frame_top, | |
| lldb::addr_t frame_bottom, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOG( | |
| log, | |
| "EntityVariable::Dematerialize [address = {0:x}, m_variable_sp = {1}]", | |
| (uint64_t)load_addr, GetName()); | |
| if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { | |
| ExecutionContextScope *scope = frame_sp.get(); | |
| if (!scope) | |
| scope = map.GetBestExecutionContextScope(); | |
| lldb::ValueObjectSP valobj_sp = SetupValueObject(scope); | |
| if (!valobj_sp) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't get a value object for variable {0}", GetName()); | |
| return; | |
| } | |
| lldb_private::DataExtractor data; | |
| Status extract_error; | |
| map.GetMemoryData( | |
| data, m_temporary_allocation, | |
| llvm::expectedToOptional(valobj_sp->GetByteSize()).value_or(0), | |
| extract_error); | |
| if (!extract_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't get the data for variable {0}", GetName()); | |
| return; | |
| } | |
| bool actually_write = true; | |
| if (m_original_data) { | |
| if ((data.GetByteSize() == m_original_data->GetByteSize()) && | |
| !memcmp(m_original_data->GetBytes(), data.GetDataStart(), | |
| data.GetByteSize())) { | |
| actually_write = false; | |
| } | |
| } | |
| Status set_error; | |
| if (actually_write) { | |
| valobj_sp->SetData(data, set_error); | |
| if (!set_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write the new contents of {0} back into the variable", | |
| GetName()); | |
| return; | |
| } | |
| } | |
| Status free_error; | |
| map.Free(m_temporary_allocation, free_error); | |
| if (!free_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't free the temporary region for {0}: {1}", GetName(), | |
| free_error.AsCString()); | |
| return; | |
| } | |
| m_original_data.reset(); | |
| m_temporary_allocation = LLDB_INVALID_ADDRESS; | |
| m_temporary_allocation_size = 0; | |
| } | |
| } | |
| void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address, | |
| Log *log) override { | |
| StreamString dump_stream; | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| dump_stream.Printf("0x%" PRIx64 ": EntityVariable\n", load_addr); | |
| Status err; | |
| lldb::addr_t ptr = LLDB_INVALID_ADDRESS; | |
| { | |
| dump_stream.PutCString("Pointer:\n"); | |
| DataBufferHeap data(m_size, 0); | |
| map.ReadMemory(data.GetBytes(), load_addr, m_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DataExtractor extractor(data.GetBytes(), data.GetByteSize(), | |
| map.GetByteOrder(), map.GetAddressByteSize()); | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| lldb::offset_t offset = 0; | |
| ptr = extractor.GetAddress(&offset); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| if (m_temporary_allocation == LLDB_INVALID_ADDRESS) { | |
| dump_stream.PutCString("Points to process memory:\n"); | |
| } else { | |
| dump_stream.PutCString("Temporary allocation:\n"); | |
| } | |
| if (ptr == LLDB_INVALID_ADDRESS) { | |
| dump_stream.PutCString(" <could not be be found>\n"); | |
| } else { | |
| DataBufferHeap data(m_temporary_allocation_size, 0); | |
| map.ReadMemory(data.GetBytes(), m_temporary_allocation, | |
| m_temporary_allocation_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| log->PutString(dump_stream.GetString()); | |
| } | |
| void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override { | |
| if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { | |
| Status free_error; | |
| map.Free(m_temporary_allocation, free_error); | |
| m_temporary_allocation = LLDB_INVALID_ADDRESS; | |
| m_temporary_allocation_size = 0; | |
| } | |
| } | |
| private: | |
| virtual ConstString GetName() const = 0; | |
| /// Creates and returns ValueObject tied to this variable | |
| /// and prepares Entity for materialization. | |
| /// | |
| /// Called each time the Materializer (de)materializes a | |
| /// variable. We re-create the ValueObject based on the | |
| /// current ExecutionContextScope since clients such as | |
| /// conditional breakpoints may materialize the same | |
| /// EntityVariable multiple times with different frames. | |
| /// | |
| /// Each subsequent use of the EntityVariableBase interface | |
| /// will query the newly created ValueObject until this | |
| /// function is called again. | |
| virtual lldb::ValueObjectSP | |
| SetupValueObject(ExecutionContextScope *scope) = 0; | |
| /// Returns size in bytes of the type associated with this variable | |
| /// | |
| /// \returns On success, returns byte size of the type associated | |
| /// with this variable. Returns std::nullopt otherwise. | |
| virtual llvm::Expected<uint64_t> | |
| GetByteSize(ExecutionContextScope *scope) const = 0; | |
| /// Returns 'true' if the location expression associated with this variable | |
| /// is valid. | |
| virtual bool LocationExpressionIsValid() const = 0; | |
| /// Returns alignment of the type associated with this variable in bits. | |
| /// | |
| /// \returns On success, returns alignment in bits for the type associated | |
| /// with this variable. Returns std::nullopt otherwise. | |
| virtual std::optional<size_t> | |
| GetTypeBitAlign(ExecutionContextScope *scope) const = 0; | |
| protected: | |
| bool m_is_reference = false; | |
| lldb::addr_t m_temporary_allocation = LLDB_INVALID_ADDRESS; | |
| size_t m_temporary_allocation_size = 0; | |
| lldb::DataBufferSP m_original_data; | |
| }; | |
| /// Represents an Entity constructed from a VariableSP. | |
| /// | |
| /// This class is used for materialization of variables for which | |
| /// the user has a VariableSP on hand. The ValueObject is then | |
| /// derived from the associated DWARF location expression when needed | |
| /// by the Materializer. | |
| class EntityVariable : public EntityVariableBase { | |
| public: | |
| EntityVariable(lldb::VariableSP &variable_sp) : m_variable_sp(variable_sp) { | |
| m_is_reference = | |
| m_variable_sp->GetType()->GetForwardCompilerType().IsReferenceType(); | |
| } | |
| ConstString GetName() const override { return m_variable_sp->GetName(); } | |
| lldb::ValueObjectSP SetupValueObject(ExecutionContextScope *scope) override { | |
| assert(m_variable_sp != nullptr); | |
| return ValueObjectVariable::Create(scope, m_variable_sp); | |
| } | |
| llvm::Expected<uint64_t> | |
| GetByteSize(ExecutionContextScope *scope) const override { | |
| return m_variable_sp->GetType()->GetByteSize(scope); | |
| } | |
| bool LocationExpressionIsValid() const override { | |
| return m_variable_sp->LocationExpressionList().IsValid(); | |
| } | |
| std::optional<size_t> | |
| GetTypeBitAlign(ExecutionContextScope *scope) const override { | |
| return m_variable_sp->GetType()->GetLayoutCompilerType().GetTypeBitAlign( | |
| scope); | |
| } | |
| private: | |
| lldb::VariableSP m_variable_sp; ///< Variable that this entity is based on. | |
| }; | |
| /// Represents an Entity constructed from a VariableSP. | |
| /// | |
| /// This class is used for materialization of variables for | |
| /// which the user does not have a VariableSP available (e.g., | |
| /// when materializing ivars). | |
| class EntityValueObject : public EntityVariableBase { | |
| public: | |
| EntityValueObject(ConstString name, ValueObjectProviderTy provider) | |
| : m_name(name), m_valobj_provider(std::move(provider)) { | |
| assert(m_valobj_provider); | |
| } | |
| ConstString GetName() const override { return m_name; } | |
| lldb::ValueObjectSP SetupValueObject(ExecutionContextScope *scope) override { | |
| m_valobj_sp = | |
| m_valobj_provider(GetName(), scope->CalculateStackFrame().get()); | |
| if (m_valobj_sp) | |
| m_is_reference = m_valobj_sp->GetCompilerType().IsReferenceType(); | |
| return m_valobj_sp; | |
| } | |
| llvm::Expected<uint64_t> | |
| GetByteSize(ExecutionContextScope *scope) const override { | |
| if (m_valobj_sp) | |
| return m_valobj_sp->GetCompilerType().GetByteSize(scope); | |
| return llvm::createStringError("no value object"); | |
| } | |
| bool LocationExpressionIsValid() const override { | |
| if (m_valobj_sp) | |
| return m_valobj_sp->GetError().Success(); | |
| return false; | |
| } | |
| std::optional<size_t> | |
| GetTypeBitAlign(ExecutionContextScope *scope) const override { | |
| if (m_valobj_sp) | |
| return m_valobj_sp->GetCompilerType().GetTypeBitAlign(scope); | |
| return {}; | |
| } | |
| private: | |
| ConstString m_name; | |
| lldb::ValueObjectSP m_valobj_sp; | |
| ValueObjectProviderTy m_valobj_provider; | |
| }; | |
| uint32_t Materializer::AddVariable(lldb::VariableSP &variable_sp, Status &err) { | |
| EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); | |
| *iter = std::make_unique<EntityVariable>(variable_sp); | |
| uint32_t ret = AddStructMember(**iter); | |
| (*iter)->SetOffset(ret); | |
| return ret; | |
| } | |
| uint32_t Materializer::AddValueObject(ConstString name, | |
| ValueObjectProviderTy valobj_provider, | |
| Status &err) { | |
| assert(valobj_provider); | |
| EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); | |
| *iter = std::make_unique<EntityValueObject>(name, std::move(valobj_provider)); | |
| uint32_t ret = AddStructMember(**iter); | |
| (*iter)->SetOffset(ret); | |
| return ret; | |
| } | |
| class EntityResultVariable : public Materializer::Entity { | |
| public: | |
| EntityResultVariable(const CompilerType &type, bool is_program_reference, | |
| bool keep_in_memory, | |
| Materializer::PersistentVariableDelegate *delegate) | |
| : Entity(), m_type(type), m_is_program_reference(is_program_reference), | |
| m_keep_in_memory(keep_in_memory), m_delegate(delegate) { | |
| // Hard-coding to maximum size of a pointer since all results are | |
| // materialized by reference | |
| m_size = g_default_var_byte_size; | |
| m_alignment = g_default_var_alignment; | |
| } | |
| void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, Status &err) override { | |
| if (!m_is_program_reference) { | |
| if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { | |
| err = Status::FromErrorString( | |
| "Trying to create a temporary region for the result " | |
| "but one exists"); | |
| return; | |
| } | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| ExecutionContextScope *exe_scope = frame_sp.get(); | |
| if (!exe_scope) | |
| exe_scope = map.GetBestExecutionContextScope(); | |
| auto byte_size_or_err = m_type.GetByteSize(exe_scope); | |
| if (!byte_size_or_err) { | |
| err = Status::FromError(byte_size_or_err.takeError()); | |
| return; | |
| } | |
| auto byte_size = *byte_size_or_err; | |
| std::optional<size_t> opt_bit_align = m_type.GetTypeBitAlign(exe_scope); | |
| if (!opt_bit_align) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "can't get the alignment of type \"{0}\"", m_type.GetTypeName()); | |
| return; | |
| } | |
| size_t byte_align = (*opt_bit_align + 7) / 8; | |
| const bool zero_memory = true; | |
| if (auto address_or_error = map.Malloc( | |
| byte_size, byte_align, | |
| lldb::ePermissionsReadable | lldb::ePermissionsWritable, | |
| IRMemoryMap::eAllocationPolicyMirror, zero_memory)) { | |
| m_temporary_allocation = *address_or_error; | |
| } else { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't allocate a temporary region for the result: %s", | |
| toString(address_or_error.takeError()).c_str()); | |
| return; | |
| } | |
| m_temporary_allocation_size = byte_size; | |
| Status pointer_write_error; | |
| map.WritePointerToMemory(load_addr, m_temporary_allocation, | |
| pointer_write_error); | |
| if (!pointer_write_error.Success()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't write the address of the " | |
| "temporary region for the result: %s", | |
| pointer_write_error.AsCString()); | |
| } | |
| } | |
| } | |
| void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, lldb::addr_t frame_top, | |
| lldb::addr_t frame_bottom, Status &err) override { | |
| err.Clear(); | |
| ExecutionContextScope *exe_scope = frame_sp.get(); | |
| if (!exe_scope) | |
| exe_scope = map.GetBestExecutionContextScope(); | |
| if (!exe_scope) { | |
| err = Status::FromErrorString( | |
| "Couldn't dematerialize a result variable: invalid " | |
| "execution context scope"); | |
| return; | |
| } | |
| lldb::addr_t address; | |
| Status read_error; | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| map.ReadPointerFromMemory(&address, load_addr, read_error); | |
| if (!read_error.Success()) { | |
| err = Status::FromErrorString( | |
| "Couldn't dematerialize a result variable: couldn't " | |
| "read its address"); | |
| return; | |
| } | |
| lldb::TargetSP target_sp = exe_scope->CalculateTarget(); | |
| if (!target_sp) { | |
| err = Status::FromErrorString( | |
| "Couldn't dematerialize a result variable: no target"); | |
| return; | |
| } | |
| auto type_system_or_err = | |
| target_sp->GetScratchTypeSystemForLanguage(m_type.GetMinimumLanguage()); | |
| if (auto error = type_system_or_err.takeError()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "Couldn't dematerialize a result variable: " | |
| "couldn't get the corresponding type " | |
| "system: %s", | |
| llvm::toString(std::move(error)).c_str()); | |
| return; | |
| } | |
| auto ts = *type_system_or_err; | |
| if (!ts) { | |
| err = Status::FromErrorStringWithFormat( | |
| "Couldn't dematerialize a result variable: " | |
| "couldn't corresponding type system is " | |
| "no longer live."); | |
| return; | |
| } | |
| PersistentExpressionState *persistent_state = | |
| ts->GetPersistentExpressionState(); | |
| if (!persistent_state) { | |
| err = Status::FromErrorString( | |
| "Couldn't dematerialize a result variable: " | |
| "corresponding type system doesn't handle persistent " | |
| "variables"); | |
| return; | |
| } | |
| ConstString name = m_delegate | |
| ? m_delegate->GetName() | |
| : persistent_state->GetNextPersistentVariableName(); | |
| lldb::ExpressionVariableSP ret = persistent_state->CreatePersistentVariable( | |
| exe_scope, name, m_type, map.GetByteOrder(), map.GetAddressByteSize()); | |
| if (!ret) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't dematerialize a result variable: failed to make persistent " | |
| "variable {0}", | |
| name); | |
| return; | |
| } | |
| lldb::ProcessSP process_sp = | |
| map.GetBestExecutionContextScope()->CalculateProcess(); | |
| if (m_delegate) { | |
| m_delegate->DidDematerialize(ret); | |
| } | |
| bool can_persist = m_is_program_reference && | |
| !(address >= frame_bottom && address < frame_top); | |
| if (can_persist && m_keep_in_memory) { | |
| ret->m_live_sp = ValueObjectConstResult::Create(exe_scope, m_type, name, | |
| address, eAddressTypeLoad, | |
| map.GetAddressByteSize()); | |
| } | |
| ret->ValueUpdated(); | |
| const size_t pvar_byte_size = | |
| llvm::expectedToOptional(ret->GetByteSize()).value_or(0); | |
| uint8_t *pvar_data = ret->GetValueBytes(); | |
| map.ReadMemory(pvar_data, address, pvar_byte_size, read_error); | |
| if (!read_error.Success()) { | |
| err = Status::FromErrorString( | |
| "Couldn't dematerialize a result variable: couldn't read its memory"); | |
| return; | |
| } | |
| if (!can_persist || !m_keep_in_memory) { | |
| ret->m_flags |= ExpressionVariable::EVNeedsAllocation; | |
| if (m_temporary_allocation != LLDB_INVALID_ADDRESS) { | |
| Status free_error; | |
| map.Free(m_temporary_allocation, free_error); | |
| } | |
| } else { | |
| ret->m_flags |= m_is_program_reference | |
| ? ExpressionVariable::EVIsProgramReference | |
| : ExpressionVariable::EVIsLLDBAllocated; | |
| } | |
| m_temporary_allocation = LLDB_INVALID_ADDRESS; | |
| m_temporary_allocation_size = 0; | |
| } | |
| void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address, | |
| Log *log) override { | |
| StreamString dump_stream; | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| dump_stream.Printf("0x%" PRIx64 ": EntityResultVariable\n", load_addr); | |
| Status err; | |
| lldb::addr_t ptr = LLDB_INVALID_ADDRESS; | |
| { | |
| dump_stream.PutCString("Pointer:\n"); | |
| DataBufferHeap data(m_size, 0); | |
| map.ReadMemory(data.GetBytes(), load_addr, m_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DataExtractor extractor(data.GetBytes(), data.GetByteSize(), | |
| map.GetByteOrder(), map.GetAddressByteSize()); | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| lldb::offset_t offset = 0; | |
| ptr = extractor.GetAddress(&offset); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| if (m_temporary_allocation == LLDB_INVALID_ADDRESS) { | |
| dump_stream.PutCString("Points to process memory:\n"); | |
| } else { | |
| dump_stream.PutCString("Temporary allocation:\n"); | |
| } | |
| if (ptr == LLDB_INVALID_ADDRESS) { | |
| dump_stream.PutCString(" <could not be be found>\n"); | |
| } else { | |
| DataBufferHeap data(m_temporary_allocation_size, 0); | |
| map.ReadMemory(data.GetBytes(), m_temporary_allocation, | |
| m_temporary_allocation_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| log->PutString(dump_stream.GetString()); | |
| } | |
| void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override { | |
| if (!m_keep_in_memory && m_temporary_allocation != LLDB_INVALID_ADDRESS) { | |
| Status free_error; | |
| map.Free(m_temporary_allocation, free_error); | |
| } | |
| m_temporary_allocation = LLDB_INVALID_ADDRESS; | |
| m_temporary_allocation_size = 0; | |
| } | |
| private: | |
| CompilerType m_type; | |
| /// This is used both to control whether this result entity can (and should) | |
| /// track the value in inferior memory, as well as to control whether LLDB | |
| /// needs to allocate memory for the variable during materialization. | |
| bool m_is_program_reference; | |
| bool m_keep_in_memory; | |
| lldb::addr_t m_temporary_allocation = LLDB_INVALID_ADDRESS; | |
| size_t m_temporary_allocation_size = 0; | |
| Materializer::PersistentVariableDelegate *m_delegate; | |
| }; | |
| uint32_t Materializer::AddResultVariable(const CompilerType &type, | |
| bool is_program_reference, | |
| bool keep_in_memory, | |
| PersistentVariableDelegate *delegate, | |
| Status &err) { | |
| EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); | |
| *iter = std::make_unique<EntityResultVariable>(type, is_program_reference, | |
| keep_in_memory, delegate); | |
| uint32_t ret = AddStructMember(**iter); | |
| (*iter)->SetOffset(ret); | |
| return ret; | |
| } | |
| class EntitySymbol : public Materializer::Entity { | |
| public: | |
| EntitySymbol(const Symbol &symbol) : Entity(), m_symbol(symbol) { | |
| // Hard-coding to maximum size of a symbol | |
| m_size = g_default_var_byte_size; | |
| m_alignment = g_default_var_alignment; | |
| } | |
| void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOG(log, "EntitySymbol::Materialize [address = {0:x}, m_symbol = {1}]", | |
| (uint64_t)load_addr, m_symbol.GetName()); | |
| const Address sym_address = m_symbol.GetAddress(); | |
| ExecutionContextScope *exe_scope = frame_sp.get(); | |
| if (!exe_scope) | |
| exe_scope = map.GetBestExecutionContextScope(); | |
| lldb::TargetSP target_sp; | |
| if (exe_scope) | |
| target_sp = map.GetBestExecutionContextScope()->CalculateTarget(); | |
| if (!target_sp) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't resolve symbol {0} because there is no target", | |
| m_symbol.GetName()); | |
| return; | |
| } | |
| lldb::addr_t resolved_address = sym_address.GetLoadAddress(target_sp.get()); | |
| if (resolved_address == LLDB_INVALID_ADDRESS) | |
| resolved_address = sym_address.GetFileAddress(); | |
| Status pointer_write_error; | |
| map.WritePointerToMemory(load_addr, resolved_address, pointer_write_error); | |
| if (!pointer_write_error.Success()) { | |
| err = Status::FromErrorStringWithFormatv( | |
| "couldn't write the address of symbol {0}: {1}", m_symbol.GetName(), | |
| pointer_write_error.AsCString()); | |
| return; | |
| } | |
| } | |
| void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, lldb::addr_t frame_top, | |
| lldb::addr_t frame_bottom, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOG(log, | |
| "EntitySymbol::Dematerialize [address = {0:x}, m_symbol = {1}]", | |
| (uint64_t)load_addr, m_symbol.GetName()); | |
| // no work needs to be done | |
| } | |
| void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address, | |
| Log *log) override { | |
| StreamString dump_stream; | |
| Status err; | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| dump_stream.Format("{0:x}: EntitySymbol ({1})\n", load_addr, | |
| m_symbol.GetName()); | |
| { | |
| dump_stream.PutCString("Pointer:\n"); | |
| DataBufferHeap data(m_size, 0); | |
| map.ReadMemory(data.GetBytes(), load_addr, m_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| log->PutString(dump_stream.GetString()); | |
| } | |
| void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {} | |
| private: | |
| Symbol m_symbol; | |
| }; | |
| uint32_t Materializer::AddSymbol(const Symbol &symbol_sp, Status &err) { | |
| EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); | |
| *iter = std::make_unique<EntitySymbol>(symbol_sp); | |
| uint32_t ret = AddStructMember(**iter); | |
| (*iter)->SetOffset(ret); | |
| return ret; | |
| } | |
| class EntityRegister : public Materializer::Entity { | |
| public: | |
| EntityRegister(const RegisterInfo ®ister_info) | |
| : Entity(), m_register_info(register_info) { | |
| // Hard-coding alignment conservatively | |
| m_size = m_register_info.byte_size; | |
| m_alignment = m_register_info.byte_size; | |
| } | |
| void Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOGF(log, | |
| "EntityRegister::Materialize [address = 0x%" PRIx64 | |
| ", m_register_info = %s]", | |
| (uint64_t)load_addr, m_register_info.name); | |
| RegisterValue reg_value; | |
| if (!frame_sp.get()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't materialize register %s without a stack frame", | |
| m_register_info.name); | |
| return; | |
| } | |
| lldb::RegisterContextSP reg_context_sp = frame_sp->GetRegisterContext(); | |
| if (!reg_context_sp->ReadRegister(&m_register_info, reg_value)) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't read the value of register %s", m_register_info.name); | |
| return; | |
| } | |
| if (reg_value.GetByteSize() != m_register_info.byte_size) { | |
| err = Status::FromErrorStringWithFormat( | |
| "data for register %s had size %llu but we expected %llu", | |
| m_register_info.name, (unsigned long long)reg_value.GetByteSize(), | |
| (unsigned long long)m_register_info.byte_size); | |
| return; | |
| } | |
| lldb_private::DataBufferHeap buf(reg_value.GetByteSize(), 0); | |
| reg_value.GetAsMemoryData(m_register_info, buf.GetBytes(), | |
| buf.GetByteSize(), map.GetByteOrder(), err); | |
| if (!err.Success()) | |
| return; | |
| m_register_contents = std::make_shared<DataBufferHeap>(buf); | |
| Status write_error; | |
| map.WriteMemory(load_addr, buf.GetBytes(), reg_value.GetByteSize(), | |
| write_error); | |
| if (!write_error.Success()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't write the contents of register %s: %s", | |
| m_register_info.name, write_error.AsCString()); | |
| return; | |
| } | |
| } | |
| void Dematerialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, lldb::addr_t frame_top, | |
| lldb::addr_t frame_bottom, Status &err) override { | |
| Log *log = GetLog(LLDBLog::Expressions); | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| LLDB_LOGF(log, | |
| "EntityRegister::Dematerialize [address = 0x%" PRIx64 | |
| ", m_register_info = %s]", | |
| (uint64_t)load_addr, m_register_info.name); | |
| Status extract_error; | |
| DataExtractor register_data; | |
| if (!frame_sp.get()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't dematerialize register %s without a stack frame", | |
| m_register_info.name); | |
| return; | |
| } | |
| lldb::RegisterContextSP reg_context_sp = frame_sp->GetRegisterContext(); | |
| map.GetMemoryData(register_data, load_addr, m_register_info.byte_size, | |
| extract_error); | |
| if (!extract_error.Success()) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't get the data for register %s: %s", m_register_info.name, | |
| extract_error.AsCString()); | |
| return; | |
| } | |
| if (!memcmp(register_data.GetDataStart(), m_register_contents->GetBytes(), | |
| register_data.GetByteSize())) { | |
| // No write required, and in particular we avoid errors if the register | |
| // wasn't writable | |
| m_register_contents.reset(); | |
| return; | |
| } | |
| m_register_contents.reset(); | |
| RegisterValue register_value(register_data.GetData(), | |
| register_data.GetByteOrder()); | |
| if (!reg_context_sp->WriteRegister(&m_register_info, register_value)) { | |
| err = Status::FromErrorStringWithFormat( | |
| "couldn't write the value of register %s", m_register_info.name); | |
| return; | |
| } | |
| } | |
| void DumpToLog(IRMemoryMap &map, lldb::addr_t process_address, | |
| Log *log) override { | |
| StreamString dump_stream; | |
| Status err; | |
| const lldb::addr_t load_addr = process_address + m_offset; | |
| dump_stream.Printf("0x%" PRIx64 ": EntityRegister (%s)\n", load_addr, | |
| m_register_info.name); | |
| { | |
| dump_stream.PutCString("Value:\n"); | |
| DataBufferHeap data(m_size, 0); | |
| map.ReadMemory(data.GetBytes(), load_addr, m_size, err); | |
| if (!err.Success()) { | |
| dump_stream.PutCString(" <could not be read>\n"); | |
| } else { | |
| DumpHexBytes(&dump_stream, data.GetBytes(), data.GetByteSize(), 16, | |
| load_addr); | |
| dump_stream.PutChar('\n'); | |
| } | |
| } | |
| log->PutString(dump_stream.GetString()); | |
| } | |
| void Wipe(IRMemoryMap &map, lldb::addr_t process_address) override {} | |
| private: | |
| RegisterInfo m_register_info; | |
| lldb::DataBufferSP m_register_contents; | |
| }; | |
| uint32_t Materializer::AddRegister(const RegisterInfo ®ister_info, | |
| Status &err) { | |
| EntityVector::iterator iter = m_entities.insert(m_entities.end(), EntityUP()); | |
| *iter = std::make_unique<EntityRegister>(register_info); | |
| uint32_t ret = AddStructMember(**iter); | |
| (*iter)->SetOffset(ret); | |
| return ret; | |
| } | |
| Materializer::~Materializer() { | |
| DematerializerSP dematerializer_sp = m_dematerializer_wp.lock(); | |
| if (dematerializer_sp) | |
| dematerializer_sp->Wipe(); | |
| } | |
| Materializer::DematerializerSP | |
| Materializer::Materialize(lldb::StackFrameSP &frame_sp, IRMemoryMap &map, | |
| lldb::addr_t process_address, Status &error) { | |
| ExecutionContextScope *exe_scope = frame_sp.get(); | |
| if (!exe_scope) | |
| exe_scope = map.GetBestExecutionContextScope(); | |
| DematerializerSP dematerializer_sp = m_dematerializer_wp.lock(); | |
| if (dematerializer_sp) { | |
| error = | |
| Status::FromErrorString("Couldn't materialize: already materialized"); | |
| } | |
| DematerializerSP ret( | |
| new Dematerializer(*this, frame_sp, map, process_address)); | |
| if (!exe_scope) { | |
| error = | |
| Status::FromErrorString("Couldn't materialize: target doesn't exist"); | |
| } | |
| for (EntityUP &entity_up : m_entities) { | |
| entity_up->Materialize(frame_sp, map, process_address, error); | |
| if (!error.Success()) | |
| return DematerializerSP(); | |
| } | |
| if (Log *log = GetLog(LLDBLog::Expressions)) { | |
| LLDB_LOGF( | |
| log, | |
| "Materializer::Materialize (frame_sp = %p, process_address = 0x%" PRIx64 | |
| ") materialized:", | |
| static_cast<void *>(frame_sp.get()), process_address); | |
| for (EntityUP &entity_up : m_entities) | |
| entity_up->DumpToLog(map, process_address, log); | |
| } | |
| m_dematerializer_wp = ret; | |
| return ret; | |
| } | |
| void Materializer::Dematerializer::Dematerialize(Status &error, | |
| lldb::addr_t frame_bottom, | |
| lldb::addr_t frame_top) { | |
| lldb::StackFrameSP frame_sp; | |
| lldb::ThreadSP thread_sp = m_thread_wp.lock(); | |
| if (thread_sp) | |
| frame_sp = thread_sp->GetFrameWithStackID(m_stack_id); | |
| ExecutionContextScope *exe_scope = frame_sp.get(); | |
| if (!exe_scope) | |
| exe_scope = m_map->GetBestExecutionContextScope(); | |
| if (!IsValid()) { | |
| error = Status::FromErrorString( | |
| "Couldn't dematerialize: invalid dematerializer"); | |
| } | |
| if (!exe_scope) { | |
| error = Status::FromErrorString("Couldn't dematerialize: target is gone"); | |
| } else { | |
| if (Log *log = GetLog(LLDBLog::Expressions)) { | |
| LLDB_LOGF(log, | |
| "Materializer::Dematerialize (frame_sp = %p, process_address " | |
| "= 0x%" PRIx64 ") about to dematerialize:", | |
| static_cast<void *>(frame_sp.get()), m_process_address); | |
| for (EntityUP &entity_up : m_materializer->m_entities) | |
| entity_up->DumpToLog(*m_map, m_process_address, log); | |
| } | |
| for (EntityUP &entity_up : m_materializer->m_entities) { | |
| entity_up->Dematerialize(frame_sp, *m_map, m_process_address, frame_top, | |
| frame_bottom, error); | |
| if (!error.Success()) | |
| break; | |
| } | |
| } | |
| Wipe(); | |
| } | |
| void Materializer::Dematerializer::Wipe() { | |
| if (!IsValid()) | |
| return; | |
| for (EntityUP &entity_up : m_materializer->m_entities) { | |
| entity_up->Wipe(*m_map, m_process_address); | |
| } | |
| m_materializer = nullptr; | |
| m_map = nullptr; | |
| m_process_address = LLDB_INVALID_ADDRESS; | |
| } | |
| Materializer::PersistentVariableDelegate::PersistentVariableDelegate() = | |
| default; | |
| Materializer::PersistentVariableDelegate::~PersistentVariableDelegate() = | |
| default; |