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