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   CoreSimulatorSupport::DeviceSet devices =
81       CoreSimulatorSupport::DeviceSet::GetAvailableDevices(
82           GetDeveloperDirectory());
83   const size_t num_devices = devices.GetNumDevices();
84   if (num_devices) {
85     strm.Printf("Available devices:\n");
86     for (size_t i = 0; i < num_devices; ++i) {
87       CoreSimulatorSupport::Device device = devices.GetDeviceAtIndex(i);
88       strm.Printf("   %s: %s\n", device.GetUDID().c_str(),
89                   device.GetName().c_str());
90     }
91 
92     if (m_device.hasValue() && m_device->operator bool()) {
93       strm.Printf("Current device: %s: %s", m_device->GetUDID().c_str(),
94                   m_device->GetName().c_str());
95       if (m_device->GetState() == CoreSimulatorSupport::Device::State::Booted) {
96         strm.Printf(" state = booted");
97       }
98       strm.Printf("\nType \"platform connect <ARG>\" where <ARG> is a device "
99                   "UDID or a device name to disconnect and connect to a "
100                   "different device.\n");
101 
102     } else {
103       strm.Printf("No current device is selected, \"platform connect <ARG>\" "
104                   "where <ARG> is a device UDID or a device name to connect to "
105                   "a specific device.\n");
106     }
107 
108   } else {
109     strm.Printf("No devices are available.\n");
110   }
111 #else
112   strm.Printf(UNSUPPORTED_ERROR);
113 #endif
114 }
115 
116 Status PlatformAppleSimulator::ConnectRemote(Args &args) {
117 #if defined(__APPLE__)
118   Status error;
119   if (args.GetArgumentCount() == 1) {
120     if (m_device)
121       DisconnectRemote();
122     PlatformAppleSimulator::LoadCoreSimulator();
123     const char *arg_cstr = args.GetArgumentAtIndex(0);
124     if (arg_cstr) {
125       std::string arg_str(arg_cstr);
126       CoreSimulatorSupport::DeviceSet devices =
127           CoreSimulatorSupport::DeviceSet::GetAvailableDevices(
128               GetDeveloperDirectory());
129       devices.ForEach(
130           [this, &arg_str](const CoreSimulatorSupport::Device &device) -> bool {
131             if (arg_str == device.GetUDID() || arg_str == device.GetName()) {
132               m_device = device;
133               return false; // Stop iterating
134             } else {
135               return true; // Keep iterating
136             }
137           });
138       if (!m_device)
139         error.SetErrorStringWithFormat(
140             "no device with UDID or name '%s' was found", arg_cstr);
141     }
142   } else {
143     error.SetErrorString("this command take a single UDID argument of the "
144                          "device you want to connect to.");
145   }
146   return error;
147 #else
148   Status err;
149   err.SetErrorString(UNSUPPORTED_ERROR);
150   return err;
151 #endif
152 }
153 
154 Status PlatformAppleSimulator::DisconnectRemote() {
155 #if defined(__APPLE__)
156   m_device.reset();
157   return Status();
158 #else
159   Status err;
160   err.SetErrorString(UNSUPPORTED_ERROR);
161   return err;
162 #endif
163 }
164 
165 lldb::ProcessSP PlatformAppleSimulator::DebugProcess(
166     ProcessLaunchInfo &launch_info, Debugger &debugger,
167     Target *target, // Can be NULL, if NULL create a new target, else use
168                     // existing one
169     Status &error) {
170 #if defined(__APPLE__)
171   ProcessSP process_sp;
172   // Make sure we stop at the entry point
173   launch_info.GetFlags().Set(eLaunchFlagDebug);
174   // We always launch the process we are going to debug in a separate process
175   // group, since then we can handle ^C interrupts ourselves w/o having to
176   // worry about the target getting them as well.
177   launch_info.SetLaunchInSeparateProcessGroup(true);
178 
179   error = LaunchProcess(launch_info);
180   if (error.Success()) {
181     if (launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) {
182       ProcessAttachInfo attach_info(launch_info);
183       process_sp = Attach(attach_info, debugger, target, error);
184       if (process_sp) {
185         launch_info.SetHijackListener(attach_info.GetHijackListener());
186 
187         // Since we attached to the process, it will think it needs to detach
188         // if the process object just goes away without an explicit call to
189         // Process::Kill() or Process::Detach(), so let it know to kill the
190         // process if this happens.
191         process_sp->SetShouldDetach(false);
192 
193         // If we didn't have any file actions, the pseudo terminal might have
194         // been used where the slave side was given as the file to open for
195         // stdin/out/err after we have already opened the master so we can
196         // read/write stdin/out/err.
197         int pty_fd = launch_info.GetPTY().ReleaseMasterFileDescriptor();
198         if (pty_fd != PseudoTerminal::invalid_fd) {
199           process_sp->SetSTDIOFileDescriptor(pty_fd);
200         }
201       }
202     }
203   }
204 
205   return process_sp;
206 #else
207   return ProcessSP();
208 #endif
209 }
210 
211 FileSpec PlatformAppleSimulator::GetCoreSimulatorPath() {
212 #if defined(__APPLE__)
213   std::lock_guard<std::mutex> guard(m_core_sim_path_mutex);
214   if (!m_core_simulator_framework_path.hasValue()) {
215     const char *developer_dir = GetDeveloperDirectory();
216     if (developer_dir) {
217       StreamString cs_path;
218       cs_path.Printf(
219           "%s/Library/PrivateFrameworks/CoreSimulator.framework/CoreSimulator",
220           developer_dir);
221       m_core_simulator_framework_path = FileSpec(cs_path.GetData());
222       FileSystem::Instance().Resolve(*m_core_simulator_framework_path);
223     }
224   }
225 
226   return m_core_simulator_framework_path.getValue();
227 #else
228   return FileSpec();
229 #endif
230 }
231 
232 void PlatformAppleSimulator::LoadCoreSimulator() {
233 #if defined(__APPLE__)
234   static llvm::once_flag g_load_core_sim_flag;
235   llvm::call_once(g_load_core_sim_flag, [this] {
236     const std::string core_sim_path(GetCoreSimulatorPath().GetPath());
237     if (core_sim_path.size())
238       dlopen(core_sim_path.c_str(), RTLD_LAZY);
239   });
240 #endif
241 }
242 
243 #if defined(__APPLE__)
244 CoreSimulatorSupport::Device PlatformAppleSimulator::GetSimulatorDevice() {
245   if (!m_device.hasValue()) {
246     const CoreSimulatorSupport::DeviceType::ProductFamilyID dev_id =
247         CoreSimulatorSupport::DeviceType::ProductFamilyID::iPhone;
248     m_device = CoreSimulatorSupport::DeviceSet::GetAvailableDevices(
249                    GetDeveloperDirectory())
250                    .GetFanciest(dev_id);
251   }
252 
253   if (m_device.hasValue())
254     return m_device.getValue();
255   else
256     return CoreSimulatorSupport::Device();
257 }
258 #endif
259