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