diff --git a/lldb/include/lldb/Host/Host.h b/lldb/include/lldb/Host/Host.h --- a/lldb/include/lldb/Host/Host.h +++ b/lldb/include/lldb/Host/Host.h @@ -232,6 +232,10 @@ static std::unique_ptr CreateDefaultConnection(llvm::StringRef url); + +protected: + static uint32_t FindProcessesImpl(const ProcessInstanceInfoMatch &match_info, + ProcessInstanceInfoList &proc_infos); }; } // namespace lldb_private diff --git a/lldb/include/lldb/Utility/ProcessInfo.h b/lldb/include/lldb/Utility/ProcessInfo.h --- a/lldb/include/lldb/Utility/ProcessInfo.h +++ b/lldb/include/lldb/Utility/ProcessInfo.h @@ -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 @@ -215,6 +216,42 @@ 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> + Create(const FileSpec &filename); + + void Record(const ProcessInstanceInfoList &process_infos); +}; + +class ProcessInfoProvider : public repro::Provider { +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 m_stream_up; + std::vector> m_process_info_recorders; +}; + +llvm::Optional GetReplayProcessInstanceInfoList(); + +} // namespace repro } // namespace lldb_private LLVM_YAML_IS_SEQUENCE_VECTOR(lldb_private::ProcessInstanceInfo) diff --git a/lldb/source/Commands/CommandObjectReproducer.cpp b/lldb/source/Commands/CommandObjectReproducer.cpp --- a/lldb/source/Commands/CommandObjectReproducer.cpp +++ b/lldb/source/Commands/CommandObjectReproducer.cpp @@ -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 @@ -27,6 +28,7 @@ eReproducerProviderCommands, eReproducerProviderFiles, eReproducerProviderGDB, + eReproducerProviderProcessInfo, eReproducerProviderVersion, eReproducerProviderWorkingDirectory, eReproducerProviderNone @@ -49,6 +51,11 @@ "GDB Remote Packets", }, { + eReproducerProviderProcessInfo, + "processes", + "Process Info", + }, + { eReproducerProviderVersion, "version", "Version", @@ -97,6 +104,24 @@ #define LLDB_OPTIONS_reproducer_xcrash #include "CommandOptions.inc" +template +llvm::Expected 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 @@ llvm::Optional 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> packets = + ReadFromYAML>(*gdb_file)) { + for (GDBRemotePacket &packet : *packets) { + packet.Dump(result.GetOutputStream()); + } + } else { + SetError(result, packets.takeError()); return false; } + } - std::vector packets; - yaml::Input yin((*error_or_file)->getBuffer()); - yin >> packets; + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + case eReproducerProviderProcessInfo: { + std::unique_ptr> + multi_loader = + repro::MultiLoader::Create(loader); - if (auto err = yin.error()) { - SetError(result, errorCodeToError(err)); - return false; - } + if (!multi_loader) { + SetError(result, make_error( + llvm::inconvertibleErrorCode(), + "Unable to create process info loader.")); + return false; + } - for (GDBRemotePacket &packet : packets) { - packet.Dump(result.GetOutputStream()); + llvm::Optional process_file; + while ((process_file = multi_loader->GetNextFile())) { + if (llvm::Expected infos = + ReadFromYAML(*process_file)) { + for (ProcessInstanceInfo info : *infos) + info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver()); + } else { + SetError(result, infos.takeError()); + return false; } } diff --git a/lldb/source/Host/common/Host.cpp b/lldb/source/Host/common/Host.cpp --- a/lldb/source/Host/common/Host.cpp +++ b/lldb/source/Host/common/Host.cpp @@ -678,3 +678,23 @@ } OS << desc << " " << int(WS.status); } + +uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info, + ProcessInstanceInfoList &process_infos) { + + if (llvm::Optional 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() + .GetNewProcessInfoRecorder() + ->Record(process_infos); + } + + return result; +} diff --git a/lldb/source/Host/linux/Host.cpp b/lldb/source/Host/linux/Host.cpp --- a/lldb/source/Host/linux/Host.cpp +++ b/lldb/source/Host/linux/Host.cpp @@ -221,8 +221,8 @@ 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); diff --git a/lldb/source/Host/macosx/objcxx/Host.mm b/lldb/source/Host/macosx/objcxx/Host.mm --- a/lldb/source/Host/macosx/objcxx/Host.mm +++ b/lldb/source/Host/macosx/objcxx/Host.mm @@ -591,8 +591,8 @@ 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 kinfos; int mib[3] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL}; diff --git a/lldb/source/Host/netbsd/Host.cpp b/lldb/source/Host/netbsd/Host.cpp --- a/lldb/source/Host/netbsd/Host.cpp +++ b/lldb/source/Host/netbsd/Host.cpp @@ -176,8 +176,8 @@ 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(); diff --git a/lldb/source/Host/openbsd/Host.cpp b/lldb/source/Host/openbsd/Host.cpp --- a/lldb/source/Host/openbsd/Host.cpp +++ b/lldb/source/Host/openbsd/Host.cpp @@ -140,8 +140,8 @@ 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 kinfos; int mib[3] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL}; diff --git a/lldb/source/Utility/ProcessInfo.cpp b/lldb/source/Utility/ProcessInfo.cpp --- a/lldb/source/Utility/ProcessInfo.cpp +++ b/lldb/source/Utility/ProcessInfo.cpp @@ -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 @@ io.mapRequired("effective-gid", Info.m_egid); io.mapRequired("parent-pid", Info.m_parent_pid); } + +llvm::Expected> +ProcessInfoRecorder::Create(const FileSpec &filename) { + std::error_code ec; + auto recorder = + std::make_unique(std::move(filename), ec); + if (ec) + return llvm::errorCodeToError(ec); + return std::move(recorder); +} + +void ProcessInfoProvider::Keep() { + std::vector 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(process_infos); + m_os.flush(); +} + +llvm::Optional +repro::GetReplayProcessInstanceInfoList() { + static std::unique_ptr> + loader = repro::MultiLoader::Create( + repro::Reproducer::Instance().GetLoader()); + + if (!loader) + return {}; + + llvm::Optional 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 --- /dev/null +++ b/lldb/test/API/functionalities/reproducers/attach/Makefile @@ -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 --- /dev/null +++ b/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py @@ -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 --- /dev/null +++ b/lldb/test/API/functionalities/reproducers/attach/main.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +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; +}