diff options
Diffstat (limited to 'source/Host')
46 files changed, 8500 insertions, 0 deletions
diff --git a/source/Host/CMakeLists.txt b/source/Host/CMakeLists.txt new file mode 100644 index 000000000000..776fcfb44ffa --- /dev/null +++ b/source/Host/CMakeLists.txt @@ -0,0 +1,190 @@ +macro(add_host_subdirectory group) + list(APPEND HOST_SOURCES ${ARGN}) + source_group(${group} FILES ${ARGN}) +endmacro() + +add_host_subdirectory(common + common/Condition.cpp + common/File.cpp + common/FileCache.cpp + common/FileSpec.cpp + common/FileSystem.cpp + common/GetOptInc.cpp + common/Host.cpp + common/HostInfoBase.cpp + common/HostNativeThreadBase.cpp + common/HostProcess.cpp + common/HostThread.cpp + common/IOObject.cpp + common/LockFileBase.cpp + common/Mutex.cpp + common/MonitoringProcessLauncher.cpp + common/NativeBreakpoint.cpp + common/NativeBreakpointList.cpp + common/NativeWatchpointList.cpp + common/NativeProcessProtocol.cpp + common/NativeRegisterContext.cpp + common/NativeRegisterContextRegisterInfo.cpp + common/NativeThreadProtocol.cpp + common/OptionParser.cpp + common/PipeBase.cpp + common/ProcessRunLock.cpp + common/Socket.cpp + common/SocketAddress.cpp + common/SoftwareBreakpoint.cpp + common/StringConvert.cpp + common/Symbols.cpp + common/TCPSocket.cpp + common/Terminal.cpp + common/ThisThread.cpp + common/ThreadLauncher.cpp + common/TimeValue.cpp + common/XML.cpp + common/UDPSocket.cpp + ) + +# Keep track of whether we want to provide a define for the +# Python's architecture-specific lib path (i.e. where a +# Python lldb module would go). +set (get_python_libdir 0) + +if (NOT LLDB_DISABLE_LIBEDIT) + add_host_subdirectory(common + common/Editline.cpp + ) +endif() + +add_host_subdirectory(posix + posix/ConnectionFileDescriptorPosix.cpp + ) + +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + add_host_subdirectory(windows + windows/Condition.cpp + windows/ConnectionGenericFileWindows.cpp + windows/EditLineWin.cpp + windows/FileSystem.cpp + windows/Host.cpp + windows/HostInfoWindows.cpp + windows/HostProcessWindows.cpp + windows/HostThreadWindows.cpp + windows/LockFileWindows.cpp + windows/Mutex.cpp + windows/PipeWindows.cpp + windows/ProcessLauncherWindows.cpp + windows/ProcessRunLock.cpp + windows/ThisThread.cpp + windows/Windows.cpp + ) +else() + if (NOT LLDB_DISABLE_PYTHON) + # We'll grab the arch-specific python libdir on POSIX systems. + set (get_python_libdir 1) + endif() + + add_host_subdirectory(posix + posix/DomainSocket.cpp + posix/FileSystem.cpp + posix/HostInfoPosix.cpp + posix/HostProcessPosix.cpp + posix/HostThreadPosix.cpp + posix/LockFilePosix.cpp + posix/MainLoopPosix.cpp + posix/PipePosix.cpp + ) + + if (NOT __ANDROID_NDK__) + add_host_subdirectory(posix + posix/ProcessLauncherPosix.cpp + ) + endif() + + if (CMAKE_SYSTEM_NAME MATCHES "Darwin") + include_directories(SYSTEM ${LIBXML2_INCLUDE_DIR}) + add_host_subdirectory(macosx + macosx/Host.mm + macosx/HostInfoMacOSX.mm + macosx/HostThreadMacOSX.mm + macosx/Symbols.cpp + macosx/ThisThread.cpp + macosx/cfcpp/CFCBundle.cpp + macosx/cfcpp/CFCData.cpp + macosx/cfcpp/CFCMutableArray.cpp + macosx/cfcpp/CFCMutableDictionary.cpp + macosx/cfcpp/CFCMutableSet.cpp + macosx/cfcpp/CFCString.cpp + ) + + elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") + if (__ANDROID_NDK__) + add_host_subdirectory(android + android/HostInfoAndroid.cpp + android/LibcGlue.cpp + android/ProcessLauncherAndroid.cpp + linux/AbstractSocket.cpp + linux/Host.cpp + linux/HostInfoLinux.cpp + linux/HostThreadLinux.cpp + linux/LibcGlue.cpp + linux/ThisThread.cpp + ) + else() + add_host_subdirectory(linux + linux/AbstractSocket.cpp + linux/Host.cpp + linux/HostInfoLinux.cpp + linux/HostThreadLinux.cpp + linux/LibcGlue.cpp + linux/ThisThread.cpp + ) + endif() + + elseif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + add_host_subdirectory(freebsd + freebsd/Host.cpp + freebsd/HostInfoFreeBSD.cpp + freebsd/HostThreadFreeBSD.cpp + freebsd/ThisThread.cpp + ) + + elseif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + add_host_subdirectory(netbsd + netbsd/Host.cpp + netbsd/HostInfoNetBSD.cpp + netbsd/HostThreadNetBSD.cpp + netbsd/ThisThread.cpp + ) + endif() +endif() + +if (${get_python_libdir}) + # Call a python script to gather the arch-specific libdir for + # modules like the lldb module. + execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/get_relative_lib_dir.py + RESULT_VARIABLE get_libdir_status + OUTPUT_VARIABLE relative_libdir + ) + if (get_libdir_status EQUAL 0) + add_definitions(-DLLDB_PYTHON_RELATIVE_LIBDIR="${relative_libdir}") + endif() +endif() + +if (${get_python_libdir}) + # Call a python script to gather the arch-specific libdir for + # modules like the lldb module. + execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/get_relative_lib_dir.py + RESULT_VARIABLE get_libdir_status + OUTPUT_VARIABLE relative_libdir + ) + if (get_libdir_status EQUAL 0) + add_definitions(-DLLDB_PYTHON_RELATIVE_LIBDIR="${relative_libdir}") + endif() +endif() + +add_lldb_library(lldbHost ${HOST_SOURCES}) + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") +target_link_libraries(lldbHost kvm) +endif () diff --git a/source/Host/Makefile b/source/Host/Makefile new file mode 100644 index 000000000000..da90c8c364a3 --- /dev/null +++ b/source/Host/Makefile @@ -0,0 +1,65 @@ +##===- source/Host/Makefile --------------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +LLDB_LEVEL := ../.. +LEVEL := $(LLDB_LEVEL)/../.. + +include $(LEVEL)/Makefile.config + +define DIR_SOURCES +SOURCES += $$(addprefix $(1)/,$$(notdir $$(wildcard $$(PROJ_SRC_DIR)/$(1)/*.cpp \ + $$(PROJ_SRC_DIR)/*.cc $$(PROJ_SRC_DIR)/$(1)/*.c $$(PROJ_SRC_DIR)/$(1)/*.mm))) +endef + +$(eval $(call DIR_SOURCES,common)) + +ifeq ($(HOST_OS),Darwin) +$(eval $(call DIR_SOURCES,posix)) +$(eval $(call DIR_SOURCES,macosx)) +CFCPP_SOURCES = \ + $(addprefix macosx/cfcpp/,$(notdir $(wildcard $(PROJ_SRC_DIR)/macosx/cfcpp/*.cpp))) +SOURCES += $(CFCPP_SOURCES) + +CFCPP_BaseNameSources := $(sort $(basename $(CFCPP_SOURCES))) +CFCPP_OBJECTS := $(CFCPP_BaseNameSources:%=$(ObjDir)/%.o) + +# Make sure the cfcpp output directory exists +$(CFCPP_OBJECTS): $(ObjDir)/cfcpp/.dir +endif + +ifeq ($(HOST_OS),Linux) +$(eval $(call DIR_SOURCES,posix)) +$(eval $(call DIR_SOURCES,linux)) +endif + +ifneq (,$(filter $(HOST_OS), FreeBSD GNU/kFreeBSD)) +$(eval $(call DIR_SOURCES,posix)) +$(eval $(call DIR_SOURCES,freebsd)) +endif + +ifeq ($(HOST_OS),NetBSD) +$(eval $(call DIR_SOURCES,posix)) +$(eval $(call DIR_SOURCES,netbsd)) +endif + +ifeq ($(HOST_OS),MingW) +$(eval $(call DIR_SOURCES,windows)) +SOURCES += posix/ConnectionFileDescriptorPosix.cpp +endif + +ifeq ($(HOST_OS),Android) +$(eval $(call DIR_SOURCES,posix)) +$(eval $(call DIR_SOURCES,linux)) +$(eval $(call DIR_SOURCES,android)) +endif + +LIBRARYNAME := lldbHost +BUILD_ARCHIVE = 1 + +include $(LLDB_LEVEL)/Makefile diff --git a/source/Host/android/HostInfoAndroid.cpp b/source/Host/android/HostInfoAndroid.cpp new file mode 100644 index 000000000000..3fa50ec8ddfd --- /dev/null +++ b/source/Host/android/HostInfoAndroid.cpp @@ -0,0 +1,104 @@ +//===-- HostInfoAndroid.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/android/HostInfoAndroid.h" +#include "lldb/Host/linux/HostInfoLinux.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" + +using namespace lldb_private; +using namespace llvm; + +void +HostInfoAndroid::ComputeHostArchitectureSupport(ArchSpec &arch_32, ArchSpec &arch_64) +{ + HostInfoLinux::ComputeHostArchitectureSupport(arch_32, arch_64); + + if (arch_32.IsValid()) + { + arch_32.GetTriple().setEnvironment(llvm::Triple::Android); + } + if (arch_64.IsValid()) + { + arch_64.GetTriple().setEnvironment(llvm::Triple::Android); + } +} + +FileSpec +HostInfoAndroid::GetDefaultShell() +{ + return FileSpec("/system/bin/sh", false); +} + +FileSpec +HostInfoAndroid::ResolveLibraryPath(const std::string& module_path, const ArchSpec& arch) +{ + static const char* const ld_library_path_separator = ":"; + static const char* const default_lib32_path[] = { + "/vendor/lib", + "/system/lib", + nullptr + }; + static const char* const default_lib64_path[] = { + "/vendor/lib64", + "/system/lib64", + nullptr + }; + + if (module_path.empty() || module_path[0] == '/') + return FileSpec(module_path.c_str(), true); + + SmallVector<StringRef, 4> ld_paths; + + if (const char* ld_library_path = ::getenv("LD_LIBRARY_PATH")) + StringRef(ld_library_path).split(ld_paths, StringRef(ld_library_path_separator), -1, false); + + const char* const* default_lib_path = nullptr; + switch (arch.GetAddressByteSize()) + { + case 4: + default_lib_path = default_lib32_path; + break; + case 8: + default_lib_path = default_lib64_path; + break; + default: + assert(false && "Unknown address byte size"); + return FileSpec(); + } + + for(const char* const* it = default_lib_path; *it; ++it) + ld_paths.push_back(StringRef(*it)); + + for (const StringRef& path : ld_paths) + { + FileSpec file_candidate(path.str().c_str(), true); + file_candidate.AppendPathComponent(module_path.c_str()); + + if (file_candidate.Exists()) + return file_candidate; + } + + return FileSpec(); +} + +bool +HostInfoAndroid::ComputeTempFileBaseDirectory(FileSpec &file_spec) +{ + bool success = HostInfoLinux::ComputeTempFileBaseDirectory(file_spec); + + // On Android, there is no path which is guaranteed to be writable. If the user has not + // provided a path via an environment variable, the generic algorithm will deduce /tmp, which + // is plain wrong. In that case we have an invalid directory, we substitute the path with + // /data/local/tmp, which is correct at least in some cases (i.e., when running as shell user). + if (!success || !file_spec.Exists()) + file_spec = FileSpec("/data/local/tmp", false); + + return file_spec.Exists(); +} diff --git a/source/Host/android/LibcGlue.cpp b/source/Host/android/LibcGlue.cpp new file mode 100644 index 000000000000..3842fb6c2a8e --- /dev/null +++ b/source/Host/android/LibcGlue.cpp @@ -0,0 +1,40 @@ +//===-- LibcGlue.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This files adds functions missing from libc on earlier versions of Android + +#include <android/api-level.h> + +#include <sys/syscall.h> + +#if __ANDROID_API__ < 21 + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> + +#include "lldb/Host/Time.h" + +time_t timegm(struct tm* t) +{ + return (time_t) timegm64(t); +} + +int signalfd (int fd, const sigset_t *mask, int flags) +{ + return syscall(__NR_signalfd4, fd, mask, _NSIG / 8, flags); +} + +int posix_openpt(int flags) +{ + return open("/dev/ptmx", flags); +} + +#endif diff --git a/source/Host/android/ProcessLauncherAndroid.cpp b/source/Host/android/ProcessLauncherAndroid.cpp new file mode 100644 index 000000000000..24eebc8c030f --- /dev/null +++ b/source/Host/android/ProcessLauncherAndroid.cpp @@ -0,0 +1,108 @@ +//===-- ProcessLauncherAndroid.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/FileSpec.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/android/ProcessLauncherAndroid.h" + +#include "lldb/Target/ProcessLaunchInfo.h" + +#include <limits.h> + +using namespace lldb; +using namespace lldb_private; + +static bool +DupDescriptor(const FileSpec &file_spec, int fd, int flags) +{ + int target_fd = ::open(file_spec.GetCString(), flags, 0666); + + if (target_fd == -1) + return false; + + if (::dup2(target_fd, fd) == -1) + return false; + + return (::close(target_fd) == -1) ? false : true; +} + +// If there is no PATH variable specified inside the environment then set the path to /system/bin. +// It is required because the default path used by execve() is wrong on android. +static void +FixupEnvironment(Args& env) +{ + static const char* path = "PATH="; + static const int path_len = ::strlen(path); + for (const char** args = env.GetConstArgumentVector(); *args; ++args) + if (::strncmp(path, *args, path_len) == 0) + return; + env.AppendArgument("PATH=/system/bin"); +} + +HostProcess +ProcessLauncherAndroid::LaunchProcess(const ProcessLaunchInfo &launch_info, Error &error) +{ + // TODO: Handle other launch parameters specified in launc_info + + char exe_path[PATH_MAX]; + launch_info.GetExecutableFile().GetPath(exe_path, sizeof(exe_path)); + + lldb::pid_t pid = ::fork(); + if (pid == static_cast<lldb::pid_t>(-1)) + { + // Fork failed + error.SetErrorStringWithFormat("Fork failed with error message: %s", strerror(errno)); + return HostProcess(LLDB_INVALID_PROCESS_ID); + } + else if (pid == 0) + { + if (const lldb_private::FileAction *file_action = launch_info.GetFileActionForFD(STDIN_FILENO)) { + FileSpec file_spec = file_action->GetFileSpec(); + if (file_spec) + if (!DupDescriptor(file_spec, STDIN_FILENO, O_RDONLY)) + exit(-1); + } + + if (const lldb_private::FileAction *file_action = launch_info.GetFileActionForFD(STDOUT_FILENO)) { + FileSpec file_spec = file_action->GetFileSpec(); + if (file_spec) + if (!DupDescriptor(file_spec, STDOUT_FILENO, O_WRONLY | O_CREAT | O_TRUNC)) + exit(-1); + } + + if (const lldb_private::FileAction *file_action = launch_info.GetFileActionForFD(STDERR_FILENO)) { + FileSpec file_spec = file_action->GetFileSpec(); + if (file_spec) + if (!DupDescriptor(file_spec, STDERR_FILENO, O_WRONLY | O_CREAT | O_TRUNC)) + exit(-1); + } + + // Child process + const char **argv = launch_info.GetArguments().GetConstArgumentVector(); + + Args env = launch_info.GetEnvironmentEntries(); + FixupEnvironment(env); + const char **envp = env.GetConstArgumentVector(); + + FileSpec working_dir = launch_info.GetWorkingDirectory(); + if (working_dir) + { + if (::chdir(working_dir.GetCString()) != 0) + exit(-1); + } + + execve(argv[0], + const_cast<char *const *>(argv), + const_cast<char *const *>(envp)); + exit(-1); + } + + return HostProcess(pid); +} diff --git a/source/Host/linux/AbstractSocket.cpp b/source/Host/linux/AbstractSocket.cpp new file mode 100644 index 000000000000..8ac0107123bb --- /dev/null +++ b/source/Host/linux/AbstractSocket.cpp @@ -0,0 +1,31 @@ +//===-- AbstractSocket.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/linux/AbstractSocket.h" + +#include "llvm/ADT/StringRef.h" + +using namespace lldb; +using namespace lldb_private; + +AbstractSocket::AbstractSocket(bool child_processes_inherit, Error &error) + : DomainSocket(ProtocolUnixAbstract, child_processes_inherit, error) +{ +} + +size_t +AbstractSocket::GetNameOffset() const +{ + return 1; +} + +void +AbstractSocket::DeleteSocketFile(llvm::StringRef name) +{ +} diff --git a/source/Host/linux/Host.cpp b/source/Host/linux/Host.cpp new file mode 100644 index 000000000000..cb7369fe7aec --- /dev/null +++ b/source/Host/linux/Host.cpp @@ -0,0 +1,393 @@ +//===-- source/Host/linux/Host.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +#include <stdio.h> +#include <sys/utsname.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> + +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Target/Process.h" + +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#ifdef __ANDROID_NDK__ +#include "lldb/Host/android/Android.h" +#endif +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" + +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Symbol/ObjectFile.h" +#include "Plugins/Process/Linux/ProcFileReader.h" + +using namespace lldb; +using namespace lldb_private; + +typedef enum ProcessStateFlags +{ + eProcessStateRunning = (1u << 0), // Running + eProcessStateSleeping = (1u << 1), // Sleeping in an interruptible wait + eProcessStateWaiting = (1u << 2), // Waiting in an uninterruptible disk sleep + eProcessStateZombie = (1u << 3), // Zombie + eProcessStateTracedOrStopped = (1u << 4), // Traced or stopped (on a signal) + eProcessStatePaging = (1u << 5) // Paging +} ProcessStateFlags; + +typedef struct ProcessStatInfo +{ + lldb::pid_t ppid; // Parent Process ID + uint32_t fProcessState; // ProcessStateFlags +} ProcessStatInfo; + +// Get the process info with additional information from /proc/$PID/stat (like process state, and tracer pid). +static bool GetProcessAndStatInfo (lldb::pid_t pid, ProcessInstanceInfo &process_info, ProcessStatInfo &stat_info, lldb::pid_t &tracerpid); + +static bool +ReadProcPseudoFileStat (lldb::pid_t pid, ProcessStatInfo& stat_info) +{ + // Read the /proc/$PID/stat file. + lldb::DataBufferSP buf_sp = process_linux::ProcFileReader::ReadIntoDataBuffer (pid, "stat"); + + // The filename of the executable is stored in parenthesis right after the pid. We look for the closing + // parenthesis for the filename and work from there in case the name has something funky like ')' in it. + const char *filename_end = strrchr ((const char *)buf_sp->GetBytes(), ')'); + if (filename_end) + { + char state = '\0'; + int ppid = LLDB_INVALID_PROCESS_ID; + + // Read state and ppid. + sscanf (filename_end + 1, " %c %d", &state, &ppid); + + stat_info.ppid = ppid; + + switch (state) + { + case 'R': + stat_info.fProcessState |= eProcessStateRunning; + break; + case 'S': + stat_info.fProcessState |= eProcessStateSleeping; + break; + case 'D': + stat_info.fProcessState |= eProcessStateWaiting; + break; + case 'Z': + stat_info.fProcessState |= eProcessStateZombie; + break; + case 'T': + stat_info.fProcessState |= eProcessStateTracedOrStopped; + break; + case 'W': + stat_info.fProcessState |= eProcessStatePaging; + break; + } + + return true; + } + + return false; +} + +static void +GetLinuxProcessUserAndGroup (lldb::pid_t pid, ProcessInstanceInfo &process_info, lldb::pid_t &tracerpid) +{ + tracerpid = 0; + uint32_t rUid = UINT32_MAX; // Real User ID + uint32_t eUid = UINT32_MAX; // Effective User ID + uint32_t rGid = UINT32_MAX; // Real Group ID + uint32_t eGid = UINT32_MAX; // Effective Group ID + + // Read the /proc/$PID/status file and parse the Uid:, Gid:, and TracerPid: fields. + lldb::DataBufferSP buf_sp = process_linux::ProcFileReader::ReadIntoDataBuffer (pid, "status"); + + static const char uid_token[] = "Uid:"; + char *buf_uid = strstr ((char *)buf_sp->GetBytes(), uid_token); + if (buf_uid) + { + // Real, effective, saved set, and file system UIDs. Read the first two. + buf_uid += sizeof(uid_token); + rUid = strtol (buf_uid, &buf_uid, 10); + eUid = strtol (buf_uid, &buf_uid, 10); + } + + static const char gid_token[] = "Gid:"; + char *buf_gid = strstr ((char *)buf_sp->GetBytes(), gid_token); + if (buf_gid) + { + // Real, effective, saved set, and file system GIDs. Read the first two. + buf_gid += sizeof(gid_token); + rGid = strtol (buf_gid, &buf_gid, 10); + eGid = strtol (buf_gid, &buf_gid, 10); + } + + static const char tracerpid_token[] = "TracerPid:"; + char *buf_tracerpid = strstr((char *)buf_sp->GetBytes(), tracerpid_token); + if (buf_tracerpid) + { + // Tracer PID. 0 if we're not being debugged. + buf_tracerpid += sizeof(tracerpid_token); + tracerpid = strtol (buf_tracerpid, &buf_tracerpid, 10); + } + + process_info.SetUserID (rUid); + process_info.SetEffectiveUserID (eUid); + process_info.SetGroupID (rGid); + process_info.SetEffectiveGroupID (eGid); +} + +lldb::DataBufferSP +Host::GetAuxvData(lldb_private::Process *process) +{ + return process_linux::ProcFileReader::ReadIntoDataBuffer (process->GetID(), "auxv"); +} + +lldb::DataBufferSP +Host::GetAuxvData (lldb::pid_t pid) +{ + return process_linux::ProcFileReader::ReadIntoDataBuffer (pid, "auxv"); +} + +static bool +IsDirNumeric(const char *dname) +{ + for (; *dname; dname++) + { + if (!isdigit (*dname)) + return false; + } + return true; +} + +uint32_t +Host::FindProcesses (const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &process_infos) +{ + static const char procdir[] = "/proc/"; + + DIR *dirproc = opendir (procdir); + if (dirproc) + { + struct dirent *direntry = NULL; + const uid_t our_uid = getuid(); + const lldb::pid_t our_pid = getpid(); + bool all_users = match_info.GetMatchAllUsers(); + + while ((direntry = readdir (dirproc)) != NULL) + { + if (direntry->d_type != DT_DIR || !IsDirNumeric (direntry->d_name)) + continue; + + lldb::pid_t pid = atoi (direntry->d_name); + + // Skip this process. + if (pid == our_pid) + continue; + + lldb::pid_t tracerpid; + ProcessStatInfo stat_info; + ProcessInstanceInfo process_info; + + if (!GetProcessAndStatInfo (pid, process_info, stat_info, tracerpid)) + continue; + + // Skip if process is being debugged. + if (tracerpid != 0) + continue; + + // Skip zombies. + if (stat_info.fProcessState & eProcessStateZombie) + continue; + + // Check for user match if we're not matching all users and not running as root. + if (!all_users && (our_uid != 0) && (process_info.GetUserID() != our_uid)) + continue; + + if (match_info.Matches (process_info)) + { + process_infos.Append (process_info); + } + } + + closedir (dirproc); + } + + return process_infos.GetSize(); +} + +bool +Host::FindProcessThreads (const lldb::pid_t pid, TidMap &tids_to_attach) +{ + bool tids_changed = false; + static const char procdir[] = "/proc/"; + static const char taskdir[] = "/task/"; + std::string process_task_dir = procdir + std::to_string(pid) + taskdir; + DIR *dirproc = opendir (process_task_dir.c_str()); + + if (dirproc) + { + struct dirent *direntry = NULL; + while ((direntry = readdir (dirproc)) != NULL) + { + if (direntry->d_type != DT_DIR || !IsDirNumeric (direntry->d_name)) + continue; + + lldb::tid_t tid = atoi(direntry->d_name); + TidMap::iterator it = tids_to_attach.find(tid); + if (it == tids_to_attach.end()) + { + tids_to_attach.insert(TidPair(tid, false)); + tids_changed = true; + } + } + closedir (dirproc); + } + + return tids_changed; +} + +static bool +GetELFProcessCPUType (const char *exe_path, ProcessInstanceInfo &process_info) +{ + // Clear the architecture. + process_info.GetArchitecture().Clear(); + + ModuleSpecList specs; + FileSpec filespec (exe_path, false); + const size_t num_specs = ObjectFile::GetModuleSpecifications (filespec, 0, 0, specs); + // GetModuleSpecifications() could fail if the executable has been deleted or is locked. + // But it shouldn't return more than 1 architecture. + assert(num_specs <= 1 && "Linux plugin supports only a single architecture"); + if (num_specs == 1) + { + ModuleSpec module_spec; + if (specs.GetModuleSpecAtIndex (0, module_spec) && module_spec.GetArchitecture().IsValid()) + { + process_info.GetArchitecture () = module_spec.GetArchitecture(); + return true; + } + } + return false; +} + +static bool +GetProcessAndStatInfo (lldb::pid_t pid, ProcessInstanceInfo &process_info, ProcessStatInfo &stat_info, lldb::pid_t &tracerpid) +{ + tracerpid = 0; + process_info.Clear(); + ::memset (&stat_info, 0, sizeof(stat_info)); + stat_info.ppid = LLDB_INVALID_PROCESS_ID; + + // Use special code here because proc/[pid]/exe is a symbolic link. + char link_path[PATH_MAX]; + char exe_path[PATH_MAX] = ""; + if (snprintf (link_path, PATH_MAX, "/proc/%" PRIu64 "/exe", pid) <= 0) + return false; + + ssize_t len = readlink (link_path, exe_path, sizeof(exe_path) - 1); + if (len <= 0) + return false; + + // readlink does not append a null byte. + exe_path[len] = 0; + + // If the binary has been deleted, the link name has " (deleted)" appended. + // Remove if there. + static const ssize_t deleted_len = strlen(" (deleted)"); + if (len > deleted_len && + !strcmp(exe_path + len - deleted_len, " (deleted)")) + { + exe_path[len - deleted_len] = 0; + } + else + { + GetELFProcessCPUType (exe_path, process_info); + } + + process_info.SetProcessID(pid); + process_info.GetExecutableFile().SetFile(exe_path, false); + process_info.GetArchitecture().MergeFrom(HostInfo::GetArchitecture()); + + lldb::DataBufferSP buf_sp; + + // Get the process environment. + buf_sp = process_linux::ProcFileReader::ReadIntoDataBuffer(pid, "environ"); + Args &info_env = process_info.GetEnvironmentEntries(); + char *next_var = (char *)buf_sp->GetBytes(); + char *end_buf = next_var + buf_sp->GetByteSize(); + while (next_var < end_buf && 0 != *next_var) + { + info_env.AppendArgument(next_var); + next_var += strlen(next_var) + 1; + } + + // Get the command line used to start the process. + buf_sp = process_linux::ProcFileReader::ReadIntoDataBuffer(pid, "cmdline"); + + // Grab Arg0 first, if there is one. + char *cmd = (char *)buf_sp->GetBytes(); + if (cmd) + { + process_info.SetArg0(cmd); + + // Now process any remaining arguments. + Args &info_args = process_info.GetArguments(); + char *next_arg = cmd + strlen(cmd) + 1; + end_buf = cmd + buf_sp->GetByteSize(); + while (next_arg < end_buf && 0 != *next_arg) + { + info_args.AppendArgument(next_arg); + next_arg += strlen(next_arg) + 1; + } + } + + // Read /proc/$PID/stat to get our parent pid. + if (ReadProcPseudoFileStat (pid, stat_info)) + { + process_info.SetParentProcessID (stat_info.ppid); + } + + // Get User and Group IDs and get tracer pid. + GetLinuxProcessUserAndGroup (pid, process_info, tracerpid); + + return true; +} + +bool +Host::GetProcessInfo (lldb::pid_t pid, ProcessInstanceInfo &process_info) +{ + lldb::pid_t tracerpid; + ProcessStatInfo stat_info; + + return GetProcessAndStatInfo (pid, process_info, stat_info, tracerpid); +} + +size_t +Host::GetEnvironment (StringList &env) +{ + char **host_env = environ; + char *env_entry; + size_t i; + for (i=0; (env_entry = host_env[i]) != NULL; ++i) + env.AppendString(env_entry); + return i; +} + +Error +Host::ShellExpandArguments (ProcessLaunchInfo &launch_info) +{ + return Error("unimplemented"); +} diff --git a/source/Host/linux/HostInfoLinux.cpp b/source/Host/linux/HostInfoLinux.cpp new file mode 100644 index 000000000000..4732a2a571b6 --- /dev/null +++ b/source/Host/linux/HostInfoLinux.cpp @@ -0,0 +1,282 @@ +//===-- HostInfoLinux.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Log.h" +#include "lldb/Host/linux/HostInfoLinux.h" + +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <sys/utsname.h> + +#include <algorithm> +#include <mutex> // std::once + +using namespace lldb_private; + +namespace +{ +struct HostInfoLinuxFields +{ + HostInfoLinuxFields() + : m_os_major(0) + , m_os_minor(0) + , m_os_update(0) + { + } + + std::string m_distribution_id; + uint32_t m_os_major; + uint32_t m_os_minor; + uint32_t m_os_update; +}; + +HostInfoLinuxFields *g_fields = nullptr; +} + +void +HostInfoLinux::Initialize() +{ + HostInfoPosix::Initialize(); + + g_fields = new HostInfoLinuxFields(); +} + +uint32_t +HostInfoLinux::GetMaxThreadNameLength() +{ + return 16; +} + +bool +HostInfoLinux::GetOSVersion(uint32_t &major, uint32_t &minor, uint32_t &update) +{ + static bool success = false; + static std::once_flag g_once_flag; + std::call_once(g_once_flag, []() { + + struct utsname un; + if (uname(&un) == 0) + { + int status = sscanf(un.release, "%u.%u.%u", &g_fields->m_os_major, &g_fields->m_os_minor, &g_fields->m_os_update); + if (status == 3) + success = true; + else + { + // Some kernels omit the update version, so try looking for just "X.Y" and + // set update to 0. + g_fields->m_os_update = 0; + status = sscanf(un.release, "%u.%u", &g_fields->m_os_major, &g_fields->m_os_minor); + if (status == 2) + success = true; + } + } + }); + + + major = g_fields->m_os_major; + minor = g_fields->m_os_minor; + update = g_fields->m_os_update; + return success; +} + +bool +HostInfoLinux::GetOSBuildString(std::string &s) +{ + struct utsname un; + ::memset(&un, 0, sizeof(utsname)); + s.clear(); + + if (uname(&un) < 0) + return false; + + s.assign(un.release); + return true; +} + +bool +HostInfoLinux::GetOSKernelDescription(std::string &s) +{ + struct utsname un; + + ::memset(&un, 0, sizeof(utsname)); + s.clear(); + + if (uname(&un) < 0) + return false; + + s.assign(un.version); + return true; +} + +llvm::StringRef +HostInfoLinux::GetDistributionId() +{ + // Try to run 'lbs_release -i', and use that response + // for the distribution id. + static std::once_flag g_once_flag; + std::call_once(g_once_flag, []() { + + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST)); + if (log) + log->Printf("attempting to determine Linux distribution..."); + + // check if the lsb_release command exists at one of the + // following paths + const char *const exe_paths[] = {"/bin/lsb_release", "/usr/bin/lsb_release"}; + + for (size_t exe_index = 0; exe_index < sizeof(exe_paths) / sizeof(exe_paths[0]); ++exe_index) + { + const char *const get_distribution_info_exe = exe_paths[exe_index]; + if (access(get_distribution_info_exe, F_OK)) + { + // this exe doesn't exist, move on to next exe + if (log) + log->Printf("executable doesn't exist: %s", get_distribution_info_exe); + continue; + } + + // execute the distribution-retrieval command, read output + std::string get_distribution_id_command(get_distribution_info_exe); + get_distribution_id_command += " -i"; + + FILE *file = popen(get_distribution_id_command.c_str(), "r"); + if (!file) + { + if (log) + log->Printf("failed to run command: \"%s\", cannot retrieve " + "platform information", + get_distribution_id_command.c_str()); + break; + } + + // retrieve the distribution id string. + char distribution_id[256] = {'\0'}; + if (fgets(distribution_id, sizeof(distribution_id) - 1, file) != NULL) + { + if (log) + log->Printf("distribution id command returned \"%s\"", distribution_id); + + const char *const distributor_id_key = "Distributor ID:\t"; + if (strstr(distribution_id, distributor_id_key)) + { + // strip newlines + std::string id_string(distribution_id + strlen(distributor_id_key)); + id_string.erase(std::remove(id_string.begin(), id_string.end(), '\n'), id_string.end()); + + // lower case it and convert whitespace to underscores + std::transform(id_string.begin(), id_string.end(), id_string.begin(), [](char ch) + { + return tolower(isspace(ch) ? '_' : ch); + }); + + g_fields->m_distribution_id = id_string; + if (log) + log->Printf("distribution id set to \"%s\"", g_fields->m_distribution_id.c_str()); + } + else + { + if (log) + log->Printf("failed to find \"%s\" field in \"%s\"", distributor_id_key, distribution_id); + } + } + else + { + if (log) + log->Printf("failed to retrieve distribution id, \"%s\" returned no" + " lines", + get_distribution_id_command.c_str()); + } + + // clean up the file + pclose(file); + } + }); + + return g_fields->m_distribution_id.c_str(); +} + +FileSpec +HostInfoLinux::GetProgramFileSpec() +{ + static FileSpec g_program_filespec; + + if (!g_program_filespec) + { + char exe_path[PATH_MAX]; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len > 0) + { + exe_path[len] = 0; + g_program_filespec.SetFile(exe_path, false); + } + } + + return g_program_filespec; +} + +bool +HostInfoLinux::ComputeSupportExeDirectory(FileSpec &file_spec) +{ + if (HostInfoPosix::ComputeSupportExeDirectory(file_spec) && + file_spec.IsAbsolute() && + file_spec.Exists()) + return true; + file_spec.GetDirectory() = GetProgramFileSpec().GetDirectory(); + return !file_spec.GetDirectory().IsEmpty(); +} + +bool +HostInfoLinux::ComputeSystemPluginsDirectory(FileSpec &file_spec) +{ + FileSpec temp_file("/usr/lib/lldb", true); + file_spec.GetDirectory().SetCString(temp_file.GetPath().c_str()); + return true; +} + +bool +HostInfoLinux::ComputeUserPluginsDirectory(FileSpec &file_spec) +{ + // XDG Base Directory Specification + // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + // If XDG_DATA_HOME exists, use that, otherwise use ~/.local/share/lldb. + const char *xdg_data_home = getenv("XDG_DATA_HOME"); + if (xdg_data_home && xdg_data_home[0]) + { + std::string user_plugin_dir(xdg_data_home); + user_plugin_dir += "/lldb"; + file_spec.GetDirectory().SetCString(user_plugin_dir.c_str()); + } + else + file_spec.GetDirectory().SetCString("~/.local/share/lldb"); + return true; +} + +void +HostInfoLinux::ComputeHostArchitectureSupport(ArchSpec &arch_32, ArchSpec &arch_64) +{ + HostInfoPosix::ComputeHostArchitectureSupport(arch_32, arch_64); + + const char *distribution_id = GetDistributionId().data(); + + // On Linux, "unknown" in the vendor slot isn't what we want for the default + // triple. It's probably an artifact of config.guess. + if (arch_32.IsValid()) + { + arch_32.SetDistributionId(distribution_id); + if (arch_32.GetTriple().getVendor() == llvm::Triple::UnknownVendor) + arch_32.GetTriple().setVendorName(llvm::StringRef()); + } + if (arch_64.IsValid()) + { + arch_64.SetDistributionId(distribution_id); + if (arch_64.GetTriple().getVendor() == llvm::Triple::UnknownVendor) + arch_64.GetTriple().setVendorName(llvm::StringRef()); + } +} diff --git a/source/Host/linux/HostThreadLinux.cpp b/source/Host/linux/HostThreadLinux.cpp new file mode 100644 index 000000000000..2312ced0107b --- /dev/null +++ b/source/Host/linux/HostThreadLinux.cpp @@ -0,0 +1,52 @@ +//===-- HostThreadLinux.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/DataBuffer.h" +#include "lldb/Host/linux/HostThreadLinux.h" +#include "Plugins/Process/Linux/ProcFileReader.h" + +#include "llvm/ADT/SmallVector.h" + +#include <pthread.h> + +using namespace lldb_private; + +HostThreadLinux::HostThreadLinux() + : HostThreadPosix() +{ +} + +HostThreadLinux::HostThreadLinux(lldb::thread_t thread) + : HostThreadPosix(thread) +{ +} + +void +HostThreadLinux::SetName(lldb::thread_t thread, llvm::StringRef name) +{ +#if (defined(__GLIBC__) && defined(_GNU_SOURCE)) || defined(__ANDROID__) + ::pthread_setname_np(thread, name.data()); +#else + (void) thread; + (void) name; +#endif +} + +void +HostThreadLinux::GetName(lldb::thread_t thread, llvm::SmallVectorImpl<char> &name) +{ + // Read /proc/$TID/comm file. + lldb::DataBufferSP buf_sp = process_linux::ProcFileReader::ReadIntoDataBuffer(thread, "comm"); + const char *comm_str = (const char *)buf_sp->GetBytes(); + const char *cr_str = ::strchr(comm_str, '\n'); + size_t length = cr_str ? (cr_str - comm_str) : strlen(comm_str); + + name.clear(); + name.append(comm_str, comm_str + length); +} diff --git a/source/Host/linux/LibcGlue.cpp b/source/Host/linux/LibcGlue.cpp new file mode 100644 index 000000000000..63d026f76c62 --- /dev/null +++ b/source/Host/linux/LibcGlue.cpp @@ -0,0 +1,30 @@ +//===-- LibcGlue.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This file adds functions missing from libc on older versions of linux + +#include <unistd.h> +#include <sys/syscall.h> +#include <lldb/Host/linux/Uio.h> +#include <cerrno> + +#ifndef HAVE_PROCESS_VM_READV // If the syscall wrapper is not available, provide one. +ssize_t process_vm_readv(::pid_t pid, + const struct iovec *local_iov, unsigned long liovcnt, + const struct iovec *remote_iov, unsigned long riovcnt, + unsigned long flags) +{ +#ifdef HAVE_NR_PROCESS_VM_READV // If we have the syscall number, we can issue the syscall ourselves. + return syscall(__NR_process_vm_readv, pid, local_iov, liovcnt, remote_iov, riovcnt, flags); +#else // If not, let's pretend the syscall is not present. + errno = ENOSYS; + return -1; +#endif +} +#endif diff --git a/source/Host/linux/ThisThread.cpp b/source/Host/linux/ThisThread.cpp new file mode 100644 index 000000000000..1c68c8ba16d7 --- /dev/null +++ b/source/Host/linux/ThisThread.cpp @@ -0,0 +1,29 @@ +//===-- ThisThread.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/HostNativeThread.h" +#include "lldb/Host/ThisThread.h" + +#include "llvm/ADT/SmallVector.h" + +#include <pthread.h> + +using namespace lldb_private; + +void +ThisThread::SetName(llvm::StringRef name) +{ + HostNativeThread::SetName(::pthread_self(), name); +} + +void +ThisThread::GetName(llvm::SmallVectorImpl<char> &name) +{ + HostNativeThread::GetName(::pthread_self(), name); +} diff --git a/source/Host/macosx/Host.mm b/source/Host/macosx/Host.mm new file mode 100644 index 000000000000..2db27a276e4a --- /dev/null +++ b/source/Host/macosx/Host.mm @@ -0,0 +1,1587 @@ +//===-- Host.mm -------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Host.h" + +#include <AvailabilityMacros.h> + +#if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 +#define NO_XPC_SERVICES 1 +#endif + +#if !defined(NO_XPC_SERVICES) +#define __XPC_PRIVATE_H__ +#include <xpc/xpc.h> + +#define LaunchUsingXPCRightName "com.apple.dt.Xcode.RootDebuggingXPCService" + +// These XPC messaging keys are used for communication between Host.mm and the XPC service. +#define LauncherXPCServiceAuthKey "auth-key" +#define LauncherXPCServiceArgPrefxKey "arg" +#define LauncherXPCServiceEnvPrefxKey "env" +#define LauncherXPCServiceCPUTypeKey "cpuType" +#define LauncherXPCServicePosixspawnFlagsKey "posixspawnFlags" +#define LauncherXPCServiceStdInPathKeyKey "stdInPath" +#define LauncherXPCServiceStdOutPathKeyKey "stdOutPath" +#define LauncherXPCServiceStdErrPathKeyKey "stdErrPath" +#define LauncherXPCServiceChildPIDKey "childPID" +#define LauncherXPCServiceErrorTypeKey "errorType" +#define LauncherXPCServiceCodeTypeKey "errorCode" + +#endif + +#include "llvm/Support/Host.h" + +#include <asl.h> +#include <crt_externs.h> +#include <grp.h> +#include <libproc.h> +#include <pwd.h> +#include <spawn.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/proc.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include "lldb/Core/ArchSpec.h" +#include "lldb/Core/Communication.h" +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Core/StructuredData.h" +#include "lldb/Host/ConnectionFileDescriptor.h" +#include "lldb/Host/Endian.h" +#include "lldb/Host/FileSpec.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/CleanUp.h" +#include "lldb/Utility/NameMatches.h" + +#include "cfcpp/CFCBundle.h" +#include "cfcpp/CFCMutableArray.h" +#include "cfcpp/CFCMutableDictionary.h" +#include "cfcpp/CFCReleaser.h" +#include "cfcpp/CFCString.h" + + +#include <objc/objc-auto.h> + +#include <CoreFoundation/CoreFoundation.h> +#include <Foundation/Foundation.h> + +#ifndef _POSIX_SPAWN_DISABLE_ASLR +#define _POSIX_SPAWN_DISABLE_ASLR 0x0100 +#endif + +extern "C" +{ + int __pthread_chdir(const char *path); + int __pthread_fchdir (int fildes); +} + +using namespace lldb; +using namespace lldb_private; + +bool +Host::GetBundleDirectory (const FileSpec &file, FileSpec &bundle_directory) +{ +#if defined (__APPLE__) + if (file.GetFileType () == FileSpec::eFileTypeDirectory) + { + char path[PATH_MAX]; + if (file.GetPath(path, sizeof(path))) + { + CFCBundle bundle (path); + if (bundle.GetPath (path, sizeof(path))) + { + bundle_directory.SetFile (path, false); + return true; + } + } + } +#endif + bundle_directory.Clear(); + return false; +} + + +bool +Host::ResolveExecutableInBundle (FileSpec &file) +{ +#if defined (__APPLE__) + if (file.GetFileType () == FileSpec::eFileTypeDirectory) + { + char path[PATH_MAX]; + if (file.GetPath(path, sizeof(path))) + { + CFCBundle bundle (path); + CFCReleaser<CFURLRef> url(bundle.CopyExecutableURL ()); + if (url.get()) + { + if (::CFURLGetFileSystemRepresentation (url.get(), YES, (UInt8*)path, sizeof(path))) + { + file.SetFile(path, false); + return true; + } + } + } + } +#endif + return false; +} + +static void * +AcceptPIDFromInferior (void *arg) +{ + const char *connect_url = (const char *)arg; + ConnectionFileDescriptor file_conn; + Error error; + if (file_conn.Connect (connect_url, &error) == eConnectionStatusSuccess) + { + char pid_str[256]; + ::memset (pid_str, 0, sizeof(pid_str)); + ConnectionStatus status; + const size_t pid_str_len = file_conn.Read (pid_str, sizeof(pid_str), 0, status, NULL); + if (pid_str_len > 0) + { + int pid = atoi (pid_str); + return (void *)(intptr_t)pid; + } + } + return NULL; +} + +static bool +WaitForProcessToSIGSTOP (const lldb::pid_t pid, const int timeout_in_seconds) +{ + const int time_delta_usecs = 100000; + const int num_retries = timeout_in_seconds/time_delta_usecs; + for (int i=0; i<num_retries; i++) + { + struct proc_bsdinfo bsd_info; + int error = ::proc_pidinfo (pid, PROC_PIDTBSDINFO, + (uint64_t) 0, + &bsd_info, + PROC_PIDTBSDINFO_SIZE); + + switch (error) + { + case EINVAL: + case ENOTSUP: + case ESRCH: + case EPERM: + return false; + + default: + break; + + case 0: + if (bsd_info.pbi_status == SSTOP) + return true; + } + ::usleep (time_delta_usecs); + } + return false; +} +#if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__) + +//static lldb::pid_t +//LaunchInNewTerminalWithCommandFile +//( +// const char **argv, +// const char **envp, +// const char *working_dir, +// const ArchSpec *arch_spec, +// bool stop_at_entry, +// bool disable_aslr +//) +//{ +// if (!argv || !argv[0]) +// return LLDB_INVALID_PROCESS_ID; +// +// OSStatus error = 0; +// +// FileSpec program (argv[0], false); +// +// +// std::string unix_socket_name; +// +// char temp_file_path[PATH_MAX]; +// const char *tmpdir = ::getenv ("TMPDIR"); +// if (tmpdir == NULL) +// tmpdir = "/tmp/"; +// ::snprintf (temp_file_path, sizeof(temp_file_path), "%s%s-XXXXXX", tmpdir, program.GetFilename().AsCString()); +// +// if (::mktemp (temp_file_path) == NULL) +// return LLDB_INVALID_PROCESS_ID; +// +// unix_socket_name.assign (temp_file_path); +// +// ::strlcat (temp_file_path, ".command", sizeof (temp_file_path)); +// +// StreamFile command_file; +// command_file.GetFile().Open (temp_file_path, +// File::eOpenOptionWrite | File::eOpenOptionCanCreate, +// lldb::eFilePermissionsDefault); +// +// if (!command_file.GetFile().IsValid()) +// return LLDB_INVALID_PROCESS_ID; +// +// FileSpec darwin_debug_file_spec; +// if (!HostInfo::GetLLDBPath (ePathTypeSupportExecutableDir, darwin_debug_file_spec)) +// return LLDB_INVALID_PROCESS_ID; +// darwin_debug_file_spec.GetFilename().SetCString("darwin-debug"); +// +// if (!darwin_debug_file_spec.Exists()) +// return LLDB_INVALID_PROCESS_ID; +// +// char launcher_path[PATH_MAX]; +// darwin_debug_file_spec.GetPath(launcher_path, sizeof(launcher_path)); +// command_file.Printf("\"%s\" ", launcher_path); +// +// command_file.Printf("--unix-socket=%s ", unix_socket_name.c_str()); +// +// if (arch_spec && arch_spec->IsValid()) +// { +// command_file.Printf("--arch=%s ", arch_spec->GetArchitectureName()); +// } +// +// if (disable_aslr) +// { +// command_file.PutCString("--disable-aslr "); +// } +// +// command_file.PutCString("-- "); +// +// if (argv) +// { +// for (size_t i=0; argv[i] != NULL; ++i) +// { +// command_file.Printf("\"%s\" ", argv[i]); +// } +// } +// command_file.PutCString("\necho Process exited with status $?\n"); +// command_file.GetFile().Close(); +// if (::chmod (temp_file_path, S_IRWXU | S_IRWXG) != 0) +// return LLDB_INVALID_PROCESS_ID; +// +// CFCMutableDictionary cf_env_dict; +// +// const bool can_create = true; +// if (envp) +// { +// for (size_t i=0; envp[i] != NULL; ++i) +// { +// const char *env_entry = envp[i]; +// const char *equal_pos = strchr(env_entry, '='); +// if (equal_pos) +// { +// std::string env_key (env_entry, equal_pos); +// std::string env_val (equal_pos + 1); +// CFCString cf_env_key (env_key.c_str(), kCFStringEncodingUTF8); +// CFCString cf_env_val (env_val.c_str(), kCFStringEncodingUTF8); +// cf_env_dict.AddValue (cf_env_key.get(), cf_env_val.get(), can_create); +// } +// } +// } +// +// LSApplicationParameters app_params; +// ::memset (&app_params, 0, sizeof (app_params)); +// app_params.flags = kLSLaunchDontAddToRecents | kLSLaunchAsync; +// app_params.argv = NULL; +// app_params.environment = (CFDictionaryRef)cf_env_dict.get(); +// +// CFCReleaser<CFURLRef> command_file_url (::CFURLCreateFromFileSystemRepresentation (NULL, +// (const UInt8 *)temp_file_path, +// strlen(temp_file_path), +// false)); +// +// CFCMutableArray urls; +// +// // Terminal.app will open the ".command" file we have created +// // and run our process inside it which will wait at the entry point +// // for us to attach. +// urls.AppendValue(command_file_url.get()); +// +// +// lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; +// +// Error lldb_error; +// // Sleep and wait a bit for debugserver to start to listen... +// char connect_url[128]; +// ::snprintf (connect_url, sizeof(connect_url), "unix-accept://%s", unix_socket_name.c_str()); +// +// // Spawn a new thread to accept incoming connection on the connect_url +// // so we can grab the pid from the inferior +// lldb::thread_t accept_thread = Host::ThreadCreate (unix_socket_name.c_str(), +// AcceptPIDFromInferior, +// connect_url, +// &lldb_error); +// +// ProcessSerialNumber psn; +// error = LSOpenURLsWithRole(urls.get(), kLSRolesShell, NULL, &app_params, &psn, 1); +// if (error == noErr) +// { +// thread_result_t accept_thread_result = NULL; +// if (Host::ThreadJoin (accept_thread, &accept_thread_result, &lldb_error)) +// { +// if (accept_thread_result) +// { +// pid = (intptr_t)accept_thread_result; +// +// // Wait for process to be stopped the entry point by watching +// // for the process status to be set to SSTOP which indicates it it +// // SIGSTOP'ed at the entry point +// WaitForProcessToSIGSTOP (pid, 5); +// } +// } +// } +// else +// { +// Host::ThreadCancel (accept_thread, &lldb_error); +// } +// +// return pid; +//} + +const char *applscript_in_new_tty = +"tell application \"Terminal\"\n" +" do script \"%s\"\n" +"end tell\n"; + + +const char *applscript_in_existing_tty = "\ +set the_shell_script to \"%s\"\n\ +tell application \"Terminal\"\n\ + repeat with the_window in (get windows)\n\ + repeat with the_tab in tabs of the_window\n\ + set the_tty to tty in the_tab\n\ + if the_tty contains \"%s\" then\n\ + if the_tab is not busy then\n\ + set selected of the_tab to true\n\ + set frontmost of the_window to true\n\ + do script the_shell_script in the_tab\n\ + return\n\ + end if\n\ + end if\n\ + end repeat\n\ + end repeat\n\ + do script the_shell_script\n\ +end tell\n"; + + +static Error +LaunchInNewTerminalWithAppleScript (const char *exe_path, ProcessLaunchInfo &launch_info) +{ + Error error; + char unix_socket_name[PATH_MAX] = "/tmp/XXXXXX"; + if (::mktemp (unix_socket_name) == NULL) + { + error.SetErrorString ("failed to make temporary path for a unix socket"); + return error; + } + + StreamString command; + FileSpec darwin_debug_file_spec; + if (!HostInfo::GetLLDBPath(ePathTypeSupportExecutableDir, darwin_debug_file_spec)) + { + error.SetErrorString ("can't locate the 'darwin-debug' executable"); + return error; + } + + darwin_debug_file_spec.GetFilename().SetCString("darwin-debug"); + + if (!darwin_debug_file_spec.Exists()) + { + error.SetErrorStringWithFormat ("the 'darwin-debug' executable doesn't exists at '%s'", + darwin_debug_file_spec.GetPath().c_str()); + return error; + } + + char launcher_path[PATH_MAX]; + darwin_debug_file_spec.GetPath(launcher_path, sizeof(launcher_path)); + + const ArchSpec &arch_spec = launch_info.GetArchitecture(); + // Only set the architecture if it is valid and if it isn't Haswell (x86_64h). + if (arch_spec.IsValid() && arch_spec.GetCore() != ArchSpec::eCore_x86_64_x86_64h) + command.Printf("arch -arch %s ", arch_spec.GetArchitectureName()); + + command.Printf("'%s' --unix-socket=%s", launcher_path, unix_socket_name); + + if (arch_spec.IsValid()) + command.Printf(" --arch=%s", arch_spec.GetArchitectureName()); + + FileSpec working_dir{launch_info.GetWorkingDirectory()}; + if (working_dir) + command.Printf(" --working-dir '%s'", working_dir.GetCString()); + else + { + char cwd[PATH_MAX]; + if (getcwd(cwd, PATH_MAX)) + command.Printf(" --working-dir '%s'", cwd); + } + + if (launch_info.GetFlags().Test (eLaunchFlagDisableASLR)) + command.PutCString(" --disable-aslr"); + + // We are launching on this host in a terminal. So compare the environment on the host + // to what is supplied in the launch_info. Any items that aren't in the host environment + // need to be sent to darwin-debug. If we send all environment entries, we might blow the + // max command line length, so we only send user modified entries. + const char **envp = launch_info.GetEnvironmentEntries().GetConstArgumentVector (); + + StringList host_env; + const size_t host_env_count = Host::GetEnvironment (host_env); + + if (envp && envp[0]) + { + const char *env_entry; + for (size_t env_idx = 0; (env_entry = envp[env_idx]) != NULL; ++env_idx) + { + bool add_entry = true; + for (size_t i=0; i<host_env_count; ++i) + { + const char *host_env_entry = host_env.GetStringAtIndex(i); + if (strcmp(env_entry, host_env_entry) == 0) + { + add_entry = false; + break; + } + } + if (add_entry) + { + command.Printf(" --env='%s'", env_entry); + } + } + } + + command.PutCString(" -- "); + + const char **argv = launch_info.GetArguments().GetConstArgumentVector (); + if (argv) + { + for (size_t i=0; argv[i] != NULL; ++i) + { + if (i==0) + command.Printf(" '%s'", exe_path); + else + command.Printf(" '%s'", argv[i]); + } + } + else + { + command.Printf(" '%s'", exe_path); + } + command.PutCString (" ; echo Process exited with status $?"); + if (launch_info.GetFlags().Test(lldb::eLaunchFlagCloseTTYOnExit)) + command.PutCString (" ; exit"); + + StreamString applescript_source; + + const char *tty_command = command.GetString().c_str(); +// if (tty_name && tty_name[0]) +// { +// applescript_source.Printf (applscript_in_existing_tty, +// tty_command, +// tty_name); +// } +// else +// { + applescript_source.Printf (applscript_in_new_tty, + tty_command); +// } + + + + const char *script_source = applescript_source.GetString().c_str(); + //puts (script_source); + NSAppleScript* applescript = [[NSAppleScript alloc] initWithSource:[NSString stringWithCString:script_source encoding:NSUTF8StringEncoding]]; + + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + + Error lldb_error; + // Sleep and wait a bit for debugserver to start to listen... + ConnectionFileDescriptor file_conn; + char connect_url[128]; + ::snprintf (connect_url, sizeof(connect_url), "unix-accept://%s", unix_socket_name); + + // Spawn a new thread to accept incoming connection on the connect_url + // so we can grab the pid from the inferior. We have to do this because we + // are sending an AppleScript that will launch a process in Terminal.app, + // in a shell and the shell will fork/exec a couple of times before we get + // to the process that we wanted to launch. So when our process actually + // gets launched, we will handshake with it and get the process ID for it. + HostThread accept_thread = ThreadLauncher::LaunchThread(unix_socket_name, AcceptPIDFromInferior, connect_url, &lldb_error); + + [applescript executeAndReturnError:nil]; + + thread_result_t accept_thread_result = NULL; + lldb_error = accept_thread.Join(&accept_thread_result); + if (lldb_error.Success() && accept_thread_result) + { + pid = (intptr_t)accept_thread_result; + + // Wait for process to be stopped at the entry point by watching + // for the process status to be set to SSTOP which indicates it it + // SIGSTOP'ed at the entry point + WaitForProcessToSIGSTOP(pid, 5); + } + + FileSystem::Unlink(FileSpec{unix_socket_name, false}); + [applescript release]; + if (pid != LLDB_INVALID_PROCESS_ID) + launch_info.SetProcessID (pid); + return error; +} + +#endif // #if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__) + + +// On MacOSX CrashReporter will display a string for each shared library if +// the shared library has an exported symbol named "__crashreporter_info__". + +static Mutex& +GetCrashReporterMutex () +{ + static Mutex g_mutex; + return g_mutex; +} + +extern "C" { + const char *__crashreporter_info__ = NULL; +} + +asm(".desc ___crashreporter_info__, 0x10"); + +void +Host::SetCrashDescriptionWithFormat (const char *format, ...) +{ + static StreamString g_crash_description; + Mutex::Locker locker (GetCrashReporterMutex ()); + + if (format) + { + va_list args; + va_start (args, format); + g_crash_description.GetString().clear(); + g_crash_description.PrintfVarArg(format, args); + va_end (args); + __crashreporter_info__ = g_crash_description.GetData(); + } + else + { + __crashreporter_info__ = NULL; + } +} + +void +Host::SetCrashDescription (const char *cstr) +{ + Mutex::Locker locker (GetCrashReporterMutex ()); + static std::string g_crash_description; + if (cstr) + { + g_crash_description.assign (cstr); + __crashreporter_info__ = g_crash_description.c_str(); + } + else + { + __crashreporter_info__ = NULL; + } +} + +bool +Host::OpenFileInExternalEditor (const FileSpec &file_spec, uint32_t line_no) +{ +#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) + return false; +#else + // We attach this to an 'odoc' event to specify a particular selection + typedef struct { + int16_t reserved0; // must be zero + int16_t fLineNumber; + int32_t fSelStart; + int32_t fSelEnd; + uint32_t reserved1; // must be zero + uint32_t reserved2; // must be zero + } BabelAESelInfo; + + Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_HOST)); + char file_path[PATH_MAX]; + file_spec.GetPath(file_path, PATH_MAX); + CFCString file_cfstr (file_path, kCFStringEncodingUTF8); + CFCReleaser<CFURLRef> file_URL (::CFURLCreateWithFileSystemPath (NULL, + file_cfstr.get(), + kCFURLPOSIXPathStyle, + false)); + + if (log) + log->Printf("Sending source file: \"%s\" and line: %d to external editor.\n", file_path, line_no); + + long error; + BabelAESelInfo file_and_line_info = + { + 0, // reserved0 + (int16_t)(line_no - 1), // fLineNumber (zero based line number) + 1, // fSelStart + 1024, // fSelEnd + 0, // reserved1 + 0 // reserved2 + }; + + AEKeyDesc file_and_line_desc; + + error = ::AECreateDesc (typeUTF8Text, + &file_and_line_info, + sizeof (file_and_line_info), + &(file_and_line_desc.descContent)); + + if (error != noErr) + { + if (log) + log->Printf("Error creating AEDesc: %ld.\n", error); + return false; + } + + file_and_line_desc.descKey = keyAEPosition; + + static std::string g_app_name; + static FSRef g_app_fsref; + + LSApplicationParameters app_params; + ::memset (&app_params, 0, sizeof (app_params)); + app_params.flags = kLSLaunchDefaults | + kLSLaunchDontAddToRecents | + kLSLaunchDontSwitch; + + char *external_editor = ::getenv ("LLDB_EXTERNAL_EDITOR"); + + if (external_editor) + { + if (log) + log->Printf("Looking for external editor \"%s\".\n", external_editor); + + if (g_app_name.empty() || strcmp (g_app_name.c_str(), external_editor) != 0) + { + CFCString editor_name (external_editor, kCFStringEncodingUTF8); + error = ::LSFindApplicationForInfo (kLSUnknownCreator, + NULL, + editor_name.get(), + &g_app_fsref, + NULL); + + // If we found the app, then store away the name so we don't have to re-look it up. + if (error != noErr) + { + if (log) + log->Printf("Could not find External Editor application, error: %ld.\n", error); + return false; + } + + } + app_params.application = &g_app_fsref; + } + + ProcessSerialNumber psn; + CFCReleaser<CFArrayRef> file_array(CFArrayCreate (NULL, (const void **) file_URL.ptr_address(false), 1, NULL)); + error = ::LSOpenURLsWithRole (file_array.get(), + kLSRolesAll, + &file_and_line_desc, + &app_params, + &psn, + 1); + + AEDisposeDesc (&(file_and_line_desc.descContent)); + + if (error != noErr) + { + if (log) + log->Printf("LSOpenURLsWithRole failed, error: %ld.\n", error); + + return false; + } + + return true; +#endif // #if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__) +} + +size_t +Host::GetEnvironment (StringList &env) +{ + char **host_env = *_NSGetEnviron(); + char *env_entry; + size_t i; + for (i=0; (env_entry = host_env[i]) != NULL; ++i) + env.AppendString(env_entry); + return i; + +} + +static bool +GetMacOSXProcessCPUType (ProcessInstanceInfo &process_info) +{ + if (process_info.ProcessIDIsValid()) + { + // Make a new mib to stay thread safe + int mib[CTL_MAXNAME]={0,}; + size_t mib_len = CTL_MAXNAME; + if (::sysctlnametomib("sysctl.proc_cputype", mib, &mib_len)) + return false; + + mib[mib_len] = process_info.GetProcessID(); + mib_len++; + + cpu_type_t cpu, sub = 0; + size_t len = sizeof(cpu); + if (::sysctl (mib, mib_len, &cpu, &len, 0, 0) == 0) + { + switch (cpu) + { + case CPU_TYPE_I386: sub = CPU_SUBTYPE_I386_ALL; break; + case CPU_TYPE_X86_64: sub = CPU_SUBTYPE_X86_64_ALL; break; + +#if defined (CPU_TYPE_ARM64) && defined (CPU_SUBTYPE_ARM64_ALL) + case CPU_TYPE_ARM64: sub = CPU_SUBTYPE_ARM64_ALL; break; +#endif + + case CPU_TYPE_ARM: + { + // Note that we fetched the cpu type from the PROCESS but we can't get a cpusubtype of the + // process -- we can only get the host's cpu subtype. + uint32_t cpusubtype = 0; + len = sizeof(cpusubtype); + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) + sub = cpusubtype; + + bool host_cpu_is_64bit; + uint32_t is64bit_capable; + size_t is64bit_capable_len = sizeof (is64bit_capable); + if (sysctlbyname("hw.cpu64bit_capable", &is64bit_capable, &is64bit_capable_len, NULL, 0) == 0) + host_cpu_is_64bit = true; + else + host_cpu_is_64bit = false; + + // if the host is an armv8 device, its cpusubtype will be in CPU_SUBTYPE_ARM64 numbering + // and we need to rewrite it to a reasonable CPU_SUBTYPE_ARM value instead. + + if (host_cpu_is_64bit) + { + sub = CPU_SUBTYPE_ARM_V7; + } + } + break; + + default: + break; + } + process_info.GetArchitecture ().SetArchitecture (eArchTypeMachO, cpu, sub); + return true; + } + } + process_info.GetArchitecture().Clear(); + return false; +} + +static bool +GetMacOSXProcessArgs (const ProcessInstanceInfoMatch *match_info_ptr, + ProcessInstanceInfo &process_info) +{ + if (process_info.ProcessIDIsValid()) + { + int proc_args_mib[3] = { CTL_KERN, KERN_PROCARGS2, (int)process_info.GetProcessID() }; + + size_t arg_data_size = 0; + if (::sysctl (proc_args_mib, 3, nullptr, &arg_data_size, NULL, 0) || arg_data_size == 0) + arg_data_size = 8192; + + // Add a few bytes to the calculated length, I know we need to add at least one byte + // to this number otherwise we get junk back, so add 128 just in case... + DataBufferHeap arg_data(arg_data_size+128, 0); + arg_data_size = arg_data.GetByteSize(); + if (::sysctl (proc_args_mib, 3, arg_data.GetBytes(), &arg_data_size , NULL, 0) == 0) + { + DataExtractor data (arg_data.GetBytes(), arg_data_size, endian::InlHostByteOrder(), sizeof(void *)); + lldb::offset_t offset = 0; + uint32_t argc = data.GetU32 (&offset); + llvm::Triple &triple = process_info.GetArchitecture().GetTriple(); + const llvm::Triple::ArchType triple_arch = triple.getArch(); + const bool check_for_ios_simulator = (triple_arch == llvm::Triple::x86 || triple_arch == llvm::Triple::x86_64); + const char *cstr = data.GetCStr (&offset); + if (cstr) + { + process_info.GetExecutableFile().SetFile(cstr, false); + + if (match_info_ptr == NULL || + NameMatches (process_info.GetExecutableFile().GetFilename().GetCString(), + match_info_ptr->GetNameMatchType(), + match_info_ptr->GetProcessInfo().GetName())) + { + // Skip NULLs + while (1) + { + const uint8_t *p = data.PeekData(offset, 1); + if ((p == NULL) || (*p != '\0')) + break; + ++offset; + } + // Now extract all arguments + Args &proc_args = process_info.GetArguments(); + for (int i=0; i<static_cast<int>(argc); ++i) + { + cstr = data.GetCStr(&offset); + if (cstr) + proc_args.AppendArgument(cstr); + } + + Args &proc_env = process_info.GetEnvironmentEntries (); + while ((cstr = data.GetCStr(&offset))) + { + if (cstr[0] == '\0') + break; + + if (check_for_ios_simulator) + { + if (strncmp(cstr, "SIMULATOR_UDID=", strlen("SIMULATOR_UDID=")) == 0) + process_info.GetArchitecture().GetTriple().setOS(llvm::Triple::IOS); + else + process_info.GetArchitecture().GetTriple().setOS(llvm::Triple::MacOSX); + } + + proc_env.AppendArgument(cstr); + } + return true; + } + } + } + } + return false; +} + +static bool +GetMacOSXProcessUserAndGroup (ProcessInstanceInfo &process_info) +{ + if (process_info.ProcessIDIsValid()) + { + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = process_info.GetProcessID(); + struct kinfo_proc proc_kinfo; + size_t proc_kinfo_size = sizeof(struct kinfo_proc); + + if (::sysctl (mib, 4, &proc_kinfo, &proc_kinfo_size, NULL, 0) == 0) + { + if (proc_kinfo_size > 0) + { + process_info.SetParentProcessID (proc_kinfo.kp_eproc.e_ppid); + process_info.SetUserID (proc_kinfo.kp_eproc.e_pcred.p_ruid); + process_info.SetGroupID (proc_kinfo.kp_eproc.e_pcred.p_rgid); + process_info.SetEffectiveUserID (proc_kinfo.kp_eproc.e_ucred.cr_uid); + if (proc_kinfo.kp_eproc.e_ucred.cr_ngroups > 0) + process_info.SetEffectiveGroupID (proc_kinfo.kp_eproc.e_ucred.cr_groups[0]); + else + process_info.SetEffectiveGroupID (UINT32_MAX); + return true; + } + } + } + process_info.SetParentProcessID (LLDB_INVALID_PROCESS_ID); + process_info.SetUserID (UINT32_MAX); + process_info.SetGroupID (UINT32_MAX); + process_info.SetEffectiveUserID (UINT32_MAX); + process_info.SetEffectiveGroupID (UINT32_MAX); + return false; +} + + +uint32_t +Host::FindProcesses (const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &process_infos) +{ + std::vector<struct kinfo_proc> kinfos; + + int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; + + size_t pid_data_size = 0; + if (::sysctl (mib, 4, NULL, &pid_data_size, NULL, 0) != 0) + return 0; + + // Add a few extra in case a few more show up + const size_t estimated_pid_count = (pid_data_size / sizeof(struct kinfo_proc)) + 10; + + kinfos.resize (estimated_pid_count); + pid_data_size = kinfos.size() * sizeof(struct kinfo_proc); + + if (::sysctl (mib, 4, &kinfos[0], &pid_data_size, NULL, 0) != 0) + return 0; + + const size_t actual_pid_count = (pid_data_size / sizeof(struct kinfo_proc)); + + bool all_users = match_info.GetMatchAllUsers(); + const lldb::pid_t our_pid = getpid(); + const uid_t our_uid = getuid(); + for (size_t i = 0; i < actual_pid_count; i++) + { + const struct kinfo_proc &kinfo = kinfos[i]; + + bool kinfo_user_matches = false; + if (all_users) + kinfo_user_matches = true; + else + kinfo_user_matches = kinfo.kp_eproc.e_pcred.p_ruid == our_uid; + + // Special case, if lldb is being run as root we can attach to anything. + if (our_uid == 0) + kinfo_user_matches = true; + + if (kinfo_user_matches == false || // Make sure the user is acceptable + static_cast<lldb::pid_t>(kinfo.kp_proc.p_pid) == our_pid || // Skip this process + kinfo.kp_proc.p_pid == 0 || // Skip kernel (kernel pid is zero) + kinfo.kp_proc.p_stat == SZOMB || // Zombies are bad, they like brains... + kinfo.kp_proc.p_flag & P_TRACED || // Being debugged? + kinfo.kp_proc.p_flag & P_WEXIT || // Working on exiting? + kinfo.kp_proc.p_flag & P_TRANSLATED) // Skip translated ppc (Rosetta) + continue; + + ProcessInstanceInfo process_info; + process_info.SetProcessID (kinfo.kp_proc.p_pid); + process_info.SetParentProcessID (kinfo.kp_eproc.e_ppid); + process_info.SetUserID (kinfo.kp_eproc.e_pcred.p_ruid); + process_info.SetGroupID (kinfo.kp_eproc.e_pcred.p_rgid); + process_info.SetEffectiveUserID (kinfo.kp_eproc.e_ucred.cr_uid); + if (kinfo.kp_eproc.e_ucred.cr_ngroups > 0) + process_info.SetEffectiveGroupID (kinfo.kp_eproc.e_ucred.cr_groups[0]); + else + process_info.SetEffectiveGroupID (UINT32_MAX); + + // Make sure our info matches before we go fetch the name and cpu type + if (match_info.Matches (process_info)) + { + // Get CPU type first so we can know to look for iOS simulator is we have x86 or x86_64 + if (GetMacOSXProcessCPUType (process_info)) + { + if (GetMacOSXProcessArgs (&match_info, process_info)) + { + if (match_info.Matches (process_info)) + process_infos.Append (process_info); + } + } + } + } + return process_infos.GetSize(); +} + +bool +Host::GetProcessInfo (lldb::pid_t pid, ProcessInstanceInfo &process_info) +{ + process_info.SetProcessID(pid); + bool success = false; + + // Get CPU type first so we can know to look for iOS simulator is we have x86 or x86_64 + if (GetMacOSXProcessCPUType (process_info)) + success = true; + + if (GetMacOSXProcessArgs (NULL, process_info)) + success = true; + + if (GetMacOSXProcessUserAndGroup (process_info)) + success = true; + + if (success) + return true; + + process_info.Clear(); + return false; +} + +#if !NO_XPC_SERVICES +static void +PackageXPCArguments (xpc_object_t message, const char *prefix, const Args& args) +{ + size_t count = args.GetArgumentCount(); + char buf[50]; // long enough for 'argXXX' + memset(buf, 0, 50); + sprintf(buf, "%sCount", prefix); + xpc_dictionary_set_int64(message, buf, count); + for (size_t i=0; i<count; i++) { + memset(buf, 0, 50); + sprintf(buf, "%s%zi", prefix, i); + xpc_dictionary_set_string(message, buf, args.GetArgumentAtIndex(i)); + } +} + +/* + A valid authorizationRef means that + - there is the LaunchUsingXPCRightName rights in the /etc/authorization + - we have successfully copied the rights to be send over the XPC wire + Once obtained, it will be valid for as long as the process lives. + */ +static AuthorizationRef authorizationRef = NULL; +static Error +getXPCAuthorization (ProcessLaunchInfo &launch_info) +{ + Error error; + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_HOST | LIBLLDB_LOG_PROCESS)); + + if ((launch_info.GetUserID() == 0) && !authorizationRef) + { + OSStatus createStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef); + if (createStatus != errAuthorizationSuccess) + { + error.SetError(1, eErrorTypeGeneric); + error.SetErrorString("Can't create authorizationRef."); + if (log) + { + error.PutToLog(log, "%s", error.AsCString()); + } + return error; + } + + OSStatus rightsStatus = AuthorizationRightGet(LaunchUsingXPCRightName, NULL); + if (rightsStatus != errAuthorizationSuccess) + { + // No rights in the security database, Create it with the right prompt. + CFStringRef prompt = CFSTR("Xcode is trying to take control of a root process."); + CFStringRef keys[] = { CFSTR("en") }; + CFTypeRef values[] = { prompt }; + CFDictionaryRef promptDict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 1, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef keys1[] = { CFSTR("class"), CFSTR("group"), CFSTR("comment"), CFSTR("default-prompt"), CFSTR("shared") }; + CFTypeRef values1[] = { CFSTR("user"), CFSTR("admin"), CFSTR(LaunchUsingXPCRightName), promptDict, kCFBooleanFalse }; + CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys1, (const void **)values1, 5, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + rightsStatus = AuthorizationRightSet(authorizationRef, LaunchUsingXPCRightName, dict, NULL, NULL, NULL); + CFRelease(promptDict); + CFRelease(dict); + } + + OSStatus copyRightStatus = errAuthorizationDenied; + if (rightsStatus == errAuthorizationSuccess) + { + AuthorizationItem item1 = { LaunchUsingXPCRightName, 0, NULL, 0 }; + AuthorizationItem items[] = {item1}; + AuthorizationRights requestedRights = {1, items }; + AuthorizationFlags authorizationFlags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights; + copyRightStatus = AuthorizationCopyRights(authorizationRef, &requestedRights, kAuthorizationEmptyEnvironment, authorizationFlags, NULL); + } + + if (copyRightStatus != errAuthorizationSuccess) + { + // Eventually when the commandline supports running as root and the user is not + // logged in in the current audit session, we will need the trick in gdb where + // we ask the user to type in the root passwd in the terminal. + error.SetError(2, eErrorTypeGeneric); + error.SetErrorStringWithFormat("Launching as root needs root authorization."); + if (log) + { + error.PutToLog(log, "%s", error.AsCString()); + } + + if (authorizationRef) + { + AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults); + authorizationRef = NULL; + } + } + } + + return error; +} +#endif + +static Error +LaunchProcessXPC(const char *exe_path, ProcessLaunchInfo &launch_info, lldb::pid_t &pid) +{ +#if !NO_XPC_SERVICES + Error error = getXPCAuthorization(launch_info); + if (error.Fail()) + return error; + + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_HOST | LIBLLDB_LOG_PROCESS)); + + uid_t requested_uid = launch_info.GetUserID(); + const char *xpc_service = nil; + bool send_auth = false; + AuthorizationExternalForm extForm; + if (requested_uid == 0) + { + if (AuthorizationMakeExternalForm(authorizationRef, &extForm) == errAuthorizationSuccess) + { + send_auth = true; + } + else + { + error.SetError(3, eErrorTypeGeneric); + error.SetErrorStringWithFormat("Launching root via XPC needs to externalize authorization reference."); + if (log) + { + error.PutToLog(log, "%s", error.AsCString()); + } + return error; + } + xpc_service = LaunchUsingXPCRightName; + } + else + { + error.SetError(4, eErrorTypeGeneric); + error.SetErrorStringWithFormat("Launching via XPC is only currently available for root."); + if (log) + { + error.PutToLog(log, "%s", error.AsCString()); + } + return error; + } + + xpc_connection_t conn = xpc_connection_create(xpc_service, NULL); + + xpc_connection_set_event_handler(conn, ^(xpc_object_t event) { + xpc_type_t type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) { + if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { + // The service has either canceled itself, crashed, or been terminated. + // The XPC connection is still valid and sending a message to it will re-launch the service. + // If the service is state-full, this is the time to initialize the new service. + return; + } else if (event == XPC_ERROR_CONNECTION_INVALID) { + // The service is invalid. Either the service name supplied to xpc_connection_create() is incorrect + // or we (this process) have canceled the service; we can do any cleanup of application state at this point. + // printf("Service disconnected"); + return; + } else { + // printf("Unexpected error from service: %s", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); + } + + } else { + // printf("Received unexpected event in handler"); + } + }); + + xpc_connection_set_finalizer_f (conn, xpc_finalizer_t(xpc_release)); + xpc_connection_resume (conn); + xpc_object_t message = xpc_dictionary_create (nil, nil, 0); + + if (send_auth) + { + xpc_dictionary_set_data(message, LauncherXPCServiceAuthKey, extForm.bytes, sizeof(AuthorizationExternalForm)); + } + + PackageXPCArguments(message, LauncherXPCServiceArgPrefxKey, launch_info.GetArguments()); + PackageXPCArguments(message, LauncherXPCServiceEnvPrefxKey, launch_info.GetEnvironmentEntries()); + + // Posix spawn stuff. + xpc_dictionary_set_int64(message, LauncherXPCServiceCPUTypeKey, launch_info.GetArchitecture().GetMachOCPUType()); + xpc_dictionary_set_int64(message, LauncherXPCServicePosixspawnFlagsKey, Host::GetPosixspawnFlags(launch_info)); + const FileAction *file_action = launch_info.GetFileActionForFD(STDIN_FILENO); + if (file_action && file_action->GetPath()) + { + xpc_dictionary_set_string(message, LauncherXPCServiceStdInPathKeyKey, file_action->GetPath()); + } + file_action = launch_info.GetFileActionForFD(STDOUT_FILENO); + if (file_action && file_action->GetPath()) + { + xpc_dictionary_set_string(message, LauncherXPCServiceStdOutPathKeyKey, file_action->GetPath()); + } + file_action = launch_info.GetFileActionForFD(STDERR_FILENO); + if (file_action && file_action->GetPath()) + { + xpc_dictionary_set_string(message, LauncherXPCServiceStdErrPathKeyKey, file_action->GetPath()); + } + + xpc_object_t reply = xpc_connection_send_message_with_reply_sync(conn, message); + xpc_type_t returnType = xpc_get_type(reply); + if (returnType == XPC_TYPE_DICTIONARY) + { + pid = xpc_dictionary_get_int64(reply, LauncherXPCServiceChildPIDKey); + if (pid == 0) + { + int errorType = xpc_dictionary_get_int64(reply, LauncherXPCServiceErrorTypeKey); + int errorCode = xpc_dictionary_get_int64(reply, LauncherXPCServiceCodeTypeKey); + + error.SetError(errorCode, eErrorTypeGeneric); + error.SetErrorStringWithFormat("Problems with launching via XPC. Error type : %i, code : %i", errorType, errorCode); + if (log) + { + error.PutToLog(log, "%s", error.AsCString()); + } + + if (authorizationRef) + { + AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults); + authorizationRef = NULL; + } + } + } + else if (returnType == XPC_TYPE_ERROR) + { + error.SetError(5, eErrorTypeGeneric); + error.SetErrorStringWithFormat("Problems with launching via XPC. XPC error : %s", xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION)); + if (log) + { + error.PutToLog(log, "%s", error.AsCString()); + } + } + + return error; +#else + Error error; + return error; +#endif +} + +static bool +ShouldLaunchUsingXPC(ProcessLaunchInfo &launch_info) +{ + bool result = false; + +#if !NO_XPC_SERVICES + bool launchingAsRoot = launch_info.GetUserID() == 0; + bool currentUserIsRoot = HostInfo::GetEffectiveUserID() == 0; + + if (launchingAsRoot && !currentUserIsRoot) + { + // If current user is already root, we don't need XPC's help. + result = true; + } +#endif + + return result; +} + +Error +Host::LaunchProcess (ProcessLaunchInfo &launch_info) +{ + Error error; + char exe_path[PATH_MAX]; + PlatformSP host_platform_sp (Platform::GetHostPlatform ()); + + ModuleSpec exe_module_spec(launch_info.GetExecutableFile(), launch_info.GetArchitecture()); + + FileSpec::FileType file_type = exe_module_spec.GetFileSpec().GetFileType(); + if (file_type != FileSpec::eFileTypeRegular) + { + lldb::ModuleSP exe_module_sp; + error = host_platform_sp->ResolveExecutable (exe_module_spec, + exe_module_sp, + NULL); + + if (error.Fail()) + return error; + + if (exe_module_sp) + exe_module_spec.GetFileSpec() = exe_module_sp->GetFileSpec(); + } + + if (exe_module_spec.GetFileSpec().Exists()) + { + exe_module_spec.GetFileSpec().GetPath (exe_path, sizeof(exe_path)); + } + else + { + launch_info.GetExecutableFile().GetPath (exe_path, sizeof(exe_path)); + error.SetErrorStringWithFormat ("executable doesn't exist: '%s'", exe_path); + return error; + } + + if (launch_info.GetFlags().Test (eLaunchFlagLaunchInTTY)) + { +#if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__) + return LaunchInNewTerminalWithAppleScript (exe_path, launch_info); +#else + error.SetErrorString ("launching a process in a new terminal is not supported on iOS devices"); + return error; +#endif + } + + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + + if (ShouldLaunchUsingXPC(launch_info)) + { + error = LaunchProcessXPC(exe_path, launch_info, pid); + } + else + { + error = LaunchProcessPosixSpawn(exe_path, launch_info, pid); + } + + if (pid != LLDB_INVALID_PROCESS_ID) + { + // If all went well, then set the process ID into the launch info + launch_info.SetProcessID(pid); + + // Make sure we reap any processes we spawn or we will have zombies. + if (!launch_info.MonitorProcess()) + { + const bool monitor_signals = false; + Host::MonitorChildProcessCallback callback = nullptr; + + if (!launch_info.GetFlags().Test(lldb::eLaunchFlagDontSetExitStatus)) + callback = Process::SetProcessExitStatus; + + StartMonitoringChildProcess (callback, + NULL, + pid, + monitor_signals); + } + } + else + { + // Invalid process ID, something didn't go well + if (error.Success()) + error.SetErrorString ("process launch failed for unknown reasons"); + } + return error; +} + +Error +Host::ShellExpandArguments (ProcessLaunchInfo &launch_info) +{ + Error error; + if (launch_info.GetFlags().Test(eLaunchFlagShellExpandArguments)) + { + FileSpec expand_tool_spec; + if (!HostInfo::GetLLDBPath(lldb::ePathTypeSupportExecutableDir, expand_tool_spec)) + { + error.SetErrorString("could not get support executable directory for lldb-argdumper tool"); + return error; + } + expand_tool_spec.AppendPathComponent("lldb-argdumper"); + if (!expand_tool_spec.Exists()) + { + error.SetErrorStringWithFormat("could not find the lldb-argdumper tool: %s", expand_tool_spec.GetPath().c_str()); + return error; + } + + StreamString expand_tool_spec_stream; + expand_tool_spec_stream.Printf("\"%s\"",expand_tool_spec.GetPath().c_str()); + + Args expand_command(expand_tool_spec_stream.GetData()); + expand_command.AppendArguments (launch_info.GetArguments()); + + int status; + std::string output; + FileSpec cwd(launch_info.GetWorkingDirectory()); + if (!cwd.Exists()) + { + char *wd = getcwd(nullptr, 0); + if (wd == nullptr) + { + error.SetErrorStringWithFormat("cwd does not exist; cannot launch with shell argument expansion"); + return error; + } + else + { + FileSpec working_dir(wd, false); + free(wd); + launch_info.SetWorkingDirectory(working_dir); + + } + } + RunShellCommand(expand_command, cwd, &status, nullptr, &output, 10); + + if (status != 0) + { + error.SetErrorStringWithFormat("lldb-argdumper exited with error %d", status); + return error; + } + + auto data_sp = StructuredData::ParseJSON(output); + if (!data_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto dict_sp = data_sp->GetAsDictionary(); + if (!data_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto args_sp = dict_sp->GetObjectForDotSeparatedPath("arguments"); + if (!args_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto args_array_sp = args_sp->GetAsArray(); + if (!args_array_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + launch_info.GetArguments().Clear(); + + for (size_t i = 0; + i < args_array_sp->GetSize(); + i++) + { + auto item_sp = args_array_sp->GetItemAtIndex(i); + if (!item_sp) + continue; + auto str_sp = item_sp->GetAsString(); + if (!str_sp) + continue; + + launch_info.GetArguments().AppendArgument(str_sp->GetValue().c_str()); + } + } + + return error; +} + +HostThread +Host::StartMonitoringChildProcess(Host::MonitorChildProcessCallback callback, void *callback_baton, lldb::pid_t pid, bool monitor_signals) +{ + unsigned long mask = DISPATCH_PROC_EXIT; + if (monitor_signals) + mask |= DISPATCH_PROC_SIGNAL; + + Log *log(lldb_private::GetLogIfAnyCategoriesSet (LIBLLDB_LOG_HOST | LIBLLDB_LOG_PROCESS)); + + + dispatch_source_t source = ::dispatch_source_create (DISPATCH_SOURCE_TYPE_PROC, + pid, + mask, + ::dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT,0)); + + if (log) + log->Printf ("Host::StartMonitoringChildProcess (callback=%p, baton=%p, pid=%i, monitor_signals=%i) source = %p\n", + callback, + callback_baton, + (int)pid, + monitor_signals, + source); + + if (source) + { + ::dispatch_source_set_cancel_handler (source, ^{ + ::dispatch_release (source); + }); + ::dispatch_source_set_event_handler (source, ^{ + + int status= 0; + int wait_pid = 0; + bool cancel = false; + bool exited = false; + do + { + wait_pid = ::waitpid (pid, &status, 0); + } while (wait_pid < 0 && errno == EINTR); + + if (wait_pid >= 0) + { + int signal = 0; + int exit_status = 0; + const char *status_cstr = NULL; + if (WIFSTOPPED(status)) + { + signal = WSTOPSIG(status); + status_cstr = "STOPPED"; + } + else if (WIFEXITED(status)) + { + exit_status = WEXITSTATUS(status); + status_cstr = "EXITED"; + exited = true; + } + else if (WIFSIGNALED(status)) + { + signal = WTERMSIG(status); + status_cstr = "SIGNALED"; + exited = true; + exit_status = -1; + } + else + { + status_cstr = "???"; + } + + if (log) + log->Printf ("::waitpid (pid = %llu, &status, 0) => pid = %i, status = 0x%8.8x (%s), signal = %i, exit_status = %i", + pid, + wait_pid, + status, + status_cstr, + signal, + exit_status); + + if (callback) + cancel = callback (callback_baton, pid, exited, signal, exit_status); + + if (exited || cancel) + { + ::dispatch_source_cancel(source); + } + } + }); + + ::dispatch_resume (source); + } + return HostThread(); +} + +//---------------------------------------------------------------------- +// Log to both stderr and to ASL Logging when running on MacOSX. +//---------------------------------------------------------------------- +void +Host::SystemLog (SystemLogType type, const char *format, va_list args) +{ + if (format && format[0]) + { + static aslmsg g_aslmsg = NULL; + if (g_aslmsg == NULL) + { + g_aslmsg = ::asl_new (ASL_TYPE_MSG); + char asl_key_sender[PATH_MAX]; + snprintf(asl_key_sender, sizeof(asl_key_sender), "com.apple.LLDB.framework"); + ::asl_set (g_aslmsg, ASL_KEY_SENDER, asl_key_sender); + } + + // Copy the va_list so we can log this message twice + va_list copy_args; + va_copy (copy_args, args); + // Log to stderr + ::vfprintf (stderr, format, copy_args); + va_end (copy_args); + + int asl_level; + switch (type) + { + case eSystemLogError: + asl_level = ASL_LEVEL_ERR; + break; + + case eSystemLogWarning: + asl_level = ASL_LEVEL_WARNING; + break; + } + + // Log to ASL + ::asl_vlog (NULL, g_aslmsg, asl_level, format, args); + } +} + +lldb::DataBufferSP +Host::GetAuxvData(lldb_private::Process *process) +{ + return lldb::DataBufferSP(); +} diff --git a/source/Host/macosx/HostInfoMacOSX.mm b/source/Host/macosx/HostInfoMacOSX.mm new file mode 100644 index 000000000000..f5a0540e8774 --- /dev/null +++ b/source/Host/macosx/HostInfoMacOSX.mm @@ -0,0 +1,372 @@ +//===-- HostInfoMacOSX.mm ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#if !defined(LLDB_DISABLE_PYTHON) +#include "Plugins/ScriptInterpreter/Python/lldb-python.h" +#endif + +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/macosx/HostInfoMacOSX.h" +#include "lldb/Core/Log.h" +#include "lldb/Interpreter/Args.h" +#include "lldb/Utility/SafeMachO.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +// C++ Includes +#include <string> + +// C inclues +#include <stdlib.h> +#include <sys/sysctl.h> +#include <sys/syslimits.h> +#include <sys/types.h> + +// Objective C/C++ includes +#include <CoreFoundation/CoreFoundation.h> +#include <Foundation/Foundation.h> +#include <mach-o/dyld.h> +#include <objc/objc-auto.h> + +// These are needed when compiling on systems +// that do not yet have these definitions +#include <AvailabilityMacros.h> +#ifndef CPU_SUBTYPE_X86_64_H +#define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8) +#endif +#ifndef CPU_TYPE_ARM64 +#define CPU_TYPE_ARM64 (CPU_TYPE_ARM|CPU_ARCH_ABI64) +#endif + +#include <TargetConditionals.h> // for TARGET_OS_TV, TARGET_OS_WATCH + +using namespace lldb_private; + +bool +HostInfoMacOSX::GetOSBuildString(std::string &s) +{ + int mib[2] = {CTL_KERN, KERN_OSVERSION}; + char cstr[PATH_MAX]; + size_t cstr_len = sizeof(cstr); + if (::sysctl(mib, 2, cstr, &cstr_len, NULL, 0) == 0) + { + s.assign(cstr, cstr_len); + return true; + } + + s.clear(); + return false; +} + +bool +HostInfoMacOSX::GetOSKernelDescription(std::string &s) +{ + int mib[2] = {CTL_KERN, KERN_VERSION}; + char cstr[PATH_MAX]; + size_t cstr_len = sizeof(cstr); + if (::sysctl(mib, 2, cstr, &cstr_len, NULL, 0) == 0) + { + s.assign(cstr, cstr_len); + return true; + } + s.clear(); + return false; +} + +bool +HostInfoMacOSX::GetOSVersion(uint32_t &major, uint32_t &minor, uint32_t &update) +{ + static uint32_t g_major = 0; + static uint32_t g_minor = 0; + static uint32_t g_update = 0; + + if (g_major == 0) + { + @autoreleasepool + { + NSDictionary *version_info = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; + NSString *version_value = [version_info objectForKey:@"ProductVersion"]; + const char *version_str = [version_value UTF8String]; + if (version_str) + Args::StringToVersion(version_str, g_major, g_minor, g_update); + } + } + + if (g_major != 0) + { + major = g_major; + minor = g_minor; + update = g_update; + return true; + } + return false; +} + +FileSpec +HostInfoMacOSX::GetProgramFileSpec() +{ + static FileSpec g_program_filespec; + if (!g_program_filespec) + { + char program_fullpath[PATH_MAX]; + // If DST is NULL, then return the number of bytes needed. + uint32_t len = sizeof(program_fullpath); + int err = _NSGetExecutablePath(program_fullpath, &len); + if (err == 0) + g_program_filespec.SetFile(program_fullpath, false); + else if (err == -1) + { + char *large_program_fullpath = (char *)::malloc(len + 1); + + err = _NSGetExecutablePath(large_program_fullpath, &len); + if (err == 0) + g_program_filespec.SetFile(large_program_fullpath, false); + + ::free(large_program_fullpath); + } + } + return g_program_filespec; +} + +bool +HostInfoMacOSX::ComputeSupportExeDirectory(FileSpec &file_spec) +{ + FileSpec lldb_file_spec; + if (!GetLLDBPath(lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + + size_t framework_pos = raw_path.find("LLDB.framework"); + if (framework_pos != std::string::npos) + { + framework_pos += strlen("LLDB.framework"); +#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) + // Shallow bundle + raw_path.resize(framework_pos); +#else + // Normal bundle + raw_path.resize(framework_pos); + raw_path.append("/Resources"); +#endif + } + else + { + // Find the bin path relative to the lib path where the cmake-based + // OS X .dylib lives. This is not going to work if the bin and lib + // dir are not both in the same dir. + // + // It is not going to work to do it by the executable path either, + // as in the case of a python script, the executable is python, not + // the lldb driver. + raw_path.append("/../bin"); + FileSpec support_dir_spec(raw_path, true); + if (!support_dir_spec.Exists() || !support_dir_spec.IsDirectory()) + { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + if (log) + log->Printf("HostInfoMacOSX::%s(): failed to find support directory", + __FUNCTION__); + return false; + } + + // Get normalization from support_dir_spec. Note the FileSpec resolve + // does not remove '..' in the path. + char *const dir_realpath = realpath(support_dir_spec.GetPath().c_str(), NULL); + if (dir_realpath) + { + raw_path = dir_realpath; + free(dir_realpath); + } + else + { + raw_path = support_dir_spec.GetPath(); + } + } + + file_spec.GetDirectory().SetString(llvm::StringRef(raw_path.c_str(), raw_path.size())); + return (bool)file_spec.GetDirectory(); +} + +bool +HostInfoMacOSX::ComputeHeaderDirectory(FileSpec &file_spec) +{ + FileSpec lldb_file_spec; + if (!HostInfo::GetLLDBPath(lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + + size_t framework_pos = raw_path.find("LLDB.framework"); + if (framework_pos != std::string::npos) + { + framework_pos += strlen("LLDB.framework"); + raw_path.resize(framework_pos); + raw_path.append("/Headers"); + } + file_spec.GetDirectory().SetString(llvm::StringRef(raw_path.c_str(), raw_path.size())); + return true; +} + +bool +HostInfoMacOSX::ComputePythonDirectory(FileSpec &file_spec) +{ +#ifndef LLDB_DISABLE_PYTHON + FileSpec lldb_file_spec; + if (!GetLLDBPath(lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + + size_t framework_pos = raw_path.find("LLDB.framework"); + if (framework_pos != std::string::npos) + { + framework_pos += strlen("LLDB.framework"); + raw_path.resize(framework_pos); + raw_path.append("/Resources/Python"); + } + else + { + llvm::SmallString<256> python_version_dir; + llvm::raw_svector_ostream os(python_version_dir); + os << "/python" << PY_MAJOR_VERSION << '.' << PY_MINOR_VERSION << "/site-packages"; + + // We may get our string truncated. Should we protect this with an assert? + raw_path.append(python_version_dir.c_str()); + } + file_spec.GetDirectory().SetString(llvm::StringRef(raw_path.c_str(), raw_path.size())); + return true; +#else + return false; +#endif +} + +bool +HostInfoMacOSX::ComputeClangDirectory(FileSpec &file_spec) +{ + FileSpec lldb_file_spec; + if (!GetLLDBPath (lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + + size_t framework_pos = raw_path.find("LLDB.framework"); + if (framework_pos != std::string::npos) + { + framework_pos += strlen("LLDB.framework"); + raw_path.resize(framework_pos); + raw_path.append("/Resources/Clang"); + } + file_spec.SetFile (raw_path.c_str(), true); + return true; +} + +bool +HostInfoMacOSX::ComputeSystemPluginsDirectory(FileSpec &file_spec) +{ + FileSpec lldb_file_spec; + if (!GetLLDBPath(lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + + size_t framework_pos = raw_path.find("LLDB.framework"); + if (framework_pos == std::string::npos) + return false; + + framework_pos += strlen("LLDB.framework"); + raw_path.resize(framework_pos); + raw_path.append("/Resources/PlugIns"); + file_spec.GetDirectory().SetString(llvm::StringRef(raw_path.c_str(), raw_path.size())); + return true; +} + +bool +HostInfoMacOSX::ComputeUserPluginsDirectory(FileSpec &file_spec) +{ + FileSpec temp_file("~/Library/Application Support/LLDB/PlugIns", true); + file_spec.GetDirectory().SetCString(temp_file.GetPath().c_str()); + return true; +} + +void +HostInfoMacOSX::ComputeHostArchitectureSupport(ArchSpec &arch_32, ArchSpec &arch_64) +{ + // All apple systems support 32 bit execution. + uint32_t cputype, cpusubtype; + uint32_t is_64_bit_capable = false; + size_t len = sizeof(cputype); + ArchSpec host_arch; + // These will tell us about the kernel architecture, which even on a 64 + // bit machine can be 32 bit... + if (::sysctlbyname("hw.cputype", &cputype, &len, NULL, 0) == 0) + { + len = sizeof(cpusubtype); + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) != 0) + cpusubtype = CPU_TYPE_ANY; + + len = sizeof(is_64_bit_capable); + ::sysctlbyname("hw.cpu64bit_capable", &is_64_bit_capable, &len, NULL, 0); + + if (is_64_bit_capable) + { + if (cputype & CPU_ARCH_ABI64) + { + // We have a 64 bit kernel on a 64 bit system + arch_64.SetArchitecture(eArchTypeMachO, cputype, cpusubtype); + } + else + { + // We have a 64 bit kernel that is returning a 32 bit cputype, the + // cpusubtype will be correct as if it were for a 64 bit architecture + arch_64.SetArchitecture(eArchTypeMachO, cputype | CPU_ARCH_ABI64, cpusubtype); + } + + // Now we need modify the cpusubtype for the 32 bit slices. + uint32_t cpusubtype32 = cpusubtype; +#if defined(__i386__) || defined(__x86_64__) + if (cpusubtype == CPU_SUBTYPE_486 || cpusubtype == CPU_SUBTYPE_X86_64_H) + cpusubtype32 = CPU_SUBTYPE_I386_ALL; +#elif defined(__arm__) || defined(__arm64__) || defined(__aarch64__) + if (cputype == CPU_TYPE_ARM || cputype == CPU_TYPE_ARM64) + cpusubtype32 = CPU_SUBTYPE_ARM_V7S; +#endif + arch_32.SetArchitecture(eArchTypeMachO, cputype & ~(CPU_ARCH_MASK), cpusubtype32); + + if (cputype == CPU_TYPE_ARM || cputype == CPU_TYPE_ARM64) + { + // When running on a watch or tv, report the host os correctly +#if defined (TARGET_OS_TV) && TARGET_OS_TV == 1 + arch_32.GetTriple().setOS(llvm::Triple::TvOS); + arch_64.GetTriple().setOS(llvm::Triple::TvOS); +#else + arch_32.GetTriple().setOS(llvm::Triple::IOS); + arch_64.GetTriple().setOS(llvm::Triple::IOS); +#endif + } + else + { + arch_32.GetTriple().setOS(llvm::Triple::MacOSX); + arch_64.GetTriple().setOS(llvm::Triple::MacOSX); + } + } + else + { + // We have a 32 bit kernel on a 32 bit system + arch_32.SetArchitecture(eArchTypeMachO, cputype, cpusubtype); +#if defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + arch_32.GetTriple().setOS(llvm::Triple::WatchOS); +#else + arch_32.GetTriple().setOS(llvm::Triple::IOS); +#endif + arch_64.Clear(); + } + } +} diff --git a/source/Host/macosx/HostThreadMacOSX.mm b/source/Host/macosx/HostThreadMacOSX.mm new file mode 100644 index 000000000000..c84a78efd9e6 --- /dev/null +++ b/source/Host/macosx/HostThreadMacOSX.mm @@ -0,0 +1,102 @@ +//===-- HostThreadMacOSX.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/macosx/HostThreadMacOSX.h" +#include "lldb/Host/Host.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <Foundation/Foundation.h> + +#include <objc/objc-auto.h> +#include <pthread.h> + +using namespace lldb_private; + +namespace +{ + +pthread_once_t g_thread_create_once = PTHREAD_ONCE_INIT; +pthread_key_t g_thread_create_key = 0; + +class MacOSXDarwinThread +{ + public: + MacOSXDarwinThread() + : m_pool(nil) + { + // Register our thread with the collector if garbage collection is enabled. + if (objc_collectingEnabled()) + { +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 + // On Leopard and earlier there is no way objc_registerThreadWithCollector + // function, so we do it manually. + auto_zone_register_thread(auto_zone()); +#else + // On SnowLeopard and later we just call the thread registration function. + objc_registerThreadWithCollector(); +#endif + } + else + { + m_pool = [[NSAutoreleasePool alloc] init]; + } + } + + ~MacOSXDarwinThread() + { + if (m_pool) + { + [m_pool drain]; + m_pool = nil; + } + } + + static void + PThreadDestructor(void *v) + { + if (v) + delete static_cast<MacOSXDarwinThread *>(v); + ::pthread_setspecific(g_thread_create_key, NULL); + } + + protected: + NSAutoreleasePool *m_pool; + + private: + DISALLOW_COPY_AND_ASSIGN(MacOSXDarwinThread); +}; + +void +InitThreadCreated() +{ + ::pthread_key_create(&g_thread_create_key, MacOSXDarwinThread::PThreadDestructor); +} +} // namespace + +HostThreadMacOSX::HostThreadMacOSX() + : HostThreadPosix() +{ +} + +HostThreadMacOSX::HostThreadMacOSX(lldb::thread_t thread) + : HostThreadPosix(thread) +{ +} + +lldb::thread_result_t +HostThreadMacOSX::ThreadCreateTrampoline(lldb::thread_arg_t arg) +{ + ::pthread_once(&g_thread_create_once, InitThreadCreated); + if (g_thread_create_key) + { + ::pthread_setspecific(g_thread_create_key, new MacOSXDarwinThread()); + } + + return HostThreadPosix::ThreadCreateTrampoline(arg); +} diff --git a/source/Host/macosx/Symbols.cpp b/source/Host/macosx/Symbols.cpp new file mode 100644 index 000000000000..f6a18febe6da --- /dev/null +++ b/source/Host/macosx/Symbols.cpp @@ -0,0 +1,559 @@ +//===-- Symbols.cpp ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Symbols.h" + +// C Includes +#include <dirent.h> +#include <pwd.h> +#include "lldb/Utility/SafeMachO.h" + +// C++ Includes +// Other libraries and framework includes +#include <CoreFoundation/CoreFoundation.h> + +// Project includes +#include "lldb/Core/ArchSpec.h" +#include "lldb/Core/DataBuffer.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Core/Timer.h" +#include "lldb/Core/UUID.h" +#include "lldb/Host/Endian.h" +#include "lldb/Host/Host.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Utility/CleanUp.h" +#include "Host/macosx/cfcpp/CFCBundle.h" +#include "Host/macosx/cfcpp/CFCData.h" +#include "Host/macosx/cfcpp/CFCReleaser.h" +#include "Host/macosx/cfcpp/CFCString.h" +#include "mach/machine.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm::MachO; + +#if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) // No DebugSymbols on the iOS devices +extern "C" { + +CFURLRef DBGCopyFullDSYMURLForUUID (CFUUIDRef uuid, CFURLRef exec_url); +CFDictionaryRef DBGCopyDSYMPropertyLists (CFURLRef dsym_url); + +} +#endif + +int +LocateMacOSXFilesUsingDebugSymbols +( + const ModuleSpec &module_spec, + ModuleSpec &return_module_spec +) +{ + return_module_spec = module_spec; + return_module_spec.GetFileSpec().Clear(); + return_module_spec.GetSymbolFileSpec().Clear(); + + int items_found = 0; + +#if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) // No DebugSymbols on the iOS devices + + const UUID *uuid = module_spec.GetUUIDPtr(); + const ArchSpec *arch = module_spec.GetArchitecturePtr(); + + if (uuid && uuid->IsValid()) + { + // Try and locate the dSYM file using DebugSymbols first + const UInt8 *module_uuid = (const UInt8 *)uuid->GetBytes(); + if (module_uuid != NULL) + { + CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes (NULL, + module_uuid[0], + module_uuid[1], + module_uuid[2], + module_uuid[3], + module_uuid[4], + module_uuid[5], + module_uuid[6], + module_uuid[7], + module_uuid[8], + module_uuid[9], + module_uuid[10], + module_uuid[11], + module_uuid[12], + module_uuid[13], + module_uuid[14], + module_uuid[15])); + + if (module_uuid_ref.get()) + { + CFCReleaser<CFURLRef> exec_url; + const FileSpec *exec_fspec = module_spec.GetFileSpecPtr(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + if (exec_fspec) + { + char exec_cf_path[PATH_MAX]; + if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path))) + exec_url.reset(::CFURLCreateFromFileSystemRepresentation (NULL, + (const UInt8 *)exec_cf_path, + strlen(exec_cf_path), + FALSE)); + } + + CFCReleaser<CFURLRef> dsym_url (::DBGCopyFullDSYMURLForUUID(module_uuid_ref.get(), exec_url.get())); + char path[PATH_MAX]; + + if (dsym_url.get()) + { + if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1)) + { + if (log) + { + log->Printf ("DebugSymbols framework returned dSYM path of %s for UUID %s -- looking for the dSYM", path, uuid->GetAsString().c_str()); + } + FileSpec dsym_filespec(path, path[0] == '~'); + + if (dsym_filespec.GetFileType () == FileSpec::eFileTypeDirectory) + { + dsym_filespec = Symbols::FindSymbolFileInBundle (dsym_filespec, uuid, arch); + ++items_found; + } + else + { + ++items_found; + } + return_module_spec.GetSymbolFileSpec() = dsym_filespec; + } + + bool success = false; + if (log) + { + if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1)) + { + log->Printf ("DebugSymbols framework returned dSYM path of %s for UUID %s -- looking for an exec file", path, uuid->GetAsString().c_str()); + } + + } + + CFCReleaser<CFDictionaryRef> dict(::DBGCopyDSYMPropertyLists (dsym_url.get())); + CFDictionaryRef uuid_dict = NULL; + if (dict.get()) + { + CFCString uuid_cfstr (uuid->GetAsString().c_str()); + uuid_dict = static_cast<CFDictionaryRef>(::CFDictionaryGetValue (dict.get(), uuid_cfstr.get())); + } + if (uuid_dict) + { + CFStringRef exec_cf_path = static_cast<CFStringRef>(::CFDictionaryGetValue (uuid_dict, CFSTR("DBGSymbolRichExecutable"))); + if (exec_cf_path && ::CFStringGetFileSystemRepresentation (exec_cf_path, path, sizeof(path))) + { + if (log) + { + log->Printf ("plist bundle has exec path of %s for UUID %s", path, uuid->GetAsString().c_str()); + } + ++items_found; + FileSpec exec_filespec (path, path[0] == '~'); + if (exec_filespec.Exists()) + { + success = true; + return_module_spec.GetFileSpec() = exec_filespec; + } + } + } + + if (!success) + { + // No dictionary, check near the dSYM bundle for an executable that matches... + if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1)) + { + char *dsym_extension_pos = ::strstr (path, ".dSYM"); + if (dsym_extension_pos) + { + *dsym_extension_pos = '\0'; + if (log) + { + log->Printf ("Looking for executable binary next to dSYM bundle with name with name %s", path); + } + FileSpec file_spec (path, true); + ModuleSpecList module_specs; + ModuleSpec matched_module_spec; + switch (file_spec.GetFileType()) + { + case FileSpec::eFileTypeDirectory: // Bundle directory? + { + CFCBundle bundle (path); + CFCReleaser<CFURLRef> bundle_exe_url (bundle.CopyExecutableURL ()); + if (bundle_exe_url.get()) + { + if (::CFURLGetFileSystemRepresentation (bundle_exe_url.get(), true, (UInt8*)path, sizeof(path)-1)) + { + FileSpec bundle_exe_file_spec (path, true); + if (ObjectFile::GetModuleSpecifications(bundle_exe_file_spec, 0, 0, module_specs) && + module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec)) + + { + ++items_found; + return_module_spec.GetFileSpec() = bundle_exe_file_spec; + if (log) + { + log->Printf ("Executable binary %s next to dSYM is compatible; using", path); + } + } + } + } + } + break; + + case FileSpec::eFileTypePipe: // Forget pipes + case FileSpec::eFileTypeSocket: // We can't process socket files + case FileSpec::eFileTypeInvalid: // File doesn't exist... + break; + + case FileSpec::eFileTypeUnknown: + case FileSpec::eFileTypeRegular: + case FileSpec::eFileTypeSymbolicLink: + case FileSpec::eFileTypeOther: + if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0, module_specs) && + module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec)) + + { + ++items_found; + return_module_spec.GetFileSpec() = file_spec; + if (log) + { + log->Printf ("Executable binary %s next to dSYM is compatible; using", path); + } + } + break; + } + } + } + } + } + } + } + } +#endif // #if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) + + return items_found; +} + +FileSpec +Symbols::FindSymbolFileInBundle (const FileSpec& dsym_bundle_fspec, + const lldb_private::UUID *uuid, + const ArchSpec *arch) +{ + char path[PATH_MAX]; + + FileSpec dsym_fspec; + + if (dsym_bundle_fspec.GetPath(path, sizeof(path))) + { + ::strncat (path, "/Contents/Resources/DWARF", sizeof(path) - strlen(path) - 1); + + lldb_utility::CleanUp <DIR *, int> dirp (opendir(path), NULL, closedir); + if (dirp.is_valid()) + { + dsym_fspec.GetDirectory().SetCString(path); + struct dirent* dp; + while ((dp = readdir(dirp.get())) != NULL) + { + // Only search directories + if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN) + { + if (dp->d_namlen == 1 && dp->d_name[0] == '.') + continue; + + if (dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.') + continue; + } + + if (dp->d_type == DT_REG || dp->d_type == DT_UNKNOWN) + { + dsym_fspec.GetFilename().SetCString(dp->d_name); + ModuleSpecList module_specs; + if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs)) + { + ModuleSpec spec; + for (size_t i = 0; i < module_specs.GetSize(); ++i) + { + assert(module_specs.GetModuleSpecAtIndex(i, spec)); + if ((uuid == NULL || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) && + (arch == NULL || (spec.GetArchitecturePtr() && spec.GetArchitecture().IsCompatibleMatch(*arch)))) + { + return dsym_fspec; + } + } + } + } + } + } + } + dsym_fspec.Clear(); + return dsym_fspec; +} + +static bool +GetModuleSpecInfoFromUUIDDictionary (CFDictionaryRef uuid_dict, ModuleSpec &module_spec) +{ + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + bool success = false; + if (uuid_dict != NULL && CFGetTypeID (uuid_dict) == CFDictionaryGetTypeID ()) + { + std::string str; + CFStringRef cf_str; + + cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGSymbolRichExecutable")); + if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ()) + { + if (CFCString::FileSystemRepresentation(cf_str, str)) + { + module_spec.GetFileSpec().SetFile (str.c_str(), true); + if (log) + { + log->Printf ("From dsymForUUID plist: Symbol rich executable is at '%s'", str.c_str()); + } + } + } + + cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGDSYMPath")); + if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ()) + { + if (CFCString::FileSystemRepresentation(cf_str, str)) + { + module_spec.GetSymbolFileSpec().SetFile (str.c_str(), true); + success = true; + if (log) + { + log->Printf ("From dsymForUUID plist: dSYM is at '%s'", str.c_str()); + } + } + } + + cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGArchitecture")); + if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ()) + { + if (CFCString::FileSystemRepresentation(cf_str, str)) + module_spec.GetArchitecture().SetTriple(str.c_str()); + } + + std::string DBGBuildSourcePath; + std::string DBGSourcePath; + + cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGBuildSourcePath")); + if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ()) + { + CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath); + } + + cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGSourcePath")); + if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ()) + { + CFCString::FileSystemRepresentation(cf_str, DBGSourcePath); + } + + if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) + { + module_spec.GetSourceMappingList().Append (ConstString(DBGBuildSourcePath.c_str()), ConstString(DBGSourcePath.c_str()), true); + } + } + return success; +} + + +bool +Symbols::DownloadObjectAndSymbolFile (ModuleSpec &module_spec, bool force_lookup) +{ + bool success = false; + const UUID *uuid_ptr = module_spec.GetUUIDPtr(); + const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr(); + + // It's expensive to check for the DBGShellCommands defaults setting, only do it once per + // lldb run and cache the result. + static bool g_have_checked_for_dbgshell_command = false; + static const char *g_dbgshell_command = NULL; + if (g_have_checked_for_dbgshell_command == false) + { + g_have_checked_for_dbgshell_command = true; + CFTypeRef defaults_setting = CFPreferencesCopyAppValue (CFSTR ("DBGShellCommands"), CFSTR ("com.apple.DebugSymbols")); + if (defaults_setting && CFGetTypeID (defaults_setting) == CFStringGetTypeID()) + { + char cstr_buf[PATH_MAX]; + if (CFStringGetCString ((CFStringRef) defaults_setting, cstr_buf, sizeof (cstr_buf), kCFStringEncodingUTF8)) + { + g_dbgshell_command = strdup (cstr_buf); // this malloc'ed memory will never be freed + } + } + if (defaults_setting) + { + CFRelease (defaults_setting); + } + } + + // When g_dbgshell_command is NULL, the user has not enabled the use of an external program + // to find the symbols, don't run it for them. + if (force_lookup == false && g_dbgshell_command == NULL) + { + return false; + } + + if (uuid_ptr || (file_spec_ptr && file_spec_ptr->Exists())) + { + static bool g_located_dsym_for_uuid_exe = false; + static bool g_dsym_for_uuid_exe_exists = false; + static char g_dsym_for_uuid_exe_path[PATH_MAX]; + if (!g_located_dsym_for_uuid_exe) + { + g_located_dsym_for_uuid_exe = true; + const char *dsym_for_uuid_exe_path_cstr = getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE"); + FileSpec dsym_for_uuid_exe_spec; + if (dsym_for_uuid_exe_path_cstr) + { + dsym_for_uuid_exe_spec.SetFile(dsym_for_uuid_exe_path_cstr, true); + g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists(); + } + + if (!g_dsym_for_uuid_exe_exists) + { + dsym_for_uuid_exe_spec.SetFile("/usr/local/bin/dsymForUUID", false); + g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists(); + if (!g_dsym_for_uuid_exe_exists) + { + long bufsize; + if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) != -1) + { + char buffer[bufsize]; + struct passwd pwd; + struct passwd *tilde_rc = NULL; + // we are a library so we need to use the reentrant version of getpwnam() + if (getpwnam_r ("rc", &pwd, buffer, bufsize, &tilde_rc) == 0 + && tilde_rc + && tilde_rc->pw_dir) + { + std::string dsymforuuid_path(tilde_rc->pw_dir); + dsymforuuid_path += "/bin/dsymForUUID"; + dsym_for_uuid_exe_spec.SetFile(dsymforuuid_path.c_str(), false); + g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists(); + } + } + } + } + if (!g_dsym_for_uuid_exe_exists && g_dbgshell_command != NULL) + { + dsym_for_uuid_exe_spec.SetFile(g_dbgshell_command, true); + g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists(); + } + + if (g_dsym_for_uuid_exe_exists) + dsym_for_uuid_exe_spec.GetPath (g_dsym_for_uuid_exe_path, sizeof(g_dsym_for_uuid_exe_path)); + } + if (g_dsym_for_uuid_exe_exists) + { + std::string uuid_str; + char file_path[PATH_MAX]; + file_path[0] = '\0'; + + if (uuid_ptr) + uuid_str = uuid_ptr->GetAsString(); + + if (file_spec_ptr) + file_spec_ptr->GetPath(file_path, sizeof(file_path)); + + StreamString command; + if (!uuid_str.empty()) + command.Printf("%s --ignoreNegativeCache --copyExecutable %s", g_dsym_for_uuid_exe_path, uuid_str.c_str()); + else if (file_path[0] != '\0') + command.Printf("%s --ignoreNegativeCache --copyExecutable %s", g_dsym_for_uuid_exe_path, file_path); + + if (!command.GetString().empty()) + { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + int exit_status = -1; + int signo = -1; + std::string command_output; + if (log) + { + if (!uuid_str.empty()) + log->Printf("Calling %s with UUID %s to find dSYM", g_dsym_for_uuid_exe_path, uuid_str.c_str()); + else if (file_path[0] != '\0') + log->Printf("Calling %s with file %s to find dSYM", g_dsym_for_uuid_exe_path, file_path); + } + Error error = Host::RunShellCommand (command.GetData(), + NULL, // current working directory + &exit_status, // Exit status + &signo, // Signal int * + &command_output, // Command output + 30, // Large timeout to allow for long dsym download times + false); // Don't run in a shell (we don't need shell expansion) + if (error.Success() && exit_status == 0 && !command_output.empty()) + { + CFCData data (CFDataCreateWithBytesNoCopy (NULL, + (const UInt8 *)command_output.data(), + command_output.size(), + kCFAllocatorNull)); + + CFCReleaser<CFDictionaryRef> plist((CFDictionaryRef)::CFPropertyListCreateFromXMLData (NULL, data.get(), kCFPropertyListImmutable, NULL)); + + if (plist.get() && CFGetTypeID (plist.get()) == CFDictionaryGetTypeID ()) + { + if (!uuid_str.empty()) + { + CFCString uuid_cfstr(uuid_str.c_str()); + CFDictionaryRef uuid_dict = (CFDictionaryRef)CFDictionaryGetValue (plist.get(), uuid_cfstr.get()); + success = GetModuleSpecInfoFromUUIDDictionary (uuid_dict, module_spec); + } + else + { + const CFIndex num_values = ::CFDictionaryGetCount(plist.get()); + if (num_values > 0) + { + std::vector<CFStringRef> keys (num_values, NULL); + std::vector<CFDictionaryRef> values (num_values, NULL); + ::CFDictionaryGetKeysAndValues(plist.get(), NULL, (const void **)&values[0]); + if (num_values == 1) + { + return GetModuleSpecInfoFromUUIDDictionary (values[0], module_spec); + } + else + { + for (CFIndex i=0; i<num_values; ++i) + { + ModuleSpec curr_module_spec; + if (GetModuleSpecInfoFromUUIDDictionary (values[i], curr_module_spec)) + { + if (module_spec.GetArchitecture().IsCompatibleMatch(curr_module_spec.GetArchitecture())) + { + module_spec = curr_module_spec; + return true; + } + } + } + } + } + } + } + } + else + { + if (log) + { + if (!uuid_str.empty()) + log->Printf("Called %s on %s, no matches", g_dsym_for_uuid_exe_path, uuid_str.c_str()); + else if (file_path[0] != '\0') + log->Printf("Called %s on %s, no matches", g_dsym_for_uuid_exe_path, file_path); + } + } + } + } + } + return success; +} + diff --git a/source/Host/macosx/ThisThread.cpp b/source/Host/macosx/ThisThread.cpp new file mode 100644 index 000000000000..95c7f2bf1e38 --- /dev/null +++ b/source/Host/macosx/ThisThread.cpp @@ -0,0 +1,39 @@ +//===-- ThisThread.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/ThisThread.h" + +#include <pthread.h> + +using namespace lldb_private; + +void +ThisThread::SetName(llvm::StringRef name) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + ::pthread_setname_np(name); +#endif +} + +void +ThisThread::GetName(llvm::SmallVectorImpl<char> &name) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + char pthread_name[1024]; + dispatch_queue_t current_queue = ::dispatch_get_current_queue(); + if (current_queue != NULL) + { + const char *queue_name = dispatch_queue_get_label(current_queue); + if (queue_name && queue_name[0]) + { + name = queue_name; + } + } +#endif +} diff --git a/source/Host/macosx/cfcpp/CFCBundle.cpp b/source/Host/macosx/cfcpp/CFCBundle.cpp new file mode 100644 index 000000000000..71b074993661 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCBundle.cpp @@ -0,0 +1,99 @@ +//===-- CFCBundle.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CFCBundle.h" +#include "CFCString.h" + +//---------------------------------------------------------------------- +// CFCBundle constructor +//---------------------------------------------------------------------- +CFCBundle::CFCBundle(const char *path) : + CFCReleaser<CFBundleRef>() +{ + if (path && path[0]) + SetPath(path); +} + +CFCBundle::CFCBundle(CFURLRef url) : + CFCReleaser<CFBundleRef>(url ? CFBundleCreate(NULL, url) : NULL) +{ +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFCBundle::~CFCBundle() +{ +} + +//---------------------------------------------------------------------- +// Set the path for a bundle by supplying a +//---------------------------------------------------------------------- +bool +CFCBundle::SetPath (const char *path) +{ + CFAllocatorRef alloc = kCFAllocatorDefault; + // Release our old bundle and URL + reset(); + + // Make a CFStringRef from the supplied path + CFCString cf_path; + cf_path.SetFileSystemRepresentation(path); + if (cf_path.get()) + { + // Make our Bundle URL + CFCReleaser<CFURLRef> bundle_url (::CFURLCreateWithFileSystemPath (alloc, cf_path.get(), kCFURLPOSIXPathStyle, true)); + if (bundle_url.get()) + reset (::CFBundleCreate (alloc, bundle_url.get())); + } + return get() != NULL; +} + +bool +CFCBundle::GetPath (char *dst, size_t dst_len) +{ + CFBundleRef bundle = get(); + if (bundle) + { + CFCReleaser<CFURLRef> bundle_url (CFBundleCopyBundleURL (bundle)); + if (bundle_url.get()) + { + Boolean resolveAgainstBase = 0; + return ::CFURLGetFileSystemRepresentation (bundle_url.get(), resolveAgainstBase, (UInt8 *)dst, dst_len) != 0; + } + } + return false; +} + +CFStringRef +CFCBundle::GetIdentifier () const +{ + CFBundleRef bundle = get(); + if (bundle != NULL) + return ::CFBundleGetIdentifier (bundle); + return NULL; +} + +CFTypeRef +CFCBundle::GetValueForInfoDictionaryKey(CFStringRef key) const +{ + CFBundleRef bundle = get(); + if (bundle != NULL) + return ::CFBundleGetValueForInfoDictionaryKey(bundle, key); + return NULL; +} + +CFURLRef +CFCBundle::CopyExecutableURL () const +{ + CFBundleRef bundle = get(); + if (bundle != NULL) + return CFBundleCopyExecutableURL(bundle); + return NULL; +} diff --git a/source/Host/macosx/cfcpp/CFCBundle.h b/source/Host/macosx/cfcpp/CFCBundle.h new file mode 100644 index 000000000000..1cd1b681af84 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCBundle.h @@ -0,0 +1,50 @@ +//===-- CFCBundle.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFBundle_h_ +#define CoreFoundationCPP_CFBundle_h_ + +#include "CFCReleaser.h" + +class CFCBundle : public CFCReleaser<CFBundleRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFCBundle (const char *path = NULL); + CFCBundle (CFURLRef url); + + virtual + ~CFCBundle(); + + CFURLRef + CopyExecutableURL () const; + + CFStringRef + GetIdentifier () const; + + CFTypeRef + GetValueForInfoDictionaryKey(CFStringRef key) const; + + bool + GetPath (char *dst, size_t dst_len); + + bool + SetPath (const char *path); + +private: + // Disallow copy and assignment constructors + CFCBundle(const CFCBundle&); + + const CFCBundle& + operator=(const CFCBundle&); +}; + +#endif // #ifndef CoreFoundationCPP_CFBundle_h_ diff --git a/source/Host/macosx/cfcpp/CFCData.cpp b/source/Host/macosx/cfcpp/CFCData.cpp new file mode 100644 index 000000000000..4f49368ad8ad --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCData.cpp @@ -0,0 +1,82 @@ +//===-- CFCData.cpp ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CFCData.h" + +//---------------------------------------------------------------------- +// CFCData constructor +//---------------------------------------------------------------------- +CFCData::CFCData(CFDataRef data) : + CFCReleaser<CFDataRef>(data) +{ + +} + +//---------------------------------------------------------------------- +// CFCData copy constructor +//---------------------------------------------------------------------- +CFCData::CFCData(const CFCData& rhs) : + CFCReleaser<CFDataRef>(rhs) +{ + +} + +//---------------------------------------------------------------------- +// CFCData copy constructor +//---------------------------------------------------------------------- +CFCData& +CFCData::operator=(const CFCData& rhs) + +{ + if (this != &rhs) + *this = rhs; + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFCData::~CFCData() +{ +} + + +CFIndex +CFCData::GetLength() const +{ + CFDataRef data = get(); + if (data) + return CFDataGetLength (data); + return 0; +} + + +const uint8_t* +CFCData::GetBytePtr() const +{ + CFDataRef data = get(); + if (data) + return CFDataGetBytePtr (data); + return NULL; +} + +CFDataRef +CFCData::Serialize(CFPropertyListRef plist, CFPropertyListFormat format) +{ + CFAllocatorRef alloc = kCFAllocatorDefault; + reset(); + CFCReleaser<CFWriteStreamRef> stream (::CFWriteStreamCreateWithAllocatedBuffers (alloc, alloc)); + ::CFWriteStreamOpen (stream.get()); + CFIndex len = ::CFPropertyListWriteToStream (plist, stream.get(), format, NULL); + if (len > 0) + reset((CFDataRef)::CFWriteStreamCopyProperty (stream.get(), kCFStreamPropertyDataWritten)); + ::CFWriteStreamClose (stream.get()); + return get(); +} + diff --git a/source/Host/macosx/cfcpp/CFCData.h b/source/Host/macosx/cfcpp/CFCData.h new file mode 100644 index 000000000000..6a718f54c055 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCData.h @@ -0,0 +1,35 @@ +//===-- CFCData.h -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFData_h_ +#define CoreFoundationCPP_CFData_h_ + +#include "CFCReleaser.h" + +class CFCData : public CFCReleaser<CFDataRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFCData(CFDataRef data = NULL); + CFCData(const CFCData& rhs); + CFCData& operator=(const CFCData& rhs); + virtual ~CFCData(); + + CFDataRef Serialize(CFPropertyListRef plist, CFPropertyListFormat format); + const uint8_t* GetBytePtr () const; + CFIndex GetLength () const; +protected: + //------------------------------------------------------------------ + // Classes that inherit from CFCData can see and modify these + //------------------------------------------------------------------ +}; + +#endif // #ifndef CoreFoundationCPP_CFData_h_ diff --git a/source/Host/macosx/cfcpp/CFCMutableArray.cpp b/source/Host/macosx/cfcpp/CFCMutableArray.cpp new file mode 100644 index 000000000000..c3c0a11193a7 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCMutableArray.cpp @@ -0,0 +1,166 @@ +//===-- CFCMutableArray.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CFCMutableArray.h" +#include "CFCString.h" + +//---------------------------------------------------------------------- +// CFCString constructor +//---------------------------------------------------------------------- +CFCMutableArray::CFCMutableArray(CFMutableArrayRef s) : + CFCReleaser<CFMutableArrayRef> (s) +{ +} + +//---------------------------------------------------------------------- +// CFCMutableArray copy constructor +//---------------------------------------------------------------------- +CFCMutableArray::CFCMutableArray(const CFCMutableArray& rhs) : + CFCReleaser<CFMutableArrayRef> (rhs) // NOTE: this won't make a copy of the array, just add a new reference to it +{ +} + +//---------------------------------------------------------------------- +// CFCMutableArray copy constructor +//---------------------------------------------------------------------- +CFCMutableArray& +CFCMutableArray::operator=(const CFCMutableArray& rhs) +{ + if (this != &rhs) + *this = rhs; // NOTE: this operator won't make a copy of the array, just add a new reference to it + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFCMutableArray::~CFCMutableArray() +{ +} + + +CFIndex +CFCMutableArray::GetCount() const +{ + CFMutableArrayRef array = get(); + if (array) + return ::CFArrayGetCount (array); + return 0; +} + +CFIndex +CFCMutableArray::GetCountOfValue(CFRange range, const void *value) const +{ + CFMutableArrayRef array = get(); + if (array) + return ::CFArrayGetCountOfValue (array, range, value); + return 0; +} + +CFIndex +CFCMutableArray::GetCountOfValue(const void *value) const +{ + CFMutableArrayRef array = get(); + if (array) + return ::CFArrayGetCountOfValue (array, CFRangeMake(0, GetCount()), value); + return 0; +} + +const void * +CFCMutableArray::GetValueAtIndex(CFIndex idx) const +{ + CFMutableArrayRef array = get(); + if (array) + { + const CFIndex num_array_items = ::CFArrayGetCount (array); + if (0 <= idx && idx < num_array_items) + { + return ::CFArrayGetValueAtIndex (array, idx); + } + } + return NULL; +} + +bool +CFCMutableArray::SetValueAtIndex(CFIndex idx, const void *value) +{ + CFMutableArrayRef array = get(); + if (array != NULL) + { + const CFIndex num_array_items = ::CFArrayGetCount (array); + if (0 <= idx && idx < num_array_items) + { + ::CFArraySetValueAtIndex (array, idx, value); + return true; + } + } + return false; +} + + +bool +CFCMutableArray::AppendValue(const void *value, bool can_create) +{ + CFMutableArrayRef array = get(); + if (array == NULL) + { + if (can_create == false) + return false; + array = ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + reset ( array ); + } + if (array != NULL) + { + ::CFArrayAppendValue(array, value); + return true; + } + return false; +} + + +bool +CFCMutableArray::AppendCStringAsCFString (const char *s, CFStringEncoding encoding, bool can_create) +{ + CFMutableArrayRef array = get(); + if (array == NULL) + { + if (can_create == false) + return false; + array = ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + reset ( array ); + } + if (array != NULL) + { + CFCString cf_str (s, encoding); + ::CFArrayAppendValue (array, cf_str.get()); + return true; + } + return false; +} + +bool +CFCMutableArray::AppendFileSystemRepresentationAsCFString (const char *s, bool can_create) +{ + CFMutableArrayRef array = get(); + if (array == NULL) + { + if (can_create == false) + return false; + array = ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + reset ( array ); + } + if (array != NULL) + { + CFCString cf_path; + cf_path.SetFileSystemRepresentation(s); + ::CFArrayAppendValue (array, cf_path.get()); + return true; + } + return false; +} diff --git a/source/Host/macosx/cfcpp/CFCMutableArray.h b/source/Host/macosx/cfcpp/CFCMutableArray.h new file mode 100644 index 000000000000..f78cd92ffab1 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCMutableArray.h @@ -0,0 +1,39 @@ +//===-- CFCMutableArray.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFMutableArray_h_ +#define CoreFoundationCPP_CFMutableArray_h_ + +#include "CFCReleaser.h" + +class CFCMutableArray : public CFCReleaser<CFMutableArrayRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFCMutableArray(CFMutableArrayRef array = NULL); + CFCMutableArray(const CFCMutableArray& rhs); // This will copy the array contents into a new array + CFCMutableArray& operator=(const CFCMutableArray& rhs); // This will re-use the same array and just bump the ref count + virtual ~CFCMutableArray(); + + CFIndex GetCount() const; + CFIndex GetCountOfValue(const void *value) const; + CFIndex GetCountOfValue(CFRange range, const void *value) const; + const void * GetValueAtIndex(CFIndex idx) const; + bool SetValueAtIndex(CFIndex idx, const void *value); + bool AppendValue(const void *value, bool can_create = true); // Appends value and optionally creates a CFCMutableArray if this class doesn't contain one + bool AppendCStringAsCFString (const char *cstr, + CFStringEncoding encoding = kCFStringEncodingUTF8, + bool can_create = true); + bool AppendFileSystemRepresentationAsCFString (const char *s, + bool can_create = true); +}; + +#endif // #ifndef CoreFoundationCPP_CFMutableArray_h_ diff --git a/source/Host/macosx/cfcpp/CFCMutableDictionary.cpp b/source/Host/macosx/cfcpp/CFCMutableDictionary.cpp new file mode 100644 index 000000000000..bce023bfd616 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCMutableDictionary.cpp @@ -0,0 +1,529 @@ +//===-- CFCMutableDictionary.cpp --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CFCMutableDictionary.h" +#include "CFCString.h" +//---------------------------------------------------------------------- +// CFCString constructor +//---------------------------------------------------------------------- +CFCMutableDictionary::CFCMutableDictionary(CFMutableDictionaryRef s) : + CFCReleaser<CFMutableDictionaryRef> (s) +{ +} + +//---------------------------------------------------------------------- +// CFCMutableDictionary copy constructor +//---------------------------------------------------------------------- +CFCMutableDictionary::CFCMutableDictionary(const CFCMutableDictionary& rhs) : + CFCReleaser<CFMutableDictionaryRef> (rhs) +{ +} + +//---------------------------------------------------------------------- +// CFCMutableDictionary copy constructor +//---------------------------------------------------------------------- +const CFCMutableDictionary& +CFCMutableDictionary::operator=(const CFCMutableDictionary& rhs) +{ + if (this != &rhs) + *this = rhs; + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFCMutableDictionary::~CFCMutableDictionary() +{ +} + + +CFIndex +CFCMutableDictionary::GetCount() const +{ + CFMutableDictionaryRef dict = get(); + if (dict) + return ::CFDictionaryGetCount (dict); + return 0; +} + +CFIndex +CFCMutableDictionary::GetCountOfKey(const void *key) const + +{ + CFMutableDictionaryRef dict = get(); + if (dict) + return ::CFDictionaryGetCountOfKey (dict, key); + return 0; +} + +CFIndex +CFCMutableDictionary::GetCountOfValue(const void *value) const + +{ + CFMutableDictionaryRef dict = get(); + if (dict) + return ::CFDictionaryGetCountOfValue (dict, value); + return 0; +} + +void +CFCMutableDictionary::GetKeysAndValues(const void **keys, const void **values) const +{ + CFMutableDictionaryRef dict = get(); + if (dict) + ::CFDictionaryGetKeysAndValues (dict, keys, values); +} + + +const void * +CFCMutableDictionary::GetValue(const void *key) const + +{ + CFMutableDictionaryRef dict = get(); + if (dict) + return ::CFDictionaryGetValue (dict, key); + return NULL; +} + +Boolean +CFCMutableDictionary::GetValueIfPresent(const void *key, const void **value_handle) const +{ + CFMutableDictionaryRef dict = get(); + if (dict) + return ::CFDictionaryGetValueIfPresent (dict, key, value_handle); + return false; +} + + +CFMutableDictionaryRef +CFCMutableDictionary::Dictionary(bool can_create) +{ + CFMutableDictionaryRef dict = get(); + if (can_create && dict == NULL) + { + dict = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + reset ( dict ); + } + return dict; +} + +bool +CFCMutableDictionary::AddValue(CFStringRef key, const void *value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, value); + return true; + } + return false; +} + +bool +CFCMutableDictionary::SetValue(CFStringRef key, const void *value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, value); + return true; + } + return false; +} + +bool +CFCMutableDictionary::AddValueSInt8(CFStringRef key, int8_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt8Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueSInt8(CFStringRef key, int8_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt8Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueSInt16(CFStringRef key, int16_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt16Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueSInt16(CFStringRef key, int16_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt16Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueSInt32(CFStringRef key, int32_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueSInt32(CFStringRef key, int32_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueSInt64(CFStringRef key, int64_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueSInt64(CFStringRef key, int64_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueUInt8(CFStringRef key, uint8_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Have to promote to the next size type so things don't appear negative of the MSBit is set... + int16_t sval = value; + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt16Type, &sval)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueUInt8(CFStringRef key, uint8_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Have to promote to the next size type so things don't appear negative of the MSBit is set... + int16_t sval = value; + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt16Type, &sval)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + + +bool +CFCMutableDictionary::AddValueUInt16(CFStringRef key, uint16_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Have to promote to the next size type so things don't appear negative of the MSBit is set... + int32_t sval = value; + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &sval)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueUInt16(CFStringRef key, uint16_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Have to promote to the next size type so things don't appear negative of the MSBit is set... + int32_t sval = value; + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &sval)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueUInt32(CFStringRef key, uint32_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Have to promote to the next size type so things don't appear negative of the MSBit is set... + int64_t sval = value; + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &sval)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueUInt32(CFStringRef key, uint32_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // Have to promote to the next size type so things don't appear negative of the MSBit is set... + int64_t sval = value; + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &sval)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + + +bool +CFCMutableDictionary::AddValueUInt64(CFStringRef key, uint64_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // The number may appear negative if the MSBit is set in "value". Due to a limitation of + // CFNumber, there isn't a way to have it show up otherwise as of this writing. + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + + +bool +CFCMutableDictionary::SetValueUInt64(CFStringRef key, uint64_t value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // The number may appear negative if the MSBit is set in "value". Due to a limitation of + // CFNumber, there isn't a way to have it show up otherwise as of this writing. + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueDouble(CFStringRef key, double value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // The number may appear negative if the MSBit is set in "value". Due to a limitation of + // CFNumber, there isn't a way to have it show up otherwise as of this writing. + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberDoubleType, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueDouble(CFStringRef key, double value, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + // The number may appear negative if the MSBit is set in "value". Due to a limitation of + // CFNumber, there isn't a way to have it show up otherwise as of this writing. + CFCReleaser<CFNumberRef> cf_number(::CFNumberCreate (kCFAllocatorDefault, kCFNumberDoubleType, &value)); + if (cf_number.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_number.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::AddValueCString(CFStringRef key, const char *cstr, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCString cf_str(cstr, kCFStringEncodingUTF8); + if (cf_str.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionaryAddValue (dict, key, cf_str.get()); + return true; + } + } + return false; +} + +bool +CFCMutableDictionary::SetValueCString(CFStringRef key, const char *cstr, bool can_create) +{ + CFMutableDictionaryRef dict = Dictionary(can_create); + if (dict != NULL) + { + CFCString cf_str(cstr, kCFStringEncodingUTF8); + if (cf_str.get()) + { + // Let the dictionary own the CFNumber + ::CFDictionarySetValue (dict, key, cf_str.get()); + return true; + } + } + return false; +} + + +void +CFCMutableDictionary::RemoveAllValues() +{ + CFMutableDictionaryRef dict = get(); + if (dict) + ::CFDictionaryRemoveAllValues(dict); +} + +void +CFCMutableDictionary::RemoveValue(const void *value) +{ + CFMutableDictionaryRef dict = get(); + if (dict) + ::CFDictionaryRemoveValue(dict, value); +} +void +CFCMutableDictionary::ReplaceValue(const void *key, const void *value) +{ + CFMutableDictionaryRef dict = get(); + if (dict) + ::CFDictionaryReplaceValue (dict, key, value); +} + diff --git a/source/Host/macosx/cfcpp/CFCMutableDictionary.h b/source/Host/macosx/cfcpp/CFCMutableDictionary.h new file mode 100644 index 000000000000..a1cfb68f569e --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCMutableDictionary.h @@ -0,0 +1,79 @@ +//===-- CFCMutableDictionary.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFMutableDictionary_h_ +#define CoreFoundationCPP_CFMutableDictionary_h_ + +#include "CFCReleaser.h" + +class CFCMutableDictionary : public CFCReleaser<CFMutableDictionaryRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFCMutableDictionary(CFMutableDictionaryRef s = NULL); + CFCMutableDictionary(const CFCMutableDictionary& rhs); + virtual ~CFCMutableDictionary(); + + //------------------------------------------------------------------ + // Operators + //------------------------------------------------------------------ + const CFCMutableDictionary& + operator=(const CFCMutableDictionary& rhs); + + + CFIndex GetCount() const; + CFIndex GetCountOfKey(const void *value) const; + CFIndex GetCountOfValue(const void *value) const; + void GetKeysAndValues(const void **keys, const void **values) const; + const void * GetValue(const void *key) const; + Boolean GetValueIfPresent(const void *key, const void **value_handle) const; + bool AddValue(CFStringRef key, const void *value, bool can_create = false); + bool SetValue(CFStringRef key, const void *value, bool can_create = false); + bool AddValueSInt8(CFStringRef key, int8_t value, bool can_create = false); + bool SetValueSInt8(CFStringRef key, int8_t value, bool can_create = false); + bool AddValueSInt16(CFStringRef key, int16_t value, bool can_create = false); + bool SetValueSInt16(CFStringRef key, int16_t value, bool can_create = false); + bool AddValueSInt32(CFStringRef key, int32_t value, bool can_create = false); + bool SetValueSInt32(CFStringRef key, int32_t value, bool can_create = false); + bool AddValueSInt64(CFStringRef key, int64_t value, bool can_create = false); + bool SetValueSInt64(CFStringRef key, int64_t value, bool can_create = false); + bool AddValueUInt8(CFStringRef key, uint8_t value, bool can_create = false); + bool SetValueUInt8(CFStringRef key, uint8_t value, bool can_create = false); + bool AddValueUInt16(CFStringRef key, uint16_t value, bool can_create = false); + bool SetValueUInt16(CFStringRef key, uint16_t value, bool can_create = false); + bool AddValueUInt32(CFStringRef key, uint32_t value, bool can_create = false); + bool SetValueUInt32(CFStringRef key, uint32_t value, bool can_create = false); + bool AddValueUInt64(CFStringRef key, uint64_t value, bool can_create = false); + bool SetValueUInt64(CFStringRef key, uint64_t value, bool can_create = false); + bool AddValueDouble(CFStringRef key, double value, bool can_create = false); + bool SetValueDouble(CFStringRef key, double value, bool can_create = false); + bool AddValueCString(CFStringRef key, const char *cstr, bool can_create = false); + bool SetValueCString(CFStringRef key, const char *cstr, bool can_create = false); + void RemoveValue(const void *value); + void ReplaceValue(const void *key, const void *value); + void RemoveAllValues(); + CFMutableDictionaryRef Dictionary(bool can_create); + + +protected: + //------------------------------------------------------------------ + // Classes that inherit from CFCMutableDictionary can see and modify these + //------------------------------------------------------------------ + +private: + //------------------------------------------------------------------ + // For CFCMutableDictionary only + //------------------------------------------------------------------ + +}; + + +#endif // CoreFoundationCPP_CFMutableDictionary_h_ diff --git a/source/Host/macosx/cfcpp/CFCMutableSet.cpp b/source/Host/macosx/cfcpp/CFCMutableSet.cpp new file mode 100644 index 000000000000..afc09e180b6b --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCMutableSet.cpp @@ -0,0 +1,114 @@ +//===-- CFCMutableSet.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CFCMutableSet.h" + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes + +//---------------------------------------------------------------------- +// CFCString constructor +//---------------------------------------------------------------------- +CFCMutableSet::CFCMutableSet(CFMutableSetRef s) : + CFCReleaser<CFMutableSetRef> (s) +{ +} + +//---------------------------------------------------------------------- +// CFCMutableSet copy constructor +//---------------------------------------------------------------------- +CFCMutableSet::CFCMutableSet(const CFCMutableSet& rhs) : + CFCReleaser<CFMutableSetRef> (rhs) +{ +} + +//---------------------------------------------------------------------- +// CFCMutableSet copy constructor +//---------------------------------------------------------------------- +const CFCMutableSet& +CFCMutableSet::operator=(const CFCMutableSet& rhs) +{ + if (this != &rhs) + *this = rhs; + return *this; +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFCMutableSet::~CFCMutableSet() +{ +} + + +CFIndex +CFCMutableSet::GetCount() const +{ + CFMutableSetRef set = get(); + if (set) + return ::CFSetGetCount (set); + return 0; +} + +CFIndex +CFCMutableSet::GetCountOfValue(const void *value) const +{ + CFMutableSetRef set = get(); + if (set) + return ::CFSetGetCountOfValue (set, value); + return 0; +} + +const void * +CFCMutableSet::GetValue(const void *value) const +{ + CFMutableSetRef set = get(); + if (set) + return ::CFSetGetValue(set, value); + return NULL; +} + + +const void * +CFCMutableSet::AddValue(const void *value, bool can_create) +{ + CFMutableSetRef set = get(); + if (set == NULL) + { + if (can_create == false) + return NULL; + set = ::CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); + reset ( set ); + } + if (set != NULL) + { + ::CFSetAddValue(set, value); + return value; + } + return NULL; +} + +void +CFCMutableSet::RemoveValue(const void *value) +{ + CFMutableSetRef set = get(); + if (set) + ::CFSetRemoveValue(set, value); +} + +void +CFCMutableSet::RemoveAllValues() +{ + CFMutableSetRef set = get(); + if (set) + ::CFSetRemoveAllValues(set); +} + diff --git a/source/Host/macosx/cfcpp/CFCMutableSet.h b/source/Host/macosx/cfcpp/CFCMutableSet.h new file mode 100644 index 000000000000..78f7a8be81d2 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCMutableSet.h @@ -0,0 +1,53 @@ +//===-- CFCMutableSet.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFMutableSet_h_ +#define CoreFoundationCPP_CFMutableSet_h_ + +#include "CFCReleaser.h" + +class CFCMutableSet : public CFCReleaser<CFMutableSetRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFCMutableSet(CFMutableSetRef s = NULL); + CFCMutableSet(const CFCMutableSet& rhs); + virtual ~CFCMutableSet(); + + //------------------------------------------------------------------ + // Operators + //------------------------------------------------------------------ + const CFCMutableSet& + operator=(const CFCMutableSet& rhs); + + + CFIndex GetCount() const; + CFIndex GetCountOfValue(const void *value) const; + const void * GetValue(const void *value) const; + const void * AddValue(const void *value, bool can_create); + void RemoveValue(const void *value); + void RemoveAllValues(); + + + +protected: + //------------------------------------------------------------------ + // Classes that inherit from CFCMutableSet can see and modify these + //------------------------------------------------------------------ + +private: + //------------------------------------------------------------------ + // For CFCMutableSet only + //------------------------------------------------------------------ + +}; + +#endif // CoreFoundationCPP_CFMutableSet_h_ diff --git a/source/Host/macosx/cfcpp/CFCReleaser.h b/source/Host/macosx/cfcpp/CFCReleaser.h new file mode 100644 index 000000000000..67dd2ead5799 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCReleaser.h @@ -0,0 +1,158 @@ +//===-- CFCReleaser.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFReleaser_h_ +#define CoreFoundationCPP_CFReleaser_h_ + +#include <CoreFoundation/CoreFoundation.h> + +#ifdef __cplusplus + +#include <assert.h> + +//---------------------------------------------------------------------- +// Templatized CF helper class that can own any CF pointer and will +// call CFRelease() on any valid pointer it owns unless that pointer is +// explicitly released using the release() member function. This class +// is designed to mimic the std::auto_ptr<T> class and has all of the +// same functions. The one thing to watch out for is the +// CFCReleaser<T>::release() function won't actually CFRelease any owned +// pointer, it is designed to relinquish ownership of the pointer just +// like std:auto_ptr<T>::release() does. +//---------------------------------------------------------------------- +template <class T> +class CFCReleaser +{ +public: + //---------------------------------------------------------- + // Constructor that takes a pointer to a CF object that is + // to be released when this object goes out of scope + //---------------------------------------------------------- + CFCReleaser(T ptr = NULL) : + _ptr(ptr) + { + } + + //---------------------------------------------------------- + // Copy constructor + // + // Note that copying a CFCReleaser will not transfer + // ownership of the contained pointer, but it will bump its + // reference count. This is where this class differs from + // std::auto_ptr. + //---------------------------------------------------------- + CFCReleaser(const CFCReleaser& rhs) : + _ptr(rhs.get()) + { + if (get()) + ::CFRetain(get()); + } + + + //---------------------------------------------------------- + // The destructor will release the pointer that it contains + // if it has a valid pointer. + //---------------------------------------------------------- + virtual ~CFCReleaser() + { + reset(); + } + + //---------------------------------------------------------- + // Assignment operator. + // + // Note that assigning one CFCReleaser to another will + // not transfer ownership of the contained pointer, but it + // will bump its reference count. This is where this class + // differs from std::auto_ptr. + //---------------------------------------------------------- + CFCReleaser& + operator= (const CFCReleaser<T>& rhs) + { + if (this != &rhs) + { + // Replace our owned pointer with the new one + reset(rhs.get()); + // Retain the current pointer that we own + if (get()) + ::CFRetain(get()); + } + return *this; + } + + //---------------------------------------------------------- + // Get the address of the contained type in case it needs + // to be passed to a function that will fill in a pointer + // value. The function currently will assert if _ptr is not + // NULL because the only time this method should be used is + // if another function will modify the contents, and we + // could leak a pointer if this is not NULL. If the + // assertion fires, check the offending code, or call + // reset() prior to using the "ptr_address()" member to make + // sure any owned objects has CFRelease called on it. + // I had to add the "enforce_null" bool here because some + // API's require the pointer address even though they don't change it. + //---------------------------------------------------------- + T* + ptr_address(bool enforce_null = true) + { + if (enforce_null) + assert (_ptr == NULL); + return &_ptr; + } + + //---------------------------------------------------------- + // Access the pointer itself + //---------------------------------------------------------- + T + get() + { + return _ptr; + } + + const T + get() const + { + return _ptr; + } + + + //---------------------------------------------------------- + // Set a new value for the pointer and CFRelease our old + // value if we had a valid one. + //---------------------------------------------------------- + void + reset(T ptr = NULL) + { + if ((_ptr != NULL) && (ptr != _ptr)) + ::CFRelease(_ptr); + _ptr = ptr; + } + + //---------------------------------------------------------- + // Release ownership without calling CFRelease. This class + // is designed to mimic std::auto_ptr<T>, so the release + // method releases ownership of the contained pointer + // and does NOT call CFRelease. + //---------------------------------------------------------- + T + release() + { + T tmp = _ptr; + _ptr = NULL; + return tmp; + } + +private: + T _ptr; +}; + +#endif // #ifdef __cplusplus +#endif // #ifndef CoreFoundationCPP_CFReleaser_h_ + diff --git a/source/Host/macosx/cfcpp/CFCString.cpp b/source/Host/macosx/cfcpp/CFCString.cpp new file mode 100644 index 000000000000..81a96b824999 --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCString.cpp @@ -0,0 +1,195 @@ +//===-- CFCString.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CFCString.h" +#include <string> +#include <glob.h> + +//---------------------------------------------------------------------- +// CFCString constructor +//---------------------------------------------------------------------- +CFCString::CFCString(CFStringRef s) : + CFCReleaser<CFStringRef> (s) +{ +} + +//---------------------------------------------------------------------- +// CFCString copy constructor +//---------------------------------------------------------------------- +CFCString::CFCString(const CFCString& rhs) : + CFCReleaser<CFStringRef> (rhs) +{ + +} + +//---------------------------------------------------------------------- +// CFCString copy constructor +//---------------------------------------------------------------------- +CFCString& +CFCString::operator=(const CFCString& rhs) +{ + if (this != &rhs) + *this = rhs; + return *this; +} + +CFCString::CFCString (const char *cstr, CFStringEncoding cstr_encoding) : + CFCReleaser<CFStringRef> () +{ + if (cstr && cstr[0]) + { + reset(::CFStringCreateWithCString(kCFAllocatorDefault, cstr, cstr_encoding)); + } +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +CFCString::~CFCString() +{ +} + +const char * +CFCString::GetFileSystemRepresentation(std::string& s) +{ + return CFCString::FileSystemRepresentation(get(), s); +} + +CFStringRef +CFCString::SetFileSystemRepresentation (const char *path) +{ + CFStringRef new_value = NULL; + if (path && path[0]) + new_value = ::CFStringCreateWithFileSystemRepresentation (kCFAllocatorDefault, path); + reset(new_value); + return get(); +} + + +CFStringRef +CFCString::SetFileSystemRepresentationFromCFType (CFTypeRef cf_type) +{ + CFStringRef new_value = NULL; + if (cf_type != NULL) + { + CFTypeID cf_type_id = ::CFGetTypeID(cf_type); + + if (cf_type_id == ::CFStringGetTypeID()) + { + // Retain since we are using the existing object + new_value = (CFStringRef)::CFRetain(cf_type); + } + else if (cf_type_id == ::CFURLGetTypeID()) + { + new_value = ::CFURLCopyFileSystemPath((CFURLRef)cf_type, kCFURLPOSIXPathStyle); + } + } + reset(new_value); + return get(); +} + +CFStringRef +CFCString::SetFileSystemRepresentationAndExpandTilde (const char *path) +{ + std::string expanded_path; + if (CFCString::ExpandTildeInPath(path, expanded_path)) + SetFileSystemRepresentation(expanded_path.c_str()); + else + reset(); + return get(); +} + +const char * +CFCString::UTF8(std::string& str) +{ + return CFCString::UTF8(get(), str); +} + +// Static function that puts a copy of the UTF8 contents of CF_STR into STR +// and returns the C string pointer that is contained in STR when successful, else +// NULL is returned. This allows the std::string parameter to own the extracted string, +// and also allows that string to be returned as a C string pointer that can be used. + +const char * +CFCString::UTF8 (CFStringRef cf_str, std::string& str) +{ + if (cf_str) + { + const CFStringEncoding encoding = kCFStringEncodingUTF8; + CFIndex max_utf8_str_len = CFStringGetLength (cf_str); + max_utf8_str_len = CFStringGetMaximumSizeForEncoding (max_utf8_str_len, encoding); + if (max_utf8_str_len > 0) + { + str.resize(max_utf8_str_len); + if (!str.empty()) + { + if (CFStringGetCString (cf_str, &str[0], str.size(), encoding)) + { + str.resize(strlen(str.c_str())); + return str.c_str(); + } + } + } + } + return NULL; +} + +const char* +CFCString::ExpandTildeInPath(const char* path, std::string &expanded_path) +{ + glob_t globbuf; + if (::glob (path, GLOB_TILDE, NULL, &globbuf) == 0) + { + expanded_path = globbuf.gl_pathv[0]; + ::globfree (&globbuf); + } + else + expanded_path.clear(); + + return expanded_path.c_str(); +} + +// Static function that puts a copy of the file system representation of CF_STR +// into STR and returns the C string pointer that is contained in STR when +// successful, else NULL is returned. This allows the std::string parameter +// to own the extracted string, and also allows that string to be returned as +// a C string pointer that can be used. + +const char * +CFCString::FileSystemRepresentation (CFStringRef cf_str, std::string& str) +{ + if (cf_str) + { + CFIndex max_length = ::CFStringGetMaximumSizeOfFileSystemRepresentation (cf_str); + if (max_length > 0) + { + str.resize(max_length); + if (!str.empty()) + { + if (::CFStringGetFileSystemRepresentation (cf_str, &str[0], str.size())) + { + str.erase(::strlen(str.c_str())); + return str.c_str(); + } + } + } + } + str.erase(); + return NULL; +} + + +CFIndex +CFCString::GetLength() const +{ + CFStringRef str = get(); + if (str) + return CFStringGetLength (str); + return 0; +} diff --git a/source/Host/macosx/cfcpp/CFCString.h b/source/Host/macosx/cfcpp/CFCString.h new file mode 100644 index 000000000000..27c090313ece --- /dev/null +++ b/source/Host/macosx/cfcpp/CFCString.h @@ -0,0 +1,41 @@ +//===-- CFCString.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef CoreFoundationCPP_CFString_h_ +#define CoreFoundationCPP_CFString_h_ + +#include <iosfwd> + +#include "CFCReleaser.h" + +class CFCString : public CFCReleaser<CFStringRef> +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + CFCString (CFStringRef cf_str = NULL); + CFCString (const char *s, CFStringEncoding encoding = kCFStringEncodingUTF8); + CFCString (const CFCString& rhs); + CFCString& operator= (const CFCString& rhs); + virtual ~CFCString (); + + const char * GetFileSystemRepresentation (std::string& str); + CFStringRef SetFileSystemRepresentation (const char *path); + CFStringRef SetFileSystemRepresentationFromCFType (CFTypeRef cf_type); + CFStringRef SetFileSystemRepresentationAndExpandTilde (const char *path); + const char * UTF8 (std::string& str); + CFIndex GetLength() const; + static const char *UTF8 (CFStringRef cf_str, std::string& str); + static const char *FileSystemRepresentation (CFStringRef cf_str, std::string& str); + static const char *ExpandTildeInPath(const char* path, std::string &expanded_path); + +}; + +#endif // #ifndef CoreFoundationCPP_CFString_h_ diff --git a/source/Host/macosx/cfcpp/CoreFoundationCPP.h b/source/Host/macosx/cfcpp/CoreFoundationCPP.h new file mode 100644 index 000000000000..6843e2649cda --- /dev/null +++ b/source/Host/macosx/cfcpp/CoreFoundationCPP.h @@ -0,0 +1,30 @@ +//===-- CoreFoundationCPP.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +//---------------------------------------------------------------------- +// +// CoreFoundationCPP.h +// CoreFoundationCPP +// +// Created by Greg Clayton on 4/23/09. +// +// +//---------------------------------------------------------------------- + +#ifndef CoreFoundationCPP_CoreFoundationCPP_H_ +#define CoreFoundationCPP_CoreFoundationCPP_H_ + +#include <CoreFoundationCPP/CFCBundle.h> +#include <CoreFoundationCPP/CFCData.h> +#include <CoreFoundationCPP/CFCReleaser.h> +#include <CoreFoundationCPP/CFCMutableArray.h> +#include <CoreFoundationCPP/CFCMutableDictionary.h> +#include <CoreFoundationCPP/CFCMutableSet.h> +#include <CoreFoundationCPP/CFCString.h> + +#endif // CoreFoundationCPP_CoreFoundationCPP_H_ diff --git a/source/Host/netbsd/Makefile b/source/Host/netbsd/Makefile new file mode 100644 index 000000000000..2502cc49c15b --- /dev/null +++ b/source/Host/netbsd/Makefile @@ -0,0 +1,14 @@ +##===- source/Host/netbsd/Makefile --------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +LLDB_LEVEL := ../../.. +LIBRARYNAME := lldbHostNetBSD +BUILD_ARCHIVE = 1 + +include $(LLDB_LEVEL)/Makefile diff --git a/source/Host/windows/Condition.cpp b/source/Host/windows/Condition.cpp new file mode 100644 index 000000000000..2f16ad77d7a3 --- /dev/null +++ b/source/Host/windows/Condition.cpp @@ -0,0 +1,98 @@ +//===-- Condition.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <errno.h> + +#include "lldb/Host/Condition.h" +#include "lldb/Host/TimeValue.h" +#include "lldb/Host/windows/windows.h" + + +using namespace lldb_private; + +//---------------------------------------------------------------------- +// Default constructor +// +// The default constructor will initialize a new pthread condition +// and maintain the condition in the object state. +//---------------------------------------------------------------------- +Condition::Condition () : + m_condition() +{ + m_condition = static_cast<PCONDITION_VARIABLE>(malloc(sizeof(CONDITION_VARIABLE))); + InitializeConditionVariable(static_cast<PCONDITION_VARIABLE>(m_condition)); +} + +//---------------------------------------------------------------------- +// Destructor +// +// Destroys the pthread condition that the object owns. +//---------------------------------------------------------------------- +Condition::~Condition () +{ + free(m_condition); +} + +//---------------------------------------------------------------------- +// Unblock all threads waiting for a condition variable +//---------------------------------------------------------------------- +int +Condition::Broadcast () +{ + WakeAllConditionVariable(static_cast<PCONDITION_VARIABLE>(m_condition)); + return 0; +} + +//---------------------------------------------------------------------- +// Unblocks one thread waiting for the condition variable +//---------------------------------------------------------------------- +int +Condition::Signal () +{ + WakeConditionVariable(static_cast<PCONDITION_VARIABLE>(m_condition)); + return 0; +} + +//---------------------------------------------------------------------- +// The Wait() function atomically blocks the current thread +// waiting on the owned condition variable, and unblocks the mutex +// specified by "mutex". The waiting thread unblocks only after +// another thread calls Signal(), or Broadcast() with the same +// condition variable, or if "abstime" is valid (non-NULL) this +// function will return when the system time reaches the time +// specified in "abstime". If "abstime" is NULL this function will +// wait for an infinite amount of time for the condition variable +// to be signaled or broadcasted. +// +// The current thread re-acquires the lock on "mutex". +//---------------------------------------------------------------------- +int +Condition::Wait (Mutex &mutex, const TimeValue *abstime, bool *timed_out) +{ + DWORD wait = INFINITE; + if (abstime != NULL) { + int wval = (*abstime - TimeValue::Now()) / 1000000; + if (wval < 0) wval = 0; + + wait = wval; + } + + int err = SleepConditionVariableCS(static_cast<PCONDITION_VARIABLE>(m_condition), static_cast<PCRITICAL_SECTION>(mutex.m_mutex), wait); + + if (timed_out != NULL) + { + if ((err == 0) && GetLastError() == ERROR_TIMEOUT) + *timed_out = true; + else + *timed_out = false; + } + + return err == 0; +} + diff --git a/source/Host/windows/ConnectionGenericFileWindows.cpp b/source/Host/windows/ConnectionGenericFileWindows.cpp new file mode 100644 index 000000000000..eebf3d4f633c --- /dev/null +++ b/source/Host/windows/ConnectionGenericFileWindows.cpp @@ -0,0 +1,354 @@ +//===-- ConnectionGenericFileWindows.cpp ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Host/TimeValue.h" +#include "lldb/Host/windows/ConnectionGenericFileWindows.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" + +using namespace lldb; +using namespace lldb_private; + +namespace +{ +// This is a simple helper class to package up the information needed to return from a Read/Write +// operation function. Since there is a lot of code to be run before exit regardless of whether the +// operation succeeded or failed, combined with many possible return paths, this is the cleanest +// way to represent it. +class ReturnInfo +{ + public: + void + Set(size_t bytes, ConnectionStatus status, DWORD error_code) + { + m_error.SetError(error_code, eErrorTypeWin32); + m_bytes = bytes; + m_status = status; + } + + void + Set(size_t bytes, ConnectionStatus status, llvm::StringRef error_msg) + { + m_error.SetErrorString(error_msg.data()); + m_bytes = bytes; + m_status = status; + } + + size_t + GetBytes() const + { + return m_bytes; + } + ConnectionStatus + GetStatus() const + { + return m_status; + } + const Error & + GetError() const + { + return m_error; + } + + private: + Error m_error; + size_t m_bytes; + ConnectionStatus m_status; +}; +} + +ConnectionGenericFile::ConnectionGenericFile() + : m_file(INVALID_HANDLE_VALUE) + , m_owns_file(false) +{ + ::ZeroMemory(&m_overlapped, sizeof(m_overlapped)); + ::ZeroMemory(&m_file_position, sizeof(m_file_position)); + InitializeEventHandles(); +} + +ConnectionGenericFile::ConnectionGenericFile(lldb::file_t file, bool owns_file) + : m_file(file) + , m_owns_file(owns_file) +{ + ::ZeroMemory(&m_overlapped, sizeof(m_overlapped)); + ::ZeroMemory(&m_file_position, sizeof(m_file_position)); + InitializeEventHandles(); +} + +ConnectionGenericFile::~ConnectionGenericFile() +{ + if (m_owns_file && IsConnected()) + ::CloseHandle(m_file); + + ::CloseHandle(m_event_handles[kBytesAvailableEvent]); + ::CloseHandle(m_event_handles[kInterruptEvent]); +} + +void +ConnectionGenericFile::InitializeEventHandles() +{ + m_event_handles[kInterruptEvent] = CreateEvent(NULL, FALSE, FALSE, NULL); + + // Note, we should use a manual reset event for the hEvent argument of the OVERLAPPED. This + // is because both WaitForMultipleObjects and GetOverlappedResult (if you set the bWait + // argument to TRUE) will wait for the event to be signalled. If we use an auto-reset event, + // WaitForMultipleObjects will reset the event, return successfully, and then + // GetOverlappedResult will block since the event is no longer signalled. + m_event_handles[kBytesAvailableEvent] = ::CreateEvent(NULL, TRUE, FALSE, NULL); +} + +bool +ConnectionGenericFile::IsConnected() const +{ + return m_file && (m_file != INVALID_HANDLE_VALUE); +} + +lldb::ConnectionStatus +ConnectionGenericFile::Connect(const char *s, Error *error_ptr) +{ + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + log->Printf("%p ConnectionGenericFile::Connect (url = '%s')", static_cast<void *>(this), s); + + if (strstr(s, "file://") != s) + { + if (error_ptr) + error_ptr->SetErrorStringWithFormat("unsupported connection URL: '%s'", s); + return eConnectionStatusError; + } + + if (IsConnected()) + { + ConnectionStatus status = Disconnect(error_ptr); + if (status != eConnectionStatusSuccess) + return status; + } + + // file://PATH + const char *path = s + strlen("file://"); + // Open the file for overlapped access. If it does not exist, create it. We open it overlapped + // so that we can issue asynchronous reads and then use WaitForMultipleObjects to allow the read + // to be interrupted by an event object. + m_file = ::CreateFile(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); + if (m_file == INVALID_HANDLE_VALUE) + { + if (error_ptr) + error_ptr->SetError(::GetLastError(), eErrorTypeWin32); + return eConnectionStatusError; + } + + m_owns_file = true; + m_uri.assign(s); + return eConnectionStatusSuccess; +} + +lldb::ConnectionStatus +ConnectionGenericFile::Disconnect(Error *error_ptr) +{ + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + log->Printf("%p ConnectionGenericFile::Disconnect ()", static_cast<void *>(this)); + + if (!IsConnected()) + return eConnectionStatusSuccess; + + // Reset the handle so that after we unblock any pending reads, subsequent calls to Read() will + // see a disconnected state. + HANDLE old_file = m_file; + m_file = INVALID_HANDLE_VALUE; + + // Set the disconnect event so that any blocking reads unblock, then cancel any pending IO operations. + ::CancelIoEx(old_file, &m_overlapped); + + // Close the file handle if we owned it, but don't close the event handles. We could always + // reconnect with the same Connection instance. + if (m_owns_file) + ::CloseHandle(old_file); + + ::ZeroMemory(&m_file_position, sizeof(m_file_position)); + m_owns_file = false; + m_uri.clear(); + return eConnectionStatusSuccess; +} + +size_t +ConnectionGenericFile::Read(void *dst, size_t dst_len, uint32_t timeout_usec, lldb::ConnectionStatus &status, Error *error_ptr) +{ + ReturnInfo return_info; + BOOL result = 0; + DWORD bytes_read = 0; + + if (error_ptr) + error_ptr->Clear(); + + if (!IsConnected()) + { + return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE); + goto finish; + } + + m_overlapped.hEvent = m_event_handles[kBytesAvailableEvent]; + + result = ::ReadFile(m_file, dst, dst_len, NULL, &m_overlapped); + if (result || ::GetLastError() == ERROR_IO_PENDING) + { + if (!result) + { + // The expected return path. The operation is pending. Wait for the operation to complete + // or be interrupted. + TimeValue time_value; + time_value.OffsetWithMicroSeconds(timeout_usec); + DWORD milliseconds = time_value.milliseconds(); + DWORD wait_result = ::WaitForMultipleObjects(llvm::array_lengthof(m_event_handles), m_event_handles, FALSE, milliseconds); + // All of the events are manual reset events, so make sure we reset them to non-signalled. + switch (wait_result) + { + case WAIT_OBJECT_0 + kBytesAvailableEvent: + break; + case WAIT_OBJECT_0 + kInterruptEvent: + return_info.Set(0, eConnectionStatusInterrupted, 0); + goto finish; + case WAIT_TIMEOUT: + return_info.Set(0, eConnectionStatusTimedOut, 0); + goto finish; + case WAIT_FAILED: + return_info.Set(0, eConnectionStatusError, ::GetLastError()); + goto finish; + } + } + // The data is ready. Figure out how much was read and return; + if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_read, FALSE)) + { + DWORD result_error = ::GetLastError(); + // ERROR_OPERATION_ABORTED occurs when someone calls Disconnect() during a blocking read. + // This triggers a call to CancelIoEx, which causes the operation to complete and the + // result to be ERROR_OPERATION_ABORTED. + if (result_error == ERROR_HANDLE_EOF || result_error == ERROR_OPERATION_ABORTED || result_error == ERROR_BROKEN_PIPE) + return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0); + else + return_info.Set(bytes_read, eConnectionStatusError, result_error); + } + else if (bytes_read == 0) + return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0); + else + return_info.Set(bytes_read, eConnectionStatusSuccess, 0); + + goto finish; + } + else if (::GetLastError() == ERROR_BROKEN_PIPE) + { + // The write end of a pipe was closed. This is equivalent to EOF. + return_info.Set(0, eConnectionStatusEndOfFile, 0); + } + else + { + // An unknown error occurred. Fail out. + return_info.Set(0, eConnectionStatusError, ::GetLastError()); + } + goto finish; + +finish: + status = return_info.GetStatus(); + if (error_ptr) + *error_ptr = return_info.GetError(); + + // kBytesAvailableEvent is a manual reset event. Make sure it gets reset here so that any + // subsequent operations don't immediately see bytes available. + ResetEvent(m_event_handles[kBytesAvailableEvent]); + + IncrementFilePointer(return_info.GetBytes()); + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + { + log->Printf("%" PRIxPTR " ConnectionGenericFile::Read() handle = %" PRIxPTR ", dst = %" PRIxPTR ", dst_len = %" PRIu64 + ") => %" PRIu64 ", error = %s", + this, m_file, dst, static_cast<uint64_t>(dst_len), static_cast<uint64_t>(return_info.GetBytes()), + return_info.GetError().AsCString()); + } + + return return_info.GetBytes(); +} + +size_t +ConnectionGenericFile::Write(const void *src, size_t src_len, lldb::ConnectionStatus &status, Error *error_ptr) +{ + ReturnInfo return_info; + DWORD bytes_written = 0; + BOOL result = 0; + + if (error_ptr) + error_ptr->Clear(); + + if (!IsConnected()) + { + return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE); + goto finish; + } + + m_overlapped.hEvent = NULL; + + // Writes are not interruptible like reads are, so just block until it's done. + result = ::WriteFile(m_file, src, src_len, NULL, &m_overlapped); + if (!result && ::GetLastError() != ERROR_IO_PENDING) + { + return_info.Set(0, eConnectionStatusError, ::GetLastError()); + goto finish; + } + + if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_written, TRUE)) + { + return_info.Set(bytes_written, eConnectionStatusError, ::GetLastError()); + goto finish; + } + + return_info.Set(bytes_written, eConnectionStatusSuccess, 0); + goto finish; + +finish: + status = return_info.GetStatus(); + if (error_ptr) + *error_ptr = return_info.GetError(); + + IncrementFilePointer(return_info.GetBytes()); + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + { + log->Printf("%" PRIxPTR " ConnectionGenericFile::Write() handle = %" PRIxPTR ", src = %" PRIxPTR ", src_len = %" PRIu64 + ") => %" PRIu64 ", error = %s", + this, m_file, src, static_cast<uint64_t>(src_len), static_cast<uint64_t>(return_info.GetBytes()), + return_info.GetError().AsCString()); + } + return return_info.GetBytes(); +} + +std::string +ConnectionGenericFile::GetURI() +{ + return m_uri; +} + +bool +ConnectionGenericFile::InterruptRead() +{ + return ::SetEvent(m_event_handles[kInterruptEvent]); +} + +void +ConnectionGenericFile::IncrementFilePointer(DWORD amount) +{ + LARGE_INTEGER old_pos; + old_pos.HighPart = m_overlapped.OffsetHigh; + old_pos.LowPart = m_overlapped.Offset; + old_pos.QuadPart += amount; + m_overlapped.Offset = old_pos.LowPart; + m_overlapped.OffsetHigh = old_pos.HighPart; +} diff --git a/source/Host/windows/EditLineWin.cpp b/source/Host/windows/EditLineWin.cpp new file mode 100644 index 000000000000..55fe52dc8ccd --- /dev/null +++ b/source/Host/windows/EditLineWin.cpp @@ -0,0 +1,435 @@ +//===-- EditLineWin.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// this file is only relevant for Visual C++ +#if defined( _WIN32 ) + +#include "lldb/Host/windows/windows.h" + +#include "lldb/Host/windows/editlinewin.h" +#include <vector> +#include <assert.h> + +// edit line EL_ADDFN function pointer type +typedef unsigned char(*el_addfn_func)(EditLine *e, int ch); +typedef const char* (*el_prompt_func)(EditLine *); + +// edit line wrapper binding container +struct el_binding +{ + // + const char *name; + const char *help; + // function pointer to callback routine + el_addfn_func func; + // ascii key this function is bound to + const char *key; +}; + +// stored key bindings +static std::vector<el_binding*> _bindings; + +//TODO: this should in fact be related to the exact edit line context we create +static void *clientData = NULL; + +// store the current prompt string +// default to what we expect to receive anyway +static const char *_prompt = "(lldb) "; + +#if !defined( _WIP_INPUT_METHOD ) + +static char * +el_get_s (char *buffer, int chars) +{ + return gets_s(buffer, chars); +} +#else + +static void +con_output (char _in) +{ + HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD written = 0; + // get the cursor position + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo( hout, &info ); + // output this char + WriteConsoleOutputCharacterA( hout, &_in, 1, info.dwCursorPosition, &written ); + // advance cursor position + info.dwCursorPosition.X++; + SetConsoleCursorPosition( hout, info.dwCursorPosition ); +} + +static void +con_backspace (void) +{ + HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD written = 0; + // get cursor position + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo( hout, &info ); + // nudge cursor backwards + info.dwCursorPosition.X--; + SetConsoleCursorPosition( hout, info.dwCursorPosition ); + // blank out the last character + WriteConsoleOutputCharacterA( hout, " ", 1, info.dwCursorPosition, &written ); +} + +static void +con_return (void) +{ + HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD written = 0; + // get cursor position + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo( hout, &info ); + // move onto the new line + info.dwCursorPosition.X = 0; + info.dwCursorPosition.Y++; + SetConsoleCursorPosition( hout, info.dwCursorPosition ); +} + +static bool +runBind (char _key) +{ + for ( int i=0; i<_bindings.size(); i++ ) + { + el_binding *bind = _bindings[i]; + if ( bind->key[0] == _key ) + { + bind->func( (EditLine*) -1, _key ); + return true; + } + } + return false; +} + +// replacement get_s which is EL_BIND aware +static char * +el_get_s (char *buffer, int chars) +{ + // + char *head = buffer; + // + for ( ;; Sleep( 10 ) ) + { + // + INPUT_RECORD _record; + // + DWORD _read = 0; + if ( ReadConsoleInputA( GetStdHandle( STD_INPUT_HANDLE ), &_record, 1, &_read ) == FALSE ) + break; + // if we didn't read a key + if ( _read == 0 ) + continue; + // only interested in key events + if ( _record.EventType != KEY_EVENT ) + continue; + // is the key down + if (! _record.Event.KeyEvent.bKeyDown ) + continue; + // read the ascii key character + char _key = _record.Event.KeyEvent.uChar.AsciiChar; + // non ascii conformant key press + if ( _key == 0 ) + { + // check the scan code + // if VK_UP scroll back through history + // if VK_DOWN scroll forward through history + continue; + } + // try to execute any bind this key may have + if ( runBind( _key ) ) + continue; + // if we read a return key + if ( _key == '\n' || _key == '\r' ) + { + con_return( ); + break; + } + // key is backspace + if ( _key == 0x8 ) + { + // avoid deleting past beginning + if ( head > buffer ) + { + con_backspace( ); + head--; + } + continue; + } + + // add this key to the input buffer + if ( (head-buffer) < (chars-1) ) + { + con_output( _key ); + *(head++) = _key; + } + } + // insert end of line character + *head = '\0'; + + return buffer; +} +#endif + +// edit line initialize +EditLine * +el_init (const char *, FILE *, FILE *, FILE *) +{ + // + SetConsoleTitleA( "lldb" ); + // return dummy handle + return (EditLine*) -1; +} + +const char * +el_gets (EditLine *el, int *length) +{ + // print the prompt if we have one + if ( _prompt != NULL ) + printf("%s", _prompt); + // create a buffer for the user input + char *buffer = new char[ MAX_PATH ]; + // try to get user input string + if ( el_get_s( buffer, MAX_PATH ) ) + { + // get the string length in 'length' + while ( buffer[ *length ] != '\0' ) + (*length)++; + // return the input buffer + // remember that this memory has the be free'd somewhere + return buffer; + } + else + { + // on error + delete [] buffer; + return NULL; + } +} + +int +el_set (EditLine *el, int code, ...) +{ + va_list vl; + va_start(vl, code); + // + switch ( code ) + { + // edit line set prompt message + case ( EL_PROMPT ): + { + // EL_PROMPT, char *(*f)( EditLine *) + // define a prompt printing function as 'f', which is to return a string that + // contains the prompt. + + // get the function pointer from the arg list + void *func_vp = (void*)va_arg(vl, el_prompt_func); + // cast to suitable prototype + el_prompt_func func_fp = (el_prompt_func)func_vp; + // call to get the prompt as a string + _prompt = func_fp( el ); + } + break; + + case (EL_PROMPT_ESC) : + { + // EL_PROMPT, char *(*f)( EditLine *) + // define a prompt printing function as 'f', which is to return a string that + // contains the prompt. + + // get the function pointer from the arg list + void *func_vp = (void*)va_arg(vl, el_prompt_func); + va_arg(vl, int); + // call to get the prompt as a string + el_prompt_func func_fp = (el_prompt_func)func_vp; + _prompt = func_fp(el); + } + break; + + case ( EL_EDITOR ): + { + // EL_EDITOR, const char *mode + // set editing mode to "emacs" or "vi" + } + break; + case ( EL_HIST ): + { + // EL_HIST, History *(*fun)(History *, int op, ... ), const char *ptr + // defines which history function to use, which is usually history(). Ptr should be the + // value returned by history_init(). + } + break; + case ( EL_ADDFN ): + { + // EL_ADDFN, const char *name, const char *help, unsigned char (*func)(EditLine *e, int ch) + // add a user defined function, func), referred to as 'name' which is invoked when a key which is bound to 'name' is + // entered. 'help' is a description of 'name'. at invocation time, 'ch' is the key which caused the invocation. the + // return value of 'func()' should be one of: + // CC_NORM add a normal character + // CC_NEWLINE end of line was entered + // CC_EOF EOF was entered + // CC_ARGHACK expecting further command input as arguments, do nothing visually. + // CC_REFRESH refresh display. + // CC_REFRESH_BEEP refresh display and beep. + // CC_CURSOR cursor moved so update and perform CC_REFRESH + // CC_REDISPLAY redisplay entire input line. this is useful if a key binding outputs extra information. + // CC_ERROR an error occurred. beep and flush tty. + // CC_FATAL fatal error, reset tty to known state. + + el_binding *binding = new el_binding; + binding->name = va_arg( vl, const char *); + binding->help = va_arg( vl, const char *); + binding->func = va_arg( vl, el_addfn_func ); + binding->key = 0; + // add this to the bindings list + _bindings.push_back( binding ); + } + break; + case ( EL_BIND ): + { + // EL_BIND, const char *, ..., NULL + // perform the BIND built-in command. Refer to editrc(5) for more information. + + const char *name = va_arg( vl, const char* ); + + for ( int i=0; i<_bindings.size(); i++ ) + { + el_binding *bind = _bindings[i]; + if ( strcmp( bind->name, name ) == 0 ) + { + bind->key = va_arg( vl, const char * ); + break; + } + } + + } + break; + case ( EL_CLIENTDATA ): + { + clientData = va_arg(vl, void*); + } + break; + } + return 0; +} + +void +el_end (EditLine *el) +{ + //assert( !"Not implemented!" ); +} + +void +el_reset (EditLine *) +{ + assert( !"Not implemented!" ); +} + +int +el_getc (EditLine *, char *) +{ + assert( !"Not implemented!" ); + return 0; +} + +void +el_push (EditLine *, const char *) +{ +} + +void +el_beep (EditLine *) +{ + Beep( 1000, 500 ); +} + +int +el_parse (EditLine *, int, const char **) +{ + assert( !"Not implemented!" ); + return 0; +} + +int +el_get (EditLine *el, int code, ...) +{ + va_list vl; + va_start( vl, code ); + + switch ( code ) + { + case ( EL_CLIENTDATA ): + { + void **dout = va_arg( vl, void** ); + *dout = clientData; + } + break; + default: + assert( !"Not implemented!" ); + } + return 0; +} + +int +el_source (EditLine *el, const char *file) +{ + // init edit line by reading the contents of 'file' + // nothing to do here on windows... + return 0; +} + +void +el_resize (EditLine *) +{ + assert( !"Not implemented!" ); +} + +const LineInfo * +el_line (EditLine *el) +{ + return 0; +} + +int +el_insertstr (EditLine *, const char *) +{ +// assert( !"Not implemented!" ); + return 0; +} + +void +el_deletestr (EditLine *, int) +{ + assert( !"Not implemented!" ); +} + +History * +history_init (void) +{ + // return dummy handle + return (History*) -1; +} + +void +history_end (History *) +{ +// assert( !"Not implemented!" ); +} + +int +history (History *, HistEvent *, int op, ...) +{ + // perform operation 'op' on the history list with + // optional arguments as needed by the operation. + return 0; +} + +#endif diff --git a/source/Host/windows/FileSystem.cpp b/source/Host/windows/FileSystem.cpp new file mode 100644 index 000000000000..2cca12b92711 --- /dev/null +++ b/source/Host/windows/FileSystem.cpp @@ -0,0 +1,221 @@ +//===-- FileSystem.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/windows/windows.h" + +#include <shellapi.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/windows/AutoHandle.h" +#include "llvm/Support/FileSystem.h" + +using namespace lldb_private; + +const char * +FileSystem::DEV_NULL = "nul"; + +FileSpec::PathSyntax +FileSystem::GetNativePathSyntax() +{ + return FileSpec::ePathSyntaxWindows; +} + +Error +FileSystem::MakeDirectory(const FileSpec &file_spec, uint32_t file_permissions) +{ + // On Win32, the mode parameter is ignored, as Windows files and directories support a + // different permission model than POSIX. + Error error; + const auto err_code = llvm::sys::fs::create_directories(file_spec.GetPath(), true); + if (err_code) + { + error.SetErrorString(err_code.message().c_str()); + } + + return error; +} + +Error +FileSystem::DeleteDirectory(const FileSpec &file_spec, bool recurse) +{ + Error error; + if (!recurse) + { + BOOL result = ::RemoveDirectory(file_spec.GetCString()); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + } + else + { + // SHFileOperation() accepts a list of paths, and so must be double-null-terminated to + // indicate the end of the list. + std::string path_buffer{file_spec.GetPath()}; + path_buffer.push_back(0); + + SHFILEOPSTRUCT shfos = {0}; + shfos.wFunc = FO_DELETE; + shfos.pFrom = path_buffer.c_str(); + shfos.fFlags = FOF_NO_UI; + + int result = ::SHFileOperation(&shfos); + // TODO(zturner): Correctly handle the intricacies of SHFileOperation return values. + if (result != 0) + error.SetErrorStringWithFormat("SHFileOperation failed"); + } + return error; +} + +Error +FileSystem::GetFilePermissions(const FileSpec &file_spec, uint32_t &file_permissions) +{ + Error error; + // Beware that Windows's permission model is different from Unix's, and it's + // not clear if this API is supposed to check ACLs. To match the caller's + // expectations as closely as possible, we'll use Microsoft's _stat, which + // attempts to emulate POSIX stat. This should be good enough for basic + // checks like FileSpec::Readable. + struct _stat file_stats; + if (::_stat(file_spec.GetCString(), &file_stats) == 0) + { + // The owner permission bits in "st_mode" currently match the definitions + // for the owner file mode bits. + file_permissions = file_stats.st_mode & (_S_IREAD | _S_IWRITE | _S_IEXEC); + } + else + { + error.SetErrorToErrno(); + } + + return error; +} + +Error +FileSystem::SetFilePermissions(const FileSpec &file_spec, uint32_t file_permissions) +{ + Error error; + error.SetErrorStringWithFormat("%s is not supported on this host", __PRETTY_FUNCTION__); + return error; +} + +lldb::user_id_t +FileSystem::GetFileSize(const FileSpec &file_spec) +{ + return file_spec.GetByteSize(); +} + +bool +FileSystem::GetFileExists(const FileSpec &file_spec) +{ + return file_spec.Exists(); +} + +Error +FileSystem::Hardlink(const FileSpec &src, const FileSpec &dst) +{ + Error error; + if (!::CreateHardLink(src.GetCString(), dst.GetCString(), nullptr)) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +int +FileSystem::GetHardlinkCount(const FileSpec &file_spec) +{ + HANDLE file_handle = ::CreateFile(file_spec.GetCString(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (file_handle == INVALID_HANDLE_VALUE) + return -1; + + AutoHandle auto_file_handle(file_handle); + BY_HANDLE_FILE_INFORMATION file_info; + if (::GetFileInformationByHandle(file_handle, &file_info)) + return file_info.nNumberOfLinks; + + return -1; +} + +Error +FileSystem::Symlink(const FileSpec &src, const FileSpec &dst) +{ + Error error; + DWORD attrib = ::GetFileAttributes(dst.GetCString()); + if (attrib == INVALID_FILE_ATTRIBUTES) + { + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; + } + bool is_directory = !!(attrib & FILE_ATTRIBUTE_DIRECTORY); + DWORD flag = is_directory ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; + BOOL result = ::CreateSymbolicLink(src.GetCString(), dst.GetCString(), flag); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +Error +FileSystem::Unlink(const FileSpec &file_spec) +{ + Error error; + BOOL result = ::DeleteFile(file_spec.GetCString()); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +Error +FileSystem::Readlink(const FileSpec &src, FileSpec &dst) +{ + Error error; + HANDLE h = ::CreateFile(src.GetCString(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (h == INVALID_HANDLE_VALUE) + { + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; + } + + char buf[PATH_MAX]; + // Subtract 1 from the path length since this function does not add a null terminator. + DWORD result = ::GetFinalPathNameByHandle(h, buf, sizeof(buf) - 1, + FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (result == 0) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + else + dst.SetFile(buf, false); + + ::CloseHandle(h); + return error; +} + +Error +FileSystem::ResolveSymbolicLink(const FileSpec &src, FileSpec &dst) +{ + return Error("ResolveSymbolicLink() isn't implemented on Windows"); +} + +bool +FileSystem::IsLocal(const FileSpec &spec) +{ + if (spec) + { + // TODO: return true if the file is on a locally mounted file system + return true; + } + + return false; +} diff --git a/source/Host/windows/Host.cpp b/source/Host/windows/Host.cpp new file mode 100644 index 000000000000..2c9a139df256 --- /dev/null +++ b/source/Host/windows/Host.cpp @@ -0,0 +1,326 @@ +//===-- source/Host/windows/Host.cpp ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +#include <stdio.h> +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/AutoHandle.h" + +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Target/Process.h" + +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/StructuredData.h" + +// Windows includes +#include <TlHelp32.h> + +using namespace lldb; +using namespace lldb_private; + +namespace +{ + bool GetTripleForProcess(const FileSpec &executable, llvm::Triple &triple) + { + // Open the PE File as a binary file, and parse just enough information to determine the + // machine type. + File imageBinary( + executable.GetPath().c_str(), + File::eOpenOptionRead, + lldb::eFilePermissionsUserRead); + imageBinary.SeekFromStart(0x3c); + int32_t peOffset = 0; + uint32_t peHead = 0; + uint16_t machineType = 0; + size_t readSize = sizeof(peOffset); + imageBinary.Read(&peOffset, readSize); + imageBinary.SeekFromStart(peOffset); + imageBinary.Read(&peHead, readSize); + if (peHead != 0x00004550) // "PE\0\0", little-endian + return false; // Error: Can't find PE header + readSize = 2; + imageBinary.Read(&machineType, readSize); + triple.setVendor(llvm::Triple::PC); + triple.setOS(llvm::Triple::Win32); + triple.setArch(llvm::Triple::UnknownArch); + if (machineType == 0x8664) + triple.setArch(llvm::Triple::x86_64); + else if (machineType == 0x14c) + triple.setArch(llvm::Triple::x86); + + return true; + } + + bool GetExecutableForProcess(const AutoHandle &handle, std::string &path) + { + // Get the process image path. MAX_PATH isn't long enough, paths can actually be up to 32KB. + std::vector<char> buffer(32768); + DWORD dwSize = buffer.size(); + if (!::QueryFullProcessImageNameA(handle.get(), 0, &buffer[0], &dwSize)) + return false; + path.assign(&buffer[0]); + return true; + } + + void GetProcessExecutableAndTriple(const AutoHandle &handle, ProcessInstanceInfo &process) + { + // We may not have permissions to read the path from the process. So start off by + // setting the executable file to whatever Toolhelp32 gives us, and then try to + // enhance this with more detailed information, but fail gracefully. + std::string executable; + llvm::Triple triple; + triple.setVendor(llvm::Triple::PC); + triple.setOS(llvm::Triple::Win32); + triple.setArch(llvm::Triple::UnknownArch); + if (GetExecutableForProcess(handle, executable)) + { + FileSpec executableFile(executable.c_str(), false); + process.SetExecutableFile(executableFile, true); + GetTripleForProcess(executableFile, triple); + } + process.SetArchitecture(ArchSpec(triple)); + + // TODO(zturner): Add the ability to get the process user name. + } +} + +lldb::DataBufferSP +Host::GetAuxvData(lldb_private::Process *process) +{ + return 0; +} + +lldb::tid_t +Host::GetCurrentThreadID() +{ + return lldb::tid_t(::GetCurrentThreadId()); +} + +lldb::thread_t +Host::GetCurrentThread () +{ + return lldb::thread_t(::GetCurrentThread()); +} + +lldb::thread_key_t +Host::ThreadLocalStorageCreate(ThreadLocalStorageCleanupCallback callback) +{ + return TlsAlloc(); +} + +void* +Host::ThreadLocalStorageGet(lldb::thread_key_t key) +{ + return ::TlsGetValue (key); +} + +void +Host::ThreadLocalStorageSet(lldb::thread_key_t key, void *value) +{ + ::TlsSetValue (key, value); +} + +void +Host::Kill(lldb::pid_t pid, int signo) +{ + TerminateProcess((HANDLE) pid, 1); +} + + +const char * +Host::GetSignalAsCString(int signo) +{ + return NULL; +} + +FileSpec +Host::GetModuleFileSpecForHostAddress (const void *host_addr) +{ + FileSpec module_filespec; + + HMODULE hmodule = NULL; + if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)host_addr, &hmodule)) + return module_filespec; + + std::vector<char> buffer(MAX_PATH); + DWORD chars_copied = 0; + do { + chars_copied = ::GetModuleFileName(hmodule, &buffer[0], buffer.size()); + if (chars_copied == buffer.size() && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) + buffer.resize(buffer.size() * 2); + } while (chars_copied >= buffer.size()); + + module_filespec.SetFile(&buffer[0], false); + return module_filespec; +} + +uint32_t +Host::FindProcesses (const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &process_infos) +{ + process_infos.Clear(); + + AutoHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + if (!snapshot.IsValid()) + return 0; + + PROCESSENTRY32 pe = {0}; + pe.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(snapshot.get(), &pe)) + { + do + { + AutoHandle handle(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID), nullptr); + + ProcessInstanceInfo process; + process.SetExecutableFile(FileSpec(pe.szExeFile, false), true); + process.SetProcessID(pe.th32ProcessID); + process.SetParentProcessID(pe.th32ParentProcessID); + GetProcessExecutableAndTriple(handle, process); + + if (match_info.MatchAllProcesses() || match_info.Matches(process)) + process_infos.Append(process); + } while (Process32Next(snapshot.get(), &pe)); + } + return process_infos.GetSize(); +} + +bool +Host::GetProcessInfo (lldb::pid_t pid, ProcessInstanceInfo &process_info) +{ + process_info.Clear(); + + AutoHandle handle(::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid), + nullptr); + if (!handle.IsValid()) + return false; + + process_info.SetProcessID(pid); + GetProcessExecutableAndTriple(handle, process_info); + + // Need to read the PEB to get parent process and command line arguments. + return true; +} + +HostThread +Host::StartMonitoringChildProcess(Host::MonitorChildProcessCallback callback, void *callback_baton, lldb::pid_t pid, bool monitor_signals) +{ + return HostThread(); +} + +Error +Host::ShellExpandArguments (ProcessLaunchInfo &launch_info) +{ + Error error; + if (launch_info.GetFlags().Test(eLaunchFlagShellExpandArguments)) + { + FileSpec expand_tool_spec; + if (!HostInfo::GetLLDBPath(lldb::ePathTypeSupportExecutableDir, expand_tool_spec)) + { + error.SetErrorString("could not find support executable directory for the lldb-argdumper tool"); + return error; + } + expand_tool_spec.AppendPathComponent("lldb-argdumper.exe"); + if (!expand_tool_spec.Exists()) + { + error.SetErrorString("could not find the lldb-argdumper tool"); + return error; + } + + std::string quoted_cmd_string; + launch_info.GetArguments().GetQuotedCommandString(quoted_cmd_string); + std::replace(quoted_cmd_string.begin(), quoted_cmd_string.end(), '\\', '/'); + StreamString expand_command; + + expand_command.Printf("\"%s\" %s", + expand_tool_spec.GetPath().c_str(), + quoted_cmd_string.c_str()); + + int status; + std::string output; + RunShellCommand(expand_command.GetData(), launch_info.GetWorkingDirectory(), &status, nullptr, &output, 10); + + if (status != 0) + { + error.SetErrorStringWithFormat("lldb-argdumper exited with error %d", status); + return error; + } + + auto data_sp = StructuredData::ParseJSON(output); + if (!data_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto dict_sp = data_sp->GetAsDictionary(); + if (!data_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto args_sp = dict_sp->GetObjectForDotSeparatedPath("arguments"); + if (!args_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto args_array_sp = args_sp->GetAsArray(); + if (!args_array_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + launch_info.GetArguments().Clear(); + + for (size_t i = 0; + i < args_array_sp->GetSize(); + i++) + { + auto item_sp = args_array_sp->GetItemAtIndex(i); + if (!item_sp) + continue; + auto str_sp = item_sp->GetAsString(); + if (!str_sp) + continue; + + launch_info.GetArguments().AppendArgument(str_sp->GetValue().c_str()); + } + } + + return error; +} + +size_t +Host::GetEnvironment(StringList &env) +{ + // The environment block on Windows is a contiguous buffer of NULL terminated strings, + // where the end of the environment block is indicated by two consecutive NULLs. + LPCH environment_block = ::GetEnvironmentStrings(); + env.Clear(); + while (*environment_block != '\0') + { + llvm::StringRef current_var(environment_block); + if (current_var[0] != '=') + env.AppendString(current_var); + + environment_block += current_var.size()+1; + } + return env.GetSize(); +} diff --git a/source/Host/windows/HostInfoWindows.cpp b/source/Host/windows/HostInfoWindows.cpp new file mode 100644 index 000000000000..6dce71d9172a --- /dev/null +++ b/source/Host/windows/HostInfoWindows.cpp @@ -0,0 +1,118 @@ +//===-- HostInfoWindows.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/windows/windows.h" + +#include <mutex> // std::once + +#include "lldb/Host/windows/HostInfoWindows.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Path.h" + +using namespace lldb_private; + +FileSpec HostInfoWindows::m_program_filespec; + +size_t +HostInfoWindows::GetPageSize() +{ + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + return systemInfo.dwPageSize; +} + +bool +HostInfoWindows::GetOSVersion(uint32_t &major, uint32_t &minor, uint32_t &update) +{ + OSVERSIONINFOEX info; + + ZeroMemory(&info, sizeof(OSVERSIONINFOEX)); + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); +#pragma warning(push) +#pragma warning(disable : 4996) + // Starting with Microsoft SDK for Windows 8.1, this function is deprecated in favor of the + // new Windows Version Helper APIs. Since we don't specify a minimum SDK version, it's easier + // to simply disable the warning rather than try to support both APIs. + if (GetVersionEx((LPOSVERSIONINFO)&info) == 0) + { + return false; + } +#pragma warning(pop) + + major = info.dwMajorVersion; + minor = info.dwMinorVersion; + update = info.wServicePackMajor; + + return true; +} + +bool +HostInfoWindows::GetOSBuildString(std::string &s) +{ + s.clear(); + uint32_t major, minor, update; + if (!GetOSVersion(major, minor, update)) + return false; + + llvm::raw_string_ostream stream(s); + stream << "Windows NT " << major << "." << minor << "." << update; + return true; +} + +bool +HostInfoWindows::GetOSKernelDescription(std::string &s) +{ + return GetOSBuildString(s); +} + +bool +HostInfoWindows::GetHostname(std::string &s) +{ + char buffer[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1; + if (!::GetComputerName(buffer, &dwSize)) + return false; + + s.assign(buffer, buffer + dwSize); + return true; +} + +FileSpec +HostInfoWindows::GetProgramFileSpec() +{ + static std::once_flag g_once_flag; + std::call_once(g_once_flag, []() { + char buffer[PATH_MAX]; + ::GetModuleFileName(NULL, buffer, sizeof(buffer)); + m_program_filespec.SetFile(buffer, false); + }); + return m_program_filespec; +} + +FileSpec +HostInfoWindows::GetDefaultShell() +{ + return FileSpec(::getenv("ComSpec"), false); +} + +bool +HostInfoWindows::ComputePythonDirectory(FileSpec &file_spec) +{ + FileSpec lldb_file_spec; + if (!GetLLDBPath(lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + llvm::SmallString<64> path(lldb_file_spec.GetDirectory().AsCString()); + llvm::sys::path::remove_filename(path); + llvm::sys::path::append(path, "lib", "site-packages"); + std::replace(path.begin(), path.end(), '\\', '/'); + file_spec.GetDirectory().SetString(path.c_str()); + return true; +} diff --git a/source/Host/windows/HostProcessWindows.cpp b/source/Host/windows/HostProcessWindows.cpp new file mode 100644 index 000000000000..0f81c18d34af --- /dev/null +++ b/source/Host/windows/HostProcessWindows.cpp @@ -0,0 +1,137 @@ +//===-- HostProcessWindows.cpp ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/FileSpec.h" +#include "lldb/Host/HostThread.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/HostProcessWindows.h" + +#include "llvm/ADT/STLExtras.h" + +#include <Psapi.h> + +using namespace lldb_private; + +namespace +{ +struct MonitorInfo +{ + HostProcess::MonitorCallback callback; + void *baton; + HANDLE process_handle; +}; +} + +HostProcessWindows::HostProcessWindows() + : HostNativeProcessBase() + , m_owns_handle(true) +{ +} + +HostProcessWindows::HostProcessWindows(lldb::process_t process) + : HostNativeProcessBase(process) + , m_owns_handle(true) +{ +} + +HostProcessWindows::~HostProcessWindows() +{ + Close(); +} + +void +HostProcessWindows::SetOwnsHandle(bool owns) +{ + m_owns_handle = owns; +} + +Error HostProcessWindows::Terminate() +{ + Error error; + if (m_process == nullptr) + error.SetError(ERROR_INVALID_HANDLE, lldb::eErrorTypeWin32); + + if (!::TerminateProcess(m_process, 0)) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + + return error; +} + +Error HostProcessWindows::GetMainModule(FileSpec &file_spec) const +{ + Error error; + if (m_process == nullptr) + error.SetError(ERROR_INVALID_HANDLE, lldb::eErrorTypeWin32); + + char path[MAX_PATH] = { 0 }; + if (::GetProcessImageFileName(m_process, path, llvm::array_lengthof(path))) + file_spec.SetFile(path, false); + else + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + + return error; +} + +lldb::pid_t HostProcessWindows::GetProcessId() const +{ + return (m_process == LLDB_INVALID_PROCESS) ? -1 : ::GetProcessId(m_process); +} + +bool HostProcessWindows::IsRunning() const +{ + if (m_process == nullptr) + return false; + + DWORD code = 0; + if (!::GetExitCodeProcess(m_process, &code)) + return false; + + return (code == STILL_ACTIVE); +} + +HostThread +HostProcessWindows::StartMonitoring(HostProcess::MonitorCallback callback, void *callback_baton, bool monitor_signals) +{ + HostThread monitor_thread; + MonitorInfo *info = new MonitorInfo; + info->callback = callback; + info->baton = callback_baton; + + // Since the life of this HostProcessWindows instance and the life of the process may be different, duplicate the handle so that + // the monitor thread can have ownership over its own copy of the handle. + HostThread result; + if (::DuplicateHandle(GetCurrentProcess(), m_process, GetCurrentProcess(), &info->process_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) + result = ThreadLauncher::LaunchThread("ChildProcessMonitor", HostProcessWindows::MonitorThread, info, nullptr); + return result; +} + +lldb::thread_result_t +HostProcessWindows::MonitorThread(void *thread_arg) +{ + DWORD exit_code; + + MonitorInfo *info = static_cast<MonitorInfo *>(thread_arg); + if (info) + { + ::WaitForSingleObject(info->process_handle, INFINITE); + ::GetExitCodeProcess(info->process_handle, &exit_code); + info->callback(info->baton, ::GetProcessId(info->process_handle), true, 0, exit_code); + ::CloseHandle(info->process_handle); + delete (info); + } + return 0; +} + +void HostProcessWindows::Close() +{ + if (m_owns_handle && m_process != LLDB_INVALID_PROCESS) + ::CloseHandle(m_process); + m_process = nullptr; +} diff --git a/source/Host/windows/HostThreadWindows.cpp b/source/Host/windows/HostThreadWindows.cpp new file mode 100644 index 000000000000..d59064fb1c15 --- /dev/null +++ b/source/Host/windows/HostThreadWindows.cpp @@ -0,0 +1,98 @@ +//===-- HostThreadWindows.cpp -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Error.h" + +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/HostThreadWindows.h" + +#include "llvm/ADT/STLExtras.h" + +using namespace lldb; +using namespace lldb_private; + +namespace +{ +void __stdcall ExitThreadProxy(ULONG_PTR dwExitCode) +{ + ::ExitThread(dwExitCode); +} +} + +HostThreadWindows::HostThreadWindows() + : HostNativeThreadBase() + , m_owns_handle(true) +{ +} + +HostThreadWindows::HostThreadWindows(lldb::thread_t thread) + : HostNativeThreadBase(thread) + , m_owns_handle(true) +{ +} + +HostThreadWindows::~HostThreadWindows() +{ + Reset(); +} + +void +HostThreadWindows::SetOwnsHandle(bool owns) +{ + m_owns_handle = owns; +} + +Error +HostThreadWindows::Join(lldb::thread_result_t *result) +{ + Error error; + if (IsJoinable()) + { + DWORD wait_result = ::WaitForSingleObject(m_thread, INFINITE); + if (WAIT_OBJECT_0 == wait_result && result) + { + DWORD exit_code = 0; + if (!::GetExitCodeThread(m_thread, &exit_code)) + *result = 0; + *result = exit_code; + } + else if (WAIT_OBJECT_0 != wait_result) + error.SetError(::GetLastError(), eErrorTypeWin32); + } + else + error.SetError(ERROR_INVALID_HANDLE, eErrorTypeWin32); + + Reset (); + return error; +} + +Error +HostThreadWindows::Cancel() +{ + Error error; + + DWORD result = ::QueueUserAPC(::ExitThreadProxy, m_thread, 0); + error.SetError(result, eErrorTypeWin32); + return error; +} + +lldb::tid_t +HostThreadWindows::GetThreadId() const +{ + return ::GetThreadId(m_thread); +} + +void +HostThreadWindows::Reset() +{ + if (m_owns_handle && m_thread != LLDB_INVALID_HOST_THREAD) + ::CloseHandle(m_thread); + + HostNativeThreadBase::Reset(); +} diff --git a/source/Host/windows/LockFileWindows.cpp b/source/Host/windows/LockFileWindows.cpp new file mode 100644 index 000000000000..161d3dbd8260 --- /dev/null +++ b/source/Host/windows/LockFileWindows.cpp @@ -0,0 +1,93 @@ +//===-- LockFileWindows.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/windows/LockFileWindows.h" + +#include <io.h> + +using namespace lldb; +using namespace lldb_private; + +namespace +{ + +Error fileLock (HANDLE file_handle, DWORD flags, const uint64_t start, const uint64_t len) +{ + if (start != 0) + return Error ("Non-zero start lock regions are not supported"); + + OVERLAPPED overlapped = {0}; + + if (!::LockFileEx (file_handle, flags, 0, len, 0, &overlapped) && ::GetLastError () != ERROR_IO_PENDING) + return Error (::GetLastError (), eErrorTypeWin32); + + DWORD bytes; + if (!::GetOverlappedResult (file_handle, &overlapped, &bytes, TRUE)) + return Error (::GetLastError (), eErrorTypeWin32); + + return Error (); +} + +} // namespace + +LockFileWindows::LockFileWindows (int fd) + : LockFileBase (fd), + m_file (reinterpret_cast<HANDLE> (_get_osfhandle (fd))) +{ +} + +LockFileWindows::~LockFileWindows () +{ + Unlock (); +} + +bool +LockFileWindows::IsValidFile () const +{ + return LockFileBase::IsValidFile() && m_file != INVALID_HANDLE_VALUE; +} + +Error +LockFileWindows::DoWriteLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, LOCKFILE_EXCLUSIVE_LOCK, start, len); +} + +Error +LockFileWindows::DoTryWriteLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, start, len); +} + +Error +LockFileWindows::DoReadLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, 0, start, len); +} + +Error +LockFileWindows::DoTryReadLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, LOCKFILE_FAIL_IMMEDIATELY, start, len); +} + +Error +LockFileWindows::DoUnlock () +{ + OVERLAPPED overlapped = {0}; + + if (!::UnlockFileEx (m_file, 0, m_len, 0, &overlapped) && ::GetLastError () != ERROR_IO_PENDING) + return Error (::GetLastError (), eErrorTypeWin32); + + DWORD bytes; + if (!::GetOverlappedResult (m_file, &overlapped, &bytes, TRUE)) + return Error (::GetLastError (), eErrorTypeWin32); + + return Error (); +} diff --git a/source/Host/windows/Mutex.cpp b/source/Host/windows/Mutex.cpp new file mode 100644 index 000000000000..ed90f46eb54b --- /dev/null +++ b/source/Host/windows/Mutex.cpp @@ -0,0 +1,109 @@ +//===-- Mutex.cpp -----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Mutex.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/windows/windows.h" + +#include <string.h> +#include <stdio.h> + +#if 0 +// This logging is way too verbose to enable even for a log channel. +// This logging can be enabled by changing the "#if 0", but should be +// reverted prior to checking in. +#include <cstdio> +#define DEBUG_LOG(fmt, ...) printf(fmt, ## __VA_ARGS__) +#else +#define DEBUG_LOG(fmt, ...) +#endif + +using namespace lldb_private; + +//---------------------------------------------------------------------- +// Default constructor. +// +// Creates a pthread mutex with no attributes. +//---------------------------------------------------------------------- +Mutex::Mutex () : + m_mutex() +{ + m_mutex = static_cast<PCRITICAL_SECTION>(malloc(sizeof(CRITICAL_SECTION))); + InitializeCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); +} + +//---------------------------------------------------------------------- +// Default constructor. +// +// Creates a pthread mutex with "type" as the mutex type. +//---------------------------------------------------------------------- +Mutex::Mutex (Mutex::Type type) : + m_mutex() +{ + m_mutex = static_cast<PCRITICAL_SECTION>(malloc(sizeof(CRITICAL_SECTION))); + InitializeCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); +} + +//---------------------------------------------------------------------- +// Destructor. +// +// Destroys the mutex owned by this object. +//---------------------------------------------------------------------- +Mutex::~Mutex() +{ + DeleteCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); + free(m_mutex); +} + +//---------------------------------------------------------------------- +// Locks the mutex owned by this object, if the mutex is already +// locked, the calling thread will block until the mutex becomes +// available. +// +// RETURNS +// The error code from the pthread_mutex_lock() function call. +//---------------------------------------------------------------------- +int +Mutex::Lock() +{ + DEBUG_LOG ("[%4.4" PRIx64 "/%4.4" PRIx64 "] pthread_mutex_lock (%p)...\n", Host::GetCurrentProcessID(), Host::GetCurrentThreadID(), m_mutex); + + EnterCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); + return 0; +} + +//---------------------------------------------------------------------- +// Attempts to lock the mutex owned by this object without blocking. +// If the mutex is already locked, TryLock() will not block waiting +// for the mutex, but will return an error condition. +// +// RETURNS +// The error code from the pthread_mutex_trylock() function call. +//---------------------------------------------------------------------- +int +Mutex::TryLock(const char *failure_message) +{ + return TryEnterCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)) == 0; +} + +//---------------------------------------------------------------------- +// If the current thread holds the lock on the owned mutex, then +// Unlock() will unlock the mutex. Calling Unlock() on this object +// that the calling thread does not hold will result in undefined +// behavior. +// +// RETURNS +// The error code from the pthread_mutex_unlock() function call. +//---------------------------------------------------------------------- +int +Mutex::Unlock() +{ + LeaveCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); + return 0; +} diff --git a/source/Host/windows/PipeWindows.cpp b/source/Host/windows/PipeWindows.cpp new file mode 100644 index 000000000000..d4afd6e74943 --- /dev/null +++ b/source/Host/windows/PipeWindows.cpp @@ -0,0 +1,336 @@ +//===-- PipeWindows.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/windows/PipeWindows.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/raw_ostream.h" + +#include <fcntl.h> +#include <io.h> +#include <rpc.h> + +#include <atomic> +#include <string> + +using namespace lldb; +using namespace lldb_private; + +namespace +{ +std::atomic<uint32_t> g_pipe_serial(0); +} + +PipeWindows::PipeWindows() +{ + m_read = INVALID_HANDLE_VALUE; + m_write = INVALID_HANDLE_VALUE; + + m_read_fd = -1; + m_write_fd = -1; + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); +} + +PipeWindows::~PipeWindows() +{ + Close(); +} + +Error +PipeWindows::CreateNew(bool child_process_inherit) +{ + // Even for anonymous pipes, we open a named pipe. This is because you cannot get + // overlapped i/o on Windows without using a named pipe. So we synthesize a unique + // name. + uint32_t serial = g_pipe_serial.fetch_add(1); + std::string pipe_name; + llvm::raw_string_ostream pipe_name_stream(pipe_name); + pipe_name_stream << "lldb.pipe." << ::GetCurrentProcessId() << "." << serial; + pipe_name_stream.flush(); + + return CreateNew(pipe_name.c_str(), child_process_inherit); +} + +Error +PipeWindows::CreateNew(llvm::StringRef name, bool child_process_inherit) +{ + if (name.empty()) + return Error(ERROR_INVALID_PARAMETER, eErrorTypeWin32); + + if (CanRead() || CanWrite()) + return Error(ERROR_ALREADY_EXISTS, eErrorTypeWin32); + + std::string pipe_path = "\\\\.\\Pipe\\"; + pipe_path.append(name); + + // Always open for overlapped i/o. We implement blocking manually in Read and Write. + DWORD read_mode = FILE_FLAG_OVERLAPPED; + m_read = + ::CreateNamedPipe(pipe_path.c_str(), PIPE_ACCESS_INBOUND | read_mode, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 1024, 1024, 120 * 1000, NULL); + if (INVALID_HANDLE_VALUE == m_read) + return Error(::GetLastError(), eErrorTypeWin32); + m_read_fd = _open_osfhandle((intptr_t)m_read, _O_RDONLY); + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + m_read_overlapped.hEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + + // Open the write end of the pipe. + Error result = OpenNamedPipe(name, child_process_inherit, false); + if (!result.Success()) + { + CloseReadFileDescriptor(); + return result; + } + + return result; +} + +Error +PipeWindows::CreateWithUniqueName(llvm::StringRef prefix, bool child_process_inherit, llvm::SmallVectorImpl<char>& name) +{ + llvm::SmallString<128> pipe_name; + Error error; + ::UUID unique_id; + RPC_CSTR unique_string; + RPC_STATUS status = ::UuidCreate(&unique_id); + if (status == RPC_S_OK || status == RPC_S_UUID_LOCAL_ONLY) + status = ::UuidToStringA(&unique_id, &unique_string); + if (status == RPC_S_OK) + { + pipe_name = prefix; + pipe_name += "-"; + pipe_name += reinterpret_cast<char *>(unique_string); + ::RpcStringFreeA(&unique_string); + error = CreateNew(pipe_name, child_process_inherit); + } + else + { + error.SetError(status, eErrorTypeWin32); + } + if (error.Success()) + name = pipe_name; + return error; +} + +Error +PipeWindows::OpenAsReader(llvm::StringRef name, bool child_process_inherit) +{ + if (CanRead() || CanWrite()) + return Error(ERROR_ALREADY_EXISTS, eErrorTypeWin32); + + return OpenNamedPipe(name, child_process_inherit, true); +} + +Error +PipeWindows::OpenAsWriterWithTimeout(llvm::StringRef name, bool child_process_inherit, const std::chrono::microseconds &timeout) +{ + if (CanRead() || CanWrite()) + return Error(ERROR_ALREADY_EXISTS, eErrorTypeWin32); + + return OpenNamedPipe(name, child_process_inherit, false); +} + +Error +PipeWindows::OpenNamedPipe(llvm::StringRef name, bool child_process_inherit, bool is_read) +{ + if (name.empty()) + return Error(ERROR_INVALID_PARAMETER, eErrorTypeWin32); + + assert(is_read ? !CanRead() : !CanWrite()); + + SECURITY_ATTRIBUTES attributes = {0}; + attributes.bInheritHandle = child_process_inherit; + + std::string pipe_path = "\\\\.\\Pipe\\"; + pipe_path.append(name); + + if (is_read) + { + m_read = ::CreateFile(pipe_path.c_str(), GENERIC_READ, 0, &attributes, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (INVALID_HANDLE_VALUE == m_read) + return Error(::GetLastError(), eErrorTypeWin32); + + m_read_fd = _open_osfhandle((intptr_t)m_read, _O_RDONLY); + + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + m_read_overlapped.hEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + } + else + { + m_write = ::CreateFile(pipe_path.c_str(), GENERIC_WRITE, 0, &attributes, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (INVALID_HANDLE_VALUE == m_write) + return Error(::GetLastError(), eErrorTypeWin32); + + m_write_fd = _open_osfhandle((intptr_t)m_write, _O_WRONLY); + + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); + } + + return Error(); +} + +int +PipeWindows::GetReadFileDescriptor() const +{ + return m_read_fd; +} + +int +PipeWindows::GetWriteFileDescriptor() const +{ + return m_write_fd; +} + +int +PipeWindows::ReleaseReadFileDescriptor() +{ + if (!CanRead()) + return -1; + int result = m_read_fd; + m_read_fd = -1; + if (m_read_overlapped.hEvent) + ::CloseHandle(m_read_overlapped.hEvent); + m_read = INVALID_HANDLE_VALUE; + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + return result; +} + +int +PipeWindows::ReleaseWriteFileDescriptor() +{ + if (!CanWrite()) + return -1; + int result = m_write_fd; + m_write_fd = -1; + m_write = INVALID_HANDLE_VALUE; + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); + return result; +} + +void +PipeWindows::CloseReadFileDescriptor() +{ + if (!CanRead()) + return; + + if (m_read_overlapped.hEvent) + ::CloseHandle(m_read_overlapped.hEvent); + _close(m_read_fd); + m_read = INVALID_HANDLE_VALUE; + m_read_fd = -1; + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); +} + +void +PipeWindows::CloseWriteFileDescriptor() +{ + if (!CanWrite()) + return; + + _close(m_write_fd); + m_write = INVALID_HANDLE_VALUE; + m_write_fd = -1; + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); +} + +void +PipeWindows::Close() +{ + CloseReadFileDescriptor(); + CloseWriteFileDescriptor(); +} + +Error +PipeWindows::Delete(llvm::StringRef name) +{ + return Error(); +} + +bool +PipeWindows::CanRead() const +{ + return (m_read != INVALID_HANDLE_VALUE); +} + +bool +PipeWindows::CanWrite() const +{ + return (m_write != INVALID_HANDLE_VALUE); +} + +HANDLE +PipeWindows::GetReadNativeHandle() +{ + return m_read; +} + +HANDLE +PipeWindows::GetWriteNativeHandle() +{ + return m_write; +} + +Error +PipeWindows::ReadWithTimeout(void *buf, size_t size, const std::chrono::microseconds &duration, size_t &bytes_read) +{ + if (!CanRead()) + return Error(ERROR_INVALID_HANDLE, eErrorTypeWin32); + + bytes_read = 0; + DWORD sys_bytes_read = size; + BOOL result = ::ReadFile(m_read, buf, sys_bytes_read, &sys_bytes_read, &m_read_overlapped); + if (!result && GetLastError() != ERROR_IO_PENDING) + return Error(::GetLastError(), eErrorTypeWin32); + + DWORD timeout = (duration == std::chrono::microseconds::zero()) ? INFINITE : duration.count() * 1000; + DWORD wait_result = ::WaitForSingleObject(m_read_overlapped.hEvent, timeout); + if (wait_result != WAIT_OBJECT_0) + { + // The operation probably failed. However, if it timed out, we need to cancel the I/O. + // Between the time we returned from WaitForSingleObject and the time we call CancelIoEx, + // the operation may complete. If that hapens, CancelIoEx will fail and return ERROR_NOT_FOUND. + // If that happens, the original operation should be considered to have been successful. + bool failed = true; + DWORD failure_error = ::GetLastError(); + if (wait_result == WAIT_TIMEOUT) + { + BOOL cancel_result = CancelIoEx(m_read, &m_read_overlapped); + if (!cancel_result && GetLastError() == ERROR_NOT_FOUND) + failed = false; + } + if (failed) + return Error(failure_error, eErrorTypeWin32); + } + + // Now we call GetOverlappedResult setting bWait to false, since we've already waited + // as long as we're willing to. + if (!GetOverlappedResult(m_read, &m_read_overlapped, &sys_bytes_read, FALSE)) + return Error(::GetLastError(), eErrorTypeWin32); + + bytes_read = sys_bytes_read; + return Error(); +} + +Error +PipeWindows::Write(const void *buf, size_t num_bytes, size_t &bytes_written) +{ + if (!CanWrite()) + return Error(ERROR_INVALID_HANDLE, eErrorTypeWin32); + + DWORD sys_bytes_written = 0; + BOOL write_result = ::WriteFile(m_write, buf, num_bytes, &sys_bytes_written, &m_write_overlapped); + if (!write_result && GetLastError() != ERROR_IO_PENDING) + return Error(::GetLastError(), eErrorTypeWin32); + + BOOL result = GetOverlappedResult(m_write, &m_write_overlapped, &sys_bytes_written, TRUE); + if (!result) + return Error(::GetLastError(), eErrorTypeWin32); + return Error(); +} diff --git a/source/Host/windows/ProcessLauncherWindows.cpp b/source/Host/windows/ProcessLauncherWindows.cpp new file mode 100644 index 000000000000..355dd40544f9 --- /dev/null +++ b/source/Host/windows/ProcessLauncherWindows.cpp @@ -0,0 +1,105 @@ +//===-- ProcessLauncherWindows.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/windows/ProcessLauncherWindows.h" +#include "lldb/Target/ProcessLaunchInfo.h" + +#include <string> +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +HostProcess +ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, Error &error) +{ + error.Clear(); + + std::string executable; + std::string commandLine; + std::vector<char> environment; + STARTUPINFO startupinfo = {0}; + PROCESS_INFORMATION pi = {0}; + + HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); + HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); + HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO); + + startupinfo.cb = sizeof(startupinfo); + startupinfo.dwFlags |= STARTF_USESTDHANDLES; + startupinfo.hStdError = stderr_handle ? stderr_handle : ::GetStdHandle(STD_ERROR_HANDLE); + startupinfo.hStdInput = stdin_handle ? stdin_handle : ::GetStdHandle(STD_INPUT_HANDLE); + startupinfo.hStdOutput = stdout_handle ? stdout_handle : ::GetStdHandle(STD_OUTPUT_HANDLE); + + const char *hide_console_var = getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE"); + if (hide_console_var && llvm::StringRef(hide_console_var).equals_lower("true")) + { + startupinfo.dwFlags |= STARTF_USESHOWWINDOW; + startupinfo.wShowWindow = SW_HIDE; + } + + DWORD flags = CREATE_NEW_CONSOLE; + if (launch_info.GetFlags().Test(eLaunchFlagDebug)) + flags |= DEBUG_ONLY_THIS_PROCESS; + + executable = launch_info.GetExecutableFile().GetPath(); + launch_info.GetArguments().GetQuotedCommandString(commandLine); + BOOL result = ::CreateProcessA(executable.c_str(), const_cast<char *>(commandLine.c_str()), NULL, NULL, TRUE, flags, NULL, + launch_info.GetWorkingDirectory().GetCString(), &startupinfo, &pi); + if (result) + { + // Do not call CloseHandle on pi.hProcess, since we want to pass that back through the HostProcess. + ::CloseHandle(pi.hThread); + } + + if (stdin_handle) + ::CloseHandle(stdin_handle); + if (stdout_handle) + ::CloseHandle(stdout_handle); + if (stderr_handle) + ::CloseHandle(stderr_handle); + + if (!result) + error.SetError(::GetLastError(), eErrorTypeWin32); + return HostProcess(pi.hProcess); +} + +HANDLE +ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd) +{ + const FileAction *action = launch_info.GetFileActionForFD(fd); + if (action == nullptr) + return NULL; + SECURITY_ATTRIBUTES secattr = {0}; + secattr.nLength = sizeof(SECURITY_ATTRIBUTES); + secattr.bInheritHandle = TRUE; + + const char *path = action->GetPath(); + DWORD access = 0; + DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + DWORD create = 0; + DWORD flags = 0; + if (fd == STDIN_FILENO) + { + access = GENERIC_READ; + create = OPEN_EXISTING; + flags = FILE_ATTRIBUTE_READONLY; + } + if (fd == STDOUT_FILENO || fd == STDERR_FILENO) + { + access = GENERIC_WRITE; + create = CREATE_ALWAYS; + if (fd == STDERR_FILENO) + flags = FILE_FLAG_WRITE_THROUGH; + } + + HANDLE result = ::CreateFile(path, access, share, &secattr, create, flags, NULL); + return (result == INVALID_HANDLE_VALUE) ? NULL : result; +} diff --git a/source/Host/windows/ProcessRunLock.cpp b/source/Host/windows/ProcessRunLock.cpp new file mode 100644 index 000000000000..1f21552a3062 --- /dev/null +++ b/source/Host/windows/ProcessRunLock.cpp @@ -0,0 +1,106 @@ +#include "lldb/Host/ProcessRunLock.h" +#include "lldb/Host/windows/windows.h" + +namespace +{ +#if defined(__MINGW32__) +// Taken from WinNT.h +typedef struct _RTL_SRWLOCK { + PVOID Ptr; +} RTL_SRWLOCK, *PRTL_SRWLOCK; + +// Taken from WinBase.h +typedef RTL_SRWLOCK SRWLOCK, *PSRWLOCK; +#endif +} + + +static PSRWLOCK GetLock(lldb::rwlock_t lock) +{ + return static_cast<PSRWLOCK>(lock); +} + +static bool ReadLock(lldb::rwlock_t rwlock) +{ + ::AcquireSRWLockShared(GetLock(rwlock)); + return true; +} + +static bool ReadUnlock(lldb::rwlock_t rwlock) +{ + ::ReleaseSRWLockShared(GetLock(rwlock)); + return true; +} + +static bool WriteLock(lldb::rwlock_t rwlock) +{ + ::AcquireSRWLockExclusive(GetLock(rwlock)); + return true; +} + +static bool WriteTryLock(lldb::rwlock_t rwlock) +{ + return !!::TryAcquireSRWLockExclusive(GetLock(rwlock)); +} + +static bool WriteUnlock(lldb::rwlock_t rwlock) +{ + ::ReleaseSRWLockExclusive(GetLock(rwlock)); + return true; +} + +using namespace lldb_private; + +ProcessRunLock::ProcessRunLock() + : m_running(false) +{ + m_rwlock = new SRWLOCK; + InitializeSRWLock(GetLock(m_rwlock)); +} + +ProcessRunLock::~ProcessRunLock() +{ + delete m_rwlock; +} + +bool ProcessRunLock::ReadTryLock() +{ + ::ReadLock(m_rwlock); + if (m_running == false) + return true; + ::ReadUnlock(m_rwlock); + return false; +} + +bool ProcessRunLock::ReadUnlock() +{ + return ::ReadUnlock(m_rwlock); +} + +bool ProcessRunLock::SetRunning () +{ + WriteLock(m_rwlock); + m_running = true; + WriteUnlock(m_rwlock); + return true; +} + +bool ProcessRunLock::TrySetRunning () +{ + if (WriteTryLock(m_rwlock)) + { + bool was_running = m_running; + m_running = true; + WriteUnlock(m_rwlock); + return !was_running; + } + return false; +} + +bool ProcessRunLock::SetStopped () +{ + WriteLock(m_rwlock); + m_running = false; + WriteUnlock(m_rwlock); + return true; +} diff --git a/source/Host/windows/ThisThread.cpp b/source/Host/windows/ThisThread.cpp new file mode 100644 index 000000000000..bcd5b8d1c1d0 --- /dev/null +++ b/source/Host/windows/ThisThread.cpp @@ -0,0 +1,66 @@ +//===-- ThisThread.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Error.h" + +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/ThisThread.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" + +using namespace lldb; +using namespace lldb_private; + +#if defined(_MSC_VER) && !defined(__clang__) + +namespace +{ +static const DWORD MS_VC_EXCEPTION = 0x406D1388; + +#pragma pack(push, 8) +struct THREADNAME_INFO +{ + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to thread name + DWORD dwThreadId; // Thread ID (-1 == current thread) + DWORD dwFlags; // Reserved. Do not use. +}; +#pragma pack(pop) +} + +#endif + +void +ThisThread::SetName(llvm::StringRef name) +{ +// Other compilers don't yet support SEH, so we can only set the thread if compiling with MSVC. +// TODO(zturner): Once clang-cl supports SEH, relax this conditional. +#if defined(_MSC_VER) && !defined(__clang__) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name.data(); + info.dwThreadId = ::GetCurrentThreadId(); + info.dwFlags = 0; + + __try { + ::RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) {} +#endif +} + +void +ThisThread::GetName(llvm::SmallVectorImpl<char> &name) +{ + // Getting the thread name is not supported on Windows. + // TODO(zturner): In SetName(), make a TLS entry that contains the thread's name, and in this function + // try to extract that TLS entry. + name.clear(); +} diff --git a/source/Host/windows/Windows.cpp b/source/Host/windows/Windows.cpp new file mode 100644 index 000000000000..71bff7a9d2e2 --- /dev/null +++ b/source/Host/windows/Windows.cpp @@ -0,0 +1,231 @@ +//===-- Windows.cpp ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This file provides Windows support functions + +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/win32.h" + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <io.h> +#include <cerrno> +#include <ctype.h> + +// These prototypes are defined in <direct.h>, but it also defines chdir() and getcwd(), giving multiply defined errors +extern "C" +{ + char *_getcwd(char *buffer, int maxlen); + int _chdir(const char *path); +} + +int vasprintf(char **ret, const char *fmt, va_list ap) +{ + char *buf; + int len; + size_t buflen; + va_list ap2; + +#if defined(_MSC_VER) || defined(__MINGW64) + ap2 = ap; + len = _vscprintf(fmt, ap2); +#else + va_copy(ap2, ap); + len = vsnprintf(NULL, 0, fmt, ap2); +#endif + + if (len >= 0 && (buf = (char*) malloc ((buflen = (size_t) (len + 1)))) != NULL) { + len = vsnprintf(buf, buflen, fmt, ap); + *ret = buf; + } else { + *ret = NULL; + len = -1; + } + + va_end(ap2); + return len; +} + +char* strcasestr(const char *s, const char* find) +{ + char c, sc; + size_t len; + + if ((c = *find++) != 0) { + c = tolower((unsigned char) c); + len = strlen(find); + do { + do { + if ((sc = *s++) == 0) + return 0; + } while ((char) tolower((unsigned char) sc) != c); + } while (strncasecmp(s, find, len) != 0); + s--; + } + return ((char *) s); +} + +char* realpath(const char * name, char * resolved) +{ + char *retname = NULL; /* we will return this, if we fail */ + + /* SUSv3 says we must set `errno = EINVAL', and return NULL, + * if `name' is passed as a NULL pointer. + */ + + if (name == NULL) + errno = EINVAL; + + /* Otherwise, `name' must refer to a readable filesystem object, + * if we are going to resolve its absolute path name. + */ + + else if (access(name, 4) == 0) + { + /* If `name' didn't point to an existing entity, + * then we don't get to here; we simply fall past this block, + * returning NULL, with `errno' appropriately set by `access'. + * + * When we _do_ get to here, then we can use `_fullpath' to + * resolve the full path for `name' into `resolved', but first, + * check that we have a suitable buffer, in which to return it. + */ + + if ((retname = resolved) == NULL) + { + /* Caller didn't give us a buffer, so we'll exercise the + * option granted by SUSv3, and allocate one. + * + * `_fullpath' would do this for us, but it uses `malloc', and + * Microsoft's implementation doesn't set `errno' on failure. + * If we don't do this explicitly ourselves, then we will not + * know if `_fullpath' fails on `malloc' failure, or for some + * other reason, and we want to set `errno = ENOMEM' for the + * `malloc' failure case. + */ + + retname = (char*) malloc(_MAX_PATH); + } + + /* By now, we should have a valid buffer. + * If we don't, then we know that `malloc' failed, + * so we can set `errno = ENOMEM' appropriately. + */ + + if (retname == NULL) + errno = ENOMEM; + + /* Otherwise, when we do have a valid buffer, + * `_fullpath' should only fail if the path name is too long. + */ + + else if ((retname = _fullpath(retname, name, _MAX_PATH)) == NULL) + errno = ENAMETOOLONG; + } + + /* By the time we get to here, + * `retname' either points to the required resolved path name, + * or it is NULL, with `errno' set appropriately, either of which + * is our required return condition. + */ + + if (retname != NULL) + { + // Do a LongPath<->ShortPath roundtrip so that case is resolved by OS + int initialLength = strlen(retname); + TCHAR buffer[MAX_PATH]; + GetShortPathName(retname, buffer, MAX_PATH); + GetLongPathName(buffer, retname, initialLength + 1); + + // Force drive to be upper case + if (retname[1] == ':') + retname[0] = toupper(retname[0]); + } + + return retname; +} + +#ifdef _MSC_VER + +char* basename(char *path) +{ + char* l1 = strrchr(path, '\\'); + char* l2 = strrchr(path, '/'); + if (l2 > l1) l1 = l2; + if (!l1) return path; // no base name + return &l1[1]; +} + +// use _getcwd() instead of GetCurrentDirectory() because it updates errno +char* getcwd(char* path, int max) +{ + return _getcwd(path, max); +} + +// use _chdir() instead of SetCurrentDirectory() because it updates errno +int chdir(const char* path) +{ + return _chdir(path); +} + +char *dirname(char *path) +{ + char* l1 = strrchr(path, '\\'); + char* l2 = strrchr(path, '/'); + if (l2 > l1) l1 = l2; + if (!l1) return NULL; // no dir name + *l1 = 0; + return path; +} + +int strcasecmp(const char* s1, const char* s2) +{ + return stricmp(s1, s2); +} + +int strncasecmp(const char* s1, const char* s2, size_t n) +{ + return strnicmp(s1, s2, n); +} + +int usleep(uint32_t useconds) +{ + Sleep(useconds / 1000); + return 0; +} + +#if _MSC_VER < 1900 +namespace lldb_private { +int vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ + int old_errno = errno; + int r = ::vsnprintf(buffer, count, format, argptr); + int new_errno = errno; + buffer[count-1] = '\0'; + if (r == -1 || r == count) + { + FILE *nul = fopen("nul", "w"); + int bytes_written = ::vfprintf(nul, format, argptr); + fclose(nul); + if (bytes_written < count) + errno = new_errno; + else + { + errno = old_errno; + r = bytes_written; + } + } + return r; +} +} // namespace lldb_private +#endif + +#endif // _MSC_VER |