| //===-- PlatformiOSSimulatorCoreSimulatorSupport.cpp ---------------*- C++ |
| //-*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "PlatformiOSSimulatorCoreSimulatorSupport.h" |
| |
| // C Includes |
| // C++ Includes |
| // Other libraries and framework includes |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <Foundation/Foundation.h> |
| // Project includes |
| #include "lldb/Host/PseudoTerminal.h" |
| #include "lldb/Target/FileAction.h" |
| |
| #include "llvm/ADT/StringRef.h" |
| |
| using namespace lldb_private; |
| // CoreSimulator lives as part of Xcode, which means we can't really link |
| // against it, so we dlopen() |
| // it at runtime, and error out nicely if that fails |
| @interface SimServiceContext { |
| } |
| + (id)sharedServiceContextForDeveloperDir:(NSString *)dir |
| error:(NSError **)error; |
| @end |
| // However, the drawback is that the compiler will not know about the selectors |
| // we're trying to use |
| // until runtime; to appease clang in this regard, define a fake protocol on |
| // NSObject that exposes |
| // the needed interface names for us |
| @protocol LLDBCoreSimulatorSupport <NSObject> |
| - (id)defaultDeviceSetWithError:(NSError **)error; |
| - (NSArray *)devices; |
| - (id)deviceType; |
| - (NSString *)name; |
| - (NSString *)identifier; |
| - (NSString *)modelIdentifier; |
| - (NSString *)productFamily; |
| - (int32_t)productFamilyID; |
| - (id)runtime; |
| - (BOOL)available; |
| - (NSString *)versionString; |
| - (NSString *)buildVersionString; |
| - (BOOL)bootWithOptions:(NSDictionary *)options error:(NSError **)error; |
| - (NSUInteger)state; |
| - (BOOL)shutdownWithError:(NSError **)error; |
| - (NSUUID *)UDID; |
| - (pid_t)spawnWithPath:(NSString *)path |
| options:(NSDictionary *)options |
| terminationHandler:(void (^)(int status))terminationHandler |
| error:(NSError **)error; |
| @end |
| |
| CoreSimulatorSupport::Process::Process(lldb::pid_t p) : m_pid(p), m_error() {} |
| |
| CoreSimulatorSupport::Process::Process(Status error) |
| : m_pid(LLDB_INVALID_PROCESS_ID), m_error(error) {} |
| |
| CoreSimulatorSupport::Process::Process(lldb::pid_t p, Status error) |
| : m_pid(p), m_error(error) {} |
| |
| CoreSimulatorSupport::DeviceType::DeviceType() |
| : m_dev(nil), m_model_identifier() {} |
| |
| CoreSimulatorSupport::DeviceType::DeviceType(id d) |
| : m_dev(d), m_model_identifier() {} |
| |
| CoreSimulatorSupport::DeviceType::operator bool() { return m_dev != nil; } |
| |
| ConstString CoreSimulatorSupport::DeviceType::GetIdentifier() { |
| return ConstString([[m_dev identifier] UTF8String]); |
| } |
| |
| ConstString CoreSimulatorSupport::DeviceType::GetProductFamily() { |
| return ConstString([[m_dev productFamily] UTF8String]); |
| } |
| |
| CoreSimulatorSupport::DeviceType::ProductFamilyID |
| CoreSimulatorSupport::DeviceType::GetProductFamilyID() { |
| return ProductFamilyID([m_dev productFamilyID]); |
| } |
| |
| CoreSimulatorSupport::DeviceRuntime::DeviceRuntime() |
| : m_dev(nil), m_os_version() {} |
| |
| CoreSimulatorSupport::DeviceRuntime::DeviceRuntime(id d) |
| : m_dev(d), m_os_version() {} |
| |
| CoreSimulatorSupport::DeviceRuntime::operator bool() { return m_dev != nil; } |
| |
| bool CoreSimulatorSupport::DeviceRuntime::IsAvailable() { |
| return [m_dev available]; |
| } |
| |
| CoreSimulatorSupport::Device::Device() |
| : m_dev(nil), m_dev_type(), m_dev_runtime() {} |
| |
| CoreSimulatorSupport::Device::Device(id d) |
| : m_dev(d), m_dev_type(), m_dev_runtime() {} |
| |
| CoreSimulatorSupport::Device::operator bool() { return m_dev != nil; } |
| |
| CoreSimulatorSupport::Device::State CoreSimulatorSupport::Device::GetState() { |
| return (State)([m_dev state]); |
| } |
| |
| CoreSimulatorSupport::ModelIdentifier::ModelIdentifier(const std::string &mi) |
| : m_family(), m_versions() { |
| bool any = false; |
| bool first_digit = false; |
| unsigned int val = 0; |
| |
| for (char c : mi) { |
| any = true; |
| if (::isdigit(c)) { |
| if (!first_digit) |
| first_digit = true; |
| val = 10 * val + (c - '0'); |
| } else if (c == ',') { |
| if (first_digit) { |
| m_versions.push_back(val); |
| val = 0; |
| } else |
| m_family.push_back(c); |
| } else { |
| if (first_digit) { |
| m_family.clear(); |
| m_versions.clear(); |
| return; |
| } else { |
| m_family.push_back(c); |
| } |
| } |
| } |
| |
| if (first_digit) |
| m_versions.push_back(val); |
| } |
| |
| CoreSimulatorSupport::ModelIdentifier::ModelIdentifier() |
| : ModelIdentifier("") {} |
| |
| CoreSimulatorSupport::OSVersion::OSVersion(const std::string &ver, |
| const std::string &build) |
| : m_versions(), m_build(build) { |
| bool any = false; |
| unsigned int val = 0; |
| for (char c : ver) { |
| if (c == '.') { |
| m_versions.push_back(val); |
| val = 0; |
| } else if (::isdigit(c)) { |
| val = 10 * val + (c - '0'); |
| any = true; |
| } else { |
| m_versions.clear(); |
| return; |
| } |
| } |
| if (any) |
| m_versions.push_back(val); |
| } |
| |
| CoreSimulatorSupport::OSVersion::OSVersion() : OSVersion("", "") {} |
| |
| CoreSimulatorSupport::ModelIdentifier |
| CoreSimulatorSupport::DeviceType::GetModelIdentifier() { |
| if (!m_model_identifier.hasValue()) { |
| auto utf8_model_id = [[m_dev modelIdentifier] UTF8String]; |
| if (utf8_model_id && *utf8_model_id) |
| m_model_identifier = ModelIdentifier(utf8_model_id); |
| } |
| |
| if (m_model_identifier.hasValue()) |
| return m_model_identifier.getValue(); |
| else |
| return ModelIdentifier(); |
| } |
| |
| CoreSimulatorSupport::OSVersion |
| CoreSimulatorSupport::DeviceRuntime::GetVersion() { |
| if (!m_os_version.hasValue()) { |
| auto utf8_ver_string = [[m_dev versionString] UTF8String]; |
| auto utf8_build_ver = [[m_dev buildVersionString] UTF8String]; |
| if (utf8_ver_string && *utf8_ver_string && utf8_build_ver && |
| *utf8_build_ver) { |
| m_os_version = OSVersion(utf8_ver_string, utf8_build_ver); |
| } |
| } |
| |
| if (m_os_version.hasValue()) |
| return m_os_version.getValue(); |
| return OSVersion(); |
| } |
| |
| std::string CoreSimulatorSupport::DeviceType::GetName() { |
| auto utf8_name = [[m_dev name] UTF8String]; |
| if (utf8_name) |
| return std::string(utf8_name); |
| return ""; |
| } |
| |
| std::string CoreSimulatorSupport::Device::GetName() const { |
| auto utf8_name = [[m_dev name] UTF8String]; |
| if (utf8_name) |
| return std::string(utf8_name); |
| return ""; |
| } |
| |
| std::string CoreSimulatorSupport::Device::GetUDID() const { |
| auto utf8_udid = [[[m_dev UDID] UUIDString] UTF8String]; |
| if (utf8_udid) |
| return std::string(utf8_udid); |
| else |
| return std::string(); |
| } |
| |
| CoreSimulatorSupport::DeviceType CoreSimulatorSupport::Device::GetDeviceType() { |
| if (!m_dev_type.hasValue()) |
| m_dev_type = DeviceType([m_dev deviceType]); |
| |
| return m_dev_type.getValue(); |
| } |
| |
| CoreSimulatorSupport::DeviceRuntime |
| CoreSimulatorSupport::Device::GetDeviceRuntime() { |
| if (!m_dev_runtime.hasValue()) |
| m_dev_runtime = DeviceRuntime([m_dev runtime]); |
| |
| return m_dev_runtime.getValue(); |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator>(const CoreSimulatorSupport::OSVersion &lhs, |
| const CoreSimulatorSupport::OSVersion &rhs) { |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l > r) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator>(const CoreSimulatorSupport::ModelIdentifier &lhs, |
| const CoreSimulatorSupport::ModelIdentifier &rhs) { |
| if (lhs.GetFamily() != rhs.GetFamily()) |
| return false; |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l > r) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator<(const CoreSimulatorSupport::OSVersion &lhs, |
| const CoreSimulatorSupport::OSVersion &rhs) { |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l < r) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator<(const CoreSimulatorSupport::ModelIdentifier &lhs, |
| const CoreSimulatorSupport::ModelIdentifier &rhs) { |
| if (lhs.GetFamily() != rhs.GetFamily()) |
| return false; |
| |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l < r) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator==(const CoreSimulatorSupport::OSVersion &lhs, |
| const CoreSimulatorSupport::OSVersion &rhs) { |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l != r) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator==(const CoreSimulatorSupport::ModelIdentifier &lhs, |
| const CoreSimulatorSupport::ModelIdentifier &rhs) { |
| if (lhs.GetFamily() != rhs.GetFamily()) |
| return false; |
| |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l != r) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator!=(const CoreSimulatorSupport::OSVersion &lhs, |
| const CoreSimulatorSupport::OSVersion &rhs) { |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l != r) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CoreSimulatorSupport:: |
| operator!=(const CoreSimulatorSupport::ModelIdentifier &lhs, |
| const CoreSimulatorSupport::ModelIdentifier &rhs) { |
| if (lhs.GetFamily() != rhs.GetFamily()) |
| return false; |
| |
| for (size_t i = 0; i < rhs.GetNumVersions(); i++) { |
| unsigned int l = lhs.GetVersionAtIndex(i); |
| unsigned int r = rhs.GetVersionAtIndex(i); |
| if (l != r) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CoreSimulatorSupport::Device::Boot(Status &err) { |
| if (m_dev == nil) { |
| err.SetErrorString("no valid simulator instance"); |
| return false; |
| } |
| |
| #define kSimDeviceBootPersist \ |
| @"persist" /* An NSNumber (boolean) indicating whether or not the session \ |
| should outlive the calling process (default false) */ |
| |
| NSDictionary *options = @{ |
| kSimDeviceBootPersist : @NO, |
| }; |
| |
| #undef kSimDeviceBootPersist |
| |
| NSError *nserror; |
| if ([m_dev bootWithOptions:options error:&nserror]) { |
| err.Clear(); |
| return true; |
| } else { |
| err.SetErrorString([[nserror description] UTF8String]); |
| return false; |
| } |
| } |
| |
| bool CoreSimulatorSupport::Device::Shutdown(Status &err) { |
| NSError *nserror; |
| if ([m_dev shutdownWithError:&nserror]) { |
| err.Clear(); |
| return true; |
| } else { |
| err.SetErrorString([[nserror description] UTF8String]); |
| return false; |
| } |
| } |
| |
| static Status HandleFileAction(ProcessLaunchInfo &launch_info, |
| NSMutableDictionary *options, NSString *key, |
| const int fd, File &file) { |
| Status error; |
| const FileAction *file_action = launch_info.GetFileActionForFD(fd); |
| if (file_action) { |
| switch (file_action->GetAction()) { |
| case FileAction::eFileActionNone: |
| break; |
| |
| case FileAction::eFileActionClose: |
| error.SetErrorStringWithFormat("close file action for %i not supported", |
| fd); |
| break; |
| |
| case FileAction::eFileActionDuplicate: |
| error.SetErrorStringWithFormat( |
| "duplication file action for %i not supported", fd); |
| break; |
| |
| case FileAction::eFileActionOpen: { |
| FileSpec file_spec = file_action->GetFileSpec(); |
| if (file_spec) { |
| const int master_fd = launch_info.GetPTY().GetMasterFileDescriptor(); |
| if (master_fd != PseudoTerminal::invalid_fd) { |
| // Check in case our file action open wants to open the slave |
| const char *slave_path = launch_info.GetPTY().GetSlaveName(NULL, 0); |
| if (slave_path) { |
| FileSpec slave_spec(slave_path, false); |
| if (file_spec == slave_spec) { |
| int slave_fd = launch_info.GetPTY().GetSlaveFileDescriptor(); |
| if (slave_fd == PseudoTerminal::invalid_fd) |
| slave_fd = launch_info.GetPTY().OpenSlave(O_RDWR, nullptr, 0); |
| if (slave_fd == PseudoTerminal::invalid_fd) { |
| error.SetErrorStringWithFormat("unable to open slave pty '%s'", |
| slave_path); |
| return error; // Failure |
| } |
| [options setValue:[NSNumber numberWithInteger:slave_fd] |
| forKey:key]; |
| return error; // Success |
| } |
| } |
| } |
| Status posix_error; |
| int created_fd = |
| open(file_spec.GetPath().c_str(), file_action->GetActionArgument(), |
| S_IRUSR | S_IWUSR); |
| if (created_fd >= 0) { |
| file.SetDescriptor(created_fd, true); |
| [options setValue:[NSNumber numberWithInteger:created_fd] forKey:key]; |
| return error; // Success |
| } else { |
| posix_error.SetErrorToErrno(); |
| error.SetErrorStringWithFormat("unable to open file '%s': %s", |
| file_spec.GetPath().c_str(), |
| posix_error.AsCString()); |
| } |
| } |
| } break; |
| } |
| } |
| return error; // Success, no file action, nothing to do |
| } |
| |
| CoreSimulatorSupport::Process |
| CoreSimulatorSupport::Device::Spawn(ProcessLaunchInfo &launch_info) { |
| #define kSimDeviceSpawnEnvironment \ |
| @"environment" /* An NSDictionary (NSStrings -> NSStrings) of environment \ |
| key/values */ |
| #define kSimDeviceSpawnStdin @"stdin" /* An NSNumber corresponding to a fd */ |
| #define kSimDeviceSpawnStdout @"stdout" /* An NSNumber corresponding to a fd \ |
| */ |
| #define kSimDeviceSpawnStderr @"stderr" /* An NSNumber corresponding to a fd \ |
| */ |
| #define kSimDeviceSpawnArguments \ |
| @"arguments" /* An NSArray of strings to use as the argv array. If not \ |
| provided, path will be argv[0] */ |
| #define kSimDeviceSpawnWaitForDebugger \ |
| @"wait_for_debugger" /* An NSNumber (bool) */ |
| |
| NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; |
| |
| if (launch_info.GetFlags().Test(lldb::eLaunchFlagDebug)) |
| [options setObject:@YES forKey:kSimDeviceSpawnWaitForDebugger]; |
| |
| if (launch_info.GetArguments().GetArgumentCount()) { |
| const Args &args(launch_info.GetArguments()); |
| NSMutableArray *args_array = [[NSMutableArray alloc] init]; |
| for (size_t idx = 0; idx < args.GetArgumentCount(); idx++) |
| [args_array |
| addObject:[NSString |
| stringWithUTF8String:args.GetArgumentAtIndex(idx)]]; |
| |
| [options setObject:args_array forKey:kSimDeviceSpawnArguments]; |
| } |
| |
| NSMutableDictionary *env_dict = [[NSMutableDictionary alloc] init]; |
| |
| for (const auto &KV : launch_info.GetEnvironment()) { |
| NSString *key_ns = [NSString stringWithUTF8String:KV.first().str().c_str()]; |
| NSString *value_ns = [NSString stringWithUTF8String:KV.second.c_str()]; |
| |
| [env_dict setValue:value_ns forKey:key_ns]; |
| } |
| |
| [options setObject:env_dict forKey:kSimDeviceSpawnEnvironment]; |
| |
| Status error; |
| File stdin_file; |
| File stdout_file; |
| File stderr_file; |
| error = HandleFileAction(launch_info, options, kSimDeviceSpawnStdin, |
| STDIN_FILENO, stdin_file); |
| |
| if (error.Fail()) |
| return CoreSimulatorSupport::Process(error); |
| |
| error = HandleFileAction(launch_info, options, kSimDeviceSpawnStdout, |
| STDOUT_FILENO, stdout_file); |
| |
| if (error.Fail()) |
| return CoreSimulatorSupport::Process(error); |
| |
| error = HandleFileAction(launch_info, options, kSimDeviceSpawnStderr, |
| STDERR_FILENO, stderr_file); |
| |
| if (error.Fail()) |
| return CoreSimulatorSupport::Process(error); |
| |
| #undef kSimDeviceSpawnEnvironment |
| #undef kSimDeviceSpawnStdin |
| #undef kSimDeviceSpawnStdout |
| #undef kSimDeviceSpawnStderr |
| #undef kSimDeviceSpawnWaitForDebugger |
| #undef kSimDeviceSpawnArguments |
| |
| NSError *nserror; |
| |
| pid_t pid = [m_dev |
| spawnWithPath:[NSString stringWithUTF8String:launch_info |
| .GetExecutableFile() |
| .GetPath() |
| .c_str()] |
| options:options |
| terminationHandler:nil |
| error:&nserror]; |
| |
| if (pid < 0) { |
| const char *nserror_string = [[nserror description] UTF8String]; |
| error.SetErrorString(nserror_string ? nserror_string : "unable to launch"); |
| } |
| |
| return CoreSimulatorSupport::Process(pid, error); |
| } |
| |
| CoreSimulatorSupport::DeviceSet |
| CoreSimulatorSupport::DeviceSet::GetAllDevices(const char *developer_dir) { |
| if (!developer_dir || !developer_dir[0]) |
| return DeviceSet([NSArray new]); |
| |
| Class SimServiceContextClass = NSClassFromString(@"SimServiceContext"); |
| NSString *dev_dir = @(developer_dir); |
| NSError *error = nil; |
| |
| id serviceContext = |
| [SimServiceContextClass sharedServiceContextForDeveloperDir:dev_dir |
| error:&error]; |
| if (!serviceContext) |
| return DeviceSet([NSArray new]); |
| |
| return DeviceSet([[serviceContext defaultDeviceSetWithError:&error] devices]); |
| } |
| |
| CoreSimulatorSupport::DeviceSet |
| CoreSimulatorSupport::DeviceSet::GetAvailableDevices( |
| const char *developer_dir) { |
| return GetAllDevices(developer_dir).GetDevicesIf([](Device d) -> bool { |
| return (d && d.GetDeviceType() && d.GetDeviceRuntime() && |
| d.GetDeviceRuntime().IsAvailable()); |
| }); |
| } |
| |
| size_t CoreSimulatorSupport::DeviceSet::GetNumDevices() { |
| return [m_dev count]; |
| } |
| |
| CoreSimulatorSupport::Device |
| CoreSimulatorSupport::DeviceSet::GetDeviceAtIndex(size_t idx) { |
| if (idx < GetNumDevices()) |
| return Device([m_dev objectAtIndex:idx]); |
| return Device(); |
| } |
| |
| CoreSimulatorSupport::DeviceSet CoreSimulatorSupport::DeviceSet::GetDevicesIf( |
| std::function<bool(CoreSimulatorSupport::Device)> f) { |
| NSMutableArray *array = [[NSMutableArray alloc] init]; |
| for (NSUInteger i = 0; i < GetNumDevices(); i++) { |
| Device d(GetDeviceAtIndex(i)); |
| if (f(d)) |
| [array addObject:(id)d.m_dev]; |
| } |
| |
| return DeviceSet(array); |
| } |
| |
| void CoreSimulatorSupport::DeviceSet::ForEach( |
| std::function<bool(const Device &)> f) { |
| const size_t n = GetNumDevices(); |
| for (NSUInteger i = 0; i < n; ++i) { |
| if (f(GetDeviceAtIndex(i)) == false) |
| break; |
| } |
| } |
| |
| CoreSimulatorSupport::DeviceSet CoreSimulatorSupport::DeviceSet::GetDevices( |
| CoreSimulatorSupport::DeviceType::ProductFamilyID dev_id) { |
| NSMutableArray *array = [[NSMutableArray alloc] init]; |
| const size_t n = GetNumDevices(); |
| for (NSUInteger i = 0; i < n; ++i) { |
| Device d(GetDeviceAtIndex(i)); |
| if (d && d.GetDeviceType() && |
| d.GetDeviceType().GetProductFamilyID() == dev_id) |
| [array addObject:(id)d.m_dev]; |
| } |
| |
| return DeviceSet(array); |
| } |
| |
| CoreSimulatorSupport::Device CoreSimulatorSupport::DeviceSet::GetFanciest( |
| CoreSimulatorSupport::DeviceType::ProductFamilyID dev_id) { |
| Device dev; |
| |
| for (NSUInteger i = 0; i < GetNumDevices(); i++) { |
| Device d(GetDeviceAtIndex(i)); |
| if (d && d.GetDeviceType() && |
| d.GetDeviceType().GetProductFamilyID() == dev_id) { |
| if (!dev) |
| dev = d; |
| else { |
| if ((d.GetDeviceType().GetModelIdentifier() > |
| dev.GetDeviceType().GetModelIdentifier()) || |
| d.GetDeviceRuntime().GetVersion() > |
| dev.GetDeviceRuntime().GetVersion()) |
| dev = d; |
| } |
| } |
| } |
| |
| return dev; |
| } |