Skip to content

Commit

Permalink
[Reproducers] Add file provider
Browse files Browse the repository at this point in the history
This patch adds the file provider which is responsible for capturing
files used by LLDB.

When capturing a reproducer, we use a file collector that is very
similar to the one used in clang. For every file that we touch, we add
an entry with a mapping from its virtual to its real path. When we
decide to generate a reproducer we copy over the files and their
permission into to reproducer folder.

When replaying a reproducer, we load the VFS mapping and instantiate a
RedirectingFileSystem. The latter will transparently use the files
available in the reproducer.

I've tested this on two macOS machines with an artificial example.
Still, it is very likely that I missed some places where we (still) use
native file system calls. I'm hoping to flesh those out while testing
with more advanced examples. However, I will fix those things in
separate patches.

Differential revision: https://reviews.llvm.org/D54617

llvm-svn: 352538
  • Loading branch information
JDevlieghere committed Jan 29, 2019
1 parent 6159e86 commit 4657517
Show file tree
Hide file tree
Showing 15 changed files with 606 additions and 15 deletions.
19 changes: 17 additions & 2 deletions lldb/include/lldb/Host/FileSystem.h
Expand Up @@ -11,6 +11,7 @@

#include "lldb/Host/File.h"
#include "lldb/Utility/DataBufferLLVM.h"
#include "lldb/Utility/FileCollector.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Status.h"

Expand All @@ -30,15 +31,24 @@ class FileSystem {
static const char *DEV_NULL;
static const char *PATH_CONVERSION_ERROR;

FileSystem() : m_fs(llvm::vfs::getRealFileSystem()) {}
FileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) : m_fs(fs) {}
FileSystem()
: m_fs(llvm::vfs::getRealFileSystem()), m_collector(nullptr),
m_mapped(false) {}
FileSystem(FileCollector &collector)
: m_fs(llvm::vfs::getRealFileSystem()), m_collector(&collector),
m_mapped(false) {}
FileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
bool mapped = false)
: m_fs(fs), m_mapped(mapped) {}

FileSystem(const FileSystem &fs) = delete;
FileSystem &operator=(const FileSystem &fs) = delete;

static FileSystem &Instance();

static void Initialize();
static void Initialize(FileCollector &collector);
static llvm::Error Initialize(const FileSpec &mapping);
static void Initialize(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
static void Terminate();

Expand Down Expand Up @@ -167,9 +177,14 @@ class FileSystem {
std::error_code GetRealPath(const llvm::Twine &path,
llvm::SmallVectorImpl<char> &output) const;

llvm::ErrorOr<std::string> GetExternalPath(const llvm::Twine &path);
llvm::ErrorOr<std::string> GetExternalPath(const FileSpec &file_spec);

private:
static llvm::Optional<FileSystem> &InstanceImpl();
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> m_fs;
FileCollector *m_collector;
bool m_mapped;
};
} // namespace lldb_private

Expand Down
74 changes: 74 additions & 0 deletions lldb/include/lldb/Utility/FileCollector.h
@@ -0,0 +1,74 @@
//===-- FileCollector.h -----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_UTILITY_FILE_COLLECTOR_H
#define LLDB_UTILITY_FILE_COLLECTOR_H

#include "lldb/Utility/FileSpec.h"

#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/VirtualFileSystem.h"

#include <mutex>

namespace lldb_private {

/// Collects files into a directory and generates a mapping that can be used by
/// the VFS.
class FileCollector {
public:
FileCollector(const FileSpec &root);

void AddFile(const llvm::Twine &file);
void AddFile(const FileSpec &file) { return AddFile(file.GetPath()); }

/// Write the yaml mapping (for the VFS) to the given file.
std::error_code WriteMapping(const FileSpec &mapping_file);

/// Copy the files into the root directory.
///
/// When stop_on_error is true (the default) we abort as soon as one file
/// cannot be copied. This is relatively common, for example when a file was
/// removed after it was added to the mapping.
std::error_code CopyFiles(bool stop_on_error = true);

protected:
void AddFileImpl(llvm::StringRef src_path);

bool MarkAsSeen(llvm::StringRef path) { return m_seen.insert(path).second; }

bool GetRealPath(llvm::StringRef src_path,
llvm::SmallVectorImpl<char> &result);

void AddFileToMapping(llvm::StringRef virtual_path,
llvm::StringRef real_path) {
m_vfs_writer.addFileMapping(virtual_path, real_path);
}

/// Synchronizes adding files.
std::mutex m_mutex;

/// The root directory where files are copied.
FileSpec m_root;

/// Tracks already seen files so they can be skipped.
llvm::StringSet<> m_seen;

/// The yaml mapping writer.
llvm::vfs::YAMLVFSWriter m_vfs_writer;

/// Caches real_path calls when resolving symlinks.
llvm::StringMap<std::string> m_symlink_map;
};

} // namespace lldb_private

#endif // LLDB_UTILITY_FILE_COLLECTOR_H
30 changes: 30 additions & 0 deletions lldb/include/lldb/Utility/Reproducer.h
Expand Up @@ -9,6 +9,7 @@
#ifndef LLDB_UTILITY_REPRODUCER_H
#define LLDB_UTILITY_REPRODUCER_H

#include "lldb/Utility/FileCollector.h"
#include "lldb/Utility/FileSpec.h"

#include "llvm/ADT/DenseMap.h"
Expand Down Expand Up @@ -81,6 +82,35 @@ template <typename ThisProviderT> class Provider : public ProviderBase {
using ProviderBase::ProviderBase; // Inherit constructor.
};

struct FileInfo {
static const char *name;
static const char *file;
};

class FileProvider : public Provider<FileProvider> {
public:
typedef FileInfo info;

FileProvider(const FileSpec &directory)
: Provider(directory),
m_collector(directory.CopyByAppendingPathComponent("root")) {}

FileCollector &GetFileCollector() { return m_collector; }

void Keep() override {
auto mapping = GetRoot().CopyByAppendingPathComponent(info::file);
// Temporary files that are removed during execution can cause copy errors.
if (auto ec = m_collector.CopyFiles(/*stop_on_error=*/false))
return;
m_collector.WriteMapping(mapping);
}

static char ID;

private:
FileCollector m_collector;
};

/// The generator is responsible for the logic needed to generate a
/// reproducer. For doing so it relies on providers, who serialize data that
/// is necessary for reproducing a failure.
Expand Down
3 changes: 3 additions & 0 deletions lldb/lit/Reproducer/Inputs/FileCapture.in
@@ -0,0 +1,3 @@
run
reproducer status
reproducer generate
2 changes: 2 additions & 0 deletions lldb/lit/Reproducer/Inputs/FileReplay.in
@@ -0,0 +1,2 @@
reproducer status
run
20 changes: 20 additions & 0 deletions lldb/lit/Reproducer/TestFileRepro.test
@@ -0,0 +1,20 @@
# REQUIRES: system-darwin

# This tests the replaying of GDB remote packets.
#
# We issue the same commands and ensure the output is identical to the original
# process. To ensure we're not actually running the original binary we check
# that the string "testing" is not printed.

# RUN: %clang %S/Inputs/simple.c -g -o %t.out
# RUN: %lldb -x -b -s %S/Inputs/FileCapture.in --capture %t.repro -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE
# RUN: rm %t.out
# RUN: %lldb -x -b -s %S/Inputs/FileReplay.in --replay %t.repro -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix REPLAY

# CAPTURE: testing
# REPLAY-NOT: testing

# CHECK: Process {{.*}} exited

# CAPTURE: Reproducer is in capture mode.
# CAPTURE: Reproducer written
4 changes: 2 additions & 2 deletions lldb/lit/Reproducer/TestGDBRemoteRepro.test
Expand Up @@ -7,8 +7,8 @@
# that the string "testing" is not printed.

# RUN: %clang %S/Inputs/simple.c -g -o %t.out
# RUN: %lldb -x -b -s %S/Inputs/GDBRemoteCapture.in --capture %T/reproducer -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE
# RUN: %lldb -x -b -s %S/Inputs/GDBRemoteReplay.in --replay %T/reproducer -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix REPLAY
# RUN: %lldb -x -b -s %S/Inputs/GDBRemoteCapture.in --capture %t.repro -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE
# RUN: %lldb -x -b -s %S/Inputs/GDBRemoteReplay.in --replay %t.repro -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix REPLAY

# CHECK: Breakpoint 1
# CHECK: Process {{.*}} stopped
Expand Down
68 changes: 64 additions & 4 deletions lldb/source/Host/common/FileSystem.cpp
Expand Up @@ -11,7 +11,9 @@
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/TildeExpressionResolver.h"

#include "llvm/Support/Errc.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
Expand Down Expand Up @@ -47,6 +49,26 @@ void FileSystem::Initialize() {
InstanceImpl().emplace();
}

void FileSystem::Initialize(FileCollector &collector) {
lldbassert(!InstanceImpl() && "Already initialized.");
InstanceImpl().emplace(collector);
}

llvm::Error FileSystem::Initialize(const FileSpec &mapping) {
lldbassert(!InstanceImpl() && "Already initialized.");

llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer =
llvm::vfs::getRealFileSystem()->getBufferForFile(mapping.GetPath());

if (!buffer)
return llvm::errorCodeToError(buffer.getError());

InstanceImpl().emplace(
llvm::vfs::getVFSFromYAML(std::move(buffer.get()), nullptr, ""), true);

return llvm::Error::success();
}

void FileSystem::Initialize(IntrusiveRefCntPtr<vfs::FileSystem> fs) {
lldbassert(!InstanceImpl() && "Already initialized.");
InstanceImpl().emplace(fs);
Expand Down Expand Up @@ -249,18 +271,25 @@ void FileSystem::Resolve(FileSpec &file_spec) {
std::shared_ptr<DataBufferLLVM>
FileSystem::CreateDataBuffer(const llvm::Twine &path, uint64_t size,
uint64_t offset) {
if (m_collector)
m_collector->AddFile(path);

const bool is_volatile = !IsLocal(path);
const ErrorOr<std::string> external_path = GetExternalPath(path);

if (!external_path)
return nullptr;

std::unique_ptr<llvm::WritableMemoryBuffer> buffer;
if (size == 0) {
auto buffer_or_error =
llvm::WritableMemoryBuffer::getFile(path, -1, is_volatile);
llvm::WritableMemoryBuffer::getFile(*external_path, -1, is_volatile);
if (!buffer_or_error)
return nullptr;
buffer = std::move(*buffer_or_error);
} else {
auto buffer_or_error = llvm::WritableMemoryBuffer::getFileSlice(
path, size, offset, is_volatile);
*external_path, size, offset, is_volatile);
if (!buffer_or_error)
return nullptr;
buffer = std::move(*buffer_or_error);
Expand Down Expand Up @@ -380,16 +409,22 @@ static mode_t GetOpenMode(uint32_t permissions) {

Status FileSystem::Open(File &File, const FileSpec &file_spec, uint32_t options,
uint32_t permissions) {
if (m_collector)
m_collector->AddFile(file_spec);

if (File.IsValid())
File.Close();

const int open_flags = GetOpenFlags(options);
const mode_t open_mode =
(open_flags & O_CREAT) ? GetOpenMode(permissions) : 0;
const std::string path = file_spec.GetPath();

auto path = GetExternalPath(file_spec);
if (!path)
return Status(path.getError());

int descriptor = llvm::sys::RetryAfterSignal(
-1, OpenWithFS, *this, path.c_str(), open_flags, open_mode);
-1, OpenWithFS, *this, path->c_str(), open_flags, open_mode);

Status error;
if (!File::DescriptorIsValid(descriptor)) {
Expand All @@ -401,3 +436,28 @@ Status FileSystem::Open(File &File, const FileSpec &file_spec, uint32_t options,
}
return error;
}

ErrorOr<std::string> FileSystem::GetExternalPath(const llvm::Twine &path) {
if (!m_mapped)
return path.str();

// If VFS mapped we know the underlying FS is a RedirectingFileSystem.
ErrorOr<vfs::RedirectingFileSystem::Entry *> E =
static_cast<vfs::RedirectingFileSystem &>(*m_fs).lookupPath(path);
if (!E) {
if (E.getError() == llvm::errc::no_such_file_or_directory) {
return path.str();
}
return E.getError();
}

auto *F = dyn_cast<vfs::RedirectingFileSystem::RedirectingFileEntry>(*E);
if (!F)
return make_error_code(llvm::errc::not_supported);

return F->getExternalContentsPath().str();
}

ErrorOr<std::string> FileSystem::GetExternalPath(const FileSpec &file_spec) {
return GetExternalPath(file_spec.GetPath());
}
15 changes: 9 additions & 6 deletions lldb/source/Host/macosx/objcxx/Host.mm
Expand Up @@ -1299,12 +1299,15 @@ static bool ShouldLaunchUsingXPC(ProcessLaunchInfo &launch_info) {

lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;

if (ShouldLaunchUsingXPC(launch_info)) {
error = LaunchProcessXPC(exe_spec.GetPath().c_str(), launch_info, pid);
} else {
error =
LaunchProcessPosixSpawn(exe_spec.GetPath().c_str(), launch_info, pid);
}
// From now on we'll deal with the external (devirtualized) path.
auto exe_path = fs.GetExternalPath(exe_spec);
if (!exe_path)
return Status(exe_path.getError());

if (ShouldLaunchUsingXPC(launch_info))
error = LaunchProcessXPC(exe_path->c_str(), launch_info, pid);
else
error = LaunchProcessPosixSpawn(exe_path->c_str(), launch_info, pid);

if (pid != LLDB_INVALID_PROCESS_ID) {
// If all went well, then set the process ID into the launch info
Expand Down
19 changes: 18 additions & 1 deletion lldb/source/Initialization/SystemInitializerCommon.cpp
Expand Up @@ -65,6 +65,7 @@ SystemInitializerCommon::Initialize(const InitializerOptions &options) {
}
#endif

// Initialize the reproducer.
ReproducerMode mode = ReproducerMode::Off;
if (options.reproducer_capture)
mode = ReproducerMode::Capture;
Expand All @@ -74,7 +75,23 @@ SystemInitializerCommon::Initialize(const InitializerOptions &options) {
if (auto e = Reproducer::Initialize(mode, FileSpec(options.reproducer_path)))
return e;

FileSystem::Initialize();
// Initialize the file system.
auto &r = repro::Reproducer::Instance();
if (repro::Loader *loader = r.GetLoader()) {
FileSpec vfs_mapping = loader->GetFile<FileInfo>();
if (vfs_mapping) {
if (llvm::Error e = FileSystem::Initialize(vfs_mapping))
return e;
} else {
FileSystem::Initialize();
}
} else if (repro::Generator *g = r.GetGenerator()) {
repro::FileProvider &fp = g->GetOrCreate<repro::FileProvider>();
FileSystem::Initialize(fp.GetFileCollector());
} else {
FileSystem::Initialize();
}

Log::Initialize();
HostInfo::Initialize();
static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Utility/CMakeLists.txt
Expand Up @@ -54,6 +54,7 @@ add_lldb_library(lldbUtility
DataEncoder.cpp
DataExtractor.cpp
Environment.cpp
FileCollector.cpp
Event.cpp
FileSpec.cpp
IOObject.cpp
Expand Down

0 comments on commit 4657517

Please sign in to comment.