//===--- TargetMacOS.swift - Represents a process we are inspecting -------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2022-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Defines `Target`, which represents the process we are inspecting. // This is the macOS version. // //===----------------------------------------------------------------------===// #if os(macOS) import Darwin import Darwin.Mach import Runtime @_spi(Internal) import Runtime @_spi(Contexts) import Runtime @_spi(MemoryReaders) import Runtime internal import BacktracingImpl.OS.Darwin internal import BacktracingImpl.Runtime #if arch(x86_64) typealias MContext = darwin_x86_64_mcontext #elseif arch(arm64) || arch(arm64_32) typealias MContext = darwin_arm64_mcontext #else #error("You need to define MContext for this architecture") #endif extension thread_extended_info { var pth_swiftName: String { withUnsafePointer(to: pth_name) { ptr in let len = strnlen(ptr, Int(MAXTHREADNAMESIZE)) return String(decoding: UnsafeRawBufferPointer(start: ptr, count: Int(len)), as: UTF8.self) } } } enum SomeBacktrace { case raw(Backtrace) case symbolicated(SymbolicatedBacktrace) } struct TargetThread { typealias ThreadID = UInt64 var id: ThreadID var context: HostContext? var name: String var backtrace: SomeBacktrace } class Target { typealias Address = UInt64 var pid: pid_t var name: String var signal: UInt64 var faultAddress: Address var crashingThread: TargetThread.ThreadID var task: task_t var images: ImageMap var threads: [TargetThread] = [] var crashingThreadNdx: Int = -1 var signalName: String { switch signal { case UInt64(SIGQUIT): return "SIGQUIT" case UInt64(SIGABRT): return "SIGABRT" case UInt64(SIGBUS): return "SIGBUS" case UInt64(SIGFPE): return "SIGFPE" case UInt64(SIGILL): return "SIGILL" case UInt64(SIGSEGV): return "SIGSEGV" case UInt64(SIGTRAP): return "SIGTRAP" default: return "\(signal)" } } var signalDescription: String { switch signal { case UInt64(SIGQUIT): return "Terminated" case UInt64(SIGABRT): return "Aborted" case UInt64(SIGBUS): return "Bus error" case UInt64(SIGFPE): return "Floating point exception" case UInt64(SIGILL): return "Illegal instruction" case UInt64(SIGSEGV): return "Bad pointer dereference" case UInt64(SIGTRAP): return "System trap" default: return "Signal \(signal)" } } var reader: RemoteMemoryReader var mcontext: MContext static func getTask(pid: pid_t) -> task_t? { var port: task_t = 0 let kr = task_read_for_pid(mach_task_self_, pid, &port) if kr != KERN_SUCCESS { return nil } return port } static func getProcessName(pid: pid_t) -> String { let buffer = UnsafeMutableBufferPointer.allocate(capacity: 4096) defer { buffer.deallocate() } let ret = proc_name(pid, buffer.baseAddress, UInt32(buffer.count)) if ret <= 0 { return "" } else { return String(decoding: buffer[0.. Bool { var flags = UInt32(0) guard csops(pid, UInt32(CS_OPS_STATUS), &flags, MemoryLayout.size) == 0 else { return true } if (flags & UInt32(CS_PLATFORM_BINARY | CS_PLATFORM_PATH | CS_RUNTIME)) != 0 { return true } return (flags & UInt32(CS_GET_TASK_ALLOW)) == 0 } init(crashInfoAddr: UInt64, limit: Int?, top: Int, cache: Bool, symbolicate: SwiftBacktrace.Symbolication) { pid = getppid() if Self.isPrivileged(pid: pid) { /* Exit silently in this case; either 1. We can't call csops(), because we're sandboxed, or 2. The target is a platform binary. If we get killed, that is also fine. */ exit(1) } // This will normally only succeed if the parent process has // the com.apple.security.get-task-allow privilege. That gets set // automatically if you're developing in Xcode; if you're developing // on the command line or using SwiftPM, you will need to code sign // your binary with that entitlement to get this to work. guard let parentTask = Self.getTask(pid: pid) else { exit(1) } task = parentTask reader = RemoteMemoryReader(task: task_t(task)) name = Self.getProcessName(pid: pid) let crashInfo: CrashInfo do { crashInfo = try reader.fetch(from: crashInfoAddr, as: CrashInfo.self) } catch { print("swift-backtrace: unable to fetch crash info.", to: &standardError) exit(1) } crashingThread = crashInfo.crashing_thread signal = crashInfo.signal faultAddress = crashInfo.fault_address guard let mctx: MContext = try? reader.fetch(from: crashInfo.mctx, as: MContext.self) else { print("swift-backtrace: unable to fetch mcontext.", to: &standardError) exit(1) } mcontext = mctx images = ImageMap.capture(for: task) fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate) } func fetchThreads( limit: Int?, top: Int, cache: Bool, symbolicate: SwiftBacktrace.Symbolication ) { var threadPorts: thread_act_array_t? = nil var threadCount: mach_msg_type_number_t = 0 let kr = task_threads(task, &threadPorts, &threadCount) if kr != KERN_SUCCESS { print("swift-backtrace: failed to enumerate threads - \(kr)", to: &standardError) exit(1) } guard let ports = threadPorts else { print("swift-backtrace: thread array is nil", to: &standardError) exit(1) } for ndx in 0.. ()) throws { #if os(macOS) return try withTemporaryDirectory(pattern: "/tmp/backtrace.XXXXXXXX") { tmpdir in let cmdfile = "\(tmpdir)/lldb.command" guard let fp = fopen(cmdfile, "wt") else { throw PosixError(errno: errno) } if fputs(""" #!/bin/bash clear echo "Once LLDB has attached, return to the other window and press any key" echo "" xcrun lldb --attach-pid \(pid) -o c """, fp) == EOF { throw PosixError(errno: errno) } if fclose(fp) != 0 { throw PosixError(errno: errno) } if chmod(cmdfile, S_IXUSR|S_IRUSR) != 0 { throw PosixError(errno: errno) } try spawn("/usr/bin/open", args: ["open", cmdfile]) body() } #else print(""" From another shell, please run lldb --attach-pid \(target.pid) -o c """) body() #endif } } private func mach_thread_info(_ thread: thread_t, _ flavor: CInt, _ result: inout T) -> kern_return_t { var count: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.stride / MemoryLayout.stride) return withUnsafeMutablePointer(to: &result) { ptr in ptr.withMemoryRebound(to: natural_t.self, capacity: Int(count)) { intPtr in return thread_info(thread, thread_flavor_t(flavor), intPtr, &count) } } } #endif // os(macOS)