[lldb/Reproducers] Intercept the FindProcesses API

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
This commit is contained in:
Jonas Devlieghere 2020-03-13 08:49:15 -07:00
parent a7325298e1
commit 2451cbf07b
12 changed files with 308 additions and 23 deletions

View 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

View 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)

View 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
@ -48,6 +50,11 @@ static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
"gdb",
"GDB Remote Packets",
},
{
eReproducerProviderProcessInfo,
"processes",
"Process Info",
},
{
eReproducerProviderVersion,
"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,24 +483,42 @@ 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));
if (!multi_loader) {
SetError(result, make_error<StringError>(
llvm::inconvertibleErrorCode(),
"Unable to create process info loader."));
return false;
}
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;
}
for (GDBRemotePacket &packet : packets) {
packet.Dump(result.GetOutputStream());
}
}
result.SetStatus(eReturnStatusSuccessFinishResult);

View 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;
}

View 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);

View 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};

View 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();

View 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};

View 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";

View File

@ -0,0 +1,2 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -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)])

View File

@ -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;
}