-
Notifications
You must be signed in to change notification settings - Fork 1
Integrate dual connection w/ LLDB-DAP using shared event thread #26
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
base: llvm-server-plugins
Are you sure you want to change the base?
Conversation
lldb/tools/lldb-dap/DAP.cpp
Outdated
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 can go inside DAPSessionManager::GetInstance(). Also add a commnt before it:
// NOTE: intentional leak to avoid issues with C++ destructor chain
lldb/tools/lldb-dap/DAP.cpp
Outdated
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 solutions implements a solution where once we have a GPU process that gets spawned, now all DAP instances after this will share the same debugger and the same event thread. It would be nice to be able to only do this for the spawned process. So if we did something like:
1 - a.out with GPU linkage gets launched
2 - GPU process is spawned and we add a new process to VS code
3 - CPU child process gets spawned
It would be nice if 1 and 2 share the same debugger and event thread, but 3 would have its own debugger and event thread.
Since we know the new SBTarget (in target
above), maybe we can do something like:
DAPSessionManager::GetInstance().SetSharedDebuggerForTarget(debugger, target_index);
This is making a strong mapping that says that our new target wants to use this debugger. But then the question is how to make sure when we start the new debug session for the GPU to get this since the debugger gets created in the initialize packet. Is there a way to pass additional info to the "initialize" packet somehow so we can specify the target index?
lldb/tools/lldb-dap/DAP.cpp
Outdated
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.
Add a comment here explaining what is going on:
// We create "attachCommands" that will select the target that already
// exists in LLDB. The DAP instance will attach to this already existing
// target and the debug session will be ready to go.
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 code makes me nervous. If the debugger already has a target, there is no guarantee that the selected target is the right one for the one we want to attach to.
One fix would be to add a new target index to the AttachRequestArguments
and into the package.json:
struct AttachRequestArguments {
...
uint32_t target_idx = UINT32_MAX;
};
Then we can add a "target_idx" as an argument to the "attach" configuration in the package.json.
Since we control the "attach" config that we send here, we can easily add it. Then this code would be something like:
lldb::SBTarget target;
if (args.target_idx != UINT32_MAX) {
lldb::SBTarget target = dap.debugger.GetTargetAtIndex(args.target_idx);
if (!target.IsValid())
error.SetErrorStringWithFormat("invalid target_idx %u in attach config", args.target_idx);
} else {
target = dap.CreateTarget(error);
}
if (error.Fail())
return ToError(error);
dap.SetTarget(target);
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 is where I don't know how to solve the issue of getting the right debugger from our shared instance. The issue is once anyone sets the shared debugger, we will always get one back here. So if we add functionality later that allows us to follow all child processes of a parent process, they will all always share the same debugger...
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.
Will revert this once other changes are approved. Needed to set this for testing since lldb wasn't breaking at "_ZN4rocr19_loader_debug_stateEv" and properly loading dbg modules, so I couldn't test setting breakpoints, etc.
5dd859e
to
db4c6e6
Compare
928542d
to
1170435
Compare
1170435
to
770558b
Compare
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.
We should make a strong mapping between target index and the debugger to use. See inlined comments for details
lldb/tools/lldb-dap/DAP.h
Outdated
DAPSessionManager(DAPSessionManager &&) = delete; | ||
DAPSessionManager &operator=(DAPSessionManager &&) = delete; | ||
|
||
std::mutex sessions_mutex_; |
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.
All instance variables in LLDB code will start with m_
instead of suffixing them with _
. So this should be:
std::mutex m_sessions_mutex;
Ditto for all ivars below.
// Initialize DAP debugger and related components if not sharing previously | ||
// launched debugger. | ||
bool use_shared_debugger = args.targetIdx != UINT32_MAX; | ||
if (Error err = dap.InitializeDebugger(use_shared_debugger)) { |
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.
If we have a target index, we should just pass this into dap.InitializeDebugger(args.targetIdx)
. Since if we have a target, we must use its debugger.
lldb/tools/lldb-dap/DAP.cpp
Outdated
return llvm::Error::success(); | ||
} | ||
|
||
llvm::Error DAP::InitializeDebugger(bool use_shared_debugger) { |
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.
We should change this to pass in the target index as an optional:
llvm::Error DAP::InitializeDebugger(std::optional<uint32_t> target_idx)
Then we can use it below.
lldb/tools/lldb-dap/DAP.cpp
Outdated
if (use_shared_debugger) { | ||
auto shared_debugger = DAPSessionManager::GetInstance().GetSharedDebugger(); | ||
if (!shared_debugger) { | ||
return llvm::createStringError(llvm::inconvertibleErrorCode(), | ||
"unable to get shared debugger"); | ||
} | ||
debugger = shared_debugger.value(); | ||
return StartEventThreads(); | ||
} |
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 code can then become:
if (target_idx.has_value())
auto shared_debugger = DAPSessionManager::GetInstance().GetSharedDebugger(*target_idx);
if (shared_debugger) {
debugger = shared_debugger.value();
return StartEventThreads();
}
}
This allows us to be clear about which target must use which debugger. Also, do we need to call StartEventThreads()
above? Don't we already have a thread for the old debugger that we can use?
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.
StartEventThreads()
here was just for consistency since StartEventThread()
will just use the shared thread from the old debugger (so it'll be a no-op and not start up a new thread), but each DAP instance still has its own ProgressEventThread()
as well that's not shared from the old debugger.
lldb/tools/lldb-dap/DAP.cpp
Outdated
auto target_index = debugger.GetIndexOfTarget(target); | ||
|
||
// Set the shared debugger for GPU processes | ||
DAPSessionManager::GetInstance().SetSharedDebugger(debugger); |
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 we want to use the target index in this call to make sure we always get the right debugger? like:
DAPSessionManager::GetInstance().SetSharedDebugger(debugger, target_index);
This then clearly specifies that a this debugger should be used for "target_index".
lldb/tools/lldb-dap/DAP.h
Outdated
|
||
/// Optional shared debugger instance set when the native process | ||
/// spawns a new GPU target | ||
std::optional<lldb::SBDebugger> shared_debugger_; |
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 a map that maps target index to which debugger to use. That would make sure we get the right debugger for the target. I am thinking of if we get two GPU connections at the same time, this could fail. So if we use a map:
std::map<uint32_t, lldb::SBDebugger> m_target_to_debugger_map;
lldb/tools/lldb-dap/DAP.h
Outdated
void WaitForAllSessionsToDisconnect(); | ||
|
||
/// Set the shared debugger instance (only for GPU processes) | ||
void SetSharedDebugger(lldb::SBDebugger debugger); |
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.
Change to make a map between target index and the right debugger to use:
void SetSharedDebugger(uint32_t target_idx, lldb::SBDebugger debugger) {
m_target_to_debugger_map[target_idx] = debugger;
}
I have a comment to creating m_target_to_debugger_map
below
lldb/tools/lldb-dap/DAP.h
Outdated
void SetSharedDebugger(lldb::SBDebugger debugger); | ||
|
||
/// Get the shared debugger instance if it exists | ||
std::optional<lldb::SBDebugger> GetSharedDebugger(); |
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.
Change to include the target index:
std::optional<lldb::SBDebugger> GetSharedDebugger(uint32_t target_idx) {
auto pos = m_target_to_debugger_map.find(target_idx);
if (pos == m_target_to_debugger_map.end())
return std::nullopt;
lldb::SBDebugger debugger = pos->second;
m_target_to_debugger_map.erase(pos);
return debugger;
}
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 think we have some use count issues with the current design. See inlined comments. We want to verify that our event thread is being joined using some logging temporarily to make sure this design is working as expected. Full details and suggestions in inlined comments.
std::string coreFile; | ||
|
||
/// Index of an existing target to attach to. | ||
uint32_t targetIdx = UINT32_MAX; |
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.
Can we make this a std::optional<uint32_t> targetIdx;
?
lldb/tools/lldb-dap/DAP.h
Outdated
std::map<lldb::user_id_t, std::shared_ptr<std::thread>> | ||
m_debugger_event_threads; |
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.
We might need to make this a map where the value is a std::weak_ptr<std::thread>>
otherwise our use count will always be 2 in DAP::StopEventHandlers() when there is just one DAP using the event thread right?
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.
Thanks for the catch! The original design didn't have the manager holding references to the event thread shared_ptr, and just had one shared_ptr per DAP, but I never updated the StopEventHandlers() accordingly to account for the new changes. weak_ptr makes sense in this context!
I've updated and logged to verify that the thread is joined at the end now when disconnecting the last dap instance.
lldb/tools/lldb-dap/DAP.cpp
Outdated
} | ||
|
||
// Create new thread and store it | ||
auto new_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.
we use the "_sp" suffix for shared pointers and "_wp" for weak pointers to make sure people know what they have. So rename new_thread
to thread_sp
.
lldb/tools/lldb-dap/DAP.cpp
Outdated
// Create new thread and store it | ||
auto new_thread = | ||
std::make_shared<std::thread>(&DAP::EventThread, requesting_dap); | ||
m_debugger_event_threads[debugger_id] = new_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.
This assignment will work if the value is a weak pointer, no changes needed.
lldb/tools/lldb-dap/DAP.cpp
Outdated
// Check if we already have a thread (most common case) | ||
auto it = m_debugger_event_threads.find(debugger_id); | ||
if (it != m_debugger_event_threads.end() && it->second) { | ||
return it->second; |
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.
If the value is a weak pointer, we will want to call lock()
on it to convert it to a shared pointer. So the code for using a weak pointer needs to be:
if (it != m_debugger_event_threads.end()) {
auto thread_sp = it->second.lock();
if (thread_sp)
return thread_sp;
// Our weak pointer has expired
m_debugger_event_threads.erase(it);
}
bool client_failed = false; | ||
{ | ||
std::scoped_lock<std::mutex> lock(dap_sessions_mutex); | ||
for (auto [sock, dap] : dap_sessions) { | ||
if (llvm::Error error = dap->Disconnect()) { | ||
client_failed = true; | ||
llvm::errs() << "DAP client " << dap->transport.GetClientName() | ||
<< " disconnected failed: " | ||
<< llvm::toString(std::move(error)) << "\n"; | ||
} | ||
} | ||
} |
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 know you are doing a lot of refactoring to existing code into DAPSessionManager class. I would suggest you create a separate PR into upstream main to upstream the change. That would avoid potential merge conflicts in future considering several folks are actively refactoring/changing this in upstream.
lldb/tools/lldb-dap/DAP.h
Outdated
using ClientFeature = protocol::ClientFeature; | ||
|
||
/// Global DAP session manager | ||
class DAPSessionManager { |
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.
Can you move this class into its own DAPSessionManager.h/DAPSessionManager.cpp
?
lldb/tools/lldb-dap/DAP.cpp
Outdated
static DAPSessionManager *instance = nullptr; | ||
if (!instance) { | ||
instance = new DAPSessionManager(); | ||
} | ||
return *instance; |
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.
Is this thread safe? I think you need mutex protection or std::call_once
for it (or Double-checked locking with manual mutex lock).
Since C++11, static local variable initialization is guaranteed to be thread-safe but since you are using pointer here, I do not think C++ compiler is providing you thread safe guarantee.
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.
Good point, it's not thread-safe since I"m initializing it to nullptr. I'll address this with a std::call_once call.
// Initialize DAP debugger and related components if not sharing previously | ||
// launched debugger. | ||
std::optional<uint32_t> target_idx = args.targetIdx; | ||
if (Error err = dap.InitializeDebugger(target_idx)) { |
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 delays InitializeDebugger
from InitializeRequestHandler to Launch/Attach
time. Is this timing/semantics changing required? How do we know this is safe to do without causing issues?
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 change was required there's not any way to pass additional info to the "initialize" packet from the startDebugging
request since we aren't the ones sending it, so we can only know whether to use the shared debugger instance at attach time. Otherwise, we'd end up always using a shared debugger, and run into the issue where all subsequent debugging sessions launched would still be under the same shared debugger instance and run the same event thread, when we only want this for the GPU case.
I did some manual testing by attaching lldb to the lldb-dap process, and the initialized debugger isn't used for anything (other than telemetry that requires a debugger to exist) until the Launch/Attach time (more details here #26 (comment)), so the change seemed safe.
The other option for solving this issue would've been having some sort of counter to decrement once the shared debugger instance was used for a GPU child session, but that option would've been racy.
lldb/tools/lldb-dap/DAP.cpp
Outdated
if (event_thread_sp && event_thread_sp.use_count() == 1 && | ||
event_thread_sp->joinable()) { |
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 is not thread-safe -- when the last two clients both are calling DAP::StopEventHandlers
at the same time, there are a race condition that both will find use_count > 1 and miss the broadcast to stop the event thread.
We need to either add the locking or probably can use std::shared_ptr
constructor's deleter to do the broadcaster and join code which will be called when target object is deleted.
Summary: This should allow the shared_ptr destruction to take care of broadcasting events and joining the event thread() on the last reference to it
@qxy11 , please let us know when this is ready for review again :) |
@walter-erquinigo - it should be ready for another review, all previous comments should've been addressed. |
I'm about to review this, but I have the overall sense that these changes are big and non-trivial, and keeping them in this fork will be a burden when doing rebases. |
lldb/include/lldb/API/SBTarget.h
Outdated
eBroadcastBitWatchpointChanged = (1 << 3), | ||
eBroadcastBitSymbolsLoaded = (1 << 4), | ||
eBroadcastBitSymbolsChanged = (1 << 5), | ||
eBroadcastBitNewTargetSpawned = (1 << 6), |
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 think NewTargetCreated
would be a better name
def _handle_startDebugging_request(self, packet): | ||
response = { | ||
"type": "response", | ||
"request_seq": packet.get("seq", 0), |
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 think you should have a default seq, as you should always get one. packet["seq"]
should be fine in this case
sourceMap: Optional[Union[list[tuple[str, str]], dict[str, str]]] = None, | ||
gdbRemotePort: Optional[int] = None, | ||
gdbRemoteHostname: Optional[str] = None, | ||
targetIdx: Optional[int] = None, |
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.
We shouldn't use target index for this. It's not guaranteed to be unique per target for an entire session. For example, see this sequence:
- you create two targets. They will have index 0 and 1
- you delete target 1
- then you create a new target. It will have index 1 again
This might result in race conditions.
I think that should use create an actual unique id for each target as part of its creation, then you can use it to select the target you want as part of the reverse request.
def _detect_rocm(): | ||
"""Detects rocm target.""" | ||
try: | ||
proc = Popen(["rocminfo"], stdout=PIPE, stderr=PIPE) | ||
return "amd" | ||
except Exception: | ||
return None | ||
|
||
def skipUnlessHasROCm(func): | ||
"""Decorate the item to skip test unless ROCm is available.""" | ||
|
||
def has_rocm(): | ||
if _detect_rocm() is None: | ||
return "ROCm not available (rocm-smi not found)" | ||
return None | ||
|
||
return skipTestIfFn(has_rocm)(func) | ||
|
||
|
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.
move this file into a new folder called amd
within lldb-dap/gpu
return skipTestIfFn(has_rocm)(func) | ||
|
||
|
||
class TestDAP_gpu_reverse_request(lldbdap_testcase.DAPTestCaseBase): |
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.
add AMD to the name of the class, also don't use underscores
lldb/tools/lldb-dap/DAP.cpp
Outdated
// process name | ||
|
||
attach_config.try_emplace("type", "lldb"); | ||
attach_config.try_emplace("name", "GPU Session"); |
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 name has to be configurable by the creator of the target. It might be useful to encode a name in the event or in the target itself. For example, in the GPUAction handler where we create the GPU target, we should be able to specify a custom name for the target that the session will pick up. A default name like Session <target id>
might be okay as a fallback.
This functionality should be not just for GPUs, as mentioned above.
lldb/tools/lldb-dap/DAP.cpp
Outdated
llvm::json::Object start_debugging_args; | ||
start_debugging_args.try_emplace("request", "attach"); | ||
start_debugging_args.try_emplace("configuration", | ||
std::move(attach_config)); |
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 might simplify this code pattern
llvm::json::Object start_debugging_args; | |
start_debugging_args.try_emplace("request", "attach"); | |
start_debugging_args.try_emplace("configuration", | |
std::move(attach_config)); | |
llvm::json::Object start_debugging_args { | |
{"request", "attach"}, | |
{"configuration", std::move(attach_config)} | |
}; | |
lldb/tools/lldb-dap/DAP.cpp
Outdated
DAP *dap_instance = | ||
DAPSessionManager::GetInstance().FindDAPForTarget(event_target); |
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.
FindDAPForTarget is used very often, you might as well create a static override that will under the hood invoke GetInstance(), just to simplify the callers
SendOutput(OutputType::Important, | ||
llvm::formatv("{0}: {1}", type, message).str()); | ||
// Global debugger events - send to all DAP instances | ||
std::vector<DAP *> active_instances = |
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.
very nice
lldb/tools/lldb-dap/DAP.cpp
Outdated
auto target_index = debugger.GetIndexOfTarget(target); | ||
|
||
// Set the shared debugger for GPU processes | ||
DAPSessionManager::GetInstance().SetSharedDebugger(target_index, |
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 think you can avoid storing a debugger per target index if you just store the Target object, and you can query its underlying debugger whenever you initialize the new debug session.
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 was looking into switching to this, but it seems like it might be a less clear API pairing since we'd still have to store a map from target ID -> target in some sort of StoreTarget
function, rather than target ID -> debugger, but now we'd have to query the underlying debugger whenever we call GetSharedDebugger
rather than directly getting it, and it doesn't seem like there's much of an overhead difference between storing just the pointer to the target vs. the debugger?
…d encode the name into the target event
… broadcast bit to eBroadcastBitNewTargetCreated and p
Sounds good! Once changes here look good, I'll put up an upstream diff with the non-gpu specific changes. |
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.
Overall this looks pretty good. There might be a few things to improve, but please start upstreaming the non-gpu parts and i'll provide specific feedback to those patches.
Thanks for all of this :)
lldb/source/Target/TargetList.cpp
Outdated
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 can't really do that because you can have a target without a process. It would be better to just assign a proper unique ID to the target class
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.
just call it session name. This should be somewhat generic
- Resolved conflicts in GPUGDBRemotePackets.h and .cpp to support both session_name and stop_id fields - Resolved conflicts in LLDBServerPluginAMDGPU.cpp to maintain dual connection functionality - Combined latest GPU plugin improvements with DAP dual connection features
2c5f647
to
53b2074
Compare
Summary
This PR adds support for dual connection debugging with LLDB-DAP using a shared debugger and event thread between DAP instances.
Target
eventeBroadcastBitNewTargetSpawned
. This is sent fromProcessGDBRemote
when it creates and connects a new GPU target. InDAP::EventThread
, we create a reversestartDebugging
request, and send it an attach configuration withattachCommands
totarget select {x}
where x is the GPU target ID, to set the GPU DAP instance's target correctly.std::shared_ptr<std::thread> event_thread_sp
;lldb-dap.cpp
to use a staticDapSessionManager
. This holds and manages the lifetimes of the shared debugger and active DAP instances, rather than creating and storing local instances inlldb-dap.cpp
. Instead of creating a new EventThread per DAP instance, we set theevent_thread_sp
to the shared pointer to a single running EventThread.std::optional<lldb::SBDebugger> shared_debugger_
to a shared debugger. When a target is spawned for the GPU case, we will set this shared debugger, and set the debugger to the shared debugger instance in theAttachRequestHandler
. Otherwise, the shared debugger is not set, so we maintain backwards compatibility.std::map<lldb::user_id_t, std::shared_ptr<std::thread>> debugger_event_threads_
. This maps debugger IDs to their shared event threads. If two DAP instances share a debugger, this allows us to return the correct shared event thread inGetEventThreadForDebugger
. BC is still maintained, since if there's no shared debugger, we will create a new event thread like before.DAPSessionManager::FindDAPForTarget
.InitializeRequestHandler
andAttachRequestHandler
. Rather than creating a new debugger inInitializeRequestHandler
, we use the global shared debugger, and rather than creating a new target during the AttachRequestHandler, we already have the GPU target, so we just use these instances instead, and set the attributes of the DAP instance accordingly.Testing
Cherry-picked the changes on top of upstream LLVM, and DAP unit tests still pass.
Wrote a couple unit tests to test that
dap-server
to simulate behavior from VSCode to initialize and launch a new debug session upon receiving "startDebugging". Tested that in server mode, this works.Did some manual testing on VSCode. We can hit breakpoints, continue, switch between the target sessions and send commands correctly.
