Skip to content

Commit 47d80ec

Browse files
authored
[LLDB/Coredump] Only take the Pthread from stack start to the stackpointer + red_zone (llvm#92002)
Currently in Core dumps, the entire pthread is copied, including the unused space beyond the stack pointer. This causes large amounts of core dump inflation when the number of threads is high, but the stack usage is low. Such as when an application is using a thread pool. This change will optimize for these situations in addition to generally improving the core dump performance for all of lldb.
1 parent 6d2219a commit 47d80ec

File tree

3 files changed

+131
-55
lines changed

3 files changed

+131
-55
lines changed

lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "lldb/Core/Module.h"
1515
#include "lldb/Core/ModuleList.h"
1616
#include "lldb/Core/Section.h"
17+
#include "lldb/Target/ABI.h"
1718
#include "lldb/Target/MemoryRegionInfo.h"
1819
#include "lldb/Target/Process.h"
1920
#include "lldb/Target/RegisterContext.h"
@@ -491,6 +492,17 @@ findStackHelper(const lldb::ProcessSP &process_sp, uint64_t rsp) {
491492
std::errc::not_supported,
492493
"unable to load stack segment of the process");
493494

495+
// This is a duplicate of the logic in
496+
// Process::SaveOffRegionsWithStackPointers but ultimately, we need to only
497+
// save up from the start of the stack down to the stack pointer
498+
const addr_t range_end = range_info.GetRange().GetRangeEnd();
499+
const addr_t red_zone = process_sp->GetABI()->GetRedZoneSize();
500+
const addr_t stack_head = rsp - red_zone;
501+
if (stack_head > range_info.GetRange().GetRangeEnd()) {
502+
range_info.GetRange().SetRangeBase(stack_head);
503+
range_info.GetRange().SetByteSize(range_end - stack_head);
504+
}
505+
494506
const addr_t addr = range_info.GetRange().GetRangeBase();
495507
const addr_t size = range_info.GetRange().GetByteSize();
496508

lldb/source/Target/Process.cpp

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3857,8 +3857,8 @@ thread_result_t Process::RunPrivateStateThread(bool is_secondary_thread) {
38573857
// case we should tell it to stop doing that. Normally, we don't NEED
38583858
// to do that because we will next close the communication to the stub
38593859
// and that will get it to shut down. But there are remote debugging
3860-
// cases where relying on that side-effect causes the shutdown to be
3861-
// flakey, so we should send a positive signal to interrupt the wait.
3860+
// cases where relying on that side-effect causes the shutdown to be
3861+
// flakey, so we should send a positive signal to interrupt the wait.
38623862
Status error = HaltPrivate();
38633863
BroadcastEvent(eBroadcastBitInterrupt, nullptr);
38643864
} else if (StateIsRunningState(m_last_broadcast_state)) {
@@ -6335,30 +6335,65 @@ static void AddRegion(const MemoryRegionInfo &region, bool try_dirty_pages,
63356335
ranges.push_back(CreateCoreFileMemoryRange(region));
63366336
}
63376337

6338+
static void SaveOffRegionsWithStackPointers(
6339+
Process &process, const MemoryRegionInfos &regions,
6340+
Process::CoreFileMemoryRanges &ranges, std::set<addr_t> &stack_ends) {
6341+
const bool try_dirty_pages = true;
6342+
6343+
// Before we take any dump, we want to save off the used portions of the
6344+
// stacks and mark those memory regions as saved. This prevents us from saving
6345+
// the unused portion of the stack below the stack pointer. Saving space on
6346+
// the dump.
6347+
for (lldb::ThreadSP thread_sp : process.GetThreadList().Threads()) {
6348+
if (!thread_sp)
6349+
continue;
6350+
StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(0);
6351+
if (!frame_sp)
6352+
continue;
6353+
RegisterContextSP reg_ctx_sp = frame_sp->GetRegisterContext();
6354+
if (!reg_ctx_sp)
6355+
continue;
6356+
const addr_t sp = reg_ctx_sp->GetSP();
6357+
const size_t red_zone = process.GetABI()->GetRedZoneSize();
6358+
lldb_private::MemoryRegionInfo sp_region;
6359+
if (process.GetMemoryRegionInfo(sp, sp_region).Success()) {
6360+
const size_t stack_head = (sp - red_zone);
6361+
const size_t stack_size = sp_region.GetRange().GetRangeEnd() - stack_head;
6362+
sp_region.GetRange().SetRangeBase(stack_head);
6363+
sp_region.GetRange().SetByteSize(stack_size);
6364+
stack_ends.insert(sp_region.GetRange().GetRangeEnd());
6365+
AddRegion(sp_region, try_dirty_pages, ranges);
6366+
}
6367+
}
6368+
}
6369+
63386370
// Save all memory regions that are not empty or have at least some permissions
63396371
// for a full core file style.
63406372
static void GetCoreFileSaveRangesFull(Process &process,
63416373
const MemoryRegionInfos &regions,
6342-
Process::CoreFileMemoryRanges &ranges) {
6374+
Process::CoreFileMemoryRanges &ranges,
6375+
std::set<addr_t> &stack_ends) {
63436376

63446377
// Don't add only dirty pages, add full regions.
63456378
const bool try_dirty_pages = false;
63466379
for (const auto &region : regions)
6347-
AddRegion(region, try_dirty_pages, ranges);
6380+
if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0)
6381+
AddRegion(region, try_dirty_pages, ranges);
63486382
}
63496383

63506384
// Save only the dirty pages to the core file. Make sure the process has at
63516385
// least some dirty pages, as some OS versions don't support reporting what
63526386
// pages are dirty within an memory region. If no memory regions have dirty
63536387
// page information fall back to saving out all ranges with write permissions.
6354-
static void
6355-
GetCoreFileSaveRangesDirtyOnly(Process &process,
6356-
const MemoryRegionInfos &regions,
6357-
Process::CoreFileMemoryRanges &ranges) {
6388+
static void GetCoreFileSaveRangesDirtyOnly(
6389+
Process &process, const MemoryRegionInfos &regions,
6390+
Process::CoreFileMemoryRanges &ranges, std::set<addr_t> &stack_ends) {
6391+
63586392
// Iterate over the regions and find all dirty pages.
63596393
bool have_dirty_page_info = false;
63606394
for (const auto &region : regions) {
6361-
if (AddDirtyPages(region, ranges))
6395+
if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0 &&
6396+
AddDirtyPages(region, ranges))
63626397
have_dirty_page_info = true;
63636398
}
63646399

@@ -6367,7 +6402,8 @@ GetCoreFileSaveRangesDirtyOnly(Process &process,
63676402
// plug-in so fall back to any region with write access permissions.
63686403
const bool try_dirty_pages = false;
63696404
for (const auto &region : regions)
6370-
if (region.GetWritable() == MemoryRegionInfo::eYes)
6405+
if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0 &&
6406+
region.GetWritable() == MemoryRegionInfo::eYes)
63716407
AddRegion(region, try_dirty_pages, ranges);
63726408
}
63736409
}
@@ -6380,43 +6416,18 @@ GetCoreFileSaveRangesDirtyOnly(Process &process,
63806416
// dirty regions as this will make the core file smaller. If the process
63816417
// doesn't support dirty regions, then it will fall back to adding the full
63826418
// stack region.
6383-
static void
6384-
GetCoreFileSaveRangesStackOnly(Process &process,
6385-
const MemoryRegionInfos &regions,
6386-
Process::CoreFileMemoryRanges &ranges) {
6419+
static void GetCoreFileSaveRangesStackOnly(
6420+
Process &process, const MemoryRegionInfos &regions,
6421+
Process::CoreFileMemoryRanges &ranges, std::set<addr_t> &stack_ends) {
6422+
const bool try_dirty_pages = true;
63876423
// Some platforms support annotating the region information that tell us that
63886424
// it comes from a thread stack. So look for those regions first.
63896425

6390-
// Keep track of which stack regions we have added
6391-
std::set<addr_t> stack_bases;
6392-
6393-
const bool try_dirty_pages = true;
63946426
for (const auto &region : regions) {
6395-
if (region.IsStackMemory() == MemoryRegionInfo::eYes) {
6396-
stack_bases.insert(region.GetRange().GetRangeBase());
6427+
// Save all the stack memory ranges not associated with a stack pointer.
6428+
if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0 &&
6429+
region.IsStackMemory() == MemoryRegionInfo::eYes)
63976430
AddRegion(region, try_dirty_pages, ranges);
6398-
}
6399-
}
6400-
6401-
// Also check with our threads and get the regions for their stack pointers
6402-
// and add those regions if not already added above.
6403-
for (lldb::ThreadSP thread_sp : process.GetThreadList().Threads()) {
6404-
if (!thread_sp)
6405-
continue;
6406-
StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(0);
6407-
if (!frame_sp)
6408-
continue;
6409-
RegisterContextSP reg_ctx_sp = frame_sp->GetRegisterContext();
6410-
if (!reg_ctx_sp)
6411-
continue;
6412-
const addr_t sp = reg_ctx_sp->GetSP();
6413-
lldb_private::MemoryRegionInfo sp_region;
6414-
if (process.GetMemoryRegionInfo(sp, sp_region).Success()) {
6415-
// Only add this region if not already added above. If our stack pointer
6416-
// is pointing off in the weeds, we will want this range.
6417-
if (stack_bases.count(sp_region.GetRange().GetRangeBase()) == 0)
6418-
AddRegion(sp_region, try_dirty_pages, ranges);
6419-
}
64206431
}
64216432
}
64226433

@@ -6428,23 +6439,27 @@ Status Process::CalculateCoreFileSaveRanges(lldb::SaveCoreStyle core_style,
64286439
return err;
64296440
if (regions.empty())
64306441
return Status("failed to get any valid memory regions from the process");
6442+
if (core_style == eSaveCoreUnspecified)
6443+
return Status("callers must set the core_style to something other than "
6444+
"eSaveCoreUnspecified");
6445+
6446+
std::set<addr_t> stack_ends;
6447+
SaveOffRegionsWithStackPointers(*this, regions, ranges, stack_ends);
64316448

64326449
switch (core_style) {
64336450
case eSaveCoreUnspecified:
6434-
err = Status("callers must set the core_style to something other than "
6435-
"eSaveCoreUnspecified");
64366451
break;
64376452

64386453
case eSaveCoreFull:
6439-
GetCoreFileSaveRangesFull(*this, regions, ranges);
6454+
GetCoreFileSaveRangesFull(*this, regions, ranges, stack_ends);
64406455
break;
64416456

64426457
case eSaveCoreDirtyOnly:
6443-
GetCoreFileSaveRangesDirtyOnly(*this, regions, ranges);
6458+
GetCoreFileSaveRangesDirtyOnly(*this, regions, ranges, stack_ends);
64446459
break;
64456460

64466461
case eSaveCoreStackOnly:
6447-
GetCoreFileSaveRangesStackOnly(*this, regions, ranges);
6462+
GetCoreFileSaveRangesStackOnly(*this, regions, ranges, stack_ends);
64486463
break;
64496464
}
64506465

lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test saving a mini dump.
33
"""
44

5-
65
import os
76
import lldb
87
from lldbsuite.test.decorators import *
@@ -12,7 +11,12 @@
1211

1312
class ProcessSaveCoreMinidumpTestCase(TestBase):
1413
def verify_core_file(
15-
self, core_path, expected_pid, expected_modules, expected_threads
14+
self,
15+
core_path,
16+
expected_pid,
17+
expected_modules,
18+
expected_threads,
19+
stacks_to_sps_map,
1620
):
1721
# To verify, we'll launch with the mini dump
1822
target = self.dbg.CreateTarget(None)
@@ -36,17 +40,36 @@ def verify_core_file(
3640
self.assertEqual(module_file_name, expected_file_name)
3741
self.assertEqual(module.GetUUIDString(), expected.GetUUIDString())
3842

43+
red_zone = process.GetTarget().GetStackRedZoneSize()
3944
for thread_idx in range(process.GetNumThreads()):
4045
thread = process.GetThreadAtIndex(thread_idx)
4146
self.assertTrue(thread.IsValid())
4247
thread_id = thread.GetThreadID()
4348
self.assertIn(thread_id, expected_threads)
49+
frame = thread.GetFrameAtIndex(0)
50+
sp_region = lldb.SBMemoryRegionInfo()
51+
sp = frame.GetSP()
52+
err = process.GetMemoryRegionInfo(sp, sp_region)
53+
self.assertTrue(err.Success(), err.GetCString())
54+
error = lldb.SBError()
55+
# Ensure thread_id is in the saved map
56+
self.assertIn(thread_id, stacks_to_sps_map)
57+
# Ensure the SP is correct
58+
self.assertEqual(stacks_to_sps_map[thread_id], sp)
59+
# Try to read at the end of the stack red zone and succeed
60+
process.ReadMemory(sp - red_zone, 1, error)
61+
self.assertTrue(error.Success(), error.GetCString())
62+
# Try to read just past the red zone and fail
63+
process.ReadMemory(sp - red_zone - 1, 1, error)
64+
self.assertTrue(error.Fail(), "No failure when reading past the red zone")
65+
4466
self.dbg.DeleteTarget(target)
4567

4668
@skipUnlessArch("x86_64")
4769
@skipUnlessPlatform(["linux"])
4870
def test_save_linux_mini_dump(self):
4971
"""Test that we can save a Linux mini dump."""
72+
5073
self.build()
5174
exe = self.getBuildArtifact("a.out")
5275
core_stack = self.getBuildArtifact("core.stack.dmp")
@@ -69,45 +92,67 @@ def test_save_linux_mini_dump(self):
6992
expected_modules = target.modules
7093
expected_number_of_threads = process.GetNumThreads()
7194
expected_threads = []
95+
stacks_to_sp_map = {}
7296

7397
for thread_idx in range(process.GetNumThreads()):
7498
thread = process.GetThreadAtIndex(thread_idx)
7599
thread_id = thread.GetThreadID()
76100
expected_threads.append(thread_id)
101+
stacks_to_sp_map[thread_id] = thread.GetFrameAtIndex(0).GetSP()
77102

78103
# save core and, kill process and verify corefile existence
79104
base_command = "process save-core --plugin-name=minidump "
80105
self.runCmd(base_command + " --style=stack '%s'" % (core_stack))
81106
self.assertTrue(os.path.isfile(core_stack))
82107
self.verify_core_file(
83-
core_stack, expected_pid, expected_modules, expected_threads
108+
core_stack,
109+
expected_pid,
110+
expected_modules,
111+
expected_threads,
112+
stacks_to_sp_map,
84113
)
85114

86115
self.runCmd(base_command + " --style=modified-memory '%s'" % (core_dirty))
87116
self.assertTrue(os.path.isfile(core_dirty))
88117
self.verify_core_file(
89-
core_dirty, expected_pid, expected_modules, expected_threads
118+
core_dirty,
119+
expected_pid,
120+
expected_modules,
121+
expected_threads,
122+
stacks_to_sp_map,
90123
)
91124

92125
self.runCmd(base_command + " --style=full '%s'" % (core_full))
93126
self.assertTrue(os.path.isfile(core_full))
94127
self.verify_core_file(
95-
core_full, expected_pid, expected_modules, expected_threads
128+
core_full,
129+
expected_pid,
130+
expected_modules,
131+
expected_threads,
132+
stacks_to_sp_map,
96133
)
97134

98135
# validate saving via SBProcess
99136
error = process.SaveCore(core_sb_stack, "minidump", lldb.eSaveCoreStackOnly)
100137
self.assertTrue(error.Success())
101138
self.assertTrue(os.path.isfile(core_sb_stack))
102139
self.verify_core_file(
103-
core_sb_stack, expected_pid, expected_modules, expected_threads
140+
core_sb_stack,
141+
expected_pid,
142+
expected_modules,
143+
expected_threads,
144+
stacks_to_sp_map,
104145
)
105146

106147
error = process.SaveCore(core_sb_dirty, "minidump", lldb.eSaveCoreDirtyOnly)
107148
self.assertTrue(error.Success())
108149
self.assertTrue(os.path.isfile(core_sb_dirty))
109150
self.verify_core_file(
110-
core_sb_dirty, expected_pid, expected_modules, expected_threads
151+
core_sb_dirty,
152+
expected_pid,
153+
expected_modules,
154+
expected_threads,
155+
stacks_to_sp_map,
111156
)
112157

113158
# Minidump can now save full core files, but they will be huge and
@@ -116,7 +161,11 @@ def test_save_linux_mini_dump(self):
116161
self.assertTrue(error.Success())
117162
self.assertTrue(os.path.isfile(core_sb_full))
118163
self.verify_core_file(
119-
core_sb_full, expected_pid, expected_modules, expected_threads
164+
core_sb_full,
165+
expected_pid,
166+
expected_modules,
167+
expected_threads,
168+
stacks_to_sp_map,
120169
)
121170

122171
self.assertSuccess(process.Kill())

0 commit comments

Comments
 (0)