jasonmolenda updated this revision to Diff 301531. jasonmolenda added a comment.
Update to address previous review comments. (1) Update SBMemoryRegionInfo API to provide access to the dirty page information. (2) Update the 'memory region' command output to print the dirty page list. (3) Update the 'process save-core' command to take a --style option with accepts an enum for the type of corefile. (4) Update 'process save-core' text to explain the limitations of a modified-memory-only corefile. (5) Add a API/functionalities/gdb_remote_client test with a stub that returns the region infos with these new fields, and tests the SBMemoryRegionInfo object filled in on the debugger side. I think that's everything. There's still a hang when running the GDBRemoteCommunicationClientTest.cpp tests (GDBRemoteCommunicationClientTest::GetMemoryRegionInfo and GDBRemoteCommunicationClientTest::GetMemoryRegionInfoInvalidResponse) that I haven't gotten to the bottom of yet, but I thought I'd update the patchset on the phab with where I am now, while I work through this last issue. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D88387/new/ https://reviews.llvm.org/D88387 Files: lldb/bindings/interface/SBMemoryRegionInfo.i lldb/docs/lldb-gdb-remote.txt lldb/include/lldb/API/SBMemoryRegionInfo.h lldb/include/lldb/Core/PluginManager.h lldb/include/lldb/Symbol/ObjectFile.h lldb/include/lldb/Target/MemoryRegionInfo.h lldb/include/lldb/lldb-enumerations.h lldb/include/lldb/lldb-private-interfaces.h lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py lldb/source/API/SBMemoryRegionInfo.cpp lldb/source/API/SBProcess.cpp lldb/source/Commands/CommandObjectMemory.cpp lldb/source/Commands/CommandObjectProcess.cpp lldb/source/Commands/Options.td lldb/source/Core/PluginManager.cpp lldb/source/Interpreter/CommandObject.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/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py 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/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py lldb/tools/debugserver/source/DNBDefs.h lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp lldb/tools/debugserver/source/RNBRemote.cpp lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
Index: lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp =================================================================== --- lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -327,6 +327,10 @@ return client.GetMemoryRegionInfo(addr, region_info); }); + // We need to have issued a qHostInfo packet in the debug session + // before we see a qMemoryRegionInfo packet. In a real debug session, + // lldb would send the qHostInfo if it hadn't been sent yet. + HandlePacket(server, "qHostInfo", "ptrsize:8;endian:little;"); HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;permissions:rx;name:2f666f6f2f6261722e736f;"); @@ -352,6 +356,10 @@ return client.GetMemoryRegionInfo(addr, region_info); }); + // We need to have issued a qHostInfo packet in the debug session + // before we see a qMemoryRegionInfo packet. In a real debug session, + // lldb would send the qHostInfo if it hadn't been sent yet. + HandlePacket(server, "qHostInfo", "ptrsize:8;endian:little;"); HandlePacket(server, "qMemoryRegionInfo:4000", "start:4000;size:0000;"); if (XMLDocument::XMLEnabled()) { // In case we have XML support, this will also do a "qXfer:memory-map". 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/tools/lldb-server/TestGdbRemoteHostInfo.py =================================================================== --- lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py +++ lldb/test/API/tools/lldb-server/TestGdbRemoteHostInfo.py @@ -28,6 +28,7 @@ "ptrsize", "triple", "vendor", + "vm-page-size", "watchpoint_exceptions_received", "default_packet_timeout", ]) 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/test/API/functionalities/gdb_remote_client/gdbclientutils.py =================================================================== --- lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py +++ 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): Index: lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py =================================================================== --- /dev/null +++ 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() + 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,6 +79,8 @@ static bool SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle requested_core_style, + lldb::SaveCoreStyle &created_core_style, 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 @@ -218,7 +218,11 @@ bool ObjectFilePECOFF::SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle requested_core_style, + lldb::SaveCoreStyle &created_core_style, + lldb_private::Status &error) { + created_core_style = SaveCoreStyle::eSaveCoreMiniDump; 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 @@ -58,6 +58,8 @@ static bool SaveCore(const lldb::ProcessSP &process_sp, const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle requested_core_style, + lldb::SaveCoreStyle &created_core_style, lldb_private::Status &error); static bool MagicBytesMatch(lldb::DataBufferSP &data_sp, lldb::addr_t offset, @@ -116,6 +118,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 +213,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<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,11 +6154,259 @@ 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<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; +} + +// 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 requested_core_style, + lldb::SaveCoreStyle &created_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 (requested_core_style != SaveCoreStyle::eSaveCoreFull) { + requested_core_style = SaveCoreStyle::eSaveCoreDirtyOnly; + } else { + created_core_style = SaveCoreStyle::eSaveCoreFull; + } + Target &target = process_sp->GetTarget(); const ArchSpec target_arch = target.GetArchitecture(); const llvm::Triple &target_triple = target_arch.GetTriple(); @@ -6191,14 +6440,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 +6453,80 @@ 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 (requested_core_style == SaveCoreStyle::eSaveCoreDirtyOnly && + dirty_page_list.hasValue()) { + created_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); } - 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 +6597,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 +6616,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 +6659,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 +6693,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) { @@ -6398,9 +6716,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; @@ -6442,3 +6761,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; + 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/Interpreter/CommandObject.cpp =================================================================== --- lldb/source/Interpreter/CommandObject.cpp +++ lldb/source/Interpreter/CommandObject.cpp @@ -1120,7 +1120,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 }; Index: lldb/source/Core/PluginManager.cpp =================================================================== --- lldb/source/Core/PluginManager.cpp +++ lldb/source/Core/PluginManager.cpp @@ -684,11 +684,15 @@ } Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp, - const FileSpec &outfile) { + const FileSpec &outfile, + lldb::SaveCoreStyle requested_core_style, + lldb::SaveCoreStyle &created_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, requested_core_style, + created_core_style, 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_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. " Index: lldb/source/Commands/CommandObjectProcess.cpp =================================================================== --- lldb/source/Commands/CommandObjectProcess.cpp +++ lldb/source/Commands/CommandObjectProcess.cpp @@ -1169,26 +1169,93 @@ // 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"}, + {eSaveCoreMiniDump, "minidump", "Create a Windows Minidump file"}}; + +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<OptionDefinition> 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 output_corefile_style; + Status error = PluginManager::SaveCore( + process_sp, output_file, m_options.m_requested_save_core_style, + output_corefile_style); if (error.Success()) { + if (output_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( @@ -1208,6 +1275,8 @@ return result.Succeeded(); } + + CommandOptions m_options; }; // CommandObjectProcessStatus Index: lldb/source/Commands/CommandObjectMemory.cpp =================================================================== --- lldb/source/Commands/CommandObjectMemory.cpp +++ lldb/source/Commands/CommandObjectMemory.cpp @@ -1739,6 +1739,27 @@ range_info.GetRange().GetRangeEnd(), range_info.GetReadable(), range_info.GetWritable(), range_info.GetExecutable(), name ? " " : "", name, section_name ? " " : "", section_name); + llvm::Optional<std::vector<addr_t>> 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; Index: lldb/source/API/SBProcess.cpp =================================================================== --- lldb/source/API/SBProcess.cpp +++ lldb/source/API/SBProcess.cpp @@ -1248,7 +1248,10 @@ } FileSpec core_file(file_name); - error.ref() = PluginManager::SaveCore(process_sp, core_file); + const SaveCoreStyle requested_core_style = SaveCoreStyle::eSaveCoreFull; + SaveCoreStyle created_core_style; + error.ref() = PluginManager::SaveCore( + process_sp, core_file, requested_core_style, created_core_style); return LLDB_RECORD_RESULT(error); } Index: lldb/source/API/SBMemoryRegionInfo.cpp =================================================================== --- lldb/source/API/SBMemoryRegionInfo.cpp +++ 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<std::vector<addr_t>> 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; + llvm::Optional<std::vector<addr_t>> 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); Index: lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -807,7 +807,8 @@ "size", "permissions", "name", - "error"]) + "error", + "dirty-pages"]) self.assertIsNotNone(val) mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", "")) Index: lldb/include/lldb/lldb-private-interfaces.h =================================================================== --- lldb/include/lldb/lldb-private-interfaces.h +++ lldb/include/lldb/lldb-private-interfaces.h @@ -54,7 +54,10 @@ 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 requested_core_style, + lldb::SaveCoreStyle &created_core_style, + Status &error); typedef EmulateInstruction *(*EmulateInstructionCreateInstance)( const ArchSpec &arch, InstructionType inst_type); typedef OperatingSystem *(*OperatingSystemCreateInstance)(Process *process, Index: lldb/include/lldb/lldb-enumerations.h =================================================================== --- lldb/include/lldb/lldb-enumerations.h +++ lldb/include/lldb/lldb-enumerations.h @@ -597,6 +597,7 @@ eArgTypeCommand, eArgTypeColumnNum, eArgTypeModuleUUID, + eArgTypeSaveCoreStyle, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; @@ -1101,6 +1102,15 @@ /// Stopped because quit was requested. eCommandInterpreterResultQuitRequested, }; + +// Style of core file to create when calling SaveCore. +enum SaveCoreStyle { + eSaveCoreUnspecified = 0, + eSaveCoreFull = 1, + eSaveCoreDirtyOnly = 2, + eSaveCoreMiniDump = 3 +}; + } // namespace lldb #endif // LLDB_LLDB_ENUMERATIONS_H 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] 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; Index: lldb/include/lldb/Core/PluginManager.h =================================================================== --- lldb/include/lldb/Core/PluginManager.h +++ lldb/include/lldb/Core/PluginManager.h @@ -191,7 +191,9 @@ GetObjectFileCreateMemoryCallbackForPluginName(ConstString name); static Status SaveCore(const lldb::ProcessSP &process_sp, - const FileSpec &outfile); + const FileSpec &outfile, + lldb::SaveCoreStyle requested_core_style, + lldb::SaveCoreStyle &created_core_style); // ObjectContainer static bool Index: lldb/include/lldb/API/SBMemoryRegionInfo.h =================================================================== --- lldb/include/lldb/API/SBMemoryRegionInfo.h +++ lldb/include/lldb/API/SBMemoryRegionInfo.h @@ -73,6 +73,40 @@ /// region. If no name can be determined the returns nullptr. const char *GetName(); + /// Returns whether this memory region has a list of memory pages + /// that have been modified -- that are dirty. + /// + /// \return + /// True if the dirty page list is available. + bool HasDirtyMemoryPageList(); + + /// Returns the number of modified pages -- dirty pages -- in this + /// memory region. + /// + /// \return + /// The number of dirty page entries will be returned. If + /// there are no dirty pages in this memory region, 0 will + /// be returned. 0 will also be returned if the dirty page + /// list is not available for this memory region -- you must + /// use HasDirtyMemoryPageList() to check for that. + uint32_t GetNumDirtyPages(); + + /// Returns the address of a memory page that has been modified in + /// this region. + /// + /// \return + /// Returns the address for his dirty page in the list. + /// If this memory region does not have a dirty page list, + /// LLDB_INVALID_ADDRESS is returned. + addr_t GetDirtyPageAddressAtIndex(uint32_t idx); + + /// Returns the size of a memory page in this region. + /// + /// \return + /// Returns the size of the memory pages in this region, + /// or 0 if this information is unavailable. + int GetPageSize(); + bool operator==(const lldb::SBMemoryRegionInfo &rhs) const; bool operator!=(const lldb::SBMemoryRegionInfo &rhs) const; 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. Index: lldb/bindings/interface/SBMemoryRegionInfo.i =================================================================== --- lldb/bindings/interface/SBMemoryRegionInfo.i +++ 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;
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits