diff --git a/lldb/bindings/interface/SBMemoryRegionInfo.i b/lldb/bindings/interface/SBMemoryRegionInfo.i --- a/lldb/bindings/interface/SBMemoryRegionInfo.i +++ b/lldb/bindings/interface/SBMemoryRegionInfo.i @@ -46,6 +46,42 @@ const char * GetName (); + %feature("autodoc", " + GetRegionEnd(SBMemoryRegionInfo self) -> lldb::addr_t + Returns whether this memory region has a list of modified (dirty) + pages available or not. When calling GetNumDirtyPages(), you will + have 0 returned for both \"dirty page list is not known\" and + \"empty dirty page list\" (that is, no modified pages in this + memory region). You must use this method to disambiguate.") HasDirtyMemoryPageList; + bool + HasDirtyMemoryPageList(); + + %feature("autodoc", " + GetNumDirtyPages(SBMemoryRegionInfo self) -> uint32_t + Return the number of dirty (modified) memory pages in this + memory region, if available. You must use the + SBMemoryRegionInfo::HasDirtyMemoryPageList() method to + determine if a dirty memory list is available; it will depend + on the target system can provide this information.") GetNumDirtyPages; + uint32_t + GetNumDirtyPages(); + + %feature("autodoc", " + GetDirtyPageAddressAtIndex(SBMemoryRegionInfo self, uint32_t idx) -> lldb::addr_t + Return the address of a modified, or dirty, page of memory. + If the provided index is out of range, or this memory region + does not have dirty page information, LLDB_INVALID_ADDRESS + is returned.") GetDirtyPageAddressAtIndex; + addr_t + GetDirtyPageAddressAtIndex(uint32_t idx); + + %feature("autodoc", " + GetPageSize(SBMemoryRegionInfo self) -> int + Return the size of pages in this memory region. 0 will be returned + if this information was unavailable.") GetPageSize(); + int + GetPageSize(); + bool operator == (const lldb::SBMemoryRegionInfo &rhs) const; diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -869,10 +869,14 @@ osmajor: optional, specifies the major version number of the OS (e.g. for macOS 10.12.2, it would be 10) osminor: optional, specifies the minor version number of the OS (e.g. for macOS 10.12.2, it would be 12) ospatch: optional, specifies the patch level number of the OS (e.g. for macOS 10.12.2, it would be 2) +vm-page-size: optional, specifies the target system VM page size, base 10. + Needed for the "dirty-pages:" list in the qMemoryRegionInfo + packet, where a list of dirty pages is sent from the remote + stub. This page size tells lldb how large each dirty page is. addressing_bits: optional, specifies how many bits in addresses are significant for addressing, base 10. If bits 38..0 in a 64-bit pointer are significant for addressing, - then the value is 39. This is needed on e.g. Aarch64 + then the value is 39. This is needed on e.g. AArch64 v8.3 ABIs that use pointer authentication, so lldb knows which bits to clear/set to get the actual addresses. @@ -1174,6 +1178,18 @@ // a hex encoded string value that // contains an error string + dirty-pages:[][, CreateCallFrameInfo(); + /// Load binaries listed in a corefile + /// + /// A corefile may have metadata listing binaries that can be loaded, + /// and the offsets at which they were loaded. This method will try + /// to add them to the Target. If any binaries were loaded, + /// + /// \param[in] process + /// Process where to load binaries. + /// + /// \return + /// Returns true if any binaries were loaded. + + virtual bool LoadCoreFileImages(lldb_private::Process &process) { + return false; + } + protected: // Member variables. FileSpec m_file; diff --git a/lldb/include/lldb/Target/MemoryRegionInfo.h b/lldb/include/lldb/Target/MemoryRegionInfo.h --- a/lldb/include/lldb/Target/MemoryRegionInfo.h +++ b/lldb/include/lldb/Target/MemoryRegionInfo.h @@ -10,8 +10,11 @@ #ifndef LLDB_TARGET_MEMORYREGIONINFO_H #define LLDB_TARGET_MEMORYREGIONINFO_H +#include + #include "lldb/Utility/ConstString.h" #include "lldb/Utility/RangeMap.h" +#include "llvm/ADT/Optional.h" #include "llvm/Support/FormatProviders.h" namespace lldb_private { @@ -32,10 +35,7 @@ RangeType &GetRange() { return m_range; } - void Clear() { - m_range.Clear(); - m_read = m_write = m_execute = m_memory_tagged = eDontKnow; - } + void Clear() { *this = MemoryRegionInfo(); } const RangeType &GetRange() const { return m_range; } @@ -97,11 +97,33 @@ m_write == rhs.m_write && m_execute == rhs.m_execute && m_mapped == rhs.m_mapped && m_name == rhs.m_name && m_flash == rhs.m_flash && m_blocksize == rhs.m_blocksize && - m_memory_tagged == rhs.m_memory_tagged; + m_memory_tagged == rhs.m_memory_tagged && + m_pagesize == rhs.m_pagesize; } bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); } + /// Get the target system's VM page size in bytes. + /// \return + /// 0 is returned if this information is unavailable. + int GetPageSize() { return m_pagesize; } + + /// Get a vector of target VM pages that are dirty -- that have been + /// modified -- within this memory region. This is an Optional return + /// value; it will only be available if the remote stub was able to + /// detail this. + llvm::Optional> &GetDirtyPageList() { + return m_dirty_pages; + } + + void SetPageSize(int pagesize) { m_pagesize = pagesize; } + + void SetDirtyPageList(std::vector pagelist) { + if (m_dirty_pages.hasValue()) + m_dirty_pages.getValue().clear(); + m_dirty_pages = std::move(pagelist); + } + protected: RangeType m_range; OptionalBool m_read = eDontKnow; @@ -112,6 +134,8 @@ OptionalBool m_flash = eDontKnow; lldb::offset_t m_blocksize = 0; OptionalBool m_memory_tagged = eDontKnow; + int m_pagesize = 0; + llvm::Optional> m_dirty_pages; }; inline bool operator<(const MemoryRegionInfo &lhs, diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -601,6 +601,7 @@ eArgTypeCommand, eArgTypeColumnNum, eArgTypeModuleUUID, + eArgTypeSaveCoreStyle, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; @@ -1111,6 +1112,14 @@ /// Stopped because quit was requested. eCommandInterpreterResultQuitRequested, }; + +// Style of core file to create when calling SaveCore. +enum SaveCoreStyle { + eSaveCoreUnspecified = 0, + eSaveCoreFull = 1, + eSaveCoreDirtyOnly = 2, +}; + } // namespace lldb #endif // LLDB_LLDB_ENUMERATIONS_H diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h --- a/lldb/include/lldb/lldb-private-interfaces.h +++ b/lldb/include/lldb/lldb-private-interfaces.h @@ -54,7 +54,9 @@ const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp, const lldb::ProcessSP &process_sp, lldb::addr_t offset); typedef bool (*ObjectFileSaveCore)(const lldb::ProcessSP &process_sp, - const FileSpec &outfile, Status &error); + const FileSpec &outfile, + lldb::SaveCoreStyle &core_style, + Status &error); typedef EmulateInstruction *(*EmulateInstructionCreateInstance)( const ArchSpec &arch, InstructionType inst_type); typedef OperatingSystem *(*OperatingSystemCreateInstance)(Process *process, diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -723,7 +723,8 @@ "permissions", "flags", "name", - "error"]) + "error", + "dirty-pages"]) self.assertIsNotNone(val) mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", "")) diff --git a/lldb/source/API/SBMemoryRegionInfo.cpp b/lldb/source/API/SBMemoryRegionInfo.cpp --- a/lldb/source/API/SBMemoryRegionInfo.cpp +++ b/lldb/source/API/SBMemoryRegionInfo.cpp @@ -116,6 +116,42 @@ return m_opaque_up->GetName().AsCString(); } +bool SBMemoryRegionInfo::HasDirtyMemoryPageList() { + LLDB_RECORD_METHOD_NO_ARGS(bool, SBMemoryRegionInfo, HasDirtyMemoryPageList); + + return m_opaque_up->GetDirtyPageList().hasValue(); +} + +uint32_t SBMemoryRegionInfo::GetNumDirtyPages() { + LLDB_RECORD_METHOD_NO_ARGS(uint32_t, SBMemoryRegionInfo, GetNumDirtyPages); + + uint32_t num_dirty_pages = 0; + llvm::Optional> dirty_page_list = + m_opaque_up->GetDirtyPageList(); + if (dirty_page_list.hasValue()) + num_dirty_pages = dirty_page_list.getValue().size(); + + return num_dirty_pages; +} + +addr_t SBMemoryRegionInfo::GetDirtyPageAddressAtIndex(uint32_t idx) { + LLDB_RECORD_METHOD(addr_t, SBMemoryRegionInfo, GetDirtyPageAddressAtIndex, + (uint32_t), idx); + + addr_t dirty_page_addr = LLDB_INVALID_ADDRESS; + const llvm::Optional> &dirty_page_list = + m_opaque_up->GetDirtyPageList(); + if (dirty_page_list.hasValue() && idx < dirty_page_list.getValue().size()) + dirty_page_addr = dirty_page_list.getValue()[idx]; + + return dirty_page_addr; +} + +int SBMemoryRegionInfo::GetPageSize() { + LLDB_RECORD_METHOD_NO_ARGS(int, SBMemoryRegionInfo, GetPageSize); + return m_opaque_up->GetPageSize(); +} + bool SBMemoryRegionInfo::GetDescription(SBStream &description) { LLDB_RECORD_METHOD(bool, SBMemoryRegionInfo, GetDescription, (lldb::SBStream &), description); diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp --- a/lldb/source/API/SBProcess.cpp +++ b/lldb/source/API/SBProcess.cpp @@ -1227,7 +1227,8 @@ } FileSpec core_file(file_name); - error.ref() = PluginManager::SaveCore(process_sp, core_file); + SaveCoreStyle core_style = SaveCoreStyle::eSaveCoreFull; + error.ref() = PluginManager::SaveCore(process_sp, core_file, core_style); return LLDB_RECORD_RESULT(error); } diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -1678,6 +1678,28 @@ if (memory_tagged == MemoryRegionInfo::OptionalBool::eYes) result.AppendMessage("memory tagging: enabled"); + const llvm::Optional> &dirty_page_list = + range_info.GetDirtyPageList(); + if (dirty_page_list.hasValue()) { + const size_t page_count = dirty_page_list.getValue().size(); + result.AppendMessageWithFormat( + "Modified memory (dirty) page list provided, %zu entries.\n", + page_count); + if (page_count > 0) { + bool print_comma = false; + result.AppendMessageWithFormat("Dirty pages: "); + for (size_t i = 0; i < page_count; i++) { + if (print_comma) + result.AppendMessageWithFormat(", "); + else + print_comma = true; + result.AppendMessageWithFormat("0x%" PRIx64, + dirty_page_list.getValue()[i]); + } + result.AppendMessageWithFormat(".\n"); + } + } + m_prev_end_addr = range_info.GetRange().GetRangeEnd(); result.SetStatus(eReturnStatusSuccessFinishResult); return true; diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -1161,26 +1161,91 @@ // CommandObjectProcessSaveCore #pragma mark CommandObjectProcessSaveCore +static constexpr OptionEnumValueElement g_corefile_save_style[] = { + {eSaveCoreFull, "full", "Create a core file with all memory saved"}, + {eSaveCoreDirtyOnly, "modified-memory", + "Create a corefile with only modified memory saved"}}; + +static constexpr OptionEnumValues SaveCoreStyles() { + return OptionEnumValues(g_corefile_save_style); +} + +#define LLDB_OPTIONS_process_save_core +#include "CommandOptions.inc" + class CommandObjectProcessSaveCore : public CommandObjectParsed { public: CommandObjectProcessSaveCore(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "process save-core", "Save the current process as a core file using an " "appropriate file type.", - "process save-core FILE", + "process save-core [-s corefile-style] FILE", eCommandRequiresProcess | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched) {} ~CommandObjectProcessSaveCore() override = default; + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() + : Options(), m_requested_save_core_style(eSaveCoreUnspecified) {} + + ~CommandOptions() override = default; + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_process_save_core_options); + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + const int short_option = m_getopt_table[option_idx].val; + Status error; + + switch (short_option) { + case 's': + m_requested_save_core_style = + (lldb::SaveCoreStyle)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eSaveCoreUnspecified, error); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return {}; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_requested_save_core_style = eSaveCoreUnspecified; + } + + // Instance variables to hold the values for command options. + SaveCoreStyle m_requested_save_core_style; + }; + protected: bool DoExecute(Args &command, CommandReturnObject &result) override { ProcessSP process_sp = m_exe_ctx.GetProcessSP(); if (process_sp) { if (command.GetArgumentCount() == 1) { FileSpec output_file(command.GetArgumentAtIndex(0)); - Status error = PluginManager::SaveCore(process_sp, output_file); + SaveCoreStyle corefile_style = m_options.m_requested_save_core_style; + Status error = + PluginManager::SaveCore(process_sp, output_file, corefile_style); if (error.Success()) { + if (corefile_style == SaveCoreStyle::eSaveCoreDirtyOnly) { + result.AppendMessageWithFormat( + "\nModified-memory only corefile " + "created. This corefile may not show \n" + "library/framework/app binaries " + "on a different system, or when \n" + "those binaries have " + "been updated/modified. Copies are not included\n" + "in this corefile. Use --style full to include all " + "process memory.\n"); + } result.SetStatus(eReturnStatusSuccessFinishResult); } else { result.AppendErrorWithFormat( @@ -1197,6 +1262,8 @@ return result.Succeeded(); } + + CommandOptions m_options; }; // CommandObjectProcessStatus diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -730,6 +730,12 @@ Desc<"Show verbose process status including extended crash information.">; } +let Command = "process save_core" in { + def process_save_core_style : Option<"style", "s">, Group<1>, + EnumArg<"SaveCoreStyle", "SaveCoreStyles()">, Desc<"Request a specific style " + "of corefile to be saved.">; +} + let Command = "script import" in { def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>, Desc<"Allow the script to be loaded even if it was already loaded before. " diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -684,11 +684,13 @@ } Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp, - const FileSpec &outfile) { + const FileSpec &outfile, + lldb::SaveCoreStyle &core_style) { Status error; auto &instances = GetObjectFileInstances().GetInstances(); for (auto &instance : instances) { - if (instance.save_core && instance.save_core(process_sp, outfile, error)) + if (instance.save_core && + instance.save_core(process_sp, outfile, core_style, error)) return error; } error.SetErrorString( diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp --- a/lldb/source/Interpreter/CommandObject.cpp +++ b/lldb/source/Interpreter/CommandObject.cpp @@ -1126,7 +1126,8 @@ { eArgRawInput, "raw-input", CommandCompletions::eNoCompletion, { nullptr, false }, "Free-form text passed to a command without prior interpretation, allowing spaces without requiring quotes. To pass arguments and free form text put two dashes ' -- ' between the last argument and any raw input." }, { eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command." }, { eArgTypeColumnNum, "column", CommandCompletions::eNoCompletion, { nullptr, false }, "Column number in a source file." }, - { eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." } + { eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." }, + { eArgTypeSaveCoreStyle, "corefile-style", CommandCompletions::eNoCompletion, { nullptr, false }, "The type of corefile that lldb will try to create, dependant on this target's capabilities." } // clang-format on }; diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h @@ -58,6 +58,7 @@ static bool SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle &core_style, lldb_private::Status &error); static bool MagicBytesMatch(lldb::DataBufferSP &data_sp, lldb::addr_t offset, @@ -116,6 +117,8 @@ lldb_private::UUID &uuid, ObjectFile::BinaryType &type) override; + bool LoadCoreFileImages(lldb_private::Process &process) override; + lldb::RegisterContextSP GetThreadContextAtIndex(uint32_t idx, lldb_private::Thread &thread) override; @@ -209,6 +212,31 @@ bool SectionIsLoadable(const lldb_private::Section *section); + /// A corefile may include metadata about all of the binaries that were + /// present in the process when the corefile was taken. This is only + /// implemented for Mach-O files for now; we'll generalize it when we + /// have other systems that can include the same. + struct MachOCorefileImageEntry { + std::string filename; + lldb_private::UUID uuid; + lldb::addr_t load_address = LLDB_INVALID_ADDRESS; + bool currently_executing; + std::vector> + segment_load_addresses; + }; + + struct MachOCorefileAllImageInfos { + std::vector all_image_infos; + bool IsValid() { return all_image_infos.size() > 0; } + }; + + /// Get the list of binary images that were present in the process + /// when the corefile was produced. + /// \return + /// The MachOCorefileAllImageInfos object returned will have + /// IsValid() == false if the information is unavailable. + MachOCorefileAllImageInfos GetCorefileAllImageInfos(); + llvm::MachO::mach_header m_header; static lldb_private::ConstString GetSegmentNameTEXT(); static lldb_private::ConstString GetSegmentNameDATA(); diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -23,6 +23,7 @@ #include "lldb/Host/Host.h" #include "lldb/Host/SafeMachO.h" #include "lldb/Symbol/DWARFCallFrameInfo.h" +#include "lldb/Symbol/LocateSymbolFile.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/MemoryRegionInfo.h" @@ -6204,11 +6205,257 @@ 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) { + Target &target = process_sp->GetTarget(); + const ModuleList &modules = target.GetImages(); + size_t modules_count = modules.GetSize(); + + std::set executing_uuids; + ThreadList &thread_list(process_sp->GetThreadList()); + for (uint32_t i = 0; i < thread_list.GetSize(); i++) { + ThreadSP thread_sp = thread_list.GetThreadAtIndex(i); + 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()); + } + } + } + } + + 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> 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_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; + ConstString name = section->GetName(); + segment_vmaddr seg_vmaddr; + strncpy(seg_vmaddr.segname, name.AsCString(), + 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; + std::vector image_entries; + 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, - const FileSpec &outfile, Status &error) { + const FileSpec &outfile, + lldb::SaveCoreStyle &core_style, Status &error) { if (!process_sp) return false; + // For Mach-O, we can only create full corefiles or dirty-page-only + // corefiles. The default is dirty-page-only. + if (core_style != SaveCoreStyle::eSaveCoreFull) { + core_style = SaveCoreStyle::eSaveCoreDirtyOnly; + } else { + core_style = SaveCoreStyle::eSaveCoreFull; + } + Target &target = process_sp->GetTarget(); const ArchSpec target_arch = target.GetArchitecture(); const llvm::Triple &target_triple = target_arch.GetTriple(); @@ -6242,14 +6489,10 @@ Status range_error = process_sp->GetMemoryRegionInfo(0, range_info); const uint32_t addr_byte_size = target_arch.GetAddressByteSize(); const ByteOrder byte_order = target_arch.GetByteOrder(); + std::vector pages_to_copy; + if (range_error.Success()) { while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) { - const addr_t addr = range_info.GetRange().GetRangeBase(); - const addr_t size = range_info.GetRange().GetByteSize(); - - if (size == 0) - break; - // Calculate correct protections uint32_t prot = 0; if (range_info.GetReadable() == MemoryRegionInfo::eYes) @@ -6259,32 +6502,28 @@ if (range_info.GetExecutable() == MemoryRegionInfo::eYes) prot |= VM_PROT_EXECUTE; + const addr_t addr = range_info.GetRange().GetRangeBase(); + const addr_t size = range_info.GetRange().GetByteSize(); + + if (size == 0) + break; + if (prot != 0) { - 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); + addr_t pagesize = range_info.GetPageSize(); + const llvm::Optional> &dirty_page_list = + range_info.GetDirtyPageList(); + if (core_style == SaveCoreStyle::eSaveCoreDirtyOnly && + dirty_page_list.hasValue()) { + core_style = SaveCoreStyle::eSaveCoreDirtyOnly; + for (addr_t dirtypage : dirty_page_list.getValue()) { + page_object obj = { + .addr = dirtypage, .size = pagesize, .prot = prot}; + pages_to_copy.push_back(obj); + } + } else { + page_object obj = {.addr = addr, .size = size, .prot = prot}; + pages_to_copy.push_back(obj); } - llvm::MachO::segment_command_64 segment = { - cmd_type, // uint32_t cmd; - segment_size, // uint32_t cmdsize; - {0}, // char segname[16]; - addr, // uint64_t vmaddr; // uint32_t for 32-bit Mach-O - size, // uint64_t vmsize; // uint32_t for 32-bit Mach-O - 0, // uint64_t fileoff; // uint32_t for 32-bit Mach-O - size, // uint64_t filesize; // uint32_t for 32-bit Mach-O - prot, // uint32_t maxprot; - prot, // uint32_t initprot; - 0, // uint32_t nsects; - 0}; // uint32_t flags; - segment_load_commands.push_back(segment); - } else { - // No protections and a size of 1 used to be returned from old - // debugservers when we asked about a region that was past the - // last memory region and it indicates the end... - if (size == 1) - break; } range_error = process_sp->GetMemoryRegionInfo( @@ -6293,6 +6532,51 @@ break; } + // Combine contiguous entries that have the same + // protections so we don't have an excess of + // load commands. + std::vector combined_page_objects; + page_object last_obj; + last_obj.addr = LLDB_INVALID_ADDRESS; + for (page_object obj : pages_to_copy) { + if (last_obj.addr == LLDB_INVALID_ADDRESS) { + last_obj = obj; + continue; + } + if (last_obj.addr + last_obj.size == obj.addr && + last_obj.prot == obj.prot) { + last_obj.size += obj.size; + continue; + } + combined_page_objects.push_back(last_obj); + last_obj = obj; + } + + for (page_object obj : combined_page_objects) { + 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); + } + llvm::MachO::segment_command_64 segment = { + cmd_type, // uint32_t cmd; + segment_size, // uint32_t cmdsize; + {0}, // char segname[16]; + obj.addr, // uint64_t vmaddr; // uint32_t for 32-bit + // Mach-O + obj.size, // uint64_t vmsize; // uint32_t for 32-bit + // Mach-O + 0, // uint64_t fileoff; // uint32_t for 32-bit Mach-O + obj.size, // uint64_t filesize; // uint32_t for 32-bit + // Mach-O + obj.prot, // uint32_t maxprot; + obj.prot, // uint32_t initprot; + 0, // uint32_t nsects; + 0}; // uint32_t flags; + segment_load_commands.push_back(segment); + } + StreamString buffer(Stream::eBinary, addr_byte_size, byte_order); llvm::MachO::mach_header_64 mach_header; @@ -6363,6 +6647,10 @@ mach_header.sizeofcmds += 8 + LC_THREAD_data.GetSize(); } + // 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); @@ -6378,10 +6666,33 @@ // 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; - if (file_offset & 0x00000fff) { - file_offset += 0x00001000ull; - file_offset &= (~0x00001000ull + 1); - } + + file_offset = llvm::alignTo(file_offset, 16); + + // Create the "all image infos" LC_NOTE payload + StreamString all_image_infos_payload(Stream::eBinary, addr_byte_size, + byte_order); + offset_t all_image_infos_payload_start = file_offset; + file_offset = CreateAllImageInfosPayload(process_sp, file_offset, + all_image_infos_payload); + + // Add the "all image infos" LC_NOTE load command + llvm::MachO::note_command all_image_info_note = { + LC_NOTE, /* uint32_t cmd */ + sizeof(llvm::MachO::note_command), /* uint32_t cmdsize */ + "all image infos", /* char data_owner[16] */ + all_image_infos_payload_start, /* uint64_t offset */ + file_offset - all_image_infos_payload_start /* uint64_t size */ + }; + buffer.PutHex32(all_image_info_note.cmd); + buffer.PutHex32(all_image_info_note.cmdsize); + buffer.PutRawBytes(all_image_info_note.data_owner, + sizeof(all_image_info_note.data_owner)); + buffer.PutHex64(all_image_info_note.offset); + buffer.PutHex64(all_image_info_note.size); + + // 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; @@ -6398,14 +6709,6 @@ // Write out all of the segment load commands for (const auto &segment : segment_load_commands) { - printf("0x%8.8x 0x%8.8x [0x%16.16" PRIx64 " - 0x%16.16" PRIx64 - ") [0x%16.16" PRIx64 " 0x%16.16" PRIx64 - ") 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x]\n", - segment.cmd, segment.cmdsize, segment.vmaddr, - segment.vmaddr + segment.vmsize, segment.fileoff, - segment.filesize, segment.maxprot, segment.initprot, - segment.nsects, segment.flags); - buffer.PutHex32(segment.cmd); buffer.PutHex32(segment.cmdsize); buffer.PutRawBytes(segment.segname, sizeof(segment.segname)); @@ -6440,6 +6743,20 @@ error = core_file.get()->Write(buffer.GetString().data(), bytes_written); if (error.Success()) { + + if (core_file.get()->SeekFromStart(all_image_info_note.offset) == + -1) { + error.SetErrorStringWithFormat( + "Unable to seek to corefile pos to write all iamge infos"); + return false; + } + + bytes_written = all_image_infos_payload.GetString().size(); + error = core_file.get()->Write( + all_image_infos_payload.GetString().data(), 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) { @@ -6449,9 +6766,10 @@ break; } - printf("Saving %" PRId64 - " bytes of data for memory region at 0x%" PRIx64 "\n", - segment.vmsize, segment.vmaddr); + 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; @@ -6493,3 +6811,121 @@ } return false; } + +ObjectFileMachO::MachOCorefileAllImageInfos +ObjectFileMachO::GetCorefileAllImageInfos() { + MachOCorefileAllImageInfos image_infos; + + // Look for an "all image infos" LC_NOTE. + lldb::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 (m_data.GetU32(&offset, &lc.cmd, 2) == nullptr) + break; + if (lc.cmd == LC_NOTE) { + char data_owner[17]; + m_data.CopyData(offset, 16, data_owner); + data_owner[16] = '\0'; + offset += 16; + uint64_t fileoff = m_data.GetU64_unchecked(&offset); + offset += 4; /* size unused */ + + if (strcmp("all image infos", data_owner) == 0) { + offset = fileoff; + // Read the struct all_image_infos_header. + uint32_t version = m_data.GetU32(&offset); + if (version != 1) { + return image_infos; + } + uint32_t imgcount = m_data.GetU32(&offset); + uint64_t entries_fileoff = m_data.GetU64(&offset); + offset += 4; // uint32_t entries_size; + offset += 4; // uint32_t unused; + + offset = entries_fileoff; + for (uint32_t i = 0; i < imgcount; i++) { + // Read the struct image_entry. + offset_t filepath_offset = m_data.GetU64(&offset); + uuid_t uuid; + memcpy(&uuid, m_data.GetData(&offset, sizeof(uuid_t)), + sizeof(uuid_t)); + uint64_t load_address = m_data.GetU64(&offset); + offset_t seg_addrs_offset = m_data.GetU64(&offset); + uint32_t segment_count = m_data.GetU32(&offset); + uint32_t currently_executing = m_data.GetU32(&offset); + + MachOCorefileImageEntry image_entry; + image_entry.filename = (const char *)m_data.GetCStr(&filepath_offset); + image_entry.uuid = UUID::fromData(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.CopyData(seg_vmaddrs_offset, 16, segname); + segname[16] = '\0'; + seg_vmaddrs_offset += 16; + uint64_t vmaddr = m_data.GetU64(&seg_vmaddrs_offset); + seg_vmaddrs_offset += 8; /* unused */ + + std::tuple new_seg{ConstString(segname), + vmaddr}; + image_entry.segment_load_addresses.push_back(new_seg); + } + image_infos.all_image_infos.push_back(image_entry); + } + } + } + offset = cmd_offset + lc.cmdsize; + } + + return image_infos; +} + +bool ObjectFileMachO::LoadCoreFileImages(lldb_private::Process &process) { + MachOCorefileAllImageInfos image_infos = GetCorefileAllImageInfos(); + bool added_images = false; + if (image_infos.IsValid()) { + for (const MachOCorefileImageEntry &image : image_infos.all_image_infos) { + ModuleSpec module_spec; + module_spec.GetUUID() = image.uuid; + module_spec.GetFileSpec() = FileSpec(image.filename.c_str()); + if (image.currently_executing) { + Symbols::DownloadObjectAndSymbolFile(module_spec, true); + if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) { + process.GetTarget().GetOrCreateModule(module_spec, false); + } + } + Status error; + ModuleSP module_sp = + process.GetTarget().GetOrCreateModule(module_spec, false, &error); + if (!module_sp.get() || !module_sp->GetObjectFile()) { + if (image.load_address != LLDB_INVALID_ADDRESS) { + module_sp = process.ReadModuleFromMemory(module_spec.GetFileSpec(), + image.load_address); + } + } + if (module_sp.get() && module_sp->GetObjectFile()) { + added_images = true; + if (module_sp->GetObjectFile()->GetType() == + ObjectFile::eTypeExecutable) { + process.GetTarget().SetExecutableModule(module_sp, eLoadDependentsNo); + } + for (auto name_vmaddr_tuple : image.segment_load_addresses) { + SectionList *sectlist = module_sp->GetObjectFile()->GetSectionList(); + 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)); + } + } + } + } + } + } + return added_images; +} diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h @@ -79,6 +79,7 @@ static bool SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle &core_style, lldb_private::Status &error); static bool MagicBytesMatch(lldb::DataBufferSP &data_sp); diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -189,7 +189,9 @@ bool ObjectFilePECOFF::SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle &core_style, lldb_private::Status &error) { + core_style = eSaveCoreFull; return SaveMiniDump(process_sp, outfile, error); } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -592,6 +592,7 @@ UINT32_MAX; // from reply to qGDBServerVersion, zero if // qGDBServerVersion is not supported std::chrono::seconds m_default_packet_timeout; + int m_target_vm_page_size = 0; // target system VM page size; 0 unspecified uint64_t m_max_packet_size = 0; // as returned by qSupported std::string m_qSupported_response; // the complete response to qSupported diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -16,6 +16,7 @@ #include "lldb/Core/ModuleSpec.h" #include "lldb/Host/HostInfo.h" +#include "lldb/Host/StringConvert.h" #include "lldb/Host/XML.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/MemoryRegionInfo.h" @@ -285,6 +286,7 @@ m_gdb_server_name.clear(); m_gdb_server_version = UINT32_MAX; m_default_packet_timeout = seconds(0); + m_target_vm_page_size = 0; m_max_packet_size = 0; m_qSupported_response.clear(); m_supported_async_json_packets_is_valid = false; @@ -1192,6 +1194,12 @@ SetPacketTimeout(m_default_packet_timeout); ++num_keys_decoded; } + } else if (name.equals("vm-page-size")) { + int page_size; + if (!value.getAsInteger(0, page_size)) { + m_target_vm_page_size = page_size; + ++num_keys_decoded; + } } } @@ -1503,9 +1511,30 @@ // Now convert the HEX bytes into a string value error_extractor.GetHexByteString(error_string); error.SetErrorString(error_string.c_str()); + } else if (name.equals("dirty-pages")) { + std::vector dirty_page_list; + std::string comma_sep_str = value.str(); + size_t comma_pos; + addr_t page; + while ((comma_pos = comma_sep_str.find(',')) != std::string::npos) { + comma_sep_str[comma_pos] = '\0'; + page = StringConvert::ToUInt64(comma_sep_str.c_str(), + LLDB_INVALID_ADDRESS, 16); + if (page != LLDB_INVALID_ADDRESS) + dirty_page_list.push_back(page); + comma_sep_str.erase(0, comma_pos + 1); + } + page = StringConvert::ToUInt64(comma_sep_str.c_str(), + LLDB_INVALID_ADDRESS, 16); + if (page != LLDB_INVALID_ADDRESS) + dirty_page_list.push_back(page); + region_info.SetDirtyPageList(dirty_page_list); } } + if (m_target_vm_page_size != 0) + region_info.SetPageSize(m_target_vm_page_size); + if (region_info.GetRange().IsValid()) { // We got a valid address range back but no permissions -- which means // this is an unmapped page diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp --- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp @@ -332,7 +332,6 @@ m_core_range_infos.Sort(); } - bool found_main_binary_definitively = false; addr_t objfile_binary_addr; @@ -414,6 +413,14 @@ } } + // If we have a "all image infos" LC_NOTE, try to load all of the + // binaries listed, and set their Section load addresses in the Target. + if (found_main_binary_definitively == false && + core_objfile->LoadCoreFileImages(*this)) { + m_dyld_plugin_name = DynamicLoaderDarwinKernel::GetPluginNameStatic(); + found_main_binary_definitively = true; + } + if (!found_main_binary_definitively && (m_dyld_addr == LLDB_INVALID_ADDRESS || m_mach_kernel_addr == LLDB_INVALID_ADDRESS)) { diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py @@ -0,0 +1,65 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from gdbclientutils import * + + +class TestMemoryRegionDirtyPages(GDBRemoteTestBase): + + @skipIfXmlSupportMissing + def test(self): + class MyResponder(MockGDBServerResponder): + + def qHostInfo(self): + return "ptrsize:8;endian:little;vm-page-size:4096;" + + def qMemoryRegionInfo(self, addr): + if addr == 0: + return "start:0;size:100000000;" + if addr == 0x100000000: + return "start:100000000;size:4000;permissions:rx;dirty-pages:;" + if addr == 0x100004000: + return "start:100004000;size:4000;permissions:r;dirty-pages:0x100004000;" + if addr == 0x1000a2000: + return "start:1000a2000;size:5000;permissions:r;dirty-pages:0x1000a2000,0x1000a3000,0x1000a4000,0x1000a5000,0x1000a6000;" + + self.server.responder = MyResponder() + target = self.dbg.CreateTarget('') + if self.TraceOn(): + self.runCmd("log enable gdb-remote packets") + self.addTearDownHook( + lambda: self.runCmd("log disable gdb-remote packets")) + process = self.connect(target) + + # A memory region where we don't know anything about dirty pages + region = lldb.SBMemoryRegionInfo() + err = process.GetMemoryRegionInfo(0, region) + self.assertTrue(err.Success()) + self.assertFalse(region.HasDirtyMemoryPageList()) + self.assertEqual(region.GetNumDirtyPages(), 0) + region.Clear() + + # A memory region with dirty page information -- and zero dirty pages + err = process.GetMemoryRegionInfo(0x100000000, region) + self.assertTrue(err.Success()) + self.assertTrue(region.HasDirtyMemoryPageList()) + self.assertEqual(region.GetNumDirtyPages(), 0) + self.assertEqual(region.GetPageSize(), 4096) + region.Clear() + + # A memory region with one dirty page + err = process.GetMemoryRegionInfo(0x100004000, region) + self.assertTrue(err.Success()) + self.assertTrue(region.HasDirtyMemoryPageList()) + self.assertEqual(region.GetNumDirtyPages(), 1) + self.assertEqual(region.GetDirtyPageAddressAtIndex(0), 0x100004000) + region.Clear() + + # A memory region with multple dirty pages + err = process.GetMemoryRegionInfo(0x1000a2000, region) + self.assertTrue(err.Success()) + self.assertTrue(region.HasDirtyMemoryPageList()) + self.assertEqual(region.GetNumDirtyPages(), 5) + self.assertEqual(region.GetDirtyPageAddressAtIndex(4), 0x1000a6000) + region.Clear() + diff --git a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py --- a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py +++ b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py @@ -166,7 +166,7 @@ if packet == "QListThreadsInStopReply": return self.QListThreadsInStopReply() if packet.startswith("qMemoryRegionInfo:"): - return self.qMemoryRegionInfo() + return self.qMemoryRegionInfo(int(packet.split(':')[1], 16)) if packet == "qQueryGDBServer": return self.qQueryGDBServer() if packet == "qHostInfo": @@ -282,7 +282,7 @@ def QListThreadsInStopReply(self): return "" - def qMemoryRegionInfo(self): + def qMemoryRegionInfo(self, addr): return "" def qPathComplete(self): diff --git a/lldb/test/API/macosx/skinny-corefile/Makefile b/lldb/test/API/macosx/skinny-corefile/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/Makefile @@ -0,0 +1,15 @@ +LD_EXTRAS = -L. -lto-be-removed -lpresent +C_SOURCES = main.c + +include Makefile.rules + +a.out: libto-be-removed libpresent + +libto-be-removed: libpresent + $(MAKE) -f $(MAKEFILE_RULES) \ + DYLIB_ONLY=YES DYLIB_C_SOURCES=to-be-removed.c DYLIB_NAME=to-be-removed \ + LD_EXTRAS="-L. -lpresent" + +libpresent: + $(MAKE) -f $(MAKEFILE_RULES) \ + DYLIB_ONLY=YES DYLIB_C_SOURCES=present.c DYLIB_NAME=present diff --git a/lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py b/lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py @@ -0,0 +1,162 @@ +"""Test that lldb can create a skinny corefile, and load all available libraries correctly.""" + + + +import os +import re +import subprocess + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestFirmwareCorefiles(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIf(debug_info=no_match(["dsym"]), bugnumber="This test is looking explicitly for a dSYM") + @skipUnlessDarwin + def test_lc_note(self): + self.build() + self.aout_exe = self.getBuildArtifact("a.out") + self.aout_dsym = self.getBuildArtifact("a.out.dSYM") + self.to_be_removed_dylib = self.getBuildArtifact("libto-be-removed.dylib") + self.to_be_removed_dsym = self.getBuildArtifact("libto-be-removed.dylib.dSYM") + self.corefile = self.getBuildArtifact("process.core") + self.dsym_for_uuid = self.getBuildArtifact("dsym-for-uuid.sh") + + # After the corefile is created, we'll move a.out and a.out.dSYM + # into hide.noindex and lldb will have to use the + # LLDB_APPLE_DSYMFORUUID_EXECUTABLE script to find them. + self.hide_dir = self.getBuildArtifact("hide.noindex") + lldbutil.mkdir_p(self.hide_dir) + self.hide_aout_exe = self.getBuildArtifact("hide.noindex/a.out") + self.hide_aout_dsym = self.getBuildArtifact("hide.noindex/a.out.dSYM") + + # We can hook in our dsym-for-uuid shell script to lldb with + # this env var instead of requiring a defaults write. + os.environ['LLDB_APPLE_DSYMFORUUID_EXECUTABLE'] = self.dsym_for_uuid + self.addTearDownHook(lambda: os.environ.pop('LLDB_APPLE_DSYMFORUUID_EXECUTABLE', None)) + + dwarfdump_uuid_regex = re.compile( + 'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*') + dwarfdump_cmd_output = subprocess.check_output( + ('/usr/bin/dwarfdump --uuid "%s"' % self.aout_exe), shell=True).decode("utf-8") + aout_uuid = None + for line in dwarfdump_cmd_output.splitlines(): + match = dwarfdump_uuid_regex.search(line) + if match: + aout_uuid = match.group(1) + self.assertNotEqual(aout_uuid, None, "Could not get uuid of built a.out") + + ### Create our dsym-for-uuid shell script which returns self.hide_aout_exe. + shell_cmds = [ + '#! /bin/sh', + '# the last argument is the uuid', + 'while [ $# -gt 1 ]', + 'do', + ' shift', + 'done', + 'ret=0', + 'echo ""', + 'echo ""', + 'echo ""', + '', + 'if [ "$1" = "%s" ]' % aout_uuid, + 'then', + ' uuid=%s' % aout_uuid, + ' bin=%s' % self.hide_aout_exe, + ' dsym=%s.dSYM/Contents/Resources/DWARF/%s' % (self.hide_aout_exe, os.path.basename(self.hide_aout_exe)), + 'fi', + 'if [ -z "$uuid" -o -z "$bin" -o ! -f "$bin" ]', + 'then', + ' echo "DBGErrornot found"', + ' echo ""', + ' exit 1', + 'fi', + 'echo "$uuid"', + '', + 'echo "DBGArchitecturex86_64"', + 'echo "DBGDSYMPath$dsym"', + 'echo "DBGSymbolRichExecutable$bin"', + 'echo ""', + 'exit $ret' + ] + + with open(self.dsym_for_uuid, "w") as writer: + for l in shell_cmds: + writer.write(l + '\n') + + os.chmod(self.dsym_for_uuid, 0o755) + + + # Launch a live process with a.out, libto-be-removed.dylib, + # libpresent.dylib all in their original locations, create + # a corefile at the breakpoint. + (target, process, t, bp) = lldbutil.run_to_source_breakpoint ( + self, "break here", lldb.SBFileSpec('present.c')) + + self.assertTrue(process.IsValid()) + + if self.TraceOn(): + self.runCmd("bt") + self.runCmd("image list") + + self.runCmd("process save-core " + self.corefile) + process.Kill() + target.Clear() + + # Move the main binary and its dSYM into the hide.noindex + # directory. Now the only way lldb can find them is with + # the LLDB_APPLE_DSYMFORUUID_EXECUTABLE shell script - + # so we're testing that this dSYM discovery method works. + os.rename(self.aout_exe, self.hide_aout_exe) + os.rename(self.aout_dsym, self.hide_aout_dsym) + + # Completely remove the libto-be-removed.dylib, so we're + # testing that lldb handles an unavailable binary correctly, + # and non-dirty memory from this binary (e.g. the executing + # instructions) are NOT included in the corefile. + os.unlink(self.to_be_removed_dylib) + shutil.rmtree(self.to_be_removed_dsym) + + + # Now load the corefile + self.target = self.dbg.CreateTarget('') + self.process = self.target.LoadCore(self.corefile) + self.assertTrue(self.process.IsValid()) + if self.TraceOn(): + self.runCmd("image list") + self.runCmd("bt") + + self.assertTrue(self.process.IsValid()) + self.assertTrue(self.process.GetSelectedThread().IsValid()) + + # f0 is present() in libpresent.dylib + f0 = self.process.GetSelectedThread().GetFrameAtIndex(0) + to_be_removed_dirty_data = f0.FindVariable("to_be_removed_dirty_data") + self.assertEqual(to_be_removed_dirty_data.GetValueAsUnsigned(), 20) + + present_heap_buf = f0.FindVariable("present_heap_buf") + self.assertTrue("have ints 5 20 20 5" in present_heap_buf.GetSummary()) + + + # f1 is to_be_removed() in libto-be-removed.dylib + # it has been removed since the corefile was created, + # and the instructions for this frame should NOT be included + # in the corefile. They were not dirty pages. + f1 = self.process.GetSelectedThread().GetFrameAtIndex(1) + err = lldb.SBError() + uint = self.process.ReadUnsignedFromMemory(f1.GetPC(), 4, err) + self.assertTrue(err.Fail()) + + + # TODO Future testing could check that read-only constant data + # (main_const_data, present_const_data) can be read both as an + # SBValue and in an expression -- which means lldb needs to read + # them out of the binaries, they are not present in the corefile. + # And checking file-scope dirty data (main_dirty_data, + # present_dirty_data) the same way would be good, instead of just + # checking the heap and stack like are being done right now. diff --git a/lldb/test/API/macosx/skinny-corefile/main.c b/lldb/test/API/macosx/skinny-corefile/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/main.c @@ -0,0 +1,20 @@ +#include +#include +#include + +#include "present.h" +#include "to-be-removed.h" + +const int main_const_data = 5; +int main_dirty_data = 10; +int main(int argc, char **argv) { + + to_be_removed_init(argc); + present_init(argc); + main_dirty_data += argc; + + char *heap_buf = (char *)malloc(80); + strcpy(heap_buf, "this is a string on the heap"); + + return to_be_removed(heap_buf, main_const_data, main_dirty_data); +} diff --git a/lldb/test/API/macosx/skinny-corefile/present.h b/lldb/test/API/macosx/skinny-corefile/present.h new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/present.h @@ -0,0 +1,2 @@ +void present_init (int in); +int present (char *to_be_removed_heap_buf, int to_be_removed_const_data, int to_be_removed_dirty_data); diff --git a/lldb/test/API/macosx/skinny-corefile/present.c b/lldb/test/API/macosx/skinny-corefile/present.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/present.c @@ -0,0 +1,22 @@ +#include +#include + +#include "present.h" + +const int present_const_data = 5; +int present_dirty_data = 10; + +void present_init(int in) { present_dirty_data += 10; } + +int present(char *to_be_removed_heap_buf, int to_be_removed_const_data, + int to_be_removed_dirty_data) { + char *present_heap_buf = (char *)malloc(256); + sprintf(present_heap_buf, "have ints %d %d %d %d", to_be_removed_const_data, + to_be_removed_dirty_data, present_dirty_data, present_const_data); + printf("%s\n", present_heap_buf); + puts(to_be_removed_heap_buf); + + puts("break here"); + + return present_const_data + present_dirty_data; +} diff --git a/lldb/test/API/macosx/skinny-corefile/to-be-removed.h b/lldb/test/API/macosx/skinny-corefile/to-be-removed.h new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/to-be-removed.h @@ -0,0 +1,2 @@ +void to_be_removed_init (int in); +int to_be_removed (char *main_heap_buf, int main_const_data, int main_dirty_data); diff --git a/lldb/test/API/macosx/skinny-corefile/to-be-removed.c b/lldb/test/API/macosx/skinny-corefile/to-be-removed.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/skinny-corefile/to-be-removed.c @@ -0,0 +1,21 @@ +#include +#include + +#include "present.h" +#include "to-be-removed.h" + +const int to_be_removed_const_data = 5; +int to_be_removed_dirty_data = 10; + +void to_be_removed_init(int in) { to_be_removed_dirty_data += 10; } + +int to_be_removed(char *main_heap_buf, int main_const_data, + int main_dirty_data) { + char *to_be_removed_heap_buf = (char *)malloc(256); + sprintf(to_be_removed_heap_buf, "got string '%s' have int %d %d %d", + main_heap_buf, to_be_removed_dirty_data, main_const_data, + main_dirty_data); + printf("%s\n", to_be_removed_heap_buf); + return present(to_be_removed_heap_buf, to_be_removed_const_data, + to_be_removed_dirty_data); +} diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py --- a/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py +++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py @@ -30,6 +30,7 @@ "ptrsize", "triple", "vendor", + "vm-page-size", "watchpoint_exceptions_received", ]) diff --git a/lldb/tools/debugserver/source/DNBDefs.h b/lldb/tools/debugserver/source/DNBDefs.h --- a/lldb/tools/debugserver/source/DNBDefs.h +++ b/lldb/tools/debugserver/source/DNBDefs.h @@ -18,6 +18,7 @@ #include #include #include +#include // Define nub_addr_t and the invalid address value from the architecture #if defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__) @@ -316,9 +317,12 @@ }; struct DNBRegionInfo { +public: + DNBRegionInfo() : addr(0), size(0), permissions(0), dirty_pages() {} nub_addr_t addr; nub_addr_t size; uint32_t permissions; + std::vector dirty_pages; }; enum DNBProfileDataScanType { diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp --- a/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp @@ -72,6 +72,49 @@ return count; } +#define MAX_STACK_ALLOC_DISPOSITIONS \ + (16 * 1024 / sizeof(int)) // 16K of allocations + +std::vector get_dirty_pages(task_t task, mach_vm_address_t addr, + mach_vm_size_t size) { + std::vector dirty_pages; + + int pages_to_query = size / vm_page_size; + // Don't try to fetch too many pages' dispositions in a single call or we + // could blow our stack out. + mach_vm_size_t dispositions_size = + std::min(pages_to_query, (int)MAX_STACK_ALLOC_DISPOSITIONS); + int dispositions[dispositions_size]; + + mach_vm_size_t chunk_count = + ((pages_to_query + MAX_STACK_ALLOC_DISPOSITIONS - 1) / + MAX_STACK_ALLOC_DISPOSITIONS); + + for (mach_vm_size_t cur_disposition_chunk = 0; + cur_disposition_chunk < chunk_count; cur_disposition_chunk++) { + mach_vm_size_t dispositions_already_queried = + cur_disposition_chunk * MAX_STACK_ALLOC_DISPOSITIONS; + + mach_vm_size_t chunk_pages_to_query = std::min( + pages_to_query - dispositions_already_queried, dispositions_size); + mach_vm_address_t chunk_page_aligned_start_addr = + addr + (dispositions_already_queried * vm_page_size); + + kern_return_t kr = mach_vm_page_range_query( + task, chunk_page_aligned_start_addr, + chunk_pages_to_query * vm_page_size, (mach_vm_address_t)dispositions, + &chunk_pages_to_query); + if (kr != KERN_SUCCESS) + return dirty_pages; + for (mach_vm_size_t i = 0; i < chunk_pages_to_query; i++) { + uint64_t dirty_addr = chunk_page_aligned_start_addr + (i * vm_page_size); + if (dispositions[i] & VM_PAGE_QUERY_PAGE_DIRTY) + dirty_pages.push_back(dirty_addr); + } + } + return dirty_pages; +} + nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address, DNBRegionInfo *region_info) { MachVMRegion vmRegion(task); @@ -80,6 +123,8 @@ region_info->addr = vmRegion.StartAddress(); region_info->size = vmRegion.GetByteSize(); region_info->permissions = vmRegion.GetDNBPermissions(); + region_info->dirty_pages = + get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize()); } else { region_info->addr = address; region_info->size = 0; diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -4447,7 +4448,7 @@ __FILE__, __LINE__, p, "Invalid address in qMemoryRegionInfo packet"); } - DNBRegionInfo region_info = {0, 0, 0}; + DNBRegionInfo region_info; DNBProcessMemoryRegionInfo(m_ctx.ProcessID(), address, ®ion_info); std::ostringstream ostrm; @@ -4467,6 +4468,18 @@ if (region_info.permissions & eMemoryPermissionsExecutable) ostrm << 'x'; ostrm << ';'; + + ostrm << "dirty-pages:"; + if (region_info.dirty_pages.size() > 0) { + bool first = true; + for (nub_addr_t addr : region_info.dirty_pages) { + if (!first) + ostrm << ","; + first = false; + ostrm << "0x" << std::hex << addr; + } + } + ostrm << ";"; } return SendPacket(ostrm.str()); } @@ -4993,6 +5006,8 @@ strm << "default_packet_timeout:10;"; #endif + strm << "vm-page-size:" << std::dec << vm_page_size << ";"; + return SendPacket(strm.str()); }