jasonmolenda created this revision.
jasonmolenda added a project: LLDB.
Herald added a subscriber: JDevlieghere.
jasonmolenda requested review of this revision.
After adding the "dirty memory only" coredump style, I've been wondering how
small a corefile I could get by only saving stack memory. I took a little time
today to find out. A normal macOS corefile is a couple of GB, a dirty-memory
corefile is around 440kb for a test binary. A stack memory corefile is about
20kb for that test binary.
I added a new "type" key-value pair to the qMemoryRegionInfo packet, and add
"stack" to stack memory regions. David added a "flags" k-v pair for MTE but
that has a specially formatted string of MTE flags in it (space separated) so I
didn't want to mix in with that. It's straightforward to detect different
memory types on Darwin systems, but I haven't thought of anything useful I
could do by writing out heap dirty memory only or something. Maybe someone
will think of some interesting way to use the memory region types.
In the test case, I create a stack-memory-only corefile, then read it back into
lldb and fetch some stack & heap objects. SBValue has the unfortunate behavior
(IMO) that it doesn't surface memory read errors in any way - so you can ask
for a summary or a value of an SBValue object that couldn't read anything, and
it will return 0/empty-string. We need to surface memory read errors better in
lldb in these cases, this is just one of the issues with that.
I'm not sure a stack-memory-only corefile is especially useful for real
C++/ObjC/Swift programs where many objects live on the heap. One thing I've
thought about is trying to get a very tiny corefile showing interesting unwind
states for writing tests against.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D107625
Files:
lldb/docs/lldb-gdb-remote.txt
lldb/include/lldb/Target/MemoryRegionInfo.h
lldb/include/lldb/lldb-enumerations.h
lldb/source/Commands/CommandObjectProcess.cpp
lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
lldb/test/API/macosx/stack-corefile/Makefile
lldb/test/API/macosx/stack-corefile/TestStackCorefile.py
lldb/test/API/macosx/stack-corefile/main.c
lldb/tools/debugserver/source/DNBDefs.h
lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
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
@@ -4310,6 +4310,8 @@
}
}
ostrm << ";";
+ if (region_info.is_stack)
+ ostrm << "type:stack;";
}
return SendPacket(ostrm.str());
}
Index: lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
===================================================================
--- lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
+++ lldb/tools/debugserver/source/MacOSX/MachVMRegion.h
@@ -40,6 +40,7 @@
vm_prot_t prot);
bool RestoreProtections();
bool GetRegionForAddress(nub_addr_t addr);
+ bool GetIsStackMemory() const;
uint32_t GetDNBPermissions() const;
Index: lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
===================================================================
--- lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
+++ lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp
@@ -182,3 +182,11 @@
dnb_permissions |= eMemoryPermissionsExecutable;
return dnb_permissions;
}
+
+bool MachVMRegion::GetIsStackMemory() const {
+ // VM_MEMORY_STACK and VM_PROT_NONE is the STACK GUARD region.
+ if (m_data.user_tag == VM_MEMORY_STACK && m_data.protection != VM_PROT_NONE)
+ return true;
+ else
+ return false;
+}
Index: lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
===================================================================
--- lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
+++ lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -125,6 +125,8 @@
region_info->permissions = vmRegion.GetDNBPermissions();
region_info->dirty_pages =
get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize());
+ if (vmRegion.GetIsStackMemory())
+ region_info->is_stack = true;
} 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
@@ -318,11 +318,13 @@
struct DNBRegionInfo {
public:
- DNBRegionInfo() : addr(0), size(0), permissions(0), dirty_pages() {}
+ DNBRegionInfo()
+ : addr(0), size(0), permissions(0), dirty_pages(), is_stack(false) {}
nub_addr_t addr;
nub_addr_t size;
uint32_t permissions;
std::vector<nub_addr_t> dirty_pages;
+ bool is_stack;
};
enum DNBProfileDataScanType {
Index: lldb/test/API/macosx/stack-corefile/main.c
===================================================================
--- /dev/null
+++ lldb/test/API/macosx/stack-corefile/main.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+int main() {
+ int stack_int = 5;
+ int *heap_int = (int*) malloc(sizeof (int));
+ *heap_int = 10;
+
+ char stack_str[80];
+ strcpy (stack_str, "stack string");
+ char *heap_str = (char*) malloc(80);
+ strcpy (heap_str, "heap string");
+
+ return stack_int; // break here;
+}
Index: lldb/test/API/macosx/stack-corefile/TestStackCorefile.py
===================================================================
--- /dev/null
+++ lldb/test/API/macosx/stack-corefile/TestStackCorefile.py
@@ -0,0 +1,69 @@
+"""Test that lldb can create a stack-only corefile, and load the main binary."""
+
+import os
+import re
+import subprocess
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestStackCorefile(TestBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ @no_debug_info_test
+ @skipUnlessDarwin
+ def test(self):
+
+ corefile = self.getBuildArtifact("process.core")
+ self.build()
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "// break here", lldb.SBFileSpec("main.c"))
+
+ frame = thread.GetFrameAtIndex(0)
+ stack_int = frame.GetValueForVariablePath("stack_int")
+ heap_int = frame.GetValueForVariablePath("*heap_int")
+ stack_str = frame.GetValueForVariablePath("stack_str")
+ heap_str = frame.GetValueForVariablePath("heap_str")
+ self.assertEqual(stack_int.GetValueAsUnsigned(), 5)
+ self.assertEqual(heap_int.GetValueAsUnsigned(), 10)
+ self.assertEqual(stack_str.summary, '"stack string"')
+ self.assertEqual(heap_str.summary, '"heap string"')
+
+ self.runCmd("process save-core -s stack " + corefile)
+ process.Kill()
+ self.dbg.DeleteTarget(target)
+
+ # Now load the corefile
+ target = self.dbg.CreateTarget('')
+ process = target.LoadCore(corefile)
+ thread = process.GetSelectedThread()
+ self.assertTrue(process.IsValid())
+ self.assertTrue(process.GetSelectedThread().IsValid())
+ if self.TraceOn():
+ self.runCmd("image list")
+ self.runCmd("bt")
+ self.runCmd("fr v")
+ num_modules = target.GetNumModules()
+ # We should only have the a.out binary and possibly
+ # the libdyld.dylib. Extra libraries loaded means
+ # extra LC_NOTE and unnecessarily large corefile.
+ self.assertTrue(num_modules == 1 or num_modules == 2)
+
+ # The heap variables should be unavailable now.
+ frame = thread.GetFrameAtIndex(0)
+ stack_int = frame.GetValueForVariablePath("stack_int")
+ heap_int = frame.GetValueForVariablePath("*heap_int")
+ stack_str = frame.GetValueForVariablePath("stack_str")
+ heap_str = frame.GetValueForVariablePath("heap_str")
+
+ ## The heap SBValues both come back as IsValid()==true,
+ ## which I'm not so sure is a great/correct thing --
+ ## it hides the memory read error that actually happened,
+ ## and we don't have a real value.
+ self.assertEqual(stack_int.GetValueAsUnsigned(), 5)
+ self.assertEqual(heap_int.GetValueAsUnsigned(), 0)
+ self.assertEqual(stack_str.summary, '"stack string"')
+ self.assertEqual(heap_str.summary, '""')
Index: lldb/test/API/macosx/stack-corefile/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/macosx/stack-corefile/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES = main.c
+
+include Makefile.rules
Index: lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
===================================================================
--- lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
+++ lldb/test/API/macosx/skinny-corefile/TestSkinnyCorefile.py
@@ -12,7 +12,7 @@
from lldbsuite.test import lldbutil
-class TestFirmwareCorefiles(TestBase):
+class TestSkinnyCorefile(TestBase):
mydir = TestBase.compute_mydir(__file__)
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
@@ -1572,6 +1572,18 @@
}
}
}
+ } else if (name.equals("type")) {
+ std::string comma_sep_str = value.str();
+ size_t comma_pos;
+ while ((comma_pos = comma_sep_str.find(',')) != std::string::npos) {
+ comma_sep_str[comma_pos] = '\0';
+ if (comma_sep_str == "stack") {
+ region_info.SetIsStackMemory(MemoryRegionInfo::eYes);
+ }
+ }
+ if (comma_sep_str == "stack") {
+ region_info.SetIsStackMemory(MemoryRegionInfo::eYes);
+ }
} else if (name.equals("error")) {
StringExtractorGDBRemote error_extractor(value);
std::string error_string;
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
@@ -6325,13 +6325,17 @@
// 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) {
+static offset_t CreateAllImageInfosPayload(
+ const lldb::ProcessSP &process_sp, offset_t initial_file_offset,
+ StreamString &all_image_infos_payload, SaveCoreStyle core_style) {
Target &target = process_sp->GetTarget();
- const ModuleList &modules = target.GetImages();
- size_t modules_count = modules.GetSize();
+ ModuleList modules = target.GetImages();
+
+ // stack-only corefiles have no reason to include binaries that
+ // are not executing; we're trying to make the smallest corefile
+ // we can, so leave the rest out.
+ if (core_style == SaveCoreStyle::eSaveCoreStackOnly)
+ modules.Clear();
std::set<std::string> executing_uuids;
ThreadList &thread_list(process_sp->GetThreadList());
@@ -6346,10 +6350,12 @@
UUID uuid = module_sp->GetUUID();
if (uuid.IsValid()) {
executing_uuids.insert(uuid.GetAsString());
+ modules.AppendIfNeeded(module_sp);
}
}
}
}
+ size_t modules_count = modules.GetSize();
struct all_image_infos_header infos;
infos.version = 1;
@@ -6491,12 +6497,9 @@
if (!process_sp)
return false;
- // For Mach-O, we can only create full corefiles or dirty-page-only
- // corefiles. The default is dirty-page-only.
- if (core_style != SaveCoreStyle::eSaveCoreFull) {
+ // Default on macOS is to create a dirty-memory-only corefile.
+ if (core_style == SaveCoreStyle::eSaveCoreUnspecified) {
core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
- } else {
- core_style = SaveCoreStyle::eSaveCoreFull;
}
Target &target = process_sp->GetTarget();
@@ -6535,6 +6538,12 @@
std::vector<page_object> pages_to_copy;
if (range_error.Success()) {
+ bool save_all_memory_types = true;
+ SaveCoreStyle original_core_style = core_style;
+ if (core_style == SaveCoreStyle::eSaveCoreStackOnly) {
+ core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
+ save_all_memory_types = false;
+ }
while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) {
// Calculate correct protections
uint32_t prot = 0;
@@ -6556,8 +6565,9 @@
const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
if (core_style == SaveCoreStyle::eSaveCoreDirtyOnly &&
- dirty_page_list.hasValue()) {
- core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
+ dirty_page_list.hasValue() &&
+ (save_all_memory_types ||
+ range_info.IsStackMemory() == MemoryRegionInfo::eYes)) {
for (addr_t dirtypage : dirty_page_list.getValue()) {
page_object obj;
obj.addr = dirtypage;
@@ -6566,11 +6576,14 @@
pages_to_copy.push_back(obj);
}
} else {
- page_object obj;
- obj.addr = addr;
- obj.size = size;
- obj.prot = prot;
- pages_to_copy.push_back(obj);
+ if (save_all_memory_types ||
+ range_info.IsStackMemory() == MemoryRegionInfo::eYes) {
+ page_object obj;
+ obj.addr = addr;
+ obj.size = size;
+ obj.prot = prot;
+ pages_to_copy.push_back(obj);
+ }
}
}
@@ -6579,6 +6592,7 @@
if (range_error.Fail())
break;
}
+ core_style = original_core_style;
// Combine contiguous entries that have the same
// protections so we don't have an excess of
@@ -6600,6 +6614,12 @@
last_obj = obj;
}
+ // If we only ended up with one contiguous memory segment
+ if (combined_page_objects.size() == 0 &&
+ last_obj.addr != LLDB_INVALID_ADDRESS) {
+ combined_page_objects.push_back(last_obj);
+ }
+
for (page_object obj : combined_page_objects) {
uint32_t cmd_type = LC_SEGMENT_64;
uint32_t segment_size = sizeof(llvm::MachO::segment_command_64);
@@ -6750,7 +6770,8 @@
all_image_infos_lcnote_up->name = "all image infos";
all_image_infos_lcnote_up->payload_file_offset = file_offset;
file_offset = CreateAllImageInfosPayload(
- process_sp, file_offset, all_image_infos_lcnote_up->payload);
+ process_sp, file_offset, all_image_infos_lcnote_up->payload,
+ core_style);
lc_notes.push_back(std::move(all_image_infos_lcnote_up));
// Add LC_NOTE load commands
Index: lldb/source/Commands/CommandObjectProcess.cpp
===================================================================
--- lldb/source/Commands/CommandObjectProcess.cpp
+++ lldb/source/Commands/CommandObjectProcess.cpp
@@ -1166,7 +1166,9 @@
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"}};
+ "Create a corefile with only modified memory saved"},
+ {eSaveCoreStackOnly, "stack",
+ "Create a corefile with only stack memory saved"}};
static constexpr OptionEnumValues SaveCoreStyles() {
return OptionEnumValues(g_corefile_save_style);
@@ -1237,11 +1239,12 @@
Status error =
PluginManager::SaveCore(process_sp, output_file, corefile_style);
if (error.Success()) {
- if (corefile_style == SaveCoreStyle::eSaveCoreDirtyOnly) {
+ if (corefile_style == SaveCoreStyle::eSaveCoreDirtyOnly ||
+ corefile_style == SaveCoreStyle::eSaveCoreStackOnly) {
result.AppendMessageWithFormat(
- "\nModified-memory only corefile "
- "created. This corefile may not show \n"
- "library/framework/app binaries "
+ "\nModified-memory or stack-memory only corefile "
+ "created. This corefile may \n"
+ "not show library/framework/app binaries "
"on a different system, or when \n"
"those binaries have "
"been updated/modified. Copies are not included\n"
Index: lldb/include/lldb/lldb-enumerations.h
===================================================================
--- lldb/include/lldb/lldb-enumerations.h
+++ lldb/include/lldb/lldb-enumerations.h
@@ -1137,6 +1137,7 @@
eSaveCoreUnspecified = 0,
eSaveCoreFull = 1,
eSaveCoreDirtyOnly = 2,
+ eSaveCoreStackOnly = 3,
};
} // namespace lldb
Index: lldb/include/lldb/Target/MemoryRegionInfo.h
===================================================================
--- lldb/include/lldb/Target/MemoryRegionInfo.h
+++ lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -28,10 +28,10 @@
MemoryRegionInfo(RangeType range, OptionalBool read, OptionalBool write,
OptionalBool execute, OptionalBool mapped, ConstString name,
OptionalBool flash, lldb::offset_t blocksize,
- OptionalBool memory_tagged)
+ OptionalBool memory_tagged, OptionalBool stack_memory)
: m_range(range), m_read(read), m_write(write), m_execute(execute),
m_mapped(mapped), m_name(name), m_flash(flash), m_blocksize(blocksize),
- m_memory_tagged(memory_tagged) {}
+ m_memory_tagged(memory_tagged), m_is_stack_memory(stack_memory) {}
RangeType &GetRange() { return m_range; }
@@ -98,7 +98,8 @@
m_mapped == rhs.m_mapped && m_name == rhs.m_name &&
m_flash == rhs.m_flash && m_blocksize == rhs.m_blocksize &&
m_memory_tagged == rhs.m_memory_tagged &&
- m_pagesize == rhs.m_pagesize;
+ m_pagesize == rhs.m_pagesize &&
+ m_is_stack_memory == rhs.m_is_stack_memory;
}
bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); }
@@ -116,6 +117,10 @@
return m_dirty_pages;
}
+ OptionalBool IsStackMemory() const { return m_is_stack_memory; }
+
+ void SetIsStackMemory(OptionalBool val) { m_is_stack_memory = val; }
+
void SetPageSize(int pagesize) { m_pagesize = pagesize; }
void SetDirtyPageList(std::vector<lldb::addr_t> pagelist) {
@@ -134,6 +139,7 @@
OptionalBool m_flash = eDontKnow;
lldb::offset_t m_blocksize = 0;
OptionalBool m_memory_tagged = eDontKnow;
+ OptionalBool m_is_stack_memory = eDontKnow;
int m_pagesize = 0;
llvm::Optional<std::vector<lldb::addr_t>> m_dirty_pages;
};
Index: lldb/docs/lldb-gdb-remote.txt
===================================================================
--- lldb/docs/lldb-gdb-remote.txt
+++ lldb/docs/lldb-gdb-remote.txt
@@ -1206,6 +1206,9 @@
// is "mt" for AArch64 memory tagging. lldb will
// ignore any other flags in this field.
+ type:[<type>][,<type>]; // memory types that apply to this region, e.g.
+ // "stack" for stack memory.
+
error:<ascii-byte-error-string>; // where <ascii-byte-error-string> is
// a hex encoded string value that
// contains an error string
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits