[lldb/Reproducers] Intercept the FindProcesses API
authorJonas Devlieghere <jonas@devlieghere.com>
Fri, 13 Mar 2020 15:49:15 +0000 (08:49 -0700)
committerJonas Devlieghere <jonas@devlieghere.com>
Fri, 13 Mar 2020 16:31:35 +0000 (09:31 -0700)
This patch extends the reproducers to intercept calls to FindProcesses.
During capture it serializes the ProcessInstanceInfoList returned by the
API. During replay, it returns the serialized data instead of querying
the host.

The motivation for this patch is supporting the process attach workflow
during replay. Without this change it would incorrectly look for the
inferior on the host during replay and failing if no matching process
was found.

Differential revision: https://reviews.llvm.org/D75877

12 files changed:
lldb/include/lldb/Host/Host.h
lldb/include/lldb/Utility/ProcessInfo.h
lldb/source/Commands/CommandObjectReproducer.cpp
lldb/source/Host/common/Host.cpp
lldb/source/Host/linux/Host.cpp
lldb/source/Host/macosx/objcxx/Host.mm
lldb/source/Host/netbsd/Host.cpp
lldb/source/Host/openbsd/Host.cpp
lldb/source/Utility/ProcessInfo.cpp
lldb/test/API/functionalities/reproducers/attach/Makefile [new file with mode: 0644]
lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py [new file with mode: 0644]
lldb/test/API/functionalities/reproducers/attach/main.cpp [new file with mode: 0644]

index 269bb18..f19cb85 100644 (file)
@@ -232,6 +232,10 @@ public:
 
   static std::unique_ptr<Connection>
   CreateDefaultConnection(llvm::StringRef url);
+
+protected:
+  static uint32_t FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+                                    ProcessInstanceInfoList &proc_infos);
 };
 
 } // namespace lldb_private
index 0d631d0..ec91060 100644 (file)
@@ -14,6 +14,7 @@
 #include "lldb/Utility/Environment.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/NameMatches.h"
+#include "lldb/Utility/Reproducer.h"
 #include "llvm/Support/YAMLTraits.h"
 #include <vector>
 
@@ -215,6 +216,42 @@ protected:
   bool m_match_all_users;
 };
 
+namespace repro {
+class ProcessInfoRecorder : public AbstractRecorder {
+public:
+  ProcessInfoRecorder(const FileSpec &filename, std::error_code &ec)
+      : AbstractRecorder(filename, ec) {}
+
+  static llvm::Expected<std::unique_ptr<ProcessInfoRecorder>>
+  Create(const FileSpec &filename);
+
+  void Record(const ProcessInstanceInfoList &process_infos);
+};
+
+class ProcessInfoProvider : public repro::Provider<ProcessInfoProvider> {
+public:
+  struct Info {
+    static const char *name;
+    static const char *file;
+  };
+
+  ProcessInfoProvider(const FileSpec &directory) : Provider(directory) {}
+
+  ProcessInfoRecorder *GetNewProcessInfoRecorder();
+
+  void Keep() override;
+  void Discard() override;
+
+  static char ID;
+
+private:
+  std::unique_ptr<llvm::raw_fd_ostream> m_stream_up;
+  std::vector<std::unique_ptr<ProcessInfoRecorder>> m_process_info_recorders;
+};
+
+llvm::Optional<ProcessInstanceInfoList> GetReplayProcessInstanceInfoList();
+
+} // namespace repro
 } // namespace lldb_private
 
 LLVM_YAML_IS_SEQUENCE_VECTOR(lldb_private::ProcessInstanceInfo)
index d73fa78..104130b 100644 (file)
@@ -8,13 +8,14 @@
 
 #include "CommandObjectReproducer.h"
 
+#include "lldb/Host/HostInfo.h"
 #include "lldb/Host/OptionParser.h"
-#include "lldb/Utility/GDBRemote.h"
-#include "lldb/Utility/Reproducer.h"
-
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Interpreter/OptionArgParser.h"
+#include "lldb/Utility/GDBRemote.h"
+#include "lldb/Utility/ProcessInfo.h"
+#include "lldb/Utility/Reproducer.h"
 
 #include <csignal>
 
@@ -27,6 +28,7 @@ enum ReproducerProvider {
   eReproducerProviderCommands,
   eReproducerProviderFiles,
   eReproducerProviderGDB,
+  eReproducerProviderProcessInfo,
   eReproducerProviderVersion,
   eReproducerProviderWorkingDirectory,
   eReproducerProviderNone
@@ -49,6 +51,11 @@ static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
         "GDB Remote Packets",
     },
     {
+        eReproducerProviderProcessInfo,
+        "processes",
+        "Process Info",
+    },
+    {
         eReproducerProviderVersion,
         "version",
         "Version",
@@ -97,6 +104,24 @@ static constexpr OptionEnumValues ReproducerSignalType() {
 #define LLDB_OPTIONS_reproducer_xcrash
 #include "CommandOptions.inc"
 
+template <typename T>
+llvm::Expected<T> static ReadFromYAML(StringRef filename) {
+  auto error_or_file = MemoryBuffer::getFile(filename);
+  if (auto err = error_or_file.getError()) {
+    return errorCodeToError(err);
+  }
+
+  T t;
+  yaml::Input yin((*error_or_file)->getBuffer());
+  yin >> t;
+
+  if (auto err = yin.error()) {
+    return errorCodeToError(err);
+  }
+
+  return t;
+}
+
 class CommandObjectReproducerGenerate : public CommandObjectParsed {
 public:
   CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
@@ -458,23 +483,41 @@ protected:
 
       llvm::Optional<std::string> gdb_file;
       while ((gdb_file = multi_loader->GetNextFile())) {
-        auto error_or_file = MemoryBuffer::getFile(*gdb_file);
-        if (auto err = error_or_file.getError()) {
-          SetError(result, errorCodeToError(err));
+        if (llvm::Expected<std::vector<GDBRemotePacket>> packets =
+                ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) {
+          for (GDBRemotePacket &packet : *packets) {
+            packet.Dump(result.GetOutputStream());
+          }
+        } else {
+          SetError(result, packets.takeError());
           return false;
         }
+      }
 
-        std::vector<GDBRemotePacket> packets;
-        yaml::Input yin((*error_or_file)->getBuffer());
-        yin >> packets;
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    }
+    case eReproducerProviderProcessInfo: {
+      std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
+          multi_loader =
+              repro::MultiLoader<repro::ProcessInfoProvider>::Create(loader);
 
-        if (auto err = yin.error()) {
-          SetError(result, errorCodeToError(err));
-          return false;
-        }
+      if (!multi_loader) {
+        SetError(result, make_error<StringError>(
+                             llvm::inconvertibleErrorCode(),
+                             "Unable to create process info loader."));
+        return false;
+      }
 
-        for (GDBRemotePacket &packet : packets) {
-          packet.Dump(result.GetOutputStream());
+      llvm::Optional<std::string> process_file;
+      while ((process_file = multi_loader->GetNextFile())) {
+        if (llvm::Expected<ProcessInstanceInfoList> infos =
+                ReadFromYAML<ProcessInstanceInfoList>(*process_file)) {
+          for (ProcessInstanceInfo info : *infos)
+            info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver());
+        } else {
+          SetError(result, infos.takeError());
+          return false;
         }
       }
 
index 63963cf..b248539 100644 (file)
@@ -678,3 +678,23 @@ void llvm::format_provider<WaitStatus>::format(const WaitStatus &WS,
   }
   OS << desc << " " << int(WS.status);
 }
+
+uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
+                             ProcessInstanceInfoList &process_infos) {
+
+  if (llvm::Optional<ProcessInstanceInfoList> infos =
+          repro::GetReplayProcessInstanceInfoList()) {
+    process_infos = *infos;
+    return process_infos.size();
+  }
+
+  uint32_t result = FindProcessesImpl(match_info, process_infos);
+
+  if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) {
+    g->GetOrCreate<repro::ProcessInfoProvider>()
+        .GetNewProcessInfoRecorder()
+        ->Record(process_infos);
+  }
+
+  return result;
+}
index 3e002ca..45973f5 100644 (file)
@@ -221,8 +221,8 @@ static bool GetProcessAndStatInfo(::pid_t pid,
   return true;
 }
 
-uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
-                             ProcessInstanceInfoList &process_infos) {
+uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+                                 ProcessInstanceInfoList &process_infos) {
   static const char procdir[] = "/proc/";
 
   DIR *dirproc = opendir(procdir);
index 0e66871..2475338 100644 (file)
@@ -591,8 +591,8 @@ static bool GetMacOSXProcessUserAndGroup(ProcessInstanceInfo &process_info) {
   return false;
 }
 
-uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
-                             ProcessInstanceInfoList &process_infos) {
+uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+                                 ProcessInstanceInfoList &process_infos) {
   std::vector<struct kinfo_proc> kinfos;
 
   int mib[3] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};
index 10c0c78..57f9851 100644 (file)
@@ -176,8 +176,8 @@ error:
   return false;
 }
 
-uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
-                             ProcessInstanceInfoList &process_infos) {
+uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+                                 ProcessInstanceInfoList &process_infos) {
   const ::pid_t our_pid = ::getpid();
   const ::uid_t our_uid = ::getuid();
 
index 8fc8801..4ce5495 100644 (file)
@@ -140,8 +140,8 @@ static bool GetOpenBSDProcessUserAndGroup(ProcessInstanceInfo &process_info) {
   return false;
 }
 
-uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
-                             ProcessInstanceInfoList &process_infos) {
+uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+                                 ProcessInstanceInfoList &process_infos) {
   std::vector<struct kinfo_proc> kinfos;
 
   int mib[3] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};
index 450e62d..9a29218 100644 (file)
@@ -18,6 +18,7 @@
 
 using namespace lldb;
 using namespace lldb_private;
+using namespace lldb_private::repro;
 
 ProcessInfo::ProcessInfo()
     : m_executable(), m_arguments(), m_environment(), m_uid(UINT32_MAX),
@@ -344,3 +345,86 @@ void llvm::yaml::MappingTraits<ProcessInstanceInfo>::mapping(
   io.mapRequired("effective-gid", Info.m_egid);
   io.mapRequired("parent-pid", Info.m_parent_pid);
 }
+
+llvm::Expected<std::unique_ptr<ProcessInfoRecorder>>
+ProcessInfoRecorder::Create(const FileSpec &filename) {
+  std::error_code ec;
+  auto recorder =
+      std::make_unique<ProcessInfoRecorder>(std::move(filename), ec);
+  if (ec)
+    return llvm::errorCodeToError(ec);
+  return std::move(recorder);
+}
+
+void ProcessInfoProvider::Keep() {
+  std::vector<std::string> files;
+  for (auto &recorder : m_process_info_recorders) {
+    recorder->Stop();
+    files.push_back(recorder->GetFilename().GetPath());
+  }
+
+  FileSpec file = GetRoot().CopyByAppendingPathComponent(Info::file);
+  std::error_code ec;
+  llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text);
+  if (ec)
+    return;
+  llvm::yaml::Output yout(os);
+  yout << files;
+}
+
+void ProcessInfoProvider::Discard() { m_process_info_recorders.clear(); }
+
+ProcessInfoRecorder *ProcessInfoProvider::GetNewProcessInfoRecorder() {
+  std::size_t i = m_process_info_recorders.size() + 1;
+  std::string filename = (llvm::Twine(Info::name) + llvm::Twine("-") +
+                          llvm::Twine(i) + llvm::Twine(".yaml"))
+                             .str();
+  auto recorder_or_error = ProcessInfoRecorder::Create(
+      GetRoot().CopyByAppendingPathComponent(filename));
+  if (!recorder_or_error) {
+    llvm::consumeError(recorder_or_error.takeError());
+    return nullptr;
+  }
+
+  m_process_info_recorders.push_back(std::move(*recorder_or_error));
+  return m_process_info_recorders.back().get();
+}
+
+void ProcessInfoRecorder::Record(const ProcessInstanceInfoList &process_infos) {
+  if (!m_record)
+    return;
+  llvm::yaml::Output yout(m_os);
+  yout << const_cast<ProcessInstanceInfoList &>(process_infos);
+  m_os.flush();
+}
+
+llvm::Optional<ProcessInstanceInfoList>
+repro::GetReplayProcessInstanceInfoList() {
+  static std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
+      loader = repro::MultiLoader<repro::ProcessInfoProvider>::Create(
+          repro::Reproducer::Instance().GetLoader());
+
+  if (!loader)
+    return {};
+
+  llvm::Optional<std::string> nextfile = loader->GetNextFile();
+  if (!nextfile)
+    return {};
+
+  auto error_or_file = llvm::MemoryBuffer::getFile(*nextfile);
+  if (std::error_code err = error_or_file.getError())
+    return {};
+
+  ProcessInstanceInfoList infos;
+  llvm::yaml::Input yin((*error_or_file)->getBuffer());
+  yin >> infos;
+
+  if (auto err = yin.error())
+    return {};
+
+  return infos;
+}
+
+char ProcessInfoProvider::ID = 0;
+const char *ProcessInfoProvider::Info::file = "process-info.yaml";
+const char *ProcessInfoProvider::Info::name = "process-info";
diff --git a/lldb/test/API/functionalities/reproducers/attach/Makefile b/lldb/test/API/functionalities/reproducers/attach/Makefile
new file mode 100644 (file)
index 0000000..3d0b98f
--- /dev/null
@@ -0,0 +1,2 @@
+CXX_SOURCES := main.cpp
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py b/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py
new file mode 100644 (file)
index 0000000..48659e4
--- /dev/null
@@ -0,0 +1,71 @@
+"""
+Test reproducer attach.
+"""
+
+import lldb
+import tempfile
+from lldbsuite.test import lldbtest_config
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class CreateAfterAttachTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipIfFreeBSD
+    @skipIfNetBSD
+    @skipIfWindows
+    @skipIfRemote
+    @skipIfiOSSimulator
+    def test_create_after_attach_with_fork(self):
+        """Test thread creation after process attach."""
+        exe = '%s_%d' % (self.testMethodName, os.getpid())
+
+        token = self.getBuildArtifact(exe + '.token')
+        if os.path.exists(token):
+            os.remove(token)
+
+        reproducer = self.getBuildArtifact(exe + '.reproducer')
+        if os.path.exists(reproducer):
+            try:
+                shutil.rmtree(reproducer)
+            except OSError:
+                pass
+
+        self.build(dictionary={'EXE': exe})
+        self.addTearDownHook(self.cleanupSubprocesses)
+
+        inferior = self.spawnSubprocess(self.getBuildArtifact(exe), [token])
+        pid = inferior.pid
+
+        lldbutil.wait_for_file_on_target(self, token)
+
+        # Use Popen because pexpect is overkill and spawnSubprocess is
+        # asynchronous.
+        capture = subprocess.Popen([
+            lldbtest_config.lldbExec, '-b', '--capture', '--capture-path',
+            reproducer, '-o', 'proc att -n {}'.format(exe), '-o',
+            'reproducer generate'
+        ],
+                                   stdin=subprocess.PIPE,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+        outs, errs = capture.communicate()
+        self.assertIn('Process {} stopped'.format(pid), outs)
+        self.assertIn('Reproducer written', outs)
+
+        # Check that replay works.
+        replay = subprocess.Popen(
+            [lldbtest_config.lldbExec, '-replay', reproducer],
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+        outs, errs = replay.communicate()
+        self.assertIn('Process {} stopped'.format(pid), outs)
+
+        # We can dump the reproducer in the current context.
+        self.expect('reproducer dump -f {} -p process'.format(reproducer),
+                    substrs=['pid = {}'.format(pid), 'name = {}'.format(exe)])
diff --git a/lldb/test/API/functionalities/reproducers/attach/main.cpp b/lldb/test/API/functionalities/reproducers/attach/main.cpp
new file mode 100644 (file)
index 0000000..781da12
--- /dev/null
@@ -0,0 +1,24 @@
+#include <chrono>
+#include <stdio.h>
+#include <thread>
+
+using std::chrono::seconds;
+
+int main(int argc, char const *argv[]) {
+  lldb_enable_attach();
+
+  // Create the synchronization token.
+  FILE *f;
+  if (f = fopen(argv[1], "wx")) {
+    fputs("\n", f);
+    fflush(f);
+    fclose(f);
+  } else
+    return 1;
+
+  while (true) {
+    std::this_thread::sleep_for(seconds(1));
+  }
+
+  return 0;
+}