Index: lldb/include/lldb/Utility/FileSpec.h =================================================================== --- lldb/include/lldb/Utility/FileSpec.h +++ lldb/include/lldb/Utility/FileSpec.h @@ -18,6 +18,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" #include #include @@ -436,6 +437,14 @@ static void format(const lldb_private::FileSpec &F, llvm::raw_ostream &Stream, StringRef Style); }; + +namespace yaml { +template <> struct ScalarTraits { + static void output(const lldb_private::FileSpec &, void *, raw_ostream &); + static StringRef input(StringRef, void *, lldb_private::FileSpec &); + static QuotingType mustQuote(StringRef S) { return QuotingType::Double; } +}; +} // namespace yaml } // namespace llvm #endif // LLDB_UTILITY_FILESPEC_H Index: lldb/include/lldb/Utility/ProcessInfo.h =================================================================== --- lldb/include/lldb/Utility/ProcessInfo.h +++ lldb/include/lldb/Utility/ProcessInfo.h @@ -15,6 +15,7 @@ #include "lldb/Utility/Environment.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/NameMatches.h" +#include "lldb/Utility/Reproducer.h" #include @@ -89,6 +90,8 @@ const Environment &GetEnvironment() const { return m_environment; } protected: + template friend struct llvm::yaml::MappingTraits; + FileSpec m_executable; std::string m_arg0; // argv[0] if supported. If empty, then use m_executable. // Not all process plug-ins support specifying an argv[0] that differs from @@ -150,6 +153,8 @@ bool verbose) const; protected: + template friend struct llvm::yaml::MappingTraits; + uint32_t m_euid; uint32_t m_egid; lldb::pid_t m_parent_pid; @@ -188,6 +193,7 @@ } protected: + template friend struct llvm::yaml::MappingTraits; std::vector m_infos; }; @@ -248,6 +254,60 @@ 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) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &io, lldb_private::ProcessInstanceInfo &PII); + static StringRef validate(IO &io, lldb_private::ProcessInstanceInfo &); +}; + +template <> struct MappingTraits { + static void mapping(IO &io, lldb_private::ProcessInstanceInfoList &PIIL); + static StringRef validate(IO &io, lldb_private::ProcessInstanceInfoList &); +}; + +} // namespace yaml +} // namespace llvm + #endif // LLDB_UTILITY_PROCESSINFO_H Index: lldb/source/Commands/CommandObjectReproducer.cpp =================================================================== --- lldb/source/Commands/CommandObjectReproducer.cpp +++ 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 @@ -48,6 +50,11 @@ "gdb", "GDB Remote Packets", }, + { + eReproducerProviderProcessInfo, + "processes", + "Process Info", + }, { eReproducerProviderVersion, "version", @@ -448,6 +455,7 @@ std::unique_ptr> multi_loader = repro::MultiLoader::Create(loader); + llvm::Optional gdb_file; while ((gdb_file = multi_loader->GetNextFile())) { auto error_or_file = MemoryBuffer::getFile(*gdb_file); @@ -473,6 +481,45 @@ result.SetStatus(eReturnStatusSuccessFinishResult); return true; } + case eReproducerProviderProcessInfo: { + std::unique_ptr> + multi_loader = + repro::MultiLoader::Create(loader); + + if (!multi_loader) { + SetError(result, make_error( + llvm::inconvertibleErrorCode(), + "Unable to create process info loader.")); + return false; + } + + llvm::Optional process_file; + + while ((process_file = multi_loader->GetNextFile())) { + auto error_or_file = MemoryBuffer::getFile(*process_file); + if (auto err = error_or_file.getError()) { + SetError(result, errorCodeToError(err)); + return false; + } + + ProcessInstanceInfoList infos; + yaml::Input yin((*error_or_file)->getBuffer()); + yin >> infos; + + if (auto err = yin.error()) { + SetError(result, errorCodeToError(err)); + return false; + } + + for (size_t i = 0; i < infos.GetSize(); ++i) { + ProcessInstanceInfo info = infos.GetProcessInfoAtIndex(i); + info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver()); + } + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } case eReproducerProviderNone: result.SetError("No valid provider specified."); return false; Index: lldb/source/Host/macosx/objcxx/Host.mm =================================================================== --- lldb/source/Host/macosx/objcxx/Host.mm +++ lldb/source/Host/macosx/objcxx/Host.mm @@ -593,6 +593,12 @@ uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &process_infos) { + if (llvm::Optional infos = + repro::GetReplayProcessInstanceInfoList()) { + process_infos = *infos; + return process_infos.GetSize(); + } + std::vector kinfos; int mib[3] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL}; @@ -664,6 +670,13 @@ } } } + + if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) { + g->GetOrCreate() + .GetNewProcessInfoRecorder() + ->Record(process_infos); + } + return process_infos.GetSize(); } Index: lldb/source/Utility/FileSpec.cpp =================================================================== --- lldb/source/Utility/FileSpec.cpp +++ lldb/source/Utility/FileSpec.cpp @@ -537,3 +537,15 @@ if (!file.empty()) Stream << file; } + +void llvm::yaml::ScalarTraits::output(const FileSpec &Val, void *, + raw_ostream &Out) { + Out << Val.GetPath(); +} + +llvm::StringRef +llvm::yaml::ScalarTraits::input(llvm::StringRef Scalar, void *, + FileSpec &Val) { + Val = FileSpec(Scalar); + return {}; +} Index: lldb/source/Utility/ProcessInfo.cpp =================================================================== --- lldb/source/Utility/ProcessInfo.cpp +++ 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), @@ -331,3 +332,113 @@ m_name_match_type = NameMatch::Ignore; m_match_all_users = false; } + +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(); +} + +void llvm::yaml::MappingTraits::mapping( + IO &io, ProcessInstanceInfo &Info) { + io.mapRequired("executable", Info.m_executable); + io.mapRequired("arg0", Info.m_arg0); + io.mapRequired("uid", Info.m_uid); + io.mapRequired("gid", Info.m_gid); + io.mapRequired("pid", Info.m_pid); + io.mapRequired("effective-uid", Info.m_euid); + io.mapRequired("effective-gid", Info.m_egid); + io.mapRequired("parent-pid", Info.m_parent_pid); +} + +llvm::StringRef llvm::yaml::MappingTraits::validate( + IO &io, ProcessInstanceInfo &) { + return {}; +} + +void llvm::yaml::MappingTraits::mapping( + IO &io, ProcessInstanceInfoList &List) { + io.mapRequired("processes", List.m_infos); +} + +llvm::StringRef llvm::yaml::MappingTraits::validate( + IO &io, ProcessInstanceInfoList &) { + return {}; +} + +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 (auto 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"; Index: lldb/test/API/functionalities/reproducers/attach/Makefile =================================================================== --- /dev/null +++ lldb/test/API/functionalities/reproducers/attach/Makefile @@ -0,0 +1,2 @@ +CXX_SOURCES := main.cpp +include Makefile.rules Index: lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py @@ -0,0 +1,75 @@ +""" +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.""" + self.build(dictionary={'EXE': 'somewhat_unique_name'}) + self.addTearDownHook(self.cleanupSubprocesses) + + reproducer_patch = tempfile.NamedTemporaryFile().name + + inferior = self.spawnSubprocess( + self.getBuildArtifact("somewhat_unique_name")) + pid = inferior.pid + + # Use Popen because pexpect is overkill and spawnSubprocess is + # asynchronous. + capture = subprocess.Popen([ + lldbtest_config.lldbExec, '-b', '--capture', '--capture-path', + reproducer_patch, '-o', 'proc att -n somewhat_unique_name', '-o', + 'reproducer generate' + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + outs, errs = capture.communicate() + self.assertTrue('Process {} stopped'.format(pid) in outs) + self.assertTrue('Reproducer written' in outs) + + # Check that replay works. + replay = subprocess.Popen( + [lldbtest_config.lldbExec, '-replay', reproducer_patch], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + outs, errs = replay.communicate() + self.assertTrue('Process {} stopped'.format(pid) in outs) + + # Check that reproducer dump works for process info. + replay = subprocess.Popen([ + lldbtest_config.lldbExec, '-b', '-o', + 'reproducer dump -f {} -p process'.format(reproducer_patch) + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + outs, errs = replay.communicate() + print(outs) + self.assertTrue('name = {}'.format('somewhat_unique_name')) + self.assertTrue('pid = {}'.format(pid)) + + # Remove the reproducer but don't complain in case the directory was + # never created. + try: + shutil.rmtree(reproducer_patch) + except OSError: + pass Index: lldb/test/API/functionalities/reproducers/attach/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/functionalities/reproducers/attach/main.cpp @@ -0,0 +1,14 @@ +#include +#include + +using std::chrono::microseconds; + +int main(int argc, char const *argv[]) { + lldb_enable_attach(); + + while (true) { + std::this_thread::sleep_for(microseconds(1)); + } + + return 0; +}