Skip to content

[lldb] Clear Frames when changing disable-language-runtime-unwindplans #151208

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 2 commits into from
Aug 1, 2025

Conversation

felipepiovezan
Copy link
Contributor

This patch uses the "setting changed" callback to clear previously cached stack frames when
target.process.disable-language-runtime-unwindplans is changed. This is necessary so that changing the setting followed by a backtrace command produces an accurate backtrace.

With this, a user can create a custom command like below in order to quickly inspect a backtrace created without language plugins.

debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans true")
debugger.HandleCommand("bt " + command)
debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans false")

In the future, we may consider implementing this as an option to backtrace. Currently, this process setting is the only way of affecting the unwinder, and changing the process setting as part of the backtrace implementation doesn't feel right.

There are no upstream users of this flag, so we cannot write a test for it here.

This patch uses the "setting changed" callback to clear previously
cached stack frames when
`target.process.disable-language-runtime-unwindplans` is changed. This
is necessary so that changing the setting followed by a `backtrace`
command produces an accurate backtrace.

With this, a user can create a custom command like below in order to
quickly inspect a backtrace created without language plugins.

```
debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans true")
debugger.HandleCommand("bt " + command)
debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans false")
```

In the future, we may consider implementing this as an option to
`backtrace`. Currently, this process setting is the only way of
affecting the unwinder, and changing the process setting as part of the
backtrace implementation doesn't feel right.

There are no upstream users of this flag, so we cannot write a test for
it here.
@llvmbot
Copy link
Member

llvmbot commented Jul 29, 2025

@llvm/pr-subscribers-lldb

Author: Felipe de Azevedo Piovezan (felipepiovezan)

Changes

This patch uses the "setting changed" callback to clear previously cached stack frames when
target.process.disable-language-runtime-unwindplans is changed. This is necessary so that changing the setting followed by a backtrace command produces an accurate backtrace.

With this, a user can create a custom command like below in order to quickly inspect a backtrace created without language plugins.

debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans true")
debugger.HandleCommand("bt " + command)
debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans false")

In the future, we may consider implementing this as an option to backtrace. Currently, this process setting is the only way of affecting the unwinder, and changing the process setting as part of the backtrace implementation doesn't feel right.

There are no upstream users of this flag, so we cannot write a test for it here.


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

2 Files Affected:

  • (modified) lldb/include/lldb/Target/Process.h (+1)
  • (modified) lldb/source/Target/Process.cpp (+10)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 7e66e315a867c..dc75d98acea70 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -100,6 +100,7 @@ class ProcessProperties : public Properties {
   void SetStopOnSharedLibraryEvents(bool stop);
   bool GetDisableLangRuntimeUnwindPlans() const;
   void SetDisableLangRuntimeUnwindPlans(bool disable);
+  void DisableLanguageRuntimeUnwindPlansCallback();
   bool GetDetachKeepsStopped() const;
   void SetDetachKeepsStopped(bool keep_stopped);
   bool GetWarningsOptimization() const;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 2aa02fd58335e..385834795cb05 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -166,6 +166,9 @@ ProcessProperties::ProcessProperties(lldb_private::Process *process)
     m_collection_sp->SetValueChangedCallback(
         ePropertyPythonOSPluginPath,
         [this] { m_process->LoadOperatingSystemPlugin(true); });
+    m_collection_sp->SetValueChangedCallback(
+        ePropertyDisableLangRuntimeUnwindPlans,
+        [this] { DisableLanguageRuntimeUnwindPlansCallback(); });
   }
 
   m_experimental_properties_up =
@@ -280,6 +283,13 @@ void ProcessProperties::SetDisableLangRuntimeUnwindPlans(bool disable) {
   m_process->Flush();
 }
 
+void ProcessProperties::DisableLanguageRuntimeUnwindPlansCallback() {
+  if (!m_process)
+    return;
+  for (auto thread_sp : m_process->Threads())
+    thread_sp->ClearStackFrames();
+}
+
 bool ProcessProperties::GetDetachKeepsStopped() const {
   const uint32_t idx = ePropertyDetachKeepsStopped;
   return GetPropertyAtIndexAs<bool>(

@felipepiovezan felipepiovezan requested a review from jimingham July 29, 2025 19:40
return;
for (auto thread_sp : m_process->Threads())
thread_sp->ClearStackFrames();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(In case you're curious: the Threads() method ensures the threads mutex is acquired for the scope of the range object)

@jimingham
Copy link
Collaborator

jimingham commented Jul 29, 2025

This mostly looks fine, with a couple questions:

  1. Do we need to unset the Currently Selected Frame, we've almost surely invalidated it, and even if it does happen to accidentally still exist, it might mean something different. This almost seems like something ClearStackFrames should do.

  2. This probably works, but to be sure... What happens if I run a function by hand, stop in a breakpoint in that function, then set this setting to undo all the cached stack frames. Do we have enough info in that case to reconstruct the frames we've pushed on the stack and continue the execution?

@felipepiovezan
Copy link
Contributor Author

  1. Do we need to unset the Currently Selected Frame, we've almost surely invalidated it, and even if it does happen to accidentally still exist, it might mean something different. This almost seems like something ClearStackFrames should do.

Based on the implementation of Thread::SetSelectedFrame, the selected frame is a property of Thread::m_curr_frames_sp, which gets freed by Thread::ClearStackFrames.

I did an experiment where I stopped a test program, selected frame 1, then updated the setting. Then I did a frame command, and what was shown was frame 0. So I think we are indeed properly reseting the selected frame.

  1. This probably works, but to be sure... What happens if I run a function by hand, stop in a breakpoint in that function, then set this setting to undo all the cached stack frames. Do we have enough info in that case to reconstruct the frames we've pushed on the stack and continue the execution?

Do you mean something like:

b some_foo
expr --ignore-breakpoints false -- some_foo()
settings set  target.process.disable-language-runtime-unwindplans true
thread return -x

This seems to work too.

What will obviously be bad is if a user starts a step-over operation, hits a breakpoint along the way, flips (but doesn't unflip!) this flag in a way that actually changes the backtrace, and then attempts to continue. This will definitely confuse the in-flight step-over operation, but it is not related to this patch.

@jimingham
Copy link
Collaborator

  1. Do we need to unset the Currently Selected Frame, we've almost surely invalidated it, and even if it does happen to accidentally still exist, it might mean something different. This almost seems like something ClearStackFrames should do.

Based on the implementation of Thread::SetSelectedFrame, the selected frame is a property of Thread::m_curr_frames_sp, which gets freed by Thread::ClearStackFrames.

I did an experiment where I stopped a test program, selected frame 1, then updated the setting. Then I did a frame command, and what was shown was frame 0. So I think we are indeed properly reseting the selected frame.

  1. This probably works, but to be sure... What happens if I run a function by hand, stop in a breakpoint in that function, then set this setting to undo all the cached stack frames. Do we have enough info in that case to reconstruct the frames we've pushed on the stack and continue the execution?

Do you mean something like:

b some_foo
expr --ignore-breakpoints false -- some_foo()
settings set  target.process.disable-language-runtime-unwindplans true
thread return -x

This seems to work too.

What will obviously be bad is if a user starts a step-over operation, hits a breakpoint along the way, flips (but doesn't unflip!) this flag in a way that actually changes the backtrace, and then attempts to continue. This will definitely confuse the in-flight step-over operation, but it is not related to this patch.

Thanks for doing these experiments...

Given that it will change the nature of the stack when you change this variable, we should probably also clear the ThreadPlanStack for these threads when we clear the stack frames? Most plans will recognize that they no longer comprehend the new stack world and mark themselves Stale. But the chances of the thread plans still being valid over this change seems small, so it seems more stable to just clear them out.

@felipepiovezan
Copy link
Contributor Author

Given that it will change the nature of the stack when you change this variable, we should probably also clear the ThreadPlanStack for these threads when we clear the stack frames? Most plans will recognize that they no longer comprehend the new stack world and mark themselves Stale. But the chances of the thread plans still being valid over this change seems small, so it seems more stable to just clear them out.

This is an interesting question, because if you toggle and then untoggle the setting, the thread plans should still work, as they keep track of StackIDs as opposed to StackFrames. If we were to clear the thread plans here, we would lose that ability.

Likewise, if we were to clear the thread plans while stopped on a breakpoint hit during expression evaluation, what would be the consequences of this? Isn't expression evaluation driven by the thread plan?

@felipepiovezan
Copy link
Contributor Author

felipepiovezan commented Jul 30, 2025

Another consideration: it is not the clearing of the stack frames that would confuse ThreadPlans, right? When we continue, the entire view of the stacks will be recomputed anyway. It is this recomputation done in a different way (through a different unwinder) that will confuse them, and not the clearing of the stack frames while execution was stopped

@jimingham
Copy link
Collaborator

The expression cancellation wouldn't be a problem, since you would pop the ThreadPlanCallExpression, which would take down the expression evaluation. It would cancel the evaluation, but that's probably what you want.

The Thread plans generally expect to do work on stop, and on resume. They aren't expecting to have to re-evaluate the world in between those two points. So for instance they might have decided on stop that they needed to do some work which is no longer relevant because the stack view changed. But they really aren't expecting to have to make decisions on that till the next stop. The damage might have already been done by then.

If your intention is to provide turning off the system unwinding as a "peek" operation, then you might consider stashing the thread plan stack to one side if the stack gets reinterpreted, and restoring it if they reverse the condition before the next "continue".

Note, if we were being really exact, we'd love to only do this for thread that were changed by the language unwinder. Not sure if you know enough to determine that, however.

@felipepiovezan
Copy link
Contributor Author

Ok, you've convinced me. I've updated the PR to clean the thread plans.

The expression cancellation wouldn't be a problem, since you would pop the ThreadPlanCallExpression, which would take down the expression evaluation. It would cancel the evaluation, but that's probably what you want.

Oh, interesting, I didn't know it would be clever enough to restore the state properly, but it does based on my testing!

If your intention is to provide turning off the system unwinding as a "peek" operation, then you might consider stashing the thread plan stack to one side if the stack gets reinterpreted, and restoring it if they reverse the condition before the next "continue".

While my intention is to provide a peek operation, I also want it to be relatively low-risk (and low effort). Stashing stack frames and restoring them seem a bit novel / risky, so I'm ok if we erase thread plans as a price of this; this process property breaks in-flight thread plans with or without this patch, so we're not changing the status quo with the callback.

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

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

LGTM

@felipepiovezan felipepiovezan merged commit 03bb10b into llvm:main Aug 1, 2025
9 checks passed
@felipepiovezan felipepiovezan deleted the felipe/unwinder_callback branch August 1, 2025 01:01
felipepiovezan added a commit to felipepiovezan/llvm-project that referenced this pull request Aug 1, 2025
…ns` (llvm#151208)

This patch uses the "setting changed" callback to clear previously
cached stack frames when
`target.process.disable-language-runtime-unwindplans` is changed. This
is necessary so that changing the setting followed by a `backtrace`
command produces an accurate backtrace.

With this, a user can create a custom command like below in order to
quickly inspect a backtrace created without language plugins.

```
debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans true")
debugger.HandleCommand("bt " + command)
debugger.HandleCommand("settings set target.process.disable-language-runtime-unwindplans false")
```

In the future, we may consider implementing this as an option to
`backtrace`. Currently, this process setting is the only way of
affecting the unwinder, and changing the process setting as part of the
backtrace implementation doesn't feel right.

There are no upstream users of this flag, so we cannot write a test for
it here.

(cherry picked from commit 03bb10b)
adrian-prantl added a commit to swiftlang/llvm-project that referenced this pull request Aug 4, 2025
…k_62

🍒 [lldb] Clear Frames when changing `disable-language-runtime-unwindplans` (llvm#151208)
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.

3 participants