diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 7fe9aa3f5ab1..307b4e932d39 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -1695,11 +1695,22 @@ public: size_t byte_size, uint64_t fail_value, Status &error); + /// Use Process::ReadMemoryRanges to efficiently read multiple unsigned + /// integers from memory at once. + llvm::SmallVector> + ReadUnsignedIntegersFromMemory(llvm::ArrayRef addresses, + unsigned byte_size); + int64_t ReadSignedIntegerFromMemory(lldb::addr_t load_addr, size_t byte_size, int64_t fail_value, Status &error); lldb::addr_t ReadPointerFromMemory(lldb::addr_t vm_addr, Status &error); + /// Use Process::ReadMemoryRanges to efficiently read multiple pointers from + /// memory at once. + llvm::SmallVector> + ReadPointersFromMemory(llvm::ArrayRef ptr_locs); + bool WritePointerToMemory(lldb::addr_t vm_addr, lldb::addr_t ptr_value, Status &error); diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index fdd9de1254fc..231678590f2d 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -59,6 +59,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/Basic/TargetInfo.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include @@ -69,6 +70,23 @@ using namespace lldb; using namespace lldb_private; +namespace { +struct RuntimeGlobalSymbolSpec { + ConstString name; + + /// Whether to only return the address or also the value. + bool read_value = true; + + /// A byte size of 0 means use the process pointer size. + uint8_t byte_size = 0; +}; + +struct RuntimeGlobalSymbolResult { + uint64_t value = LLDB_INVALID_ADDRESS; + bool success = false; +}; +} // namespace + char AppleObjCRuntimeV2::ID = 0; static const char *g_get_dynamic_class_info_name = @@ -759,6 +777,75 @@ ExtractRuntimeGlobalSymbol(Process *process, ConstString name, return symbol_load_addr; } +// Batched version of ExtractRuntimeGlobalSymbol. Resolves symbols and reads +// their values in a single batch using ReadUnsignedIntegersFromMemory. +static llvm::SmallVector +ExtractRuntimeGlobalSymbolsBatched( + Process *process, const ModuleSP &module_sp, + llvm::ArrayRef specs) { + + // Start out with all results in a failed state. + llvm::SmallVector results(specs.size()); + + if (!process || !module_sp) + return results; + + const uint8_t ptr_size = process->GetAddressByteSize(); + + // Phase 1: Resolve all symbols to addresses. Build a work list of entries + // that need their values read in Phase 2. + struct ReadEntry { + size_t result_idx; + lldb::addr_t addr; + uint8_t byte_size; + }; + llvm::SmallVector work_list; + for (auto [i, spec] : llvm::enumerate(specs)) { + const uint8_t size = spec.byte_size ? spec.byte_size : ptr_size; + const Symbol *symbol = module_sp->FindFirstSymbolWithNameAndType( + spec.name, lldb::eSymbolTypeData); + + if (!symbol || !symbol->ValueIsAddress()) + continue; + + lldb::addr_t symbol_load_addr = + symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); + if (symbol_load_addr == LLDB_INVALID_ADDRESS) + continue; + + if (!spec.read_value) + results[i] = {symbol_load_addr, true}; + else + work_list.push_back({i, symbol_load_addr, size}); + } + + // Phase 2: Batch read values, grouping consecutive entries with the same + // byte size into a single ReadUnsignedIntegersFromMemory call. + llvm::stable_sort(work_list, [](const ReadEntry &a, const ReadEntry &b) { + return a.byte_size < b.byte_size; + }); + + for (size_t i = 0; i < work_list.size();) { + const uint8_t byte_size = work_list[i].byte_size; + const size_t group_start = i; + + llvm::SmallVector addrs; + while (i < work_list.size() && work_list[i].byte_size == byte_size) + addrs.push_back(work_list[i++].addr); + + auto read_values = + process->ReadUnsignedIntegersFromMemory(addrs, byte_size); + + for (size_t j = 0; j < addrs.size(); ++j) { + size_t idx = work_list[group_start + j].result_idx; + if (read_values[j].has_value()) + results[idx] = {*read_values[j], true}; + } + } + + return results; +} + static void RegisterObjCExceptionRecognizer(Process *process); AppleObjCRuntimeV2::AppleObjCRuntimeV2(Process *process, @@ -2835,56 +2922,61 @@ AppleObjCRuntimeV2::NonPointerISACache::CreateInstance( AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { Process *process(runtime.GetProcess()); - Status error; - Log *log = GetLog(LLDBLog::Types); - auto objc_debug_isa_magic_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_isa_magic_mask"), objc_module_sp, error); - if (error.Fail()) + // Batch read all the ISA-related symbols in one go. + enum ISASymbol { + kMagicMask, + kMagicValue, + kClassMask, + kIndexedMagicMask, + kIndexedMagicValue, + kIndexedIndexMask, + kIndexedIndexShift, + kIndexedClasses, + kISASymbolCount, + }; + llvm::SmallVector specs = { + {ConstString("objc_debug_isa_magic_mask")}, + {ConstString("objc_debug_isa_magic_value")}, + {ConstString("objc_debug_isa_class_mask")}, + {ConstString("objc_debug_indexed_isa_magic_mask")}, + {ConstString("objc_debug_indexed_isa_magic_value")}, + {ConstString("objc_debug_indexed_isa_index_mask")}, + {ConstString("objc_debug_indexed_isa_index_shift")}, + {ConstString("objc_indexed_classes"), /*read_value=*/false}, + }; + assert(specs.size() == kISASymbolCount); + + auto results = + ExtractRuntimeGlobalSymbolsBatched(process, objc_module_sp, specs); + + // Check required symbols. + if (!results[kMagicMask].success || !results[kMagicValue].success || + !results[kClassMask].success) return nullptr; - auto objc_debug_isa_magic_value = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_isa_magic_value"), objc_module_sp, - error); - if (error.Fail()) - return nullptr; - - auto objc_debug_isa_class_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_isa_class_mask"), objc_module_sp, error); - if (error.Fail()) - return nullptr; + auto objc_debug_isa_magic_mask = results[kMagicMask].value; + auto objc_debug_isa_magic_value = results[kMagicValue].value; + auto objc_debug_isa_class_mask = results[kClassMask].value; if (log) log->PutCString("AOCRT::NPI: Found all the non-indexed ISA masks"); - bool foundError = false; - auto objc_debug_indexed_isa_magic_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_indexed_isa_magic_mask"), objc_module_sp, - error); - foundError |= error.Fail(); + // Check optional indexed ISA symbols. + bool foundError = !results[kIndexedMagicMask].success || + !results[kIndexedMagicValue].success || + !results[kIndexedIndexMask].success || + !results[kIndexedIndexShift].success || + !results[kIndexedClasses].success; - auto objc_debug_indexed_isa_magic_value = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_indexed_isa_magic_value"), - objc_module_sp, error); - foundError |= error.Fail(); + auto objc_debug_indexed_isa_magic_mask = results[kIndexedMagicMask].value; + auto objc_debug_indexed_isa_magic_value = results[kIndexedMagicValue].value; + auto objc_debug_indexed_isa_index_mask = results[kIndexedIndexMask].value; + auto objc_debug_indexed_isa_index_shift = results[kIndexedIndexShift].value; + auto objc_indexed_classes = results[kIndexedClasses].value; - auto objc_debug_indexed_isa_index_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_indexed_isa_index_mask"), objc_module_sp, - error); - foundError |= error.Fail(); - - auto objc_debug_indexed_isa_index_shift = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_indexed_isa_index_shift"), - objc_module_sp, error); - foundError |= error.Fail(); - - auto objc_indexed_classes = - ExtractRuntimeGlobalSymbol(process, ConstString("objc_indexed_classes"), - objc_module_sp, error, false); - foundError |= error.Fail(); - - if (log) + if (log && !foundError) log->PutCString("AOCRT::NPI: Found all the indexed ISA masks"); // we might want to have some rules to outlaw these other values (e.g if the @@ -2903,84 +2995,72 @@ AppleObjCRuntimeV2::TaggedPointerVendorV2::CreateInstance( AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { Process *process(runtime.GetProcess()); - Status error; + // Batch read all tagged pointer symbols in one go. + enum TaggedPtrSymbol { + kMask, + kSlotShift, + kSlotMask, + kPayloadLshift, + kPayloadRshift, + kClasses, + kExtMask, + kExtSlotShift, + kExtSlotMask, + kExtClasses, + kExtPayloadLshift, + kExtPayloadRshift, + kTaggedPtrSymbolCount, + }; + llvm::SmallVector specs = { + {ConstString("objc_debug_taggedpointer_mask")}, + {ConstString("objc_debug_taggedpointer_slot_shift"), true, 4}, + {ConstString("objc_debug_taggedpointer_slot_mask"), true, 4}, + {ConstString("objc_debug_taggedpointer_payload_lshift"), true, 4}, + {ConstString("objc_debug_taggedpointer_payload_rshift"), true, 4}, + {ConstString("objc_debug_taggedpointer_classes"), false}, + {ConstString("objc_debug_taggedpointer_ext_mask")}, + {ConstString("objc_debug_taggedpointer_ext_slot_shift"), true, 4}, + {ConstString("objc_debug_taggedpointer_ext_slot_mask"), true, 4}, + {ConstString("objc_debug_taggedpointer_ext_classes"), false}, + {ConstString("objc_debug_taggedpointer_ext_payload_lshift"), true, 4}, + {ConstString("objc_debug_taggedpointer_ext_payload_rshift"), true, 4}, + }; + assert(specs.size() == kTaggedPtrSymbolCount); - auto objc_debug_taggedpointer_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_mask"), objc_module_sp, - error); - if (error.Fail()) + auto results = + ExtractRuntimeGlobalSymbolsBatched(process, objc_module_sp, specs); + + // Check required symbols. + bool required_success = + results[kMask].success && results[kSlotShift].success && + results[kSlotMask].success && results[kPayloadLshift].success && + results[kPayloadRshift].success && results[kClasses].success; + + if (!required_success) return new TaggedPointerVendorLegacy(runtime); - auto objc_debug_taggedpointer_slot_shift = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_slot_shift"), - objc_module_sp, error, true, 4); - if (error.Fail()) - return new TaggedPointerVendorLegacy(runtime); + auto objc_debug_taggedpointer_mask = results[kMask].value; + auto objc_debug_taggedpointer_slot_shift = results[kSlotShift].value; + auto objc_debug_taggedpointer_slot_mask = results[kSlotMask].value; + auto objc_debug_taggedpointer_payload_lshift = results[kPayloadLshift].value; + auto objc_debug_taggedpointer_payload_rshift = results[kPayloadRshift].value; + auto objc_debug_taggedpointer_classes = results[kClasses].value; - auto objc_debug_taggedpointer_slot_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_slot_mask"), - objc_module_sp, error, true, 4); - if (error.Fail()) - return new TaggedPointerVendorLegacy(runtime); - - auto objc_debug_taggedpointer_payload_lshift = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_payload_lshift"), - objc_module_sp, error, true, 4); - if (error.Fail()) - return new TaggedPointerVendorLegacy(runtime); - - auto objc_debug_taggedpointer_payload_rshift = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_payload_rshift"), - objc_module_sp, error, true, 4); - if (error.Fail()) - return new TaggedPointerVendorLegacy(runtime); - - auto objc_debug_taggedpointer_classes = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_classes"), objc_module_sp, - error, false); - if (error.Fail()) - return new TaggedPointerVendorLegacy(runtime); - - // try to detect the "extended tagged pointer" variables - if any are - // missing, use the non-extended vendor - do { - auto objc_debug_taggedpointer_ext_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_ext_mask"), - objc_module_sp, error); - if (error.Fail()) - break; - - auto objc_debug_taggedpointer_ext_slot_shift = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_ext_slot_shift"), - objc_module_sp, error, true, 4); - if (error.Fail()) - break; - - auto objc_debug_taggedpointer_ext_slot_mask = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_ext_slot_mask"), - objc_module_sp, error, true, 4); - if (error.Fail()) - break; - - auto objc_debug_taggedpointer_ext_classes = ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_ext_classes"), - objc_module_sp, error, false); - if (error.Fail()) - break; + // Check if extended symbols are all present. + bool extended_success = + results[kExtMask].success && results[kExtSlotShift].success && + results[kExtSlotMask].success && results[kExtClasses].success && + results[kExtPayloadLshift].success && results[kExtPayloadRshift].success; + if (extended_success) { + auto objc_debug_taggedpointer_ext_mask = results[kExtMask].value; + auto objc_debug_taggedpointer_ext_slot_shift = results[kExtSlotShift].value; + auto objc_debug_taggedpointer_ext_slot_mask = results[kExtSlotMask].value; + auto objc_debug_taggedpointer_ext_classes = results[kExtClasses].value; auto objc_debug_taggedpointer_ext_payload_lshift = - ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_ext_payload_lshift"), - objc_module_sp, error, true, 4); - if (error.Fail()) - break; - + results[kExtPayloadLshift].value; auto objc_debug_taggedpointer_ext_payload_rshift = - ExtractRuntimeGlobalSymbol( - process, ConstString("objc_debug_taggedpointer_ext_payload_rshift"), - objc_module_sp, error, true, 4); - if (error.Fail()) - break; + results[kExtPayloadRshift].value; return new TaggedPointerVendorExtended( runtime, objc_debug_taggedpointer_mask, @@ -2993,7 +3073,7 @@ AppleObjCRuntimeV2::TaggedPointerVendorV2::CreateInstance( objc_debug_taggedpointer_ext_payload_lshift, objc_debug_taggedpointer_ext_payload_rshift, objc_debug_taggedpointer_classes, objc_debug_taggedpointer_ext_classes); - } while (false); + } // we might want to have some rules to outlaw these values (e.g if the // table's address is zero) diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 7e69b5bedc48..e725f9eac7d5 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -2317,6 +2317,45 @@ uint64_t Process::ReadUnsignedIntegerFromMemory(lldb::addr_t vm_addr, return fail_value; } +llvm::SmallVector> +Process::ReadUnsignedIntegersFromMemory(llvm::ArrayRef addresses, + unsigned integer_byte_size) { + if (addresses.empty()) + return {}; + // Like ReadUnsignedIntegerFromMemory, this only supports a handful + // of widths. + if (!llvm::is_contained({1u, 2u, 4u, 8u}, integer_byte_size)) + return llvm::SmallVector>(addresses.size(), + std::nullopt); + + llvm::SmallVector> ranges{ + llvm::map_range(addresses, [=](addr_t ptr) { + return Range(ptr, integer_byte_size); + })}; + + std::vector buffer(integer_byte_size * addresses.size(), 0); + llvm::SmallVector> memory = + ReadMemoryRanges(ranges, buffer); + + llvm::SmallVector> result; + result.reserve(addresses.size()); + const uint32_t addr_size = GetAddressByteSize(); + const ByteOrder byte_order = GetByteOrder(); + + for (llvm::MutableArrayRef range : memory) { + if (range.size() != integer_byte_size) { + result.push_back(std::nullopt); + continue; + } + + DataExtractor data(range.data(), integer_byte_size, byte_order, addr_size); + offset_t offset = 0; + result.push_back(data.GetMaxU64(&offset, integer_byte_size)); + assert(offset == integer_byte_size); + } + return result; +} + int64_t Process::ReadSignedIntegerFromMemory(lldb::addr_t vm_addr, size_t integer_byte_size, int64_t fail_value, @@ -2336,6 +2375,12 @@ addr_t Process::ReadPointerFromMemory(lldb::addr_t vm_addr, Status &error) { return LLDB_INVALID_ADDRESS; } +llvm::SmallVector> +Process::ReadPointersFromMemory(llvm::ArrayRef ptr_locs) { + const size_t ptr_size = GetAddressByteSize(); + return ReadUnsignedIntegersFromMemory(ptr_locs, ptr_size); +} + bool Process::WritePointerToMemory(lldb::addr_t vm_addr, lldb::addr_t ptr_value, Status &error) { Scalar scalar; diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp index 47aa105cdfec..21045725be84 100644 --- a/lldb/unittests/Target/MemoryTest.cpp +++ b/lldb/unittests/Target/MemoryTest.cpp @@ -521,3 +521,98 @@ TEST_F(MemoryTest, TestReadCStringsFromMemory) { llvm::zip(expected_valid_strings, expected_answers)) EXPECT_EQ(maybe_str, expected_answer); } + +TEST_F(MemoryTest, TestReadPointersFromMemory) { + ArchSpec arch("x86_64-apple-macosx-"); + Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch)); + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + TargetSP target_sp = CreateTarget(debugger_sp, arch); + ASSERT_TRUE(target_sp); + ListenerSP listener_sp(Listener::MakeListener("dummy")); + ProcessSP process = + std::make_shared(target_sp, listener_sp); + ASSERT_TRUE(process); + + // Read pointers at arbitrary addresses. + llvm::SmallVector ptr_locs = {0x0, 0x100, 0x2000, 0x123400}; + // Because of how DummyReaderProcess works, each byte of a memory read result + // is its address modulo 256: + constexpr addr_t expected_result = 0x0706050403020100; + + llvm::SmallVector> read_results = + process->ReadPointersFromMemory(ptr_locs); + + for (std::optional maybe_ptr : read_results) { + ASSERT_TRUE(maybe_ptr.has_value()); + EXPECT_EQ(*maybe_ptr, expected_result); + } +} + +TEST_F(MemoryTest, TestReadUnsignedIntegersFromMemory) { + ArchSpec arch("x86_64-apple-macosx-"); + + Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch)); + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + TargetSP target_sp = CreateTarget(debugger_sp, arch); + ASSERT_TRUE(target_sp); + ListenerSP listener_sp(Listener::MakeListener("dummy")); + ProcessSP process = + std::make_shared(target_sp, listener_sp); + ASSERT_TRUE(process); + + { // Test reads of size 1 + llvm::SmallVector locs = {0x0, 0x101, 0x2002, 0x123403}; + llvm::SmallVector> read_results = + process->ReadUnsignedIntegersFromMemory(locs, /*byte_size=*/1); + + for (auto [maybe_int, loc] : llvm::zip(read_results, locs)) { + ASSERT_TRUE(maybe_int.has_value()); + EXPECT_EQ(*maybe_int, static_cast(loc)); + } + } + + { // Test reads of size 2 + llvm::SmallVector locs = {0x0, 0x101, 0x2002, 0x123403}; + llvm::SmallVector> read_results = + process->ReadUnsignedIntegersFromMemory(locs, /*byte_size=*/2); + + for (auto [maybe_int, loc] : llvm::zip(read_results, locs)) { + ASSERT_TRUE(maybe_int.has_value()); + uint64_t lsb = static_cast(loc); + uint64_t expected_result = ((lsb + 1) << 8) | lsb; + EXPECT_EQ(*maybe_int, expected_result); + } + } + + { // Test reads of size 4 + llvm::SmallVector locs = {0x0, 0x101, 0x2002, 0x123403}; + llvm::SmallVector> read_results = + process->ReadUnsignedIntegersFromMemory(locs, /*byte_size=*/4); + + for (auto [maybe_int, loc] : llvm::zip(read_results, locs)) { + ASSERT_TRUE(maybe_int.has_value()); + uint64_t lsb = static_cast(loc); + uint64_t expected_result = + ((lsb + 3) << 24) | ((lsb + 2) << 16) | ((lsb + 1) << 8) | lsb; + EXPECT_EQ(*maybe_int, expected_result); + } + } + + { // Test reads of size 8 + llvm::SmallVector locs = {0x0, 0x101, 0x2002, 0x123403}; + llvm::SmallVector> read_results = + process->ReadUnsignedIntegersFromMemory(locs, /*byte_size=*/8); + + for (auto [maybe_int, loc] : llvm::zip(read_results, locs)) { + ASSERT_TRUE(maybe_int.has_value()); + uint64_t lsb = static_cast(loc); + uint64_t expected_result = ((lsb + 7) << 56) | ((lsb + 6) << 48) | + ((lsb + 5) << 40) | ((lsb + 4) << 32) | + ((lsb + 3) << 24) | ((lsb + 2) << 16) | + ((lsb + 1) << 8) | lsb; + EXPECT_EQ(*maybe_int, expected_result); + } + } +}