jasonmolenda updated this revision to Diff 295963. jasonmolenda added a comment. Herald added a subscriber: dang.
This patch update incorporates most of Greg's suggestions. Most importantly, I isolated all of the loading of the binaries specified in the corefile down into ObjectFileMachO so I didn't have to add so much special case API to the ObjectFile base API. Greg made a case in direct email for the usefulness of a full coredump -- e.g. people may save a skinny core on one mac system, and send it to someone on a different mac system where the system libraries are all different, and no backtraces would be meaningful. This patch implements a -f option to process save-core to force a full coredump, if the system defaults to skinny (as it will on macos). The alternative we were discussing is a hybrid approach where the core dumper saves all of the memory pages that cover the binary, for images that are currently executing on a thread. We'd need the full symbol table, mach-o header, and text which is executing, so it's probably easier to just copy the whole thing in than to try to pare it back. This is an interesting idea, and most of the groundwork is laid for it - we are already computing which libraries are actively executing and noting that in the corefile metadata so lldb can do more expensive searches for those binary images. I switched to using the RangeDataVector container, but it has a little bug in the CombineConsecutiveEntriesWithEqualData where consecutive entries with the same Data value are combined, even if their ranges are not consecutive. So you could get a vast address range claimed if a user process page and a shared cache page had the same protections. Fixing that and writing a test case, I found a second problem where RangeDataVector isn't setting its 'upper_bound' field properly for this use case and searches don't work as they should. At which point I booted it out of the patch and went back to the hand-rolled container that I'd started with; I'll try to look at RangeDataVector more closely next week and switch to using it in this method. It is a cleaner and simpler approach. I also got rid of the "executing uuids" LC_NOTE - as Greg pointed out, I have a perfectly good unused field for alignment in the binary image entries already, using that to flag executing images was even easier. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D88387/new/ https://reviews.llvm.org/D88387 Files: lldb/docs/lldb-gdb-remote.txt lldb/include/lldb/Core/PluginManager.h lldb/include/lldb/Symbol/ObjectFile.h lldb/include/lldb/Target/MemoryRegionInfo.h lldb/include/lldb/lldb-private-interfaces.h lldb/source/API/SBProcess.cpp lldb/source/Commands/CommandObjectProcess.cpp lldb/source/Commands/Options.td lldb/source/Core/PluginManager.cpp lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp lldb/test/API/macosx/skinny-corefile/Makefile lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py lldb/test/API/macosx/skinny-corefile/main.c lldb/test/API/macosx/skinny-corefile/present.c lldb/test/API/macosx/skinny-corefile/present.h lldb/test/API/macosx/skinny-corefile/to-be-removed.c lldb/test/API/macosx/skinny-corefile/to-be-removed.h lldb/tools/debugserver/source/DNBDefs.h lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp lldb/tools/debugserver/source/RNBRemote.cpp
Index: lldb/tools/debugserver/source/RNBRemote.cpp =================================================================== --- lldb/tools/debugserver/source/RNBRemote.cpp +++ lldb/tools/debugserver/source/RNBRemote.cpp @@ -18,6 +18,7 @@ #include <libproc.h> #include <mach-o/loader.h> #include <mach/exception_types.h> +#include <mach/mach_vm.h> #include <mach/task_info.h> #include <pwd.h> #include <signal.h> @@ -4422,7 +4423,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; @@ -4442,6 +4443,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()); } @@ -4963,6 +4976,8 @@ strm << "default_packet_timeout:10;"; #endif + strm << "vm-page-size:" << std::dec << vm_page_size << ";"; + return SendPacket(strm.str()); } Index: lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp +++ 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<nub_addr_t> get_dirty_pages(task_t task, mach_vm_address_t addr, + mach_vm_size_t size) { + std::vector<nub_addr_t> 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; Index: lldb/tools/debugserver/source/DNBDefs.h =================================================================== --- lldb/tools/debugserver/source/DNBDefs.h +++ lldb/tools/debugserver/source/DNBDefs.h @@ -18,6 +18,7 @@ #include <stdio.h> #include <sys/syslimits.h> #include <unistd.h> +#include <vector> // 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<nub_addr_t> dirty_pages; }; enum DNBProfileDataScanType { Index: lldb/test/API/macosx/skinny-corefile/to-be-removed.h =================================================================== --- /dev/null +++ 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); Index: lldb/test/API/macosx/skinny-corefile/to-be-removed.c =================================================================== --- /dev/null +++ lldb/test/API/macosx/skinny-corefile/to-be-removed.c @@ -0,0 +1,21 @@ +#include <stdio.h> +#include <stdlib.h> + +#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); +} Index: lldb/test/API/macosx/skinny-corefile/present.h =================================================================== --- /dev/null +++ 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); Index: lldb/test/API/macosx/skinny-corefile/present.c =================================================================== --- /dev/null +++ lldb/test/API/macosx/skinny-corefile/present.c @@ -0,0 +1,22 @@ +#include <stdio.h> +#include <stdlib.h> + +#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; +} Index: lldb/test/API/macosx/skinny-corefile/main.c =================================================================== --- /dev/null +++ lldb/test/API/macosx/skinny-corefile/main.c @@ -0,0 +1,20 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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); +} Index: lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py =================================================================== --- /dev/null +++ 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 "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>"', + 'echo "<!DOCTYPE plist PUBLIC \\"-//Apple//DTD PLIST 1.0//EN\\" \\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\">"', + 'echo "<plist version=\\"1.0\\">"', + '', + '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 "<key>DBGError</key><string>not found</string>"', + ' echo "</plist>"', + ' exit 1', + 'fi', + 'echo "<dict><key>$uuid</key><dict>"', + '', + 'echo "<key>DBGArchitecture</key><string>x86_64</string>"', + 'echo "<key>DBGDSYMPath</key><string>$dsym</string>"', + 'echo "<key>DBGSymbolRichExecutable</key><string>$bin</string>"', + 'echo "</dict></dict></plist>"', + '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. Index: lldb/test/API/macosx/skinny-corefile/Makefile =================================================================== --- /dev/null +++ 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 Index: lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp =================================================================== --- lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +++ lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp @@ -276,7 +276,6 @@ m_core_range_infos.Sort(); } - bool found_main_binary_definitively = false; addr_t objfile_binary_addr; @@ -404,6 +403,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)) { Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -244,6 +244,10 @@ std::chrono::seconds GetHostDefaultPacketTimeout(); + // Returns the size of VM pages on the target system; + // 0 is returned for an un-set value. + int GetTargetVMPageSize(); + const ArchSpec &GetProcessArchitecture(); void GetRemoteQSupported(); @@ -585,6 +589,7 @@ uint32_t m_gdb_server_version; // from reply to qGDBServerVersion, zero if // qGDBServerVersion is not supported std::chrono::seconds m_default_packet_timeout; + int m_target_vm_page_size; // target system VM page size; 0 if unspecified uint64_t m_max_packet_size; // as returned by qSupported std::string m_qSupported_response; // the complete response to qSupported Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ 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" @@ -102,7 +103,7 @@ m_num_supported_hardware_watchpoints(0), m_host_arch(), m_process_arch(), m_os_build(), m_os_kernel(), m_hostname(), m_gdb_server_name(), m_gdb_server_version(UINT32_MAX), m_default_packet_timeout(0), - m_max_packet_size(0), m_qSupported_response(), + m_target_vm_page_size(0), m_max_packet_size(0), m_qSupported_response(), m_supported_async_json_packets_is_valid(false), m_supported_async_json_packets_sp(), m_qXfer_memory_map(), m_qXfer_memory_map_loaded(false) {} @@ -321,6 +322,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; @@ -1245,6 +1247,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; + } } } @@ -1380,6 +1388,12 @@ return m_default_packet_timeout; } +int GDBRemoteCommunicationClient::GetTargetVMPageSize() { + if (m_qHostInfo_is_valid == eLazyBoolCalculate) + GetHostInfo(); + return m_target_vm_page_size; +} + addr_t GDBRemoteCommunicationClient::AllocateMemory(size_t size, uint32_t permissions) { if (m_supports_alloc_dealloc_memory != eLazyBoolNo) { @@ -1529,6 +1543,24 @@ std::string name; name_extractor.GetHexByteString(name); region_info.SetName(name.c_str()); + } else if (name.equals("dirty-pages")) { + std::vector<addr_t> 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); } else if (name.equals("error")) { StringExtractorGDBRemote error_extractor(value); std::string error_string; @@ -1538,6 +1570,9 @@ } } + if (GetTargetVMPageSize() != 0) + region_info.SetPageSize(GetTargetVMPageSize()); + if (region_info.GetRange().IsValid()) { // We got a valid address range back but no permissions -- which means // this is an unmapped page Index: lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h =================================================================== --- lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h +++ lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h @@ -79,7 +79,7 @@ static bool SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, - lldb_private::Status &error); + bool full_corefile, lldb_private::Status &error); static bool MagicBytesMatch(lldb::DataBufferSP &data_sp); Index: lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp =================================================================== --- lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -219,6 +219,7 @@ bool ObjectFilePECOFF::SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + bool full_corefile, lldb_private::Status &error) { return SaveMiniDump(process_sp, outfile, error); } Index: lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h =================================================================== --- lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h +++ lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h @@ -15,6 +15,7 @@ #include "lldb/Symbol/ObjectFile.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/RangeMap.h" +#include "lldb/Utility/StreamString.h" #include "lldb/Utility/UUID.h" // This class needs to be hidden as eventually belongs in a plugin that @@ -58,7 +59,7 @@ static bool SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, - lldb_private::Status &error); + bool full_corefile, lldb_private::Status &error); static bool MagicBytesMatch(lldb::DataBufferSP &data_sp, lldb::addr_t offset, lldb::addr_t length); @@ -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,38 @@ bool SectionIsLoadable(const lldb_private::Section *section); + typedef lldb_private::RangeDataVector<lldb::addr_t, lldb::addr_t, uint32_t> + PageObjects; + + static lldb::offset_t CreateAllImageInfosPayload( + const lldb::ProcessSP &process_sp, lldb::offset_t file_offset, + lldb_private::StreamString &all_image_infos_payload); + + /// 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<std::tuple<lldb_private::ConstString, lldb::addr_t>> + segment_load_addresses; + }; + + struct MachOCorefileAllImageInfos { + std::vector<MachOCorefileImageEntry> 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(); Index: lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp =================================================================== --- lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -21,6 +21,7 @@ #include "lldb/Core/StreamFile.h" #include "lldb/Host/Host.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" @@ -6153,8 +6154,17 @@ return num_loaded_sections > 0; } +// 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, bool full_corefile, + Status &error) { if (!process_sp) return false; @@ -6191,14 +6201,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<page_object> 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) @@ -6208,40 +6214,78 @@ 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(segment_command_64); - if (addr_byte_size == 4) { - cmd_type = LC_SEGMENT; - segment_size = sizeof(segment_command); + addr_t pagesize = range_info.GetPageSize(); + llvm::Optional<std::vector<addr_t>> dirty_page_list = + range_info.GetDirtyPageList(); + if (full_corefile == false && dirty_page_list.hasValue()) { + 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); } - 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( range_info.GetRange().GetRangeEnd(), range_info); if (range_error.Fail()) break; } + // Combine contiguous entries that have the same + // protections so we don't have an excess of + // load commands. + std::vector<page_object> 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(segment_command_64); + if (addr_byte_size == 4) { + cmd_type = LC_SEGMENT; + segment_size = sizeof(segment_command); + } + 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); mach_header_64 mach_header; @@ -6312,6 +6356,10 @@ mach_header.sizeofcmds += 8 + LC_THREAD_data.GetSize(); } + // LC_NOTE "all image infos" + mach_header.ncmds++; + mach_header.sizeofcmds += sizeof(struct note_command); + // Write the mach header buffer.PutHex32(mach_header.magic); buffer.PutHex32(mach_header.cputype); @@ -6327,10 +6375,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 + struct note_command all_image_info_note = { + LC_NOTE, /* uint32_t cmd */ + sizeof(struct 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; @@ -6347,14 +6418,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)); @@ -6389,6 +6452,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) { @@ -6442,3 +6519,349 @@ } return false; } + +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. + +offset_t ObjectFileMachO::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<std::string> 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<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; + 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<struct image_entry> 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; +} + +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; + 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<ConstString, addr_t> 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; +} Index: lldb/source/Core/PluginManager.cpp =================================================================== --- lldb/source/Core/PluginManager.cpp +++ lldb/source/Core/PluginManager.cpp @@ -683,11 +683,12 @@ } Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp, - const FileSpec &outfile) { + const FileSpec &outfile, bool full_corefile) { 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, full_corefile, error)) return error; } error.SetErrorString( Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -699,6 +699,12 @@ Desc<"Show verbose process status including extended crash information.">; } +let Command = "process save_core" in { + def process_save_core_force_full : Option<"force-full", "f">, Group<1>, + Desc<"Force a full corefile save, on systems where the default is to save " + "only modified memory.">; +} + 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. " Index: lldb/source/Commands/CommandObjectProcess.cpp =================================================================== --- lldb/source/Commands/CommandObjectProcess.cpp +++ lldb/source/Commands/CommandObjectProcess.cpp @@ -1168,6 +1168,8 @@ // CommandObjectProcessSaveCore #pragma mark CommandObjectProcessSaveCore +#define LLDB_OPTIONS_process_save_core +#include "CommandOptions.inc" class CommandObjectProcessSaveCore : public CommandObjectParsed { public: @@ -1181,13 +1183,49 @@ ~CommandObjectProcessSaveCore() override = default; + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_full_corefile(false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + m_full_corefile = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return {}; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_full_corefile = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_save_core_options); + } + + // Instance variables to hold the values for command options. + bool m_full_corefile; + }; + 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); + Status error = PluginManager::SaveCore(process_sp, output_file, + m_options.m_full_corefile); if (error.Success()) { result.SetStatus(eReturnStatusSuccessFinishResult); } else { @@ -1208,6 +1246,8 @@ return result.Succeeded(); } + + CommandOptions m_options; }; // CommandObjectProcessStatus Index: lldb/source/API/SBProcess.cpp =================================================================== --- lldb/source/API/SBProcess.cpp +++ lldb/source/API/SBProcess.cpp @@ -1248,7 +1248,8 @@ } FileSpec core_file(file_name); - error.ref() = PluginManager::SaveCore(process_sp, core_file); + const bool full_corefile = false; + error.ref() = PluginManager::SaveCore(process_sp, core_file, full_corefile); return LLDB_RECORD_RESULT(error); } Index: lldb/include/lldb/lldb-private-interfaces.h =================================================================== --- lldb/include/lldb/lldb-private-interfaces.h +++ lldb/include/lldb/lldb-private-interfaces.h @@ -53,7 +53,8 @@ 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, bool full_corefile, + Status &error); typedef EmulateInstruction *(*EmulateInstructionCreateInstance)( const ArchSpec &arch, InstructionType inst_type); typedef OperatingSystem *(*OperatingSystemCreateInstance)(Process *process, Index: lldb/include/lldb/Target/MemoryRegionInfo.h =================================================================== --- lldb/include/lldb/Target/MemoryRegionInfo.h +++ lldb/include/lldb/Target/MemoryRegionInfo.h @@ -10,8 +10,11 @@ #ifndef LLDB_TARGET_MEMORYREGIONINFO_H #define LLDB_TARGET_MEMORYREGIONINFO_H +#include <vector> + #include "lldb/Utility/ConstString.h" #include "lldb/Utility/RangeMap.h" +#include "llvm/ADT/Optional.h" #include "llvm/Support/FormatProviders.h" namespace lldb_private { @@ -33,7 +36,14 @@ void Clear() { m_range.Clear(); - m_read = m_write = m_execute = eDontKnow; + m_read = m_write = m_execute = m_mapped = m_flash = eDontKnow; + m_name.Clear(); + m_blocksize = 0; + m_pagesize = 0; + if (m_dirty_pages.hasValue()) { + m_dirty_pages.getValue().clear(); + } + m_dirty_pages.reset(); } const RangeType &GetRange() const { return m_range; } @@ -96,6 +106,27 @@ 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<std::vector<lldb::addr_t>> GetDirtyPageList() { + return m_dirty_pages; + } + + void SetPageSize(int pagesize) { m_pagesize = pagesize; } + + void SetDirtyPageList(std::vector<lldb::addr_t> pagelist) { + if (m_dirty_pages.hasValue()) + m_dirty_pages.getValue().clear(); + m_dirty_pages = pagelist; + } + protected: RangeType m_range; OptionalBool m_read = eDontKnow; @@ -105,6 +136,8 @@ ConstString m_name; OptionalBool m_flash = eDontKnow; lldb::offset_t m_blocksize = 0; + int m_pagesize = 0; + llvm::Optional<std::vector<lldb::addr_t>> m_dirty_pages; }; inline bool operator<(const MemoryRegionInfo &lhs, Index: lldb/include/lldb/Symbol/ObjectFile.h =================================================================== --- lldb/include/lldb/Symbol/ObjectFile.h +++ lldb/include/lldb/Symbol/ObjectFile.h @@ -666,6 +666,22 @@ /// Creates a plugin-specific call frame info virtual std::unique_ptr<CallFrameInfo> 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] target + /// Target 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; Index: lldb/include/lldb/Core/PluginManager.h =================================================================== --- lldb/include/lldb/Core/PluginManager.h +++ lldb/include/lldb/Core/PluginManager.h @@ -191,7 +191,7 @@ GetObjectFileCreateMemoryCallbackForPluginName(ConstString name); static Status SaveCore(const lldb::ProcessSP &process_sp, - const FileSpec &outfile); + const FileSpec &outfile, bool full_corefile); // ObjectContainer static bool Index: lldb/docs/lldb-gdb-remote.txt =================================================================== --- lldb/docs/lldb-gdb-remote.txt +++ lldb/docs/lldb-gdb-remote.txt @@ -790,10 +790,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. @@ -1090,6 +1094,18 @@ // a hex encoded string value that // contains an error string + dirty-pages:[<hexaddr>][,<hexaddr]; // A list of memory pages within this + // region that are "dirty" -- they have been modified. + // Page addresses are in base16. The size of a page can + // be found from the qHostInfo's page-size key-value. + // + // If the stub supports identifying dirty pages within a + // memory region, this key should always be present for all + // qMemoryRegionInfo replies. This key with no pages + // listed ("dirty-pages:;") indicates no dirty pages in + // this memory region. The *absence* of this key means + // that this stub cannot determine dirty pages. + If the address requested is not in a mapped region (e.g. we've jumped through a NULL pointer and are at 0x0) currently lldb expects to get back the size of the unmapped region -- that is, the distance to the next valid region.
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits