This is an archive of the discontinued LLVM Phabricator instance.

Add the ability to run expressions that call fork() or vfork().
Needs RevisionPublic

Authored by clayborg on Jul 11 2022, 3:49 PM.

Details

Summary

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.

Diff Detail

Event Timeline

clayborg created this revision.Jul 11 2022, 3:49 PM
Herald added a project: Restricted Project. · View Herald TranscriptJul 11 2022, 3:49 PM
clayborg requested review of this revision.Jul 11 2022, 3:49 PM
Herald added a project: Restricted Project. · View Herald TranscriptJul 11 2022, 3:49 PM

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?

labath added a subscriber: mgorny.Jul 12 2022, 1:17 AM

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 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?

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.

jingham requested changes to this revision.Jul 14 2022, 5:45 PM
This revision now requires changes to proceed.Jul 14 2022, 5:45 PM

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.

Ping. I would love to get this in if possible.