Skip to content

[LLDB/Coredump] Only take the Pthread from stack start to the stackpointer + red_zone #92002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 16, 2024

Conversation

Jlalond
Copy link
Contributor

@Jlalond Jlalond commented May 13, 2024

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.

Jlalond added 3 commits May 10, 2024 12:59
…d zone)

Add python tests to verify we can read up to sp + redzone - 1.
…nters instead of trying to take the entire range
…the process to ensure the addresses are saved correctly.
@Jlalond Jlalond requested a review from JDevlieghere as a code owner May 13, 2024 17:20
@Jlalond
Copy link
Contributor Author

Jlalond commented May 13, 2024

@clayborg Could you take a look at this?

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be
notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write
permissions for the repository. In which case you can instead tag reviewers by
name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review
by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate
is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added the lldb label May 13, 2024
@llvmbot
Copy link
Member

llvmbot commented May 13, 2024

@llvm/pr-subscribers-lldb

Author: Jacob Lalonde (Jlalond)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/92002.diff

2 Files Affected:

  • (modified) lldb/source/Target/Process.cpp (+11-3)
  • (modified) lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py (+29-8)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 25afade9a8275..a11e45909202f 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -3857,8 +3857,8 @@ thread_result_t Process::RunPrivateStateThread(bool is_secondary_thread) {
         // case we should tell it to stop doing that.  Normally, we don't NEED
         // to do that because we will next close the communication to the stub
         // and that will get it to shut down.  But there are remote debugging
-        // cases where relying on that side-effect causes the shutdown to be 
-        // flakey, so we should send a positive signal to interrupt the wait. 
+        // cases where relying on that side-effect causes the shutdown to be
+        // flakey, so we should send a positive signal to interrupt the wait.
         Status error = HaltPrivate();
         BroadcastEvent(eBroadcastBitInterrupt, nullptr);
       } else if (StateIsRunningState(m_last_broadcast_state)) {
@@ -6410,12 +6410,20 @@ GetCoreFileSaveRangesStackOnly(Process &process,
     if (!reg_ctx_sp)
       continue;
     const addr_t sp = reg_ctx_sp->GetSP();
+    const size_t red_zone = process.GetABI()->GetRedZoneSize();
     lldb_private::MemoryRegionInfo sp_region;
     if (process.GetMemoryRegionInfo(sp, sp_region).Success()) {
       // Only add this region if not already added above. If our stack pointer
       // is pointing off in the weeds, we will want this range.
-      if (stack_bases.count(sp_region.GetRange().GetRangeBase()) == 0)
+      if (stack_bases.count(sp_region.GetRange().GetRangeBase()) == 0) {
+        // Take only the start of the stack to the stack pointer and include the redzone.
+        // Because stacks grow 'down' to include the red_zone we have to subtract it from the sp.
+        const size_t stack_head = (sp - red_zone);
+        const size_t stack_size = sp_region.GetRange().GetRangeEnd() - (stack_head);
+        sp_region.GetRange().SetRangeBase(stack_head);
+        sp_region.GetRange().SetByteSize(stack_size);
         AddRegion(sp_region, try_dirty_pages, ranges);
+      }
     }
   }
 }
diff --git a/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py b/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
index 9fe5e89142987..56f75ec7e9708 100644
--- a/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
+++ b/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
@@ -9,10 +9,9 @@
 from lldbsuite.test.lldbtest import *
 from lldbsuite.test import lldbutil
 
-
 class ProcessSaveCoreMinidumpTestCase(TestBase):
     def verify_core_file(
-        self, core_path, expected_pid, expected_modules, expected_threads
+        self, core_path, expected_pid, expected_modules, expected_threads, stacks_to_sps_map
     ):
         # To verify, we'll launch with the mini dump
         target = self.dbg.CreateTarget(None)
@@ -36,17 +35,37 @@ def verify_core_file(
             self.assertEqual(module_file_name, expected_file_name)
             self.assertEqual(module.GetUUIDString(), expected.GetUUIDString())
 
+        red_zone = process.GetTarget().GetStackRedZoneSize()
         for thread_idx in range(process.GetNumThreads()):
             thread = process.GetThreadAtIndex(thread_idx)
             self.assertTrue(thread.IsValid())
             thread_id = thread.GetThreadID()
             self.assertIn(thread_id, expected_threads)
+            frame = thread.GetFrameAtIndex(0)
+            sp_region = lldb.SBMemoryRegionInfo()
+            sp = frame.GetSP()
+            err = process.GetMemoryRegionInfo(sp, sp_region)
+            self.assertTrue(err.Success(), err.GetCString())
+            error = lldb.SBError()
+            # Try to read at the end of the stack red zone and succeed
+            process.ReadMemory(sp_region.GetRegionEnd() - 1, 1, error)
+            self.assertTrue(error.Success(), error.GetCString())
+            # Try to read just past the red zone and fail
+            process.ReadMemory(sp_region.GetRegionEnd() + 1, 1, error)
+            # Try to read from the base of the stack
+            self.assertTrue(error.Fail(), error.GetCString())
+            self.assertTrue(stacks_to_sps_map.__contains__(thread_id), "stacks_to_sps_map does not contain thread_idx: %d" % thread_idx)
+            # Ensure the SP is correct
+            self.assertEqual(stacks_to_sps_map[thread_id], sp)
+
+
         self.dbg.DeleteTarget(target)
 
     @skipUnlessArch("x86_64")
     @skipUnlessPlatform(["linux"])
     def test_save_linux_mini_dump(self):
         """Test that we can save a Linux mini dump."""
+
         self.build()
         exe = self.getBuildArtifact("a.out")
         core_stack = self.getBuildArtifact("core.stack.dmp")
@@ -69,30 +88,32 @@ def test_save_linux_mini_dump(self):
             expected_modules = target.modules
             expected_number_of_threads = process.GetNumThreads()
             expected_threads = []
+            stacks_to_sp_map = {}
 
             for thread_idx in range(process.GetNumThreads()):
                 thread = process.GetThreadAtIndex(thread_idx)
                 thread_id = thread.GetThreadID()
                 expected_threads.append(thread_id)
+                stacks_to_sp_map[thread_id] = thread.GetFrameAtIndex(0).GetSP()
 
             # save core and, kill process and verify corefile existence
             base_command = "process save-core --plugin-name=minidump "
             self.runCmd(base_command + " --style=stack '%s'" % (core_stack))
             self.assertTrue(os.path.isfile(core_stack))
             self.verify_core_file(
-                core_stack, expected_pid, expected_modules, expected_threads
+                core_stack, expected_pid, expected_modules, expected_threads, stacks_to_sp_map
             )
 
             self.runCmd(base_command + " --style=modified-memory '%s'" % (core_dirty))
             self.assertTrue(os.path.isfile(core_dirty))
             self.verify_core_file(
-                core_dirty, expected_pid, expected_modules, expected_threads
+                core_dirty, expected_pid, expected_modules, expected_threads, stacks_to_sp_map
             )
 
             self.runCmd(base_command + " --style=full '%s'" % (core_full))
             self.assertTrue(os.path.isfile(core_full))
             self.verify_core_file(
-                core_full, expected_pid, expected_modules, expected_threads
+                core_full, expected_pid, expected_modules, expected_threads, stacks_to_sp_map
             )
 
             # validate saving via SBProcess
@@ -100,14 +121,14 @@ def test_save_linux_mini_dump(self):
             self.assertTrue(error.Success())
             self.assertTrue(os.path.isfile(core_sb_stack))
             self.verify_core_file(
-                core_sb_stack, expected_pid, expected_modules, expected_threads
+                core_sb_stack, expected_pid, expected_modules, expected_threads, stacks_to_sp_map
             )
 
             error = process.SaveCore(core_sb_dirty, "minidump", lldb.eSaveCoreDirtyOnly)
             self.assertTrue(error.Success())
             self.assertTrue(os.path.isfile(core_sb_dirty))
             self.verify_core_file(
-                core_sb_dirty, expected_pid, expected_modules, expected_threads
+                core_sb_dirty, expected_pid, expected_modules, expected_threads, stacks_to_sp_map
             )
 
             # Minidump can now save full core files, but they will be huge and
@@ -116,7 +137,7 @@ def test_save_linux_mini_dump(self):
             self.assertTrue(error.Success())
             self.assertTrue(os.path.isfile(core_sb_full))
             self.verify_core_file(
-                core_sb_full, expected_pid, expected_modules, expected_threads
+                core_sb_full, expected_pid, expected_modules, expected_threads, stacks_to_sp_map
             )
 
             self.assertSuccess(process.Kill())

@@ -6410,12 +6410,20 @@ GetCoreFileSaveRangesStackOnly(Process &process,
if (!reg_ctx_sp)
continue;
const addr_t sp = reg_ctx_sp->GetSP();
const size_t red_zone = process.GetABI()->GetRedZoneSize();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asking for my understanding:
This will only really do anything if the size of the red zone is greater than 0 right? Otherwise, this change is a no-op. Is that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, but on systems where it would be greater than 0, we ensure we capture it for the memory dump as well.

@Jlalond Jlalond changed the title [LLDB/Coredump] Only take the Pthread from start start to the stackpointer + red_zone [LLDB/Coredump] Only take the Pthread from stack start to the stackpointer + red_zone May 13, 2024
Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change look good for the GetCoreFileSaveRangesStackOnly, but we want this to work for the other two modes modified-memory and full. So we need to fix GetCoreFileSaveRangesFull and GetCoreFileSaveRangesDirtyOnly.

Copy link
Contributor

@jeffreytan81 jeffreytan81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes looks good but the title and description should make it clear that we are only optimizing this for stack-only option.

Comment on lines +3860 to +3861
// cases where relying on that side-effect causes the shutdown to be
// flakey, so we should send a positive signal to interrupt the wait.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo these changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got pulled back in by running clang-formatter on this file. Several different places were reformatted, do you want me to manually revert this still?

if (stack_bases.count(sp_region.GetRange().GetRangeBase()) == 0) {
// Take only the start of the stack to the stack pointer and include the redzone.
// Because stacks grow 'down' to include the red_zone we have to subtract it from the sp.
const size_t stack_head = (sp - red_zone);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do some validation that stack_head is greater than range start in case GetRedZoneSize returns some bogus value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you have in mind? Can we not trust ABI to return us a valid value?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on where does the API get the value from. I haven't looked but it is likely GetRedZoneSize() value is fetched from dwarf, which can be bogus value generated from compiler/linker/BOLT etc...

Simply sanity check that if (stack_head > sp_region.GetRange().GetRangeBase()) will ensure we are not reading reading beyond valid memory region.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on where does the API get the value from. I haven't looked but it is likely GetRedZoneSize() value is fetched from dwarf, which can be bogus value generated from compiler/linker/BOLT etc...

Simply sanity check that if (stack_head > sp_region.GetRange().GetRangeBase()) will ensure we are not reading reading beyond valid memory region.

Red zone is gotten from the ABI plug-ins. This is correct behavior, but we should make sure that (sp - red_zone) is actually not before the start of the memory region.

const addr_t stack_head = (sp - red_zone) > sp_region.GetRange.GetRangeBase() ? (sp - red_zone) : sp_region.GetRange.GetRangeBase();

…r | Fix the saivng stack logic in MinidumpFileBuilder | Save off stack's before all other styles and prevent double saving of that region
@Jlalond Jlalond force-pushed the memory-regions-stackpointer-limiting branch from 99c0a9f to 25ff389 Compare May 15, 2024 21:57
Copy link

github-actions bot commented May 15, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link

github-actions bot commented May 15, 2024

✅ With the latest revision this PR passed the Python code formatter.

// This is a duplicate of the logic in Process::SaveOffRegionsWithStackPointers
// but ultimately, we need to only save up from the start of `the stack down to the stack pointer.
const addr_t range_end = range_info.GetRange().GetRangeEnd();
const size_t red_zone = process_sp->GetABI()->GetRedZoneSize();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we don't want to use size_t, it is 32 bits on 32 bit system and 64 on 64 bit systems. Change to addr_t

Comment on lines 6361 to 6362
const size_t stack_head = (sp - red_zone);
const size_t stack_size = sp_region.GetRange().GetRangeEnd() - stack_head;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use addr_t as this will cause 32 bit compiled LLDB's to not be able to correctly save 64 bit core files because size_t is 32 bits on 32 bit systems

Comment on lines 6357 to 6367
const addr_t sp = reg_ctx_sp->GetSP();
const size_t red_zone = process.GetABI()->GetRedZoneSize();
lldb_private::MemoryRegionInfo sp_region;
if (process.GetMemoryRegionInfo(sp, sp_region).Success()) {
const size_t stack_head = (sp - red_zone);
const size_t stack_size = sp_region.GetRange().GetRangeEnd() - stack_head;
sp_region.GetRange().SetRangeBase(stack_head);
sp_region.GetRange().SetByteSize(stack_size);
stack_ends.insert(sp_region.GetRange().GetRangeEnd());
AddRegion(sp_region, try_dirty_pages, ranges);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check if the stack head makes sense before adusting it:
We should make sure stack_head is >= sp_region.GetRange().GetRangeBase():

// Adjust start of head of stack if possible 
const size_t stack_head = (sp - red_zone);
if (stack_head > sp_region.GetRange().GetRangeBase()) {
  const size_t stack_size = sp_region.GetRange().GetRangeEnd() - stack_head;
  sp_region.GetRange().SetRangeBase(stack_head);
  sp_region.GetRange().SetByteSize(stack_size);
}
stack_ends.insert(sp_region.GetRange().GetRangeEnd());
AddRegion(sp_region, try_dirty_pages, ranges);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, also made sure we do this in MinidumpFileBuilder

…ckhead is above the bottom of the memory range, and finally run the formatter
@clayborg clayborg merged commit 47d80ec into llvm:main May 16, 2024
4 checks passed
Copy link

@Jlalond Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested
by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as
the builds can include changes from many authors. It is not uncommon for your
change to be included in a build that fails due to someone else's changes, or
infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself.
This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@Jlalond Jlalond deleted the memory-regions-stackpointer-limiting branch August 6, 2024 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants