)]}'
{
  "commit": "d8bbae6ea080249c05ca90b1f8640fde48a18301",
  "tree": "963f15c025a7bed3d81f18787ae3558bf19b89b5",
  "parents": [
    "05d65a7a6c888a0b80a7591a72059fec520c6a2e"
  ],
  "author": {
    "name": "Simon Marchi",
    "email": "simon.marchi@efficios.com",
    "time": "Fri Jan 14 15:40:59 2022 -0500"
  },
  "committer": {
    "name": "Simon Marchi",
    "email": "simon.marchi@polymtl.ca",
    "time": "Mon Apr 04 22:11:51 2022 -0400"
  },
  "message": "gdb: fix handling of vfork by multi-threaded program (follow-fork-mode\u003dparent, detach-on-fork\u003don)\n\nThere is a problem with how GDB handles a vfork happening in a\nmulti-threaded program.  This problem was reported to me by somebody not\nusing vfork directly, but using system(3) in a multi-threaded program,\nwhich may be implemented using vfork.\n\nThis patch only deals about the follow-fork-mode\u003dparent,\ndetach-on-fork\u003don case, because it would be too much to chew at once to\nfix the bugs in the other cases as well (I tried).\n\nThe problem\n-----------\n\nWhen a program vforks, the parent thread is suspended by the kernel\nuntil the child process exits or execs.  Specifically, in a\nmulti-threaded program, only the thread that called vfork is suspended,\nother threads keep running freely. This is documented in the vfork(2)\nman page (\"Caveats\" section).\n\nLet\u0027s suppose GDB is handling a vfork and the user\u0027s desire is to detach\nfrom the child. Before detaching the child, GDB must remove the software\nbreakpoints inserted in the shared parent/child address space, in case\nthere\u0027s a breakpoint in the path the child is going to take before\nexec\u0027ing or exit\u0027ing (unlikely, but possible). Otherwise the child could\nhit a breakpoint instruction while running outside the control of GDB,\nwhich would make it crash.  GDB must also avoid re-inserting breakpoints\nin the parent as long as it didn\u0027t receive the \"vfork done\" event (that\nis, when the child has exited or execed): since the address space is\nshared with the child, that would re-insert breakpoints in the child\nprocess also. So what GDB does is:\n\n  1. Receive \"vfork\" event for the parent\n  2. Remove breakpoints from the (shared) address space and set\n     program_space::breakpoints_not_allowed to avoid re-inserting them\n  3. Detach from the child thread\n  4. Resume the parent\n  5. Wait for and receive \"vfork done\" event for the parent\n  6. Clean program_space::breakpoints_not_allowed and re-insert\n     breakpoints\n  7. Resume the parent\n\nResuming the parent at step 4 is necessary in order for the kernel to\nreport the \"vfork done\" event.  The kernel won\u0027t report a ptrace event\nfor a thread that is ptrace-stopped.  But the theory behind this is that\nbetween steps 4 and 5, the parent won\u0027t actually do any progress even\nthough it is ptrace-resumed, because the kernel keeps it suspended,\nwaiting for the child to exec or exit.  So it doesn\u0027t matter for that\nthread if breakpoints are not inserted.\n\nThe problem is when the program is multi-threaded.  In step 4, GDB\nresumes all threads of the parent. The thread that did the vfork stays\nsuspended by the kernel, so that\u0027s fine. But other threads are running\nfreely while breakpoints are removed, which is a problem because they\ncould miss a breakpoint that they should have hit.\n\nThe problem is present with all-stop and non-stop targets.  The only\ndifference is that with an all-stop targets, the other threads are\nstopped by the target when it reports the vfork event and are resumed by\nthe target when GDB resumes the parent.  With a non-stop target, the\nother threads are simply never stopped.\n\nThe fix\n-------\n\nThere many combinations of settings to consider (all-stop/non-stop,\ntarget-non-stop on/off, follow-fork-mode parent/child, detach-on-fork\non/off, schedule-multiple on/off), but for this patch I restrict the\nscope to follow-fork-mode\u003dparent, detach-on-fork\u003don.  That\u0027s the\n\"default\" case, where we detach the child and keep debugging the\nparent.  I tried to fix them all, but it\u0027s just too much to do at once.\nThe code paths and behaviors for when we don\u0027t detach the child are\ncompletely different.\n\nThe guiding principle for this patch is that all threads of the vforking\ninferior should be stopped as long as breakpoints are removed.  This is\nsimilar to handling in-line step-overs, in a way.\n\nFor non-stop targets (the default on Linux native), this is what\nhappens:\n\n - In follow_fork, we call stop_all_threads to stop all threads of the\n   inferior\n - In follow_fork_inferior, we record the vfork parent thread in\n   inferior::thread_waiting_for_vfork_done\n - Back in handle_inferior_event, we call keep_going, which resumes only\n   the event thread (this is already the case, with a non-stop target).\n   This is the thread that will be waiting for vfork-done.\n - When we get the vfork-done event, we go in the (new) handle_vfork_done\n   function to restart the previously stopped threads.\n\nIn the same scenario, but with an all-stop target:\n\n - In follow_fork, no need to stop all threads of the inferior, the\n   target has stopped all threads of all its inferiors before returning\n   the event.\n - In follow_fork_inferior, we record the vfork parent thread in\n   inferior::thread_waiting_for_vfork_done.\n - Back in handle_inferior_event, we also call keep_going.  However, we\n   only want to resume the event thread here, not all inferior threads.\n   In internal_resume_ptid (called by resume_1), we therefore now check\n   whether one of the inferiors we are about to resume has\n   thread_waiting_for_vfork_done set.  If so, we only resume that\n   thread.\n\n   Note that when resuming multiple inferiors, one vforking and one not\n   non-vforking, we could resume the vforking thread from the vforking\n   inferior plus all threads from the non-vforking inferior.  However,\n   this is not implemented, it would require more work.\n - When we get the vfork-done event, the existing call to keep_going\n   naturally resumes all threads.\n\nTesting-wise, add a test that tries to make the main thread hit a\nbreakpoint while a secondary thread calls vfork.  Without the fix, the\nmain thread keeps going while breakpoints are removed, resulting in a\nmissed breakpoint and the program exiting.\n\nChange-Id: I20eb78e17ca91f93c19c2b89a7e12c382ee814a1\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "5c7192eef30447cf355c2177a506a50a4df1c82c",
      "old_mode": 33188,
      "old_path": "gdb/infrun.c",
      "new_id": "899b2ae3cd09c6e60860f164fb7fda8c43fc0e80",
      "new_mode": 33188,
      "new_path": "gdb/infrun.c"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "7e38f8a993921ba6876bb19502f635a1e590b560",
      "new_mode": 33188,
      "new_path": "gdb/testsuite/gdb.threads/vfork-multi-thread.c"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "d405411be012f5e8b911612345e3a652936173cb",
      "new_mode": 33188,
      "new_path": "gdb/testsuite/gdb.threads/vfork-multi-thread.exp"
    }
  ]
}
