1 //===-- ObjectContainerMachOFileset.cpp -----------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ObjectContainerMachOFileset.h" 10 #include "lldb/Core/Module.h" 11 #include "lldb/Core/ModuleSpec.h" 12 #include "lldb/Core/PluginManager.h" 13 #include "lldb/Symbol/ObjectFile.h" 14 #include "lldb/Target/Target.h" 15 #include "lldb/Utility/ArchSpec.h" 16 #include "lldb/Utility/DataBuffer.h" 17 #include "lldb/Utility/Stream.h" 18 #include <optional> 19 20 using namespace lldb; 21 using namespace lldb_private; 22 using namespace llvm::MachO; 23 24 LLDB_PLUGIN_DEFINE(ObjectContainerMachOFileset) 25 26 void ObjectContainerMachOFileset::Initialize() { 27 PluginManager::RegisterPlugin(GetPluginNameStatic(), 28 GetPluginDescriptionStatic(), CreateInstance, 29 GetModuleSpecifications, CreateMemoryInstance); 30 } 31 32 void ObjectContainerMachOFileset::Terminate() { 33 PluginManager::UnregisterPlugin(CreateInstance); 34 } 35 36 ObjectContainerMachOFileset::ObjectContainerMachOFileset( 37 const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp, 38 lldb::offset_t data_offset, const lldb_private::FileSpec *file, 39 lldb::offset_t offset, lldb::offset_t length) 40 : ObjectContainer(module_sp, file, offset, length, data_sp, data_offset), 41 m_memory_addr(LLDB_INVALID_ADDRESS) {} 42 43 ObjectContainerMachOFileset::ObjectContainerMachOFileset( 44 const lldb::ModuleSP &module_sp, lldb::WritableDataBufferSP data_sp, 45 const lldb::ProcessSP &process_sp, lldb::addr_t header_addr) 46 : ObjectContainer(module_sp, nullptr, 0, data_sp->GetByteSize(), data_sp, 47 0), 48 m_process_wp(process_sp), m_memory_addr(header_addr) {} 49 50 ObjectContainer *ObjectContainerMachOFileset::CreateInstance( 51 const lldb::ModuleSP &module_sp, DataBufferSP &data_sp, 52 lldb::offset_t data_offset, const FileSpec *file, 53 lldb::offset_t file_offset, lldb::offset_t length) { 54 if (!data_sp) 55 return {}; 56 57 DataExtractor data; 58 data.SetData(data_sp, data_offset, length); 59 if (!MagicBytesMatch(data)) 60 return {}; 61 62 auto container_up = std::make_unique<ObjectContainerMachOFileset>( 63 module_sp, data_sp, data_offset, file, file_offset, length); 64 if (!container_up->ParseHeader()) 65 return {}; 66 67 return container_up.release(); 68 } 69 70 ObjectContainer *ObjectContainerMachOFileset::CreateMemoryInstance( 71 const lldb::ModuleSP &module_sp, lldb::WritableDataBufferSP data_sp, 72 const lldb::ProcessSP &process_sp, lldb::addr_t header_addr) { 73 if (!MagicBytesMatch(data_sp, 0, data_sp->GetByteSize())) 74 return {}; 75 76 auto container_up = std::make_unique<ObjectContainerMachOFileset>( 77 module_sp, data_sp, process_sp, header_addr); 78 if (!container_up->ParseHeader()) 79 return {}; 80 81 return container_up.release(); 82 } 83 84 ObjectContainerMachOFileset::~ObjectContainerMachOFileset() = default; 85 86 static uint32_t MachHeaderSizeFromMagic(uint32_t magic) { 87 switch (magic) { 88 case MH_MAGIC: 89 case MH_CIGAM: 90 return sizeof(struct mach_header); 91 case MH_MAGIC_64: 92 case MH_CIGAM_64: 93 return sizeof(struct mach_header_64); 94 default: 95 return 0; 96 } 97 } 98 99 static std::optional<mach_header> ParseMachOHeader(DataExtractor &data) { 100 lldb::offset_t offset = 0; 101 mach_header header; 102 header.magic = data.GetU32(&offset); 103 switch (header.magic) { 104 case MH_MAGIC: 105 data.SetByteOrder(endian::InlHostByteOrder()); 106 data.SetAddressByteSize(4); 107 break; 108 case MH_MAGIC_64: 109 data.SetByteOrder(endian::InlHostByteOrder()); 110 data.SetAddressByteSize(8); 111 break; 112 case MH_CIGAM: 113 data.SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig 114 ? eByteOrderLittle 115 : eByteOrderBig); 116 data.SetAddressByteSize(4); 117 break; 118 case MH_CIGAM_64: 119 data.SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig 120 ? eByteOrderLittle 121 : eByteOrderBig); 122 data.SetAddressByteSize(8); 123 break; 124 default: 125 return {}; 126 } 127 128 header.cputype = data.GetU32(&offset); 129 header.cpusubtype = data.GetU32(&offset); 130 header.filetype = data.GetU32(&offset); 131 header.ncmds = data.GetU32(&offset); 132 header.sizeofcmds = data.GetU32(&offset); 133 return header; 134 } 135 136 static bool 137 ParseFileset(DataExtractor &data, mach_header header, 138 std::vector<ObjectContainerMachOFileset::Entry> &entries, 139 std::optional<lldb::addr_t> load_addr = std::nullopt) { 140 lldb::offset_t offset = MachHeaderSizeFromMagic(header.magic); 141 lldb::offset_t slide = 0; 142 for (uint32_t i = 0; i < header.ncmds; ++i) { 143 const lldb::offset_t load_cmd_offset = offset; 144 load_command lc = {}; 145 if (data.GetU32(&offset, &lc.cmd, 2) == nullptr) 146 break; 147 148 // If we know the load address we can compute the slide. 149 if (load_addr) { 150 if (lc.cmd == llvm::MachO::LC_SEGMENT_64) { 151 segment_command_64 segment; 152 data.CopyData(load_cmd_offset, sizeof(segment_command_64), &segment); 153 if (llvm::StringRef(segment.segname) == "__TEXT") 154 slide = *load_addr - segment.vmaddr; 155 } 156 } 157 158 if (lc.cmd == LC_FILESET_ENTRY) { 159 fileset_entry_command entry; 160 data.CopyData(load_cmd_offset, sizeof(fileset_entry_command), &entry); 161 lldb::offset_t entry_id_offset = load_cmd_offset + entry.entry_id; 162 const char *id = data.GetCStr(&entry_id_offset); 163 entries.emplace_back(entry.vmaddr + slide, entry.fileoff, 164 std::string(id)); 165 } 166 167 offset = load_cmd_offset + lc.cmdsize; 168 } 169 170 return true; 171 } 172 173 bool ObjectContainerMachOFileset::ParseHeader( 174 DataExtractor &data, const lldb_private::FileSpec &file, 175 lldb::offset_t file_offset, std::vector<Entry> &entries) { 176 std::optional<mach_header> header = ParseMachOHeader(data); 177 178 if (!header) 179 return false; 180 181 const size_t header_size = MachHeaderSizeFromMagic(header->magic); 182 const size_t header_and_lc_size = header_size + header->sizeofcmds; 183 184 if (data.GetByteSize() < header_and_lc_size) { 185 DataBufferSP data_sp = 186 ObjectFile::MapFileData(file, header_and_lc_size, file_offset); 187 data.SetData(data_sp); 188 } 189 190 return ParseFileset(data, *header, entries); 191 } 192 193 bool ObjectContainerMachOFileset::ParseHeader() { 194 ModuleSP module_sp(GetModule()); 195 if (!module_sp) 196 return false; 197 198 std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); 199 200 std::optional<mach_header> header = ParseMachOHeader(m_data); 201 if (!header) 202 return false; 203 204 const size_t header_size = MachHeaderSizeFromMagic(header->magic); 205 const size_t header_and_lc_size = header_size + header->sizeofcmds; 206 207 if (m_data.GetByteSize() < header_and_lc_size) { 208 ProcessSP process_sp(m_process_wp.lock()); 209 DataBufferSP data_sp = 210 process_sp 211 ? ObjectFile::ReadMemory(process_sp, m_memory_addr, 212 header_and_lc_size) 213 : ObjectFile::MapFileData(m_file, header_and_lc_size, m_offset); 214 m_data.SetData(data_sp); 215 } 216 217 return ParseFileset(m_data, *header, m_entries, m_memory_addr); 218 } 219 220 size_t ObjectContainerMachOFileset::GetModuleSpecifications( 221 const lldb_private::FileSpec &file, lldb::DataBufferSP &data_sp, 222 lldb::offset_t data_offset, lldb::offset_t file_offset, 223 lldb::offset_t file_size, lldb_private::ModuleSpecList &specs) { 224 const size_t initial_count = specs.GetSize(); 225 226 DataExtractor data; 227 data.SetData(data_sp, data_offset, data_sp->GetByteSize()); 228 229 if (MagicBytesMatch(data)) { 230 std::vector<Entry> entries; 231 if (ParseHeader(data, file, file_offset, entries)) { 232 for (const Entry &entry : entries) { 233 const lldb::offset_t entry_offset = entry.fileoff + file_offset; 234 if (ObjectFile::GetModuleSpecifications( 235 file, entry_offset, file_size - entry_offset, specs)) { 236 ModuleSpec &spec = specs.GetModuleSpecRefAtIndex(specs.GetSize() - 1); 237 spec.GetObjectName() = ConstString(entry.id); 238 } 239 } 240 } 241 } 242 return specs.GetSize() - initial_count; 243 } 244 245 bool ObjectContainerMachOFileset::MagicBytesMatch(DataBufferSP data_sp, 246 lldb::addr_t data_offset, 247 lldb::addr_t data_length) { 248 DataExtractor data; 249 data.SetData(data_sp, data_offset, data_length); 250 return MagicBytesMatch(data); 251 } 252 253 bool ObjectContainerMachOFileset::MagicBytesMatch(const DataExtractor &data) { 254 lldb::offset_t offset = 0; 255 uint32_t magic = data.GetU32(&offset); 256 switch (magic) { 257 case MH_MAGIC: 258 case MH_CIGAM: 259 case MH_MAGIC_64: 260 case MH_CIGAM_64: 261 break; 262 default: 263 return false; 264 } 265 offset += 4; // cputype 266 offset += 4; // cpusubtype 267 uint32_t filetype = data.GetU32(&offset); 268 return filetype == MH_FILESET; 269 } 270 271 ObjectFileSP 272 ObjectContainerMachOFileset::GetObjectFile(const lldb_private::FileSpec *file) { 273 ModuleSP module_sp(GetModule()); 274 if (!module_sp) 275 return {}; 276 277 ConstString object_name = module_sp->GetObjectName(); 278 if (!object_name) 279 return {}; 280 281 Entry *entry = FindEntry(object_name.GetCString()); 282 if (!entry) 283 return {}; 284 285 DataBufferSP data_sp; 286 lldb::offset_t data_offset = 0; 287 return ObjectFile::FindPlugin(module_sp, file, m_offset + entry->fileoff, 288 m_data.GetByteSize() - entry->fileoff, data_sp, 289 data_offset); 290 } 291 292 ObjectContainerMachOFileset::Entry * 293 ObjectContainerMachOFileset::FindEntry(llvm::StringRef id) { 294 for (Entry &entry : m_entries) { 295 if (entry.id == id) 296 return &entry; 297 } 298 return nullptr; 299 } 300