forked from OSchip/llvm-project
[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:
parent
a7325298e1
commit
2451cbf07b
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
CXX_SOURCES := main.cpp
|
||||
include Makefile.rules
|
|
@ -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)])
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue