1 //===-- PlatformAppleSimulator.cpp ----------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "PlatformAppleSimulator.h"
10 
11 #if defined(__APPLE__)
12 #include <dlfcn.h>
13 #endif
14 
15 #include <mutex>
16 #include <thread>
17 #include "lldb/Host/PseudoTerminal.h"
18 #include "lldb/Target/Process.h"
19 #include "lldb/Utility/LLDBAssert.h"
20 #include "lldb/Utility/Status.h"
21 #include "lldb/Utility/StreamString.h"
22 #include "llvm/Support/Threading.h"
23 
24 using namespace lldb;
25 using namespace lldb_private;
26 
27 #if !defined(__APPLE__)
28 #define UNSUPPORTED_ERROR ("Apple simulators aren't supported on this platform")
29 #endif
30 
31 // Static Functions
32 void PlatformAppleSimulator::Initialize() { PlatformDarwin::Initialize(); }
33 
34 void PlatformAppleSimulator::Terminate() { PlatformDarwin::Terminate(); }
35 
36 /// Default Constructor
37 PlatformAppleSimulator::PlatformAppleSimulator()
38     : PlatformDarwin(true), m_core_sim_path_mutex(),
39       m_core_simulator_framework_path(), m_device() {}
40 
41 /// Destructor.
42 ///
43 /// The destructor is virtual since this class is designed to be
44 /// inherited from by the plug-in instance.
45 PlatformAppleSimulator::~PlatformAppleSimulator() {}
46 
47 lldb_private::Status PlatformAppleSimulator::LaunchProcess(
48     lldb_private::ProcessLaunchInfo &launch_info) {
49 #if defined(__APPLE__)
50   LoadCoreSimulator();
51   CoreSimulatorSupport::Device device(GetSimulatorDevice());
52 
53   if (device.GetState() != CoreSimulatorSupport::Device::State::Booted) {
54     Status boot_err;
55     device.Boot(boot_err);
56     if (boot_err.Fail())
57       return boot_err;
58   }
59 
60   auto spawned = device.Spawn(launch_info);
61 
62   if (spawned) {
63     launch_info.SetProcessID(spawned.GetPID());
64     return Status();
65   } else
66     return spawned.GetError();
67 #else
68   Status err;
69   err.SetErrorString(UNSUPPORTED_ERROR);
70   return err;
71 #endif
72 }
73 
74 void PlatformAppleSimulator::GetStatus(Stream &strm) {
75 #if defined(__APPLE__)
76   // This will get called by subclasses, so just output status on the current
77   // simulator
78   PlatformAppleSimulator::LoadCoreSimulator();
79 
80   std::string developer_dir = GetXcodeDeveloperDirectory().GetPath();
81   CoreSimulatorSupport::DeviceSet devices =
82       CoreSimulatorSupport::DeviceSet::GetAvailableDevices(
83           developer_dir.c_str());
84   const size_t num_devices = devices.GetNumDevices();
85   if (num_devices) {
86     strm.Printf("Available devices:\n");
87     for (size_t i = 0; i < num_devices; ++i) {
88       CoreSimulatorSupport::Device device = devices.GetDeviceAtIndex(i);
89       strm.Printf("   %s: %s\n", device.GetUDID().c_str(),
90                   device.GetName().c_str());
91     }
92 
93     if (m_device.hasValue() && m_device->operator bool()) {
94       strm.Printf("Current device: %s: %s", m_device->GetUDID().c_str(),
95                   m_device->GetName().c_str());
96       if (m_device->GetState() == CoreSimulatorSupport::Device::State::Booted) {
97         strm.Printf(" state = booted");
98       }
99       strm.Printf("\nType \"platform connect <ARG>\" where <ARG> is a device "
100                   "UDID or a device name to disconnect and connect to a "
101                   "different device.\n");
102 
103     } else {
104       strm.Printf("No current device is selected, \"platform connect <ARG>\" "
105                   "where <ARG> is a device UDID or a device name to connect to "
106                   "a specific device.\n");
107     }
108 
109   } else {
110     strm.Printf("No devices are available.\n");
111   }
112 #else
113   strm.Printf(UNSUPPORTED_ERROR);
114 #endif
115 }
116 
117 Status PlatformAppleSimulator::ConnectRemote(Args &args) {
118 #if defined(__APPLE__)
119   Status error;
120   if (args.GetArgumentCount() == 1) {
121     if (m_device)
122       DisconnectRemote();
123     PlatformAppleSimulator::LoadCoreSimulator();
124     const char *arg_cstr = args.GetArgumentAtIndex(0);
125     if (arg_cstr) {
126       std::string arg_str(arg_cstr);
127       std::string developer_dir = GetXcodeDeveloperDirectory().GetPath();
128       CoreSimulatorSupport::DeviceSet devices =
129           CoreSimulatorSupport::DeviceSet::GetAvailableDevices(
130               developer_dir.c_str());
131       devices.ForEach(
132           [this, &arg_str](const CoreSimulatorSupport::Device &device) -> bool {
133             if (arg_str == device.GetUDID() || arg_str == device.GetName()) {
134               m_device = device;
135               return false; // Stop iterating
136             } else {
137               return true; // Keep iterating
138             }
139           });
140       if (!m_device)
141         error.SetErrorStringWithFormat(
142             "no device with UDID or name '%s' was found", arg_cstr);
143     }
144   } else {
145     error.SetErrorString("this command take a single UDID argument of the "
146                          "device you want to connect to.");
147   }
148   return error;
149 #else
150   Status err;
151   err.SetErrorString(UNSUPPORTED_ERROR);
152   return err;
153 #endif
154 }
155 
156 Status PlatformAppleSimulator::DisconnectRemote() {
157 #if defined(__APPLE__)
158   m_device.reset();
159   return Status();
160 #else
161   Status err;
162   err.SetErrorString(UNSUPPORTED_ERROR);
163   return err;
164 #endif
165 }
166 
167 lldb::ProcessSP PlatformAppleSimulator::DebugProcess(
168     ProcessLaunchInfo &launch_info, Debugger &debugger,
169     Target *target, // Can be NULL, if NULL create a new target, else use
170                     // existing one
171     Status &error) {
172 #if defined(__APPLE__)
173   ProcessSP process_sp;
174   // Make sure we stop at the entry point
175   launch_info.GetFlags().Set(eLaunchFlagDebug);
176   // We always launch the process we are going to debug in a separate process
177   // group, since then we can handle ^C interrupts ourselves w/o having to
178   // worry about the target getting them as well.
179   launch_info.SetLaunchInSeparateProcessGroup(true);
180 
181   error = LaunchProcess(launch_info);
182   if (error.Success()) {
183     if (launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) {
184       ProcessAttachInfo attach_info(launch_info);
185       process_sp = Attach(attach_info, debugger, target, error);
186       if (process_sp) {
187         launch_info.SetHijackListener(attach_info.GetHijackListener());
188 
189         // Since we attached to the process, it will think it needs to detach
190         // if the process object just goes away without an explicit call to
191         // Process::Kill() or Process::Detach(), so let it know to kill the
192         // process if this happens.
193         process_sp->SetShouldDetach(false);
194 
195         // If we didn't have any file actions, the pseudo terminal might have
196         // been used where the slave side was given as the file to open for
197         // stdin/out/err after we have already opened the master so we can
198         // read/write stdin/out/err.
199         int pty_fd = launch_info.GetPTY().ReleaseMasterFileDescriptor();
200         if (pty_fd != PseudoTerminal::invalid_fd) {
201           process_sp->SetSTDIOFileDescriptor(pty_fd);
202         }
203       }
204     }
205   }
206 
207   return process_sp;
208 #else
209   return ProcessSP();
210 #endif
211 }
212 
213 FileSpec PlatformAppleSimulator::GetCoreSimulatorPath() {
214 #if defined(__APPLE__)
215   std::lock_guard<std::mutex> guard(m_core_sim_path_mutex);
216   if (!m_core_simulator_framework_path.hasValue()) {
217     if (FileSpec fspec = GetXcodeDeveloperDirectory()) {
218       std::string developer_dir = fspec.GetPath();
219       StreamString cs_path;
220       cs_path.Printf(
221           "%s/Library/PrivateFrameworks/CoreSimulator.framework/CoreSimulator",
222           developer_dir.c_str());
223       m_core_simulator_framework_path = FileSpec(cs_path.GetData());
224       FileSystem::Instance().Resolve(*m_core_simulator_framework_path);
225     }
226   }
227 
228   return m_core_simulator_framework_path.getValue();
229 #else
230   return FileSpec();
231 #endif
232 }
233 
234 void PlatformAppleSimulator::LoadCoreSimulator() {
235 #if defined(__APPLE__)
236   static llvm::once_flag g_load_core_sim_flag;
237   llvm::call_once(g_load_core_sim_flag, [this] {
238     const std::string core_sim_path(GetCoreSimulatorPath().GetPath());
239     if (core_sim_path.size())
240       dlopen(core_sim_path.c_str(), RTLD_LAZY);
241   });
242 #endif
243 }
244 
245 #if defined(__APPLE__)
246 CoreSimulatorSupport::Device PlatformAppleSimulator::GetSimulatorDevice() {
247   if (!m_device.hasValue()) {
248     const CoreSimulatorSupport::DeviceType::ProductFamilyID dev_id =
249         CoreSimulatorSupport::DeviceType::ProductFamilyID::iPhone;
250     std::string developer_dir = GetXcodeDeveloperDirectory().GetPath();
251     m_device = CoreSimulatorSupport::DeviceSet::GetAvailableDevices(
252                    developer_dir.c_str())
253                    .GetFanciest(dev_id);
254   }
255 
256   if (m_device.hasValue())
257     return m_device.getValue();
258   else
259     return CoreSimulatorSupport::Device();
260 }
261 #endif
262