Before this fix if you tried to call fork for vfork, the expression would stop when it received the eStopReasonFork or eStopReasonVFork in the thread plan that runs the expression. This is now fixed and I have added a test case.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Change looks good to me. I will let @jingham take a second look before accepting.
I do wonder if there will be more unexpected signals (like SIGCHILD, SIGPIPE etc...) causing expression evaluation to pause? Should we maybe default to not stop on signals?
lldb/source/Target/ThreadPlanCallFunction.cpp | ||
---|---|---|
380 | Undo this accidental change? |
The general idea makes sense, as we don't stop for forks even during normal execution. I'll too defer to @jingham on the implementation.
I am also wondering what is the current and expected behavior in the follow-fork=child mode. The user probably did not intend to follow the expression into the child process (and the thread plan will definitely get completely confused), but I'm also not sure that overriding the user setting is the right thing to do.
I think that's a different (and more complicated) discussion, and probably not relevant here. The default disposition of SIGCHLD is already set to no-stop, and if the user deliberately changed it, I wouldn't want to override it.
lldb/test/API/commands/expression/fork/TestForkExprs.py | ||
---|---|---|
17–19 | unused code. | |
21 | Technically, I think this should pass on all systems that have the fork function (i.e. they are not windows), even if we don't support fork tracking there. |
First off, I think making "follows-fork=child" work in expressions will be pretty tricky at present, since lldb can only control either the parent or the child. But you have to make sure the parent side of the expression completes, so you can't let go of the parent right away. You'll have to let the parent run a bit more before you can switch over to the child. You either need to suspend the child while doing that (which may cause the parent side of the forked expression not to complete) or deal with the fact that the child might not survive the expression, and you're left with nothing to switch over to. That seems messy.
If we get to the point where instead of replacing the parent with the child we create another Target/Process for the child, then dealing with the fork would be pretty straightforward. You would temporarily force "follows-fork=both". The function call plan would not explain the fork stop, the event would proceed to whoever does the fork following, it would create an new Target/Process for the child, and then letting both run will complete the expression with a live child to cooperate in that process. Then if the follows-fork mode was actually "child" you would just discard the first Target/Process, and switch to the new one.
So I'd defer the feature of "follows-fork=child in expressions" till we are doing it that way, and for now just document that follows-fork does NOT pertain to forks created by running expressions.
If that seems right, then for now you want to just ensure that the vfork is allowed to continue while lldb follows the parent. In that case, this isn't quite the right way to do it. If all you do is to say that the function call plan doesn't explain the stop, then everybody else above you on the ThreadPlanStack gets a whack at the stop event. Maybe one of them is waiting for a vfork stop for some other reason so they would react to this stop, which isn't, I think, what you wanted. You actually want the expression to claim responsibility for the vfork, but also force the process to continue.
This situation is pretty much the same as for breakpoints when we're ignoring breakpoint hits in expressions. The way that works is that DoPlanExplainsStop says it does explain the stop, but then it overrides the real stop info's "should stop" to force it to auto-continue. In the breakpoint case this is in the if (m_ignore_breakpoints) branch around line 315:
if (m_ignore_breakpoints) { LLDB_LOGF(log, "ThreadPlanCallFunction::PlanExplainsStop: we are ignoring " "breakpoints, overriding breakpoint stop info ShouldStop, " "returning true"); m_real_stop_info_sp->OverrideShouldStop(false); return true; }
That way nobody else gets a look at the stop, which is I'm pretty sure what you want here, but when we get to deciding what to do in the ShouldStop negotiation, we'll continue because we forced the relevant StopInfo to do that.
lldb/source/Target/ThreadPlanCallFunction.cpp | ||
---|---|---|
272 | This is a bit silly, but "not done" sounds more to me like you didn't do PlanExplainsStop - which isn't true and would be weird - than that it is returning false. Maybe just "returning false for fork stop reasons" would be better. |
If that seems right, then for now you want to just ensure that the vfork is allowed to continue while lldb follows the parent. In that case, this isn't quite the right way to do it. If all you do is to say that the function call plan doesn't explain the stop, then everybody else above you on the ThreadPlanStack gets a whack at the stop event. Maybe one of them is waiting for a vfork stop for some other reason so they would react to this stop, which isn't, I think, what you wanted. You actually want the expression to claim responsibility for the vfork, but also force the process to continue.
Actually we do need all of the stuff above us to get a chance to handle this because it needs to respond and detach from the child process. See more below
This situation is pretty much the same as for breakpoints when we're ignoring breakpoint hits in expressions. The way that works is that DoPlanExplainsStop says it does explain the stop, but then it overrides the real stop info's "should stop" to force it to auto-continue. In the breakpoint case this is in the if (m_ignore_breakpoints) branch around line 315:
if (m_ignore_breakpoints) { LLDB_LOGF(log, "ThreadPlanCallFunction::PlanExplainsStop: we are ignoring " "breakpoints, overriding breakpoint stop info ShouldStop, " "returning true"); m_real_stop_info_sp->OverrideShouldStop(false); return true; }That way nobody else gets a look at the stop, which is I'm pretty sure what you want here, but when we get to deciding what to do in the ShouldStop negotiation, we'll continue because we forced the relevant StopInfo to do that.
I tried doing this and it stops everything from working. Fork handling needs to detach from the child process. I tried doing what you suggested above, but then none of that fork machinery gets invoked correctly and things go really south quickly.
This is a bit silly, but "not done" sounds more to me like you didn't do PlanExplainsStop - which isn't true and would be weird - than that it is returning false. Maybe just "returning false for fork stop reasons" would be better.