Greg Clayton 529a3d87a7 [NFC] Improve FileSpec internal APIs and usage in preparation for adding caching of resolved/absolute.
Resubmission of https://reviews.llvm.org/D130309 with the 2 patches that fixed the linux buildbot, and new windows fixes.

The FileSpec APIs allow users to modify instance variables directly by getting a non const reference to the directory and filename instance variables. This makes it impossible to control all of the times the FileSpec object is modified so we can clear cached member variables like m_resolved and with an upcoming patch caching if the file is relative or absolute. This patch modifies the APIs of FileSpec so no one can modify the directory or filename instance variables directly by adding set accessors and by removing the get accessors that are non const.

Many clients were using FileSpec::GetCString(...) which returned a unique C string from a ConstString'ified version of the result of GetPath() which returned a std::string. This caused many locations to use this convenient function incorrectly and could cause many strings to be added to the constant string pool that didn't need to. Most clients were converted to using FileSpec::GetPath().c_str() when possible. Other clients were modified to use the newly renamed version of this function which returns an actualy ConstString:

ConstString FileSpec::GetPathAsConstString(bool denormalize = true) const;

This avoids the issue where people were getting an already uniqued "const char *" that came from a ConstString only to put the "const char *" back into a "ConstString" object. By returning the ConstString instead of a "const char *" clients can be more efficient with the result.

The patch:
- Removes the non const GetDirectory() and GetFilename() get accessors
- Adds set accessors to replace the above functions: SetDirectory() and SetFilename().
- Adds ClearDirectory() and ClearFilename() to replace usage of the FileSpec::GetDirectory().Clear()/FileSpec::GetFilename().Clear() call sites
- Fixed all incorrect usage of FileSpec::GetCString() to use FileSpec::GetPath().c_str() where appropriate, and updated other call sites that wanted a ConstString to use the newly returned ConstString appropriately and efficiently.

Differential Revision: https://reviews.llvm.org/D130549
2022-07-28 13:28:26 -07:00

2078 lines
71 KiB
C++

//===-- Platform.cpp ------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include <algorithm>
#include <csignal>
#include <fstream>
#include <memory>
#include <vector>
#include "lldb/Breakpoint/BreakpointIDList.h"
#include "lldb/Breakpoint/BreakpointLocation.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/StreamFile.h"
#include "lldb/Host/FileCache.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/OptionValueFileSpec.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Interpreter/Property.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/ModuleCache.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/UnixSignals.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
// Define these constants from POSIX mman.h rather than include the file so
// that they will be correct even when compiled on Linux.
#define MAP_PRIVATE 2
#define MAP_ANON 0x1000
using namespace lldb;
using namespace lldb_private;
// Use a singleton function for g_local_platform_sp to avoid init constructors
// since LLDB is often part of a shared library
static PlatformSP &GetHostPlatformSP() {
static PlatformSP g_platform_sp;
return g_platform_sp;
}
const char *Platform::GetHostPlatformName() { return "host"; }
namespace {
#define LLDB_PROPERTIES_platform
#include "TargetProperties.inc"
enum {
#define LLDB_PROPERTIES_platform
#include "TargetPropertiesEnum.inc"
};
} // namespace
ConstString PlatformProperties::GetSettingName() {
static ConstString g_setting_name("platform");
return g_setting_name;
}
PlatformProperties::PlatformProperties() {
m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName());
m_collection_sp->Initialize(g_platform_properties);
auto module_cache_dir = GetModuleCacheDirectory();
if (module_cache_dir)
return;
llvm::SmallString<64> user_home_dir;
if (!FileSystem::Instance().GetHomeDirectory(user_home_dir))
return;
module_cache_dir = FileSpec(user_home_dir.c_str());
module_cache_dir.AppendPathComponent(".lldb");
module_cache_dir.AppendPathComponent("module_cache");
SetDefaultModuleCacheDirectory(module_cache_dir);
SetModuleCacheDirectory(module_cache_dir);
}
bool PlatformProperties::GetUseModuleCache() const {
const auto idx = ePropertyUseModuleCache;
return m_collection_sp->GetPropertyAtIndexAsBoolean(
nullptr, idx, g_platform_properties[idx].default_uint_value != 0);
}
bool PlatformProperties::SetUseModuleCache(bool use_module_cache) {
return m_collection_sp->SetPropertyAtIndexAsBoolean(
nullptr, ePropertyUseModuleCache, use_module_cache);
}
FileSpec PlatformProperties::GetModuleCacheDirectory() const {
return m_collection_sp->GetPropertyAtIndexAsFileSpec(
nullptr, ePropertyModuleCacheDirectory);
}
bool PlatformProperties::SetModuleCacheDirectory(const FileSpec &dir_spec) {
return m_collection_sp->SetPropertyAtIndexAsFileSpec(
nullptr, ePropertyModuleCacheDirectory, dir_spec);
}
void PlatformProperties::SetDefaultModuleCacheDirectory(
const FileSpec &dir_spec) {
auto f_spec_opt = m_collection_sp->GetPropertyAtIndexAsOptionValueFileSpec(
nullptr, false, ePropertyModuleCacheDirectory);
assert(f_spec_opt);
f_spec_opt->SetDefaultValue(dir_spec);
}
/// Get the native host platform plug-in.
///
/// There should only be one of these for each host that LLDB runs
/// upon that should be statically compiled in and registered using
/// preprocessor macros or other similar build mechanisms.
///
/// This platform will be used as the default platform when launching
/// or attaching to processes unless another platform is specified.
PlatformSP Platform::GetHostPlatform() { return GetHostPlatformSP(); }
void Platform::Initialize() {}
void Platform::Terminate() {}
PlatformProperties &Platform::GetGlobalPlatformProperties() {
static PlatformProperties g_settings;
return g_settings;
}
void Platform::SetHostPlatform(const lldb::PlatformSP &platform_sp) {
// The native platform should use its static void Platform::Initialize()
// function to register itself as the native platform.
GetHostPlatformSP() = platform_sp;
}
Status Platform::GetFileWithUUID(const FileSpec &platform_file,
const UUID *uuid_ptr, FileSpec &local_file) {
// Default to the local case
local_file = platform_file;
return Status();
}
FileSpecList
Platform::LocateExecutableScriptingResources(Target *target, Module &module,
Stream *feedback_stream) {
return FileSpecList();
}
// PlatformSP
// Platform::FindPlugin (Process *process, ConstString plugin_name)
//{
// PlatformCreateInstance create_callback = nullptr;
// if (plugin_name)
// {
// create_callback =
// PluginManager::GetPlatformCreateCallbackForPluginName (plugin_name);
// if (create_callback)
// {
// ArchSpec arch;
// if (process)
// {
// arch = process->GetTarget().GetArchitecture();
// }
// PlatformSP platform_sp(create_callback(process, &arch));
// if (platform_sp)
// return platform_sp;
// }
// }
// else
// {
// for (uint32_t idx = 0; (create_callback =
// PluginManager::GetPlatformCreateCallbackAtIndex(idx)) != nullptr;
// ++idx)
// {
// PlatformSP platform_sp(create_callback(process, nullptr));
// if (platform_sp)
// return platform_sp;
// }
// }
// return PlatformSP();
//}
Status Platform::GetSharedModule(
const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp,
const FileSpecList *module_search_paths_ptr,
llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) {
if (IsHost())
return ModuleList::GetSharedModule(module_spec, module_sp,
module_search_paths_ptr, old_modules,
did_create_ptr, false);
// Module resolver lambda.
auto resolver = [&](const ModuleSpec &spec) {
Status error(eErrorTypeGeneric);
ModuleSpec resolved_spec;
// Check if we have sysroot set.
if (m_sdk_sysroot) {
// Prepend sysroot to module spec.
resolved_spec = spec;
resolved_spec.GetFileSpec().PrependPathComponent(
m_sdk_sysroot.GetStringRef());
// Try to get shared module with resolved spec.
error = ModuleList::GetSharedModule(resolved_spec, module_sp,
module_search_paths_ptr, old_modules,
did_create_ptr, false);
}
// If we don't have sysroot or it didn't work then
// try original module spec.
if (!error.Success()) {
resolved_spec = spec;
error = ModuleList::GetSharedModule(resolved_spec, module_sp,
module_search_paths_ptr, old_modules,
did_create_ptr, false);
}
if (error.Success() && module_sp)
module_sp->SetPlatformFileSpec(resolved_spec.GetFileSpec());
return error;
};
return GetRemoteSharedModule(module_spec, process, module_sp, resolver,
did_create_ptr);
}
bool Platform::GetModuleSpec(const FileSpec &module_file_spec,
const ArchSpec &arch, ModuleSpec &module_spec) {
ModuleSpecList module_specs;
if (ObjectFile::GetModuleSpecifications(module_file_spec, 0, 0,
module_specs) == 0)
return false;
ModuleSpec matched_module_spec;
return module_specs.FindMatchingModuleSpec(ModuleSpec(module_file_spec, arch),
module_spec);
}
PlatformSP Platform::Create(llvm::StringRef name) {
lldb::PlatformSP platform_sp;
if (name == GetHostPlatformName())
return GetHostPlatform();
if (PlatformCreateInstance create_callback =
PluginManager::GetPlatformCreateCallbackForPluginName(name))
return create_callback(true, nullptr);
return nullptr;
}
ArchSpec Platform::GetAugmentedArchSpec(Platform *platform, llvm::StringRef triple) {
if (platform)
return platform->GetAugmentedArchSpec(triple);
return HostInfo::GetAugmentedArchSpec(triple);
}
/// Default Constructor
Platform::Platform(bool is_host)
: m_is_host(is_host), m_os_version_set_while_connected(false),
m_system_arch_set_while_connected(false), m_max_uid_name_len(0),
m_max_gid_name_len(0), m_supports_rsync(false), m_rsync_opts(),
m_rsync_prefix(), m_supports_ssh(false), m_ssh_opts(),
m_ignores_remote_hostname(false), m_trap_handlers(),
m_calculated_trap_handlers(false),
m_module_cache(std::make_unique<ModuleCache>()) {
Log *log = GetLog(LLDBLog::Object);
LLDB_LOGF(log, "%p Platform::Platform()", static_cast<void *>(this));
}
Platform::~Platform() = default;
void Platform::GetStatus(Stream &strm) {
strm.Format(" Platform: {0}\n", GetPluginName());
ArchSpec arch(GetSystemArchitecture());
if (arch.IsValid()) {
if (!arch.GetTriple().str().empty()) {
strm.Printf(" Triple: ");
arch.DumpTriple(strm.AsRawOstream());
strm.EOL();
}
}
llvm::VersionTuple os_version = GetOSVersion();
if (!os_version.empty()) {
strm.Format("OS Version: {0}", os_version.getAsString());
if (llvm::Optional<std::string> s = GetOSBuildString())
strm.Format(" ({0})", *s);
strm.EOL();
}
if (IsHost()) {
strm.Printf(" Hostname: %s\n", GetHostname());
} else {
const bool is_connected = IsConnected();
if (is_connected)
strm.Printf(" Hostname: %s\n", GetHostname());
strm.Printf(" Connected: %s\n", is_connected ? "yes" : "no");
}
if (GetSDKRootDirectory()) {
strm.Format(" Sysroot: {0}\n", GetSDKRootDirectory());
}
if (GetWorkingDirectory()) {
strm.Printf("WorkingDir: %s\n", GetWorkingDirectory().GetPath().c_str());
}
if (!IsConnected())
return;
std::string specific_info(GetPlatformSpecificConnectionInformation());
if (!specific_info.empty())
strm.Printf("Platform-specific connection: %s\n", specific_info.c_str());
if (llvm::Optional<std::string> s = GetOSKernelDescription())
strm.Format(" Kernel: {0}\n", *s);
}
llvm::VersionTuple Platform::GetOSVersion(Process *process) {
std::lock_guard<std::mutex> guard(m_mutex);
if (IsHost()) {
if (m_os_version.empty()) {
// We have a local host platform
m_os_version = HostInfo::GetOSVersion();
m_os_version_set_while_connected = !m_os_version.empty();
}
} else {
// We have a remote platform. We can only fetch the remote
// OS version if we are connected, and we don't want to do it
// more than once.
const bool is_connected = IsConnected();
bool fetch = false;
if (!m_os_version.empty()) {
// We have valid OS version info, check to make sure it wasn't manually
// set prior to connecting. If it was manually set prior to connecting,
// then lets fetch the actual OS version info if we are now connected.
if (is_connected && !m_os_version_set_while_connected)
fetch = true;
} else {
// We don't have valid OS version info, fetch it if we are connected
fetch = is_connected;
}
if (fetch)
m_os_version_set_while_connected = GetRemoteOSVersion();
}
if (!m_os_version.empty())
return m_os_version;
if (process) {
// Check with the process in case it can answer the question if a process
// was provided
return process->GetHostOSVersion();
}
return llvm::VersionTuple();
}
llvm::Optional<std::string> Platform::GetOSBuildString() {
if (IsHost())
return HostInfo::GetOSBuildString();
return GetRemoteOSBuildString();
}
llvm::Optional<std::string> Platform::GetOSKernelDescription() {
if (IsHost())
return HostInfo::GetOSKernelDescription();
return GetRemoteOSKernelDescription();
}
void Platform::AddClangModuleCompilationOptions(
Target *target, std::vector<std::string> &options) {
std::vector<std::string> default_compilation_options = {
"-x", "c++", "-Xclang", "-nostdsysteminc", "-Xclang", "-nostdsysteminc"};
options.insert(options.end(), default_compilation_options.begin(),
default_compilation_options.end());
}
FileSpec Platform::GetWorkingDirectory() {
if (IsHost()) {
llvm::SmallString<64> cwd;
if (llvm::sys::fs::current_path(cwd))
return {};
else {
FileSpec file_spec(cwd);
FileSystem::Instance().Resolve(file_spec);
return file_spec;
}
} else {
if (!m_working_dir)
m_working_dir = GetRemoteWorkingDirectory();
return m_working_dir;
}
}
struct RecurseCopyBaton {
const FileSpec &dst;
Platform *platform_ptr;
Status error;
};
static FileSystem::EnumerateDirectoryResult
RecurseCopy_Callback(void *baton, llvm::sys::fs::file_type ft,
llvm::StringRef path) {
RecurseCopyBaton *rc_baton = (RecurseCopyBaton *)baton;
FileSpec src(path);
namespace fs = llvm::sys::fs;
switch (ft) {
case fs::file_type::fifo_file:
case fs::file_type::socket_file:
// we have no way to copy pipes and sockets - ignore them and continue
return FileSystem::eEnumerateDirectoryResultNext;
break;
case fs::file_type::directory_file: {
// make the new directory and get in there
FileSpec dst_dir = rc_baton->dst;
if (!dst_dir.GetFilename())
dst_dir.SetFilename(src.GetLastPathComponent());
Status error = rc_baton->platform_ptr->MakeDirectory(
dst_dir, lldb::eFilePermissionsDirectoryDefault);
if (error.Fail()) {
rc_baton->error.SetErrorStringWithFormat(
"unable to setup directory %s on remote end",
dst_dir.GetPath().c_str());
return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out
}
// now recurse
std::string src_dir_path(src.GetPath());
// Make a filespec that only fills in the directory of a FileSpec so when
// we enumerate we can quickly fill in the filename for dst copies
FileSpec recurse_dst;
recurse_dst.SetDirectory(dst_dir.GetPathAsConstString());
RecurseCopyBaton rc_baton2 = {recurse_dst, rc_baton->platform_ptr,
Status()};
FileSystem::Instance().EnumerateDirectory(src_dir_path, true, true, true,
RecurseCopy_Callback, &rc_baton2);
if (rc_baton2.error.Fail()) {
rc_baton->error.SetErrorString(rc_baton2.error.AsCString());
return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out
}
return FileSystem::eEnumerateDirectoryResultNext;
} break;
case fs::file_type::symlink_file: {
// copy the file and keep going
FileSpec dst_file = rc_baton->dst;
if (!dst_file.GetFilename())
dst_file.SetFilename(src.GetFilename());
FileSpec src_resolved;
rc_baton->error = FileSystem::Instance().Readlink(src, src_resolved);
if (rc_baton->error.Fail())
return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out
rc_baton->error =
rc_baton->platform_ptr->CreateSymlink(dst_file, src_resolved);
if (rc_baton->error.Fail())
return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out
return FileSystem::eEnumerateDirectoryResultNext;
} break;
case fs::file_type::regular_file: {
// copy the file and keep going
FileSpec dst_file = rc_baton->dst;
if (!dst_file.GetFilename())
dst_file.SetFilename(src.GetFilename());
Status err = rc_baton->platform_ptr->PutFile(src, dst_file);
if (err.Fail()) {
rc_baton->error.SetErrorString(err.AsCString());
return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out
}
return FileSystem::eEnumerateDirectoryResultNext;
} break;
default:
rc_baton->error.SetErrorStringWithFormat(
"invalid file detected during copy: %s", src.GetPath().c_str());
return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out
break;
}
llvm_unreachable("Unhandled file_type!");
}
Status Platform::Install(const FileSpec &src, const FileSpec &dst) {
Status error;
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "Platform::Install (src='%s', dst='%s')",
src.GetPath().c_str(), dst.GetPath().c_str());
FileSpec fixed_dst(dst);
if (!fixed_dst.GetFilename())
fixed_dst.SetFilename(src.GetFilename());
FileSpec working_dir = GetWorkingDirectory();
if (dst) {
if (dst.GetDirectory()) {
const char first_dst_dir_char = dst.GetDirectory().GetCString()[0];
if (first_dst_dir_char == '/' || first_dst_dir_char == '\\') {
fixed_dst.SetDirectory(dst.GetDirectory());
}
// If the fixed destination file doesn't have a directory yet, then we
// must have a relative path. We will resolve this relative path against
// the platform's working directory
if (!fixed_dst.GetDirectory()) {
FileSpec relative_spec;
std::string path;
if (working_dir) {
relative_spec = working_dir;
relative_spec.AppendPathComponent(dst.GetPath());
fixed_dst.SetDirectory(relative_spec.GetDirectory());
} else {
error.SetErrorStringWithFormat(
"platform working directory must be valid for relative path '%s'",
dst.GetPath().c_str());
return error;
}
}
} else {
if (working_dir) {
fixed_dst.SetDirectory(working_dir.GetPathAsConstString());
} else {
error.SetErrorStringWithFormat(
"platform working directory must be valid for relative path '%s'",
dst.GetPath().c_str());
return error;
}
}
} else {
if (working_dir) {
fixed_dst.SetDirectory(working_dir.GetPathAsConstString());
} else {
error.SetErrorStringWithFormat("platform working directory must be valid "
"when destination directory is empty");
return error;
}
}
LLDB_LOGF(log, "Platform::Install (src='%s', dst='%s') fixed_dst='%s'",
src.GetPath().c_str(), dst.GetPath().c_str(),
fixed_dst.GetPath().c_str());
if (GetSupportsRSync()) {
error = PutFile(src, dst);
} else {
namespace fs = llvm::sys::fs;
switch (fs::get_file_type(src.GetPath(), false)) {
case fs::file_type::directory_file: {
llvm::sys::fs::remove(fixed_dst.GetPath());
uint32_t permissions = FileSystem::Instance().GetPermissions(src);
if (permissions == 0)
permissions = eFilePermissionsDirectoryDefault;
error = MakeDirectory(fixed_dst, permissions);
if (error.Success()) {
// Make a filespec that only fills in the directory of a FileSpec so
// when we enumerate we can quickly fill in the filename for dst copies
FileSpec recurse_dst;
recurse_dst.SetDirectory(fixed_dst.GetPathAsConstString());
std::string src_dir_path(src.GetPath());
RecurseCopyBaton baton = {recurse_dst, this, Status()};
FileSystem::Instance().EnumerateDirectory(
src_dir_path, true, true, true, RecurseCopy_Callback, &baton);
return baton.error;
}
} break;
case fs::file_type::regular_file:
llvm::sys::fs::remove(fixed_dst.GetPath());
error = PutFile(src, fixed_dst);
break;
case fs::file_type::symlink_file: {
llvm::sys::fs::remove(fixed_dst.GetPath());
FileSpec src_resolved;
error = FileSystem::Instance().Readlink(src, src_resolved);
if (error.Success())
error = CreateSymlink(dst, src_resolved);
} break;
case fs::file_type::fifo_file:
error.SetErrorString("platform install doesn't handle pipes");
break;
case fs::file_type::socket_file:
error.SetErrorString("platform install doesn't handle sockets");
break;
default:
error.SetErrorString(
"platform install doesn't handle non file or directory items");
break;
}
}
return error;
}
bool Platform::SetWorkingDirectory(const FileSpec &file_spec) {
if (IsHost()) {
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOG(log, "{0}", file_spec);
if (std::error_code ec = llvm::sys::fs::set_current_path(file_spec.GetPath())) {
LLDB_LOG(log, "error: {0}", ec.message());
return false;
}
return true;
} else {
m_working_dir.Clear();
return SetRemoteWorkingDirectory(file_spec);
}
}
Status Platform::MakeDirectory(const FileSpec &file_spec,
uint32_t permissions) {
if (IsHost())
return llvm::sys::fs::create_directory(file_spec.GetPath(), permissions);
else {
Status error;
error.SetErrorStringWithFormatv("remote platform {0} doesn't support {1}",
GetPluginName(), LLVM_PRETTY_FUNCTION);
return error;
}
}
Status Platform::GetFilePermissions(const FileSpec &file_spec,
uint32_t &file_permissions) {
if (IsHost()) {
auto Value = llvm::sys::fs::getPermissions(file_spec.GetPath());
if (Value)
file_permissions = Value.get();
return Status(Value.getError());
} else {
Status error;
error.SetErrorStringWithFormatv("remote platform {0} doesn't support {1}",
GetPluginName(), LLVM_PRETTY_FUNCTION);
return error;
}
}
Status Platform::SetFilePermissions(const FileSpec &file_spec,
uint32_t file_permissions) {
if (IsHost()) {
auto Perms = static_cast<llvm::sys::fs::perms>(file_permissions);
return llvm::sys::fs::setPermissions(file_spec.GetPath(), Perms);
} else {
Status error;
error.SetErrorStringWithFormatv("remote platform {0} doesn't support {1}",
GetPluginName(), LLVM_PRETTY_FUNCTION);
return error;
}
}
user_id_t Platform::OpenFile(const FileSpec &file_spec,
File::OpenOptions flags, uint32_t mode,
Status &error) {
if (IsHost())
return FileCache::GetInstance().OpenFile(file_spec, flags, mode, error);
return UINT64_MAX;
}
bool Platform::CloseFile(user_id_t fd, Status &error) {
if (IsHost())
return FileCache::GetInstance().CloseFile(fd, error);
return false;
}
user_id_t Platform::GetFileSize(const FileSpec &file_spec) {
if (!IsHost())
return UINT64_MAX;
uint64_t Size;
if (llvm::sys::fs::file_size(file_spec.GetPath(), Size))
return 0;
return Size;
}
uint64_t Platform::ReadFile(lldb::user_id_t fd, uint64_t offset, void *dst,
uint64_t dst_len, Status &error) {
if (IsHost())
return FileCache::GetInstance().ReadFile(fd, offset, dst, dst_len, error);
error.SetErrorStringWithFormatv(
"Platform::ReadFile() is not supported in the {0} platform",
GetPluginName());
return -1;
}
uint64_t Platform::WriteFile(lldb::user_id_t fd, uint64_t offset,
const void *src, uint64_t src_len, Status &error) {
if (IsHost())
return FileCache::GetInstance().WriteFile(fd, offset, src, src_len, error);
error.SetErrorStringWithFormatv(
"Platform::WriteFile() is not supported in the {0} platform",
GetPluginName());
return -1;
}
UserIDResolver &Platform::GetUserIDResolver() {
if (IsHost())
return HostInfo::GetUserIDResolver();
return UserIDResolver::GetNoopResolver();
}
const char *Platform::GetHostname() {
if (IsHost())
return "127.0.0.1";
if (m_hostname.empty())
return nullptr;
return m_hostname.c_str();
}
ConstString Platform::GetFullNameForDylib(ConstString basename) {
return basename;
}
bool Platform::SetRemoteWorkingDirectory(const FileSpec &working_dir) {
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "Platform::SetRemoteWorkingDirectory('%s')",
working_dir.GetPath().c_str());
m_working_dir = working_dir;
return true;
}
bool Platform::SetOSVersion(llvm::VersionTuple version) {
if (IsHost()) {
// We don't need anyone setting the OS version for the host platform, we
// should be able to figure it out by calling HostInfo::GetOSVersion(...).
return false;
} else {
// We have a remote platform, allow setting the target OS version if we
// aren't connected, since if we are connected, we should be able to
// request the remote OS version from the connected platform.
if (IsConnected())
return false;
else {
// We aren't connected and we might want to set the OS version ahead of
// time before we connect so we can peruse files and use a local SDK or
// PDK cache of support files to disassemble or do other things.
m_os_version = version;
return true;
}
}
return false;
}
Status
Platform::ResolveExecutable(const ModuleSpec &module_spec,
lldb::ModuleSP &exe_module_sp,
const FileSpecList *module_search_paths_ptr) {
Status error;
if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
if (module_spec.GetArchitecture().IsValid()) {
error = ModuleList::GetSharedModule(module_spec, exe_module_sp,
module_search_paths_ptr, nullptr,
nullptr);
} else {
// No valid architecture was specified, ask the platform for the
// architectures that we should be using (in the correct order) and see
// if we can find a match that way
ModuleSpec arch_module_spec(module_spec);
ArchSpec process_host_arch;
for (const ArchSpec &arch :
GetSupportedArchitectures(process_host_arch)) {
arch_module_spec.GetArchitecture() = arch;
error = ModuleList::GetSharedModule(arch_module_spec, exe_module_sp,
module_search_paths_ptr, nullptr,
nullptr);
// Did we find an executable using one of the
if (error.Success() && exe_module_sp)
break;
}
}
} else {
error.SetErrorStringWithFormat(
"'%s' does not exist", module_spec.GetFileSpec().GetPath().c_str());
}
return error;
}
Status
Platform::ResolveRemoteExecutable(const ModuleSpec &module_spec,
lldb::ModuleSP &exe_module_sp,
const FileSpecList *module_search_paths_ptr) {
Status error;
// We may connect to a process and use the provided executable (Don't use
// local $PATH).
ModuleSpec resolved_module_spec(module_spec);
// Resolve any executable within a bundle on MacOSX
Host::ResolveExecutableInBundle(resolved_module_spec.GetFileSpec());
if (FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec()) ||
module_spec.GetUUID().IsValid()) {
if (resolved_module_spec.GetArchitecture().IsValid() ||
resolved_module_spec.GetUUID().IsValid()) {
error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp,
module_search_paths_ptr, nullptr,
nullptr);
if (exe_module_sp && exe_module_sp->GetObjectFile())
return error;
exe_module_sp.reset();
}
// No valid architecture was specified or the exact arch wasn't found so
// ask the platform for the architectures that we should be using (in the
// correct order) and see if we can find a match that way
StreamString arch_names;
llvm::ListSeparator LS;
ArchSpec process_host_arch;
for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) {
resolved_module_spec.GetArchitecture() = arch;
error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp,
module_search_paths_ptr, nullptr,
nullptr);
// Did we find an executable using one of the
if (error.Success()) {
if (exe_module_sp && exe_module_sp->GetObjectFile())
break;
else
error.SetErrorToGenericError();
}
arch_names << LS << arch.GetArchitectureName();
}
if (error.Fail() || !exe_module_sp) {
if (FileSystem::Instance().Readable(resolved_module_spec.GetFileSpec())) {
error.SetErrorStringWithFormatv(
"'{0}' doesn't contain any '{1}' platform architectures: {2}",
resolved_module_spec.GetFileSpec(), GetPluginName(),
arch_names.GetData());
} else {
error.SetErrorStringWithFormatv("'{0}' is not readable",
resolved_module_spec.GetFileSpec());
}
}
} else {
error.SetErrorStringWithFormatv("'{0}' does not exist",
resolved_module_spec.GetFileSpec());
}
return error;
}
Status Platform::ResolveSymbolFile(Target &target, const ModuleSpec &sym_spec,
FileSpec &sym_file) {
Status error;
if (FileSystem::Instance().Exists(sym_spec.GetSymbolFileSpec()))
sym_file = sym_spec.GetSymbolFileSpec();
else
error.SetErrorString("unable to resolve symbol file");
return error;
}
bool Platform::ResolveRemotePath(const FileSpec &platform_path,
FileSpec &resolved_platform_path) {
resolved_platform_path = platform_path;
FileSystem::Instance().Resolve(resolved_platform_path);
return true;
}
const ArchSpec &Platform::GetSystemArchitecture() {
if (IsHost()) {
if (!m_system_arch.IsValid()) {
// We have a local host platform
m_system_arch = HostInfo::GetArchitecture();
m_system_arch_set_while_connected = m_system_arch.IsValid();
}
} else {
// We have a remote platform. We can only fetch the remote system
// architecture if we are connected, and we don't want to do it more than
// once.
const bool is_connected = IsConnected();
bool fetch = false;
if (m_system_arch.IsValid()) {
// We have valid OS version info, check to make sure it wasn't manually
// set prior to connecting. If it was manually set prior to connecting,
// then lets fetch the actual OS version info if we are now connected.
if (is_connected && !m_system_arch_set_while_connected)
fetch = true;
} else {
// We don't have valid OS version info, fetch it if we are connected
fetch = is_connected;
}
if (fetch) {
m_system_arch = GetRemoteSystemArchitecture();
m_system_arch_set_while_connected = m_system_arch.IsValid();
}
}
return m_system_arch;
}
ArchSpec Platform::GetAugmentedArchSpec(llvm::StringRef triple) {
if (triple.empty())
return ArchSpec();
llvm::Triple normalized_triple(llvm::Triple::normalize(triple));
if (!ArchSpec::ContainsOnlyArch(normalized_triple))
return ArchSpec(triple);
if (auto kind = HostInfo::ParseArchitectureKind(triple))
return HostInfo::GetArchitecture(*kind);
ArchSpec compatible_arch;
ArchSpec raw_arch(triple);
if (!IsCompatibleArchitecture(raw_arch, {}, false, &compatible_arch))
return raw_arch;
if (!compatible_arch.IsValid())
return ArchSpec(normalized_triple);
const llvm::Triple &compatible_triple = compatible_arch.GetTriple();
if (normalized_triple.getVendorName().empty())
normalized_triple.setVendor(compatible_triple.getVendor());
if (normalized_triple.getOSName().empty())
normalized_triple.setOS(compatible_triple.getOS());
if (normalized_triple.getEnvironmentName().empty())
normalized_triple.setEnvironment(compatible_triple.getEnvironment());
return ArchSpec(normalized_triple);
}
Status Platform::ConnectRemote(Args &args) {
Status error;
if (IsHost())
error.SetErrorStringWithFormatv(
"The currently selected platform ({0}) is "
"the host platform and is always connected.",
GetPluginName());
else
error.SetErrorStringWithFormatv(
"Platform::ConnectRemote() is not supported by {0}", GetPluginName());
return error;
}
Status Platform::DisconnectRemote() {
Status error;
if (IsHost())
error.SetErrorStringWithFormatv(
"The currently selected platform ({0}) is "
"the host platform and is always connected.",
GetPluginName());
else
error.SetErrorStringWithFormatv(
"Platform::DisconnectRemote() is not supported by {0}",
GetPluginName());
return error;
}
bool Platform::GetProcessInfo(lldb::pid_t pid,
ProcessInstanceInfo &process_info) {
// Take care of the host case so that each subclass can just call this
// function to get the host functionality.
if (IsHost())
return Host::GetProcessInfo(pid, process_info);
return false;
}
uint32_t Platform::FindProcesses(const ProcessInstanceInfoMatch &match_info,
ProcessInstanceInfoList &process_infos) {
// Take care of the host case so that each subclass can just call this
// function to get the host functionality.
uint32_t match_count = 0;
if (IsHost())
match_count = Host::FindProcesses(match_info, process_infos);
return match_count;
}
Status Platform::LaunchProcess(ProcessLaunchInfo &launch_info) {
Status error;
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "Platform::%s()", __FUNCTION__);
// Take care of the host case so that each subclass can just call this
// function to get the host functionality.
if (IsHost()) {
if (::getenv("LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY"))
launch_info.GetFlags().Set(eLaunchFlagLaunchInTTY);
if (launch_info.GetFlags().Test(eLaunchFlagLaunchInShell)) {
const bool will_debug = launch_info.GetFlags().Test(eLaunchFlagDebug);
const bool first_arg_is_full_shell_command = false;
uint32_t num_resumes = GetResumeCountForLaunchInfo(launch_info);
if (log) {
const FileSpec &shell = launch_info.GetShell();
std::string shell_str = (shell) ? shell.GetPath() : "<null>";
LLDB_LOGF(log,
"Platform::%s GetResumeCountForLaunchInfo() returned %" PRIu32
", shell is '%s'",
__FUNCTION__, num_resumes, shell_str.c_str());
}
if (!launch_info.ConvertArgumentsForLaunchingInShell(
error, will_debug, first_arg_is_full_shell_command, num_resumes))
return error;
} else if (launch_info.GetFlags().Test(eLaunchFlagShellExpandArguments)) {
error = ShellExpandArguments(launch_info);
if (error.Fail()) {
error.SetErrorStringWithFormat("shell expansion failed (reason: %s). "
"consider launching with 'process "
"launch'.",
error.AsCString("unknown"));
return error;
}
}
LLDB_LOGF(log, "Platform::%s final launch_info resume count: %" PRIu32,
__FUNCTION__, launch_info.GetResumeCount());
error = Host::LaunchProcess(launch_info);
} else
error.SetErrorString(
"base lldb_private::Platform class can't launch remote processes");
return error;
}
Status Platform::ShellExpandArguments(ProcessLaunchInfo &launch_info) {
if (IsHost())
return Host::ShellExpandArguments(launch_info);
return Status("base lldb_private::Platform class can't expand arguments");
}
Status Platform::KillProcess(const lldb::pid_t pid) {
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "Platform::%s, pid %" PRIu64, __FUNCTION__, pid);
if (!IsHost()) {
return Status(
"base lldb_private::Platform class can't kill remote processes");
}
Host::Kill(pid, SIGKILL);
return Status();
}
lldb::ProcessSP Platform::DebugProcess(ProcessLaunchInfo &launch_info,
Debugger &debugger, Target &target,
Status &error) {
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOG(log, "target = {0})", &target);
ProcessSP process_sp;
// Make sure we stop at the entry point
launch_info.GetFlags().Set(eLaunchFlagDebug);
// We always launch the process we are going to debug in a separate process
// group, since then we can handle ^C interrupts ourselves w/o having to
// worry about the target getting them as well.
launch_info.SetLaunchInSeparateProcessGroup(true);
// Allow any StructuredData process-bound plugins to adjust the launch info
// if needed
size_t i = 0;
bool iteration_complete = false;
// Note iteration can't simply go until a nullptr callback is returned, as it
// is valid for a plugin to not supply a filter.
auto get_filter_func = PluginManager::GetStructuredDataFilterCallbackAtIndex;
for (auto filter_callback = get_filter_func(i, iteration_complete);
!iteration_complete;
filter_callback = get_filter_func(++i, iteration_complete)) {
if (filter_callback) {
// Give this ProcessLaunchInfo filter a chance to adjust the launch info.
error = (*filter_callback)(launch_info, &target);
if (!error.Success()) {
LLDB_LOGF(log,
"Platform::%s() StructuredDataPlugin launch "
"filter failed.",
__FUNCTION__);
return process_sp;
}
}
}
error = LaunchProcess(launch_info);
if (error.Success()) {
LLDB_LOGF(log,
"Platform::%s LaunchProcess() call succeeded (pid=%" PRIu64 ")",
__FUNCTION__, launch_info.GetProcessID());
if (launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) {
ProcessAttachInfo attach_info(launch_info);
process_sp = Attach(attach_info, debugger, &target, error);
if (process_sp) {
LLDB_LOG(log, "Attach() succeeded, Process plugin: {0}",
process_sp->GetPluginName());
launch_info.SetHijackListener(attach_info.GetHijackListener());
// Since we attached to the process, it will think it needs to detach
// if the process object just goes away without an explicit call to
// Process::Kill() or Process::Detach(), so let it know to kill the
// process if this happens.
process_sp->SetShouldDetach(false);
// If we didn't have any file actions, the pseudo terminal might have
// been used where the secondary side was given as the file to open for
// stdin/out/err after we have already opened the primary so we can
// read/write stdin/out/err.
int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();
if (pty_fd != PseudoTerminal::invalid_fd) {
process_sp->SetSTDIOFileDescriptor(pty_fd);
}
} else {
LLDB_LOGF(log, "Platform::%s Attach() failed: %s", __FUNCTION__,
error.AsCString());
}
} else {
LLDB_LOGF(log,
"Platform::%s LaunchProcess() returned launch_info with "
"invalid process id",
__FUNCTION__);
}
} else {
LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__,
error.AsCString());
}
return process_sp;
}
std::vector<ArchSpec>
Platform::CreateArchList(llvm::ArrayRef<llvm::Triple::ArchType> archs,
llvm::Triple::OSType os) {
std::vector<ArchSpec> list;
for(auto arch : archs) {
llvm::Triple triple;
triple.setArch(arch);
triple.setOS(os);
list.push_back(ArchSpec(triple));
}
return list;
}
/// Lets a platform answer if it is compatible with a given
/// architecture and the target triple contained within.
bool Platform::IsCompatibleArchitecture(const ArchSpec &arch,
const ArchSpec &process_host_arch,
bool exact_arch_match,
ArchSpec *compatible_arch_ptr) {
// If the architecture is invalid, we must answer true...
if (arch.IsValid()) {
ArchSpec platform_arch;
auto match = exact_arch_match ? &ArchSpec::IsExactMatch
: &ArchSpec::IsCompatibleMatch;
for (const ArchSpec &platform_arch :
GetSupportedArchitectures(process_host_arch)) {
if ((arch.*match)(platform_arch)) {
if (compatible_arch_ptr)
*compatible_arch_ptr = platform_arch;
return true;
}
}
}
if (compatible_arch_ptr)
compatible_arch_ptr->Clear();
return false;
}
Status Platform::PutFile(const FileSpec &source, const FileSpec &destination,
uint32_t uid, uint32_t gid) {
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "[PutFile] Using block by block transfer....\n");
auto source_open_options =
File::eOpenOptionReadOnly | File::eOpenOptionCloseOnExec;
namespace fs = llvm::sys::fs;
if (fs::is_symlink_file(source.GetPath()))
source_open_options |= File::eOpenOptionDontFollowSymlinks;
auto source_file = FileSystem::Instance().Open(source, source_open_options,
lldb::eFilePermissionsUserRW);
if (!source_file)
return Status(source_file.takeError());
Status error;
uint32_t permissions = source_file.get()->GetPermissions(error);
if (permissions == 0)
permissions = lldb::eFilePermissionsFileDefault;
lldb::user_id_t dest_file = OpenFile(
destination, File::eOpenOptionCanCreate | File::eOpenOptionWriteOnly |
File::eOpenOptionTruncate | File::eOpenOptionCloseOnExec,
permissions, error);
LLDB_LOGF(log, "dest_file = %" PRIu64 "\n", dest_file);
if (error.Fail())
return error;
if (dest_file == UINT64_MAX)
return Status("unable to open target file");
lldb::WritableDataBufferSP buffer_sp(new DataBufferHeap(1024 * 16, 0));
uint64_t offset = 0;
for (;;) {
size_t bytes_read = buffer_sp->GetByteSize();
error = source_file.get()->Read(buffer_sp->GetBytes(), bytes_read);
if (error.Fail() || bytes_read == 0)
break;
const uint64_t bytes_written =
WriteFile(dest_file, offset, buffer_sp->GetBytes(), bytes_read, error);
if (error.Fail())
break;
offset += bytes_written;
if (bytes_written != bytes_read) {
// We didn't write the correct number of bytes, so adjust the file
// position in the source file we are reading from...
source_file.get()->SeekFromStart(offset);
}
}
CloseFile(dest_file, error);
if (uid == UINT32_MAX && gid == UINT32_MAX)
return error;
// TODO: ChownFile?
return error;
}
Status Platform::GetFile(const FileSpec &source, const FileSpec &destination) {
Status error("unimplemented");
return error;
}
Status
Platform::CreateSymlink(const FileSpec &src, // The name of the link is in src
const FileSpec &dst) // The symlink points to dst
{
if (IsHost())
return FileSystem::Instance().Symlink(src, dst);
return Status("unimplemented");
}
bool Platform::GetFileExists(const lldb_private::FileSpec &file_spec) {
if (IsHost())
return FileSystem::Instance().Exists(file_spec);
return false;
}
Status Platform::Unlink(const FileSpec &path) {
if (IsHost())
return llvm::sys::fs::remove(path.GetPath());
return Status("unimplemented");
}
MmapArgList Platform::GetMmapArgumentList(const ArchSpec &arch, addr_t addr,
addr_t length, unsigned prot,
unsigned flags, addr_t fd,
addr_t offset) {
uint64_t flags_platform = 0;
if (flags & eMmapFlagsPrivate)
flags_platform |= MAP_PRIVATE;
if (flags & eMmapFlagsAnon)
flags_platform |= MAP_ANON;
MmapArgList args({addr, length, prot, flags_platform, fd, offset});
return args;
}
lldb_private::Status Platform::RunShellCommand(
llvm::StringRef command,
const FileSpec &
working_dir, // Pass empty FileSpec to use the current working directory
int *status_ptr, // Pass nullptr if you don't want the process exit status
int *signo_ptr, // Pass nullptr if you don't want the signal that caused the
// process to exit
std::string
*command_output, // Pass nullptr if you don't want the command output
const Timeout<std::micro> &timeout) {
return RunShellCommand(llvm::StringRef(), command, working_dir, status_ptr,
signo_ptr, command_output, timeout);
}
lldb_private::Status Platform::RunShellCommand(
llvm::StringRef shell, // Pass empty if you want to use the default
// shell interpreter
llvm::StringRef command, // Shouldn't be empty
const FileSpec &
working_dir, // Pass empty FileSpec to use the current working directory
int *status_ptr, // Pass nullptr if you don't want the process exit status
int *signo_ptr, // Pass nullptr if you don't want the signal that caused the
// process to exit
std::string
*command_output, // Pass nullptr if you don't want the command output
const Timeout<std::micro> &timeout) {
if (IsHost())
return Host::RunShellCommand(shell, command, working_dir, status_ptr,
signo_ptr, command_output, timeout);
return Status("unable to run a remote command without a platform");
}
bool Platform::CalculateMD5(const FileSpec &file_spec, uint64_t &low,
uint64_t &high) {
if (!IsHost())
return false;
auto Result = llvm::sys::fs::md5_contents(file_spec.GetPath());
if (!Result)
return false;
std::tie(high, low) = Result->words();
return true;
}
void Platform::SetLocalCacheDirectory(const char *local) {
m_local_cache_directory.assign(local);
}
const char *Platform::GetLocalCacheDirectory() {
return m_local_cache_directory.c_str();
}
static constexpr OptionDefinition g_rsync_option_table[] = {
{LLDB_OPT_SET_ALL, false, "rsync", 'r', OptionParser::eNoArgument, nullptr,
{}, 0, eArgTypeNone, "Enable rsync."},
{LLDB_OPT_SET_ALL, false, "rsync-opts", 'R',
OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeCommandName,
"Platform-specific options required for rsync to work."},
{LLDB_OPT_SET_ALL, false, "rsync-prefix", 'P',
OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeCommandName,
"Platform-specific rsync prefix put before the remote path."},
{LLDB_OPT_SET_ALL, false, "ignore-remote-hostname", 'i',
OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
"Do not automatically fill in the remote hostname when composing the "
"rsync command."},
};
static constexpr OptionDefinition g_ssh_option_table[] = {
{LLDB_OPT_SET_ALL, false, "ssh", 's', OptionParser::eNoArgument, nullptr,
{}, 0, eArgTypeNone, "Enable SSH."},
{LLDB_OPT_SET_ALL, false, "ssh-opts", 'S', OptionParser::eRequiredArgument,
nullptr, {}, 0, eArgTypeCommandName,
"Platform-specific options required for SSH to work."},
};
static constexpr OptionDefinition g_caching_option_table[] = {
{LLDB_OPT_SET_ALL, false, "local-cache-dir", 'c',
OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypePath,
"Path in which to store local copies of files."},
};
llvm::ArrayRef<OptionDefinition> OptionGroupPlatformRSync::GetDefinitions() {
return llvm::makeArrayRef(g_rsync_option_table);
}
void OptionGroupPlatformRSync::OptionParsingStarting(
ExecutionContext *execution_context) {
m_rsync = false;
m_rsync_opts.clear();
m_rsync_prefix.clear();
m_ignores_remote_hostname = false;
}
lldb_private::Status
OptionGroupPlatformRSync::SetOptionValue(uint32_t option_idx,
llvm::StringRef option_arg,
ExecutionContext *execution_context) {
Status error;
char short_option = (char)GetDefinitions()[option_idx].short_option;
switch (short_option) {
case 'r':
m_rsync = true;
break;
case 'R':
m_rsync_opts.assign(std::string(option_arg));
break;
case 'P':
m_rsync_prefix.assign(std::string(option_arg));
break;
case 'i':
m_ignores_remote_hostname = true;
break;
default:
error.SetErrorStringWithFormat("unrecognized option '%c'", short_option);
break;
}
return error;
}
lldb::BreakpointSP
Platform::SetThreadCreationBreakpoint(lldb_private::Target &target) {
return lldb::BreakpointSP();
}
llvm::ArrayRef<OptionDefinition> OptionGroupPlatformSSH::GetDefinitions() {
return llvm::makeArrayRef(g_ssh_option_table);
}
void OptionGroupPlatformSSH::OptionParsingStarting(
ExecutionContext *execution_context) {
m_ssh = false;
m_ssh_opts.clear();
}
lldb_private::Status
OptionGroupPlatformSSH::SetOptionValue(uint32_t option_idx,
llvm::StringRef option_arg,
ExecutionContext *execution_context) {
Status error;
char short_option = (char)GetDefinitions()[option_idx].short_option;
switch (short_option) {
case 's':
m_ssh = true;
break;
case 'S':
m_ssh_opts.assign(std::string(option_arg));
break;
default:
error.SetErrorStringWithFormat("unrecognized option '%c'", short_option);
break;
}
return error;
}
llvm::ArrayRef<OptionDefinition> OptionGroupPlatformCaching::GetDefinitions() {
return llvm::makeArrayRef(g_caching_option_table);
}
void OptionGroupPlatformCaching::OptionParsingStarting(
ExecutionContext *execution_context) {
m_cache_dir.clear();
}
lldb_private::Status OptionGroupPlatformCaching::SetOptionValue(
uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) {
Status error;
char short_option = (char)GetDefinitions()[option_idx].short_option;
switch (short_option) {
case 'c':
m_cache_dir.assign(std::string(option_arg));
break;
default:
error.SetErrorStringWithFormat("unrecognized option '%c'", short_option);
break;
}
return error;
}
Environment Platform::GetEnvironment() {
if (IsHost())
return Host::GetEnvironment();
return Environment();
}
const std::vector<ConstString> &Platform::GetTrapHandlerSymbolNames() {
if (!m_calculated_trap_handlers) {
std::lock_guard<std::mutex> guard(m_mutex);
if (!m_calculated_trap_handlers) {
CalculateTrapHandlerSymbolNames();
m_calculated_trap_handlers = true;
}
}
return m_trap_handlers;
}
Status
Platform::GetCachedExecutable(ModuleSpec &module_spec,
lldb::ModuleSP &module_sp,
const FileSpecList *module_search_paths_ptr) {
FileSpec platform_spec = module_spec.GetFileSpec();
Status error = GetRemoteSharedModule(
module_spec, nullptr, module_sp,
[&](const ModuleSpec &spec) {
return ResolveRemoteExecutable(spec, module_sp,
module_search_paths_ptr);
},
nullptr);
if (error.Success()) {
module_spec.GetFileSpec() = module_sp->GetFileSpec();
module_spec.GetPlatformFileSpec() = platform_spec;
}
return error;
}
Status Platform::GetRemoteSharedModule(const ModuleSpec &module_spec,
Process *process,
lldb::ModuleSP &module_sp,
const ModuleResolver &module_resolver,
bool *did_create_ptr) {
// Get module information from a target.
ModuleSpec resolved_module_spec;
ArchSpec process_host_arch;
bool got_module_spec = false;
if (process) {
process_host_arch = process->GetSystemArchitecture();
// Try to get module information from the process
if (process->GetModuleSpec(module_spec.GetFileSpec(),
module_spec.GetArchitecture(),
resolved_module_spec)) {
if (!module_spec.GetUUID().IsValid() ||
module_spec.GetUUID() == resolved_module_spec.GetUUID()) {
got_module_spec = true;
}
}
}
if (!module_spec.GetArchitecture().IsValid()) {
Status error;
// No valid architecture was specified, ask the platform for the
// architectures that we should be using (in the correct order) and see if
// we can find a match that way
ModuleSpec arch_module_spec(module_spec);
for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) {
arch_module_spec.GetArchitecture() = arch;
error = ModuleList::GetSharedModule(arch_module_spec, module_sp, nullptr,
nullptr, nullptr);
// Did we find an executable using one of the
if (error.Success() && module_sp)
break;
}
if (module_sp) {
resolved_module_spec = arch_module_spec;
got_module_spec = true;
}
}
if (!got_module_spec) {
// Get module information from a target.
if (GetModuleSpec(module_spec.GetFileSpec(), module_spec.GetArchitecture(),
resolved_module_spec)) {
if (!module_spec.GetUUID().IsValid() ||
module_spec.GetUUID() == resolved_module_spec.GetUUID()) {
got_module_spec = true;
}
}
}
if (!got_module_spec) {
// Fall back to the given module resolver, which may have its own
// search logic.
return module_resolver(module_spec);
}
// If we are looking for a specific UUID, make sure resolved_module_spec has
// the same one before we search.
if (module_spec.GetUUID().IsValid()) {
resolved_module_spec.GetUUID() = module_spec.GetUUID();
}
// Trying to find a module by UUID on local file system.
const auto error = module_resolver(resolved_module_spec);
if (error.Fail()) {
if (GetCachedSharedModule(resolved_module_spec, module_sp, did_create_ptr))
return Status();
}
return error;
}
bool Platform::GetCachedSharedModule(const ModuleSpec &module_spec,
lldb::ModuleSP &module_sp,
bool *did_create_ptr) {
if (IsHost() || !GetGlobalPlatformProperties().GetUseModuleCache() ||
!GetGlobalPlatformProperties().GetModuleCacheDirectory())
return false;
Log *log = GetLog(LLDBLog::Platform);
// Check local cache for a module.
auto error = m_module_cache->GetAndPut(
GetModuleCacheRoot(), GetCacheHostname(), module_spec,
[this](const ModuleSpec &module_spec,
const FileSpec &tmp_download_file_spec) {
return DownloadModuleSlice(
module_spec.GetFileSpec(), module_spec.GetObjectOffset(),
module_spec.GetObjectSize(), tmp_download_file_spec);
},
[this](const ModuleSP &module_sp,
const FileSpec &tmp_download_file_spec) {
return DownloadSymbolFile(module_sp, tmp_download_file_spec);
},
module_sp, did_create_ptr);
if (error.Success())
return true;
LLDB_LOGF(log, "Platform::%s - module %s not found in local cache: %s",
__FUNCTION__, module_spec.GetUUID().GetAsString().c_str(),
error.AsCString());
return false;
}
Status Platform::DownloadModuleSlice(const FileSpec &src_file_spec,
const uint64_t src_offset,
const uint64_t src_size,
const FileSpec &dst_file_spec) {
Status error;
std::error_code EC;
llvm::raw_fd_ostream dst(dst_file_spec.GetPath(), EC, llvm::sys::fs::OF_None);
if (EC) {
error.SetErrorStringWithFormat("unable to open destination file: %s",
dst_file_spec.GetPath().c_str());
return error;
}
auto src_fd = OpenFile(src_file_spec, File::eOpenOptionReadOnly,
lldb::eFilePermissionsFileDefault, error);
if (error.Fail()) {
error.SetErrorStringWithFormat("unable to open source file: %s",
error.AsCString());
return error;
}
std::vector<char> buffer(1024);
auto offset = src_offset;
uint64_t total_bytes_read = 0;
while (total_bytes_read < src_size) {
const auto to_read = std::min(static_cast<uint64_t>(buffer.size()),
src_size - total_bytes_read);
const uint64_t n_read =
ReadFile(src_fd, offset, &buffer[0], to_read, error);
if (error.Fail())
break;
if (n_read == 0) {
error.SetErrorString("read 0 bytes");
break;
}
offset += n_read;
total_bytes_read += n_read;
dst.write(&buffer[0], n_read);
}
Status close_error;
CloseFile(src_fd, close_error); // Ignoring close error.
return error;
}
Status Platform::DownloadSymbolFile(const lldb::ModuleSP &module_sp,
const FileSpec &dst_file_spec) {
return Status(
"Symbol file downloading not supported by the default platform.");
}
FileSpec Platform::GetModuleCacheRoot() {
auto dir_spec = GetGlobalPlatformProperties().GetModuleCacheDirectory();
dir_spec.AppendPathComponent(GetPluginName());
return dir_spec;
}
const char *Platform::GetCacheHostname() { return GetHostname(); }
const UnixSignalsSP &Platform::GetRemoteUnixSignals() {
static const auto s_default_unix_signals_sp = std::make_shared<UnixSignals>();
return s_default_unix_signals_sp;
}
UnixSignalsSP Platform::GetUnixSignals() {
if (IsHost())
return UnixSignals::CreateForHost();
return GetRemoteUnixSignals();
}
uint32_t Platform::LoadImage(lldb_private::Process *process,
const lldb_private::FileSpec &local_file,
const lldb_private::FileSpec &remote_file,
lldb_private::Status &error) {
if (local_file && remote_file) {
// Both local and remote file was specified. Install the local file to the
// given location.
if (IsRemote() || local_file != remote_file) {
error = Install(local_file, remote_file);
if (error.Fail())
return LLDB_INVALID_IMAGE_TOKEN;
}
return DoLoadImage(process, remote_file, nullptr, error);
}
if (local_file) {
// Only local file was specified. Install it to the current working
// directory.
FileSpec target_file = GetWorkingDirectory();
target_file.AppendPathComponent(local_file.GetFilename().AsCString());
if (IsRemote() || local_file != target_file) {
error = Install(local_file, target_file);
if (error.Fail())
return LLDB_INVALID_IMAGE_TOKEN;
}
return DoLoadImage(process, target_file, nullptr, error);
}
if (remote_file) {
// Only remote file was specified so we don't have to do any copying
return DoLoadImage(process, remote_file, nullptr, error);
}
error.SetErrorString("Neither local nor remote file was specified");
return LLDB_INVALID_IMAGE_TOKEN;
}
uint32_t Platform::DoLoadImage(lldb_private::Process *process,
const lldb_private::FileSpec &remote_file,
const std::vector<std::string> *paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_image) {
error.SetErrorString("LoadImage is not supported on the current platform");
return LLDB_INVALID_IMAGE_TOKEN;
}
uint32_t Platform::LoadImageUsingPaths(lldb_private::Process *process,
const lldb_private::FileSpec &remote_filename,
const std::vector<std::string> &paths,
lldb_private::Status &error,
lldb_private::FileSpec *loaded_path)
{
FileSpec file_to_use;
if (remote_filename.IsAbsolute())
file_to_use = FileSpec(remote_filename.GetFilename().GetStringRef(),
remote_filename.GetPathStyle());
else
file_to_use = remote_filename;
return DoLoadImage(process, file_to_use, &paths, error, loaded_path);
}
Status Platform::UnloadImage(lldb_private::Process *process,
uint32_t image_token) {
return Status("UnloadImage is not supported on the current platform");
}
lldb::ProcessSP Platform::ConnectProcess(llvm::StringRef connect_url,
llvm::StringRef plugin_name,
Debugger &debugger, Target *target,
Status &error) {
return DoConnectProcess(connect_url, plugin_name, debugger, nullptr, target,
error);
}
lldb::ProcessSP Platform::ConnectProcessSynchronous(
llvm::StringRef connect_url, llvm::StringRef plugin_name,
Debugger &debugger, Stream &stream, Target *target, Status &error) {
return DoConnectProcess(connect_url, plugin_name, debugger, &stream, target,
error);
}
lldb::ProcessSP Platform::DoConnectProcess(llvm::StringRef connect_url,
llvm::StringRef plugin_name,
Debugger &debugger, Stream *stream,
Target *target, Status &error) {
error.Clear();
if (!target) {
ArchSpec arch;
if (target && target->GetArchitecture().IsValid())
arch = target->GetArchitecture();
else
arch = Target::GetDefaultArchitecture();
const char *triple = "";
if (arch.IsValid())
triple = arch.GetTriple().getTriple().c_str();
TargetSP new_target_sp;
error = debugger.GetTargetList().CreateTarget(
debugger, "", triple, eLoadDependentsNo, nullptr, new_target_sp);
target = new_target_sp.get();
}
if (!target || error.Fail())
return nullptr;
lldb::ProcessSP process_sp =
target->CreateProcess(debugger.GetListener(), plugin_name, nullptr, true);
if (!process_sp)
return nullptr;
// If this private method is called with a stream we are synchronous.
const bool synchronous = stream != nullptr;
ListenerSP listener_sp(
Listener::MakeListener("lldb.Process.ConnectProcess.hijack"));
if (synchronous)
process_sp->HijackProcessEvents(listener_sp);
error = process_sp->ConnectRemote(connect_url);
if (error.Fail()) {
if (synchronous)
process_sp->RestoreProcessEvents();
return nullptr;
}
if (synchronous) {
EventSP event_sp;
process_sp->WaitForProcessToStop(llvm::None, &event_sp, true, listener_sp,
nullptr);
process_sp->RestoreProcessEvents();
bool pop_process_io_handler = false;
Process::HandleProcessStateChangedEvent(event_sp, stream,
pop_process_io_handler);
}
return process_sp;
}
size_t Platform::ConnectToWaitingProcesses(lldb_private::Debugger &debugger,
lldb_private::Status &error) {
error.Clear();
return 0;
}
size_t Platform::GetSoftwareBreakpointTrapOpcode(Target &target,
BreakpointSite *bp_site) {
ArchSpec arch = target.GetArchitecture();
assert(arch.IsValid());
const uint8_t *trap_opcode = nullptr;
size_t trap_opcode_size = 0;
switch (arch.GetMachine()) {
case llvm::Triple::aarch64_32:
case llvm::Triple::aarch64: {
static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x20, 0xd4};
trap_opcode = g_aarch64_opcode;
trap_opcode_size = sizeof(g_aarch64_opcode);
} break;
case llvm::Triple::arc: {
static const uint8_t g_hex_opcode[] = { 0xff, 0x7f };
trap_opcode = g_hex_opcode;
trap_opcode_size = sizeof(g_hex_opcode);
} break;
// TODO: support big-endian arm and thumb trap codes.
case llvm::Triple::arm: {
// The ARM reference recommends the use of 0xe7fddefe and 0xdefe but the
// linux kernel does otherwise.
static const uint8_t g_arm_breakpoint_opcode[] = {0xf0, 0x01, 0xf0, 0xe7};
static const uint8_t g_thumb_breakpoint_opcode[] = {0x01, 0xde};
lldb::BreakpointLocationSP bp_loc_sp(bp_site->GetOwnerAtIndex(0));
AddressClass addr_class = AddressClass::eUnknown;
if (bp_loc_sp) {
addr_class = bp_loc_sp->GetAddress().GetAddressClass();
if (addr_class == AddressClass::eUnknown &&
(bp_loc_sp->GetAddress().GetFileAddress() & 1))
addr_class = AddressClass::eCodeAlternateISA;
}
if (addr_class == AddressClass::eCodeAlternateISA) {
trap_opcode = g_thumb_breakpoint_opcode;
trap_opcode_size = sizeof(g_thumb_breakpoint_opcode);
} else {
trap_opcode = g_arm_breakpoint_opcode;
trap_opcode_size = sizeof(g_arm_breakpoint_opcode);
}
} break;
case llvm::Triple::avr: {
static const uint8_t g_hex_opcode[] = {0x98, 0x95};
trap_opcode = g_hex_opcode;
trap_opcode_size = sizeof(g_hex_opcode);
} break;
case llvm::Triple::mips:
case llvm::Triple::mips64: {
static const uint8_t g_hex_opcode[] = {0x00, 0x00, 0x00, 0x0d};
trap_opcode = g_hex_opcode;
trap_opcode_size = sizeof(g_hex_opcode);
} break;
case llvm::Triple::mipsel:
case llvm::Triple::mips64el: {
static const uint8_t g_hex_opcode[] = {0x0d, 0x00, 0x00, 0x00};
trap_opcode = g_hex_opcode;
trap_opcode_size = sizeof(g_hex_opcode);
} break;
case llvm::Triple::systemz: {
static const uint8_t g_hex_opcode[] = {0x00, 0x01};
trap_opcode = g_hex_opcode;
trap_opcode_size = sizeof(g_hex_opcode);
} break;
case llvm::Triple::hexagon: {
static const uint8_t g_hex_opcode[] = {0x0c, 0xdb, 0x00, 0x54};
trap_opcode = g_hex_opcode;
trap_opcode_size = sizeof(g_hex_opcode);
} break;
case llvm::Triple::ppc:
case llvm::Triple::ppc64: {
static const uint8_t g_ppc_opcode[] = {0x7f, 0xe0, 0x00, 0x08};
trap_opcode = g_ppc_opcode;
trap_opcode_size = sizeof(g_ppc_opcode);
} break;
case llvm::Triple::ppc64le: {
static const uint8_t g_ppc64le_opcode[] = {0x08, 0x00, 0xe0, 0x7f}; // trap
trap_opcode = g_ppc64le_opcode;
trap_opcode_size = sizeof(g_ppc64le_opcode);
} break;
case llvm::Triple::x86:
case llvm::Triple::x86_64: {
static const uint8_t g_i386_opcode[] = {0xCC};
trap_opcode = g_i386_opcode;
trap_opcode_size = sizeof(g_i386_opcode);
} break;
default:
return 0;
}
assert(bp_site);
if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size))
return trap_opcode_size;
return 0;
}
CompilerType Platform::GetSiginfoType(const llvm::Triple& triple) {
return CompilerType();
}
Args Platform::GetExtraStartupCommands() {
return {};
}
PlatformSP PlatformList::GetOrCreate(llvm::StringRef name) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
for (const PlatformSP &platform_sp : m_platforms) {
if (platform_sp->GetName() == name)
return platform_sp;
}
return Create(name);
}
PlatformSP PlatformList::GetOrCreate(const ArchSpec &arch,
const ArchSpec &process_host_arch,
ArchSpec *platform_arch_ptr,
Status &error) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
// First try exact arch matches across all platforms already created
for (const auto &platform_sp : m_platforms) {
if (platform_sp->IsCompatibleArchitecture(arch, process_host_arch, true,
platform_arch_ptr))
return platform_sp;
}
// Next try compatible arch matches across all platforms already created
for (const auto &platform_sp : m_platforms) {
if (platform_sp->IsCompatibleArchitecture(arch, process_host_arch, false,
platform_arch_ptr))
return platform_sp;
}
PlatformCreateInstance create_callback;
// First try exact arch matches across all platform plug-ins
uint32_t idx;
for (idx = 0;
(create_callback = PluginManager::GetPlatformCreateCallbackAtIndex(idx));
++idx) {
PlatformSP platform_sp = create_callback(false, &arch);
if (platform_sp && platform_sp->IsCompatibleArchitecture(
arch, process_host_arch, true, platform_arch_ptr)) {
m_platforms.push_back(platform_sp);
return platform_sp;
}
}
// Next try compatible arch matches across all platform plug-ins
for (idx = 0;
(create_callback = PluginManager::GetPlatformCreateCallbackAtIndex(idx));
++idx) {
PlatformSP platform_sp = create_callback(false, &arch);
if (platform_sp && platform_sp->IsCompatibleArchitecture(
arch, process_host_arch, false, platform_arch_ptr)) {
m_platforms.push_back(platform_sp);
return platform_sp;
}
}
if (platform_arch_ptr)
platform_arch_ptr->Clear();
return nullptr;
}
PlatformSP PlatformList::GetOrCreate(const ArchSpec &arch,
const ArchSpec &process_host_arch,
ArchSpec *platform_arch_ptr) {
Status error;
if (arch.IsValid())
return GetOrCreate(arch, process_host_arch, platform_arch_ptr, error);
return nullptr;
}
PlatformSP PlatformList::GetOrCreate(llvm::ArrayRef<ArchSpec> archs,
const ArchSpec &process_host_arch,
std::vector<PlatformSP> &candidates) {
candidates.clear();
candidates.reserve(archs.size());
if (archs.empty())
return nullptr;
PlatformSP host_platform_sp = Platform::GetHostPlatform();
// Prefer the selected platform if it matches at least one architecture.
if (m_selected_platform_sp) {
for (const ArchSpec &arch : archs) {
if (m_selected_platform_sp->IsCompatibleArchitecture(
arch, process_host_arch, false, nullptr))
return m_selected_platform_sp;
}
}
// Prefer the host platform if it matches at least one architecture.
if (host_platform_sp) {
for (const ArchSpec &arch : archs) {
if (host_platform_sp->IsCompatibleArchitecture(arch, process_host_arch,
false, nullptr))
return host_platform_sp;
}
}
// Collect a list of candidate platforms for the architectures.
for (const ArchSpec &arch : archs) {
if (PlatformSP platform = GetOrCreate(arch, process_host_arch, nullptr))
candidates.push_back(platform);
}
// The selected or host platform didn't match any of the architectures. If
// the same platform supports all architectures then that's the obvious next
// best thing.
if (candidates.size() == archs.size()) {
if (std::all_of(candidates.begin(), candidates.end(),
[&](const PlatformSP &p) -> bool {
return p->GetName() == candidates.front()->GetName();
})) {
return candidates.front();
}
}
// At this point we either have no platforms that match the given
// architectures or multiple platforms with no good way to disambiguate
// between them.
return nullptr;
}
PlatformSP PlatformList::Create(llvm::StringRef name) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
PlatformSP platform_sp = Platform::Create(name);
m_platforms.push_back(platform_sp);
return platform_sp;
}