-
Notifications
You must be signed in to change notification settings - Fork 332
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
[lldb] Swift OS plugin #9839
base: stable/20240723
Are you sure you want to change the base?
[lldb] Swift OS plugin #9839
Conversation
@@ -0,0 +1,164 @@ | |||
//===-- OperatingSystemSwift.cpp -----------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not important. "OperatingSystemSwift" sounds slightly wrong to me. What about "OperatingSystemSwiftTasks" or "OperatingSystemSwiftConcurrency"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the name seems misleading the full lldb affordance name is "Operating System Thread Plugin"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the full lldb affordance name is "Operating System Thread Plugin"
While this may be true, it's worth noting that the only other plugin also uses the short version: "OperatingSystemX", where X = Python.
OperatingSystemSwiftTasks
I'm going to rename to this!
return nullptr; | ||
|
||
/// Heuristic to only load the plugin for swift programs. | ||
FileSpec swift_module_name = FileSpec("libswiftCore.dylib"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works in a very narrow subset of Swift programs. You should defer to SwiftLanguageRuntime's helper code.
The cases this doesn't support are: non-Darwin (".dylib"), statically linked Swift stdlib, embedded Swift (no dylibs at all).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a helper function mind? The only thing I found is GetLikelySwiftImageNamesForModule
, which has some very weird behavior I'm not sure I understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also arrange that this can only be accessed by the SwiftLanguageRuntime, and then its loading will be gated on the Runtime loading, which already has to do this sort of computation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jimingham I looked into that option, but AFAICT the SwiftLanguageRuntime is loaded regardless of modules.
I.e. the first call to "load runtimes" will always load it, even if the relevant libraries haven't been loaded yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adrian-prantl I don't think the code in SwiftLanguageRuntime handles the cases you mentioned either, it always returns a dylib for Darwin platforms:
static ConstString GetStandardLibraryName(Process &process) {
// This result needs to be stored in the constructor.
PlatformSP platform_sp(process.GetTarget().GetPlatform());
if (platform_sp)
return platform_sp->GetFullNameForDylib(
ConstString(SwiftLanguageRuntime::GetStandardLibraryBaseName()));
return {};
}
ConstString SwiftLanguageRuntime::GetStandardLibraryName() {
return ::GetStandardLibraryName(*m_process);
}
ConstString PlatformDarwin::GetFullNameForDylib(ConstString basename) {
if (basename.IsEmpty())
return basename;
StreamString stream;
stream.Printf("lib%s.dylib", basename.GetCString());
return ConstString(stream.GetString());
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a private method in there that maybe we could expose...
swift::tls_get_key(swift::tls_key::concurrency_task) * ptr_size; | ||
// Offset of a Task ID inside a Task data structure, guaranteed by the ABI. | ||
// See Job in swift/RemoteInspection/RuntimeInternals.h. | ||
m_task_id_offset = 4 * ptr_size + 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should check that the swift concurrency version is still 1 here.
} | ||
|
||
// Mask higher bits to avoid conflicts with core thread IDs. | ||
uint64_t masked_task_id = 0xdeadbeef00000000 | *task_id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw it is probably fine to not do this, TIDs are generally higher numbers and there might be value in exposing the task id directly. If we do mask, we should use some other mask
} | ||
|
||
// Mask higher bits to avoid conflicts with core thread IDs. | ||
uint64_t masked_task_id = 0xdeadbeef00000000 | *task_id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be the visible "TID" for running tasks, right? And what I'd have to type if I wanted to set a "task specific" breakpoint? If so, we might not want it to be deadbeef. That's cute if you know the reference, but otherwise somewhat off-putting...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, I'm actually looking for suggestions here, if we should mask it at all and what to use if we do
Deadbeef was just something to help me grep the logs as I debugged.
ThreadSP swift_thread = [&]() -> ThreadSP { | ||
// If we already had a thread for this Task in the last stop, re-use it. | ||
if (ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id); | ||
IsOperatingSystemPluginThread(old_thread)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have to handle the case where when you stopped the first time, this Task was backed by "core thread 1" but the next time you stopped it was backed by a different core thread? I don't see where you are changing the backing thread in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where you are changing the backing thread in this case?
It's just a few lines below, both cases are handled the same way:
swift_thread->SetBackingThread(real_thread);
I note that the only swift specific bits of this are finding the task on a particular thread, and a few checks about whether this should be used that I think belong more properly in the LanguageRuntime that owns the OS plugin... Would it be possible to make an |
969ca8a
to
8aeb614
Compare
@@ -302,6 +302,7 @@ class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan { | |||
auto &target = thread.GetProcess()->GetTarget(); | |||
m_funclet_bp = target.CreateBreakpoint(destination_addr, true, false); | |||
m_funclet_bp->SetBreakpointKind("async-run-to-funclet"); | |||
m_funclet_bp->SetThreadID(thread.GetID()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to refresh reviewer's memories: this is the thread plan to "run through a task switch and stop when we schedule this same async function again. This plan now sets thread specific breakpoints, since they are now task breakpoints!
If we somehow push this type of plan without having the OS plugin, we are at a risk of breaking in the wrong task. But if the plugin is not there and there are tasks, we are going to have much bigger problems anyway. For example, this code has been associated with a lot of LLDB crashes because it triggers stack plan migration (this is a downstream-only thing that becomes dead code with this patch, and I'll delete it in a follow up PR)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be this an inline comment in the source file ?
8aeb614
to
9bd7fa3
Compare
This is here mostly out of an abundance of caution, the default is to use the plugins.
9bd7fa3
to
5142ea2
Compare
@@ -91,6 +91,10 @@ const char *SwiftLanguageRuntime::GetStandardLibraryBaseName() { | |||
return "swiftCore"; | |||
} | |||
|
|||
const char *SwiftLanguageRuntime::GetConcurrencyLibraryBaseName() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can this return a StringRef
or StringLiteral
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool stuff! I left some comments / suggestions :)
@@ -131,6 +149,11 @@ static bool IsStaticSwiftRuntime(Module &image) { | |||
return image.FindFirstSymbolWithNameAndType(swift_reflection_version_sym); | |||
} | |||
|
|||
static bool IsStaticSwiftConcurrency(Module &image) { | |||
static const ConstString task_switch_symbol("_swift_task_switch"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be:
static const ConstString task_switch_symbol("_swift_task_switch"); | |
static constexpr llvm::StringLiteral task_switch_symbol = "_swift_task_switch"; |
@@ -168,6 +191,52 @@ static ModuleSP findRuntime(Process &process, RuntimeKind runtime_kind) { | |||
return runtime_image; | |||
} | |||
|
|||
ModuleSP SwiftLanguageRuntime::findConcurrencyModule(Process &process) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: casing
ModuleSP SwiftLanguageRuntime::findConcurrencyModule(Process &process) { | |
ModuleSP SwiftLanguageRuntime::FindConcurrencyModule(Process &process) { |
} | ||
|
||
std::optional<uint32_t> | ||
SwiftLanguageRuntime::findConcurrencyDebugVersion(Process &process) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
@@ -98,6 +98,16 @@ class SwiftLanguageRuntime : public LanguageRuntime { | |||
static SwiftLanguageRuntime *Get(lldb::ProcessSP process_sp) { | |||
return SwiftLanguageRuntime::Get(process_sp.get()); | |||
} | |||
|
|||
/// Returns the Module containing the Swift Concurrency runtime, if it exists. | |||
static lldb::ModuleSP findConcurrencyModule(Process &process); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
/// returned. | ||
/// Returns 0 for versions of the module prior to the introduction | ||
/// of versioning. | ||
static std::optional<uint32_t> findConcurrencyDebugVersion(Process &process); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
@@ -302,6 +302,7 @@ class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan { | |||
auto &target = thread.GetProcess()->GetTarget(); | |||
m_funclet_bp = target.CreateBreakpoint(destination_addr, true, false); | |||
m_funclet_bp->SetBreakpointKind("async-run-to-funclet"); | |||
m_funclet_bp->SetThreadID(thread.GetID()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be this an inline comment in the source file ?
} | ||
|
||
// Mask higher bits to avoid conflicts with core thread IDs. | ||
uint64_t masked_task_id = 0x0000000f00000000 | *task_id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice hack :p
if (ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id); | ||
IsOperatingSystemPluginThread(old_thread)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First time I'm seeing this syntax :p Don't you want to run the second part of the if condition only if the first one if valid ?
if (ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id); | |
IsOperatingSystemPluginThread(old_thread)) | |
if ((ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id)) && IsOperatingSystemPluginThread(old_thread)) |
No description provided.