Skip to content

Fix lldb crash while handling concurrent vfork() #81564

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

Merged
merged 8 commits into from
Mar 6, 2024

Conversation

jeffreytan81
Copy link
Contributor

@jeffreytan81 jeffreytan81 commented Feb 13, 2024

We got user reporting lldb crash while the debuggee is calling vfork() concurrently from multiple threads.
The crash happens because the current implementation can only handle single vfork, vforkdone protocol transaction.

This diff fixes the crash by lldb-server storing forked debuggee's <pid, tid> pair in jstopinfo which will be decoded by lldb client to create StopInfoVFork for follow parent/child policy. Each StopInfoVFork will later have a corresponding vforkdone packet. So the patch also changes the m_vfork_in_progress to be reference counting based.

Two new test cases are added which crash/assert without the changes in this patch.

Copy link

github-actions bot commented Feb 13, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@jeffreytan81 jeffreytan81 marked this pull request as ready for review February 13, 2024 19:59
@llvmbot llvmbot added the lldb label Feb 13, 2024
@llvmbot
Copy link
Member

llvmbot commented Feb 13, 2024

@llvm/pr-subscribers-lldb

Author: None (jeffreytan81)

Changes

We got user reporting lldb crash while the debuggee is calling vfork() concurrently from multiple threads.
The crash happens because the current implementation can only handle single vfork, vforkdone protocol transaction.

This diff fixes the crash by lldb-server storing forked debuggee's <pid, tid> pair in jstopinfo which will be decoded by lldb client to create StopInfoVFork for follow parent/child policy. Each StopInfoVFork will later have a corresponding vforkdone packet. So the patch also changes the m_vfork_in_progress to be reference counting based.

Two new test cases are added which crash/assert without the changes in this patch.


Full diff: https://github.com/llvm/llvm-project/pull/81564.diff

6 Files Affected:

  • (modified) lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp (+11-1)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp (+15-9)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h (+2-1)
  • (added) lldb/test/API/functionalities/fork/concurrent_vfork/Makefile (+4)
  • (added) lldb/test/API/functionalities/fork/concurrent_vfork/TestConcurrentVFork.py (+31)
  • (added) lldb/test/API/functionalities/fork/concurrent_vfork/main.cpp (+45)
diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
index b62e9f643fa792..cf8a1e7d34392a 100644
--- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp
@@ -120,7 +120,7 @@ bool NativeThreadLinux::GetStopReason(ThreadStopInfo &stop_info,
   case eStateCrashed:
   case eStateExited:
   case eStateSuspended:
-  case eStateUnloaded:
+  case eStateUnloaded: {
     if (log)
       LogThreadStopInfo(*log, m_stop_info, "m_stop_info in thread:");
     stop_info = m_stop_info;
@@ -128,7 +128,17 @@ bool NativeThreadLinux::GetStopReason(ThreadStopInfo &stop_info,
     if (log)
       LogThreadStopInfo(*log, stop_info, "returned stop_info:");
 
+    // Include child process PID/TID for forks.
+    // Client expects "<fork_pid> <fork_tid>" format.
+    if (stop_info.reason == eStopReasonFork ||
+        stop_info.reason == eStopReasonVFork) {
+      description = std::to_string(stop_info.details.fork.child_pid);
+      description += " ";
+      description += std::to_string(stop_info.details.fork.child_tid);
+    }
+
     return true;
+  }
 
   case eStateInvalid:
   case eStateConnected:
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 629b191f3117aa..6fdb062e712c78 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -263,10 +263,9 @@ ProcessGDBRemote::ProcessGDBRemote(lldb::TargetSP target_sp,
       m_continue_C_tids(), m_continue_s_tids(), m_continue_S_tids(),
       m_max_memory_size(0), m_remote_stub_max_memory_size(0),
       m_addr_to_mmap_size(), m_thread_create_bp_sp(),
-      m_waiting_for_attach(false),
-      m_command_sp(), m_breakpoint_pc_offset(0),
+      m_waiting_for_attach(false), m_command_sp(), m_breakpoint_pc_offset(0),
       m_initial_tid(LLDB_INVALID_THREAD_ID), m_allow_flash_writes(false),
-      m_erased_flash_ranges(), m_vfork_in_progress(false) {
+      m_erased_flash_ranges(), m_vfork_in_progress(0) {
   m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit,
                                    "async thread should exit");
   m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue,
@@ -5615,8 +5614,12 @@ void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
 void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
   Log *log = GetLog(GDBRLog::Process);
 
-  assert(!m_vfork_in_progress);
-  m_vfork_in_progress = true;
+  LLDB_LOG(
+      log,
+      "ProcessGDBRemote::DidFork() called for child_pid: {0}, child_tid {1}",
+      child_pid, child_tid);
+  assert(m_vfork_in_progress >= 0);
+  ++m_vfork_in_progress;
 
   // Disable all software breakpoints for the duration of vfork.
   if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
@@ -5670,8 +5673,8 @@ void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
 }
 
 void ProcessGDBRemote::DidVForkDone() {
-  assert(m_vfork_in_progress);
-  m_vfork_in_progress = false;
+  --m_vfork_in_progress;
+  assert(m_vfork_in_progress >= 0);
 
   // Reenable all software breakpoints that were enabled before vfork.
   if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
@@ -5681,7 +5684,10 @@ void ProcessGDBRemote::DidVForkDone() {
 void ProcessGDBRemote::DidExec() {
   // If we are following children, vfork is finished by exec (rather than
   // vforkdone that is submitted for parent).
-  if (GetFollowForkMode() == eFollowChild)
-    m_vfork_in_progress = false;
+  if (GetFollowForkMode() == eFollowChild) {
+    if (m_vfork_in_progress > 0)
+      --m_vfork_in_progress;
+    assert(m_vfork_in_progress >= 0);
+  }
   Process::DidExec();
 }
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index c1ea1cc7905587..29ed770c1275ea 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -301,7 +301,8 @@ class ProcessGDBRemote : public Process,
   using FlashRange = FlashRangeVector::Entry;
   FlashRangeVector m_erased_flash_ranges;
 
-  bool m_vfork_in_progress;
+  // Number of vfork in process.
+  int m_vfork_in_progress;
 
   // Accessors
   bool IsRunning(lldb::StateType state) {
diff --git a/lldb/test/API/functionalities/fork/concurrent_vfork/Makefile b/lldb/test/API/functionalities/fork/concurrent_vfork/Makefile
new file mode 100644
index 00000000000000..c46619c6623481
--- /dev/null
+++ b/lldb/test/API/functionalities/fork/concurrent_vfork/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+ENABLE_THREADS := YES
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/fork/concurrent_vfork/TestConcurrentVFork.py b/lldb/test/API/functionalities/fork/concurrent_vfork/TestConcurrentVFork.py
new file mode 100644
index 00000000000000..fcd26d6f936850
--- /dev/null
+++ b/lldb/test/API/functionalities/fork/concurrent_vfork/TestConcurrentVFork.py
@@ -0,0 +1,31 @@
+"""
+Make sure that the concurrent vfork() from multiple threads works correctly.
+"""
+
+
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+
+
+class TestConcurrentVFork(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipIfWindows
+    def test_vfork_follow_parent(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp")
+        )
+        self.runCmd("settings set target.process.follow-fork-mode parent")
+        self.expect("continue", substrs=["exited with status = 0"])
+
+    @skipIfWindows
+    def test_vfork_follow_child(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp")
+        )
+        self.runCmd("settings set target.process.follow-fork-mode child")
+        self.expect("continue", substrs=["exited with status = 0"])
diff --git a/lldb/test/API/functionalities/fork/concurrent_vfork/main.cpp b/lldb/test/API/functionalities/fork/concurrent_vfork/main.cpp
new file mode 100644
index 00000000000000..1b75852c3164d0
--- /dev/null
+++ b/lldb/test/API/functionalities/fork/concurrent_vfork/main.cpp
@@ -0,0 +1,45 @@
+#include <iostream>
+#include <thread>
+#include <unistd.h>
+#include <vector>
+
+int call_vfork() {
+  printf("Before vfork\n");
+
+  pid_t child_pid = vfork();
+
+  if (child_pid == -1) {
+    // Error handling
+    perror("vfork");
+    return 1;
+  } else if (child_pid == 0) {
+    // This code is executed by the child process
+    printf("Child process\n");
+    _exit(0); // Exit the child process
+  } else {
+    // This code is executed by the parent process
+    printf("Parent process\n");
+  }
+
+  printf("After vfork\n");
+  return 0;
+}
+
+void worker_thread() { call_vfork(); }
+
+void create_threads(int num_threads) {
+  std::vector<std::thread> threads;
+  for (int i = 0; i < num_threads; ++i) {
+    threads.emplace_back(std::thread(worker_thread));
+  }
+  printf("Created %d threads, joining...\n",
+         num_threads); // end_of_create_threads
+  for (auto &thread : threads) {
+    thread.join();
+  }
+}
+
+int main() {
+  int num_threads = 5; // break here
+  create_threads(num_threads);
+}

Comment on lines 135 to 137
description = std::to_string(stop_info.details.fork.child_pid);
description += " ";
description += std::to_string(stop_info.details.fork.child_tid);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding it here and not changing the original "m_stop_description" to contain it? We want everyone to get all of the information.

If this is a human readable string, we should probably print out something like:

child-pid = 12345, child-tid = 345645

instead of two magic numbers separate by a space.

Or is this output used in the GDB remote packets somehow? Don't we need to add the child pid and tid to the JSON stop info for the other threads in a key/value pair encoding?

Comment on lines 5676 to 5677
--m_vfork_in_progress;
assert(m_vfork_in_progress >= 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert(m_vfork_in_progress > 0);
 --m_vfork_in_progress;

if (GetFollowForkMode() == eFollowChild) {
if (m_vfork_in_progress > 0)
--m_vfork_in_progress;
assert(m_vfork_in_progress >= 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert does nothing as you won't decrement it if it is not > 0. Remove

@@ -301,7 +301,8 @@ class ProcessGDBRemote : public Process,
using FlashRange = FlashRangeVector::Entry;
FlashRangeVector m_erased_flash_ranges;

bool m_vfork_in_progress;
// Number of vfork in process.
int m_vfork_in_progress;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want a number that can go below zero? Doesn't make sense. I would store this as a uint32_t and never let it get decremented if it is zero.

NO_DEBUG_INFO_TESTCASE = True

@skipIfWindows
def test_vfork_follow_parent(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document what you are testing for here a bit more? I know what you are doing, but others might not.

self, "// break here", lldb.SBFileSpec("main.cpp")
)
self.runCmd("settings set target.process.follow-fork-mode parent")
self.expect("continue", substrs=["exited with status = 0"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check for a output from our process that matches "Parent process" to verify it worked as expected?

self, "// break here", lldb.SBFileSpec("main.cpp")
)
self.runCmd("settings set target.process.follow-fork-mode child")
self.expect("continue", substrs=["exited with status = 0"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check for a output from our process that matches "Child process" to verify it worked as expected?

#include <thread>
#include <unistd.h>
#include <vector>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a good idea to create a global std::vector<pid_t> and a mutex to protect it. And any child processes that get forked get added to this vector. So maybe add:

std::mutex g_child_pids_mutex;
std::vector<pid_t> g_child_pids;

I will comment below where to use it.

_exit(0); // Exit the child process
} else {
// This code is executed by the parent process
printf("Parent process\n");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the pid to the global vector:

printf("Parent process\n");
std::lock_guard<std::mutex> Lock(g_child_pids_mutex);
g_child_pids.append(child_pid);

int main() {
int num_threads = 5; // break here
create_threads(num_threads);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now reap the children you spawned:

std::lock_guard<std::mutex> Lock(g_child_pids_mutex);
for (pid_t child_pid: g_child_pids) {
  int child_status = 0;
  pid_t pid = waitpid(child_pid, &child_status, 0);
  if (child_status != 0)
    _exit(1); // This will let our program know that some child processes didn't exist with a zero exit status.
  if (pid !=  child_pid)
    _exit(2); // This will let our program know it didn't succeed
}

This will ensure that if we stayed with the parent, then we successfully resumed all of the child processes and that they didn't crash and that they exited. If we don't resume the child processes correctly, they can get caught in limbo if they weren't resumed and this test will deadlock waiting for the child process to exit. There are some options you can do that allows you to not hang where instead of zero passed as the last parameter of waitpid(), you can specify WNOHANG, but that will get racy. We would poll for a few seconds. We will want to make sure that the test doesn't just deadlock and cause the test suite to never complete.

Copy link

github-actions bot commented Feb 14, 2024

✅ With the latest revision this PR passed the Python code formatter.

@@ -301,7 +301,8 @@ class ProcessGDBRemote : public Process,
using FlashRange = FlashRangeVector::Entry;
FlashRangeVector m_erased_flash_ranges;

bool m_vfork_in_progress;
// Number of vfork in process.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Number of fork() or vfork() operations being handled.

Copy link
Contributor Author

@jeffreytan81 jeffreytan81 Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used by vfork() not fork() though.

Comment on lines 127 to 134
// Include child process PID/TID for forks.
// Client expects "<fork_pid> <fork_tid>" format for parsing.
if (stop_info.reason == eStopReasonFork ||
stop_info.reason == eStopReasonVFork) {
m_stop_description = std::to_string(stop_info.details.fork.child_pid);
m_stop_description += " ";
m_stop_description += std::to_string(stop_info.details.fork.child_tid);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be doing this work in:

void NativeThreadLinux::SetStoppedByFork(bool is_vfork, lldb::pid_t child_pid);

log,
"ProcessGDBRemote::DidFork() called for child_pid: {0}, child_tid {1}",
child_pid, child_tid);
assert(m_vfork_in_progress >= 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this assert now that m_vfork_in_progress in unsigned, it doesn't do anything.

Comment on lines 5676 to 5678
assert(m_vfork_in_progress_count > 0);
if (m_vfork_in_progress_count > 0)
--m_vfork_in_progress_count;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decrement m_vfork_in_progress_count in two places, here and in ProcessGDBRemote::DidExec(). Before this wouldn't affect anything because it was a boolean, but I fear we will decrement this twice now. We probably need to add an exec() into our test case for this to ensure this assertion doesn't fire as I believe the assertion inside of ProcessGDBRemote::DidExec() will assert and crash.

Comment on lines 5689 to 5691
assert(m_vfork_in_progress_count > 0);
if (m_vfork_in_progress_count > 0)
--m_vfork_in_progress_count;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to verify this is needed here. Usually someone does a fork() or vfork() and then they do an exec*() call (there are many different flavors of exec:

int execl(const char *path, const char *arg0, ..., /*, (char *)0, */);
int execle(const char *path, const char *arg0, ..., /* (char *)0 char *const envp[] */);
int execlp(const char *file, const char *arg0, ..., /*, (char *)0, */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvP(const char *file, const char *search_path, char *const argv[]);

So we need to verify in our test case that if we do a fork() + exec*() or vfork() + exec*() call that we don't run both ProcessGDBRemote::DidVForkDone() and ProcessGDBRemote::DidExec() because if we do this assertion will fire.

std::mutex g_child_pids_mutex;
std::vector<pid_t> g_child_pids;

int call_vfork(int index) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to test with both fork() and vfork() here and we want to know if we want to call exec*() or not. If we add a const char *exec_path to the call and this is not NULL, then we call exec. So maybe change this to be:

int call_vfork(int index, bool use_vfork, const char *exec_path)

std::vector<pid_t> g_child_pids;

int call_vfork(int index) {
pid_t child_pid = vfork();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use either fork() of vfork() depending on the value of the use_vfork argument:

pid_t child_pid = use_vfork ? vfork() : fork();

int main() {
g_pid = getpid();
printf("Entering main() pid: %d\n", g_pid);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse args manually to check if we should use fork or vfork and if we should exec:

bool use_vfork = true;
bool call_exec = false;
for (int i=1; i<argc; ++i) {
  if (strcmp(argv[i], "--fork") == 0)
    use_vfork = false;
  else if (strcmp(argv[i], "--exec") == 0)
    call_exec = true;
}

printf("Entering main() pid: %d\n", g_pid);

int num_threads = 5; // break here
create_threads(num_threads);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass use_vfork and exec_path into this function so we can pass it to int call_vfork(int index, bool use_vfork, const char *exec_path):

create_threads(num_threads, use_vfork, call_exec ? argv[0] : nullptr);

return target.FindFirstGlobalVariable("g_pid").GetValueAsUnsigned()

@skipIfWindows
def test_vfork_follow_parent(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to test this with exec and without exec to make sure things work without asserts firing from decrementing the fork count. So this function should be:

def vfork_follow_parent(self, call_exec):

Then we wall this twice:

def test_vfork_follow_parent_no_exec(self):
    return self.vfork_follow_parent(False);

def test_vfork_follow_parent_with_exec(self):
    return self.vfork_follow_parent(True);

And follow-parent successfully detach all child processes and exit debugger.
"""

self.build()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add arguments to this to specify if we are calling vfork or fork and if we are calling exec.

We should make a helper function that calls lldbutil.run_to_source_breakpoint so it can be used correctly with the right arguemnts getting to the program we launch:

def run_to_breakpoint(self, use_fork, call_exec):
    args = [self.getBuildArtifact("a.out")]
    if  use_fork:
        args.append("--fork");
    if call_exec:
        args.append("--exec");
    launch_info = lldb.SBLaunchInfo(args)
    launch_info.SetWorkingDirectory(self.getBuildDir())
    lldbutil.run_to_source_breakpoint(
        self, "// break here", lldb.SBFileSpec("main.cpp"), launch_info=launch_info
    )

Then we can call this function from each of the variations we need:

def test_vfork_follow_parent_no_exec(self):
def test_vfork_follow_parent_with_exec(self):
def test_vfork_follow_child_no_exec(self):
def test_vfork_follow_child_with_exec(self):

Comment on lines 27 to 29
lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp")
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will become:

use_fork = False  # Call `vfork()`
call_exec = False  # Don't call `exec()`
self.run_to_breakpoint(use_fork, call_exec);

And all of the other variations will need to set use_fork and call_exec correctly.

launch_info = lldb.SBLaunchInfo(args)
launch_info.SetWorkingDirectory(self.getBuildDir())

lldbutil.run_to_source_breakpoint(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get the target, process, thread and breakpoint from the return values:

(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(

NO_DEBUG_INFO_TESTCASE = True

def get_pid_from_variable(self):
target = self.dbg.GetTargetAtIndex(0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use this, the target should be returned from the call to lldbutil.run_to_source_breakpoint. I put example code in an inline comment below. So pass the target into here. I don't think we can rely on the debugger having only one target.


lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp")
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return all of the info from lldbutil.run_to_source_breakpoint from this function:

return (target, process, thread, bkpt)

)

def follow_parent_helper(self, use_fork, call_exec):
self.build_run_to_breakpoint(use_fork, call_exec)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(target, process, thread, bkpt) = self.build_run_to_breakpoint(use_fork, call_exec)

def follow_parent_helper(self, use_fork, call_exec):
self.build_run_to_breakpoint(use_fork, call_exec)

parent_pid = self.get_pid_from_variable()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass target into this function:

parent_pid = self.get_pid_from_variable(target)

)

def follow_child_helper(self, use_fork, call_exec):
self.build_run_to_breakpoint(use_fork, call_exec)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(target, process, thread, bkpt) = self.build_run_to_breakpoint(use_fork, call_exec)

Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One little nit you can fix as suggested in the inline comments.

use_fork, call_exec
)

parent_pid = self.get_pid_from_variable(target)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one caller to this function, inline the code:

parent_pid = target.FindFirstGlobalVariable("g_pid").GetValueAsUnsigned()

Comment on lines 15 to 16
def get_pid_from_variable(self, target):
return target.FindFirstGlobalVariable("g_pid").GetValueAsUnsigned()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one caller to this, just inline the code as suggested below.

@jeffreytan81
Copy link
Contributor Author

The buildbot seems to complain about strcmp not available. Unfortunately I can't reproduce on my linux machine.
Guess draft patch #84224 in case anyone has access to build machine can reproduce and verify this.

jeffreytan81 added a commit that referenced this pull request Mar 7, 2024
The buildbot seems to complain about `strcmp` function not available in
the vfork patch (#81564):

https://lab.llvm.org/buildbot/#/builders/68/builds/70093/steps/6/logs/stdio

Unfortunately, I can't reproduce the failure on my linux machine so this
is a guessing fix. If anyone has a way to reproduce and very this fix,
please feel free to merge this change.

Co-authored-by: jeffreytan81 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants