1//===-- HostInfoMacOSX.mm ---------------------------------------*- C++ -*-===// 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 "lldb/Host/macosx/HostInfoMacOSX.h" 10#include "lldb/Host/FileSystem.h" 11#include "lldb/Host/Host.h" 12#include "lldb/Host/HostInfo.h" 13#include "lldb/Utility/Args.h" 14#include "lldb/Utility/Log.h" 15 16#include "llvm/ADT/SmallString.h" 17#include "llvm/Support/FileSystem.h" 18#include "llvm/Support/Path.h" 19#include "llvm/Support/raw_ostream.h" 20 21// C++ Includes 22#include <string> 23 24// C inclues 25#include <stdlib.h> 26#include <sys/sysctl.h> 27#include <sys/syslimits.h> 28#include <sys/types.h> 29 30// Objective-C/C++ includes 31#include <CoreFoundation/CoreFoundation.h> 32#include <Foundation/Foundation.h> 33#include <mach-o/dyld.h> 34#include <objc/objc-auto.h> 35 36// These are needed when compiling on systems 37// that do not yet have these definitions 38#include <AvailabilityMacros.h> 39#ifndef CPU_SUBTYPE_X86_64_H 40#define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8) 41#endif 42#ifndef CPU_TYPE_ARM64 43#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) 44#endif 45 46#ifndef CPU_TYPE_ARM64_32 47#define CPU_ARCH_ABI64_32 0x02000000 48#define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) 49#endif 50 51#include <TargetConditionals.h> // for TARGET_OS_TV, TARGET_OS_WATCH 52 53using namespace lldb_private; 54 55bool HostInfoMacOSX::GetOSBuildString(std::string &s) { 56 int mib[2] = {CTL_KERN, KERN_OSVERSION}; 57 char cstr[PATH_MAX]; 58 size_t cstr_len = sizeof(cstr); 59 if (::sysctl(mib, 2, cstr, &cstr_len, NULL, 0) == 0) { 60 s.assign(cstr, cstr_len); 61 return true; 62 } 63 64 s.clear(); 65 return false; 66} 67 68bool HostInfoMacOSX::GetOSKernelDescription(std::string &s) { 69 int mib[2] = {CTL_KERN, KERN_VERSION}; 70 char cstr[PATH_MAX]; 71 size_t cstr_len = sizeof(cstr); 72 if (::sysctl(mib, 2, cstr, &cstr_len, NULL, 0) == 0) { 73 s.assign(cstr, cstr_len); 74 return true; 75 } 76 s.clear(); 77 return false; 78} 79 80static void ParseOSVersion(llvm::VersionTuple &version, NSString *Key) { 81 @autoreleasepool { 82 NSDictionary *version_info = 83 [NSDictionary dictionaryWithContentsOfFile: 84 @"/System/Library/CoreServices/SystemVersion.plist"]; 85 NSString *version_value = [version_info objectForKey: Key]; 86 const char *version_str = [version_value UTF8String]; 87 version.tryParse(version_str); 88 } 89} 90 91llvm::VersionTuple HostInfoMacOSX::GetOSVersion() { 92 static llvm::VersionTuple g_version; 93 if (g_version.empty()) 94 ParseOSVersion(g_version, @"ProductVersion"); 95 return g_version; 96} 97 98llvm::VersionTuple HostInfoMacOSX::GetMacCatalystVersion() { 99 static llvm::VersionTuple g_version; 100 if (g_version.empty()) 101 ParseOSVersion(g_version, @"iOSSupportVersion"); 102 return g_version; 103} 104 105 106FileSpec HostInfoMacOSX::GetProgramFileSpec() { 107 static FileSpec g_program_filespec; 108 if (!g_program_filespec) { 109 char program_fullpath[PATH_MAX]; 110 // If DST is NULL, then return the number of bytes needed. 111 uint32_t len = sizeof(program_fullpath); 112 int err = _NSGetExecutablePath(program_fullpath, &len); 113 if (err == 0) 114 g_program_filespec.SetFile(program_fullpath, FileSpec::Style::native); 115 else if (err == -1) { 116 char *large_program_fullpath = (char *)::malloc(len + 1); 117 118 err = _NSGetExecutablePath(large_program_fullpath, &len); 119 if (err == 0) 120 g_program_filespec.SetFile(large_program_fullpath, 121 FileSpec::Style::native); 122 123 ::free(large_program_fullpath); 124 } 125 } 126 return g_program_filespec; 127} 128 129bool HostInfoMacOSX::ComputeSupportExeDirectory(FileSpec &file_spec) { 130 FileSpec lldb_file_spec = GetShlibDir(); 131 if (!lldb_file_spec) 132 return false; 133 134 std::string raw_path = lldb_file_spec.GetPath(); 135 136 size_t framework_pos = raw_path.find("LLDB.framework"); 137 if (framework_pos != std::string::npos) { 138 framework_pos += strlen("LLDB.framework"); 139#if TARGET_OS_EMBEDDED 140 // Shallow bundle 141 raw_path.resize(framework_pos); 142#else 143 // Normal bundle 144 raw_path.resize(framework_pos); 145 raw_path.append("/Resources"); 146#endif 147 } else { 148 // Find the bin path relative to the lib path where the cmake-based 149 // OS X .dylib lives. This is not going to work if the bin and lib 150 // dir are not both in the same dir. 151 // 152 // It is not going to work to do it by the executable path either, 153 // as in the case of a python script, the executable is python, not 154 // the lldb driver. 155 raw_path.append("/../bin"); 156 FileSpec support_dir_spec(raw_path); 157 FileSystem::Instance().Resolve(support_dir_spec); 158 if (!FileSystem::Instance().IsDirectory(support_dir_spec)) { 159 Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); 160 LLDB_LOGF(log, "HostInfoMacOSX::%s(): failed to find support directory", 161 __FUNCTION__); 162 return false; 163 } 164 165 // Get normalization from support_dir_spec. Note the FileSpec resolve 166 // does not remove '..' in the path. 167 char *const dir_realpath = 168 realpath(support_dir_spec.GetPath().c_str(), NULL); 169 if (dir_realpath) { 170 raw_path = dir_realpath; 171 free(dir_realpath); 172 } else { 173 raw_path = support_dir_spec.GetPath(); 174 } 175 } 176 177 file_spec.GetDirectory().SetString( 178 llvm::StringRef(raw_path.c_str(), raw_path.size())); 179 return (bool)file_spec.GetDirectory(); 180} 181 182bool HostInfoMacOSX::ComputeHeaderDirectory(FileSpec &file_spec) { 183 FileSpec lldb_file_spec = GetShlibDir(); 184 if (!lldb_file_spec) 185 return false; 186 187 std::string raw_path = lldb_file_spec.GetPath(); 188 189 size_t framework_pos = raw_path.find("LLDB.framework"); 190 if (framework_pos != std::string::npos) { 191 framework_pos += strlen("LLDB.framework"); 192 raw_path.resize(framework_pos); 193 raw_path.append("/Headers"); 194 } 195 file_spec.GetDirectory().SetString( 196 llvm::StringRef(raw_path.c_str(), raw_path.size())); 197 return true; 198} 199 200bool HostInfoMacOSX::ComputeSystemPluginsDirectory(FileSpec &file_spec) { 201 FileSpec lldb_file_spec = GetShlibDir(); 202 if (!lldb_file_spec) 203 return false; 204 205 std::string raw_path = lldb_file_spec.GetPath(); 206 207 size_t framework_pos = raw_path.find("LLDB.framework"); 208 if (framework_pos == std::string::npos) 209 return false; 210 211 framework_pos += strlen("LLDB.framework"); 212 raw_path.resize(framework_pos); 213 raw_path.append("/Resources/PlugIns"); 214 file_spec.GetDirectory().SetString( 215 llvm::StringRef(raw_path.c_str(), raw_path.size())); 216 return true; 217} 218 219bool HostInfoMacOSX::ComputeUserPluginsDirectory(FileSpec &file_spec) { 220 FileSpec temp_file("~/Library/Application Support/LLDB/PlugIns"); 221 FileSystem::Instance().Resolve(temp_file); 222 file_spec.GetDirectory().SetCString(temp_file.GetPath().c_str()); 223 return true; 224} 225 226void HostInfoMacOSX::ComputeHostArchitectureSupport(ArchSpec &arch_32, 227 ArchSpec &arch_64) { 228 // All apple systems support 32 bit execution. 229 uint32_t cputype, cpusubtype; 230 uint32_t is_64_bit_capable = false; 231 size_t len = sizeof(cputype); 232 ArchSpec host_arch; 233 // These will tell us about the kernel architecture, which even on a 64 234 // bit machine can be 32 bit... 235 if (::sysctlbyname("hw.cputype", &cputype, &len, NULL, 0) == 0) { 236 len = sizeof(cpusubtype); 237 if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) != 0) 238 cpusubtype = CPU_TYPE_ANY; 239 240 len = sizeof(is_64_bit_capable); 241 ::sysctlbyname("hw.cpu64bit_capable", &is_64_bit_capable, &len, NULL, 0); 242 243 if (is_64_bit_capable) { 244 if (cputype & CPU_ARCH_ABI64) { 245 // We have a 64 bit kernel on a 64 bit system 246 arch_64.SetArchitecture(eArchTypeMachO, cputype, cpusubtype); 247 } else { 248 // We have a 64 bit kernel that is returning a 32 bit cputype, the 249 // cpusubtype will be correct as if it were for a 64 bit architecture 250 arch_64.SetArchitecture(eArchTypeMachO, cputype | CPU_ARCH_ABI64, 251 cpusubtype); 252 } 253 254 // Now we need modify the cpusubtype for the 32 bit slices. 255 uint32_t cpusubtype32 = cpusubtype; 256#if defined(__i386__) || defined(__x86_64__) 257 if (cpusubtype == CPU_SUBTYPE_486 || cpusubtype == CPU_SUBTYPE_X86_64_H) 258 cpusubtype32 = CPU_SUBTYPE_I386_ALL; 259#elif defined(__arm__) || defined(__arm64__) || defined(__aarch64__) 260 if (cputype == CPU_TYPE_ARM || cputype == CPU_TYPE_ARM64) 261 cpusubtype32 = CPU_SUBTYPE_ARM_V7S; 262#endif 263 arch_32.SetArchitecture(eArchTypeMachO, cputype & ~(CPU_ARCH_MASK), 264 cpusubtype32); 265 266 if (cputype == CPU_TYPE_ARM || 267 cputype == CPU_TYPE_ARM64 || 268 cputype == CPU_TYPE_ARM64_32) { 269// When running on a watch or tv, report the host os correctly 270#if defined(TARGET_OS_TV) && TARGET_OS_TV == 1 271 arch_32.GetTriple().setOS(llvm::Triple::TvOS); 272 arch_64.GetTriple().setOS(llvm::Triple::TvOS); 273#elif defined(TARGET_OS_BRIDGE) && TARGET_OS_BRIDGE == 1 274 arch_32.GetTriple().setOS(llvm::Triple::BridgeOS); 275 arch_64.GetTriple().setOS(llvm::Triple::BridgeOS); 276#elif defined(TARGET_OS_WATCHOS) && TARGET_OS_WATCHOS == 1 277 arch_32.GetTriple().setOS(llvm::Triple::WatchOS); 278 arch_64.GetTriple().setOS(llvm::Triple::WatchOS); 279#elif defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1 280 arch_32.GetTriple().setOS(llvm::Triple::MacOSX); 281 arch_64.GetTriple().setOS(llvm::Triple::MacOSX); 282#else 283 arch_32.GetTriple().setOS(llvm::Triple::IOS); 284 arch_64.GetTriple().setOS(llvm::Triple::IOS); 285#endif 286 } else { 287 arch_32.GetTriple().setOS(llvm::Triple::MacOSX); 288 arch_64.GetTriple().setOS(llvm::Triple::MacOSX); 289 } 290 } else { 291 // We have a 32 bit kernel on a 32 bit system 292 arch_32.SetArchitecture(eArchTypeMachO, cputype, cpusubtype); 293#if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 294 arch_32.GetTriple().setOS(llvm::Triple::WatchOS); 295#else 296 arch_32.GetTriple().setOS(llvm::Triple::IOS); 297#endif 298 arch_64.Clear(); 299 } 300 } 301} 302 303/// Return and cache $DEVELOPER_DIR if it is set and exists. 304static std::string GetEnvDeveloperDir() { 305 static std::string g_env_developer_dir; 306 static std::once_flag g_once_flag; 307 std::call_once(g_once_flag, [&]() { 308 if (const char *developer_dir_env_var = getenv("DEVELOPER_DIR")) { 309 FileSpec fspec(developer_dir_env_var); 310 if (FileSystem::Instance().Exists(fspec)) 311 g_env_developer_dir = fspec.GetPath(); 312 }}); 313 return g_env_developer_dir; 314} 315 316FileSpec HostInfoMacOSX::GetXcodeContentsDirectory() { 317 static FileSpec g_xcode_contents_path; 318 static std::once_flag g_once_flag; 319 std::call_once(g_once_flag, [&]() { 320 // Try the shlib dir first. 321 if (FileSpec fspec = HostInfo::GetShlibDir()) { 322 if (FileSystem::Instance().Exists(fspec)) { 323 std::string xcode_contents_dir = 324 XcodeSDK::FindXcodeContentsDirectoryInPath(fspec.GetPath()); 325 if (!xcode_contents_dir.empty()) { 326 g_xcode_contents_path = FileSpec(xcode_contents_dir); 327 return; 328 } 329 } 330 } 331 332 llvm::SmallString<128> env_developer_dir(GetEnvDeveloperDir()); 333 if (!env_developer_dir.empty()) { 334 llvm::sys::path::append(env_developer_dir, "Contents"); 335 std::string xcode_contents_dir = 336 XcodeSDK::FindXcodeContentsDirectoryInPath(env_developer_dir); 337 if (!xcode_contents_dir.empty()) { 338 g_xcode_contents_path = FileSpec(xcode_contents_dir); 339 return; 340 } 341 } 342 343 FileSpec fspec(HostInfo::GetXcodeSDKPath(XcodeSDK::GetAnyMacOS())); 344 if (fspec) { 345 if (FileSystem::Instance().Exists(fspec)) { 346 std::string xcode_contents_dir = 347 XcodeSDK::FindXcodeContentsDirectoryInPath(fspec.GetPath()); 348 if (!xcode_contents_dir.empty()) { 349 g_xcode_contents_path = FileSpec(xcode_contents_dir); 350 return; 351 } 352 } 353 } 354 }); 355 return g_xcode_contents_path; 356} 357 358lldb_private::FileSpec HostInfoMacOSX::GetXcodeDeveloperDirectory() { 359 static lldb_private::FileSpec g_developer_directory; 360 static llvm::once_flag g_once_flag; 361 llvm::call_once(g_once_flag, []() { 362 if (FileSpec fspec = GetXcodeContentsDirectory()) { 363 fspec.AppendPathComponent("Developer"); 364 if (FileSystem::Instance().Exists(fspec)) 365 g_developer_directory = fspec; 366 } 367 }); 368 return g_developer_directory; 369} 370 371static std::string GetXcodeSDK(XcodeSDK sdk) { 372 XcodeSDK::Info info = sdk.Parse(); 373 std::string sdk_name = XcodeSDK::GetCanonicalName(info); 374 auto find_sdk = [](std::string sdk_name) -> std::string { 375 std::string xcrun_cmd; 376 std::string developer_dir = GetEnvDeveloperDir(); 377 if (developer_dir.empty()) 378 if (FileSpec fspec = HostInfo::GetShlibDir()) 379 if (FileSystem::Instance().Exists(fspec)) { 380 FileSpec path( 381 XcodeSDK::FindXcodeContentsDirectoryInPath(fspec.GetPath())); 382 if (path.RemoveLastPathComponent()) 383 developer_dir = path.GetPath(); 384 } 385 if (!developer_dir.empty()) 386 xcrun_cmd = "/usr/bin/env DEVELOPER_DIR=\"" + developer_dir + "\" "; 387 xcrun_cmd += "xcrun --show-sdk-path --sdk " + sdk_name; 388 389 int status = 0; 390 int signo = 0; 391 std::string output_str; 392 lldb_private::Status error = 393 Host::RunShellCommand(xcrun_cmd.c_str(), FileSpec(), &status, &signo, 394 &output_str, std::chrono::seconds(15)); 395 396 // Check that xcrun return something useful. 397 if (status != 0 || output_str.empty()) 398 return {}; 399 400 // Convert to a StringRef so we can manipulate the string without modifying 401 // the underlying data. 402 llvm::StringRef output(output_str); 403 404 // Remove any trailing newline characters. 405 output = output.rtrim(); 406 407 // Strip any leading newline characters and everything before them. 408 const size_t last_newline = output.rfind('\n'); 409 if (last_newline != llvm::StringRef::npos) 410 output = output.substr(last_newline + 1); 411 412 return output.str(); 413 }; 414 415 std::string path = find_sdk(sdk_name); 416 while (path.empty()) { 417 // Try an alternate spelling of the name ("macosx10.9internal"). 418 if (info.type == XcodeSDK::Type::MacOSX && !info.version.empty() && 419 info.internal) { 420 llvm::StringRef fixed(sdk_name); 421 if (fixed.consume_back(".internal")) 422 sdk_name = fixed.str() + "internal"; 423 path = find_sdk(sdk_name); 424 if (!path.empty()) 425 break; 426 } 427 Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); 428 LLDB_LOGF(log, "Couldn't find SDK %s on host", sdk_name.c_str()); 429 430 // Try without the version. 431 if (!info.version.empty()) { 432 info.version = {}; 433 sdk_name = XcodeSDK::GetCanonicalName(info); 434 path = find_sdk(sdk_name); 435 if (!path.empty()) 436 break; 437 } 438 439 LLDB_LOGF(log, "Couldn't find any matching SDK on host"); 440 return {}; 441 } 442 443 // Whatever is left in output should be a valid path. 444 if (!FileSystem::Instance().Exists(path)) 445 return {}; 446 return path; 447} 448 449llvm::StringRef HostInfoMacOSX::GetXcodeSDKPath(XcodeSDK sdk) { 450 static llvm::StringMap<std::string> g_sdk_path; 451 static std::mutex g_sdk_path_mutex; 452 453 std::lock_guard<std::mutex> guard(g_sdk_path_mutex); 454 auto it = g_sdk_path.find(sdk.GetString()); 455 if (it != g_sdk_path.end()) 456 return it->second; 457 auto it_new = g_sdk_path.insert({sdk.GetString(), GetXcodeSDK(sdk)}); 458 return it_new.first->second; 459} 460