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
static std::unique_ptr<Connection>
CreateDefaultConnection(llvm::StringRef url);
+
+protected:
+ static uint32_t FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+ ProcessInstanceInfoList &proc_infos);
};
} // namespace lldb_private
#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>
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)
#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>
eReproducerProviderCommands,
eReproducerProviderFiles,
eReproducerProviderGDB,
+ eReproducerProviderProcessInfo,
eReproducerProviderVersion,
eReproducerProviderWorkingDirectory,
eReproducerProviderNone
"GDB Remote Packets",
},
{
+ eReproducerProviderProcessInfo,
+ "processes",
+ "Process Info",
+ },
+ {
eReproducerProviderVersion,
"version",
"Version",
#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)
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;
}
}
}
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;
+}
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);
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};
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();
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};
using namespace lldb;
using namespace lldb_private;
+using namespace lldb_private::repro;
ProcessInfo::ProcessInfo()
: m_executable(), m_arguments(), m_environment(), m_uid(UINT32_MAX),
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";
--- /dev/null
+CXX_SOURCES := main.cpp
+include Makefile.rules
--- /dev/null
+"""
+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)])
--- /dev/null
+#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;
+}