Repository navigation
Rework process attaching #199
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: dev/0.8
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -293,7 +293,6 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| fn write_stop_common( | ||
| &mut self, | ||
| res: &mut ResponseWriter<'_, C>, | ||
| target: &mut T, | ||
| tid: Option<T::Tid>, | ||
| signal: Signal, | ||
| ) -> Result<(), Error<T::Error, C::Error>> { | ||
|
|
@@ -309,7 +308,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| pid: self | ||
| .features | ||
| .multiprocess() | ||
| .then_some(SpecificIdKind::WithId(self.get_current_pid(target)?)), | ||
| .then_some(SpecificIdKind::WithId(self.current_active_pid)), | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems wrong. this should be using the this ties into my other comment wrt. how current_active_pid shouldn't really be a "thing".
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hah nice catch! That looks like a remnant that's been there for awhile, but nothing exposed the bug because only one process at a time has been able to be attached to (and before the changes from last week, it was only ever passed a |
||
| tid: SpecificIdKind::WithId(tid.into_fully_qualified_tid()), | ||
| })?; | ||
| res.write_str(";")?; | ||
|
|
@@ -365,11 +364,10 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| pub(crate) fn finish_signal_with_thread( | ||
| &mut self, | ||
| res: &mut ResponseWriter<'_, C>, | ||
| target: &mut T, | ||
| tid: T::Tid, | ||
| sig: Signal, | ||
| ) -> Result<(), Error<T::Error, C::Error>> { | ||
| self.write_stop_common(res, target, Some(tid), sig)?; | ||
| self.write_stop_common(res, Some(tid), sig)?; | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -390,7 +388,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
|
|
||
| crate::__dead_code_marker!("sw_breakpoint", "stop_reason"); | ||
|
|
||
| self.write_stop_common(res, target, Some(tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?; | ||
| res.write_str("swbreak:;")?; | ||
| Ok(()) | ||
| } | ||
|
|
@@ -412,7 +410,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
|
|
||
| crate::__dead_code_marker!("hw_breakpoint", "stop_reason"); | ||
|
|
||
| self.write_stop_common(res, target, Some(tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?; | ||
| res.write_str("hwbreak:;")?; | ||
| Ok(()) | ||
| } | ||
|
|
@@ -436,7 +434,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
|
|
||
| crate::__dead_code_marker!("hw_watchpoint", "stop_reason"); | ||
|
|
||
| self.write_stop_common(res, target, Some(tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?; | ||
|
|
||
| res.write_str(match kind { | ||
| WatchKind::Write => "watch:", | ||
|
|
@@ -479,7 +477,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
|
|
||
| crate::__dead_code_marker!("reverse_exec", "stop_reason"); | ||
|
|
||
| self.write_stop_common(res, target, tid, Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, tid, Signal::SIGTRAP)?; | ||
|
|
||
| res.write_str("replaylog:")?; | ||
| res.write_str(match pos { | ||
|
|
@@ -505,7 +503,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
|
|
||
| crate::__dead_code_marker!("catch_syscall", "stop_reason"); | ||
|
|
||
| self.write_stop_common(res, target, tid, Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, tid, Signal::SIGTRAP)?; | ||
|
|
||
| res.write_str(match position { | ||
| CatchSyscallPosition::Entry => "syscall_entry:", | ||
|
|
@@ -520,10 +518,9 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| pub(crate) fn finish_library( | ||
| &mut self, | ||
| res: &mut ResponseWriter<'_, C>, | ||
| target: &mut T, | ||
| tid: T::Tid, | ||
| ) -> Result<(), Error<T::Error, C::Error>> { | ||
| self.write_stop_common(res, target, Some(tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?; | ||
| res.write_str("library:;")?; | ||
| Ok(()) | ||
| } | ||
|
|
@@ -541,13 +538,13 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| } | ||
|
|
||
| crate::__dead_code_marker!("fork_events", "stop_reason"); | ||
| self.write_stop_common(res, target, Some(cur_tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(cur_tid), Signal::SIGTRAP)?; | ||
| res.write_str("fork:")?; | ||
| res.write_specific_thread_id(SpecificThreadId { | ||
| pid: self | ||
| .features | ||
| .multiprocess() | ||
| .then_some(SpecificIdKind::WithId(self.get_current_pid(target)?)), | ||
| .then_some(SpecificIdKind::WithId(self.current_active_pid)), | ||
| tid: SpecificIdKind::WithId(new_tid.into_fully_qualified_tid()), | ||
| })?; | ||
| res.write_str(";")?; | ||
|
|
@@ -567,13 +564,13 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| } | ||
|
|
||
| crate::__dead_code_marker!("vfork_events", "stop_reason"); | ||
| self.write_stop_common(res, target, Some(cur_tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(cur_tid), Signal::SIGTRAP)?; | ||
| res.write_str("vfork:")?; | ||
| res.write_specific_thread_id(SpecificThreadId { | ||
| pid: self | ||
| .features | ||
| .multiprocess() | ||
| .then_some(SpecificIdKind::WithId(self.get_current_pid(target)?)), | ||
| .then_some(SpecificIdKind::WithId(self.current_active_pid)), | ||
| tid: SpecificIdKind::WithId(new_tid.into_fully_qualified_tid()), | ||
| })?; | ||
| res.write_str(";")?; | ||
|
|
@@ -592,7 +589,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| } | ||
|
|
||
| crate::__dead_code_marker!("vforkdone_events", "stop_reason"); | ||
| self.write_stop_common(res, target, Some(tid), Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, Some(tid), Signal::SIGTRAP)?; | ||
| res.write_str("vforkdone:;")?; | ||
| Ok(()) | ||
| } | ||
|
|
@@ -609,7 +606,7 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> { | |
| } | ||
|
|
||
| crate::__dead_code_marker!("vforkdone_events", "stop_reason"); | ||
| self.write_stop_common(res, target, None, Signal::SIGTRAP)?; | ||
| self.write_stop_common(res, None, Signal::SIGTRAP)?; | ||
| res.write_str("exec:")?; | ||
| res.write_hex_buf(path)?; | ||
| res.write_str(";")?; | ||
|
|
||
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.
so, IIRC, there's not real notion of "active" thread-id in the GDB RSP. The only firm concepts are the currently "selected" thread-id for memory operations, and resume operations.
as such, adding a new type here doesn't seem right. rather, I think the
IsValidTidtrait will need to grow some new methods for generically setting / getting the pid component ofT::Tid.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 few places in the protocol that mention the notion of a "current thread":
qCRSP message is described as this (annoyingly, this is entire thing, and there is no elaboration on what "current" means anywhere else...I know I already complained about this, but it doesn't make it any less annoying)qXfer:exec-file:readincludes this:Unless I'm misreading these, it seems like there's a need to represent the current process/thread somewhere; would you rather close this PR and keep on representing it in the target implementations like they were before this PR?
I do worry that handling it in the targets could be potentially confusing for multiprocess target implementations, who also have to keep track of what the "current" PID is, along with all their "attached" PIDs; like what does it really mean to have a "current" thread when you're keeping track of 3 separate processes that are all running at the same time?
Extending the
IsValidTidtrait to return the pid component seems like it would have to interact with the stub somehow to get this current pid for multithreaded stubs, since the client can still attach to an arbitrary process; we can't just returnFAKE_PID...it would probably be more useful as a method of the stub, where theIsValidTidtrait would returnNoneif there was no pid component, and the stub could provide a current pid.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 GDB RSP docs definitely have some annoying ambiguities... sigh.
Inferring the correct behavior typically requires playing around with the code, and maybe cross-referencing with the upstream implementation in the GDB source code (i.e: in remote.c).
Well yes, of course. I'm not saying we shouldn't track it.
What I'm saying is that the correct place to track this info is in
current_{mem,resume}_tid, which can only be done by extendingT::Tidto properly trackpids.Or, in other words, to refine my initial comment - the terms "active" thread-id and "selected" thread-id are one and the same, and should be tracked via the same bit of storage.
Again, this all goes back to the fact that you really shouldn't be thinking about
pids andtids as separate concepts for the vast majority of the GDB RSP.Outside of a few narrow packets (e.g: attach, run), the only thing that ever really matters is a specific tuple of
(pid, tid)(AKA, what the GDB RSP calls athread-id). That's the thing that should be tracked viacurrent_{mem,resume}_tid, that's the thing that will be passed toTargetIDETs via thetid: T::Tidparam, etc...When you think about it that way, it should be very clear why a separate
current_active_pid: Pidtype doesn't really make sense. You're splitting out an indivisible aspect of thethread-iddata type into its own field.The notion of the selected/active thread-id is something that should never leak out of the
gdbstubimplementation itself. And indeed, if you look, you'll find thatqCdoesn't even correspond to an IDET.Indeed, not leaking "current active thread" semantics is is one of
gdbstubs major ergonomic wins over trying to roll your own stub code!From the target's perspective, there is no such thing as a 'Current PID'. The target is essentially just a set of stateless functions that say 'Read memory from PID X, Thread Y'. The only entity that needs to know which PID is 'current' is
gdbstub, so it knows which numbers to pass into those functions.It will only ever be asked to operate on specific thread-ids (i.e:
(pid, tid)tuples), or alltids in a singlepid(via some of the resume APIs). Sure, there are some APIs that will ask the target to do something related to a specificpid(i.e: attach), but it's only when the Target affirmatively responds to those packets (via a stop reason) thatgdbstubchecks to see what(pid, tid)is being reported as part of the stop response, and then updates its internal active-pid tracking.Or, another example (not related to attach): when the GDB client wants to switch contexts, it sends an
Hg(set general thread) orHc(set continue thread) packet.gdbstubintercepts these and exclusively uses them to update its internalcurrent_{mem,resume}_tidstate. The target implementation never sees theseHpackets directly, and it never needs to maintain a concept of a 'selected' thread.Instead, when a subsequent packet arrives that relies on this context (like a memory read
mor a register readg),gdbstubautomatically grabs that stored state and passes the fully-resolved(pid, tid)directly into the relevantTargettrait method. Keeping that boundary strict is exactly why we want to avoid leaking acurrent_active_pidor similar state into the target traits.Hopefully you can now see why this isn't really accurate.
IsValidTiddoesn't need to 'interact' with the stub to get the PID because the stub is the one that constructs theT::Tidand gives it to the trait in the first place. The trait simply acts as the storage container for that tuple.i.e: when the client attaches to an arbitrary process,
gdbstubwill pass that request through to the state machine, and when the request is acknowledged by the Target with a corresponding stop-reason on attach,gdbstubwill update its internal tracking to reflect the newly selected thread-id.Does this all make a bit more sense now? Do you see why
IsValidTidwill need to be tweaked to also trackpidhandling?wrt. handling the FAKE_PID codepaths - look at how I reworked
gdbstub's code in #198 to report an error in cases where a single-threaded target (i.e: whereT::Tid = ()) somehow runs into a situation where the GDB client is requesting something that isn'tSINGLE_THREAD_TID. That same style of handling should be easy to do wrt.FAKE_PIDwhenT::Tidis()orTidin the single/multi-thread use cases, while transparently converting to/from(tid, pid)in the new multi-process use-cases.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.
There was a lot in there; I was already familiar with how tids (and one day, pids) get to the target method, but just to make sure I understood the actionable parts of your response, it's basically:
current_active_pidfield of theGdbStubImpl(how this this PR does it) or theCurrentActivePidtrait (what v0.8 currently does), because it should be tracked incurrent_{mem,resume}_tidIsValidTidto get thepid, and it would look something like this:If I'm mischaracterizing this, please ignore everything after this line and correct me!
I enthusiastically agree that this covers almost everything in the protocol, but I don't see how this handles a response to a
qCpacket. The client can still attach to any pid in the multithreaded (or even single threaded) case, but if the only candidates we can store that attached pid are incurrent_mem_tid/current_resume_tid, there's literally nowhere the value of the pid can go. When we use the above implementation for the return value ofqC, we returnFAKE_PID, and the gdb client crashes after avAttachfollowed by aqC, with an assertion failure because the process portion ofqCdoesn't match the process it originally attached to (at least that's what versions 12.1, and IIRC 17.1, of the gdb client do).Maybe there's a different path that fixes all of these? Currently the stub unconditionally reports it supports multiprocess features:
https://github.com/daniel5151/gdbstub/blob/dev/0.8/src/stub/core_impl/base.rs#L115-L116
This forces us to return extended thread IDs with a process and a thread in stop replies and
qC. What if we only reply that we support multiprocess features in the (soon-to-exist) multiprocess mode? It helps us be more honest with the client about our actual capabilities, and then according to the protocol it's not supposed to even send us pids, or expect to receive pids from us. Then we don't have to worry about conjuring up some kind of pid (FAKE_PID...) to respond with for thread ID types that don't have one (like()orTid).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.
Almost!
You're right on 1, but for 2, what you actually want to do is change the
IsValidTidtrait itself to operate on this newExtendedThreadIdtype (which, should prob just be a(Pid, Tid)tuple, rather than a custom type).P.S: might be time to rename
SINGLE_THREAD_TIDtoFAKE_TIDfor consistency, hah. Similarly,IsValidTidcan prob be renamed toValidTid, since it's now far more than just a marker trait. neither of these needs to happen now - just jotting down the thoughts so I don't forget.I'm not totally following, so apologies if this response is us talking past eachother 😅
You are correct that there is a narrow window of time where
gdbstubneeds to use a "dummy" thread-id value internally, as it hasn't yet interacted with theTargetenough to ascertain the true thread-id it should be reporting. The inline comments inGdbStubImpl::newtalk about it briefly.That said, IIRC, the very first message any GDB client sends over is
?, which will trigger the attach path I talked about above, and would therefore givegdbstuba chance to update its internalcurrent_{mem,resume}_tidvalue from the stop reply issued by the user's integration.Similarly, if the GDB client were to start off with a vAttach to a specific PID, same logic applies - it would trigger the same attach path, we would sniff the current thread-if from the stop reply packet, and we'd be all set.
Essentially, my claim is that
qCis never sent prior to us having a chance to ascertain a specific thread-id to report from it.If this turns out to be false in practice / in some narrow edge case... we can always have
gdbstubartificially start its state machine into the attach state (instead of starting in theIdlestate) in order to force users to declare up-front what thread-id is currently attached (and in that artificial attach state, simply swallow the stop reason they report, as the GDB client wouldn't be expecting a stop reason packet at that point in the GDB RSP sequence). But this isn't something I think we need... since my claim is that all GDB clients are "reasonable" insofar as starting off each conversation with a?/vAttach/vRun, packet in order to understand what state the stub is in.your idea of only using multiprocess mode in, well, multi-process mode is intriguing, but comes with a major caveat: it means that single/multi-threaded targets would be excluded from extended mode facilities. See this comment in the docs https://docs.rs/gdbstub/latest/gdbstub/target/ext/extended_mode/trait.ExtendedMode.html#extended-mode-for-singlemulti-threaded-targets
But in any case, hopefully the explanation I offer above makes it a bit clearer why - in practice - things should work fine.
Uh oh!
There was an error while loading. Please reload this page.
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've been operating under the assumption extended mode support and multiprocess support are orthogonal(-ish?), where stubs can support extended mode without multiprocess. The client can attach to any process it wants, it just can't attach to more than one of them at the same time. The documentation seems careful about specifying when packets are only available in extended mode versus when packets are only available in multiprocess mode, and I haven't seen anything suggesting that extended mode only applies for multiprocess stubs. The gdb client source code also doesn't seem to check for multiprocess support when it sends an extended mode packet (
extended_remote_target::openingdb/remote.c).If you find that this isn't the case, please let me know!
Anyways, I agree that
qCwill always have a chance to snoop aTid; the issue is that we lose the pid forTidtypes that don't have a pid field. Please hear me out; I know there's already a lot written about how we don't need to care about the pid for 99% of the packets, and I agree: I'm saying that the response forqCis the 1% that does. Here's the exact scenario I'm worried about:For the gdb clients I've tested, when the gdb client attaches to a process
P, it sendsvAttach P,Hgp0.0,qC, and thenHg<thread returned by qC>. The biggest note here is that the gdb client will crash with an assertion failure if the pid returned byqCdoesn't match the pid passed byvAttach(akaP).And here's how it would play out based on how I understand what you're proposing:
vAttach Pmessage, and passesPto the target'sattachmethod.current_{mem,resume}_tidfields. The stop reply takes aTid(or()), the stop reply's pid value isFAKE_PIDafter fully qualifying it, and we've totally lost track ofPafter this point.Hgp0.0message, nowcurrent_mem_tidis an arbitrary thread. Not super relevant here.qCmessage. If it does not reply with a pid that isP, the gdb client will crash. When we fully qualifycurrent_{mem,resume}_tidin our response, we send backFAKE_PID, which is probably not equal toP. There's nothing else we can do; gdbstub lost track ofPin step 2! The gdb client crashes.As always, please let me know if I'm misunderstanding what you're proposing and how it fits in here!
This is also why I'm saying not advertising multiprocess support might be the fix here: we don't have to include a process field in the response to
qCanymore. We can reply with just aTidvalue, and gdb doesn't lose its mind when we don't keep track of information we shouldn't have to care about.I'm not sure what you mean about how this would break single/multi-threaded extended mode; the linked documentation seems to agree that we can have extended mode without needing to be multiprocess.
[0] I assume this is a future enhancement;
gdbstubdoesn't currently snoop the stop reply:vAttachand?usereport_reasonable_stop_reason, which doesn't callwrite_stop_common, which is where thecurrent_{mem,resume}_tidget set.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... that's what I get for drafting a response before the morning coffee has fully kicked in, hah.
Yes, multi process extensions are orthogonal to extended mode, whoops.
yes, all my comments are refer to a world post #194, where
report_reasonable_stop_reasonis in the rear-view mirror.Lets zoom in on these steps.
In a nutshell, here is my thesis wrt multi-process in 0.8: once proper multi-process support lands, any time the GDB client attempts to attach / interact with a PID that isn't FAKE_PID (i.e: 1),
gdbstubraises a runtime error (likely including some error text that nudges users towards implementing proper support for multi-process). This is similar to the current behavior in single-threaded mode, in cases where a non-1 tid gets sent/recv'd bygdbstub.And in this world, I see two ways this scenario you're describing could play out:
vAttach Pmessage with a stop reason that usesFAKE_PID... maybe the GDB client itself simply disregards thePit sent, and only cares that the effect of the vAttach (i.e: the stop reply packet) was that we attached to a process withpid = FAKE_PID?If the behavior is 2, I feel like we're totally in the clear with my thesis, since it sidesteps the crash scenario entirely. If the behavior is 1, that's less fun for the user... but it doesn't seem unreasonable that if they want to multi-process drift, they should support multi-process extensions?
One thing to note about the
multiprocess+feature:I bring this up because I'd be curious to understand how a stub would communicate to GDB that it only supports connecting to a single process at a time?
This ties into the question of enabling/disable this feature, as well as my thesis above... as this implies there is a class of targets that support multi-threaded debugging, and can switch between debugging a set of processes... but only one at a time. This is essentially the only kind of "multi-process" target that
gdbstubcurrently supports (via the various hax that have landed in 0.7).On one hand, it seems a bit sad to lose support for modeling these sorts of targets in multi-thread mode... but on the other hand, maybe it's fine if there's a "complexity jump" in
gdbstub's API if you need to support jumping between processes as a multi-thread target?But again, the crux of my question is how does such a target tell GDB / how does GDB infer that a target that supports
multiprocess+is a target that supports debugging multiple processes simultaneously, vs. one at a timeHere is one theory (which, full disclosure - the robot helped me draft):
GDB's architecture for multi-process debugging is entirely "try it and find out" (optimistic execution). It never infers the stub's capacity upfront because the protocol has no mechanism to communicate "I am a simultaneous target" vs "I am a one-at-a-time target".
Here is how the distinction actually plays out using that
remote.clogic:P1. GDB sendsvAttach;P1. The stub replies with a stop packet. Success.add-inferiorandattach P2. GDB sendsvAttach;P2.P2and sends another stop packet. GDB is now debugging both.P1. GDB sendsvAttach;P1. The stub replies with a stop packet. Success.add-inferiorandattach P2. GDB sendsvAttach;P2.P1, it rejects the packet by returningE01.Enn, hits thedefault:case, triggers theerror()macro, and aborts the attach. It tells the user "Attaching to P2 failed."D(detach) orvKillforP1before trying to attach toP2, the stub would be "empty" again, and would accept thevAttach;P2request.So, maybe
gdbstubcan include some extra logic in single/multi-thread mode to enforce disconnect prior to re-attach?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.
Aha! Okay, I think that's the root cause of all this discussion! I was trying to keep the ability to attach to non-
FAKE_PIDprocesses in non-multiprocess targets, and it sounds like you're okay with dropping it.Nice catch on the fact that
multiprocessis just syntactic and means that the client/stub can send process IDs back and forth. Maybe this is still a compelling reason to not advertisemultiprocess+though? If the intention is to ignore any thread ID withpid != FAKE_PID, then telling the client "don't bother sending us a PID, we're only using the TID anyways" helps keep the client from sending us messages formatted in a way we don't care about...which ironically would actually help preserve the behavior of being able to attach to arbitrary processes.Uh oh!
There was an error while loading. Please reload this page.
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.
Yes. Well... mostly yes.
All the current code related to overriding FAKE_PID is only really there as a transient hack until proper multi-process support can land. I suspect that things "work" today in a pretty jank sense, and it was never totally clear to me what the existing semantics are / what they should be. It worked "well enough" in #129, and that's about it, hah.
So, while thinking about this problem (multi process, attach semantics, etc...), I strongly suggest imagining a codebase before #129.
Now, with that mindset, the question at hand - how to handle attaching?
Honestly, the more I think about it... the more I think that we should just straight up disallow vAttach when running in single/multi-threaded mode, eh? I think it just complicates the problem space way too much.
If someone wants to multi-process drift, they should go ahead and implement the multi-process handlers - simple as that. And whether or not they support attaching to multiple processes simultaneously or not is up to them (and the optionality of supporting simultaneous process debugging can be pointed out in the docs).
That said, I do want to preserve support for vRun when running in single/multi-threaded mode, as its super useful for swapping out the currently running code in, say, emulation contexts. I think that's more tractable, since I think the GDB client understands that the current process has been "swapped out" when you respond with a stop reason that has the same FAKE_PID?
To achieve these new attach / run semantics, we'll need to execute on that ExtendedMode trait rework that I hinted at a while back (recall that it was designed a long time ago, and isn't really aligned with modern
gdbstubAPI design wrt. how it bundles all those ops together), since the current organization really isn't cutting it.And honestly... now that I think about it... why does "extended mode" even need to be a thing that consumers are aware of?
Concepts of attach, run, etc... can all just be modeled as their own IDETs that hang directly off of
{Single,Multi}ThreadBase/MultiProcessBase, andgdbstubcan simply infer how to respond to the!packet based on whether the user has implemented any of those IDETs.Yeah... it really feels like the
ExtendedModetrait should just go away, and its API surface re-allocated across the*Basetraits appropriately. No reason to leak this RSP-ism to end users, right?I'm open to the idea!
Forcing the feature on was a choice I made a loooooong time ago, and if I had to guess why, it was likely related to future proofing / improving client compatibility... but honestly, I don't totally know.
I do know that at some point,
multiprocess+feature negation was added (bfe83e1) to work around WinDbg being a really dumb GDB RSP client (at the time), but I wager most targets still wantmultiprocess+extensions.Indeed, I'd be interested to see how the changes we're making affect LLDB. The stance on LLDB compat is fuzzy (see #99), but the tl;dr is that I certainly don't want to break LLDB (especially since we just landed some juicy LLDB-only WASM extensions, hah).
This doesn't really apply if we just straight up say "don't even expose
vAttachin multi-threaded mode", but assuming you think that's a bad idea on my part, I'd be interested to see what the GDB client does if you forcemultiprocess+off and then try doing some multi-process shenanigans withvAttach.