| //===-- ObjectFileMachO.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 "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include <algorithm> | |
| #include "Plugins/Process/Utility/RegisterContextDarwin_arm.h" | |
| #include "Plugins/Process/Utility/RegisterContextDarwin_arm64.h" | |
| #include "Plugins/Process/Utility/RegisterContextDarwin_riscv32.h" | |
| #include "Plugins/Process/Utility/RegisterContextDarwin_x86_64.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/ModuleSpec.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Core/Progress.h" | |
| #include "lldb/Core/Section.h" | |
| #include "lldb/Host/Host.h" | |
| #include "lldb/Symbol/DWARFCallFrameInfo.h" | |
| #include "lldb/Symbol/ObjectFile.h" | |
| #include "lldb/Target/DynamicLoader.h" | |
| #include "lldb/Target/MemoryRegionInfo.h" | |
| #include "lldb/Target/Platform.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/SectionLoadList.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Target/ThreadList.h" | |
| #include "lldb/Utility/ArchSpec.h" | |
| #include "lldb/Utility/DataBuffer.h" | |
| #include "lldb/Utility/FileSpec.h" | |
| #include "lldb/Utility/FileSpecList.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/RangeMap.h" | |
| #include "lldb/Utility/RegisterValue.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include "lldb/Utility/Timer.h" | |
| #include "lldb/Utility/UUID.h" | |
| #include "lldb/Host/SafeMachO.h" | |
| #include "llvm/ADT/DenseSet.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/Support/MemoryBuffer.h" | |
| #include "MachOTrie.h" | |
| #include "ObjectFileMachO.h" | |
| #if defined(__APPLE__) | |
| #include <TargetConditionals.h> | |
| // GetLLDBSharedCacheUUID() needs to call dlsym() | |
| #include <dlfcn.h> | |
| #include <mach/mach_init.h> | |
| #include <mach/vm_map.h> | |
| #include <lldb/Host/SafeMachO.h> | |
| #endif | |
| #ifndef __APPLE__ | |
| #include "lldb/Utility/AppleUuidCompatibility.h" | |
| #else | |
| #include <uuid/uuid.h> | |
| #endif | |
| #include <bitset> | |
| #include <memory> | |
| #include <optional> | |
| // Unfortunately the signpost header pulls in the system MachO header, too. | |
| #ifdef CPU_TYPE_ARM | |
| #undef CPU_TYPE_ARM | |
| #endif | |
| #ifdef CPU_TYPE_ARM64 | |
| #undef CPU_TYPE_ARM64 | |
| #endif | |
| #ifdef CPU_TYPE_ARM64_32 | |
| #undef CPU_TYPE_ARM64_32 | |
| #endif | |
| #ifdef CPU_TYPE_X86_64 | |
| #undef CPU_TYPE_X86_64 | |
| #endif | |
| #ifdef MH_DYLINKER | |
| #undef MH_DYLINKER | |
| #endif | |
| #ifdef MH_OBJECT | |
| #undef MH_OBJECT | |
| #endif | |
| #ifdef LC_VERSION_MIN_MACOSX | |
| #undef LC_VERSION_MIN_MACOSX | |
| #endif | |
| #ifdef LC_VERSION_MIN_IPHONEOS | |
| #undef LC_VERSION_MIN_IPHONEOS | |
| #endif | |
| #ifdef LC_VERSION_MIN_TVOS | |
| #undef LC_VERSION_MIN_TVOS | |
| #endif | |
| #ifdef LC_VERSION_MIN_WATCHOS | |
| #undef LC_VERSION_MIN_WATCHOS | |
| #endif | |
| #ifdef LC_BUILD_VERSION | |
| #undef LC_BUILD_VERSION | |
| #endif | |
| #ifdef PLATFORM_MACOS | |
| #undef PLATFORM_MACOS | |
| #endif | |
| #ifdef PLATFORM_MACCATALYST | |
| #undef PLATFORM_MACCATALYST | |
| #endif | |
| #ifdef PLATFORM_IOS | |
| #undef PLATFORM_IOS | |
| #endif | |
| #ifdef PLATFORM_IOSSIMULATOR | |
| #undef PLATFORM_IOSSIMULATOR | |
| #endif | |
| #ifdef PLATFORM_TVOS | |
| #undef PLATFORM_TVOS | |
| #endif | |
| #ifdef PLATFORM_TVOSSIMULATOR | |
| #undef PLATFORM_TVOSSIMULATOR | |
| #endif | |
| #ifdef PLATFORM_WATCHOS | |
| #undef PLATFORM_WATCHOS | |
| #endif | |
| #ifdef PLATFORM_WATCHOSSIMULATOR | |
| #undef PLATFORM_WATCHOSSIMULATOR | |
| #endif | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace llvm::MachO; | |
| static constexpr llvm::StringLiteral g_loader_path = "@loader_path"; | |
| static constexpr llvm::StringLiteral g_executable_path = "@executable_path"; | |
| LLDB_PLUGIN_DEFINE(ObjectFileMachO) | |
| /// 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(const 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; | |
| } | |
| static void PrintRegisterValue(RegisterContext *reg_ctx, const char *name, | |
| const char *alt_name, size_t reg_byte_size, | |
| Stream &data) { | |
| const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(name); | |
| if (reg_info == nullptr) | |
| reg_info = reg_ctx->GetRegisterInfoByName(alt_name); | |
| if (reg_info) { | |
| lldb_private::RegisterValue reg_value; | |
| if (reg_ctx->ReadRegister(reg_info, reg_value)) { | |
| if (reg_info->byte_size >= reg_byte_size) | |
| data.Write(reg_value.GetBytes(), reg_byte_size); | |
| else { | |
| data.Write(reg_value.GetBytes(), reg_info->byte_size); | |
| for (size_t i = 0, n = reg_byte_size - reg_info->byte_size; i < n; ++i) | |
| data.PutChar(0); | |
| } | |
| return; | |
| } | |
| } | |
| // Just write zeros if all else fails | |
| for (size_t i = 0; i < reg_byte_size; ++i) | |
| data.PutChar(0); | |
| } | |
| class RegisterContextDarwin_x86_64_Mach : public RegisterContextDarwin_x86_64 { | |
| public: | |
| RegisterContextDarwin_x86_64_Mach(lldb_private::Thread &thread, | |
| const DataExtractor &data) | |
| : RegisterContextDarwin_x86_64(thread, 0) { | |
| SetRegisterDataFrom_LC_THREAD(data); | |
| } | |
| void InvalidateAllRegisters() override { | |
| // Do nothing... registers are always valid... | |
| } | |
| void SetRegisterDataFrom_LC_THREAD(const DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| SetError(GPRRegSet, Read, -1); | |
| SetError(FPURegSet, Read, -1); | |
| SetError(EXCRegSet, Read, -1); | |
| while (offset < data.GetByteSize()) { | |
| int flavor = data.GetU32(&offset); | |
| if (flavor == 0) | |
| break; | |
| uint32_t count = data.GetU32(&offset); | |
| switch (flavor) { | |
| case GPRRegSet: { | |
| uint32_t *gpr_data = reinterpret_cast<uint32_t *>(&gpr.rax); | |
| for (uint32_t i = 0; i < count && offset < data.GetByteSize(); ++i) | |
| gpr_data[i] = data.GetU32(&offset); | |
| SetError(GPRRegSet, Read, 0); | |
| } break; | |
| case FPURegSet: | |
| // TODO: fill in FPU regs.... | |
| SetError(FPURegSet, Read, -1); | |
| break; | |
| case EXCRegSet: | |
| exc.trapno = data.GetU32(&offset); | |
| exc.err = data.GetU32(&offset); | |
| exc.faultvaddr = data.GetU64(&offset); | |
| SetError(EXCRegSet, Read, 0); | |
| break; | |
| default: | |
| offset += count * 4; | |
| break; | |
| } | |
| } | |
| } | |
| static bool Create_LC_THREAD(Thread *thread, Stream &data) { | |
| RegisterContextSP reg_ctx_sp(thread->GetRegisterContext()); | |
| if (reg_ctx_sp) { | |
| RegisterContext *reg_ctx = reg_ctx_sp.get(); | |
| data.PutHex32(GPRRegSet); // Flavor | |
| data.PutHex32(GPRWordCount); | |
| PrintRegisterValue(reg_ctx, "rax", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rbx", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rcx", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rdx", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rdi", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rsi", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rbp", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rsp", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r8", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r9", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r10", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r11", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r12", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r13", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r14", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "r15", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rip", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "rflags", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "cs", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "fs", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "gs", nullptr, 8, data); | |
| // // Write out the FPU registers | |
| // const size_t fpu_byte_size = sizeof(FPU); | |
| // size_t bytes_written = 0; | |
| // data.PutHex32 (FPURegSet); | |
| // data.PutHex32 (fpu_byte_size/sizeof(uint64_t)); | |
| // bytes_written += data.PutHex32(0); // uint32_t pad[0] | |
| // bytes_written += data.PutHex32(0); // uint32_t pad[1] | |
| // bytes_written += WriteRegister (reg_ctx, "fcw", "fctrl", 2, | |
| // data); // uint16_t fcw; // "fctrl" | |
| // bytes_written += WriteRegister (reg_ctx, "fsw" , "fstat", 2, | |
| // data); // uint16_t fsw; // "fstat" | |
| // bytes_written += WriteRegister (reg_ctx, "ftw" , "ftag", 1, | |
| // data); // uint8_t ftw; // "ftag" | |
| // bytes_written += data.PutHex8 (0); // uint8_t pad1; | |
| // bytes_written += WriteRegister (reg_ctx, "fop" , NULL, 2, | |
| // data); // uint16_t fop; // "fop" | |
| // bytes_written += WriteRegister (reg_ctx, "fioff", "ip", 4, | |
| // data); // uint32_t ip; // "fioff" | |
| // bytes_written += WriteRegister (reg_ctx, "fiseg", NULL, 2, | |
| // data); // uint16_t cs; // "fiseg" | |
| // bytes_written += data.PutHex16 (0); // uint16_t pad2; | |
| // bytes_written += WriteRegister (reg_ctx, "dp", "fooff" , 4, | |
| // data); // uint32_t dp; // "fooff" | |
| // bytes_written += WriteRegister (reg_ctx, "foseg", NULL, 2, | |
| // data); // uint16_t ds; // "foseg" | |
| // bytes_written += data.PutHex16 (0); // uint16_t pad3; | |
| // bytes_written += WriteRegister (reg_ctx, "mxcsr", NULL, 4, | |
| // data); // uint32_t mxcsr; | |
| // bytes_written += WriteRegister (reg_ctx, "mxcsrmask", NULL, | |
| // 4, data);// uint32_t mxcsrmask; | |
| // bytes_written += WriteRegister (reg_ctx, "stmm0", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm1", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm2", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm3", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm4", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm5", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm6", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "stmm7", NULL, | |
| // sizeof(MMSReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm0" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm1" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm2" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm3" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm4" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm5" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm6" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm7" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm8" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm9" , NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm10", NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm11", NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm12", NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm13", NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm14", NULL, | |
| // sizeof(XMMReg), data); | |
| // bytes_written += WriteRegister (reg_ctx, "xmm15", NULL, | |
| // sizeof(XMMReg), data); | |
| // | |
| // // Fill rest with zeros | |
| // for (size_t i=0, n = fpu_byte_size - bytes_written; i<n; ++ | |
| // i) | |
| // data.PutChar(0); | |
| // Write out the EXC registers | |
| data.PutHex32(EXCRegSet); | |
| data.PutHex32(EXCWordCount); | |
| PrintRegisterValue(reg_ctx, "trapno", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "err", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "faultvaddr", nullptr, 8, data); | |
| return true; | |
| } | |
| return false; | |
| } | |
| protected: | |
| int DoReadGPR(lldb::tid_t tid, int flavor, GPR &gpr) override { return -1; } | |
| int DoReadFPU(lldb::tid_t tid, int flavor, FPU &fpu) override { return -1; } | |
| int DoReadEXC(lldb::tid_t tid, int flavor, EXC &exc) override { return -1; } | |
| int DoWriteGPR(lldb::tid_t tid, int flavor, const GPR &gpr) override { | |
| return 0; | |
| } | |
| int DoWriteFPU(lldb::tid_t tid, int flavor, const FPU &fpu) override { | |
| return 0; | |
| } | |
| int DoWriteEXC(lldb::tid_t tid, int flavor, const EXC &exc) override { | |
| return 0; | |
| } | |
| }; | |
| class RegisterContextDarwin_arm_Mach : public RegisterContextDarwin_arm { | |
| public: | |
| RegisterContextDarwin_arm_Mach(lldb_private::Thread &thread, | |
| const DataExtractor &data) | |
| : RegisterContextDarwin_arm(thread, 0) { | |
| SetRegisterDataFrom_LC_THREAD(data); | |
| } | |
| void InvalidateAllRegisters() override { | |
| // Do nothing... registers are always valid... | |
| } | |
| void SetRegisterDataFrom_LC_THREAD(const DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| SetError(GPRRegSet, Read, -1); | |
| SetError(FPURegSet, Read, -1); | |
| SetError(EXCRegSet, Read, -1); | |
| while (offset < data.GetByteSize()) { | |
| int flavor = data.GetU32(&offset); | |
| uint32_t count = data.GetU32(&offset); | |
| offset_t next_thread_state = offset + (count * 4); | |
| switch (flavor) { | |
| case GPRAltRegSet: | |
| case GPRRegSet: { | |
| // r0-r15, plus CPSR | |
| uint32_t gpr_buf_count = (sizeof(gpr.r) / sizeof(gpr.r[0])) + 1; | |
| if (count == gpr_buf_count) { | |
| for (uint32_t i = 0; i < (count - 1); ++i) { | |
| gpr.r[i] = data.GetU32(&offset); | |
| } | |
| gpr.cpsr = data.GetU32(&offset); | |
| SetError(GPRRegSet, Read, 0); | |
| } | |
| } break; | |
| case FPURegSet: { | |
| uint8_t *fpu_reg_buf = (uint8_t *)&fpu.floats; | |
| const int fpu_reg_buf_size = sizeof(fpu.floats); | |
| if (data.ExtractBytes(offset, fpu_reg_buf_size, eByteOrderLittle, | |
| fpu_reg_buf) == fpu_reg_buf_size) { | |
| offset += fpu_reg_buf_size; | |
| fpu.fpscr = data.GetU32(&offset); | |
| SetError(FPURegSet, Read, 0); | |
| } | |
| } break; | |
| case EXCRegSet: | |
| if (count == 3) { | |
| exc.exception = data.GetU32(&offset); | |
| exc.fsr = data.GetU32(&offset); | |
| exc.far = data.GetU32(&offset); | |
| SetError(EXCRegSet, Read, 0); | |
| } | |
| break; | |
| } | |
| offset = next_thread_state; | |
| } | |
| } | |
| static bool Create_LC_THREAD(Thread *thread, Stream &data) { | |
| RegisterContextSP reg_ctx_sp(thread->GetRegisterContext()); | |
| if (reg_ctx_sp) { | |
| RegisterContext *reg_ctx = reg_ctx_sp.get(); | |
| data.PutHex32(GPRRegSet); // Flavor | |
| data.PutHex32(GPRWordCount); | |
| PrintRegisterValue(reg_ctx, "r0", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r1", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r2", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r3", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r4", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r5", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r6", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r7", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r8", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r9", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r10", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r11", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "r12", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "sp", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "lr", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "pc", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "cpsr", nullptr, 4, data); | |
| // Write out the EXC registers | |
| // data.PutHex32 (EXCRegSet); | |
| // data.PutHex32 (EXCWordCount); | |
| // WriteRegister (reg_ctx, "exception", NULL, 4, data); | |
| // WriteRegister (reg_ctx, "fsr", NULL, 4, data); | |
| // WriteRegister (reg_ctx, "far", NULL, 4, data); | |
| return true; | |
| } | |
| return false; | |
| } | |
| protected: | |
| int DoReadGPR(lldb::tid_t tid, int flavor, GPR &gpr) override { return -1; } | |
| int DoReadFPU(lldb::tid_t tid, int flavor, FPU &fpu) override { return -1; } | |
| int DoReadEXC(lldb::tid_t tid, int flavor, EXC &exc) override { return -1; } | |
| int DoReadDBG(lldb::tid_t tid, int flavor, DBG &dbg) override { return -1; } | |
| int DoWriteGPR(lldb::tid_t tid, int flavor, const GPR &gpr) override { | |
| return 0; | |
| } | |
| int DoWriteFPU(lldb::tid_t tid, int flavor, const FPU &fpu) override { | |
| return 0; | |
| } | |
| int DoWriteEXC(lldb::tid_t tid, int flavor, const EXC &exc) override { | |
| return 0; | |
| } | |
| int DoWriteDBG(lldb::tid_t tid, int flavor, const DBG &dbg) override { | |
| return -1; | |
| } | |
| }; | |
| class RegisterContextDarwin_arm64_Mach : public RegisterContextDarwin_arm64 { | |
| public: | |
| RegisterContextDarwin_arm64_Mach(lldb_private::Thread &thread, | |
| const DataExtractor &data) | |
| : RegisterContextDarwin_arm64(thread, 0) { | |
| SetRegisterDataFrom_LC_THREAD(data); | |
| } | |
| void InvalidateAllRegisters() override { | |
| // Do nothing... registers are always valid... | |
| } | |
| void SetRegisterDataFrom_LC_THREAD(const DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| SetError(GPRRegSet, Read, -1); | |
| SetError(FPURegSet, Read, -1); | |
| SetError(EXCRegSet, Read, -1); | |
| while (offset < data.GetByteSize()) { | |
| int flavor = data.GetU32(&offset); | |
| uint32_t count = data.GetU32(&offset); | |
| offset_t next_thread_state = offset + (count * 4); | |
| switch (flavor) { | |
| case GPRRegSet: | |
| // x0-x29 + fp + lr + sp + pc (== 33 64-bit registers) plus cpsr (1 | |
| // 32-bit register) | |
| if (count >= (33 * 2) + 1) { | |
| for (uint32_t i = 0; i < 29; ++i) | |
| gpr.x[i] = data.GetU64(&offset); | |
| gpr.fp = data.GetU64(&offset); | |
| gpr.lr = data.GetU64(&offset); | |
| gpr.sp = data.GetU64(&offset); | |
| gpr.pc = data.GetU64(&offset); | |
| gpr.cpsr = data.GetU32(&offset); | |
| SetError(GPRRegSet, Read, 0); | |
| } | |
| break; | |
| case FPURegSet: { | |
| uint8_t *fpu_reg_buf = (uint8_t *)&fpu.v[0]; | |
| const int fpu_reg_buf_size = sizeof(fpu); | |
| if (fpu_reg_buf_size == count * sizeof(uint32_t) && | |
| data.ExtractBytes(offset, fpu_reg_buf_size, eByteOrderLittle, | |
| fpu_reg_buf) == fpu_reg_buf_size) { | |
| SetError(FPURegSet, Read, 0); | |
| } | |
| } break; | |
| case EXCRegSet: | |
| if (count == 4) { | |
| exc.far = data.GetU64(&offset); | |
| exc.esr = data.GetU32(&offset); | |
| exc.exception = data.GetU32(&offset); | |
| SetError(EXCRegSet, Read, 0); | |
| } | |
| break; | |
| } | |
| offset = next_thread_state; | |
| } | |
| } | |
| static bool Create_LC_THREAD(Thread *thread, Stream &data) { | |
| RegisterContextSP reg_ctx_sp(thread->GetRegisterContext()); | |
| if (reg_ctx_sp) { | |
| RegisterContext *reg_ctx = reg_ctx_sp.get(); | |
| data.PutHex32(GPRRegSet); // Flavor | |
| data.PutHex32(GPRWordCount); | |
| PrintRegisterValue(reg_ctx, "x0", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x1", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x2", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x3", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x4", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x5", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x6", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x7", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x8", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x9", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x10", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x11", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x12", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x13", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x14", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x15", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x16", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x17", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x18", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x19", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x20", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x21", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x22", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x23", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x24", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x25", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x26", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x27", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "x28", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "fp", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "lr", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "sp", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "pc", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "cpsr", nullptr, 4, data); | |
| data.PutHex32(0); // uint32_t pad at the end | |
| // Write out the EXC registers | |
| data.PutHex32(EXCRegSet); | |
| data.PutHex32(EXCWordCount); | |
| PrintRegisterValue(reg_ctx, "far", nullptr, 8, data); | |
| PrintRegisterValue(reg_ctx, "esr", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "exception", nullptr, 4, data); | |
| return true; | |
| } | |
| return false; | |
| } | |
| protected: | |
| int DoReadGPR(lldb::tid_t tid, int flavor, GPR &gpr) override { return -1; } | |
| int DoReadFPU(lldb::tid_t tid, int flavor, FPU &fpu) override { return -1; } | |
| int DoReadEXC(lldb::tid_t tid, int flavor, EXC &exc) override { return -1; } | |
| int DoReadDBG(lldb::tid_t tid, int flavor, DBG &dbg) override { return -1; } | |
| int DoWriteGPR(lldb::tid_t tid, int flavor, const GPR &gpr) override { | |
| return 0; | |
| } | |
| int DoWriteFPU(lldb::tid_t tid, int flavor, const FPU &fpu) override { | |
| return 0; | |
| } | |
| int DoWriteEXC(lldb::tid_t tid, int flavor, const EXC &exc) override { | |
| return 0; | |
| } | |
| int DoWriteDBG(lldb::tid_t tid, int flavor, const DBG &dbg) override { | |
| return -1; | |
| } | |
| }; | |
| class RegisterContextDarwin_riscv32_Mach | |
| : public RegisterContextDarwin_riscv32 { | |
| public: | |
| RegisterContextDarwin_riscv32_Mach(lldb_private::Thread &thread, | |
| const DataExtractor &data) | |
| : RegisterContextDarwin_riscv32(thread, 0) { | |
| SetRegisterDataFrom_LC_THREAD(data); | |
| } | |
| void InvalidateAllRegisters() override { | |
| // Do nothing... registers are always valid... | |
| } | |
| void SetRegisterDataFrom_LC_THREAD(const DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| SetError(GPRRegSet, Read, -1); | |
| SetError(FPURegSet, Read, -1); | |
| SetError(EXCRegSet, Read, -1); | |
| SetError(CSRRegSet, Read, -1); | |
| while (offset < data.GetByteSize()) { | |
| int flavor = data.GetU32(&offset); | |
| uint32_t count = data.GetU32(&offset); | |
| offset_t next_thread_state = offset + (count * 4); | |
| switch (flavor) { | |
| case GPRRegSet: | |
| // x0-x31 + pc | |
| if (count >= 32) { | |
| for (uint32_t i = 0; i < 32; ++i) | |
| ((uint32_t *)&gpr.x0)[i] = data.GetU32(&offset); | |
| gpr.pc = data.GetU32(&offset); | |
| SetError(GPRRegSet, Read, 0); | |
| } | |
| break; | |
| case FPURegSet: { | |
| // f0-f31 + fcsr | |
| if (count >= 32) { | |
| for (uint32_t i = 0; i < 32; ++i) | |
| ((uint32_t *)&fpr.f0)[i] = data.GetU32(&offset); | |
| fpr.fcsr = data.GetU32(&offset); | |
| SetError(FPURegSet, Read, 0); | |
| } | |
| } break; | |
| case EXCRegSet: | |
| if (count == 3) { | |
| exc.exception = data.GetU32(&offset); | |
| exc.fsr = data.GetU32(&offset); | |
| exc.far = data.GetU32(&offset); | |
| SetError(EXCRegSet, Read, 0); | |
| } | |
| break; | |
| } | |
| offset = next_thread_state; | |
| } | |
| } | |
| static bool Create_LC_THREAD(Thread *thread, Stream &data) { | |
| RegisterContextSP reg_ctx_sp(thread->GetRegisterContext()); | |
| if (reg_ctx_sp) { | |
| RegisterContext *reg_ctx = reg_ctx_sp.get(); | |
| data.PutHex32(GPRRegSet); // Flavor | |
| data.PutHex32(GPRWordCount); | |
| PrintRegisterValue(reg_ctx, "x0", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x1", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x2", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x3", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x4", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x5", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x6", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x7", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x8", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x9", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x10", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x11", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x12", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x13", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x14", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x15", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x16", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x17", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x18", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x19", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x20", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x21", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x22", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x23", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x24", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x25", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x26", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x27", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x28", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x29", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x30", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "x31", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "pc", nullptr, 4, data); | |
| data.PutHex32(0); // uint32_t pad at the end | |
| // Write out the EXC registers | |
| data.PutHex32(EXCRegSet); | |
| data.PutHex32(EXCWordCount); | |
| PrintRegisterValue(reg_ctx, "exception", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "fsr", nullptr, 4, data); | |
| PrintRegisterValue(reg_ctx, "far", nullptr, 4, data); | |
| return true; | |
| } | |
| return false; | |
| } | |
| protected: | |
| int DoReadGPR(lldb::tid_t tid, int flavor, GPR &gpr) override { return -1; } | |
| int DoReadFPU(lldb::tid_t tid, int flavor, FPU &fpu) override { return -1; } | |
| int DoReadEXC(lldb::tid_t tid, int flavor, EXC &exc) override { return -1; } | |
| int DoReadCSR(lldb::tid_t tid, int flavor, CSR &csr) override { return -1; } | |
| int DoWriteGPR(lldb::tid_t tid, int flavor, const GPR &gpr) override { | |
| return 0; | |
| } | |
| int DoWriteFPU(lldb::tid_t tid, int flavor, const FPU &fpu) override { | |
| return 0; | |
| } | |
| int DoWriteEXC(lldb::tid_t tid, int flavor, const EXC &exc) override { | |
| return 0; | |
| } | |
| int DoWriteCSR(lldb::tid_t tid, int flavor, const CSR &csr) override { | |
| return 0; | |
| } | |
| }; | |
| static uint32_t MachHeaderSizeFromMagic(uint32_t magic) { | |
| switch (magic) { | |
| case MH_MAGIC: | |
| case MH_CIGAM: | |
| return sizeof(struct llvm::MachO::mach_header); | |
| case MH_MAGIC_64: | |
| case MH_CIGAM_64: | |
| return sizeof(struct llvm::MachO::mach_header_64); | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| #define MACHO_NLIST_ARM_SYMBOL_IS_THUMB 0x0008 | |
| char ObjectFileMachO::ID; | |
| void ObjectFileMachO::Initialize() { | |
| PluginManager::RegisterPlugin( | |
| GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, | |
| CreateMemoryInstance, GetModuleSpecifications, SaveCore); | |
| } | |
| void ObjectFileMachO::Terminate() { | |
| PluginManager::UnregisterPlugin(CreateInstance); | |
| } | |
| ObjectFile *ObjectFileMachO::CreateInstance(const lldb::ModuleSP &module_sp, | |
| DataExtractorSP extractor_sp, | |
| lldb::offset_t data_offset, | |
| const FileSpec *file, | |
| lldb::offset_t file_offset, | |
| lldb::offset_t length) { | |
| if (!extractor_sp || !extractor_sp->HasData()) { | |
| DataBufferSP data_sp = MapFileData(*file, length, file_offset); | |
| if (!data_sp) | |
| return nullptr; | |
| data_offset = 0; | |
| extractor_sp = std::make_shared<DataExtractor>(data_sp); | |
| } | |
| if (!ObjectFileMachO::MagicBytesMatch(extractor_sp, data_offset, length)) | |
| return nullptr; | |
| // Update the data to contain the entire file if it doesn't already | |
| if (extractor_sp->GetByteSize() < length) { | |
| DataBufferSP data_sp = MapFileData(*file, length, file_offset); | |
| if (!data_sp) | |
| return nullptr; | |
| data_offset = 0; | |
| extractor_sp = std::make_shared<DataExtractor>(data_sp); | |
| } | |
| auto objfile_up = std::make_unique<ObjectFileMachO>( | |
| module_sp, extractor_sp, data_offset, file, file_offset, length); | |
| if (!objfile_up || !objfile_up->ParseHeader()) | |
| return nullptr; | |
| return objfile_up.release(); | |
| } | |
| ObjectFile *ObjectFileMachO::CreateMemoryInstance( | |
| const lldb::ModuleSP &module_sp, WritableDataBufferSP data_sp, | |
| const ProcessSP &process_sp, lldb::addr_t header_addr) { | |
| DataExtractorSP extractor_sp = std::make_shared<DataExtractor>(data_sp); | |
| if (ObjectFileMachO::MagicBytesMatch(extractor_sp, 0, | |
| extractor_sp->GetByteSize())) { | |
| std::unique_ptr<ObjectFile> objfile_up( | |
| new ObjectFileMachO(module_sp, data_sp, process_sp, header_addr)); | |
| if (objfile_up.get() && objfile_up->ParseHeader()) | |
| return objfile_up.release(); | |
| } | |
| return nullptr; | |
| } | |
| ModuleSpecList ObjectFileMachO::GetModuleSpecifications( | |
| const lldb_private::FileSpec &file, lldb::DataExtractorSP &extractor_sp, | |
| lldb::offset_t file_offset, lldb::offset_t length) { | |
| if (!extractor_sp || !extractor_sp->HasData()) | |
| return {}; | |
| ModuleSpecList specs; | |
| if (ObjectFileMachO::MagicBytesMatch(extractor_sp, 0, | |
| extractor_sp->GetByteSize())) { | |
| llvm::MachO::mach_header header; | |
| offset_t data_offset = 0; | |
| if (ParseHeader(extractor_sp, &data_offset, header)) { | |
| size_t header_and_load_cmds = | |
| header.sizeofcmds + MachHeaderSizeFromMagic(header.magic); | |
| if (header_and_load_cmds >= extractor_sp->GetByteSize()) { | |
| DataBufferSP file_data_sp = | |
| MapFileData(file, header_and_load_cmds, file_offset); | |
| if (file_data_sp) | |
| extractor_sp->SetData(file_data_sp); | |
| data_offset = MachHeaderSizeFromMagic(header.magic); | |
| } | |
| if (extractor_sp && extractor_sp->HasData()) { | |
| ModuleSpec base_spec; | |
| base_spec.GetFileSpec() = file; | |
| base_spec.SetObjectOffset(file_offset); | |
| base_spec.SetObjectSize(length); | |
| GetAllArchSpecs(header, *extractor_sp, data_offset, base_spec, specs); | |
| } | |
| } | |
| } | |
| return specs; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameTEXT() { | |
| static constexpr llvm::StringLiteral g_segment_name_TEXT("__TEXT"); | |
| return g_segment_name_TEXT; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameDATA() { | |
| static constexpr llvm::StringLiteral g_segment_name_DATA("__DATA"); | |
| return g_segment_name_DATA; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameDATA_DIRTY() { | |
| static constexpr llvm::StringLiteral g_segment_name("__DATA_DIRTY"); | |
| return g_segment_name; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameDATA_CONST() { | |
| static constexpr llvm::StringLiteral g_segment_name("__DATA_CONST"); | |
| return g_segment_name; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameOBJC() { | |
| static constexpr llvm::StringLiteral g_segment_name_OBJC("__OBJC"); | |
| return g_segment_name_OBJC; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameLINKEDIT() { | |
| static constexpr llvm::StringLiteral g_section_name_LINKEDIT("__LINKEDIT"); | |
| return g_section_name_LINKEDIT; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameDWARF() { | |
| static constexpr llvm::StringLiteral g_section_name("__DWARF"); | |
| return g_section_name; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSegmentNameLLVM_COV() { | |
| static constexpr llvm::StringLiteral g_section_name("__LLVM_COV"); | |
| return g_section_name; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSectionNameEHFrame() { | |
| static constexpr llvm::StringLiteral g_section_name_eh_frame("__eh_frame"); | |
| return g_section_name_eh_frame; | |
| } | |
| llvm::StringRef ObjectFileMachO::GetSectionNameLLDBNoNlist() { | |
| static constexpr llvm::StringLiteral g_section_name_lldb_no_nlist( | |
| "__lldb_no_nlist"); | |
| return g_section_name_lldb_no_nlist; | |
| } | |
| bool ObjectFileMachO::MagicBytesMatch(DataExtractorSP extractor_sp, | |
| lldb::addr_t data_offset, | |
| lldb::addr_t data_length) { | |
| lldb::offset_t offset = data_offset; | |
| uint32_t magic = extractor_sp->GetU32(&offset); | |
| offset += 4; // cputype | |
| offset += 4; // cpusubtype | |
| uint32_t filetype = extractor_sp->GetU32(&offset); | |
| // A fileset has a Mach-O header but is not an | |
| // individual file and must be handled via an | |
| // ObjectContainer plugin. | |
| if (filetype == llvm::MachO::MH_FILESET) | |
| return false; | |
| return MachHeaderSizeFromMagic(magic) != 0; | |
| } | |
| ObjectFileMachO::ObjectFileMachO(const lldb::ModuleSP &module_sp, | |
| DataExtractorSP extractor_sp, | |
| lldb::offset_t data_offset, | |
| const FileSpec *file, | |
| lldb::offset_t file_offset, | |
| lldb::offset_t length) | |
| : ObjectFile(module_sp, file, file_offset, length, extractor_sp, | |
| data_offset), | |
| m_mach_sections(), m_entry_point_address(), m_thread_context_offsets(), | |
| m_thread_context_offsets_valid(false), m_reexported_dylibs(), | |
| m_allow_assembly_emulation_unwind_plans(true) { | |
| ::memset(&m_header, 0, sizeof(m_header)); | |
| ::memset(&m_dysymtab, 0, sizeof(m_dysymtab)); | |
| } | |
| ObjectFileMachO::ObjectFileMachO(const lldb::ModuleSP &module_sp, | |
| lldb::WritableDataBufferSP header_data_sp, | |
| const lldb::ProcessSP &process_sp, | |
| lldb::addr_t header_addr) | |
| : ObjectFile(module_sp, process_sp, header_addr, | |
| std::make_shared<DataExtractor>(header_data_sp)), | |
| m_mach_sections(), m_entry_point_address(), m_thread_context_offsets(), | |
| m_thread_context_offsets_valid(false), m_reexported_dylibs(), | |
| m_allow_assembly_emulation_unwind_plans(true) { | |
| ::memset(&m_header, 0, sizeof(m_header)); | |
| ::memset(&m_dysymtab, 0, sizeof(m_dysymtab)); | |
| } | |
| bool ObjectFileMachO::ParseHeader(DataExtractorSP &extractor_sp, | |
| lldb::offset_t *data_offset_ptr, | |
| llvm::MachO::mach_header &header) { | |
| extractor_sp->SetByteOrder(endian::InlHostByteOrder()); | |
| // Leave magic in the original byte order | |
| header.magic = extractor_sp->GetU32(data_offset_ptr); | |
| bool can_parse = false; | |
| bool is_64_bit = false; | |
| switch (header.magic) { | |
| case MH_MAGIC: | |
| extractor_sp->SetByteOrder(endian::InlHostByteOrder()); | |
| extractor_sp->SetAddressByteSize(4); | |
| can_parse = true; | |
| break; | |
| case MH_MAGIC_64: | |
| extractor_sp->SetByteOrder(endian::InlHostByteOrder()); | |
| extractor_sp->SetAddressByteSize(8); | |
| can_parse = true; | |
| is_64_bit = true; | |
| break; | |
| case MH_CIGAM: | |
| extractor_sp->SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig | |
| ? eByteOrderLittle | |
| : eByteOrderBig); | |
| extractor_sp->SetAddressByteSize(4); | |
| can_parse = true; | |
| break; | |
| case MH_CIGAM_64: | |
| extractor_sp->SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig | |
| ? eByteOrderLittle | |
| : eByteOrderBig); | |
| extractor_sp->SetAddressByteSize(8); | |
| is_64_bit = true; | |
| can_parse = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (can_parse) { | |
| extractor_sp->GetU32(data_offset_ptr, &header.cputype, 6); | |
| if (is_64_bit) | |
| *data_offset_ptr += 4; | |
| return true; | |
| } else { | |
| memset(&header, 0, sizeof(header)); | |
| } | |
| return false; | |
| } | |
| bool ObjectFileMachO::ParseHeader() { | |
| ModuleSP module_sp(GetModule()); | |
| if (!module_sp) | |
| return false; | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| bool can_parse = false; | |
| lldb::offset_t offset = 0; | |
| m_data_nsp->SetByteOrder(endian::InlHostByteOrder()); | |
| // Leave magic in the original byte order | |
| m_header.magic = m_data_nsp->GetU32(&offset); | |
| switch (m_header.magic) { | |
| case MH_MAGIC: | |
| m_data_nsp->SetByteOrder(endian::InlHostByteOrder()); | |
| m_data_nsp->SetAddressByteSize(4); | |
| can_parse = true; | |
| break; | |
| case MH_MAGIC_64: | |
| m_data_nsp->SetByteOrder(endian::InlHostByteOrder()); | |
| m_data_nsp->SetAddressByteSize(8); | |
| can_parse = true; | |
| break; | |
| case MH_CIGAM: | |
| m_data_nsp->SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig | |
| ? eByteOrderLittle | |
| : eByteOrderBig); | |
| m_data_nsp->SetAddressByteSize(4); | |
| can_parse = true; | |
| break; | |
| case MH_CIGAM_64: | |
| m_data_nsp->SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig | |
| ? eByteOrderLittle | |
| : eByteOrderBig); | |
| m_data_nsp->SetAddressByteSize(8); | |
| can_parse = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (can_parse) { | |
| m_data_nsp->GetU32(&offset, &m_header.cputype, 6); | |
| ModuleSpecList all_specs; | |
| ModuleSpec base_spec; | |
| GetAllArchSpecs(m_header, *m_data_nsp, | |
| MachHeaderSizeFromMagic(m_header.magic), base_spec, | |
| all_specs); | |
| for (unsigned i = 0, e = all_specs.GetSize(); i != e; ++i) { | |
| ArchSpec mach_arch = | |
| all_specs.GetModuleSpecRefAtIndex(i).GetArchitecture(); | |
| // Check if the module has a required architecture | |
| const ArchSpec &module_arch = module_sp->GetArchitecture(); | |
| if (module_arch.IsValid() && !module_arch.IsCompatibleMatch(mach_arch)) | |
| continue; | |
| if (SetModulesArchitecture(mach_arch)) { | |
| const size_t header_and_lc_size = | |
| m_header.sizeofcmds + MachHeaderSizeFromMagic(m_header.magic); | |
| if (m_data_nsp->GetByteSize() < header_and_lc_size) { | |
| DataBufferSP data_sp; | |
| ProcessSP process_sp(m_process_wp.lock()); | |
| if (process_sp) { | |
| data_sp = ReadMemory(process_sp, m_memory_addr, header_and_lc_size); | |
| } else { | |
| // Read in all only the load command data from the file on disk | |
| data_sp = MapFileData(m_file, header_and_lc_size, m_file_offset); | |
| if (data_sp->GetByteSize() != header_and_lc_size) | |
| continue; | |
| } | |
| if (data_sp) | |
| m_data_nsp->SetData(data_sp); | |
| } | |
| } | |
| return true; | |
| } | |
| // None found. | |
| return false; | |
| } else { | |
| memset(&m_header, 0, sizeof(struct llvm::MachO::mach_header)); | |
| } | |
| return false; | |
| } | |
| ByteOrder ObjectFileMachO::GetByteOrder() const { | |
| return m_data_nsp->GetByteOrder(); | |
| } | |
| bool ObjectFileMachO::IsExecutable() const { | |
| return m_header.filetype == MH_EXECUTE; | |
| } | |
| bool ObjectFileMachO::IsDynamicLoader() const { | |
| return m_header.filetype == MH_DYLINKER; | |
| } | |
| bool ObjectFileMachO::IsSharedCacheBinary() const { | |
| return m_header.flags & MH_DYLIB_IN_CACHE; | |
| } | |
| bool ObjectFileMachO::IsKext() const { | |
| return m_header.filetype == MH_KEXT_BUNDLE; | |
| } | |
| uint32_t ObjectFileMachO::GetAddressByteSize() const { | |
| return m_data_nsp->GetAddressByteSize(); | |
| } | |
| AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) { | |
| Symtab *symtab = GetSymtab(); | |
| if (!symtab) | |
| return AddressClass::eUnknown; | |
| const Symbol *symbol = symtab->FindSymbolContainingFileAddress(file_addr); | |
| if (symbol) { | |
| if (symbol->ValueIsAddress()) { | |
| SectionSP section_sp(symbol->GetAddressRef().GetSection()); | |
| if (section_sp) { | |
| const lldb::SectionType section_type = section_sp->GetType(); | |
| switch (section_type) { | |
| case eSectionTypeInvalid: | |
| return AddressClass::eUnknown; | |
| case eSectionTypeCode: | |
| if (m_header.cputype == llvm::MachO::CPU_TYPE_ARM) { | |
| // For ARM we have a bit in the n_desc field of the symbol that | |
| // tells us ARM/Thumb which is bit 0x0008. | |
| if (symbol->GetFlags() & MACHO_NLIST_ARM_SYMBOL_IS_THUMB) | |
| return AddressClass::eCodeAlternateISA; | |
| } | |
| return AddressClass::eCode; | |
| case eSectionTypeContainer: | |
| return AddressClass::eUnknown; | |
| case eSectionTypeData: | |
| case eSectionTypeDataCString: | |
| case eSectionTypeDataCStringPointers: | |
| case eSectionTypeDataSymbolAddress: | |
| case eSectionTypeData4: | |
| case eSectionTypeData8: | |
| case eSectionTypeData16: | |
| case eSectionTypeDataPointers: | |
| case eSectionTypeZeroFill: | |
| case eSectionTypeDataObjCMessageRefs: | |
| case eSectionTypeDataObjCCFStrings: | |
| case eSectionTypeGoSymtab: | |
| case eSectionTypeWasmName: | |
| case eSectionTypeWasmGlobal: | |
| return AddressClass::eData; | |
| case eSectionTypeDebug: | |
| case eSectionTypeDWARFDebugAbbrev: | |
| case eSectionTypeDWARFDebugAbbrevDwo: | |
| case eSectionTypeDWARFDebugAddr: | |
| case eSectionTypeDWARFDebugAranges: | |
| case eSectionTypeDWARFDebugCuIndex: | |
| case eSectionTypeDWARFDebugFrame: | |
| case eSectionTypeDWARFDebugInfo: | |
| case eSectionTypeDWARFDebugInfoDwo: | |
| case eSectionTypeDWARFDebugLine: | |
| case eSectionTypeDWARFDebugLineStr: | |
| case eSectionTypeDWARFDebugLoc: | |
| case eSectionTypeDWARFDebugLocDwo: | |
| case eSectionTypeDWARFDebugLocLists: | |
| case eSectionTypeDWARFDebugLocListsDwo: | |
| case eSectionTypeDWARFDebugMacInfo: | |
| case eSectionTypeDWARFDebugMacro: | |
| case eSectionTypeDWARFDebugNames: | |
| case eSectionTypeDWARFDebugPubNames: | |
| case eSectionTypeDWARFDebugPubTypes: | |
| case eSectionTypeDWARFDebugRanges: | |
| case eSectionTypeDWARFDebugRngLists: | |
| case eSectionTypeDWARFDebugRngListsDwo: | |
| case eSectionTypeDWARFDebugStr: | |
| case eSectionTypeDWARFDebugStrDwo: | |
| case eSectionTypeDWARFDebugStrOffsets: | |
| case eSectionTypeDWARFDebugStrOffsetsDwo: | |
| case eSectionTypeDWARFDebugTuIndex: | |
| case eSectionTypeDWARFDebugTypes: | |
| case eSectionTypeDWARFDebugTypesDwo: | |
| case eSectionTypeDWARFAppleNames: | |
| case eSectionTypeDWARFAppleTypes: | |
| case eSectionTypeDWARFAppleNamespaces: | |
| case eSectionTypeDWARFAppleObjC: | |
| case eSectionTypeDWARFGNUDebugAltLink: | |
| case eSectionTypeCTF: | |
| case eSectionTypeLLDBTypeSummaries: | |
| case eSectionTypeLLDBFormatters: | |
| case eSectionTypeSwiftModules: | |
| return AddressClass::eDebug; | |
| case eSectionTypeEHFrame: | |
| case eSectionTypeARMexidx: | |
| case eSectionTypeARMextab: | |
| case eSectionTypeCompactUnwind: | |
| return AddressClass::eRuntime; | |
| case eSectionTypeAbsoluteAddress: | |
| case eSectionTypeELFSymbolTable: | |
| case eSectionTypeELFDynamicSymbols: | |
| case eSectionTypeELFRelocationEntries: | |
| case eSectionTypeELFDynamicLinkInfo: | |
| case eSectionTypeOther: | |
| return AddressClass::eUnknown; | |
| } | |
| } | |
| } | |
| const SymbolType symbol_type = symbol->GetType(); | |
| switch (symbol_type) { | |
| case eSymbolTypeAny: | |
| return AddressClass::eUnknown; | |
| case eSymbolTypeAbsolute: | |
| return AddressClass::eUnknown; | |
| case eSymbolTypeCode: | |
| case eSymbolTypeTrampoline: | |
| case eSymbolTypeResolver: | |
| if (m_header.cputype == llvm::MachO::CPU_TYPE_ARM) { | |
| // For ARM we have a bit in the n_desc field of the symbol that tells | |
| // us ARM/Thumb which is bit 0x0008. | |
| if (symbol->GetFlags() & MACHO_NLIST_ARM_SYMBOL_IS_THUMB) | |
| return AddressClass::eCodeAlternateISA; | |
| } | |
| return AddressClass::eCode; | |
| case eSymbolTypeData: | |
| return AddressClass::eData; | |
| case eSymbolTypeRuntime: | |
| return AddressClass::eRuntime; | |
| case eSymbolTypeException: | |
| return AddressClass::eRuntime; | |
| case eSymbolTypeSourceFile: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeHeaderFile: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeObjectFile: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeCommonBlock: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeBlock: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeLocal: | |
| return AddressClass::eData; | |
| case eSymbolTypeParam: | |
| return AddressClass::eData; | |
| case eSymbolTypeVariable: | |
| return AddressClass::eData; | |
| case eSymbolTypeVariableType: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeLineEntry: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeLineHeader: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeScopeBegin: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeScopeEnd: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeAdditional: | |
| return AddressClass::eUnknown; | |
| case eSymbolTypeCompiler: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeInstrumentation: | |
| return AddressClass::eDebug; | |
| case eSymbolTypeUndefined: | |
| return AddressClass::eUnknown; | |
| case eSymbolTypeObjCClass: | |
| return AddressClass::eRuntime; | |
| case eSymbolTypeObjCMetaClass: | |
| return AddressClass::eRuntime; | |
| case eSymbolTypeObjCIVar: | |
| return AddressClass::eRuntime; | |
| case eSymbolTypeReExported: | |
| return AddressClass::eRuntime; | |
| } | |
| } | |
| return AddressClass::eUnknown; | |
| } | |
| bool ObjectFileMachO::IsStripped() { | |
| if (m_dysymtab.cmd == 0) { | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| for (uint32_t i = 0; i < m_header.ncmds; ++i) { | |
| const lldb::offset_t load_cmd_offset = offset; | |
| llvm::MachO::load_command lc = {}; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, lc)) | |
| break; | |
| if (lc.cmd == LC_DYSYMTAB) { | |
| m_dysymtab.cmd = lc.cmd; | |
| m_dysymtab.cmdsize = lc.cmdsize; | |
| if (m_data_nsp->GetU32(&offset, &m_dysymtab.ilocalsym, | |
| (sizeof(m_dysymtab) / sizeof(uint32_t)) - 2) == | |
| nullptr) { | |
| // Clear m_dysymtab if we were unable to read all items from the | |
| // load command | |
| ::memset(&m_dysymtab, 0, sizeof(m_dysymtab)); | |
| } | |
| } | |
| offset = load_cmd_offset + lc.cmdsize; | |
| } | |
| } | |
| } | |
| if (m_dysymtab.cmd) | |
| return m_dysymtab.nlocalsym <= 1; | |
| return false; | |
| } | |
| ObjectFileMachO::EncryptedFileRanges ObjectFileMachO::GetEncryptedFileRanges() { | |
| EncryptedFileRanges result; | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| llvm::MachO::encryption_info_command encryption_cmd; | |
| for (uint32_t i = 0; i < m_header.ncmds; ++i) { | |
| const lldb::offset_t load_cmd_offset = offset; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, encryption_cmd)) | |
| break; | |
| // LC_ENCRYPTION_INFO and LC_ENCRYPTION_INFO_64 have the same sizes for the | |
| // 3 fields we care about, so treat them the same. | |
| if (encryption_cmd.cmd == LC_ENCRYPTION_INFO || | |
| encryption_cmd.cmd == LC_ENCRYPTION_INFO_64) { | |
| if (m_data_nsp->GetU32(&offset, &encryption_cmd.cryptoff, 3)) { | |
| if (encryption_cmd.cryptid != 0) { | |
| EncryptedFileRanges::Entry entry; | |
| entry.SetRangeBase(encryption_cmd.cryptoff); | |
| entry.SetByteSize(encryption_cmd.cryptsize); | |
| result.Append(entry); | |
| } | |
| } | |
| } | |
| offset = load_cmd_offset + encryption_cmd.cmdsize; | |
| } | |
| return result; | |
| } | |
| void ObjectFileMachO::SanitizeSegmentCommand( | |
| llvm::MachO::segment_command_64 &seg_cmd, uint32_t cmd_idx) { | |
| if (m_length == 0 || seg_cmd.filesize == 0) | |
| return; | |
| if (IsSharedCacheBinary() && !IsInMemory()) { | |
| // In shared cache images, the load commands are relative to the | |
| // shared cache file, and not the specific image we are | |
| // examining. Let's fix this up so that it looks like a normal | |
| // image. | |
| llvm::StringRef segname(seg_cmd.segname, | |
| strnlen(seg_cmd.segname, sizeof(seg_cmd.segname))); | |
| if (segname == GetSegmentNameTEXT()) | |
| m_text_address = seg_cmd.vmaddr; | |
| if (segname == GetSegmentNameLINKEDIT()) | |
| m_linkedit_original_offset = seg_cmd.fileoff; | |
| seg_cmd.fileoff = seg_cmd.vmaddr - m_text_address; | |
| } | |
| if (seg_cmd.fileoff > m_length) { | |
| // We have a load command that says it extends past the end of the file. | |
| // This is likely a corrupt file. We don't have any way to return an error | |
| // condition here (this method was likely invoked from something like | |
| // ObjectFile::GetSectionList()), so we just null out the section contents, | |
| // and dump a message to stdout. The most common case here is core file | |
| // debugging with a truncated file. | |
| const char *lc_segment_name = | |
| seg_cmd.cmd == LC_SEGMENT_64 ? "LC_SEGMENT_64" : "LC_SEGMENT"; | |
| GetModule()->ReportWarning( | |
| "load command {0} {1} has a fileoff ({2:x16}) that extends beyond " | |
| "the end of the file ({3:x16}), ignoring this section", | |
| cmd_idx, lc_segment_name, seg_cmd.fileoff, m_length); | |
| seg_cmd.fileoff = 0; | |
| seg_cmd.filesize = 0; | |
| } | |
| if (seg_cmd.fileoff + seg_cmd.filesize > m_length) { | |
| // We have a load command that says it extends past the end of the file. | |
| // This is likely a corrupt file. We don't have any way to return an error | |
| // condition here (this method was likely invoked from something like | |
| // ObjectFile::GetSectionList()), so we just null out the section contents, | |
| // and dump a message to stdout. The most common case here is core file | |
| // debugging with a truncated file. | |
| const char *lc_segment_name = | |
| seg_cmd.cmd == LC_SEGMENT_64 ? "LC_SEGMENT_64" : "LC_SEGMENT"; | |
| GetModule()->ReportWarning( | |
| "load command {0} {1} has a fileoff + filesize ({2:x16}) that " | |
| "extends beyond the end of the file ({3:x16}), the segment will be " | |
| "truncated to match", | |
| cmd_idx, lc_segment_name, seg_cmd.fileoff + seg_cmd.filesize, m_length); | |
| // Truncate the length | |
| seg_cmd.filesize = m_length - seg_cmd.fileoff; | |
| } | |
| } | |
| static uint32_t | |
| GetSegmentPermissions(const llvm::MachO::segment_command_64 &seg_cmd) { | |
| uint32_t result = 0; | |
| if (seg_cmd.initprot & VM_PROT_READ) | |
| result |= ePermissionsReadable; | |
| if (seg_cmd.initprot & VM_PROT_WRITE) | |
| result |= ePermissionsWritable; | |
| if (seg_cmd.initprot & VM_PROT_EXECUTE) | |
| result |= ePermissionsExecutable; | |
| return result; | |
| } | |
| static lldb::SectionType GetSectionType(uint32_t flags, | |
| llvm::StringRef section_name) { | |
| if (flags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS)) | |
| return eSectionTypeCode; | |
| uint32_t mach_sect_type = flags & SECTION_TYPE; | |
| static constexpr llvm::StringLiteral g_sect_name_objc_data("__objc_data"); | |
| static constexpr llvm::StringLiteral g_sect_name_objc_msgrefs( | |
| "__objc_msgrefs"); | |
| static constexpr llvm::StringLiteral g_sect_name_objc_selrefs( | |
| "__objc_selrefs"); | |
| static constexpr llvm::StringLiteral g_sect_name_objc_classrefs( | |
| "__objc_classrefs"); | |
| static constexpr llvm::StringLiteral g_sect_name_objc_superrefs( | |
| "__objc_superrefs"); | |
| static constexpr llvm::StringLiteral g_sect_name_objc_const("__objc_const"); | |
| static constexpr llvm::StringLiteral g_sect_name_objc_classlist( | |
| "__objc_classlist"); | |
| static constexpr llvm::StringLiteral g_sect_name_cfstring("__cfstring"); | |
| static constexpr llvm::StringLiteral g_sect_name_dwarf_debug_str_offs( | |
| "__debug_str_offs"); | |
| static constexpr llvm::StringLiteral g_sect_name_dwarf_debug_str_offs_dwo( | |
| "__debug_str_offs.dwo"); | |
| static constexpr llvm::StringLiteral g_sect_name_dwarf_apple_names( | |
| "__apple_names"); | |
| static constexpr llvm::StringLiteral g_sect_name_dwarf_apple_types( | |
| "__apple_types"); | |
| static constexpr llvm::StringLiteral g_sect_name_dwarf_apple_namespaces( | |
| "__apple_namespac"); | |
| static constexpr llvm::StringLiteral g_sect_name_dwarf_apple_objc( | |
| "__apple_objc"); | |
| static constexpr llvm::StringLiteral g_sect_name_eh_frame("__eh_frame"); | |
| static constexpr llvm::StringLiteral g_sect_name_compact_unwind( | |
| "__unwind_info"); | |
| static constexpr llvm::StringLiteral g_sect_name_text("__text"); | |
| static constexpr llvm::StringLiteral g_sect_name_data("__data"); | |
| static constexpr llvm::StringLiteral g_sect_name_go_symtab("__gosymtab"); | |
| static constexpr llvm::StringLiteral g_sect_name_ctf("__ctf"); | |
| static constexpr llvm::StringLiteral g_sect_name_lldb_summaries( | |
| "__lldbsummaries"); | |
| static constexpr llvm::StringLiteral g_sect_name_lldb_formatters( | |
| "__lldbformatters"); | |
| static constexpr llvm::StringLiteral g_sect_name_swift_ast("__swift_ast"); | |
| if (section_name == g_sect_name_dwarf_debug_str_offs) | |
| return eSectionTypeDWARFDebugStrOffsets; | |
| if (section_name == g_sect_name_dwarf_debug_str_offs_dwo) | |
| return eSectionTypeDWARFDebugStrOffsetsDwo; | |
| llvm::StringRef stripped_name = section_name; | |
| if (stripped_name.consume_front("__debug_")) | |
| return ObjectFile::GetDWARFSectionTypeFromName(stripped_name); | |
| if (section_name == g_sect_name_dwarf_apple_names) | |
| return eSectionTypeDWARFAppleNames; | |
| if (section_name == g_sect_name_dwarf_apple_types) | |
| return eSectionTypeDWARFAppleTypes; | |
| if (section_name == g_sect_name_dwarf_apple_namespaces) | |
| return eSectionTypeDWARFAppleNamespaces; | |
| if (section_name == g_sect_name_dwarf_apple_objc) | |
| return eSectionTypeDWARFAppleObjC; | |
| if (section_name == g_sect_name_objc_selrefs) | |
| return eSectionTypeDataCStringPointers; | |
| if (section_name == g_sect_name_objc_msgrefs) | |
| return eSectionTypeDataObjCMessageRefs; | |
| if (section_name == g_sect_name_eh_frame) | |
| return eSectionTypeEHFrame; | |
| if (section_name == g_sect_name_compact_unwind) | |
| return eSectionTypeCompactUnwind; | |
| if (section_name == g_sect_name_cfstring) | |
| return eSectionTypeDataObjCCFStrings; | |
| if (section_name == g_sect_name_go_symtab) | |
| return eSectionTypeGoSymtab; | |
| if (section_name == g_sect_name_ctf) | |
| return eSectionTypeCTF; | |
| if (section_name == g_sect_name_lldb_summaries) | |
| return lldb::eSectionTypeLLDBTypeSummaries; | |
| if (section_name == g_sect_name_lldb_formatters) | |
| return lldb::eSectionTypeLLDBFormatters; | |
| if (section_name == g_sect_name_swift_ast) | |
| return eSectionTypeSwiftModules; | |
| if (section_name == g_sect_name_objc_data || | |
| section_name == g_sect_name_objc_classrefs || | |
| section_name == g_sect_name_objc_superrefs || | |
| section_name == g_sect_name_objc_const || | |
| section_name == g_sect_name_objc_classlist) { | |
| return eSectionTypeDataPointers; | |
| } | |
| switch (mach_sect_type) { | |
| // TODO: categorize sections by other flags for regular sections | |
| case S_REGULAR: | |
| if (section_name == g_sect_name_text) | |
| return eSectionTypeCode; | |
| if (section_name == g_sect_name_data) | |
| return eSectionTypeData; | |
| return eSectionTypeOther; | |
| case S_ZEROFILL: | |
| return eSectionTypeZeroFill; | |
| case S_CSTRING_LITERALS: // section with only literal C strings | |
| return eSectionTypeDataCString; | |
| case S_4BYTE_LITERALS: // section with only 4 byte literals | |
| return eSectionTypeData4; | |
| case S_8BYTE_LITERALS: // section with only 8 byte literals | |
| return eSectionTypeData8; | |
| case S_LITERAL_POINTERS: // section with only pointers to literals | |
| return eSectionTypeDataPointers; | |
| case S_NON_LAZY_SYMBOL_POINTERS: // section with only non-lazy symbol pointers | |
| return eSectionTypeDataPointers; | |
| case S_LAZY_SYMBOL_POINTERS: // section with only lazy symbol pointers | |
| return eSectionTypeDataPointers; | |
| case S_SYMBOL_STUBS: // section with only symbol stubs, byte size of stub in | |
| // the reserved2 field | |
| return eSectionTypeCode; | |
| case S_MOD_INIT_FUNC_POINTERS: // section with only function pointers for | |
| // initialization | |
| return eSectionTypeDataPointers; | |
| case S_MOD_TERM_FUNC_POINTERS: // section with only function pointers for | |
| // termination | |
| return eSectionTypeDataPointers; | |
| case S_COALESCED: | |
| return eSectionTypeOther; | |
| case S_GB_ZEROFILL: | |
| return eSectionTypeZeroFill; | |
| case S_INTERPOSING: // section with only pairs of function pointers for | |
| // interposing | |
| return eSectionTypeCode; | |
| case S_16BYTE_LITERALS: // section with only 16 byte literals | |
| return eSectionTypeData16; | |
| case S_DTRACE_DOF: | |
| return eSectionTypeDebug; | |
| case S_LAZY_DYLIB_SYMBOL_POINTERS: | |
| return eSectionTypeDataPointers; | |
| default: | |
| return eSectionTypeOther; | |
| } | |
| } | |
| struct ObjectFileMachO::SegmentParsingContext { | |
| const EncryptedFileRanges EncryptedRanges; | |
| lldb_private::SectionList &UnifiedList; | |
| uint32_t NextSegmentIdx = 0; | |
| uint32_t NextSectionIdx = 0; | |
| bool FileAddressesChanged = false; | |
| SegmentParsingContext(EncryptedFileRanges EncryptedRanges, | |
| lldb_private::SectionList &UnifiedList) | |
| : EncryptedRanges(std::move(EncryptedRanges)), UnifiedList(UnifiedList) {} | |
| }; | |
| void ObjectFileMachO::ProcessSegmentCommand( | |
| const llvm::MachO::load_command &load_cmd_, lldb::offset_t offset, | |
| uint32_t cmd_idx, SegmentParsingContext &context) { | |
| llvm::MachO::segment_command_64 load_cmd; | |
| memcpy(&load_cmd, &load_cmd_, sizeof(load_cmd_)); | |
| if (!m_data_nsp->GetU8(&offset, (uint8_t *)load_cmd.segname, 16)) | |
| return; | |
| ModuleSP module_sp = GetModule(); | |
| const bool is_core = GetType() == eTypeCoreFile; | |
| const bool is_dsym = (m_header.filetype == MH_DSYM); | |
| bool add_section = true; | |
| bool add_to_unified = true; | |
| llvm::StringRef segname(load_cmd.segname, | |
| strnlen(load_cmd.segname, sizeof(load_cmd.segname))); | |
| SectionSP unified_section_sp(context.UnifiedList.FindSectionByName(segname)); | |
| if (is_dsym && unified_section_sp) { | |
| if (segname == GetSegmentNameLINKEDIT()) { | |
| // We need to keep the __LINKEDIT segment private to this object file | |
| // only | |
| add_to_unified = false; | |
| } else { | |
| // This is the dSYM file and this section has already been created by the | |
| // object file, no need to create it. | |
| add_section = false; | |
| } | |
| } | |
| load_cmd.vmaddr = m_data_nsp->GetAddress(&offset); | |
| load_cmd.vmsize = m_data_nsp->GetAddress(&offset); | |
| load_cmd.fileoff = m_data_nsp->GetAddress(&offset); | |
| load_cmd.filesize = m_data_nsp->GetAddress(&offset); | |
| if (!m_data_nsp->GetU32(&offset, &load_cmd.maxprot, 4)) | |
| return; | |
| SanitizeSegmentCommand(load_cmd, cmd_idx); | |
| const uint32_t segment_permissions = GetSegmentPermissions(load_cmd); | |
| const bool segment_is_encrypted = | |
| (load_cmd.flags & SG_PROTECTED_VERSION_1) != 0; | |
| // Use a segment ID of the segment index shifted left by 8 so they never | |
| // conflict with any of the sections. | |
| SectionSP segment_sp; | |
| if (add_section && (!segname.empty() || is_core)) { | |
| segment_sp = std::make_shared<Section>( | |
| module_sp, // Module to which this section belongs | |
| this, // Object file to which this sections belongs | |
| ++context.NextSegmentIdx | |
| << 8, // Section ID is the 1 based segment index | |
| // shifted right by 8 bits as not to collide with any of the 256 | |
| // section IDs that are possible | |
| segname.str(), // Name of this section | |
| eSectionTypeContainer, // This section is a container of other | |
| // sections. | |
| load_cmd.vmaddr, // File VM address == addresses as they are | |
| // found in the object file | |
| load_cmd.vmsize, // VM size in bytes of this section | |
| load_cmd.fileoff, // Offset to the data for this section in | |
| // the file | |
| load_cmd.filesize, // Size in bytes of this section as found | |
| // in the file | |
| 0, // Segments have no alignment information | |
| load_cmd.flags); // Flags for this section | |
| segment_sp->SetIsEncrypted(segment_is_encrypted); | |
| m_sections_up->AddSection(segment_sp); | |
| segment_sp->SetPermissions(segment_permissions); | |
| if (add_to_unified) | |
| context.UnifiedList.AddSection(segment_sp); | |
| } else if (unified_section_sp) { | |
| // If this is a dSYM and the file addresses in the dSYM differ from the | |
| // file addresses in the ObjectFile, we must use the file base address for | |
| // the Section from the dSYM for the DWARF to resolve correctly. | |
| // This only happens with binaries in the shared cache in practice; | |
| // normally a mismatch like this would give a binary & dSYM that do not | |
| // match UUIDs. When a binary is included in the shared cache, its | |
| // segments are rearranged to optimize the shared cache, so its file | |
| // addresses will differ from what the ObjectFile had originally, | |
| // and what the dSYM has. | |
| if (is_dsym && unified_section_sp->GetFileAddress() != load_cmd.vmaddr) { | |
| LLDB_LOG(GetLog(LLDBLog::Symbols), | |
| "Installing dSYM's {0} segment file address over ObjectFile's " | |
| "so symbol table/debug info resolves correctly for {1}", | |
| segname, module_sp->GetFileSpec().GetFilename()); | |
| // Make sure we've parsed the symbol table from the ObjectFile before | |
| // we go around changing its Sections. | |
| module_sp->GetObjectFile()->GetSymtab(); | |
| // eh_frame would present the same problems but we parse that on a per- | |
| // function basis as-needed so it's more difficult to remove its use of | |
| // the Sections. Realistically, the environments where this code path | |
| // will be taken will not have eh_frame sections. | |
| unified_section_sp->SetFileAddress(load_cmd.vmaddr); | |
| // Notify the module that the section addresses have been changed once | |
| // we're done so any file-address caches can be updated. | |
| context.FileAddressesChanged = true; | |
| } | |
| m_sections_up->AddSection(unified_section_sp); | |
| } | |
| llvm::MachO::section_64 sect64; | |
| ::memset(§64, 0, sizeof(sect64)); | |
| // Push a section into our mach sections for the section at index zero | |
| // (NO_SECT) if we don't have any mach sections yet... | |
| if (m_mach_sections.empty()) | |
| m_mach_sections.push_back(sect64); | |
| uint32_t segment_sect_idx; | |
| const lldb::user_id_t first_segment_sectID = context.NextSectionIdx + 1; | |
| // 64 bit mach-o files have sections with 32 bit file offsets. If any section | |
| // data end will exceed UINT32_MAX, then we need to do some bookkeeping to | |
| // ensure we can access this data correctly. | |
| uint64_t section_offset_adjust = 0; | |
| const uint32_t num_u32s = load_cmd.cmd == LC_SEGMENT ? 7 : 8; | |
| for (segment_sect_idx = 0; segment_sect_idx < load_cmd.nsects; | |
| ++segment_sect_idx) { | |
| if (m_data_nsp->GetU8(&offset, (uint8_t *)sect64.sectname, | |
| sizeof(sect64.sectname)) == nullptr) | |
| break; | |
| if (m_data_nsp->GetU8(&offset, (uint8_t *)sect64.segname, | |
| sizeof(sect64.segname)) == nullptr) | |
| break; | |
| sect64.addr = m_data_nsp->GetAddress(&offset); | |
| sect64.size = m_data_nsp->GetAddress(&offset); | |
| if (m_data_nsp->GetU32(&offset, §64.offset, num_u32s) == nullptr) | |
| break; | |
| if (IsSharedCacheBinary() && !IsInMemory()) { | |
| sect64.offset = sect64.addr - m_text_address; | |
| } | |
| // Keep a list of mach sections around in case we need to get at data that | |
| // isn't stored in the abstracted Sections. | |
| m_mach_sections.push_back(sect64); | |
| // Make sure we can load sections in mach-o files where some sections cross | |
| // a 4GB boundary. llvm::MachO::section_64 have only 32 bit file offsets | |
| // for the file offset of the section contents, so we need to track and | |
| // sections that overflow and adjust the offsets accordingly. | |
| const uint64_t section_file_offset = | |
| (uint64_t)sect64.offset + section_offset_adjust; | |
| const uint64_t end_section_offset = (uint64_t)sect64.offset + sect64.size; | |
| if (end_section_offset >= UINT32_MAX) | |
| section_offset_adjust += end_section_offset & 0xFFFFFFFF00000000ull; | |
| if (add_section) { | |
| llvm::StringRef section_name( | |
| sect64.sectname, strnlen(sect64.sectname, sizeof(sect64.sectname))); | |
| if (segname.empty()) { | |
| // We have a segment with no name so we need to conjure up segments | |
| // that correspond to the section's segname if there isn't already such | |
| // a section. If there is such a section, we resize the section so that | |
| // it spans all sections. We also mark these sections as fake so | |
| // address matches don't hit if they land in the gaps between the child | |
| // sections. | |
| segname = llvm::StringRef( | |
| sect64.segname, strnlen(sect64.segname, sizeof(sect64.segname))); | |
| segment_sp = context.UnifiedList.FindSectionByName(segname); | |
| if (segment_sp.get()) { | |
| Section *segment = segment_sp.get(); | |
| // Grow the section size as needed. | |
| const lldb::addr_t sect64_min_addr = sect64.addr; | |
| const lldb::addr_t sect64_max_addr = sect64_min_addr + sect64.size; | |
| const lldb::addr_t curr_seg_byte_size = segment->GetByteSize(); | |
| const lldb::addr_t curr_seg_min_addr = segment->GetFileAddress(); | |
| const lldb::addr_t curr_seg_max_addr = | |
| curr_seg_min_addr + curr_seg_byte_size; | |
| if (sect64_min_addr >= curr_seg_min_addr) { | |
| const lldb::addr_t new_seg_byte_size = | |
| sect64_max_addr - curr_seg_min_addr; | |
| // Only grow the section size if needed | |
| if (new_seg_byte_size > curr_seg_byte_size) | |
| segment->SetByteSize(new_seg_byte_size); | |
| } else { | |
| // We need to change the base address of the segment and adjust the | |
| // child section offsets for all existing children. | |
| const lldb::addr_t slide_amount = | |
| sect64_min_addr - curr_seg_min_addr; | |
| segment->Slide(slide_amount, false); | |
| segment->GetChildren().Slide(-slide_amount, false); | |
| segment->SetByteSize(curr_seg_max_addr - sect64_min_addr); | |
| } | |
| // Grow the section size as needed. | |
| if (section_file_offset) { | |
| const lldb::addr_t segment_min_file_offset = | |
| segment->GetFileOffset(); | |
| const lldb::addr_t segment_max_file_offset = | |
| segment_min_file_offset + segment->GetFileSize(); | |
| const lldb::addr_t section_min_file_offset = section_file_offset; | |
| const lldb::addr_t section_max_file_offset = | |
| section_min_file_offset + sect64.size; | |
| const lldb::addr_t new_file_offset = | |
| std::min(section_min_file_offset, segment_min_file_offset); | |
| const lldb::addr_t new_file_size = | |
| std::max(section_max_file_offset, segment_max_file_offset) - | |
| new_file_offset; | |
| segment->SetFileOffset(new_file_offset); | |
| segment->SetFileSize(new_file_size); | |
| } | |
| } else { | |
| // Create a fake section for the section's named segment | |
| segment_sp = std::make_shared<Section>( | |
| segment_sp, // Parent section | |
| module_sp, // Module to which this section belongs | |
| this, // Object file to which this section belongs | |
| ++context.NextSegmentIdx | |
| << 8, // Section ID is the 1 based segment index | |
| // shifted right by 8 bits as not to | |
| // collide with any of the 256 section IDs | |
| // that are possible | |
| segname.str(), // Name of this section | |
| eSectionTypeContainer, // This section is a container of | |
| // other sections. | |
| sect64.addr, // File VM address == addresses as they are | |
| // found in the object file | |
| sect64.size, // VM size in bytes of this section | |
| section_file_offset, // Offset to the data for this section in | |
| // the file | |
| section_file_offset ? sect64.size : 0, // Size in bytes of | |
| // this section as | |
| // found in the file | |
| sect64.align, | |
| load_cmd.flags); // Flags for this section | |
| segment_sp->SetIsFake(true); | |
| segment_sp->SetPermissions(segment_permissions); | |
| m_sections_up->AddSection(segment_sp); | |
| if (add_to_unified) | |
| context.UnifiedList.AddSection(segment_sp); | |
| segment_sp->SetIsEncrypted(segment_is_encrypted); | |
| } | |
| } | |
| assert(segment_sp.get()); | |
| lldb::SectionType sect_type = GetSectionType(sect64.flags, section_name); | |
| SectionSP section_sp = std::make_shared<Section>( | |
| segment_sp, module_sp, this, ++context.NextSectionIdx, | |
| section_name.str(), sect_type, | |
| sect64.addr - segment_sp->GetFileAddress(), sect64.size, | |
| section_file_offset, section_file_offset == 0 ? 0 : sect64.size, | |
| sect64.align, sect64.flags); | |
| // Set the section to be encrypted to match the segment | |
| bool section_is_encrypted = false; | |
| if (!segment_is_encrypted && load_cmd.filesize != 0) | |
| section_is_encrypted = context.EncryptedRanges.FindEntryThatContains( | |
| section_file_offset) != nullptr; | |
| section_sp->SetIsEncrypted(segment_is_encrypted || section_is_encrypted); | |
| section_sp->SetPermissions(segment_permissions); | |
| segment_sp->GetChildren().AddSection(section_sp); | |
| if (segment_sp->IsFake()) { | |
| segment_sp.reset(); | |
| segname = {}; | |
| } | |
| } | |
| } | |
| if (segment_sp && is_dsym) { | |
| if (first_segment_sectID <= context.NextSectionIdx) { | |
| lldb::user_id_t sect_uid; | |
| for (sect_uid = first_segment_sectID; sect_uid <= context.NextSectionIdx; | |
| ++sect_uid) { | |
| SectionSP curr_section_sp( | |
| segment_sp->GetChildren().FindSectionByID(sect_uid)); | |
| SectionSP next_section_sp; | |
| if (sect_uid + 1 <= context.NextSectionIdx) | |
| next_section_sp = | |
| segment_sp->GetChildren().FindSectionByID(sect_uid + 1); | |
| if (curr_section_sp.get()) { | |
| if (curr_section_sp->GetByteSize() == 0) { | |
| if (next_section_sp.get() != nullptr) | |
| curr_section_sp->SetByteSize(next_section_sp->GetFileAddress() - | |
| curr_section_sp->GetFileAddress()); | |
| else | |
| curr_section_sp->SetByteSize(load_cmd.vmsize); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void ObjectFileMachO::ProcessDysymtabCommand( | |
| const llvm::MachO::load_command &load_cmd, lldb::offset_t offset) { | |
| m_dysymtab.cmd = load_cmd.cmd; | |
| m_dysymtab.cmdsize = load_cmd.cmdsize; | |
| m_data_nsp->GetU32(&offset, &m_dysymtab.ilocalsym, | |
| (sizeof(m_dysymtab) / sizeof(uint32_t)) - 2); | |
| } | |
| void ObjectFileMachO::CreateSections(SectionList &unified_section_list) { | |
| if (m_sections_up) | |
| return; | |
| m_sections_up = std::make_unique<SectionList>(); | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| // bool dump_sections = false; | |
| ModuleSP module_sp(GetModule()); | |
| offset = MachHeaderSizeFromMagic(m_header.magic); | |
| SegmentParsingContext context(GetEncryptedFileRanges(), unified_section_list); | |
| llvm::MachO::load_command load_cmd; | |
| for (uint32_t i = 0; i < m_header.ncmds; ++i) { | |
| const lldb::offset_t load_cmd_offset = offset; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, load_cmd)) | |
| break; | |
| if (load_cmd.cmd == LC_SEGMENT || load_cmd.cmd == LC_SEGMENT_64) | |
| ProcessSegmentCommand(load_cmd, offset, i, context); | |
| else if (load_cmd.cmd == LC_DYSYMTAB) | |
| ProcessDysymtabCommand(load_cmd, offset); | |
| offset = load_cmd_offset + load_cmd.cmdsize; | |
| } | |
| if (context.FileAddressesChanged && module_sp) | |
| module_sp->SectionFileAddressesChanged(); | |
| } | |
| class MachSymtabSectionInfo { | |
| public: | |
| MachSymtabSectionInfo(SectionList *section_list) | |
| : m_section_list(section_list), m_section_infos() { | |
| // Get the number of sections down to a depth of 1 to include all segments | |
| // and their sections, but no other sections that may be added for debug | |
| // map or | |
| m_section_infos.resize(section_list->GetNumSections(1)); | |
| } | |
| SectionSP GetSection(uint8_t n_sect, addr_t file_addr) { | |
| if (n_sect == 0) | |
| return SectionSP(); | |
| if (n_sect < m_section_infos.size()) { | |
| if (!m_section_infos[n_sect].section_sp) { | |
| SectionSP section_sp(m_section_list->FindSectionByID(n_sect)); | |
| m_section_infos[n_sect].section_sp = section_sp; | |
| if (section_sp) { | |
| m_section_infos[n_sect].vm_range.SetRangeBase( | |
| section_sp->GetFileAddress()); | |
| m_section_infos[n_sect].vm_range.SetByteSize( | |
| section_sp->GetByteSize()); | |
| } else { | |
| std::string filename = "<unknown>"; | |
| SectionSP first_section_sp(m_section_list->GetSectionAtIndex(0)); | |
| if (first_section_sp) | |
| filename = first_section_sp->GetObjectFile()->GetFileSpec().GetPath(); | |
| Debugger::ReportError( | |
| llvm::formatv("unable to find section {0} for a symbol in " | |
| "{1}, corrupt file?", | |
| n_sect, filename)); | |
| } | |
| } | |
| if (m_section_infos[n_sect].vm_range.Contains(file_addr)) { | |
| // Symbol is in section. | |
| return m_section_infos[n_sect].section_sp; | |
| } else if (m_section_infos[n_sect].vm_range.GetByteSize() == 0 && | |
| m_section_infos[n_sect].vm_range.GetRangeBase() == file_addr) { | |
| // Symbol is in section with zero size, but has the same start address | |
| // as the section. This can happen with linker symbols (symbols that | |
| // start with the letter 'l' or 'L'. | |
| return m_section_infos[n_sect].section_sp; | |
| } | |
| } | |
| return m_section_list->FindSectionContainingFileAddress(file_addr); | |
| } | |
| protected: | |
| struct SectionInfo { | |
| SectionInfo() : vm_range(), section_sp() {} | |
| Range<addr_t, addr_t> vm_range; | |
| SectionSP section_sp; | |
| }; | |
| SectionList *m_section_list; | |
| std::vector<SectionInfo> m_section_infos; | |
| }; | |
| static bool | |
| TryParseV2ObjCMetadataSymbol(const char *&symbol_name, | |
| const char *&symbol_name_non_abi_mangled, | |
| SymbolType &type) { | |
| static constexpr llvm::StringLiteral g_objc_v2_prefix_class("_OBJC_CLASS_$_"); | |
| static constexpr llvm::StringLiteral g_objc_v2_prefix_metaclass( | |
| "_OBJC_METACLASS_$_"); | |
| static constexpr llvm::StringLiteral g_objc_v2_prefix_ivar("_OBJC_IVAR_$_"); | |
| llvm::StringRef symbol_name_ref(symbol_name); | |
| if (symbol_name_ref.empty()) | |
| return false; | |
| if (symbol_name_ref.starts_with(g_objc_v2_prefix_class)) { | |
| symbol_name_non_abi_mangled = symbol_name + 1; | |
| symbol_name = symbol_name + g_objc_v2_prefix_class.size(); | |
| type = eSymbolTypeObjCClass; | |
| return true; | |
| } | |
| if (symbol_name_ref.starts_with(g_objc_v2_prefix_metaclass)) { | |
| symbol_name_non_abi_mangled = symbol_name + 1; | |
| symbol_name = symbol_name + g_objc_v2_prefix_metaclass.size(); | |
| type = eSymbolTypeObjCMetaClass; | |
| return true; | |
| } | |
| if (symbol_name_ref.starts_with(g_objc_v2_prefix_ivar)) { | |
| symbol_name_non_abi_mangled = symbol_name + 1; | |
| symbol_name = symbol_name + g_objc_v2_prefix_ivar.size(); | |
| type = eSymbolTypeObjCIVar; | |
| return true; | |
| } | |
| return false; | |
| } | |
| static SymbolType GetSymbolType(const char *&symbol_name, | |
| bool &demangled_is_synthesized, | |
| const SectionSP &text_section_sp, | |
| const SectionSP &data_section_sp, | |
| const SectionSP &data_dirty_section_sp, | |
| const SectionSP &data_const_section_sp, | |
| const SectionSP &symbol_section) { | |
| SymbolType type = eSymbolTypeInvalid; | |
| llvm::StringRef symbol_sect_name = symbol_section->GetName(); | |
| if (symbol_section->IsDescendant(text_section_sp.get())) { | |
| if (symbol_section->IsClear(S_ATTR_PURE_INSTRUCTIONS | | |
| S_ATTR_SELF_MODIFYING_CODE | | |
| S_ATTR_SOME_INSTRUCTIONS)) | |
| type = eSymbolTypeData; | |
| else | |
| type = eSymbolTypeCode; | |
| } else if (symbol_section->IsDescendant(data_section_sp.get()) || | |
| symbol_section->IsDescendant(data_dirty_section_sp.get()) || | |
| symbol_section->IsDescendant(data_const_section_sp.get())) { | |
| if (symbol_sect_name.starts_with("__objc")) { | |
| type = eSymbolTypeRuntime; | |
| if (symbol_name) { | |
| llvm::StringRef symbol_name_ref(symbol_name); | |
| if (symbol_name_ref.starts_with("OBJC_")) { | |
| static const llvm::StringRef g_objc_v2_prefix_class("OBJC_CLASS_$_"); | |
| static const llvm::StringRef g_objc_v2_prefix_metaclass( | |
| "OBJC_METACLASS_$_"); | |
| static const llvm::StringRef g_objc_v2_prefix_ivar("OBJC_IVAR_$_"); | |
| if (symbol_name_ref.starts_with(g_objc_v2_prefix_class)) { | |
| symbol_name = symbol_name + g_objc_v2_prefix_class.size(); | |
| type = eSymbolTypeObjCClass; | |
| demangled_is_synthesized = true; | |
| } else if (symbol_name_ref.starts_with(g_objc_v2_prefix_metaclass)) { | |
| symbol_name = symbol_name + g_objc_v2_prefix_metaclass.size(); | |
| type = eSymbolTypeObjCMetaClass; | |
| demangled_is_synthesized = true; | |
| } else if (symbol_name_ref.starts_with(g_objc_v2_prefix_ivar)) { | |
| symbol_name = symbol_name + g_objc_v2_prefix_ivar.size(); | |
| type = eSymbolTypeObjCIVar; | |
| demangled_is_synthesized = true; | |
| } | |
| } | |
| } | |
| } else if (symbol_sect_name.starts_with("__gcc_except_tab")) { | |
| type = eSymbolTypeException; | |
| } else { | |
| type = eSymbolTypeData; | |
| } | |
| } else if (symbol_sect_name.starts_with("__IMPORT")) { | |
| type = eSymbolTypeTrampoline; | |
| } | |
| return type; | |
| } | |
| static std::optional<struct nlist_64> | |
| ParseNList(DataExtractor &nlist_data, lldb::offset_t &nlist_data_offset, | |
| size_t nlist_byte_size) { | |
| struct nlist_64 nlist; | |
| if (!nlist_data.ValidOffsetForDataOfSize(nlist_data_offset, nlist_byte_size)) | |
| return {}; | |
| nlist.n_strx = nlist_data.GetU32_unchecked(&nlist_data_offset); | |
| nlist.n_type = nlist_data.GetU8_unchecked(&nlist_data_offset); | |
| nlist.n_sect = nlist_data.GetU8_unchecked(&nlist_data_offset); | |
| nlist.n_desc = nlist_data.GetU16_unchecked(&nlist_data_offset); | |
| nlist.n_value = nlist_data.GetAddress_unchecked(&nlist_data_offset); | |
| return nlist; | |
| } | |
| enum { DebugSymbols = true, NonDebugSymbols = false }; | |
| void ObjectFileMachO::ParseSymtab(Symtab &symtab) { | |
| ModuleSP module_sp(GetModule()); | |
| if (!module_sp) | |
| return; | |
| Log *log = GetLog(LLDBLog::Symbols); | |
| const FileSpec &file = m_file ? m_file : module_sp->GetFileSpec(); | |
| llvm::StringRef file_name = file.GetFilename().nonEmptyOr("<Unknown>"); | |
| LLDB_SCOPED_TIMERF("ObjectFileMachO::ParseSymtab () module = %s", | |
| file_name.str().c_str()); | |
| LLDB_LOG(log, "Parsing symbol table for {0}", file_name); | |
| Progress progress("Parsing symbol table", file_name.str()); | |
| LinkeditDataCommandLargeOffsets function_starts_load_command; | |
| LinkeditDataCommandLargeOffsets exports_trie_load_command; | |
| DyldInfoCommandLargeOffsets dyld_info; | |
| DysymtabCommandLargeOffsets dysymtab(m_dysymtab); | |
| SymtabCommandLargeOffsets symtab_load_command; | |
| // The data element of type bool indicates that this entry is thumb | |
| // code. | |
| typedef AddressDataArray<lldb::addr_t, bool, 100> FunctionStarts; | |
| // Record the address of every function/data that we add to the symtab. | |
| // We add symbols to the table in the order of most information (nlist | |
| // records) to least (function starts), and avoid duplicating symbols | |
| // via this set. | |
| llvm::DenseSet<addr_t> symbols_added; | |
| // We are using a llvm::DenseSet for "symbols_added" so we must be sure we | |
| // do not add the empty key to the set. | |
| auto add_symbol_addr = [&symbols_added](lldb::addr_t file_addr) { | |
| // Don't add the empty key. | |
| if (file_addr == UINT64_MAX) | |
| return; | |
| symbols_added.insert(file_addr); | |
| }; | |
| FunctionStarts function_starts; | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| uint32_t i; | |
| FileSpecList dylib_files; | |
| UUID image_uuid; | |
| for (i = 0; i < m_header.ncmds; ++i) { | |
| const lldb::offset_t cmd_offset = offset; | |
| // Read in the load command and load command size | |
| llvm::MachO::load_command lc; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, lc)) | |
| break; | |
| // Watch for the symbol table load command | |
| switch (lc.cmd) { | |
| case LC_SYMTAB: { | |
| llvm::MachO::symtab_command lc_obj; | |
| if (m_data_nsp->GetU32(&offset, &lc_obj.symoff, 4)) { | |
| lc_obj.cmd = lc.cmd; | |
| lc_obj.cmdsize = lc.cmdsize; | |
| symtab_load_command = lc_obj; | |
| } | |
| } break; | |
| case LC_DYLD_INFO: | |
| case LC_DYLD_INFO_ONLY: { | |
| llvm::MachO::dyld_info_command lc_obj; | |
| if (m_data_nsp->GetU32(&offset, &lc_obj.rebase_off, 10)) { | |
| lc_obj.cmd = lc.cmd; | |
| lc_obj.cmdsize = lc.cmdsize; | |
| dyld_info = lc_obj; | |
| } | |
| } break; | |
| case LC_LOAD_DYLIB: | |
| case LC_LOAD_WEAK_DYLIB: | |
| case LC_REEXPORT_DYLIB: | |
| case LC_LOADFVMLIB: | |
| case LC_LOAD_UPWARD_DYLIB: { | |
| uint32_t name_offset = cmd_offset + m_data_nsp->GetU32(&offset); | |
| if (std::optional<llvm::StringRef> path = | |
| m_data_nsp->PeekCStr(name_offset)) { | |
| FileSpec file_spec(*path); | |
| // Strip the path if there is @rpath, @executable, etc so we just use | |
| // the basename | |
| if (path->starts_with("@")) | |
| file_spec.ClearDirectory(); | |
| if (lc.cmd == LC_REEXPORT_DYLIB) { | |
| m_reexported_dylibs.AppendIfUnique(file_spec); | |
| } | |
| dylib_files.Append(file_spec); | |
| } | |
| } break; | |
| case LC_DYLD_EXPORTS_TRIE: { | |
| llvm::MachO::linkedit_data_command lc_obj; | |
| lc_obj.cmd = lc.cmd; | |
| lc_obj.cmdsize = lc.cmdsize; | |
| if (m_data_nsp->GetU32(&offset, &lc_obj.dataoff, 2)) | |
| exports_trie_load_command = lc_obj; | |
| } break; | |
| case LC_FUNCTION_STARTS: { | |
| llvm::MachO::linkedit_data_command lc_obj; | |
| lc_obj.cmd = lc.cmd; | |
| lc_obj.cmdsize = lc.cmdsize; | |
| if (m_data_nsp->GetU32(&offset, &lc_obj.dataoff, 2)) | |
| function_starts_load_command = lc_obj; | |
| } break; | |
| case LC_UUID: { | |
| const uint8_t *uuid_bytes = m_data_nsp->PeekData(offset, 16); | |
| if (uuid_bytes) | |
| image_uuid = UUID(uuid_bytes, 16); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| offset = cmd_offset + lc.cmdsize; | |
| } | |
| if (!symtab_load_command.cmd) | |
| return; | |
| SectionList *section_list = GetSectionList(); | |
| if (section_list == nullptr) | |
| return; | |
| const uint32_t addr_byte_size = m_data_nsp->GetAddressByteSize(); | |
| const ByteOrder byte_order = m_data_nsp->GetByteOrder(); | |
| bool bit_width_32 = addr_byte_size == 4; | |
| const size_t nlist_byte_size = | |
| bit_width_32 ? sizeof(struct nlist) : sizeof(struct nlist_64); | |
| DataExtractor nlist_data(nullptr, 0, byte_order, addr_byte_size); | |
| DataExtractor strtab_data(nullptr, 0, byte_order, addr_byte_size); | |
| DataExtractor function_starts_data(nullptr, 0, byte_order, addr_byte_size); | |
| DataExtractor indirect_symbol_index_data(nullptr, 0, byte_order, | |
| addr_byte_size); | |
| DataExtractor dyld_trie_data(nullptr, 0, byte_order, addr_byte_size); | |
| const addr_t nlist_data_byte_size = | |
| symtab_load_command.nsyms * nlist_byte_size; | |
| const addr_t strtab_data_byte_size = symtab_load_command.strsize; | |
| addr_t strtab_addr = LLDB_INVALID_ADDRESS; | |
| ProcessSP process_sp(m_process_wp.lock()); | |
| Process *process = process_sp.get(); | |
| uint32_t memory_module_load_level = eMemoryModuleLoadLevelComplete; | |
| bool is_shared_cache_image = IsSharedCacheBinary(); | |
| bool is_local_shared_cache_image = is_shared_cache_image && !IsInMemory(); | |
| SectionSP text_section_sp( | |
| section_list->FindSectionByName(GetSegmentNameTEXT())); | |
| SectionSP data_section_sp( | |
| section_list->FindSectionByName(GetSegmentNameDATA())); | |
| SectionSP linkedit_section_sp( | |
| section_list->FindSectionByName(GetSegmentNameLINKEDIT())); | |
| SectionSP data_dirty_section_sp( | |
| section_list->FindSectionByName(GetSegmentNameDATA_DIRTY())); | |
| SectionSP data_const_section_sp( | |
| section_list->FindSectionByName(GetSegmentNameDATA_CONST())); | |
| SectionSP objc_section_sp( | |
| section_list->FindSectionByName(GetSegmentNameOBJC())); | |
| SectionSP eh_frame_section_sp; | |
| SectionSP lldb_no_nlist_section_sp; | |
| llvm::StringRef g_section_name_eh_frame = GetSectionNameEHFrame(); | |
| llvm::StringRef g_section_name_lldb_no_nlist = GetSectionNameLLDBNoNlist(); | |
| if (text_section_sp.get()) { | |
| eh_frame_section_sp = text_section_sp->GetChildren().FindSectionByName( | |
| g_section_name_eh_frame); | |
| lldb_no_nlist_section_sp = text_section_sp->GetChildren().FindSectionByName( | |
| g_section_name_lldb_no_nlist); | |
| } else { | |
| eh_frame_section_sp = | |
| section_list->FindSectionByName(g_section_name_eh_frame); | |
| lldb_no_nlist_section_sp = | |
| section_list->FindSectionByName(g_section_name_lldb_no_nlist); | |
| } | |
| if (process && m_header.filetype != llvm::MachO::MH_OBJECT && | |
| !is_local_shared_cache_image) { | |
| Target &target = process->GetTarget(); | |
| memory_module_load_level = target.GetMemoryModuleLoadLevel(); | |
| // If __TEXT,__lldb_no_nlist section is present in this binary, | |
| // and we're reading it out of memory, do not read any of the | |
| // nlist entries. They are not needed in lldb and it may be | |
| // expensive to load these. This is to handle a dylib consisting | |
| // of only metadata, no code, but it has many nlist entries. | |
| if (lldb_no_nlist_section_sp) | |
| memory_module_load_level = eMemoryModuleLoadLevelMinimal; | |
| // Reading mach file from memory in a process or core file... | |
| if (linkedit_section_sp) { | |
| addr_t linkedit_load_addr = | |
| linkedit_section_sp->GetLoadBaseAddress(&target); | |
| if (linkedit_load_addr == LLDB_INVALID_ADDRESS) { | |
| // We might be trying to access the symbol table before the | |
| // __LINKEDIT's load address has been set in the target. We can't | |
| // fail to read the symbol table, so calculate the right address | |
| // manually | |
| linkedit_load_addr = CalculateSectionLoadAddressForMemoryImage( | |
| m_memory_addr, GetMachHeaderSection(), linkedit_section_sp.get()); | |
| } | |
| const addr_t linkedit_file_offset = linkedit_section_sp->GetFileOffset(); | |
| const addr_t symoff_addr = linkedit_load_addr + | |
| symtab_load_command.symoff - | |
| linkedit_file_offset; | |
| strtab_addr = linkedit_load_addr + symtab_load_command.stroff - | |
| linkedit_file_offset; | |
| // Always load dyld - the dynamic linker - from memory if we didn't | |
| // find a binary anywhere else. lldb will not register | |
| // dylib/framework/bundle loads/unloads if we don't have the dyld | |
| // symbols, we force dyld to load from memory despite the user's | |
| // target.memory-module-load-level setting. | |
| if (memory_module_load_level == eMemoryModuleLoadLevelComplete || | |
| m_header.filetype == llvm::MachO::MH_DYLINKER) { | |
| DataBufferSP nlist_data_sp( | |
| ReadMemory(process_sp, symoff_addr, nlist_data_byte_size)); | |
| if (nlist_data_sp) | |
| nlist_data.SetData(nlist_data_sp, 0, nlist_data_sp->GetByteSize()); | |
| if (dysymtab.nindirectsyms != 0) { | |
| const addr_t indirect_syms_addr = linkedit_load_addr + | |
| dysymtab.indirectsymoff - | |
| linkedit_file_offset; | |
| DataBufferSP indirect_syms_data_sp(ReadMemory( | |
| process_sp, indirect_syms_addr, dysymtab.nindirectsyms * 4)); | |
| if (indirect_syms_data_sp) | |
| indirect_symbol_index_data.SetData( | |
| indirect_syms_data_sp, 0, indirect_syms_data_sp->GetByteSize()); | |
| // If this binary is outside the shared cache, | |
| // cache the string table. | |
| // Binaries in the shared cache all share a giant string table, | |
| // and we can't share the string tables across multiple | |
| // ObjectFileMachO's, so we'd end up re-reading this mega-strtab | |
| // for every binary in the shared cache - it would be a big perf | |
| // problem. For binaries outside the shared cache, it's faster to | |
| // read the entire strtab at once instead of piece-by-piece as we | |
| // process the nlist records. | |
| if (!is_shared_cache_image) { | |
| DataBufferSP strtab_data_sp( | |
| ReadMemory(process_sp, strtab_addr, strtab_data_byte_size)); | |
| if (strtab_data_sp) { | |
| strtab_data.SetData(strtab_data_sp, 0, | |
| strtab_data_sp->GetByteSize()); | |
| } | |
| } | |
| } | |
| if (memory_module_load_level >= eMemoryModuleLoadLevelPartial) { | |
| if (function_starts_load_command.cmd) { | |
| const addr_t func_start_addr = | |
| linkedit_load_addr + function_starts_load_command.dataoff - | |
| linkedit_file_offset; | |
| DataBufferSP func_start_data_sp( | |
| ReadMemory(process_sp, func_start_addr, | |
| function_starts_load_command.datasize)); | |
| if (func_start_data_sp) | |
| function_starts_data.SetData(func_start_data_sp, 0, | |
| func_start_data_sp->GetByteSize()); | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| if (is_local_shared_cache_image && linkedit_section_sp) { | |
| // The load commands in shared cache images are relative to the | |
| // beginning of the shared cache, not the library image. The | |
| // data we get handed when creating the ObjectFileMachO starts | |
| // at the beginning of a specific library and spans to the end | |
| // of the cache to be able to reach the shared LINKEDIT | |
| // segments. We need to convert the load command offsets to be | |
| // relative to the beginning of our specific image. | |
| lldb::addr_t linkedit_offset = linkedit_section_sp->GetFileOffset(); | |
| lldb::offset_t linkedit_slide = | |
| linkedit_offset - m_linkedit_original_offset; | |
| symtab_load_command.symoff += linkedit_slide; | |
| symtab_load_command.stroff += linkedit_slide; | |
| dyld_info.export_off += linkedit_slide; | |
| dysymtab.indirectsymoff += linkedit_slide; | |
| function_starts_load_command.dataoff += linkedit_slide; | |
| exports_trie_load_command.dataoff += linkedit_slide; | |
| } | |
| nlist_data = *m_data_nsp->GetSubsetExtractorSP(symtab_load_command.symoff, | |
| nlist_data_byte_size); | |
| strtab_data = *m_data_nsp->GetSubsetExtractorSP(symtab_load_command.stroff, | |
| strtab_data_byte_size); | |
| // We shouldn't have exports data from both the LC_DYLD_INFO command | |
| // AND the LC_DYLD_EXPORTS_TRIE command in the same binary: | |
| lldbassert(!((dyld_info.export_size > 0) | |
| && (exports_trie_load_command.datasize > 0))); | |
| if (dyld_info.export_size > 0) { | |
| dyld_trie_data = *m_data_nsp->GetSubsetExtractorSP(dyld_info.export_off, | |
| dyld_info.export_size); | |
| } else if (exports_trie_load_command.datasize > 0) { | |
| dyld_trie_data = | |
| *m_data_nsp->GetSubsetExtractorSP(exports_trie_load_command.dataoff, | |
| exports_trie_load_command.datasize); | |
| } | |
| if (dysymtab.nindirectsyms != 0) { | |
| indirect_symbol_index_data = *m_data_nsp->GetSubsetExtractorSP( | |
| dysymtab.indirectsymoff, dysymtab.nindirectsyms * 4); | |
| } | |
| if (function_starts_load_command.cmd) { | |
| function_starts_data = *m_data_nsp->GetSubsetExtractorSP( | |
| function_starts_load_command.dataoff, | |
| function_starts_load_command.datasize); | |
| } | |
| } | |
| const bool have_strtab_data = strtab_data.GetByteSize() > 0; | |
| const bool is_arm = (m_header.cputype == llvm::MachO::CPU_TYPE_ARM); | |
| const bool always_thumb = GetArchitecture().IsAlwaysThumbInstructions(); | |
| // lldb works best if it knows the start address of all functions in a | |
| // module. Linker symbols or debug info are normally the best source of | |
| // information for start addr / size but they may be stripped in a released | |
| // binary. Two additional sources of information exist in Mach-O binaries: | |
| // LC_FUNCTION_STARTS - a list of ULEB128 encoded offsets of each | |
| // function's start address in the | |
| // binary, relative to the text section. | |
| // eh_frame - the eh_frame FDEs have the start addr & size of | |
| // each function | |
| // LC_FUNCTION_STARTS is the fastest source to read in, and is present on | |
| // all modern binaries. | |
| // Binaries built to run on older releases may need to use eh_frame | |
| // information. | |
| if (text_section_sp && function_starts_data.GetByteSize()) { | |
| FunctionStarts::Entry function_start_entry; | |
| function_start_entry.data = false; | |
| lldb::offset_t function_start_offset = 0; | |
| function_start_entry.addr = text_section_sp->GetFileAddress(); | |
| uint64_t delta; | |
| while ((delta = function_starts_data.GetULEB128(&function_start_offset)) > | |
| 0) { | |
| // Now append the current entry | |
| function_start_entry.addr += delta; | |
| if (is_arm) { | |
| if (function_start_entry.addr & 1) { | |
| function_start_entry.addr &= THUMB_ADDRESS_BIT_MASK; | |
| function_start_entry.data = true; | |
| } else if (always_thumb) { | |
| function_start_entry.data = true; | |
| } | |
| } | |
| function_starts.Append(function_start_entry); | |
| } | |
| } else { | |
| // If m_type is eTypeDebugInfo, then this is a dSYM - it will have the | |
| // load command claiming an eh_frame but it doesn't actually have the | |
| // eh_frame content. And if we have a dSYM, we don't need to do any of | |
| // this fill-in-the-missing-symbols works anyway - the debug info should | |
| // give us all the functions in the module. | |
| if (text_section_sp.get() && eh_frame_section_sp.get() && | |
| m_type != eTypeDebugInfo) { | |
| DWARFCallFrameInfo eh_frame(*this, eh_frame_section_sp, | |
| DWARFCallFrameInfo::EH); | |
| DWARFCallFrameInfo::FunctionAddressAndSizeVector functions; | |
| eh_frame.GetFunctionAddressAndSizeVector(functions); | |
| addr_t text_base_addr = text_section_sp->GetFileAddress(); | |
| size_t count = functions.GetSize(); | |
| for (size_t i = 0; i < count; ++i) { | |
| const DWARFCallFrameInfo::FunctionAddressAndSizeVector::Entry *func = | |
| functions.GetEntryAtIndex(i); | |
| if (func) { | |
| FunctionStarts::Entry function_start_entry; | |
| function_start_entry.addr = func->base - text_base_addr; | |
| if (is_arm) { | |
| if (function_start_entry.addr & 1) { | |
| function_start_entry.addr &= THUMB_ADDRESS_BIT_MASK; | |
| function_start_entry.data = true; | |
| } else if (always_thumb) { | |
| function_start_entry.data = true; | |
| } | |
| } | |
| function_starts.Append(function_start_entry); | |
| } | |
| } | |
| } | |
| } | |
| const size_t function_starts_count = function_starts.GetSize(); | |
| // For user process binaries (executables, dylibs, frameworks, bundles), if | |
| // we don't have LC_FUNCTION_STARTS/eh_frame section in this binary, we're | |
| // going to assume the binary has been stripped. Don't allow assembly | |
| // language instruction emulation because we don't know proper function | |
| // start boundaries. | |
| // | |
| // For all other types of binaries (kernels, stand-alone bare board | |
| // binaries, kexts), they may not have LC_FUNCTION_STARTS / eh_frame | |
| // sections - we should not make any assumptions about them based on that. | |
| if (function_starts_count == 0 && CalculateStrata() == eStrataUser) { | |
| m_allow_assembly_emulation_unwind_plans = false; | |
| Log *unwind_or_symbol_log(GetLog(LLDBLog::Symbols | LLDBLog::Unwind)); | |
| if (unwind_or_symbol_log) | |
| module_sp->LogMessage( | |
| unwind_or_symbol_log, | |
| "no LC_FUNCTION_STARTS, will not allow assembly profiled unwinds"); | |
| } | |
| const user_id_t TEXT_eh_frame_sectID = eh_frame_section_sp.get() | |
| ? eh_frame_section_sp->GetID() | |
| : static_cast<user_id_t>(NO_SECT); | |
| uint32_t N_SO_index = UINT32_MAX; | |
| MachSymtabSectionInfo section_info(section_list); | |
| std::vector<uint32_t> N_FUN_indexes; | |
| std::vector<uint32_t> N_NSYM_indexes; | |
| std::vector<uint32_t> N_INCL_indexes; | |
| std::vector<uint32_t> N_BRAC_indexes; | |
| std::vector<uint32_t> N_COMM_indexes; | |
| typedef std::multimap<uint64_t, uint32_t> ValueToSymbolIndexMap; | |
| typedef llvm::DenseMap<uint32_t, uint32_t> NListIndexToSymbolIndexMap; | |
| typedef llvm::DenseMap<const char *, uint32_t> ConstNameToSymbolIndexMap; | |
| ValueToSymbolIndexMap N_FUN_addr_to_sym_idx; | |
| ValueToSymbolIndexMap N_STSYM_addr_to_sym_idx; | |
| ConstNameToSymbolIndexMap N_GSYM_name_to_sym_idx; | |
| // Any symbols that get merged into another will get an entry in this map | |
| // so we know | |
| NListIndexToSymbolIndexMap m_nlist_idx_to_sym_idx; | |
| uint32_t nlist_idx = 0; | |
| Symbol *symbol_ptr = nullptr; | |
| uint32_t sym_idx = 0; | |
| Symbol *sym = nullptr; | |
| size_t num_syms = 0; | |
| std::string memory_symbol_name; | |
| uint32_t unmapped_local_symbols_found = 0; | |
| std::vector<TrieEntryWithOffset> reexport_trie_entries; | |
| std::vector<TrieEntryWithOffset> external_sym_trie_entries; | |
| std::set<lldb::addr_t> resolver_addresses; | |
| const size_t dyld_trie_data_size = dyld_trie_data.GetByteSize(); | |
| if (dyld_trie_data_size > 0) { | |
| LLDB_LOG(log, "Parsing {0} bytes of dyld trie data", dyld_trie_data_size); | |
| SectionSP text_segment_sp = | |
| GetSectionList()->FindSectionByName(GetSegmentNameTEXT()); | |
| lldb::addr_t text_segment_file_addr = LLDB_INVALID_ADDRESS; | |
| if (text_segment_sp) | |
| text_segment_file_addr = text_segment_sp->GetFileAddress(); | |
| ParseTrieEntries(dyld_trie_data, is_arm, text_segment_file_addr, | |
| resolver_addresses, reexport_trie_entries, | |
| external_sym_trie_entries); | |
| } | |
| typedef std::set<ConstString> IndirectSymbols; | |
| IndirectSymbols indirect_symbol_names; | |
| #if TARGET_OS_IPHONE | |
| // Some recent builds of the dyld_shared_cache (hereafter: DSC) have been | |
| // optimized by moving LOCAL symbols out of the memory mapped portion of | |
| // the DSC. The symbol information has all been retained, but it isn't | |
| // available in the normal nlist data. However, there *are* duplicate | |
| // entries of *some* | |
| // LOCAL symbols in the normal nlist data. To handle this situation | |
| // correctly, we must first attempt | |
| // to parse any DSC unmapped symbol information. If we find any, we set a | |
| // flag that tells the normal nlist parser to ignore all LOCAL symbols. | |
| if (IsSharedCacheBinary()) { | |
| // Before we can start mapping the DSC, we need to make certain the | |
| // target process is actually using the cache we can find. | |
| // Next we need to determine the correct path for the dyld shared cache. | |
| ArchSpec header_arch = GetArchitecture(); | |
| UUID dsc_uuid; | |
| UUID process_shared_cache_uuid; | |
| addr_t process_shared_cache_base_addr; | |
| if (process) { | |
| GetProcessSharedCacheUUID(process, process_shared_cache_base_addr, | |
| process_shared_cache_uuid); | |
| } | |
| __block bool found_image = false; | |
| __block void *nlist_buffer = nullptr; | |
| __block unsigned nlist_count = 0; | |
| __block char *string_table = nullptr; | |
| __block vm_offset_t vm_nlist_memory = 0; | |
| __block mach_msg_type_number_t vm_nlist_bytes_read = 0; | |
| __block vm_offset_t vm_string_memory = 0; | |
| __block mach_msg_type_number_t vm_string_bytes_read = 0; | |
| llvm::scope_exit _(^{ | |
| if (vm_nlist_memory) | |
| vm_deallocate(mach_task_self(), vm_nlist_memory, vm_nlist_bytes_read); | |
| if (vm_string_memory) | |
| vm_deallocate(mach_task_self(), vm_string_memory, vm_string_bytes_read); | |
| }); | |
| typedef llvm::DenseMap<ConstString, uint16_t> UndefinedNameToDescMap; | |
| typedef llvm::DenseMap<uint32_t, ConstString> SymbolIndexToName; | |
| UndefinedNameToDescMap undefined_name_to_desc; | |
| SymbolIndexToName reexport_shlib_needs_fixup; | |
| dyld_for_each_installed_shared_cache(^(dyld_shared_cache_t shared_cache) { | |
| uuid_t cache_uuid; | |
| dyld_shared_cache_copy_uuid(shared_cache, &cache_uuid); | |
| if (found_image) | |
| return; | |
| if (process_shared_cache_uuid.IsValid() && | |
| process_shared_cache_uuid != UUID(&cache_uuid, 16)) | |
| return; | |
| dyld_shared_cache_for_each_image(shared_cache, ^(dyld_image_t image) { | |
| uuid_t dsc_image_uuid; | |
| if (found_image) | |
| return; | |
| dyld_image_copy_uuid(image, &dsc_image_uuid); | |
| if (image_uuid != UUID(dsc_image_uuid, 16)) | |
| return; | |
| found_image = true; | |
| // Compute the size of the string table. We need to ask dyld for a | |
| // new SPI to avoid this step. | |
| dyld_image_local_nlist_content_4Symbolication( | |
| image, ^(const void *nlistStart, uint64_t nlistCount, | |
| const char *stringTable) { | |
| if (!nlistStart || !nlistCount) | |
| return; | |
| // The buffers passed here are valid only inside the block. | |
| // Use vm_read to make a cheap copy of them available for our | |
| // processing later. | |
| kern_return_t ret = | |
| vm_read(mach_task_self(), (vm_address_t)nlistStart, | |
| nlist_byte_size * nlistCount, &vm_nlist_memory, | |
| &vm_nlist_bytes_read); | |
| if (ret != KERN_SUCCESS) | |
| return; | |
| assert(vm_nlist_bytes_read == nlist_byte_size * nlistCount); | |
| // We don't know the size of the string table. It's cheaper | |
| // to map the whole VM region than to determine the size by | |
| // parsing all the nlist entries. | |
| vm_address_t string_address = (vm_address_t)stringTable; | |
| vm_size_t region_size; | |
| mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; | |
| vm_region_basic_info_data_t info; | |
| memory_object_name_t object; | |
| ret = vm_region_64(mach_task_self(), &string_address, | |
| ®ion_size, VM_REGION_BASIC_INFO_64, | |
| (vm_region_info_t)&info, &info_count, &object); | |
| if (ret != KERN_SUCCESS) | |
| return; | |
| ret = vm_read(mach_task_self(), (vm_address_t)stringTable, | |
| region_size - | |
| ((vm_address_t)stringTable - string_address), | |
| &vm_string_memory, &vm_string_bytes_read); | |
| if (ret != KERN_SUCCESS) | |
| return; | |
| nlist_buffer = (void *)vm_nlist_memory; | |
| string_table = (char *)vm_string_memory; | |
| nlist_count = nlistCount; | |
| }); | |
| }); | |
| }); | |
| if (nlist_buffer) { | |
| DataExtractor dsc_local_symbols_data(nlist_buffer, | |
| nlist_count * nlist_byte_size, | |
| byte_order, addr_byte_size); | |
| DataExtractor dsc_string_table_data(string_table, vm_string_bytes_read, | |
| byte_order, addr_byte_size); | |
| unmapped_local_symbols_found = nlist_count; | |
| // The normal nlist code cannot correctly size the Symbols | |
| // array, we need to allocate it here. | |
| sym = symtab.Resize( | |
| symtab_load_command.nsyms + m_dysymtab.nindirectsyms + | |
| unmapped_local_symbols_found - m_dysymtab.nlocalsym); | |
| num_syms = symtab.GetNumSymbols(); | |
| lldb::offset_t nlist_data_offset = 0; | |
| for (uint32_t nlist_index = 0; | |
| nlist_index < nlist_count; | |
| nlist_index++) { | |
| ///////////////////////////// | |
| { | |
| std::optional<struct nlist_64> nlist_maybe = | |
| ParseNList(dsc_local_symbols_data, nlist_data_offset, | |
| nlist_byte_size); | |
| if (!nlist_maybe) | |
| break; | |
| struct nlist_64 nlist = *nlist_maybe; | |
| SymbolType type = eSymbolTypeInvalid; | |
| const char *symbol_name = NULL; | |
| std::optional<llvm::StringRef> name = | |
| dsc_string_table_data.PeekCStr(nlist.n_strx); | |
| if (!name) { | |
| // No symbol should be NULL, even the symbols with no | |
| // string values should have an offset zero which | |
| // points to an empty C-string | |
| Debugger::ReportError(llvm::formatv( | |
| "DSC unmapped local symbol[{0}] has invalid or " | |
| "unterminated string table offset {1:x} in {2}, " | |
| "ignoring symbol", | |
| nlist_index, nlist.n_strx, | |
| module_sp->GetFileSpec().GetPath())); | |
| continue; | |
| } | |
| // The code below spells "no name" as a NULL pointer. | |
| if (!name->empty()) | |
| symbol_name = name->data(); | |
| const char *symbol_name_non_abi_mangled = NULL; | |
| SectionSP symbol_section; | |
| bool add_nlist = true; | |
| bool is_debug = ((nlist.n_type & N_STAB) != 0); | |
| bool demangled_is_synthesized = false; | |
| bool is_gsym = false; | |
| bool set_value = true; | |
| assert(sym_idx < num_syms); | |
| sym[sym_idx].SetDebug(is_debug); | |
| if (is_debug) { | |
| switch (nlist.n_type) { | |
| case N_GSYM: | |
| // global symbol: name,,NO_SECT,type,0 | |
| // Sometimes the N_GSYM value contains the address. | |
| // FIXME: In the .o files, we have a GSYM and a debug | |
| // symbol for all the ObjC data. They | |
| // have the same address, but we want to ensure that | |
| // we always find only the real symbol, 'cause we | |
| // don't currently correctly attribute the | |
| // GSYM one to the ObjCClass/Ivar/MetaClass | |
| // symbol type. This is a temporary hack to make | |
| // sure the ObjectiveC symbols get treated correctly. | |
| // To do this right, we should coalesce all the GSYM | |
| // & global symbols that have the same address. | |
| is_gsym = true; | |
| sym[sym_idx].SetExternal(true); | |
| if (TryParseV2ObjCMetadataSymbol( | |
| symbol_name, symbol_name_non_abi_mangled, | |
| type)) { | |
| demangled_is_synthesized = true; | |
| } else { | |
| if (nlist.n_value != 0) | |
| symbol_section = section_info.GetSection( | |
| nlist.n_sect, nlist.n_value); | |
| type = eSymbolTypeData; | |
| } | |
| break; | |
| case N_FNAME: | |
| // procedure name (f77 kludge): name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_FUN: | |
| // procedure: name,,n_sect,linenumber,address | |
| if (symbol_name) { | |
| type = eSymbolTypeCode; | |
| symbol_section = section_info.GetSection( | |
| nlist.n_sect, nlist.n_value); | |
| N_FUN_addr_to_sym_idx.insert( | |
| std::make_pair(nlist.n_value, sym_idx)); | |
| // We use the current number of symbols in the | |
| // symbol table in lieu of using nlist_idx in case | |
| // we ever start trimming entries out | |
| N_FUN_indexes.push_back(sym_idx); | |
| } else { | |
| type = eSymbolTypeCompiler; | |
| if (!N_FUN_indexes.empty()) { | |
| // Copy the size of the function into the | |
| // original | |
| // STAB entry so we don't have | |
| // to hunt for it later | |
| symtab.SymbolAtIndex(N_FUN_indexes.back()) | |
| ->SetByteSize(nlist.n_value); | |
| N_FUN_indexes.pop_back(); | |
| // We don't really need the end function STAB as | |
| // it contains the size which we already placed | |
| // with the original symbol, so don't add it if | |
| // we want a minimal symbol table | |
| add_nlist = false; | |
| } | |
| } | |
| break; | |
| case N_STSYM: | |
| // static symbol: name,,n_sect,type,address | |
| N_STSYM_addr_to_sym_idx.insert( | |
| std::make_pair(nlist.n_value, sym_idx)); | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| if (symbol_name && symbol_name[0]) { | |
| type = ObjectFile::GetSymbolTypeFromName( | |
| symbol_name + 1, eSymbolTypeData); | |
| } | |
| break; | |
| case N_LCSYM: | |
| // .lcomm symbol: name,,n_sect,type,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| type = eSymbolTypeCommonBlock; | |
| break; | |
| case N_BNSYM: | |
| // We use the current number of symbols in the symbol | |
| // table in lieu of using nlist_idx in case we ever | |
| // start trimming entries out Skip these if we want | |
| // minimal symbol tables | |
| add_nlist = false; | |
| break; | |
| case N_ENSYM: | |
| // Set the size of the N_BNSYM to the terminating | |
| // index of this N_ENSYM so that we can always skip | |
| // the entire symbol if we need to navigate more | |
| // quickly at the source level when parsing STABS | |
| // Skip these if we want minimal symbol tables | |
| add_nlist = false; | |
| break; | |
| case N_OPT: | |
| // emitted with gcc2_compiled and in gcc source | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_RSYM: | |
| // register sym: name,,NO_SECT,type,register | |
| type = eSymbolTypeVariable; | |
| break; | |
| case N_SLINE: | |
| // src line: 0,,n_sect,linenumber,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| type = eSymbolTypeLineEntry; | |
| break; | |
| case N_SSYM: | |
| // structure elt: name,,NO_SECT,type,struct_offset | |
| type = eSymbolTypeVariableType; | |
| break; | |
| case N_SO: | |
| // source file name | |
| type = eSymbolTypeSourceFile; | |
| if (symbol_name == NULL) { | |
| add_nlist = false; | |
| if (N_SO_index != UINT32_MAX) { | |
| // Set the size of the N_SO to the terminating | |
| // index of this N_SO so that we can always skip | |
| // the entire N_SO if we need to navigate more | |
| // quickly at the source level when parsing STABS | |
| symbol_ptr = symtab.SymbolAtIndex(N_SO_index); | |
| symbol_ptr->SetByteSize(sym_idx); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| } | |
| N_NSYM_indexes.clear(); | |
| N_INCL_indexes.clear(); | |
| N_BRAC_indexes.clear(); | |
| N_COMM_indexes.clear(); | |
| N_FUN_indexes.clear(); | |
| N_SO_index = UINT32_MAX; | |
| } else { | |
| // We use the current number of symbols in the | |
| // symbol table in lieu of using nlist_idx in case | |
| // we ever start trimming entries out | |
| const bool N_SO_has_full_path = symbol_name[0] == '/'; | |
| if (N_SO_has_full_path) { | |
| if ((N_SO_index == sym_idx - 1) && | |
| ((sym_idx - 1) < num_syms)) { | |
| // We have two consecutive N_SO entries where | |
| // the first contains a directory and the | |
| // second contains a full path. | |
| sym[sym_idx - 1].GetMangled().SetValue( | |
| ConstString(symbol_name)); | |
| m_nlist_idx_to_sym_idx[nlist_idx] = sym_idx - 1; | |
| add_nlist = false; | |
| } else { | |
| // This is the first entry in a N_SO that | |
| // contains a directory or | |
| // a full path to the source file | |
| N_SO_index = sym_idx; | |
| } | |
| } else if ((N_SO_index == sym_idx - 1) && | |
| ((sym_idx - 1) < num_syms)) { | |
| // This is usually the second N_SO entry that | |
| // contains just the filename, so here we combine | |
| // it with the first one if we are minimizing the | |
| // symbol table | |
| const char *so_path = sym[sym_idx - 1] | |
| .GetMangled() | |
| .GetDemangledName() | |
| .AsCString(); | |
| if (so_path && so_path[0]) { | |
| std::string full_so_path(so_path); | |
| const size_t double_slash_pos = | |
| full_so_path.find("//"); | |
| if (double_slash_pos != std::string::npos) { | |
| // The linker has been generating bad N_SO | |
| // entries with doubled up paths | |
| // in the format "%s%s" where the first | |
| // string in the DW_AT_comp_dir, and the | |
| // second is the directory for the source | |
| // file so you end up with a path that looks | |
| // like "/tmp/src//tmp/src/" | |
| FileSpec so_dir(so_path); | |
| if (!FileSystem::Instance().Exists(so_dir)) { | |
| so_dir.SetFile( | |
| &full_so_path[double_slash_pos + 1], | |
| FileSpec::Style::native); | |
| if (FileSystem::Instance().Exists(so_dir)) { | |
| // Trim off the incorrect path | |
| full_so_path.erase(0, double_slash_pos + 1); | |
| } | |
| } | |
| } | |
| if (*full_so_path.rbegin() != '/') | |
| full_so_path += '/'; | |
| full_so_path += symbol_name; | |
| sym[sym_idx - 1].GetMangled().SetValue( | |
| ConstString(full_so_path.c_str())); | |
| add_nlist = false; | |
| m_nlist_idx_to_sym_idx[nlist_idx] = sym_idx - 1; | |
| } | |
| } else { | |
| // This could be a relative path to a N_SO | |
| N_SO_index = sym_idx; | |
| } | |
| } | |
| break; | |
| case N_OSO: | |
| // object file name: name,,0,0,st_mtime | |
| type = eSymbolTypeObjectFile; | |
| break; | |
| case N_LSYM: | |
| // local sym: name,,NO_SECT,type,offset | |
| type = eSymbolTypeLocal; | |
| break; | |
| // INCL scopes | |
| case N_BINCL: | |
| // include file beginning: name,,NO_SECT,0,sum We use | |
| // the current number of symbols in the symbol table | |
| // in lieu of using nlist_idx in case we ever start | |
| // trimming entries out | |
| N_INCL_indexes.push_back(sym_idx); | |
| type = eSymbolTypeScopeBegin; | |
| break; | |
| case N_EINCL: | |
| // include file end: name,,NO_SECT,0,0 | |
| // Set the size of the N_BINCL to the terminating | |
| // index of this N_EINCL so that we can always skip | |
| // the entire symbol if we need to navigate more | |
| // quickly at the source level when parsing STABS | |
| if (!N_INCL_indexes.empty()) { | |
| symbol_ptr = | |
| symtab.SymbolAtIndex(N_INCL_indexes.back()); | |
| symbol_ptr->SetByteSize(sym_idx + 1); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| N_INCL_indexes.pop_back(); | |
| } | |
| type = eSymbolTypeScopeEnd; | |
| break; | |
| case N_SOL: | |
| // #included file name: name,,n_sect,0,address | |
| type = eSymbolTypeHeaderFile; | |
| // We currently don't use the header files on darwin | |
| add_nlist = false; | |
| break; | |
| case N_PARAMS: | |
| // compiler parameters: name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_VERSION: | |
| // compiler version: name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_OLEVEL: | |
| // compiler -O level: name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_PSYM: | |
| // parameter: name,,NO_SECT,type,offset | |
| type = eSymbolTypeVariable; | |
| break; | |
| case N_ENTRY: | |
| // alternate entry: name,,n_sect,linenumber,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| type = eSymbolTypeLineEntry; | |
| break; | |
| // Left and Right Braces | |
| case N_LBRAC: | |
| // left bracket: 0,,NO_SECT,nesting level,address We | |
| // use the current number of symbols in the symbol | |
| // table in lieu of using nlist_idx in case we ever | |
| // start trimming entries out | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| N_BRAC_indexes.push_back(sym_idx); | |
| type = eSymbolTypeScopeBegin; | |
| break; | |
| case N_RBRAC: | |
| // right bracket: 0,,NO_SECT,nesting level,address | |
| // Set the size of the N_LBRAC to the terminating | |
| // index of this N_RBRAC so that we can always skip | |
| // the entire symbol if we need to navigate more | |
| // quickly at the source level when parsing STABS | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| if (!N_BRAC_indexes.empty()) { | |
| symbol_ptr = | |
| symtab.SymbolAtIndex(N_BRAC_indexes.back()); | |
| symbol_ptr->SetByteSize(sym_idx + 1); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| N_BRAC_indexes.pop_back(); | |
| } | |
| type = eSymbolTypeScopeEnd; | |
| break; | |
| case N_EXCL: | |
| // deleted include file: name,,NO_SECT,0,sum | |
| type = eSymbolTypeHeaderFile; | |
| break; | |
| // COMM scopes | |
| case N_BCOMM: | |
| // begin common: name,,NO_SECT,0,0 | |
| // We use the current number of symbols in the symbol | |
| // table in lieu of using nlist_idx in case we ever | |
| // start trimming entries out | |
| type = eSymbolTypeScopeBegin; | |
| N_COMM_indexes.push_back(sym_idx); | |
| break; | |
| case N_ECOML: | |
| // end common (local name): 0,,n_sect,0,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| // Fall through | |
| case N_ECOMM: | |
| // end common: name,,n_sect,0,0 | |
| // Set the size of the N_BCOMM to the terminating | |
| // index of this N_ECOMM/N_ECOML so that we can | |
| // always skip the entire symbol if we need to | |
| // navigate more quickly at the source level when | |
| // parsing STABS | |
| if (!N_COMM_indexes.empty()) { | |
| symbol_ptr = | |
| symtab.SymbolAtIndex(N_COMM_indexes.back()); | |
| symbol_ptr->SetByteSize(sym_idx + 1); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| N_COMM_indexes.pop_back(); | |
| } | |
| type = eSymbolTypeScopeEnd; | |
| break; | |
| case N_LENG: | |
| // second stab entry with length information | |
| type = eSymbolTypeAdditional; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else { | |
| // uint8_t n_pext = N_PEXT & nlist.n_type; | |
| uint8_t n_type = N_TYPE & nlist.n_type; | |
| sym[sym_idx].SetExternal((N_EXT & nlist.n_type) != 0); | |
| switch (n_type) { | |
| case N_INDR: { | |
| std::optional<llvm::StringRef> reexport_name_str = | |
| strtab_data.PeekCStr(nlist.n_value); | |
| if (reexport_name_str && !reexport_name_str->empty()) { | |
| type = eSymbolTypeReExported; | |
| ConstString reexport_name( | |
| reexport_name_str->drop_front( | |
| reexport_name_str->front() == '_' ? 1 : 0)); | |
| sym[sym_idx].SetReExportedSymbolName(reexport_name); | |
| set_value = false; | |
| reexport_shlib_needs_fixup[sym_idx] = reexport_name; | |
| indirect_symbol_names.insert(ConstString( | |
| symbol_name + ((symbol_name[0] == '_') ? 1 : 0))); | |
| } else | |
| type = eSymbolTypeUndefined; | |
| } break; | |
| case N_UNDF: | |
| if (symbol_name && symbol_name[0]) { | |
| ConstString undefined_name( | |
| symbol_name + ((symbol_name[0] == '_') ? 1 : 0)); | |
| undefined_name_to_desc[undefined_name] = nlist.n_desc; | |
| } | |
| // Fall through | |
| case N_PBUD: | |
| type = eSymbolTypeUndefined; | |
| break; | |
| case N_ABS: | |
| type = eSymbolTypeAbsolute; | |
| break; | |
| case N_SECT: { | |
| symbol_section = section_info.GetSection(nlist.n_sect, | |
| nlist.n_value); | |
| if (symbol_section == NULL) { | |
| // TODO: warn about this? | |
| add_nlist = false; | |
| break; | |
| } | |
| if (TEXT_eh_frame_sectID == nlist.n_sect) { | |
| type = eSymbolTypeException; | |
| } else { | |
| uint32_t section_type = | |
| symbol_section->Get() & SECTION_TYPE; | |
| switch (section_type) { | |
| case S_CSTRING_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only literal C strings | |
| case S_4BYTE_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only 4 byte literals | |
| case S_8BYTE_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only 8 byte literals | |
| case S_LITERAL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only pointers to literals | |
| case S_NON_LAZY_SYMBOL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only non-lazy symbol | |
| // pointers | |
| case S_LAZY_SYMBOL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only lazy symbol pointers | |
| case S_SYMBOL_STUBS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only symbol stubs, byte | |
| // size of stub in the reserved2 field | |
| case S_MOD_INIT_FUNC_POINTERS: | |
| type = eSymbolTypeCode; | |
| break; // section with only function pointers for | |
| // initialization | |
| case S_MOD_TERM_FUNC_POINTERS: | |
| type = eSymbolTypeCode; | |
| break; // section with only function pointers for | |
| // termination | |
| case S_INTERPOSING: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only pairs of function | |
| // pointers for interposing | |
| case S_16BYTE_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only 16 byte literals | |
| case S_DTRACE_DOF: | |
| type = eSymbolTypeInstrumentation; | |
| break; | |
| case S_LAZY_DYLIB_SYMBOL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; | |
| default: | |
| switch (symbol_section->GetType()) { | |
| case lldb::eSectionTypeCode: | |
| type = eSymbolTypeCode; | |
| break; | |
| case eSectionTypeData: | |
| case eSectionTypeDataCString: // Inlined C string | |
| // data | |
| case eSectionTypeDataCStringPointers: // Pointers | |
| // to C | |
| // string | |
| // data | |
| case eSectionTypeDataSymbolAddress: // Address of | |
| // a symbol in | |
| // the symbol | |
| // table | |
| case eSectionTypeData4: | |
| case eSectionTypeData8: | |
| case eSectionTypeData16: | |
| type = eSymbolTypeData; | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| if (type == eSymbolTypeInvalid) { | |
| llvm::StringRef symbol_sect_name = | |
| symbol_section->GetName(); | |
| if (symbol_section->IsDescendant( | |
| text_section_sp.get())) { | |
| if (symbol_section->IsClear( | |
| S_ATTR_PURE_INSTRUCTIONS | | |
| S_ATTR_SELF_MODIFYING_CODE | | |
| S_ATTR_SOME_INSTRUCTIONS)) | |
| type = eSymbolTypeData; | |
| else | |
| type = eSymbolTypeCode; | |
| } else if (symbol_section->IsDescendant( | |
| data_section_sp.get()) || | |
| symbol_section->IsDescendant( | |
| data_dirty_section_sp.get()) || | |
| symbol_section->IsDescendant( | |
| data_const_section_sp.get())) { | |
| if (symbol_sect_name.starts_with("__objc")) { | |
| type = eSymbolTypeRuntime; | |
| if (TryParseV2ObjCMetadataSymbol( | |
| symbol_name, | |
| symbol_name_non_abi_mangled, type)) | |
| demangled_is_synthesized = true; | |
| } else if (symbol_sect_name.starts_with("__gcc_except_tab")) { | |
| type = eSymbolTypeException; | |
| } else { | |
| type = eSymbolTypeData; | |
| } | |
| } else if (symbol_sect_name.starts_with("__IMPORT")) | |
| type = eSymbolTypeTrampoline; | |
| } else if (symbol_section->IsDescendant( | |
| objc_section_sp.get())) { | |
| type = eSymbolTypeRuntime; | |
| if (symbol_name && symbol_name[0] == '.') { | |
| llvm::StringRef symbol_name_ref(symbol_name); | |
| llvm::StringRef | |
| g_objc_v1_prefix_class(".objc_class_name_"); | |
| if (symbol_name_ref.starts_with( | |
| g_objc_v1_prefix_class)) { | |
| symbol_name_non_abi_mangled = symbol_name; | |
| symbol_name = symbol_name + | |
| g_objc_v1_prefix_class.size(); | |
| type = eSymbolTypeObjCClass; | |
| demangled_is_synthesized = true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } break; | |
| } | |
| } | |
| if (add_nlist) { | |
| uint64_t symbol_value = nlist.n_value; | |
| if (symbol_name_non_abi_mangled) { | |
| sym[sym_idx].GetMangled().SetMangledName( | |
| ConstString(symbol_name_non_abi_mangled)); | |
| sym[sym_idx].GetMangled().SetDemangledName( | |
| ConstString(symbol_name)); | |
| } else { | |
| if (symbol_name && symbol_name[0] == '_') { | |
| symbol_name++; // Skip the leading underscore | |
| } | |
| if (symbol_name) { | |
| ConstString const_symbol_name(symbol_name); | |
| sym[sym_idx].GetMangled().SetValue(const_symbol_name); | |
| if (is_gsym && is_debug) { | |
| const char *gsym_name = | |
| sym[sym_idx] | |
| .GetMangled() | |
| .GetName(Mangled::ePreferMangled) | |
| .GetCString(); | |
| if (gsym_name) | |
| N_GSYM_name_to_sym_idx[gsym_name] = sym_idx; | |
| } | |
| } | |
| } | |
| if (symbol_section) { | |
| const addr_t section_file_addr = | |
| symbol_section->GetFileAddress(); | |
| symbol_value -= section_file_addr; | |
| } | |
| if (is_debug == false) { | |
| if (type == eSymbolTypeCode) { | |
| // See if we can find a N_FUN entry for any code | |
| // symbols. If we do find a match, and the name | |
| // matches, then we can merge the two into just the | |
| // function symbol to avoid duplicate entries in | |
| // the symbol table | |
| auto range = | |
| N_FUN_addr_to_sym_idx.equal_range(nlist.n_value); | |
| if (range.first != range.second) { | |
| bool found_it = false; | |
| for (auto pos = range.first; pos != range.second; | |
| ++pos) { | |
| if (sym[sym_idx].GetMangled().GetName( | |
| Mangled::ePreferMangled) == | |
| sym[pos->second].GetMangled().GetName( | |
| Mangled::ePreferMangled)) { | |
| m_nlist_idx_to_sym_idx[nlist_idx] = pos->second; | |
| // We just need the flags from the linker | |
| // symbol, so put these flags | |
| // into the N_FUN flags to avoid duplicate | |
| // symbols in the symbol table | |
| sym[pos->second].SetExternal( | |
| sym[sym_idx].IsExternal()); | |
| sym[pos->second].SetFlags(nlist.n_type << 16 | | |
| nlist.n_desc); | |
| if (resolver_addresses.find(nlist.n_value) != | |
| resolver_addresses.end()) | |
| sym[pos->second].SetType(eSymbolTypeResolver); | |
| sym[sym_idx].Clear(); | |
| found_it = true; | |
| break; | |
| } | |
| } | |
| if (found_it) | |
| continue; | |
| } else { | |
| if (resolver_addresses.find(nlist.n_value) != | |
| resolver_addresses.end()) | |
| type = eSymbolTypeResolver; | |
| } | |
| } else if (type == eSymbolTypeData || | |
| type == eSymbolTypeObjCClass || | |
| type == eSymbolTypeObjCMetaClass || | |
| type == eSymbolTypeObjCIVar) { | |
| // See if we can find a N_STSYM entry for any data | |
| // symbols. If we do find a match, and the name | |
| // matches, then we can merge the two into just the | |
| // Static symbol to avoid duplicate entries in the | |
| // symbol table | |
| auto range = N_STSYM_addr_to_sym_idx.equal_range( | |
| nlist.n_value); | |
| if (range.first != range.second) { | |
| bool found_it = false; | |
| for (auto pos = range.first; pos != range.second; | |
| ++pos) { | |
| if (sym[sym_idx].GetMangled().GetName( | |
| Mangled::ePreferMangled) == | |
| sym[pos->second].GetMangled().GetName( | |
| Mangled::ePreferMangled)) { | |
| m_nlist_idx_to_sym_idx[nlist_idx] = pos->second; | |
| // We just need the flags from the linker | |
| // symbol, so put these flags | |
| // into the N_STSYM flags to avoid duplicate | |
| // symbols in the symbol table | |
| sym[pos->second].SetExternal( | |
| sym[sym_idx].IsExternal()); | |
| sym[pos->second].SetFlags(nlist.n_type << 16 | | |
| nlist.n_desc); | |
| sym[sym_idx].Clear(); | |
| found_it = true; | |
| break; | |
| } | |
| } | |
| if (found_it) | |
| continue; | |
| } else { | |
| const char *gsym_name = | |
| sym[sym_idx] | |
| .GetMangled() | |
| .GetName(Mangled::ePreferMangled) | |
| .GetCString(); | |
| if (gsym_name) { | |
| // Combine N_GSYM stab entries with the non | |
| // stab symbol | |
| ConstNameToSymbolIndexMap::const_iterator pos = | |
| N_GSYM_name_to_sym_idx.find(gsym_name); | |
| if (pos != N_GSYM_name_to_sym_idx.end()) { | |
| const uint32_t GSYM_sym_idx = pos->second; | |
| m_nlist_idx_to_sym_idx[nlist_idx] = | |
| GSYM_sym_idx; | |
| // Copy the address, because often the N_GSYM | |
| // address has an invalid address of zero | |
| // when the global is a common symbol | |
| sym[GSYM_sym_idx].GetAddressRef() = | |
| Address(symbol_section, symbol_value); | |
| add_symbol_addr(sym[GSYM_sym_idx] | |
| .GetAddress() | |
| .GetFileAddress()); | |
| // We just need the flags from the linker | |
| // symbol, so put these flags | |
| // into the N_GSYM flags to avoid duplicate | |
| // symbols in the symbol table | |
| sym[GSYM_sym_idx].SetFlags(nlist.n_type << 16 | | |
| nlist.n_desc); | |
| sym[sym_idx].Clear(); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| sym[sym_idx].SetID(nlist_idx); | |
| sym[sym_idx].SetType(type); | |
| if (set_value) { | |
| sym[sym_idx].GetAddressRef() = | |
| Address(symbol_section, symbol_value); | |
| add_symbol_addr( | |
| sym[sym_idx].GetAddress().GetFileAddress()); | |
| } | |
| sym[sym_idx].SetFlags(nlist.n_type << 16 | nlist.n_desc); | |
| if (demangled_is_synthesized) | |
| sym[sym_idx].SetDemangledNameIsSynthesized(true); | |
| ++sym_idx; | |
| } else { | |
| sym[sym_idx].Clear(); | |
| } | |
| } | |
| ///////////////////////////// | |
| } | |
| } | |
| for (const auto &pos : reexport_shlib_needs_fixup) { | |
| const auto undef_pos = undefined_name_to_desc.find(pos.second); | |
| if (undef_pos != undefined_name_to_desc.end()) { | |
| const uint8_t dylib_ordinal = | |
| llvm::MachO::GET_LIBRARY_ORDINAL(undef_pos->second); | |
| if (dylib_ordinal > 0 && dylib_ordinal < dylib_files.GetSize()) | |
| sym[pos.first].SetReExportedSymbolSharedLibrary( | |
| dylib_files.GetFileSpecAtIndex(dylib_ordinal - 1)); | |
| } | |
| } | |
| } | |
| #endif | |
| lldb::offset_t nlist_data_offset = 0; | |
| if (nlist_data.GetByteSize() > 0) { | |
| const uint64_t max_nsyms = nlist_data.GetByteSize() / nlist_byte_size; | |
| const uint64_t max_nindirectsyms = | |
| indirect_symbol_index_data.GetByteSize() / sizeof(uint32_t); | |
| // If the sym array was not created while parsing the DSC unmapped | |
| // symbols, create it now. | |
| if (sym == nullptr) { | |
| sym = symtab.Resize( | |
| std::min<uint64_t>(symtab_load_command.nsyms, max_nsyms) + | |
| std::min<uint64_t>(m_dysymtab.nindirectsyms, max_nindirectsyms)); | |
| num_syms = symtab.GetNumSymbols(); | |
| } | |
| if (unmapped_local_symbols_found) { | |
| assert(m_dysymtab.ilocalsym == 0); | |
| nlist_data_offset += (m_dysymtab.nlocalsym * nlist_byte_size); | |
| nlist_idx = m_dysymtab.nlocalsym; | |
| } else { | |
| nlist_idx = 0; | |
| } | |
| typedef llvm::DenseMap<ConstString, uint16_t> UndefinedNameToDescMap; | |
| typedef llvm::DenseMap<uint32_t, ConstString> SymbolIndexToName; | |
| UndefinedNameToDescMap undefined_name_to_desc; | |
| SymbolIndexToName reexport_shlib_needs_fixup; | |
| // Symtab parsing is a huge mess. Everything is entangled and the code | |
| // requires access to a ridiculous amount of variables. LLDB depends | |
| // heavily on the proper merging of symbols and to get that right we need | |
| // to make sure we have parsed all the debug symbols first. Therefore we | |
| // invoke the lambda twice, once to parse only the debug symbols and then | |
| // once more to parse the remaining symbols. | |
| auto ParseSymbolLambda = [&](struct nlist_64 &nlist, uint32_t nlist_idx, | |
| bool debug_only) { | |
| const bool is_debug = ((nlist.n_type & N_STAB) != 0); | |
| if (is_debug != debug_only) | |
| return true; | |
| const char *symbol_name_non_abi_mangled = nullptr; | |
| const char *symbol_name = nullptr; | |
| if (have_strtab_data) { | |
| std::optional<llvm::StringRef> name = | |
| strtab_data.PeekCStr(nlist.n_strx); | |
| if (!name) { | |
| // No symbol should be NULL, even the symbols with no string values | |
| // should have an offset zero which points to an empty C-string | |
| Debugger::ReportError(llvm::formatv( | |
| "symbol[{0}] has invalid or unterminated string table offset " | |
| "{1:x} in {2}, ignoring symbol", | |
| nlist_idx, nlist.n_strx, module_sp->GetFileSpec().GetPath())); | |
| return true; | |
| } | |
| // The code below spells "no name" as a nullptr. | |
| if (!name->empty()) | |
| symbol_name = name->data(); | |
| } else { | |
| const addr_t str_addr = strtab_addr + nlist.n_strx; | |
| Status str_error; | |
| if (process->ReadCStringFromMemory(str_addr, memory_symbol_name, | |
| str_error)) | |
| symbol_name = memory_symbol_name.c_str(); | |
| } | |
| SymbolType type = eSymbolTypeInvalid; | |
| SectionSP symbol_section; | |
| bool add_nlist = true; | |
| bool is_gsym = false; | |
| bool demangled_is_synthesized = false; | |
| bool set_value = true; | |
| assert(sym_idx < num_syms); | |
| sym[sym_idx].SetDebug(is_debug); | |
| if (is_debug) { | |
| switch (nlist.n_type) { | |
| case N_GSYM: { | |
| // global symbol: name,,NO_SECT,type,0 | |
| // Sometimes the N_GSYM value contains the address. | |
| // FIXME: In the .o files, we have a GSYM and a debug symbol for all | |
| // the ObjC data. They | |
| // have the same address, but we want to ensure that we always find | |
| // only the real symbol, 'cause we don't currently correctly | |
| // attribute the GSYM one to the ObjCClass/Ivar/MetaClass symbol | |
| // type. This is a temporary hack to make sure the ObjectiveC | |
| // symbols get treated correctly. To do this right, we should | |
| // coalesce all the GSYM & global symbols that have the same | |
| // address. | |
| is_gsym = true; | |
| sym[sym_idx].SetExternal(true); | |
| if (TryParseV2ObjCMetadataSymbol(symbol_name, | |
| symbol_name_non_abi_mangled, type)) { | |
| demangled_is_synthesized = true; | |
| } else { | |
| if (nlist.n_value != 0) | |
| symbol_section = | |
| section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| type = eSymbolTypeData; | |
| } | |
| } break; | |
| case N_FNAME: | |
| // procedure name (f77 kludge): name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_FUN: | |
| // procedure: name,,n_sect,linenumber,address | |
| if (symbol_name) { | |
| type = eSymbolTypeCode; | |
| symbol_section = | |
| section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| N_FUN_addr_to_sym_idx.insert( | |
| std::make_pair(nlist.n_value, sym_idx)); | |
| // We use the current number of symbols in the symbol table in | |
| // lieu of using nlist_idx in case we ever start trimming entries | |
| // out | |
| N_FUN_indexes.push_back(sym_idx); | |
| } else { | |
| type = eSymbolTypeCompiler; | |
| if (!N_FUN_indexes.empty()) { | |
| // Copy the size of the function into the original STAB entry | |
| // so we don't have to hunt for it later | |
| symtab.SymbolAtIndex(N_FUN_indexes.back()) | |
| ->SetByteSize(nlist.n_value); | |
| N_FUN_indexes.pop_back(); | |
| // We don't really need the end function STAB as it contains | |
| // the size which we already placed with the original symbol, | |
| // so don't add it if we want a minimal symbol table | |
| add_nlist = false; | |
| } | |
| } | |
| break; | |
| case N_STSYM: | |
| // static symbol: name,,n_sect,type,address | |
| N_STSYM_addr_to_sym_idx.insert( | |
| std::make_pair(nlist.n_value, sym_idx)); | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| if (symbol_name && symbol_name[0]) { | |
| type = ObjectFile::GetSymbolTypeFromName(symbol_name + 1, | |
| eSymbolTypeData); | |
| } | |
| break; | |
| case N_LCSYM: | |
| // .lcomm symbol: name,,n_sect,type,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| type = eSymbolTypeCommonBlock; | |
| break; | |
| case N_BNSYM: | |
| // We use the current number of symbols in the symbol table in lieu | |
| // of using nlist_idx in case we ever start trimming entries out | |
| // Skip these if we want minimal symbol tables | |
| add_nlist = false; | |
| break; | |
| case N_ENSYM: | |
| // Set the size of the N_BNSYM to the terminating index of this | |
| // N_ENSYM so that we can always skip the entire symbol if we need | |
| // to navigate more quickly at the source level when parsing STABS | |
| // Skip these if we want minimal symbol tables | |
| add_nlist = false; | |
| break; | |
| case N_OPT: | |
| // emitted with gcc2_compiled and in gcc source | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_RSYM: | |
| // register sym: name,,NO_SECT,type,register | |
| type = eSymbolTypeVariable; | |
| break; | |
| case N_SLINE: | |
| // src line: 0,,n_sect,linenumber,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| type = eSymbolTypeLineEntry; | |
| break; | |
| case N_SSYM: | |
| // structure elt: name,,NO_SECT,type,struct_offset | |
| type = eSymbolTypeVariableType; | |
| break; | |
| case N_SO: | |
| // source file name | |
| type = eSymbolTypeSourceFile; | |
| if (symbol_name == nullptr) { | |
| add_nlist = false; | |
| if (N_SO_index != UINT32_MAX) { | |
| // Set the size of the N_SO to the terminating index of this | |
| // N_SO so that we can always skip the entire N_SO if we need | |
| // to navigate more quickly at the source level when parsing | |
| // STABS | |
| symbol_ptr = symtab.SymbolAtIndex(N_SO_index); | |
| symbol_ptr->SetByteSize(sym_idx); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| } | |
| N_NSYM_indexes.clear(); | |
| N_INCL_indexes.clear(); | |
| N_BRAC_indexes.clear(); | |
| N_COMM_indexes.clear(); | |
| N_FUN_indexes.clear(); | |
| N_SO_index = UINT32_MAX; | |
| } else { | |
| // We use the current number of symbols in the symbol table in | |
| // lieu of using nlist_idx in case we ever start trimming entries | |
| // out | |
| const bool N_SO_has_full_path = symbol_name[0] == '/'; | |
| if (N_SO_has_full_path) { | |
| if ((N_SO_index == sym_idx - 1) && ((sym_idx - 1) < num_syms)) { | |
| // We have two consecutive N_SO entries where the first | |
| // contains a directory and the second contains a full path. | |
| sym[sym_idx - 1].GetMangled().SetValue( | |
| ConstString(symbol_name)); | |
| m_nlist_idx_to_sym_idx[nlist_idx] = sym_idx - 1; | |
| add_nlist = false; | |
| } else { | |
| // This is the first entry in a N_SO that contains a | |
| // directory or a full path to the source file | |
| N_SO_index = sym_idx; | |
| } | |
| } else if ((N_SO_index == sym_idx - 1) && | |
| ((sym_idx - 1) < num_syms)) { | |
| // This is usually the second N_SO entry that contains just the | |
| // filename, so here we combine it with the first one if we are | |
| // minimizing the symbol table | |
| llvm::StringRef so_path = sym[sym_idx - 1] | |
| .GetMangled() | |
| .GetDemangledName() | |
| .GetStringRef(); | |
| if (!so_path.empty()) { | |
| std::string full_so_path(so_path); | |
| const size_t double_slash_pos = full_so_path.find("//"); | |
| if (double_slash_pos != std::string::npos) { | |
| // The linker has been generating bad N_SO entries with | |
| // doubled up paths in the format "%s%s" where the first | |
| // string in the DW_AT_comp_dir, and the second is the | |
| // directory for the source file so you end up with a path | |
| // that looks like "/tmp/src//tmp/src/" | |
| FileSpec so_dir(so_path); | |
| if (!FileSystem::Instance().Exists(so_dir)) { | |
| so_dir.SetFile(&full_so_path[double_slash_pos + 1], | |
| FileSpec::Style::native); | |
| if (FileSystem::Instance().Exists(so_dir)) { | |
| // Trim off the incorrect path | |
| full_so_path.erase(0, double_slash_pos + 1); | |
| } | |
| } | |
| } | |
| if (*full_so_path.rbegin() != '/') | |
| full_so_path += '/'; | |
| full_so_path += symbol_name; | |
| sym[sym_idx - 1].GetMangled().SetValue( | |
| ConstString(full_so_path.c_str())); | |
| add_nlist = false; | |
| m_nlist_idx_to_sym_idx[nlist_idx] = sym_idx - 1; | |
| } | |
| } else { | |
| // This could be a relative path to a N_SO | |
| N_SO_index = sym_idx; | |
| } | |
| } | |
| break; | |
| case N_OSO: | |
| // object file name: name,,0,0,st_mtime | |
| type = eSymbolTypeObjectFile; | |
| break; | |
| case N_LSYM: | |
| // local sym: name,,NO_SECT,type,offset | |
| type = eSymbolTypeLocal; | |
| break; | |
| // INCL scopes | |
| case N_BINCL: | |
| // include file beginning: name,,NO_SECT,0,sum We use the current | |
| // number of symbols in the symbol table in lieu of using nlist_idx | |
| // in case we ever start trimming entries out | |
| N_INCL_indexes.push_back(sym_idx); | |
| type = eSymbolTypeScopeBegin; | |
| break; | |
| case N_EINCL: | |
| // include file end: name,,NO_SECT,0,0 | |
| // Set the size of the N_BINCL to the terminating index of this | |
| // N_EINCL so that we can always skip the entire symbol if we need | |
| // to navigate more quickly at the source level when parsing STABS | |
| if (!N_INCL_indexes.empty()) { | |
| symbol_ptr = symtab.SymbolAtIndex(N_INCL_indexes.back()); | |
| symbol_ptr->SetByteSize(sym_idx + 1); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| N_INCL_indexes.pop_back(); | |
| } | |
| type = eSymbolTypeScopeEnd; | |
| break; | |
| case N_SOL: | |
| // #included file name: name,,n_sect,0,address | |
| type = eSymbolTypeHeaderFile; | |
| // We currently don't use the header files on darwin | |
| add_nlist = false; | |
| break; | |
| case N_PARAMS: | |
| // compiler parameters: name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_VERSION: | |
| // compiler version: name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_OLEVEL: | |
| // compiler -O level: name,,NO_SECT,0,0 | |
| type = eSymbolTypeCompiler; | |
| break; | |
| case N_PSYM: | |
| // parameter: name,,NO_SECT,type,offset | |
| type = eSymbolTypeVariable; | |
| break; | |
| case N_ENTRY: | |
| // alternate entry: name,,n_sect,linenumber,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| type = eSymbolTypeLineEntry; | |
| break; | |
| // Left and Right Braces | |
| case N_LBRAC: | |
| // left bracket: 0,,NO_SECT,nesting level,address We use the | |
| // current number of symbols in the symbol table in lieu of using | |
| // nlist_idx in case we ever start trimming entries out | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| N_BRAC_indexes.push_back(sym_idx); | |
| type = eSymbolTypeScopeBegin; | |
| break; | |
| case N_RBRAC: | |
| // right bracket: 0,,NO_SECT,nesting level,address Set the size of | |
| // the N_LBRAC to the terminating index of this N_RBRAC so that we | |
| // can always skip the entire symbol if we need to navigate more | |
| // quickly at the source level when parsing STABS | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| if (!N_BRAC_indexes.empty()) { | |
| symbol_ptr = symtab.SymbolAtIndex(N_BRAC_indexes.back()); | |
| symbol_ptr->SetByteSize(sym_idx + 1); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| N_BRAC_indexes.pop_back(); | |
| } | |
| type = eSymbolTypeScopeEnd; | |
| break; | |
| case N_EXCL: | |
| // deleted include file: name,,NO_SECT,0,sum | |
| type = eSymbolTypeHeaderFile; | |
| break; | |
| // COMM scopes | |
| case N_BCOMM: | |
| // begin common: name,,NO_SECT,0,0 | |
| // We use the current number of symbols in the symbol table in lieu | |
| // of using nlist_idx in case we ever start trimming entries out | |
| type = eSymbolTypeScopeBegin; | |
| N_COMM_indexes.push_back(sym_idx); | |
| break; | |
| case N_ECOML: | |
| // end common (local name): 0,,n_sect,0,address | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| [[fallthrough]]; | |
| case N_ECOMM: | |
| // end common: name,,n_sect,0,0 | |
| // Set the size of the N_BCOMM to the terminating index of this | |
| // N_ECOMM/N_ECOML so that we can always skip the entire symbol if | |
| // we need to navigate more quickly at the source level when | |
| // parsing STABS | |
| if (!N_COMM_indexes.empty()) { | |
| symbol_ptr = symtab.SymbolAtIndex(N_COMM_indexes.back()); | |
| symbol_ptr->SetByteSize(sym_idx + 1); | |
| symbol_ptr->SetSizeIsSibling(true); | |
| N_COMM_indexes.pop_back(); | |
| } | |
| type = eSymbolTypeScopeEnd; | |
| break; | |
| case N_LENG: | |
| // second stab entry with length information | |
| type = eSymbolTypeAdditional; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else { | |
| uint8_t n_type = N_TYPE & nlist.n_type; | |
| sym[sym_idx].SetExternal((N_EXT & nlist.n_type) != 0); | |
| switch (n_type) { | |
| case N_INDR: { | |
| std::optional<llvm::StringRef> reexport_name_str = | |
| strtab_data.PeekCStr(nlist.n_value); | |
| if (reexport_name_str && !reexport_name_str->empty() && symbol_name) { | |
| type = eSymbolTypeReExported; | |
| ConstString reexport_name(reexport_name_str->drop_front( | |
| reexport_name_str->front() == '_' ? 1 : 0)); | |
| sym[sym_idx].SetReExportedSymbolName(reexport_name); | |
| set_value = false; | |
| reexport_shlib_needs_fixup[sym_idx] = reexport_name; | |
| indirect_symbol_names.insert( | |
| ConstString(symbol_name + ((symbol_name[0] == '_') ? 1 : 0))); | |
| } else | |
| type = eSymbolTypeUndefined; | |
| } break; | |
| case N_UNDF: | |
| if (symbol_name && symbol_name[0]) { | |
| ConstString undefined_name(symbol_name + | |
| ((symbol_name[0] == '_') ? 1 : 0)); | |
| undefined_name_to_desc[undefined_name] = nlist.n_desc; | |
| } | |
| [[fallthrough]]; | |
| case N_PBUD: | |
| type = eSymbolTypeUndefined; | |
| break; | |
| case N_ABS: | |
| type = eSymbolTypeAbsolute; | |
| break; | |
| case N_SECT: { | |
| symbol_section = section_info.GetSection(nlist.n_sect, nlist.n_value); | |
| if (!symbol_section) { | |
| // TODO: warn about this? | |
| add_nlist = false; | |
| break; | |
| } | |
| if (TEXT_eh_frame_sectID == nlist.n_sect) { | |
| type = eSymbolTypeException; | |
| } else { | |
| uint32_t section_type = symbol_section->Get() & SECTION_TYPE; | |
| switch (section_type) { | |
| case S_CSTRING_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only literal C strings | |
| case S_4BYTE_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only 4 byte literals | |
| case S_8BYTE_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only 8 byte literals | |
| case S_LITERAL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only pointers to literals | |
| case S_NON_LAZY_SYMBOL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only non-lazy symbol pointers | |
| case S_LAZY_SYMBOL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only lazy symbol pointers | |
| case S_SYMBOL_STUBS: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only symbol stubs, byte size of stub in | |
| // the reserved2 field | |
| case S_MOD_INIT_FUNC_POINTERS: | |
| type = eSymbolTypeCode; | |
| break; // section with only function pointers for initialization | |
| case S_MOD_TERM_FUNC_POINTERS: | |
| type = eSymbolTypeCode; | |
| break; // section with only function pointers for termination | |
| case S_INTERPOSING: | |
| type = eSymbolTypeTrampoline; | |
| break; // section with only pairs of function pointers for | |
| // interposing | |
| case S_16BYTE_LITERALS: | |
| type = eSymbolTypeData; | |
| break; // section with only 16 byte literals | |
| case S_DTRACE_DOF: | |
| type = eSymbolTypeInstrumentation; | |
| break; | |
| case S_LAZY_DYLIB_SYMBOL_POINTERS: | |
| type = eSymbolTypeTrampoline; | |
| break; | |
| default: | |
| switch (symbol_section->GetType()) { | |
| case lldb::eSectionTypeCode: | |
| type = eSymbolTypeCode; | |
| break; | |
| case eSectionTypeData: | |
| case eSectionTypeDataCString: // Inlined C string data | |
| case eSectionTypeDataCStringPointers: // Pointers to C string | |
| // data | |
| case eSectionTypeDataSymbolAddress: // Address of a symbol in | |
| // the symbol table | |
| case eSectionTypeData4: | |
| case eSectionTypeData8: | |
| case eSectionTypeData16: | |
| type = eSymbolTypeData; | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| if (type == eSymbolTypeInvalid) { | |
| llvm::StringRef symbol_sect_name = symbol_section->GetName(); | |
| if (symbol_section->IsDescendant(text_section_sp.get())) { | |
| if (symbol_section->IsClear(S_ATTR_PURE_INSTRUCTIONS | | |
| S_ATTR_SELF_MODIFYING_CODE | | |
| S_ATTR_SOME_INSTRUCTIONS)) | |
| type = eSymbolTypeData; | |
| else | |
| type = eSymbolTypeCode; | |
| } else if (symbol_section->IsDescendant(data_section_sp.get()) || | |
| symbol_section->IsDescendant( | |
| data_dirty_section_sp.get()) || | |
| symbol_section->IsDescendant( | |
| data_const_section_sp.get())) { | |
| if (symbol_sect_name.starts_with("__objc")) { | |
| type = eSymbolTypeRuntime; | |
| if (TryParseV2ObjCMetadataSymbol( | |
| symbol_name, symbol_name_non_abi_mangled, type)) | |
| demangled_is_synthesized = true; | |
| } else if (symbol_sect_name.starts_with("__gcc_except_tab")) { | |
| type = eSymbolTypeException; | |
| } else { | |
| type = eSymbolTypeData; | |
| } | |
| } else if (symbol_sect_name.starts_with("__IMPORT")) { | |
| type = eSymbolTypeTrampoline; | |
| } else if (symbol_section->IsDescendant(objc_section_sp.get())) { | |
| type = eSymbolTypeRuntime; | |
| if (symbol_name && symbol_name[0] == '.') { | |
| llvm::StringRef symbol_name_ref(symbol_name); | |
| llvm::StringRef g_objc_v1_prefix_class( | |
| ".objc_class_name_"); | |
| if (symbol_name_ref.starts_with(g_objc_v1_prefix_class)) { | |
| symbol_name_non_abi_mangled = symbol_name; | |
| symbol_name = symbol_name + g_objc_v1_prefix_class.size(); | |
| type = eSymbolTypeObjCClass; | |
| demangled_is_synthesized = true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } break; | |
| } | |
| } | |
| if (!add_nlist) { | |
| sym[sym_idx].Clear(); | |
| return true; | |
| } | |
| uint64_t symbol_value = nlist.n_value; | |
| if (symbol_name_non_abi_mangled) { | |
| sym[sym_idx].GetMangled().SetMangledName( | |
| ConstString(symbol_name_non_abi_mangled)); | |
| sym[sym_idx].GetMangled().SetDemangledName(ConstString(symbol_name)); | |
| } else { | |
| if (symbol_name && symbol_name[0] == '_') { | |
| symbol_name++; // Skip the leading underscore | |
| } | |
| if (symbol_name) { | |
| ConstString const_symbol_name(symbol_name); | |
| sym[sym_idx].GetMangled().SetValue(const_symbol_name); | |
| } | |
| } | |
| if (is_gsym) { | |
| const char *gsym_name = sym[sym_idx] | |
| .GetMangled() | |
| .GetName(Mangled::ePreferMangled) | |
| .GetCString(); | |
| if (gsym_name) | |
| N_GSYM_name_to_sym_idx[gsym_name] = sym_idx; | |
| } | |
| if (symbol_section) { | |
| const addr_t section_file_addr = symbol_section->GetFileAddress(); | |
| symbol_value -= section_file_addr; | |
| } | |
| if (!is_debug) { | |
| if (type == eSymbolTypeCode) { | |
| // See if we can find a N_FUN entry for any code symbols. If we do | |
| // find a match, and the name matches, then we can merge the two into | |
| // just the function symbol to avoid duplicate entries in the symbol | |
| // table. | |
| std::pair<ValueToSymbolIndexMap::const_iterator, | |
| ValueToSymbolIndexMap::const_iterator> | |
| range; | |
| range = N_FUN_addr_to_sym_idx.equal_range(nlist.n_value); | |
| if (range.first != range.second) { | |
| for (ValueToSymbolIndexMap::const_iterator pos = range.first; | |
| pos != range.second; ++pos) { | |
| if (sym[sym_idx].GetMangled().GetName(Mangled::ePreferMangled) == | |
| sym[pos->second].GetMangled().GetName( | |
| Mangled::ePreferMangled)) { | |
| m_nlist_idx_to_sym_idx[nlist_idx] = pos->second; | |
| // We just need the flags from the linker symbol, so put these | |
| // flags into the N_FUN flags to avoid duplicate symbols in the | |
| // symbol table. | |
| sym[pos->second].SetExternal(sym[sym_idx].IsExternal()); | |
| sym[pos->second].SetFlags(nlist.n_type << 16 | nlist.n_desc); | |
| if (resolver_addresses.find(nlist.n_value) != | |
| resolver_addresses.end()) | |
| sym[pos->second].SetType(eSymbolTypeResolver); | |
| sym[sym_idx].Clear(); | |
| return true; | |
| } | |
| } | |
| } else { | |
| if (resolver_addresses.find(nlist.n_value) != | |
| resolver_addresses.end()) | |
| type = eSymbolTypeResolver; | |
| } | |
| } else if (type == eSymbolTypeData || type == eSymbolTypeObjCClass || | |
| type == eSymbolTypeObjCMetaClass || | |
| type == eSymbolTypeObjCIVar) { | |
| // See if we can find a N_STSYM entry for any data symbols. If we do | |
| // find a match, and the name matches, then we can merge the two into | |
| // just the Static symbol to avoid duplicate entries in the symbol | |
| // table. | |
| std::pair<ValueToSymbolIndexMap::const_iterator, | |
| ValueToSymbolIndexMap::const_iterator> | |
| range; | |
| range = N_STSYM_addr_to_sym_idx.equal_range(nlist.n_value); | |
| if (range.first != range.second) { | |
| for (ValueToSymbolIndexMap::const_iterator pos = range.first; | |
| pos != range.second; ++pos) { | |
| if (sym[sym_idx].GetMangled().GetName(Mangled::ePreferMangled) == | |
| sym[pos->second].GetMangled().GetName( | |
| Mangled::ePreferMangled)) { | |
| m_nlist_idx_to_sym_idx[nlist_idx] = pos->second; | |
| // We just need the flags from the linker symbol, so put these | |
| // flags into the N_STSYM flags to avoid duplicate symbols in | |
| // the symbol table. | |
| sym[pos->second].SetExternal(sym[sym_idx].IsExternal()); | |
| sym[pos->second].SetFlags(nlist.n_type << 16 | nlist.n_desc); | |
| sym[sym_idx].Clear(); | |
| return true; | |
| } | |
| } | |
| } else { | |
| // Combine N_GSYM stab entries with the non stab symbol. | |
| const char *gsym_name = sym[sym_idx] | |
| .GetMangled() | |
| .GetName(Mangled::ePreferMangled) | |
| .GetCString(); | |
| if (gsym_name) { | |
| ConstNameToSymbolIndexMap::const_iterator pos = | |
| N_GSYM_name_to_sym_idx.find(gsym_name); | |
| if (pos != N_GSYM_name_to_sym_idx.end()) { | |
| const uint32_t GSYM_sym_idx = pos->second; | |
| m_nlist_idx_to_sym_idx[nlist_idx] = GSYM_sym_idx; | |
| // Copy the address, because often the N_GSYM address has an | |
| // invalid address of zero when the global is a common symbol. | |
| sym[GSYM_sym_idx].GetAddressRef() = | |
| Address(symbol_section, symbol_value); | |
| add_symbol_addr( | |
| sym[GSYM_sym_idx].GetAddress().GetFileAddress()); | |
| // We just need the flags from the linker symbol, so put these | |
| // flags into the N_GSYM flags to avoid duplicate symbols in | |
| // the symbol table. | |
| sym[GSYM_sym_idx].SetFlags(nlist.n_type << 16 | nlist.n_desc); | |
| sym[sym_idx].Clear(); | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| sym[sym_idx].SetID(nlist_idx); | |
| sym[sym_idx].SetType(type); | |
| if (set_value) { | |
| sym[sym_idx].GetAddressRef() = Address(symbol_section, symbol_value); | |
| if (symbol_section) | |
| add_symbol_addr(sym[sym_idx].GetAddress().GetFileAddress()); | |
| } | |
| sym[sym_idx].SetFlags(nlist.n_type << 16 | nlist.n_desc); | |
| if (nlist.n_desc & N_WEAK_REF) | |
| sym[sym_idx].SetIsWeak(true); | |
| if (demangled_is_synthesized) | |
| sym[sym_idx].SetDemangledNameIsSynthesized(true); | |
| ++sym_idx; | |
| return true; | |
| }; | |
| // First parse all the nlists but don't process them yet. See the next | |
| // comment for an explanation why. | |
| std::vector<struct nlist_64> nlists; | |
| nlists.reserve(std::min<uint64_t>(symtab_load_command.nsyms, max_nsyms)); | |
| for (; nlist_idx < symtab_load_command.nsyms; ++nlist_idx) { | |
| if (auto nlist = | |
| ParseNList(nlist_data, nlist_data_offset, nlist_byte_size)) | |
| nlists.push_back(*nlist); | |
| else | |
| break; | |
| } | |
| // Now parse all the debug symbols. This is needed to merge non-debug | |
| // symbols in the next step. Non-debug symbols are always coalesced into | |
| // the debug symbol. Doing this in one step would mean that some symbols | |
| // won't be merged. | |
| nlist_idx = 0; | |
| for (auto &nlist : nlists) { | |
| if (!ParseSymbolLambda(nlist, nlist_idx++, DebugSymbols)) | |
| break; | |
| } | |
| // Finally parse all the non debug symbols. | |
| nlist_idx = 0; | |
| for (auto &nlist : nlists) { | |
| if (!ParseSymbolLambda(nlist, nlist_idx++, NonDebugSymbols)) | |
| break; | |
| } | |
| for (const auto &pos : reexport_shlib_needs_fixup) { | |
| const auto undef_pos = undefined_name_to_desc.find(pos.second); | |
| if (undef_pos != undefined_name_to_desc.end()) { | |
| const uint8_t dylib_ordinal = | |
| llvm::MachO::GET_LIBRARY_ORDINAL(undef_pos->second); | |
| if (dylib_ordinal > 0 && dylib_ordinal < dylib_files.GetSize()) | |
| sym[pos.first].SetReExportedSymbolSharedLibrary( | |
| dylib_files.GetFileSpecAtIndex(dylib_ordinal - 1)); | |
| } | |
| } | |
| } | |
| // Count how many trie symbols we'll add to the symbol table | |
| int trie_symbol_table_augment_count = 0; | |
| for (auto &e : external_sym_trie_entries) { | |
| if (!symbols_added.contains(e.entry.address)) | |
| trie_symbol_table_augment_count++; | |
| } | |
| if (num_syms < sym_idx + trie_symbol_table_augment_count) { | |
| num_syms = sym_idx + trie_symbol_table_augment_count; | |
| sym = symtab.Resize(num_syms); | |
| } | |
| uint32_t synthetic_sym_id = symtab_load_command.nsyms; | |
| // Add symbols from the trie to the symbol table. | |
| for (auto &e : external_sym_trie_entries) { | |
| if (symbols_added.contains(e.entry.address)) | |
| continue; | |
| // Find the section that this trie address is in, use that to annotate | |
| // symbol type as we add the trie address and name to the symbol table. | |
| Address symbol_addr; | |
| if (module_sp->ResolveFileAddress(e.entry.address, symbol_addr)) { | |
| SectionSP symbol_section(symbol_addr.GetSection()); | |
| const char *symbol_name = e.entry.name.GetCString(); | |
| bool demangled_is_synthesized = false; | |
| SymbolType type = | |
| GetSymbolType(symbol_name, demangled_is_synthesized, text_section_sp, | |
| data_section_sp, data_dirty_section_sp, | |
| data_const_section_sp, symbol_section); | |
| sym[sym_idx].SetType(type); | |
| if (symbol_section) { | |
| sym[sym_idx].SetID(synthetic_sym_id++); | |
| sym[sym_idx].GetMangled().SetMangledName(ConstString(symbol_name)); | |
| if (demangled_is_synthesized) | |
| sym[sym_idx].SetDemangledNameIsSynthesized(true); | |
| sym[sym_idx].SetIsSynthetic(true); | |
| sym[sym_idx].SetExternal(true); | |
| sym[sym_idx].GetAddressRef() = symbol_addr; | |
| add_symbol_addr(symbol_addr.GetFileAddress()); | |
| if (e.entry.flags & TRIE_SYMBOL_IS_THUMB) | |
| sym[sym_idx].SetFlags(MACHO_NLIST_ARM_SYMBOL_IS_THUMB); | |
| ++sym_idx; | |
| } | |
| } | |
| } | |
| if (function_starts_count > 0) { | |
| uint32_t num_synthetic_function_symbols = 0; | |
| for (i = 0; i < function_starts_count; ++i) { | |
| if (!symbols_added.contains(function_starts.GetEntryRef(i).addr)) | |
| ++num_synthetic_function_symbols; | |
| } | |
| if (num_synthetic_function_symbols > 0) { | |
| if (num_syms < sym_idx + num_synthetic_function_symbols) { | |
| num_syms = sym_idx + num_synthetic_function_symbols; | |
| sym = symtab.Resize(num_syms); | |
| } | |
| for (i = 0; i < function_starts_count; ++i) { | |
| const FunctionStarts::Entry *func_start_entry = | |
| function_starts.GetEntryAtIndex(i); | |
| if (!symbols_added.contains(func_start_entry->addr)) { | |
| addr_t symbol_file_addr = func_start_entry->addr; | |
| uint32_t symbol_flags = 0; | |
| if (func_start_entry->data) | |
| symbol_flags = MACHO_NLIST_ARM_SYMBOL_IS_THUMB; | |
| Address symbol_addr; | |
| if (module_sp->ResolveFileAddress(symbol_file_addr, symbol_addr)) { | |
| SectionSP symbol_section(symbol_addr.GetSection()); | |
| if (symbol_section) { | |
| sym[sym_idx].SetID(synthetic_sym_id++); | |
| // Don't set the name for any synthetic symbols, the Symbol | |
| // object will generate one if needed when the name is accessed | |
| // via accessors. | |
| sym[sym_idx].GetMangled().SetDemangledName(ConstString()); | |
| sym[sym_idx].SetType(eSymbolTypeCode); | |
| sym[sym_idx].SetIsSynthetic(true); | |
| sym[sym_idx].GetAddressRef() = symbol_addr; | |
| add_symbol_addr(symbol_addr.GetFileAddress()); | |
| if (symbol_flags) | |
| sym[sym_idx].SetFlags(symbol_flags); | |
| ++sym_idx; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Trim our symbols down to just what we ended up with after removing any | |
| // symbols. | |
| if (sym_idx < num_syms) { | |
| num_syms = sym_idx; | |
| sym = symtab.Resize(num_syms); | |
| } | |
| // Now synthesize indirect symbols | |
| if (m_dysymtab.nindirectsyms != 0) { | |
| if (indirect_symbol_index_data.GetByteSize()) { | |
| NListIndexToSymbolIndexMap::const_iterator end_index_pos = | |
| m_nlist_idx_to_sym_idx.end(); | |
| for (uint32_t sect_idx = 1; sect_idx < m_mach_sections.size(); | |
| ++sect_idx) { | |
| if ((m_mach_sections[sect_idx].flags & SECTION_TYPE) == | |
| S_SYMBOL_STUBS) { | |
| uint32_t symbol_stub_byte_size = m_mach_sections[sect_idx].reserved2; | |
| if (symbol_stub_byte_size == 0) | |
| continue; | |
| const uint32_t num_symbol_stubs = | |
| m_mach_sections[sect_idx].size / symbol_stub_byte_size; | |
| if (num_symbol_stubs == 0) | |
| continue; | |
| const uint32_t symbol_stub_index_offset = | |
| m_mach_sections[sect_idx].reserved1; | |
| for (uint32_t stub_idx = 0; stub_idx < num_symbol_stubs; ++stub_idx) { | |
| const uint32_t symbol_stub_index = | |
| symbol_stub_index_offset + stub_idx; | |
| const lldb::addr_t symbol_stub_addr = | |
| m_mach_sections[sect_idx].addr + | |
| (stub_idx * symbol_stub_byte_size); | |
| lldb::offset_t symbol_stub_offset = symbol_stub_index * 4; | |
| if (indirect_symbol_index_data.ValidOffsetForDataOfSize( | |
| symbol_stub_offset, 4)) { | |
| const uint32_t stub_sym_id = | |
| indirect_symbol_index_data.GetU32(&symbol_stub_offset); | |
| if (stub_sym_id & (INDIRECT_SYMBOL_ABS | INDIRECT_SYMBOL_LOCAL)) | |
| continue; | |
| NListIndexToSymbolIndexMap::const_iterator index_pos = | |
| m_nlist_idx_to_sym_idx.find(stub_sym_id); | |
| Symbol *stub_symbol = nullptr; | |
| if (index_pos != end_index_pos) { | |
| // We have a remapping from the original nlist index to a | |
| // current symbol index, so just look this up by index | |
| stub_symbol = symtab.SymbolAtIndex(index_pos->second); | |
| } else { | |
| // We need to lookup a symbol using the original nlist symbol | |
| // index since this index is coming from the S_SYMBOL_STUBS | |
| stub_symbol = symtab.FindSymbolByID(stub_sym_id); | |
| } | |
| if (stub_symbol) { | |
| Address so_addr(symbol_stub_addr, section_list); | |
| if (stub_symbol->GetType() == eSymbolTypeUndefined) { | |
| // Change the external symbol into a trampoline that makes | |
| // sense These symbols were N_UNDF N_EXT, and are useless | |
| // to us, so we can re-use them so we don't have to make up | |
| // a synthetic symbol for no good reason. | |
| if (resolver_addresses.find(symbol_stub_addr) == | |
| resolver_addresses.end()) | |
| stub_symbol->SetType(eSymbolTypeTrampoline); | |
| else | |
| stub_symbol->SetType(eSymbolTypeResolver); | |
| stub_symbol->SetExternal(false); | |
| stub_symbol->GetAddressRef() = so_addr; | |
| stub_symbol->SetByteSize(symbol_stub_byte_size); | |
| } else { | |
| // Make a synthetic symbol to describe the trampoline stub | |
| Mangled stub_symbol_mangled_name(stub_symbol->GetMangled()); | |
| if (sym_idx >= num_syms) { | |
| sym = symtab.Resize(++num_syms); | |
| stub_symbol = nullptr; // this pointer no longer valid | |
| } | |
| sym[sym_idx].SetID(synthetic_sym_id++); | |
| sym[sym_idx].GetMangled() = stub_symbol_mangled_name; | |
| if (resolver_addresses.find(symbol_stub_addr) == | |
| resolver_addresses.end()) | |
| sym[sym_idx].SetType(eSymbolTypeTrampoline); | |
| else | |
| sym[sym_idx].SetType(eSymbolTypeResolver); | |
| sym[sym_idx].SetIsSynthetic(true); | |
| sym[sym_idx].GetAddressRef() = so_addr; | |
| add_symbol_addr(so_addr.GetFileAddress()); | |
| sym[sym_idx].SetByteSize(symbol_stub_byte_size); | |
| ++sym_idx; | |
| } | |
| } else { | |
| LLDB_LOGF(log, | |
| "warning: symbol stub referencing symbol table " | |
| "symbol %u that isn't in our minimal symbol table, " | |
| "fix this!!!", | |
| stub_sym_id); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!reexport_trie_entries.empty()) { | |
| for (const auto &e : reexport_trie_entries) { | |
| if (e.entry.import_name) { | |
| // Only add indirect symbols from the Trie entries if we didn't have | |
| // a N_INDR nlist entry for this already | |
| if (indirect_symbol_names.find(e.entry.name) == | |
| indirect_symbol_names.end()) { | |
| // Make a synthetic symbol to describe re-exported symbol. | |
| if (sym_idx >= num_syms) | |
| sym = symtab.Resize(++num_syms); | |
| sym[sym_idx].SetID(synthetic_sym_id++); | |
| sym[sym_idx].GetMangled() = Mangled(e.entry.name); | |
| sym[sym_idx].SetType(eSymbolTypeReExported); | |
| sym[sym_idx].SetIsSynthetic(true); | |
| sym[sym_idx].SetReExportedSymbolName(e.entry.import_name); | |
| if (e.entry.other > 0 && e.entry.other <= dylib_files.GetSize()) { | |
| sym[sym_idx].SetReExportedSymbolSharedLibrary( | |
| dylib_files.GetFileSpecAtIndex(e.entry.other - 1)); | |
| } | |
| ++sym_idx; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void ObjectFileMachO::Dump(Stream *s) { | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| s->Printf("%p: ", static_cast<void *>(this)); | |
| s->Indent(); | |
| if (m_header.magic == MH_MAGIC_64 || m_header.magic == MH_CIGAM_64) | |
| s->PutCString("ObjectFileMachO64"); | |
| else | |
| s->PutCString("ObjectFileMachO32"); | |
| *s << ", file = '" << m_file; | |
| ModuleSpecList all_specs; | |
| ModuleSpec base_spec; | |
| GetAllArchSpecs(m_header, *m_data_nsp, | |
| MachHeaderSizeFromMagic(m_header.magic), base_spec, | |
| all_specs); | |
| for (unsigned i = 0, e = all_specs.GetSize(); i != e; ++i) { | |
| *s << "', triple"; | |
| if (e) | |
| s->Printf("[%d]", i); | |
| *s << " = "; | |
| *s << all_specs.GetModuleSpecRefAtIndex(i) | |
| .GetArchitecture() | |
| .GetTriple() | |
| .getTriple(); | |
| } | |
| *s << "\n"; | |
| SectionList *sections = GetSectionList(); | |
| if (sections) | |
| sections->Dump(s->AsRawOstream(), s->GetIndentLevel(), nullptr, true, | |
| UINT32_MAX); | |
| if (m_symtab_up) | |
| m_symtab_up->Dump(s, nullptr, eSortOrderNone); | |
| } | |
| } | |
| UUID ObjectFileMachO::GetUUID(const llvm::MachO::mach_header &header, | |
| const lldb_private::DataExtractor &data, | |
| lldb::offset_t lc_offset) { | |
| uint32_t i; | |
| llvm::MachO::uuid_command load_cmd; | |
| lldb::offset_t offset = lc_offset; | |
| for (i = 0; i < header.ncmds; ++i) { | |
| const lldb::offset_t cmd_offset = offset; | |
| if (!ReadMachOCommand(data, offset, load_cmd)) | |
| break; | |
| if (load_cmd.cmd == LC_UUID) { | |
| const uint8_t *uuid_bytes = data.PeekData(offset, 16); | |
| if (uuid_bytes) { | |
| // OpenCL on Mac OS X uses the same UUID for each of its object files. | |
| // We pretend these object files have no UUID to prevent crashing. | |
| const uint8_t opencl_uuid[] = {0x8c, 0x8e, 0xb3, 0x9b, 0x3b, 0xa8, | |
| 0x4b, 0x16, 0xb6, 0xa4, 0x27, 0x63, | |
| 0xbb, 0x14, 0xf0, 0x0d}; | |
| if (!memcmp(uuid_bytes, opencl_uuid, 16)) | |
| return UUID(); | |
| return UUID(uuid_bytes, 16); | |
| } | |
| return UUID(); | |
| } | |
| offset = cmd_offset + load_cmd.cmdsize; | |
| } | |
| return UUID(); | |
| } | |
| static llvm::StringRef GetOSName(uint32_t cmd) { | |
| switch (cmd) { | |
| case llvm::MachO::LC_VERSION_MIN_IPHONEOS: | |
| return llvm::Triple::getOSTypeName(llvm::Triple::IOS); | |
| case llvm::MachO::LC_VERSION_MIN_MACOSX: | |
| return llvm::Triple::getOSTypeName(llvm::Triple::MacOSX); | |
| case llvm::MachO::LC_VERSION_MIN_TVOS: | |
| return llvm::Triple::getOSTypeName(llvm::Triple::TvOS); | |
| case llvm::MachO::LC_VERSION_MIN_WATCHOS: | |
| return llvm::Triple::getOSTypeName(llvm::Triple::WatchOS); | |
| default: | |
| llvm_unreachable("unexpected LC_VERSION load command"); | |
| } | |
| } | |
| namespace { | |
| struct OSEnv { | |
| llvm::StringRef os_type; | |
| llvm::StringRef environment; | |
| OSEnv(uint32_t cmd) { | |
| switch (cmd) { | |
| case llvm::MachO::PLATFORM_MACOS: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::MacOSX); | |
| return; | |
| case llvm::MachO::PLATFORM_IOS: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::IOS); | |
| return; | |
| case llvm::MachO::PLATFORM_TVOS: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::TvOS); | |
| return; | |
| case llvm::MachO::PLATFORM_WATCHOS: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::WatchOS); | |
| return; | |
| case llvm::MachO::PLATFORM_BRIDGEOS: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::BridgeOS); | |
| return; | |
| case llvm::MachO::PLATFORM_DRIVERKIT: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::DriverKit); | |
| return; | |
| case llvm::MachO::PLATFORM_MACCATALYST: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::IOS); | |
| environment = llvm::Triple::getEnvironmentTypeName(llvm::Triple::MacABI); | |
| return; | |
| case llvm::MachO::PLATFORM_IOSSIMULATOR: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::IOS); | |
| environment = | |
| llvm::Triple::getEnvironmentTypeName(llvm::Triple::Simulator); | |
| return; | |
| case llvm::MachO::PLATFORM_TVOSSIMULATOR: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::TvOS); | |
| environment = | |
| llvm::Triple::getEnvironmentTypeName(llvm::Triple::Simulator); | |
| return; | |
| case llvm::MachO::PLATFORM_WATCHOSSIMULATOR: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::WatchOS); | |
| environment = | |
| llvm::Triple::getEnvironmentTypeName(llvm::Triple::Simulator); | |
| return; | |
| case llvm::MachO::PLATFORM_XROS: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::XROS); | |
| return; | |
| case llvm::MachO::PLATFORM_XROS_SIMULATOR: | |
| os_type = llvm::Triple::getOSTypeName(llvm::Triple::XROS); | |
| environment = | |
| llvm::Triple::getEnvironmentTypeName(llvm::Triple::Simulator); | |
| return; | |
| default: { | |
| Log *log(GetLog(LLDBLog::Symbols | LLDBLog::Process)); | |
| LLDB_LOGF(log, "unsupported platform in LC_BUILD_VERSION"); | |
| } | |
| } | |
| } | |
| }; | |
| struct MinOS { | |
| uint32_t major_version, minor_version, patch_version; | |
| MinOS(uint32_t version) | |
| : major_version(version >> 16), minor_version((version >> 8) & 0xffu), | |
| patch_version(version & 0xffu) {} | |
| }; | |
| } // namespace | |
| void ObjectFileMachO::GetAllArchSpecs(const llvm::MachO::mach_header &header, | |
| const lldb_private::DataExtractor &data, | |
| lldb::offset_t lc_offset, | |
| ModuleSpec &base_spec, | |
| lldb_private::ModuleSpecList &all_specs) { | |
| auto &base_arch = base_spec.GetArchitecture(); | |
| base_arch.SetArchitecture(eArchTypeMachO, header.cputype, header.cpusubtype); | |
| if (!base_arch.IsValid()) | |
| return; | |
| bool found_any = false; | |
| auto add_triple = [&](const llvm::Triple &triple) { | |
| auto spec = base_spec; | |
| spec.GetArchitecture().GetTriple() = triple; | |
| if (spec.GetArchitecture().IsValid()) { | |
| spec.GetUUID() = ObjectFileMachO::GetUUID(header, data, lc_offset); | |
| all_specs.Append(spec); | |
| found_any = true; | |
| } | |
| }; | |
| // Set OS to an unspecified unknown or a "*" so it can match any OS | |
| llvm::Triple base_triple = base_arch.GetTriple(); | |
| base_triple.setOS(llvm::Triple::UnknownOS); | |
| base_triple.setOSName(llvm::StringRef()); | |
| if (header.filetype == MH_PRELOAD) { | |
| if (header.cputype == CPU_TYPE_ARM) { | |
| // If this is a 32-bit arm binary, and it's a standalone binary, force | |
| // the Vendor to Apple so we don't accidentally pick up the generic | |
| // armv7 ABI at runtime. Apple's armv7 ABI always uses r7 for the | |
| // frame pointer register; most other armv7 ABIs use a combination of | |
| // r7 and r11. | |
| base_triple.setVendor(llvm::Triple::Apple); | |
| } else { | |
| // Set vendor to an unspecified unknown or a "*" so it can match any | |
| // vendor This is required for correct behavior of EFI debugging on | |
| // x86_64 | |
| base_triple.setVendor(llvm::Triple::UnknownVendor); | |
| base_triple.setVendorName(llvm::StringRef()); | |
| } | |
| return add_triple(base_triple); | |
| } | |
| llvm::MachO::load_command load_cmd; | |
| // See if there is an LC_VERSION_MIN_* load command that can give | |
| // us the OS type. | |
| lldb::offset_t offset = lc_offset; | |
| for (uint32_t i = 0; i < header.ncmds; ++i) { | |
| const lldb::offset_t cmd_offset = offset; | |
| if (!ReadMachOCommand(data, offset, load_cmd)) | |
| break; | |
| llvm::MachO::version_min_command version_min; | |
| switch (load_cmd.cmd) { | |
| case llvm::MachO::LC_VERSION_MIN_MACOSX: | |
| case llvm::MachO::LC_VERSION_MIN_IPHONEOS: | |
| case llvm::MachO::LC_VERSION_MIN_TVOS: | |
| case llvm::MachO::LC_VERSION_MIN_WATCHOS: { | |
| if (load_cmd.cmdsize != sizeof(version_min)) | |
| break; | |
| if (data.ExtractBytes(cmd_offset, sizeof(version_min), | |
| data.GetByteOrder(), &version_min) == 0) | |
| break; | |
| MinOS min_os(version_min.version); | |
| llvm::SmallString<32> os_name; | |
| llvm::raw_svector_ostream os(os_name); | |
| os << GetOSName(load_cmd.cmd) << min_os.major_version << '.' | |
| << min_os.minor_version << '.' << min_os.patch_version; | |
| auto triple = base_triple; | |
| triple.setOSName(os.str()); | |
| // Disambiguate legacy simulator platforms. | |
| if (load_cmd.cmd != llvm::MachO::LC_VERSION_MIN_MACOSX && | |
| (base_triple.getArch() == llvm::Triple::x86_64 || | |
| base_triple.getArch() == llvm::Triple::x86)) { | |
| // The combination of legacy LC_VERSION_MIN load command and | |
| // x86 architecture always indicates a simulator environment. | |
| // The combination of LC_VERSION_MIN and arm architecture only | |
| // appears for native binaries. Back-deploying simulator | |
| // binaries on Apple Silicon Macs use the modern unambigous | |
| // LC_BUILD_VERSION load commands; no special handling required. | |
| triple.setEnvironment(llvm::Triple::Simulator); | |
| } | |
| add_triple(triple); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| offset = cmd_offset + load_cmd.cmdsize; | |
| } | |
| // See if there are LC_BUILD_VERSION load commands that can give | |
| // us the OS type. | |
| offset = lc_offset; | |
| for (uint32_t i = 0; i < header.ncmds; ++i) { | |
| const lldb::offset_t cmd_offset = offset; | |
| if (!ReadMachOCommand(data, offset, load_cmd)) | |
| break; | |
| do { | |
| if (load_cmd.cmd == llvm::MachO::LC_BUILD_VERSION) { | |
| llvm::MachO::build_version_command build_version; | |
| if (load_cmd.cmdsize < sizeof(build_version)) { | |
| // Malformed load command. | |
| break; | |
| } | |
| if (data.ExtractBytes(cmd_offset, sizeof(build_version), | |
| data.GetByteOrder(), &build_version) == 0) | |
| break; | |
| MinOS min_os(build_version.minos); | |
| OSEnv os_env(build_version.platform); | |
| llvm::SmallString<16> os_name; | |
| llvm::raw_svector_ostream os(os_name); | |
| os << os_env.os_type << min_os.major_version << '.' | |
| << min_os.minor_version << '.' << min_os.patch_version; | |
| auto triple = base_triple; | |
| triple.setOSName(os.str()); | |
| os_name.clear(); | |
| if (!os_env.environment.empty()) | |
| triple.setEnvironmentName(os_env.environment); | |
| add_triple(triple); | |
| } | |
| } while (false); | |
| offset = cmd_offset + load_cmd.cmdsize; | |
| } | |
| if (!found_any) { | |
| add_triple(base_triple); | |
| } | |
| } | |
| ArchSpec ObjectFileMachO::GetArchitecture( | |
| ModuleSP module_sp, const llvm::MachO::mach_header &header, | |
| const lldb_private::DataExtractor &data, lldb::offset_t lc_offset) { | |
| ModuleSpecList all_specs; | |
| ModuleSpec base_spec; | |
| GetAllArchSpecs(header, data, MachHeaderSizeFromMagic(header.magic), | |
| base_spec, all_specs); | |
| // If the object file offers multiple alternative load commands, | |
| // pick the one that matches the module. | |
| if (module_sp) { | |
| const ArchSpec &module_arch = module_sp->GetArchitecture(); | |
| for (unsigned i = 0, e = all_specs.GetSize(); i != e; ++i) { | |
| ArchSpec mach_arch = | |
| all_specs.GetModuleSpecRefAtIndex(i).GetArchitecture(); | |
| if (module_arch.IsCompatibleMatch(mach_arch)) | |
| return mach_arch; | |
| } | |
| } | |
| // Return the first arch we found. | |
| if (all_specs.GetSize() == 0) | |
| return {}; | |
| return all_specs.GetModuleSpecRefAtIndex(0).GetArchitecture(); | |
| } | |
| UUID ObjectFileMachO::GetUUID() { | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| return GetUUID(m_header, *m_data_nsp, offset); | |
| } | |
| return UUID(); | |
| } | |
| uint32_t ObjectFileMachO::GetDependentModules(FileSpecList &files) { | |
| ModuleSP module_sp = GetModule(); | |
| if (!module_sp) | |
| return 0; | |
| uint32_t count = 0; | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| llvm::MachO::load_command load_cmd; | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| std::vector<std::string> rpath_paths; | |
| std::vector<std::string> rpath_relative_paths; | |
| std::vector<std::string> at_exec_relative_paths; | |
| uint32_t i; | |
| for (i = 0; i < m_header.ncmds; ++i) { | |
| const uint32_t cmd_offset = offset; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, load_cmd)) | |
| break; | |
| switch (load_cmd.cmd) { | |
| case LC_RPATH: | |
| case LC_LOAD_DYLIB: | |
| case LC_LOAD_WEAK_DYLIB: | |
| case LC_REEXPORT_DYLIB: | |
| case LC_LOAD_DYLINKER: | |
| case LC_LOADFVMLIB: | |
| case LC_LOAD_UPWARD_DYLIB: { | |
| uint32_t name_offset = cmd_offset + m_data_nsp->GetU32(&offset); | |
| // For LC_LOAD_DYLIB there is an alternate encoding | |
| // which adds a uint32_t `flags` field for `DYLD_USE_*` | |
| // flags. This can be detected by a timestamp field with | |
| // the `DYLIB_USE_MARKER` constant value. | |
| bool is_delayed_init = false; | |
| uint32_t use_command_marker = m_data_nsp->GetU32(&offset); | |
| if (use_command_marker == 0x1a741800 /* DYLIB_USE_MARKER */) { | |
| offset += 4; /* uint32_t current_version */ | |
| offset += 4; /* uint32_t compat_version */ | |
| uint32_t flags = m_data_nsp->GetU32(&offset); | |
| // If this LC_LOAD_DYLIB is marked delay-init, | |
| // don't report it as a dependent library -- it | |
| // may be loaded in the process at some point, | |
| // but will most likely not be load at launch. | |
| if (flags & 0x08 /* DYLIB_USE_DELAYED_INIT */) | |
| is_delayed_init = true; | |
| } | |
| std::optional<llvm::StringRef> maybe_path = | |
| m_data_nsp->PeekCStr(name_offset); | |
| if (maybe_path && !is_delayed_init) { | |
| llvm::StringRef path = *maybe_path; | |
| if (load_cmd.cmd == LC_RPATH) | |
| rpath_paths.push_back(path.str()); | |
| else if (path.consume_front("@rpath")) | |
| rpath_relative_paths.push_back(path.str()); | |
| else if (path.consume_front("@executable_path")) | |
| at_exec_relative_paths.push_back(path.str()); | |
| else if (!path.starts_with("@")) { | |
| FileSpec file_spec(path); | |
| if (files.AppendIfUnique(file_spec)) | |
| count++; | |
| } | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| offset = cmd_offset + load_cmd.cmdsize; | |
| } | |
| FileSpec this_file_spec(m_file); | |
| FileSystem::Instance().Resolve(this_file_spec); | |
| if (!rpath_paths.empty()) { | |
| // Fixup all LC_RPATH values to be absolute paths. | |
| const std::string this_directory = this_file_spec.GetDirectory().str(); | |
| for (auto &rpath : rpath_paths) { | |
| if (llvm::StringRef(rpath).starts_with(g_loader_path)) | |
| rpath = this_directory + rpath.substr(g_loader_path.size()); | |
| else if (llvm::StringRef(rpath).starts_with(g_executable_path)) | |
| rpath = this_directory + rpath.substr(g_executable_path.size()); | |
| } | |
| for (const auto &rpath_relative_path : rpath_relative_paths) { | |
| for (const auto &rpath : rpath_paths) { | |
| std::string path = rpath; | |
| path += rpath_relative_path; | |
| // It is OK to resolve this path because we must find a file on disk | |
| // for us to accept it anyway if it is rpath relative. | |
| FileSpec file_spec(path); | |
| FileSystem::Instance().Resolve(file_spec); | |
| if (FileSystem::Instance().Exists(file_spec) && | |
| files.AppendIfUnique(file_spec)) { | |
| count++; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // We may have @executable_paths but no RPATHS. Figure those out here. | |
| // Only do this if this object file is the executable. We have no way to | |
| // get back to the actual executable otherwise, so we won't get the right | |
| // path. | |
| if (!at_exec_relative_paths.empty() && CalculateType() == eTypeExecutable) { | |
| FileSpec exec_dir = this_file_spec.CopyByRemovingLastPathComponent(); | |
| for (const auto &at_exec_relative_path : at_exec_relative_paths) { | |
| FileSpec file_spec = | |
| exec_dir.CopyByAppendingPathComponent(at_exec_relative_path); | |
| if (FileSystem::Instance().Exists(file_spec) && | |
| files.AppendIfUnique(file_spec)) | |
| count++; | |
| } | |
| } | |
| return count; | |
| } | |
| lldb_private::Address ObjectFileMachO::GetEntryPointAddress() { | |
| // If the object file is not an executable it can't hold the entry point. | |
| // m_entry_point_address is initialized to an invalid address, so we can just | |
| // return that. If m_entry_point_address is valid it means we've found it | |
| // already, so return the cached value. | |
| if ((!IsExecutable() && !IsDynamicLoader()) || | |
| m_entry_point_address.IsValid()) { | |
| return m_entry_point_address; | |
| } | |
| // Otherwise, look for the UnixThread or Thread command. The data for the | |
| // Thread command is given in /usr/include/mach-o.h, but it is basically: | |
| // | |
| // uint32_t flavor - this is the flavor argument you would pass to | |
| // thread_get_state | |
| // uint32_t count - this is the count of longs in the thread state data | |
| // struct XXX_thread_state state - this is the structure from | |
| // <machine/thread_status.h> corresponding to the flavor. | |
| // <repeat this trio> | |
| // | |
| // So we just keep reading the various register flavors till we find the GPR | |
| // one, then read the PC out of there. | |
| // FIXME: We will need to have a "RegisterContext data provider" class at some | |
| // point that can get all the registers | |
| // out of data in this form & attach them to a given thread. That should | |
| // underlie the MacOS X User process plugin, and we'll also need it for the | |
| // MacOS X Core File process plugin. When we have that we can also use it | |
| // here. | |
| // | |
| // For now we hard-code the offsets and flavors we need: | |
| // | |
| // | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| llvm::MachO::load_command load_cmd; | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| uint32_t i; | |
| lldb::addr_t start_address = LLDB_INVALID_ADDRESS; | |
| bool done = false; | |
| for (i = 0; i < m_header.ncmds; ++i) { | |
| const lldb::offset_t cmd_offset = offset; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, load_cmd)) | |
| break; | |
| switch (load_cmd.cmd) { | |
| case LC_UNIXTHREAD: | |
| case LC_THREAD: { | |
| while (offset < cmd_offset + load_cmd.cmdsize) { | |
| uint32_t flavor = m_data_nsp->GetU32(&offset); | |
| uint32_t count = m_data_nsp->GetU32(&offset); | |
| if (count == 0) { | |
| // We've gotten off somehow, log and exit; | |
| return m_entry_point_address; | |
| } | |
| switch (m_header.cputype) { | |
| case llvm::MachO::CPU_TYPE_ARM: | |
| if (flavor == 1 || | |
| flavor == 9) // ARM_THREAD_STATE/ARM_THREAD_STATE32 | |
| // from mach/arm/thread_status.h | |
| { | |
| offset += 60; // This is the offset of pc in the GPR thread state | |
| // data structure. | |
| start_address = m_data_nsp->GetU32(&offset); | |
| done = true; | |
| } | |
| break; | |
| case llvm::MachO::CPU_TYPE_ARM64: | |
| case llvm::MachO::CPU_TYPE_ARM64_32: | |
| if (flavor == 6) // ARM_THREAD_STATE64 from mach/arm/thread_status.h | |
| { | |
| offset += 256; // This is the offset of pc in the GPR thread state | |
| // data structure. | |
| start_address = m_data_nsp->GetU64(&offset); | |
| done = true; | |
| } | |
| break; | |
| case llvm::MachO::CPU_TYPE_X86_64: | |
| if (flavor == | |
| 4) // x86_THREAD_STATE64 from mach/i386/thread_status.h | |
| { | |
| offset += 16 * 8; // This is the offset of rip in the GPR thread | |
| // state data structure. | |
| start_address = m_data_nsp->GetU64(&offset); | |
| done = true; | |
| } | |
| break; | |
| default: | |
| return m_entry_point_address; | |
| } | |
| // Haven't found the GPR flavor yet, skip over the data for this | |
| // flavor: | |
| if (done) | |
| break; | |
| offset += count * 4; | |
| } | |
| } break; | |
| case LC_MAIN: { | |
| uint64_t entryoffset = m_data_nsp->GetU64(&offset); | |
| SectionSP text_segment_sp = | |
| GetSectionList()->FindSectionByName(GetSegmentNameTEXT()); | |
| if (text_segment_sp) { | |
| done = true; | |
| start_address = text_segment_sp->GetFileAddress() + entryoffset; | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| if (done) | |
| break; | |
| // Go to the next load command: | |
| offset = cmd_offset + load_cmd.cmdsize; | |
| } | |
| if (start_address == LLDB_INVALID_ADDRESS && IsDynamicLoader()) { | |
| if (GetSymtab()) { | |
| const Symbol *dyld_start_sym = | |
| GetSymtab()->FindFirstSymbolWithNameAndType( | |
| ConstString("_dyld_start"), SymbolType::eSymbolTypeCode, | |
| Symtab::eDebugAny, Symtab::eVisibilityAny); | |
| if (dyld_start_sym && dyld_start_sym->GetAddress().IsValid()) { | |
| start_address = dyld_start_sym->GetAddress().GetFileAddress(); | |
| } | |
| } | |
| } | |
| if (start_address != LLDB_INVALID_ADDRESS) { | |
| // We got the start address from the load commands, so now resolve that | |
| // address in the sections of this ObjectFile: | |
| if (!m_entry_point_address.ResolveAddressUsingFileSections( | |
| start_address, GetSectionList())) { | |
| m_entry_point_address.Clear(); | |
| } | |
| } else { | |
| // We couldn't read the UnixThread load command - maybe it wasn't there. | |
| // As a fallback look for the "start" symbol in the main executable. | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| SymbolContextList contexts; | |
| SymbolContext context; | |
| module_sp->FindSymbolsWithNameAndType(ConstString("start"), | |
| eSymbolTypeCode, contexts); | |
| if (contexts.GetSize()) { | |
| if (contexts.GetContextAtIndex(0, context)) | |
| m_entry_point_address = context.symbol->GetAddress(); | |
| } | |
| } | |
| } | |
| } | |
| return m_entry_point_address; | |
| } | |
| lldb_private::Address ObjectFileMachO::GetBaseAddress() { | |
| lldb_private::Address header_addr; | |
| SectionList *section_list = GetSectionList(); | |
| if (section_list) { | |
| SectionSP text_segment_sp( | |
| section_list->FindSectionByName(GetSegmentNameTEXT())); | |
| if (text_segment_sp) | |
| header_addr = Address(text_segment_sp, /*offset=*/0); | |
| } | |
| return header_addr; | |
| } | |
| uint32_t ObjectFileMachO::GetNumThreadContexts() { | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| if (!m_thread_context_offsets_valid) { | |
| m_thread_context_offsets_valid = true; | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| FileRangeArray::Entry file_range; | |
| llvm::MachO::thread_command thread_cmd; | |
| for (uint32_t i = 0; i < m_header.ncmds; ++i) { | |
| const uint32_t cmd_offset = offset; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, thread_cmd)) | |
| break; | |
| if (thread_cmd.cmd == LC_THREAD) { | |
| file_range.SetRangeBase(offset); | |
| file_range.SetByteSize(thread_cmd.cmdsize - 8); | |
| m_thread_context_offsets.Append(file_range); | |
| } | |
| offset = cmd_offset + thread_cmd.cmdsize; | |
| } | |
| } | |
| } | |
| return m_thread_context_offsets.GetSize(); | |
| } | |
| std::vector<std::tuple<offset_t, offset_t>> | |
| ObjectFileMachO::FindLC_NOTEByName(std::string name) { | |
| std::vector<std::tuple<offset_t, offset_t>> results; | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| for (uint32_t i = 0; i < m_header.ncmds; ++i) { | |
| const uint32_t cmd_offset = offset; | |
| llvm::MachO::load_command lc = {}; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, lc)) | |
| break; | |
| if (lc.cmd == LC_NOTE) { | |
| char data_owner[17]; | |
| m_data_nsp->CopyData(offset, 16, data_owner); | |
| data_owner[16] = '\0'; | |
| offset += 16; | |
| if (name == data_owner) { | |
| offset_t payload_offset = m_data_nsp->GetU64_unchecked(&offset); | |
| offset_t payload_size = m_data_nsp->GetU64_unchecked(&offset); | |
| results.push_back({payload_offset, payload_size}); | |
| } | |
| } | |
| offset = cmd_offset + lc.cmdsize; | |
| } | |
| } | |
| return results; | |
| } | |
| std::string ObjectFileMachO::GetIdentifierString() { | |
| Log *log( | |
| GetLog(LLDBLog::Symbols | LLDBLog::Process | LLDBLog::DynamicLoader)); | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| auto lc_notes = FindLC_NOTEByName("kern ver str"); | |
| for (auto lc_note : lc_notes) { | |
| offset_t payload_offset = std::get<0>(lc_note); | |
| offset_t payload_size = std::get<1>(lc_note); | |
| uint32_t version; | |
| if (m_data_nsp->GetU32(&payload_offset, &version, 1) != nullptr) { | |
| if (version == 1) { | |
| uint32_t strsize = payload_size - sizeof(uint32_t); | |
| std::string result(strsize, '\0'); | |
| m_data_nsp->CopyData(payload_offset, strsize, result.data()); | |
| LLDB_LOGF(log, "LC_NOTE 'kern ver str' found with text '%s'", | |
| result.c_str()); | |
| return result; | |
| } | |
| } | |
| } | |
| // Second, make a pass over the load commands looking for an obsolete | |
| // LC_IDENT load command. | |
| offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| for (uint32_t i = 0; i < m_header.ncmds; ++i) { | |
| const uint32_t cmd_offset = offset; | |
| llvm::MachO::ident_command ident_command; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, ident_command)) | |
| break; | |
| if (ident_command.cmd == LC_IDENT && ident_command.cmdsize != 0) { | |
| std::string result(ident_command.cmdsize, '\0'); | |
| if (m_data_nsp->CopyData(offset, ident_command.cmdsize, | |
| result.data()) == ident_command.cmdsize) { | |
| LLDB_LOGF(log, "LC_IDENT found with text '%s'", result.c_str()); | |
| return result; | |
| } | |
| } | |
| offset = cmd_offset + ident_command.cmdsize; | |
| } | |
| } | |
| return {}; | |
| } | |
| AddressableBits ObjectFileMachO::GetAddressableBits() { | |
| AddressableBits addressable_bits; | |
| Log *log(GetLog(LLDBLog::Process)); | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| auto lc_notes = FindLC_NOTEByName("addrable bits"); | |
| for (auto lc_note : lc_notes) { | |
| offset_t payload_offset = std::get<0>(lc_note); | |
| uint32_t version; | |
| if (m_data_nsp->GetU32(&payload_offset, &version, 1) != nullptr) { | |
| if (version == 3) { | |
| uint32_t num_addr_bits = | |
| m_data_nsp->GetU32_unchecked(&payload_offset); | |
| addressable_bits.SetAddressableBits(num_addr_bits); | |
| LLDB_LOGF(log, | |
| "LC_NOTE 'addrable bits' v3 found, value %d " | |
| "bits", | |
| num_addr_bits); | |
| } | |
| if (version == 4) { | |
| uint32_t lo_addr_bits = m_data_nsp->GetU32_unchecked(&payload_offset); | |
| uint32_t hi_addr_bits = m_data_nsp->GetU32_unchecked(&payload_offset); | |
| if (lo_addr_bits == hi_addr_bits) | |
| addressable_bits.SetAddressableBits(lo_addr_bits); | |
| else | |
| addressable_bits.SetAddressableBits(lo_addr_bits, hi_addr_bits); | |
| LLDB_LOGF(log, "LC_NOTE 'addrable bits' v4 found, value %d & %d bits", | |
| lo_addr_bits, hi_addr_bits); | |
| } | |
| } | |
| } | |
| } | |
| return addressable_bits; | |
| } | |
| bool ObjectFileMachO::GetCorefileMainBinaryInfo(addr_t &value, | |
| bool &value_is_offset, | |
| UUID &uuid, | |
| ObjectFile::BinaryType &type) { | |
| Log *log( | |
| GetLog(LLDBLog::Symbols | LLDBLog::Process | LLDBLog::DynamicLoader)); | |
| value = LLDB_INVALID_ADDRESS; | |
| value_is_offset = false; | |
| uuid.Clear(); | |
| uint32_t log2_pagesize = 0; // not currently passed up to caller | |
| uint32_t platform = 0; // not currently passed up to caller | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| auto lc_notes = FindLC_NOTEByName("main bin spec"); | |
| for (auto lc_note : lc_notes) { | |
| offset_t payload_offset = std::get<0>(lc_note); | |
| // struct main_bin_spec | |
| // { | |
| // uint32_t version; // currently 2 | |
| // uint32_t type; // 0 == unspecified, | |
| // // 1 == kernel | |
| // // 2 == user process, | |
| // dyld mach-o binary addr | |
| // // 3 == standalone binary | |
| // // 4 == user process, | |
| // // dyld_all_image_infos addr | |
| // uint64_t address; // UINT64_MAX if address not specified | |
| // uint64_t slide; // slide, UINT64_MAX if unspecified | |
| // // 0 if no slide needs to be applied to | |
| // // file address | |
| // uuid_t uuid; // all zero's if uuid not specified | |
| // uint32_t log2_pagesize; // process page size in log base 2, | |
| // // e.g. 4k pages are 12. | |
| // // 0 for unspecified | |
| // uint32_t platform; // The Mach-O platform for this corefile. | |
| // // 0 for unspecified. | |
| // // The values are defined in | |
| // // <mach-o/loader.h>, PLATFORM_*. | |
| // } __attribute((packed)); | |
| // "main bin spec" (main binary specification) data payload is | |
| // formatted: | |
| // uint32_t version [currently 1] | |
| // uint32_t type [0 == unspecified, 1 == kernel, | |
| // 2 == user process, 3 == firmware ] | |
| // uint64_t address [ UINT64_MAX if address not specified ] | |
| // uuid_t uuid [ all zero's if uuid not specified ] | |
| // uint32_t log2_pagesize [ process page size in log base | |
| // 2, e.g. 4k pages are 12. | |
| // 0 for unspecified ] | |
| // uint32_t unused [ for alignment ] | |
| uint32_t version; | |
| if (m_data_nsp->GetU32(&payload_offset, &version, 1) != nullptr && | |
| version <= 2) { | |
| uint32_t binspec_type = 0; | |
| uuid_t raw_uuid; | |
| memset(raw_uuid, 0, sizeof(uuid_t)); | |
| if (!m_data_nsp->GetU32(&payload_offset, &binspec_type, 1)) | |
| return false; | |
| if (!m_data_nsp->GetU64(&payload_offset, &value, 1)) | |
| return false; | |
| uint64_t slide = LLDB_INVALID_ADDRESS; | |
| if (version > 1 && !m_data_nsp->GetU64(&payload_offset, &slide, 1)) | |
| return false; | |
| if (value == LLDB_INVALID_ADDRESS && slide != LLDB_INVALID_ADDRESS) { | |
| value = slide; | |
| value_is_offset = true; | |
| } | |
| if (m_data_nsp->CopyData(payload_offset, sizeof(uuid_t), raw_uuid) != | |
| 0) { | |
| uuid = UUID(raw_uuid, sizeof(uuid_t)); | |
| // convert the "main bin spec" type into our | |
| // ObjectFile::BinaryType enum | |
| const char *typestr = "unrecognized type"; | |
| type = eBinaryTypeInvalid; | |
| switch (binspec_type) { | |
| case 0: | |
| type = eBinaryTypeUnknown; | |
| typestr = "uknown"; | |
| break; | |
| case 1: | |
| type = eBinaryTypeKernel; | |
| typestr = "xnu kernel"; | |
| break; | |
| case 2: | |
| type = eBinaryTypeUser; | |
| typestr = "userland dyld"; | |
| break; | |
| case 3: | |
| type = eBinaryTypeStandalone; | |
| typestr = "standalone"; | |
| break; | |
| case 4: | |
| type = eBinaryTypeUserAllImageInfos; | |
| typestr = "userland dyld_all_image_infos"; | |
| break; | |
| } | |
| LLDB_LOGF(log, | |
| "LC_NOTE 'main bin spec' found, version %d type %d " | |
| "(%s), value 0x%" PRIx64 " value-is-slide==%s uuid %s", | |
| version, type, typestr, value, | |
| value_is_offset ? "true" : "false", | |
| uuid.GetAsString().c_str()); | |
| if (!m_data_nsp->GetU32(&payload_offset, &log2_pagesize, 1)) | |
| return false; | |
| if (version > 1 && !m_data_nsp->GetU32(&payload_offset, &platform, 1)) | |
| return false; | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| bool ObjectFileMachO::GetCorefileThreadExtraInfos( | |
| std::vector<lldb::tid_t> &tids) { | |
| tids.clear(); | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| Log *log(GetLog(LLDBLog::Object | LLDBLog::Process | LLDBLog::Thread)); | |
| if (StructuredData::ObjectSP object_sp = GetCorefileProcessMetadata()) { | |
| StructuredData::Dictionary *dict = object_sp->GetAsDictionary(); | |
| StructuredData::Array *threads; | |
| if (!dict->GetValueForKeyAsArray("threads", threads) || !threads) { | |
| LLDB_LOGF(log, | |
| "'process metadata' LC_NOTE does not have a 'threads' key"); | |
| return false; | |
| } | |
| if (threads->GetSize() != GetNumThreadContexts()) { | |
| LLDB_LOGF(log, "Unable to read 'process metadata' LC_NOTE, number of " | |
| "threads does not match number of LC_THREADS."); | |
| return false; | |
| } | |
| const size_t num_threads = threads->GetSize(); | |
| for (size_t i = 0; i < num_threads; i++) { | |
| std::optional<StructuredData::Dictionary *> maybe_thread = | |
| threads->GetItemAtIndexAsDictionary(i); | |
| if (!maybe_thread) { | |
| LLDB_LOGF(log, | |
| "Unable to read 'process metadata' LC_NOTE, threads " | |
| "array does not have a dictionary at index %zu.", | |
| i); | |
| return false; | |
| } | |
| StructuredData::Dictionary *thread = *maybe_thread; | |
| lldb::tid_t tid = LLDB_INVALID_THREAD_ID; | |
| if (thread->GetValueForKeyAsInteger<lldb::tid_t>("thread_id", tid)) | |
| if (tid == 0) | |
| tid = LLDB_INVALID_THREAD_ID; | |
| tids.push_back(tid); | |
| } | |
| if (log) { | |
| StreamString logmsg; | |
| logmsg.Printf("LC_NOTE 'process metadata' found: "); | |
| dict->Dump(logmsg, /* pretty_print */ false); | |
| LLDB_LOGF(log, "%s", logmsg.GetData()); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| StructuredData::ObjectSP ObjectFileMachO::GetCorefileProcessMetadata() { | |
| ModuleSP module_sp(GetModule()); | |
| if (!module_sp) | |
| return {}; | |
| Log *log(GetLog(LLDBLog::Object | LLDBLog::Process | LLDBLog::Thread)); | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| auto lc_notes = FindLC_NOTEByName("process metadata"); | |
| if (lc_notes.size() == 0) | |
| return {}; | |
| if (lc_notes.size() > 1) | |
| LLDB_LOGF( | |
| log, | |
| "Multiple 'process metadata' LC_NOTEs found, only using the first."); | |
| auto [payload_offset, strsize] = lc_notes[0]; | |
| std::string buf(strsize, '\0'); | |
| if (m_data_nsp->CopyData(payload_offset, strsize, buf.data()) != strsize) { | |
| LLDB_LOGF(log, | |
| "Unable to read %" PRIu64 | |
| " bytes of 'process metadata' LC_NOTE JSON contents", | |
| strsize); | |
| return {}; | |
| } | |
| while (buf.back() == '\0') | |
| buf.resize(buf.size() - 1); | |
| StructuredData::ObjectSP object_sp = StructuredData::ParseJSON(buf); | |
| if (!object_sp) { | |
| LLDB_LOGF(log, "Unable to read 'process metadata' LC_NOTE, did not " | |
| "parse as valid JSON."); | |
| return {}; | |
| } | |
| StructuredData::Dictionary *dict = object_sp->GetAsDictionary(); | |
| if (!dict) { | |
| LLDB_LOGF(log, "Unable to read 'process metadata' LC_NOTE, did not " | |
| "get a dictionary."); | |
| return {}; | |
| } | |
| return object_sp; | |
| } | |
| lldb::RegisterContextSP | |
| ObjectFileMachO::GetThreadContextAtIndex(uint32_t idx, | |
| lldb_private::Thread &thread) { | |
| lldb::RegisterContextSP reg_ctx_sp; | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| if (!m_thread_context_offsets_valid) | |
| GetNumThreadContexts(); | |
| const FileRangeArray::Entry *thread_context_file_range = | |
| m_thread_context_offsets.GetEntryAtIndex(idx); | |
| if (thread_context_file_range) { | |
| DataExtractor data(*m_data_nsp, thread_context_file_range->GetRangeBase(), | |
| thread_context_file_range->GetByteSize()); | |
| switch (m_header.cputype) { | |
| case llvm::MachO::CPU_TYPE_ARM64: | |
| case llvm::MachO::CPU_TYPE_ARM64_32: | |
| reg_ctx_sp = | |
| std::make_shared<RegisterContextDarwin_arm64_Mach>(thread, data); | |
| break; | |
| case llvm::MachO::CPU_TYPE_ARM: | |
| reg_ctx_sp = | |
| std::make_shared<RegisterContextDarwin_arm_Mach>(thread, data); | |
| break; | |
| case llvm::MachO::CPU_TYPE_X86_64: | |
| reg_ctx_sp = | |
| std::make_shared<RegisterContextDarwin_x86_64_Mach>(thread, data); | |
| break; | |
| case llvm::MachO::CPU_TYPE_RISCV: | |
| reg_ctx_sp = | |
| std::make_shared<RegisterContextDarwin_riscv32_Mach>(thread, data); | |
| break; | |
| } | |
| } | |
| } | |
| return reg_ctx_sp; | |
| } | |
| ObjectFile::Type ObjectFileMachO::CalculateType() { | |
| switch (m_header.filetype) { | |
| case MH_OBJECT: // 0x1u | |
| if (GetAddressByteSize() == 4) { | |
| // 32 bit kexts are just object files, but they do have a valid | |
| // UUID load command. | |
| if (GetUUID()) { | |
| // this checking for the UUID load command is not enough we could | |
| // eventually look for the symbol named "OSKextGetCurrentIdentifier" as | |
| // this is required of kexts | |
| if (m_strata == eStrataInvalid) | |
| m_strata = eStrataKernel; | |
| return eTypeSharedLibrary; | |
| } | |
| } | |
| return eTypeObjectFile; | |
| case MH_EXECUTE: | |
| return eTypeExecutable; // 0x2u | |
| case MH_FVMLIB: | |
| return eTypeSharedLibrary; // 0x3u | |
| case MH_CORE: | |
| return eTypeCoreFile; // 0x4u | |
| case MH_PRELOAD: | |
| return eTypeSharedLibrary; // 0x5u | |
| case MH_DYLIB: | |
| return eTypeSharedLibrary; // 0x6u | |
| case MH_DYLINKER: | |
| return eTypeDynamicLinker; // 0x7u | |
| case MH_BUNDLE: | |
| return eTypeSharedLibrary; // 0x8u | |
| case MH_DYLIB_STUB: | |
| return eTypeStubLibrary; // 0x9u | |
| case MH_DSYM: | |
| return eTypeDebugInfo; // 0xAu | |
| case MH_KEXT_BUNDLE: | |
| return eTypeSharedLibrary; // 0xBu | |
| default: | |
| break; | |
| } | |
| return eTypeUnknown; | |
| } | |
| ObjectFile::Strata ObjectFileMachO::CalculateStrata() { | |
| switch (m_header.filetype) { | |
| case MH_OBJECT: // 0x1u | |
| { | |
| // 32 bit kexts are just object files, but they do have a valid | |
| // UUID load command. | |
| if (GetUUID()) { | |
| // this checking for the UUID load command is not enough we could | |
| // eventually look for the symbol named "OSKextGetCurrentIdentifier" as | |
| // this is required of kexts | |
| if (m_type == eTypeInvalid) | |
| m_type = eTypeSharedLibrary; | |
| return eStrataKernel; | |
| } | |
| } | |
| return eStrataUnknown; | |
| case MH_EXECUTE: // 0x2u | |
| // Check for the MH_DYLDLINK bit in the flags | |
| if (m_header.flags & MH_DYLDLINK) { | |
| return eStrataUser; | |
| } else { | |
| SectionList *section_list = GetSectionList(); | |
| if (section_list) { | |
| if (section_list->FindSectionByName("__KLD")) | |
| return eStrataKernel; | |
| } | |
| } | |
| return eStrataRawImage; | |
| case MH_FVMLIB: | |
| return eStrataUser; // 0x3u | |
| case MH_CORE: | |
| return eStrataUnknown; // 0x4u | |
| case MH_PRELOAD: | |
| return eStrataRawImage; // 0x5u | |
| case MH_DYLIB: | |
| return eStrataUser; // 0x6u | |
| case MH_DYLINKER: | |
| return eStrataUser; // 0x7u | |
| case MH_BUNDLE: | |
| return eStrataUser; // 0x8u | |
| case MH_DYLIB_STUB: | |
| return eStrataUser; // 0x9u | |
| case MH_DSYM: | |
| return eStrataUnknown; // 0xAu | |
| case MH_KEXT_BUNDLE: | |
| return eStrataKernel; // 0xBu | |
| default: | |
| break; | |
| } | |
| return eStrataUnknown; | |
| } | |
| llvm::VersionTuple ObjectFileMachO::GetVersion() { | |
| ModuleSP module_sp(GetModule()); | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| llvm::MachO::dylib_command load_cmd; | |
| lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); | |
| uint32_t version_cmd = 0; | |
| uint64_t version = 0; | |
| uint32_t i; | |
| for (i = 0; i < m_header.ncmds; ++i) { | |
| const lldb::offset_t cmd_offset = offset; | |
| if (!ReadMachOCommand(*m_data_nsp, offset, load_cmd)) | |
| break; | |
| if (load_cmd.cmd == LC_ID_DYLIB) { | |
| if (version_cmd == 0) { | |
| version_cmd = load_cmd.cmd; | |
| if (m_data_nsp->GetU32(&offset, &load_cmd.dylib, 4) == nullptr) | |
| break; | |
| version = load_cmd.dylib.current_version; | |
| } | |
| break; // Break for now unless there is another more complete version | |
| // number load command in the future. | |
| } | |
| offset = cmd_offset + load_cmd.cmdsize; | |
| } | |
| if (version_cmd == LC_ID_DYLIB) { | |
| unsigned major = (version & 0xFFFF0000ull) >> 16; | |
| unsigned minor = (version & 0x0000FF00ull) >> 8; | |
| unsigned subminor = (version & 0x000000FFull); | |
| return llvm::VersionTuple(major, minor, subminor); | |
| } | |
| } | |
| return llvm::VersionTuple(); | |
| } | |
| ArchSpec ObjectFileMachO::GetArchitecture() { | |
| ModuleSP module_sp(GetModule()); | |
| ArchSpec arch; | |
| if (module_sp) { | |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); | |
| return GetArchitecture(module_sp, m_header, *m_data_nsp, | |
| MachHeaderSizeFromMagic(m_header.magic)); | |
| } | |
| return arch; | |
| } | |
| void ObjectFileMachO::GetProcessSharedCacheUUID(Process *process, | |
| addr_t &base_addr, UUID &uuid) { | |
| uuid.Clear(); | |
| base_addr = LLDB_INVALID_ADDRESS; | |
| if (process && process->GetDynamicLoader()) { | |
| DynamicLoader *dl = process->GetDynamicLoader(); | |
| LazyBool using_shared_cache; | |
| LazyBool private_shared_cache; | |
| FileSpec sc_filepath; | |
| std::optional<uint64_t> size; | |
| dl->GetSharedCacheInformation(base_addr, uuid, using_shared_cache, | |
| private_shared_cache, sc_filepath, size); | |
| } | |
| Log *log(GetLog(LLDBLog::Symbols | LLDBLog::Process)); | |
| LLDB_LOGF( | |
| log, | |
| "inferior process shared cache has a UUID of %s, base address 0x%" PRIx64, | |
| uuid.GetAsString().c_str(), base_addr); | |
| } | |
| // From dyld SPI header dyld_process_info.h | |
| typedef void *dyld_process_info; | |
| struct lldb_copy__dyld_process_cache_info { | |
| uuid_t cacheUUID; // UUID of cache used by process | |
| uint64_t cacheBaseAddress; // load address of dyld shared cache | |
| bool noCache; // process is running without a dyld cache | |
| bool privateCache; // process is using a private copy of its dyld cache | |
| }; | |
| // #including mach/mach.h pulls in machine.h & CPU_TYPE_ARM etc conflicts with | |
| // llvm enum definitions llvm::MachO::CPU_TYPE_ARM turning them into compile | |
| // errors. So we need to use the actual underlying types of task_t and | |
| // kern_return_t below. | |
| extern "C" unsigned int /*task_t*/ mach_task_self(); | |
| void ObjectFileMachO::GetLLDBSharedCacheUUID(addr_t &base_addr, UUID &uuid) { | |
| uuid.Clear(); | |
| base_addr = LLDB_INVALID_ADDRESS; | |
| #if defined(__APPLE__) | |
| uint8_t *(*dyld_get_all_image_infos)(void); | |
| dyld_get_all_image_infos = | |
| (uint8_t * (*)()) dlsym(RTLD_DEFAULT, "_dyld_get_all_image_infos"); | |
| if (dyld_get_all_image_infos) { | |
| uint8_t *dyld_all_image_infos_address = dyld_get_all_image_infos(); | |
| if (dyld_all_image_infos_address) { | |
| uint32_t *version = (uint32_t *) | |
| dyld_all_image_infos_address; // version <mach-o/dyld_images.h> | |
| if (*version >= 13) { | |
| uuid_t *sharedCacheUUID_address = 0; | |
| int wordsize = sizeof(uint8_t *); | |
| if (wordsize == 8) { | |
| sharedCacheUUID_address = | |
| (uuid_t *)((uint8_t *)dyld_all_image_infos_address + | |
| 160); // sharedCacheUUID <mach-o/dyld_images.h> | |
| if (*version >= 15) | |
| base_addr = | |
| *(uint64_t | |
| *)((uint8_t *)dyld_all_image_infos_address + | |
| 176); // sharedCacheBaseAddress <mach-o/dyld_images.h> | |
| } else { | |
| sharedCacheUUID_address = | |
| (uuid_t *)((uint8_t *)dyld_all_image_infos_address + | |
| 84); // sharedCacheUUID <mach-o/dyld_images.h> | |
| if (*version >= 15) { | |
| base_addr = 0; | |
| base_addr = | |
| *(uint32_t | |
| *)((uint8_t *)dyld_all_image_infos_address + | |
| 100); // sharedCacheBaseAddress <mach-o/dyld_images.h> | |
| } | |
| } | |
| uuid = UUID(sharedCacheUUID_address, sizeof(uuid_t)); | |
| } | |
| } | |
| } else { | |
| // Exists in macOS 10.12 and later, iOS 10.0 and later - dyld SPI | |
| dyld_process_info (*dyld_process_info_create)( | |
| unsigned int /* task_t */ task, uint64_t timestamp, | |
| unsigned int /*kern_return_t*/ *kernelError); | |
| void (*dyld_process_info_get_cache)(void *info, void *cacheInfo); | |
| void (*dyld_process_info_release)(dyld_process_info info); | |
| dyld_process_info_create = (void *(*)(unsigned int /* task_t */, uint64_t, | |
| unsigned int /*kern_return_t*/ *)) | |
| dlsym(RTLD_DEFAULT, "_dyld_process_info_create"); | |
| dyld_process_info_get_cache = (void (*)(void *, void *))dlsym( | |
| RTLD_DEFAULT, "_dyld_process_info_get_cache"); | |
| dyld_process_info_release = | |
| (void (*)(void *))dlsym(RTLD_DEFAULT, "_dyld_process_info_release"); | |
| if (dyld_process_info_create && dyld_process_info_get_cache) { | |
| unsigned int /*kern_return_t */ kern_ret; | |
| dyld_process_info process_info = | |
| dyld_process_info_create(::mach_task_self(), 0, &kern_ret); | |
| if (process_info) { | |
| struct lldb_copy__dyld_process_cache_info sc_info; | |
| memset(&sc_info, 0, sizeof(struct lldb_copy__dyld_process_cache_info)); | |
| dyld_process_info_get_cache(process_info, &sc_info); | |
| if (sc_info.cacheBaseAddress != 0) { | |
| base_addr = sc_info.cacheBaseAddress; | |
| uuid = UUID(sc_info.cacheUUID, sizeof(uuid_t)); | |
| } | |
| dyld_process_info_release(process_info); | |
| } | |
| } | |
| } | |
| Log *log(GetLog(LLDBLog::Symbols | LLDBLog::Process)); | |
| if (log && uuid.IsValid()) | |
| LLDB_LOGF(log, | |
| "lldb's in-memory shared cache has a UUID of %s base address of " | |
| "0x%" PRIx64, | |
| uuid.GetAsString().c_str(), base_addr); | |
| #endif | |
| } | |
| static llvm::VersionTuple FindMinimumVersionInfo(DataExtractor &data, | |
| lldb::offset_t offset, | |
| size_t ncmds) { | |
| for (size_t i = 0; i < ncmds; i++) { | |
| const lldb::offset_t load_cmd_offset = offset; | |
| llvm::MachO::load_command lc = {}; | |
| if (!ReadMachOCommand(data, offset, lc)) | |
| break; | |
| uint32_t version = 0; | |
| if (lc.cmd == llvm::MachO::LC_VERSION_MIN_MACOSX || | |
| lc.cmd == llvm::MachO::LC_VERSION_MIN_IPHONEOS || | |
| lc.cmd == llvm::MachO::LC_VERSION_MIN_TVOS || | |
| lc.cmd == llvm::MachO::LC_VERSION_MIN_WATCHOS) { | |
| // struct version_min_command { | |
| // uint32_t cmd; // LC_VERSION_MIN_* | |
| // uint32_t cmdsize; | |
| // uint32_t version; // X.Y.Z encoded in nibbles xxxx.yy.zz | |
| // uint32_t sdk; | |
| // }; | |
| // We want to read version. | |
| version = data.GetU32(&offset); | |
| } else if (lc.cmd == llvm::MachO::LC_BUILD_VERSION) { | |
| // struct build_version_command { | |
| // uint32_t cmd; // LC_BUILD_VERSION | |
| // uint32_t cmdsize; | |
| // uint32_t platform; | |
| // uint32_t minos; // X.Y.Z encoded in nibbles xxxx.yy.zz | |
| // uint32_t sdk; | |
| // uint32_t ntools; | |
| // }; | |
| // We want to read minos. | |
| offset += sizeof(uint32_t); // Skip over platform | |
| version = data.GetU32(&offset); // Extract minos | |
| } | |
| if (version) { | |
| const uint32_t xxxx = version >> 16; | |
| const uint32_t yy = (version >> 8) & 0xffu; | |
| const uint32_t zz = version & 0xffu; | |
| if (xxxx) | |
| return llvm::VersionTuple(xxxx, yy, zz); | |
| } | |
| offset = load_cmd_offset + lc.cmdsize; | |
| } | |
| return llvm::VersionTuple(); | |
| } | |
| llvm::VersionTuple ObjectFileMachO::GetMinimumOSVersion() { | |
| if (!m_min_os_version) | |
| m_min_os_version = FindMinimumVersionInfo( | |
| *m_data_nsp, MachHeaderSizeFromMagic(m_header.magic), m_header.ncmds); | |
| return *m_min_os_version; | |
| } | |
| llvm::VersionTuple ObjectFileMachO::GetSDKVersion() { | |
| if (!m_sdk_versions) | |
| m_sdk_versions = FindMinimumVersionInfo( | |
| *m_data_nsp, MachHeaderSizeFromMagic(m_header.magic), m_header.ncmds); | |
| return *m_sdk_versions; | |
| } | |
| bool ObjectFileMachO::GetIsDynamicLinkEditor() { | |
| return m_header.filetype == llvm::MachO::MH_DYLINKER; | |
| } | |
| bool ObjectFileMachO::CanTrustAddressRanges() { | |
| // Dsymutil guarantees that the .debug_aranges accelerator is complete and can | |
| // be trusted by LLDB. | |
| return m_header.filetype == llvm::MachO::MH_DSYM; | |
| } | |
| bool ObjectFileMachO::AllowAssemblyEmulationUnwindPlans() { | |
| return m_allow_assembly_emulation_unwind_plans; | |
| } | |
| Section *ObjectFileMachO::GetMachHeaderSection() { | |
| // Find the first address of the mach header which is the first non-zero file | |
| // sized section whose file offset is zero. This is the base file address of | |
| // the mach-o file which can be subtracted from the vmaddr of the other | |
| // segments found in memory and added to the load address | |
| ModuleSP module_sp = GetModule(); | |
| if (!module_sp) | |
| return nullptr; | |
| SectionList *section_list = GetSectionList(); | |
| if (!section_list) | |
| return nullptr; | |
| // Some binaries can have a TEXT segment with a non-zero file offset. | |
| // Binaries in the shared cache are one example. Some hand-generated | |
| // binaries may not be laid out in the normal TEXT,DATA,LC_SYMTAB order | |
| // in the file, even though they're laid out correctly in vmaddr terms. | |
| SectionSP text_segment_sp = | |
| section_list->FindSectionByName(GetSegmentNameTEXT()); | |
| if (text_segment_sp.get() && SectionIsLoadable(text_segment_sp.get())) | |
| return text_segment_sp.get(); | |
| const size_t num_sections = section_list->GetSize(); | |
| for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { | |
| Section *section = section_list->GetSectionAtIndex(sect_idx).get(); | |
| if (section->GetFileOffset() == 0 && SectionIsLoadable(section)) | |
| return section; | |
| } | |
| return nullptr; | |
| } | |
| bool ObjectFileMachO::IsGOTSection(const lldb_private::Section §ion) const { | |
| assert(section.GetObjectFile() == this && "Wrong object file!"); | |
| SectionSP segment = section.GetParent(); | |
| if (!segment) | |
| return false; | |
| const bool is_data_const_got = | |
| segment->GetName() == "__DATA_CONST" && section.GetName() == "__got"; | |
| const bool is_auth_const_ptr = | |
| segment->GetName() == "__AUTH_CONST" && | |
| (section.GetName() == "__auth_got" || section.GetName() == "__auth_ptr"); | |
| return is_data_const_got || is_auth_const_ptr; | |
| } | |
| bool ObjectFileMachO::SectionIsLoadable(const Section *section) { | |
| if (!section) | |
| return false; | |
| if (section->IsThreadSpecific()) | |
| return false; | |
| if (GetModule().get() != section->GetModule().get()) | |
| return false; | |
| // firmware style binaries with llvm gcov segment do | |
| // not have that segment mapped into memory. | |
| if (section->GetName() == GetSegmentNameLLVM_COV()) { | |
| const Strata strata = GetStrata(); | |
| if (strata == eStrataKernel || strata == eStrataRawImage) | |
| return false; | |
| } | |
| // Be careful with __LINKEDIT and __DWARF segments | |
| if (section->GetName() == GetSegmentNameLINKEDIT() || | |
| section->GetName() == GetSegmentNameDWARF()) { | |
| // Only map __LINKEDIT and __DWARF if we have an in memory image and | |
| // this isn't a kernel binary like a kext or mach_kernel. | |
| const bool is_memory_image = (bool)m_process_wp.lock(); | |
| const Strata strata = GetStrata(); | |
| if (is_memory_image == false || strata == eStrataKernel) | |
| return false; | |
| } | |
| return true; | |
| } | |
| lldb::addr_t ObjectFileMachO::CalculateSectionLoadAddressForMemoryImage( | |
| lldb::addr_t header_load_address, const Section *header_section, | |
| const Section *section) { | |
| ModuleSP module_sp = GetModule(); | |
| if (module_sp && header_section && section && | |
| header_load_address != LLDB_INVALID_ADDRESS) { | |
| lldb::addr_t file_addr = header_section->GetFileAddress(); | |
| if (file_addr != LLDB_INVALID_ADDRESS && SectionIsLoadable(section)) | |
| return section->GetFileAddress() - file_addr + header_load_address; | |
| } | |
| return LLDB_INVALID_ADDRESS; | |
| } | |
| bool ObjectFileMachO::SetLoadAddress(Target &target, lldb::addr_t value, | |
| bool value_is_offset) { | |
| Log *log(GetLog(LLDBLog::DynamicLoader)); | |
| ModuleSP module_sp = GetModule(); | |
| if (!module_sp) | |
| return false; | |
| SectionList *section_list = GetSectionList(); | |
| if (!section_list) | |
| return false; | |
| size_t num_loaded_sections = 0; | |
| const size_t num_sections = section_list->GetSize(); | |
| // Warn if some top-level segments map to the same address. The binary may be | |
| // malformed. | |
| const bool warn_multiple = true; | |
| if (log) { | |
| StreamString logmsg; | |
| logmsg << "ObjectFileMachO::SetLoadAddress "; | |
| if (GetFileSpec()) | |
| logmsg << "path='" << GetFileSpec().GetPath() << "' "; | |
| if (GetUUID()) { | |
| logmsg << "uuid=" << GetUUID().GetAsString(); | |
| } | |
| LLDB_LOGF(log, "%s", logmsg.GetData()); | |
| } | |
| if (value_is_offset) { | |
| // "value" is an offset to apply to each top level segment | |
| for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { | |
| // Iterate through the object file sections to find all of the | |
| // sections that size on disk (to avoid __PAGEZERO) and load them | |
| SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); | |
| if (SectionIsLoadable(section_sp.get())) { | |
| LLDB_LOG( | |
| log, | |
| "ObjectFileMachO::SetLoadAddress segment '{0}' load addr is {1:x}", | |
| section_sp->GetName(), section_sp->GetFileAddress() + value); | |
| if (target.SetSectionLoadAddress(section_sp, | |
| section_sp->GetFileAddress() + value, | |
| warn_multiple)) | |
| ++num_loaded_sections; | |
| } | |
| } | |
| } else { | |
| // "value" is the new base address of the mach_header, adjust each | |
| // section accordingly | |
| Section *mach_header_section = GetMachHeaderSection(); | |
| if (mach_header_section) { | |
| for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { | |
| SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); | |
| lldb::addr_t section_load_addr = | |
| CalculateSectionLoadAddressForMemoryImage( | |
| value, mach_header_section, section_sp.get()); | |
| if (section_load_addr != LLDB_INVALID_ADDRESS) { | |
| LLDB_LOG(log, | |
| "ObjectFileMachO::SetLoadAddress segment '{0}' load addr is " | |
| "{1:x}", | |
| section_sp->GetName(), section_load_addr); | |
| if (target.SetSectionLoadAddress(section_sp, section_load_addr, | |
| warn_multiple)) | |
| ++num_loaded_sections; | |
| } | |
| } | |
| } | |
| } | |
| return num_loaded_sections > 0; | |
| } | |
| struct all_image_infos_header { | |
| uint32_t version; // currently 1 | |
| uint32_t imgcount; // number of binary images | |
| uint64_t entries_fileoff; // file offset in the corefile of where the array of | |
| // struct entry's begin. | |
| uint32_t entries_size; // size of 'struct entry'. | |
| uint32_t unused; | |
| }; | |
| struct image_entry { | |
| uint64_t filepath_offset; // offset in corefile to c-string of the file path, | |
| // UINT64_MAX if unavailable. | |
| uuid_t uuid; // uint8_t[16]. should be set to all zeroes if | |
| // uuid is unknown. | |
| uint64_t load_address; // UINT64_MAX if unknown. | |
| uint64_t seg_addrs_offset; // offset to the array of struct segment_vmaddr's. | |
| uint32_t segment_count; // The number of segments for this binary. | |
| uint32_t unused; | |
| image_entry() { | |
| filepath_offset = UINT64_MAX; | |
| memset(&uuid, 0, sizeof(uuid_t)); | |
| segment_count = 0; | |
| load_address = UINT64_MAX; | |
| seg_addrs_offset = UINT64_MAX; | |
| unused = 0; | |
| } | |
| image_entry(const image_entry &rhs) { | |
| filepath_offset = rhs.filepath_offset; | |
| memcpy(&uuid, &rhs.uuid, sizeof(uuid_t)); | |
| segment_count = rhs.segment_count; | |
| seg_addrs_offset = rhs.seg_addrs_offset; | |
| load_address = rhs.load_address; | |
| unused = rhs.unused; | |
| } | |
| }; | |
| struct segment_vmaddr { | |
| char segname[16]; | |
| uint64_t vmaddr; | |
| uint64_t unused; | |
| segment_vmaddr() { | |
| memset(&segname, 0, 16); | |
| vmaddr = UINT64_MAX; | |
| unused = 0; | |
| } | |
| segment_vmaddr(const segment_vmaddr &rhs) { | |
| memcpy(&segname, &rhs.segname, 16); | |
| vmaddr = rhs.vmaddr; | |
| unused = rhs.unused; | |
| } | |
| }; | |
| // Write the payload for the "all image infos" LC_NOTE into | |
| // the supplied all_image_infos_payload, assuming that this | |
| // will be written into the corefile starting at | |
| // initial_file_offset. | |
| // | |
| // The placement of this payload is a little tricky. We're | |
| // laying this out as | |
| // | |
| // 1. header (struct all_image_info_header) | |
| // 2. Array of fixed-size (struct image_entry)'s, one | |
| // per binary image present in the process. | |
| // 3. Arrays of (struct segment_vmaddr)'s, a varying number | |
| // for each binary image. | |
| // 4. Variable length c-strings of binary image filepaths, | |
| // one per binary. | |
| // | |
| // To compute where everything will be laid out in the | |
| // payload, we need to iterate over the images and calculate | |
| // how many segment_vmaddr structures each image will need, | |
| // and how long each image's filepath c-string is. There | |
| // are some multiple passes over the image list while calculating | |
| // everything. | |
| static offset_t | |
| CreateAllImageInfosPayload(const lldb::ProcessSP &process_sp, | |
| offset_t initial_file_offset, | |
| StreamString &all_image_infos_payload, | |
| lldb_private::SaveCoreOptions &options) { | |
| Target &target = process_sp->GetTarget(); | |
| ModuleList modules = target.GetImages(); | |
| // stack-only corefiles have no reason to include binaries that | |
| // are not executing; we're trying to make the smallest corefile | |
| // we can, so leave the rest out. | |
| if (options.GetStyle() == SaveCoreStyle::eSaveCoreStackOnly) | |
| modules.Clear(); | |
| std::set<std::string> executing_uuids; | |
| std::vector<ThreadSP> thread_list = | |
| process_sp->CalculateCoreFileThreadList(options); | |
| for (const ThreadSP &thread_sp : thread_list) { | |
| uint32_t stack_frame_count = thread_sp->GetStackFrameCount(); | |
| for (uint32_t j = 0; j < stack_frame_count; j++) { | |
| StackFrameSP stack_frame_sp = thread_sp->GetStackFrameAtIndex(j); | |
| Address pc = stack_frame_sp->GetFrameCodeAddress(); | |
| ModuleSP module_sp = pc.GetModule(); | |
| if (module_sp) { | |
| UUID uuid = module_sp->GetUUID(); | |
| if (uuid.IsValid()) { | |
| executing_uuids.insert(uuid.GetAsString()); | |
| modules.AppendIfNeeded(module_sp); | |
| } | |
| } | |
| } | |
| } | |
| size_t modules_count = modules.GetSize(); | |
| struct all_image_infos_header infos; | |
| infos.version = 1; | |
| infos.imgcount = modules_count; | |
| infos.entries_size = sizeof(image_entry); | |
| infos.entries_fileoff = initial_file_offset + sizeof(all_image_infos_header); | |
| infos.unused = 0; | |
| all_image_infos_payload.PutHex32(infos.version); | |
| all_image_infos_payload.PutHex32(infos.imgcount); | |
| all_image_infos_payload.PutHex64(infos.entries_fileoff); | |
| all_image_infos_payload.PutHex32(infos.entries_size); | |
| all_image_infos_payload.PutHex32(infos.unused); | |
| // First create the structures for all of the segment name+vmaddr vectors | |
| // for each module, so we will know the size of them as we add the | |
| // module entries. | |
| std::vector<std::vector<segment_vmaddr>> modules_segment_vmaddrs; | |
| for (size_t i = 0; i < modules_count; i++) { | |
| ModuleSP module = modules.GetModuleAtIndex(i); | |
| SectionList *sections = module->GetSectionList(); | |
| size_t sections_count = sections->GetSize(); | |
| std::vector<segment_vmaddr> segment_vmaddrs; | |
| for (size_t j = 0; j < sections_count; j++) { | |
| SectionSP section = sections->GetSectionAtIndex(j); | |
| if (!section->GetParent().get()) { | |
| addr_t vmaddr = section->GetLoadBaseAddress(&target); | |
| if (vmaddr == LLDB_INVALID_ADDRESS) | |
| continue; | |
| llvm::StringRef name = section->GetName(); | |
| segment_vmaddr seg_vmaddr; | |
| // This is the uncommon case where strncpy is exactly | |
| // the right one, doesn't need to be null-terminated. | |
| // The segment name in a Mach-O LC_SEGMENT/LC_SEGMENT_64 is char[16] and | |
| // is not guaranteed to be null-terminated if all 16 characters are | |
| // used. | |
| // coverity[buffer_size_warning] | |
| strncpy(seg_vmaddr.segname, name.data(), | |
| std::min(name.size(), sizeof(seg_vmaddr.segname))); | |
| seg_vmaddr.vmaddr = vmaddr; | |
| seg_vmaddr.unused = 0; | |
| segment_vmaddrs.push_back(seg_vmaddr); | |
| } | |
| } | |
| modules_segment_vmaddrs.push_back(segment_vmaddrs); | |
| } | |
| offset_t size_of_vmaddr_structs = 0; | |
| for (size_t i = 0; i < modules_segment_vmaddrs.size(); i++) { | |
| size_of_vmaddr_structs += | |
| modules_segment_vmaddrs[i].size() * sizeof(segment_vmaddr); | |
| } | |
| offset_t size_of_filepath_cstrings = 0; | |
| for (size_t i = 0; i < modules_count; i++) { | |
| ModuleSP module_sp = modules.GetModuleAtIndex(i); | |
| size_of_filepath_cstrings += module_sp->GetFileSpec().GetPath().size() + 1; | |
| } | |
| // Calculate the file offsets of our "all image infos" payload in the | |
| // corefile. initial_file_offset the original value passed in to this method. | |
| offset_t start_of_entries = | |
| initial_file_offset + sizeof(all_image_infos_header); | |
| offset_t start_of_seg_vmaddrs = | |
| start_of_entries + sizeof(image_entry) * modules_count; | |
| offset_t start_of_filenames = start_of_seg_vmaddrs + size_of_vmaddr_structs; | |
| offset_t final_file_offset = start_of_filenames + size_of_filepath_cstrings; | |
| // Now write the one-per-module 'struct image_entry' into the | |
| // StringStream; keep track of where the struct segment_vmaddr | |
| // entries for each module will end up in the corefile. | |
| offset_t current_string_offset = start_of_filenames; | |
| offset_t current_segaddrs_offset = start_of_seg_vmaddrs; | |
| for (size_t i = 0; i < modules_count; i++) { | |
| ModuleSP module_sp = modules.GetModuleAtIndex(i); | |
| struct image_entry ent; | |
| memcpy(&ent.uuid, module_sp->GetUUID().GetBytes().data(), sizeof(ent.uuid)); | |
| if (modules_segment_vmaddrs[i].size() > 0) { | |
| ent.segment_count = modules_segment_vmaddrs[i].size(); | |
| ent.seg_addrs_offset = current_segaddrs_offset; | |
| } | |
| ent.filepath_offset = current_string_offset; | |
| ObjectFile *objfile = module_sp->GetObjectFile(); | |
| if (objfile) { | |
| Address base_addr(objfile->GetBaseAddress()); | |
| if (base_addr.IsValid()) { | |
| ent.load_address = base_addr.GetLoadAddress(&target); | |
| } | |
| } | |
| all_image_infos_payload.PutHex64(ent.filepath_offset); | |
| all_image_infos_payload.PutRawBytes(ent.uuid, sizeof(ent.uuid)); | |
| all_image_infos_payload.PutHex64(ent.load_address); | |
| all_image_infos_payload.PutHex64(ent.seg_addrs_offset); | |
| all_image_infos_payload.PutHex32(ent.segment_count); | |
| if (executing_uuids.find(module_sp->GetUUID().GetAsString()) != | |
| executing_uuids.end()) | |
| all_image_infos_payload.PutHex32(1); | |
| else | |
| all_image_infos_payload.PutHex32(0); | |
| current_segaddrs_offset += ent.segment_count * sizeof(segment_vmaddr); | |
| current_string_offset += module_sp->GetFileSpec().GetPath().size() + 1; | |
| } | |
| // Now write the struct segment_vmaddr entries into the StringStream. | |
| for (size_t i = 0; i < modules_segment_vmaddrs.size(); i++) { | |
| if (modules_segment_vmaddrs[i].size() == 0) | |
| continue; | |
| for (struct segment_vmaddr segvm : modules_segment_vmaddrs[i]) { | |
| all_image_infos_payload.PutRawBytes(segvm.segname, sizeof(segvm.segname)); | |
| all_image_infos_payload.PutHex64(segvm.vmaddr); | |
| all_image_infos_payload.PutHex64(segvm.unused); | |
| } | |
| } | |
| for (size_t i = 0; i < modules_count; i++) { | |
| ModuleSP module_sp = modules.GetModuleAtIndex(i); | |
| std::string filepath = module_sp->GetFileSpec().GetPath(); | |
| all_image_infos_payload.PutRawBytes(filepath.data(), filepath.size() + 1); | |
| } | |
| return final_file_offset; | |
| } | |
| // Temp struct used to combine contiguous memory regions with | |
| // identical permissions. | |
| struct page_object { | |
| addr_t addr; | |
| addr_t size; | |
| uint32_t prot; | |
| }; | |
| bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp, | |
| lldb_private::SaveCoreOptions &options, | |
| Status &error) { | |
| // The FileSpec and Process are already checked in PluginManager::SaveCore. | |
| assert(options.GetOutputFile().has_value()); | |
| assert(process_sp); | |
| const FileSpec outfile = options.GetOutputFile().value(); | |
| // MachO defaults to dirty pages | |
| if (options.GetStyle() == SaveCoreStyle::eSaveCoreUnspecified) | |
| options.SetStyle(eSaveCoreDirtyOnly); | |
| Target &target = process_sp->GetTarget(); | |
| const ArchSpec target_arch = target.GetArchitecture(); | |
| const llvm::Triple &target_triple = target_arch.GetTriple(); | |
| if (target_triple.getVendor() == llvm::Triple::Apple && | |
| (target_triple.getOS() == llvm::Triple::MacOSX || | |
| target_triple.getOS() == llvm::Triple::IOS || | |
| target_triple.getOS() == llvm::Triple::WatchOS || | |
| target_triple.getOS() == llvm::Triple::TvOS || | |
| target_triple.getOS() == llvm::Triple::BridgeOS || | |
| target_triple.getOS() == llvm::Triple::XROS)) { | |
| bool make_core = false; | |
| switch (target_arch.GetMachine()) { | |
| case llvm::Triple::aarch64: | |
| case llvm::Triple::aarch64_32: | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: | |
| case llvm::Triple::x86: | |
| case llvm::Triple::x86_64: | |
| make_core = true; | |
| break; | |
| default: | |
| error = Status::FromErrorStringWithFormat( | |
| "unsupported core architecture: %s", target_triple.str().c_str()); | |
| break; | |
| } | |
| if (make_core) { | |
| CoreFileMemoryRanges core_ranges; | |
| error = process_sp->CalculateCoreFileSaveRanges(options, core_ranges); | |
| if (error.Success()) { | |
| const uint32_t addr_byte_size = target_arch.GetAddressByteSize(); | |
| const ByteOrder byte_order = target_arch.GetByteOrder(); | |
| std::vector<llvm::MachO::segment_command_64> segment_load_commands; | |
| for (const auto &core_range_info : core_ranges) { | |
| // TODO: Refactor RangeDataVector to have a data iterator. | |
| const auto &core_range = core_range_info.data; | |
| uint32_t cmd_type = LC_SEGMENT_64; | |
| uint32_t segment_size = sizeof(llvm::MachO::segment_command_64); | |
| if (addr_byte_size == 4) { | |
| cmd_type = LC_SEGMENT; | |
| segment_size = sizeof(llvm::MachO::segment_command); | |
| } | |
| // Skip any ranges with no read/write/execute permissions and empty | |
| // ranges. | |
| if (core_range.lldb_permissions == 0 || core_range.range.size() == 0) | |
| continue; | |
| uint32_t vm_prot = 0; | |
| if (core_range.lldb_permissions & ePermissionsReadable) | |
| vm_prot |= VM_PROT_READ; | |
| if (core_range.lldb_permissions & ePermissionsWritable) | |
| vm_prot |= VM_PROT_WRITE; | |
| if (core_range.lldb_permissions & ePermissionsExecutable) | |
| vm_prot |= VM_PROT_EXECUTE; | |
| const addr_t vm_addr = core_range.range.start(); | |
| const addr_t vm_size = core_range.range.size(); | |
| llvm::MachO::segment_command_64 segment = { | |
| cmd_type, // uint32_t cmd; | |
| segment_size, // uint32_t cmdsize; | |
| {0}, // char segname[16]; | |
| vm_addr, // uint64_t vmaddr; // uint32_t for 32-bit Mach-O | |
| vm_size, // uint64_t vmsize; // uint32_t for 32-bit Mach-O | |
| 0, // uint64_t fileoff; // uint32_t for 32-bit Mach-O | |
| vm_size, // uint64_t filesize; // uint32_t for 32-bit Mach-O | |
| vm_prot, // uint32_t maxprot; | |
| vm_prot, // uint32_t initprot; | |
| 0, // uint32_t nsects; | |
| 0}; // uint32_t flags; | |
| segment_load_commands.push_back(segment); | |
| } | |
| StreamString buffer(Stream::eBinary, byte_order); | |
| llvm::MachO::mach_header_64 mach_header; | |
| mach_header.magic = addr_byte_size == 8 ? MH_MAGIC_64 : MH_MAGIC; | |
| mach_header.cputype = target_arch.GetMachOCPUType(); | |
| mach_header.cpusubtype = target_arch.GetMachOCPUSubType(); | |
| mach_header.filetype = MH_CORE; | |
| mach_header.ncmds = segment_load_commands.size(); | |
| mach_header.flags = 0; | |
| mach_header.reserved = 0; | |
| ThreadList &thread_list = process_sp->GetThreadList(); | |
| const uint32_t num_threads = thread_list.GetSize(); | |
| // Make an array of LC_THREAD data items. Each one contains the | |
| // contents of the LC_THREAD load command. The data doesn't contain | |
| // the load command + load command size, we will add the load command | |
| // and load command size as we emit the data. | |
| std::vector<StreamString> LC_THREAD_datas(num_threads); | |
| for (auto &LC_THREAD_data : LC_THREAD_datas) { | |
| LC_THREAD_data.GetFlags().Set(Stream::eBinary); | |
| LC_THREAD_data.SetByteOrder(byte_order); | |
| } | |
| for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { | |
| ThreadSP thread_sp(thread_list.GetThreadAtIndex(thread_idx)); | |
| if (thread_sp) { | |
| switch (mach_header.cputype) { | |
| case llvm::MachO::CPU_TYPE_ARM64: | |
| case llvm::MachO::CPU_TYPE_ARM64_32: | |
| RegisterContextDarwin_arm64_Mach::Create_LC_THREAD( | |
| thread_sp.get(), LC_THREAD_datas[thread_idx]); | |
| break; | |
| case llvm::MachO::CPU_TYPE_ARM: | |
| RegisterContextDarwin_arm_Mach::Create_LC_THREAD( | |
| thread_sp.get(), LC_THREAD_datas[thread_idx]); | |
| break; | |
| case llvm::MachO::CPU_TYPE_X86_64: | |
| RegisterContextDarwin_x86_64_Mach::Create_LC_THREAD( | |
| thread_sp.get(), LC_THREAD_datas[thread_idx]); | |
| break; | |
| case llvm::MachO::CPU_TYPE_RISCV: | |
| RegisterContextDarwin_riscv32_Mach::Create_LC_THREAD( | |
| thread_sp.get(), LC_THREAD_datas[thread_idx]); | |
| break; | |
| } | |
| } | |
| } | |
| // The size of the load command is the size of the segments... | |
| if (addr_byte_size == 8) { | |
| mach_header.sizeofcmds = segment_load_commands.size() * | |
| sizeof(llvm::MachO::segment_command_64); | |
| } else { | |
| mach_header.sizeofcmds = segment_load_commands.size() * | |
| sizeof(llvm::MachO::segment_command); | |
| } | |
| // and the size of all LC_THREAD load command | |
| for (const auto &LC_THREAD_data : LC_THREAD_datas) { | |
| ++mach_header.ncmds; | |
| mach_header.sizeofcmds += 8 + LC_THREAD_data.GetSize(); | |
| } | |
| // Bits will be set to indicate which bits are NOT used in | |
| // addressing in this process or 0 for unknown. | |
| uint64_t address_mask = process_sp->GetCodeAddressMask(); | |
| if (address_mask != LLDB_INVALID_ADDRESS_MASK) { | |
| // LC_NOTE "addrable bits" | |
| mach_header.ncmds++; | |
| mach_header.sizeofcmds += sizeof(llvm::MachO::note_command); | |
| } | |
| // LC_NOTE "process metadata" | |
| mach_header.ncmds++; | |
| mach_header.sizeofcmds += sizeof(llvm::MachO::note_command); | |
| // LC_NOTE "all image infos" | |
| mach_header.ncmds++; | |
| mach_header.sizeofcmds += sizeof(llvm::MachO::note_command); | |
| // Write the mach header | |
| buffer.PutHex32(mach_header.magic); | |
| buffer.PutHex32(mach_header.cputype); | |
| buffer.PutHex32(mach_header.cpusubtype); | |
| buffer.PutHex32(mach_header.filetype); | |
| buffer.PutHex32(mach_header.ncmds); | |
| buffer.PutHex32(mach_header.sizeofcmds); | |
| buffer.PutHex32(mach_header.flags); | |
| if (addr_byte_size == 8) { | |
| buffer.PutHex32(mach_header.reserved); | |
| } | |
| // Skip the mach header and all load commands and align to the next | |
| // 0x1000 byte boundary | |
| addr_t file_offset = buffer.GetSize() + mach_header.sizeofcmds; | |
| file_offset = llvm::alignTo(file_offset, 16); | |
| std::vector<std::unique_ptr<LCNoteEntry>> lc_notes; | |
| // Add "addrable bits" LC_NOTE when an address mask is available | |
| if (address_mask != LLDB_INVALID_ADDRESS_MASK) { | |
| std::unique_ptr<LCNoteEntry> addrable_bits_lcnote_up( | |
| new LCNoteEntry(byte_order)); | |
| addrable_bits_lcnote_up->name = "addrable bits"; | |
| addrable_bits_lcnote_up->payload_file_offset = file_offset; | |
| int bits = std::bitset<64>(~address_mask).count(); | |
| addrable_bits_lcnote_up->payload.PutHex32(4); // version | |
| addrable_bits_lcnote_up->payload.PutHex32( | |
| bits); // # of bits used for low addresses | |
| addrable_bits_lcnote_up->payload.PutHex32( | |
| bits); // # of bits used for high addresses | |
| addrable_bits_lcnote_up->payload.PutHex32(0); // reserved | |
| file_offset += addrable_bits_lcnote_up->payload.GetSize(); | |
| lc_notes.push_back(std::move(addrable_bits_lcnote_up)); | |
| } | |
| // Add "process metadata" LC_NOTE | |
| std::unique_ptr<LCNoteEntry> thread_extrainfo_lcnote_up( | |
| new LCNoteEntry(byte_order)); | |
| thread_extrainfo_lcnote_up->name = "process metadata"; | |
| thread_extrainfo_lcnote_up->payload_file_offset = file_offset; | |
| StructuredData::DictionarySP dict( | |
| std::make_shared<StructuredData::Dictionary>()); | |
| StructuredData::ArraySP threads( | |
| std::make_shared<StructuredData::Array>()); | |
| for (const ThreadSP &thread_sp : | |
| process_sp->CalculateCoreFileThreadList(options)) { | |
| StructuredData::DictionarySP thread( | |
| std::make_shared<StructuredData::Dictionary>()); | |
| thread->AddIntegerItem("thread_id", thread_sp->GetID()); | |
| threads->AddItem(thread); | |
| } | |
| dict->AddItem("threads", threads); | |
| StreamString strm; | |
| dict->Dump(strm, /* pretty */ false); | |
| thread_extrainfo_lcnote_up->payload.PutRawBytes(strm.GetData(), | |
| strm.GetSize()); | |
| file_offset += thread_extrainfo_lcnote_up->payload.GetSize(); | |
| file_offset = llvm::alignTo(file_offset, 16); | |
| lc_notes.push_back(std::move(thread_extrainfo_lcnote_up)); | |
| // Add "all image infos" LC_NOTE | |
| std::unique_ptr<LCNoteEntry> all_image_infos_lcnote_up( | |
| new LCNoteEntry(byte_order)); | |
| all_image_infos_lcnote_up->name = "all image infos"; | |
| all_image_infos_lcnote_up->payload_file_offset = file_offset; | |
| file_offset = CreateAllImageInfosPayload( | |
| process_sp, file_offset, all_image_infos_lcnote_up->payload, | |
| options); | |
| lc_notes.push_back(std::move(all_image_infos_lcnote_up)); | |
| // Add LC_NOTE load commands | |
| for (auto &lcnote : lc_notes) { | |
| // Add the LC_NOTE load command to the file. | |
| buffer.PutHex32(LC_NOTE); | |
| buffer.PutHex32(sizeof(llvm::MachO::note_command)); | |
| char namebuf[16]; | |
| memset(namebuf, 0, sizeof(namebuf)); | |
| // This is the uncommon case where strncpy is exactly | |
| // the right one, doesn't need to be null-terminated. | |
| // LC_NOTE name field is char[16] and is not guaranteed to be | |
| // null-terminated. | |
| // coverity[buffer_size_warning] | |
| strncpy(namebuf, lcnote->name.c_str(), sizeof(namebuf)); | |
| buffer.PutRawBytes(namebuf, sizeof(namebuf)); | |
| buffer.PutHex64(lcnote->payload_file_offset); | |
| buffer.PutHex64(lcnote->payload.GetSize()); | |
| } | |
| // Align to 4096-byte page boundary for the LC_SEGMENTs. | |
| file_offset = llvm::alignTo(file_offset, 4096); | |
| for (auto &segment : segment_load_commands) { | |
| segment.fileoff = file_offset; | |
| file_offset += segment.filesize; | |
| } | |
| // Write out all of the LC_THREAD load commands | |
| for (const auto &LC_THREAD_data : LC_THREAD_datas) { | |
| const size_t LC_THREAD_data_size = LC_THREAD_data.GetSize(); | |
| buffer.PutHex32(LC_THREAD); | |
| buffer.PutHex32(8 + LC_THREAD_data_size); // cmd + cmdsize + data | |
| buffer.Write(LC_THREAD_data.GetString().data(), LC_THREAD_data_size); | |
| } | |
| // Write out all of the segment load commands | |
| for (const auto &segment : segment_load_commands) { | |
| buffer.PutHex32(segment.cmd); | |
| buffer.PutHex32(segment.cmdsize); | |
| buffer.PutRawBytes(segment.segname, sizeof(segment.segname)); | |
| if (addr_byte_size == 8) { | |
| buffer.PutHex64(segment.vmaddr); | |
| buffer.PutHex64(segment.vmsize); | |
| buffer.PutHex64(segment.fileoff); | |
| buffer.PutHex64(segment.filesize); | |
| } else { | |
| buffer.PutHex32(static_cast<uint32_t>(segment.vmaddr)); | |
| buffer.PutHex32(static_cast<uint32_t>(segment.vmsize)); | |
| buffer.PutHex32(static_cast<uint32_t>(segment.fileoff)); | |
| buffer.PutHex32(static_cast<uint32_t>(segment.filesize)); | |
| } | |
| buffer.PutHex32(segment.maxprot); | |
| buffer.PutHex32(segment.initprot); | |
| buffer.PutHex32(segment.nsects); | |
| buffer.PutHex32(segment.flags); | |
| } | |
| std::string core_file_path(outfile.GetPath()); | |
| auto core_file = FileSystem::Instance().Open( | |
| outfile, File::eOpenOptionWriteOnly | File::eOpenOptionTruncate | | |
| File::eOpenOptionCanCreate); | |
| if (!core_file) { | |
| error = Status::FromError(core_file.takeError()); | |
| } else { | |
| // Read 1 page at a time | |
| uint8_t bytes[0x1000]; | |
| // Write the mach header and load commands out to the core file | |
| size_t bytes_written = buffer.GetString().size(); | |
| error = | |
| core_file.get()->Write(buffer.GetString().data(), bytes_written); | |
| if (error.Success()) { | |
| for (auto &lcnote : lc_notes) { | |
| if (core_file.get()->SeekFromStart(lcnote->payload_file_offset) == | |
| -1) { | |
| error = Status::FromErrorStringWithFormat( | |
| "Unable to seek to corefile pos " | |
| "to write '%s' LC_NOTE payload", | |
| lcnote->name.c_str()); | |
| return false; | |
| } | |
| bytes_written = lcnote->payload.GetSize(); | |
| error = core_file.get()->Write(lcnote->payload.GetData(), | |
| bytes_written); | |
| if (!error.Success()) | |
| return false; | |
| } | |
| // Now write the file data for all memory segments in the process | |
| for (const auto &segment : segment_load_commands) { | |
| if (core_file.get()->SeekFromStart(segment.fileoff) == -1) { | |
| error = Status::FromErrorStringWithFormat( | |
| "unable to seek to offset 0x%" PRIx64 " in '%s'", | |
| segment.fileoff, core_file_path.c_str()); | |
| break; | |
| } | |
| target.GetDebugger().GetAsyncOutputStream()->Printf( | |
| "Saving %" PRId64 | |
| " bytes of data for memory region at 0x%" PRIx64 "\n", | |
| segment.vmsize, segment.vmaddr); | |
| addr_t bytes_left = segment.vmsize; | |
| addr_t addr = segment.vmaddr; | |
| Status memory_read_error; | |
| while (bytes_left > 0 && error.Success()) { | |
| const size_t bytes_to_read = | |
| bytes_left > sizeof(bytes) ? sizeof(bytes) : bytes_left; | |
| // In a savecore setting, we don't really care about caching, | |
| // as the data is dumped and very likely never read again, | |
| // so we call ReadMemoryFromInferior to bypass it. | |
| const size_t bytes_read = process_sp->ReadMemoryFromInferior( | |
| addr, bytes, bytes_to_read, memory_read_error); | |
| if (bytes_read == bytes_to_read) { | |
| size_t bytes_written = bytes_read; | |
| error = core_file.get()->Write(bytes, bytes_written); | |
| bytes_left -= bytes_read; | |
| addr += bytes_read; | |
| } else { | |
| // Some pages within regions are not readable, those should | |
| // be zero filled | |
| memset(bytes, 0, bytes_to_read); | |
| size_t bytes_written = bytes_to_read; | |
| error = core_file.get()->Write(bytes, bytes_written); | |
| bytes_left -= bytes_to_read; | |
| addr += bytes_to_read; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return true; // This is the right plug to handle saving core files for | |
| // this process | |
| } | |
| return false; | |
| } | |
| ObjectFileMachO::MachOCorefileAllImageInfos | |
| ObjectFileMachO::GetCorefileAllImageInfos() { | |
| MachOCorefileAllImageInfos image_infos; | |
| Log *log(GetLog(LLDBLog::Object | LLDBLog::Symbols | LLDBLog::Process | | |
| LLDBLog::DynamicLoader)); | |
| auto lc_notes = FindLC_NOTEByName("all image infos"); | |
| for (auto lc_note : lc_notes) { | |
| offset_t payload_offset = std::get<0>(lc_note); | |
| // Read the struct all_image_infos_header. | |
| uint32_t version = m_data_nsp->GetU32(&payload_offset); | |
| if (version != 1) { | |
| return image_infos; | |
| } | |
| uint32_t imgcount = m_data_nsp->GetU32(&payload_offset); | |
| uint64_t entries_fileoff = m_data_nsp->GetU64(&payload_offset); | |
| // 'entries_size' is not used, nor is the 'unused' entry. | |
| // offset += 4; // uint32_t entries_size; | |
| // offset += 4; // uint32_t unused; | |
| LLDB_LOGF(log, "LC_NOTE 'all image infos' found version %d with %d images", | |
| version, imgcount); | |
| payload_offset = entries_fileoff; | |
| for (uint32_t i = 0; i < imgcount; i++) { | |
| // Read the struct image_entry. | |
| offset_t filepath_offset = m_data_nsp->GetU64(&payload_offset); | |
| uuid_t uuid; | |
| memcpy(&uuid, m_data_nsp->GetData(&payload_offset, sizeof(uuid_t)), | |
| sizeof(uuid_t)); | |
| uint64_t load_address = m_data_nsp->GetU64(&payload_offset); | |
| offset_t seg_addrs_offset = m_data_nsp->GetU64(&payload_offset); | |
| uint32_t segment_count = m_data_nsp->GetU32(&payload_offset); | |
| uint32_t currently_executing = m_data_nsp->GetU32(&payload_offset); | |
| MachOCorefileImageEntry image_entry; | |
| image_entry.filename = | |
| (const char *)m_data_nsp->GetCStr(&filepath_offset); | |
| image_entry.uuid = UUID(uuid, sizeof(uuid_t)); | |
| image_entry.load_address = load_address; | |
| image_entry.currently_executing = currently_executing; | |
| offset_t seg_vmaddrs_offset = seg_addrs_offset; | |
| for (uint32_t j = 0; j < segment_count; j++) { | |
| char segname[17]; | |
| m_data_nsp->CopyData(seg_vmaddrs_offset, 16, segname); | |
| segname[16] = '\0'; | |
| seg_vmaddrs_offset += 16; | |
| uint64_t vmaddr = m_data_nsp->GetU64(&seg_vmaddrs_offset); | |
| seg_vmaddrs_offset += 8; /* unused */ | |
| std::tuple<ConstString, addr_t> new_seg{ConstString(segname), vmaddr}; | |
| image_entry.segment_load_addresses.push_back(new_seg); | |
| } | |
| LLDB_LOGF(log, " image entry: %s %s 0x%" PRIx64 " %s", | |
| image_entry.filename.c_str(), | |
| image_entry.uuid.GetAsString().c_str(), | |
| image_entry.load_address, | |
| image_entry.currently_executing ? "currently executing" | |
| : "not currently executing"); | |
| image_infos.all_image_infos.push_back(image_entry); | |
| } | |
| } | |
| lc_notes = FindLC_NOTEByName("load binary"); | |
| for (auto lc_note : lc_notes) { | |
| offset_t payload_offset = std::get<0>(lc_note); | |
| uint32_t version = m_data_nsp->GetU32(&payload_offset); | |
| if (version == 1) { | |
| uuid_t uuid; | |
| memcpy(&uuid, m_data_nsp->GetData(&payload_offset, sizeof(uuid_t)), | |
| sizeof(uuid_t)); | |
| uint64_t load_address = m_data_nsp->GetU64(&payload_offset); | |
| uint64_t slide = m_data_nsp->GetU64(&payload_offset); | |
| std::string filename = m_data_nsp->GetCStr(&payload_offset); | |
| MachOCorefileImageEntry image_entry; | |
| image_entry.filename = filename; | |
| image_entry.uuid = UUID(uuid, sizeof(uuid_t)); | |
| image_entry.load_address = load_address; | |
| image_entry.slide = slide; | |
| image_entry.currently_executing = true; | |
| image_infos.all_image_infos.push_back(image_entry); | |
| LLDB_LOGF(log, | |
| "LC_NOTE 'load binary' found, filename %s uuid %s load " | |
| "address 0x%" PRIx64 " slide 0x%" PRIx64, | |
| filename.c_str(), | |
| image_entry.uuid.IsValid() | |
| ? image_entry.uuid.GetAsString().c_str() | |
| : "00000000-0000-0000-0000-000000000000", | |
| load_address, slide); | |
| } | |
| } | |
| return image_infos; | |
| } | |
| bool ObjectFileMachO::LoadCoreFileImages(lldb_private::Process &process) { | |
| MachOCorefileAllImageInfos image_infos = GetCorefileAllImageInfos(); | |
| Log *log = GetLog(LLDBLog::Object | LLDBLog::DynamicLoader); | |
| bool found_platform_binary = false; | |
| ModuleList added_modules; | |
| llvm::SmallVector<const MachOCorefileImageEntry *> pending_images; | |
| std::vector<DynamicLoader::BinarySpec> pending_specs; | |
| for (MachOCorefileImageEntry &image : image_infos.all_image_infos) { | |
| // If this is a platform binary, it has been loaded (or registered with | |
| // the DynamicLoader to be loaded), we don't need to do any further | |
| // processing. We're not going to call ModulesDidLoad on this in this | |
| // method, so notify==true. | |
| // | |
| // Setting up a platform binary can replace the Target's platform and | |
| // dynamic loader, so no image is searched for until this loop has run to | |
| // the end. | |
| if (process.GetTarget() | |
| .GetDebugger() | |
| .GetPlatformList() | |
| .LoadPlatformBinaryAndSetup(&process, image.load_address, | |
| true /* notify */)) { | |
| LLDB_LOGF(log, | |
| "ObjectFileMachO::%s binary at 0x%" PRIx64 | |
| " is a platform binary, has been handled by a Platform plugin.", | |
| __FUNCTION__, image.load_address); | |
| found_platform_binary = true; | |
| continue; | |
| } | |
| bool value_is_offset = image.load_address == LLDB_INVALID_ADDRESS; | |
| uint64_t value = value_is_offset ? image.slide : image.load_address; | |
| if (value_is_offset && value == LLDB_INVALID_ADDRESS) { | |
| // We have neither address nor slide; so we will find the binary | |
| // by UUID and load it at slide/offset 0. | |
| value = 0; | |
| } | |
| // We have either a UUID, or we have a load address which | |
| // and can try to read load commands and find a UUID. | |
| if (!image.uuid.IsValid() && | |
| (value_is_offset || value == LLDB_INVALID_ADDRESS)) | |
| continue; | |
| DynamicLoader::BinarySpec bin_spec; | |
| bin_spec.name = image.filename; | |
| bin_spec.uuid = image.uuid; | |
| bin_spec.value = value; | |
| bin_spec.value_is_offset = value_is_offset; | |
| bin_spec.force_symbol_search = image.currently_executing; | |
| bin_spec.notify = false; | |
| // Userland Darwin binaries will have segment load addresses via | |
| // the `all image infos` LC_NOTE. | |
| bin_spec.set_address_in_target = image.segment_load_addresses.empty(); | |
| bin_spec.allow_memory_image_last_resort = | |
| !image.segment_load_addresses.empty(); | |
| pending_images.push_back(&image); | |
| pending_specs.push_back(std::move(bin_spec)); | |
| } | |
| DynamicLoader::LocateBinaries(&process, pending_specs); | |
| for (auto [image, bin_spec] : | |
| llvm::zip_equal(pending_images, pending_specs)) { | |
| ModuleSP module_sp; | |
| if (llvm::Expected<ModuleSP> loaded = | |
| DynamicLoader::LoadBinaryInTarget(&process, bin_spec)) { | |
| module_sp = *loaded; | |
| } else if (bin_spec.force_symbol_search) { | |
| *process.GetTarget().GetDebugger().GetAsyncErrorStream() | |
| << llvm::toString(loaded.takeError()) << "\n"; | |
| } else { | |
| // A corefile image that isn't on this machine is routine, and has | |
| // already been logged. | |
| llvm::consumeError(loaded.takeError()); | |
| } | |
| if (!module_sp) | |
| continue; | |
| added_modules.Append(module_sp, false /* notify */); | |
| // We have a list of segment load address | |
| if (image->segment_load_addresses.size() > 0) { | |
| if (log) { | |
| std::string uuidstr = image->uuid.GetAsString(); | |
| log->Printf("ObjectFileMachO::LoadCoreFileImages adding binary '%s' " | |
| "UUID %s with section load addresses", | |
| module_sp->GetFileSpec().GetPath().c_str(), | |
| uuidstr.c_str()); | |
| } | |
| ObjectFile *objfile = module_sp->GetObjectFile(); | |
| SectionList *sectlist = objfile ? objfile->GetSectionList() : nullptr; | |
| for (auto name_vmaddr_tuple : image->segment_load_addresses) { | |
| if (sectlist) { | |
| SectionSP sect_sp = | |
| sectlist->FindSectionByName(std::get<0>(name_vmaddr_tuple)); | |
| if (sect_sp) { | |
| process.GetTarget().SetSectionLoadAddress( | |
| sect_sp, std::get<1>(name_vmaddr_tuple)); | |
| } | |
| } | |
| } | |
| } else { | |
| if (log) { | |
| std::string uuidstr = image->uuid.GetAsString(); | |
| log->Printf("ObjectFileMachO::LoadCoreFileImages adding binary '%s' " | |
| "UUID %s with %s 0x%" PRIx64, | |
| module_sp->GetFileSpec().GetPath().c_str(), uuidstr.c_str(), | |
| bin_spec.value_is_offset ? "slide" : "load address", | |
| bin_spec.value); | |
| } | |
| bool changed; | |
| module_sp->SetLoadAddress(process.GetTarget(), bin_spec.value, | |
| bin_spec.value_is_offset, changed); | |
| } | |
| } | |
| if (added_modules.GetSize() > 0) { | |
| process.GetTarget().ModulesDidLoad(added_modules); | |
| process.Flush(); | |
| return true; | |
| } | |
| // Return true if the only binary we found was the platform binary, | |
| // and it was loaded outside the scope of this method. | |
| if (found_platform_binary) | |
| return true; | |
| // No binaries. | |
| return false; | |
| } |