| //===-- ObjectContainerMachOFileset.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 "ObjectContainerMachOFileset.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/ModuleSpec.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Symbol/ObjectFile.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/ArchSpec.h" | |
| #include "lldb/Utility/DataBuffer.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace llvm::MachO; | |
| /// Read a Mach-O load-command header (cmd + cmdsize) from \p data at | |
| /// \p offset into \p cmd, advancing \p offset by 8 bytes. \p T may be | |
| /// \c llvm::MachO::load_command or any of its richer variants | |
| /// (\c thread_command, \c dylib_command, \c encryption_info_command, ...); | |
| /// only the leading cmd/cmdsize fields are touched by this read. Returns | |
| /// false on EOF or on a cmdsize smaller than sizeof(load_command), in which | |
| /// case callers should break out of their load-command loop to avoid spinning | |
| /// on malformed input. | |
| template <typename T> | |
| static bool ReadMachOCommand(DataExtractor &data, lldb::offset_t &offset, | |
| T &cmd) { | |
| static_assert(offsetof(T, cmd) == 0, "T::cmd must be the first field"); | |
| static_assert(offsetof(T, cmdsize) == sizeof(uint32_t), | |
| "T::cmdsize must immediately follow T::cmd"); | |
| static_assert(std::is_same<decltype(T::cmd), uint32_t>::value, | |
| "T::cmd must be uint32_t"); | |
| static_assert(std::is_same<decltype(T::cmdsize), uint32_t>::value, | |
| "T::cmdsize must be uint32_t"); | |
| if (data.GetU32(&offset, &cmd, 2) == nullptr) | |
| return false; | |
| if (cmd.cmdsize < sizeof(load_command)) | |
| return false; | |
| return true; | |
| } | |
| LLDB_PLUGIN_DEFINE(ObjectContainerMachOFileset) | |
| void ObjectContainerMachOFileset::Initialize() { | |
| PluginManager::RegisterPlugin(GetPluginNameStatic(), | |
| GetPluginDescriptionStatic(), CreateInstance, | |
| GetModuleSpecifications, CreateMemoryInstance); | |
| } | |
| void ObjectContainerMachOFileset::Terminate() { | |
| PluginManager::UnregisterPlugin(CreateInstance); | |
| } | |
| ObjectContainerMachOFileset::ObjectContainerMachOFileset( | |
| const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp, | |
| lldb::offset_t data_offset, const lldb_private::FileSpec *file, | |
| lldb::offset_t offset, lldb::offset_t length) | |
| : ObjectContainer(module_sp, file, offset, length, data_sp, data_offset), | |
| m_memory_addr(LLDB_INVALID_ADDRESS) {} | |
| ObjectContainerMachOFileset::ObjectContainerMachOFileset( | |
| const lldb::ModuleSP &module_sp, lldb::WritableDataBufferSP data_sp, | |
| const lldb::ProcessSP &process_sp, lldb::addr_t header_addr) | |
| : ObjectContainer(module_sp, nullptr, 0, data_sp->GetByteSize(), data_sp, | |
| 0), | |
| m_process_wp(process_sp), m_memory_addr(header_addr) {} | |
| ObjectContainer *ObjectContainerMachOFileset::CreateInstance( | |
| const lldb::ModuleSP &module_sp, DataBufferSP &data_sp, | |
| lldb::offset_t data_offset, const FileSpec *file, | |
| lldb::offset_t file_offset, lldb::offset_t length) { | |
| if (!data_sp) | |
| return {}; | |
| DataExtractor extractor; | |
| extractor.SetData(data_sp, data_offset, length); | |
| if (!MagicBytesMatch(extractor)) | |
| return {}; | |
| auto container_up = std::make_unique<ObjectContainerMachOFileset>( | |
| module_sp, data_sp, data_offset, file, file_offset, length); | |
| if (!container_up->ParseHeader()) | |
| return {}; | |
| return container_up.release(); | |
| } | |
| ObjectContainer *ObjectContainerMachOFileset::CreateMemoryInstance( | |
| const lldb::ModuleSP &module_sp, lldb::WritableDataBufferSP data_sp, | |
| const lldb::ProcessSP &process_sp, lldb::addr_t header_addr) { | |
| if (!MagicBytesMatch(data_sp, 0, data_sp->GetByteSize())) | |
| return {}; | |
| auto container_up = std::make_unique<ObjectContainerMachOFileset>( | |
| module_sp, data_sp, process_sp, header_addr); | |
| if (!container_up->ParseHeader()) | |
| return {}; | |
| return container_up.release(); | |
| } | |
| ObjectContainerMachOFileset::~ObjectContainerMachOFileset() = default; | |
| static uint32_t MachHeaderSizeFromMagic(uint32_t magic) { | |
| switch (magic) { | |
| case MH_MAGIC: | |
| case MH_CIGAM: | |
| return sizeof(struct mach_header); | |
| case MH_MAGIC_64: | |
| case MH_CIGAM_64: | |
| return sizeof(struct mach_header_64); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static std::optional<mach_header> ParseMachOHeader(DataExtractor &extractor) { | |
| lldb::offset_t offset = 0; | |
| mach_header header; | |
| header.magic = extractor.GetU32(&offset); | |
| switch (header.magic) { | |
| case MH_MAGIC: | |
| extractor.SetByteOrder(endian::InlHostByteOrder()); | |
| extractor.SetAddressByteSize(4); | |
| break; | |
| case MH_MAGIC_64: | |
| extractor.SetByteOrder(endian::InlHostByteOrder()); | |
| extractor.SetAddressByteSize(8); | |
| break; | |
| case MH_CIGAM: | |
| extractor.SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig | |
| ? eByteOrderLittle | |
| : eByteOrderBig); | |
| extractor.SetAddressByteSize(4); | |
| break; | |
| case MH_CIGAM_64: | |
| extractor.SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig | |
| ? eByteOrderLittle | |
| : eByteOrderBig); | |
| extractor.SetAddressByteSize(8); | |
| break; | |
| default: | |
| return {}; | |
| } | |
| header.cputype = extractor.GetU32(&offset); | |
| header.cpusubtype = extractor.GetU32(&offset); | |
| header.filetype = extractor.GetU32(&offset); | |
| header.ncmds = extractor.GetU32(&offset); | |
| header.sizeofcmds = extractor.GetU32(&offset); | |
| return header; | |
| } | |
| static bool | |
| ParseFileset(DataExtractor &extractor, mach_header header, | |
| std::vector<ObjectContainerMachOFileset::Entry> &entries, | |
| std::optional<lldb::addr_t> load_addr = std::nullopt) { | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(header.magic); | |
| lldb::offset_t slide = 0; | |
| for (uint32_t i = 0; i < header.ncmds; ++i) { | |
| const lldb::offset_t load_cmd_offset = offset; | |
| load_command lc = {}; | |
| if (!ReadMachOCommand(extractor, offset, lc)) | |
| break; | |
| // If we know the load address we can compute the slide. | |
| if (load_addr) { | |
| if (lc.cmd == llvm::MachO::LC_SEGMENT_64) { | |
| segment_command_64 segment; | |
| extractor.CopyData(load_cmd_offset, sizeof(segment_command_64), | |
| &segment); | |
| if (llvm::StringRef(segment.segname) == "__TEXT") | |
| slide = *load_addr - segment.vmaddr; | |
| } | |
| } | |
| if (lc.cmd == LC_FILESET_ENTRY) { | |
| fileset_entry_command entry; | |
| extractor.CopyData(load_cmd_offset, sizeof(fileset_entry_command), | |
| &entry); | |
| lldb::offset_t entry_id_offset = load_cmd_offset + entry.entry_id.offset; | |
| if (const char *id = extractor.GetCStr(&entry_id_offset)) | |
| entries.emplace_back(entry.vmaddr + slide, entry.fileoff, | |
| std::string(id)); | |
| } | |
| offset = load_cmd_offset + lc.cmdsize; | |
| } | |
| return true; | |
| } | |
| bool ObjectContainerMachOFileset::ParseHeader( | |
| DataExtractor &extractor, const lldb_private::FileSpec &file, | |
| lldb::offset_t file_offset, std::vector<Entry> &entries) { | |
| std::optional<mach_header> header = ParseMachOHeader(extractor); | |
| if (!header) | |
| return false; | |
| const size_t header_size = MachHeaderSizeFromMagic(header->magic); | |
| const size_t header_and_lc_size = header_size + header->sizeofcmds; | |
| if (extractor.GetByteSize() < header_and_lc_size) { | |
| DataBufferSP data_sp = | |
| ObjectFile::MapFileData(file, header_and_lc_size, file_offset); | |
| extractor.SetData(data_sp); | |
| } | |
| return ParseFileset(extractor, *header, entries); | |
| } | |
| bool ObjectContainerMachOFileset::ParseHeader() { | |
| ModuleSP module_sp(GetModule()); | |
| if (!module_sp) | |
| return false; | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| std::optional<mach_header> header = ParseMachOHeader(*m_extractor_sp); | |
| if (!header) | |
| return false; | |
| const size_t header_size = MachHeaderSizeFromMagic(header->magic); | |
| const size_t header_and_lc_size = header_size + header->sizeofcmds; | |
| if (m_extractor_sp->GetByteSize() < header_and_lc_size) { | |
| ProcessSP process_sp(m_process_wp.lock()); | |
| DataBufferSP data_sp = | |
| process_sp | |
| ? ObjectFile::ReadMemory(process_sp, m_memory_addr, | |
| header_and_lc_size) | |
| : ObjectFile::MapFileData(m_file, header_and_lc_size, m_offset); | |
| m_extractor_sp->SetData(data_sp); | |
| } | |
| return ParseFileset(*m_extractor_sp, *header, m_entries, m_memory_addr); | |
| } | |
| ModuleSpecList ObjectContainerMachOFileset::GetModuleSpecifications( | |
| const lldb_private::FileSpec &file, lldb::DataExtractorSP &extractor_sp, | |
| lldb::offset_t file_offset, lldb::offset_t file_size) { | |
| if (!extractor_sp) | |
| return {}; | |
| ModuleSpecList specs; | |
| if (MagicBytesMatch(*extractor_sp)) { | |
| std::vector<Entry> entries; | |
| if (ParseHeader(*extractor_sp, file, file_offset, entries)) { | |
| for (const Entry &entry : entries) { | |
| const lldb::offset_t entry_offset = entry.fileoff + file_offset; | |
| ModuleSpecList entry_specs = ObjectFile::GetModuleSpecifications( | |
| file, entry_offset, file_size - entry_offset); | |
| if (entry_specs.GetSize() > 0) { | |
| ModuleSpec &spec = | |
| entry_specs.GetModuleSpecRefAtIndex(entry_specs.GetSize() - 1); | |
| spec.SetObjectName(entry.id); | |
| specs.Append(spec); | |
| } | |
| } | |
| } | |
| } | |
| return specs; | |
| } | |
| bool ObjectContainerMachOFileset::MagicBytesMatch(DataBufferSP data_sp, | |
| lldb::addr_t data_offset, | |
| lldb::addr_t data_length) { | |
| DataExtractor extractor; | |
| extractor.SetData(data_sp, data_offset, data_length); | |
| return MagicBytesMatch(extractor); | |
| } | |
| bool ObjectContainerMachOFileset::MagicBytesMatch( | |
| const DataExtractor &extractor) { | |
| lldb::offset_t offset = 0; | |
| uint32_t magic = extractor.GetU32(&offset); | |
| switch (magic) { | |
| case MH_MAGIC: | |
| case MH_CIGAM: | |
| case MH_MAGIC_64: | |
| case MH_CIGAM_64: | |
| break; | |
| default: | |
| return false; | |
| } | |
| offset += 4; // cputype | |
| offset += 4; // cpusubtype | |
| uint32_t filetype = extractor.GetU32(&offset); | |
| return filetype == MH_FILESET; | |
| } | |
| ObjectFileSP | |
| ObjectContainerMachOFileset::GetObjectFile(const lldb_private::FileSpec *file) { | |
| ModuleSP module_sp(GetModule()); | |
| if (!module_sp) | |
| return {}; | |
| ConstString object_name = module_sp->GetObjectName(); | |
| if (!object_name) | |
| return {}; | |
| Entry *entry = FindEntry(object_name.GetCString()); | |
| if (!entry) | |
| return {}; | |
| DataExtractorSP extractor_sp; | |
| lldb::offset_t data_offset = 0; | |
| return ObjectFile::FindPlugin(module_sp, file, m_offset + entry->fileoff, | |
| m_extractor_sp->GetByteSize() - entry->fileoff, | |
| extractor_sp, data_offset); | |
| } | |
| ObjectContainerMachOFileset::Entry * | |
| ObjectContainerMachOFileset::FindEntry(llvm::StringRef id) { | |
| for (Entry &entry : m_entries) { | |
| if (entry.id == id) | |
| return &entry; | |
| } | |
| return nullptr; | |
| } |