CwlCatchBadInstruction.swift 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. //
  2. // CwlCatchBadInstruction.swift
  3. // CwlPreconditionTesting
  4. //
  5. // Created by Matt Gallagher on 2016/01/10.
  6. // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved.
  7. //
  8. // Permission to use, copy, modify, and/or distribute this software for any
  9. // purpose with or without fee is hereby granted, provided that the above
  10. // copyright notice and this permission notice appear in all copies.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  13. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  14. // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  15. // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  16. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  17. // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
  18. // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  19. //
  20. #if (os(macOS) || os(iOS)) && arch(x86_64)
  21. import Foundation
  22. import Swift
  23. #if canImport(NimbleCwlCatchException) && canImport(NimbleCwlMachBadInstructionHandler)
  24. import NimbleCwlCatchException
  25. import NimbleCwlMachBadInstructionHandler
  26. #endif
  27. private enum PthreadError: Error { case code(Int32) }
  28. private enum MachExcServer: Error { case code(kern_return_t) }
  29. /// A quick function for converting Mach error results into Swift errors
  30. private func kernCheck(_ f: () -> Int32) throws {
  31. let r = f()
  32. guard r == KERN_SUCCESS else {
  33. throw NSError(domain: NSMachErrorDomain, code: Int(r), userInfo: nil)
  34. }
  35. }
  36. extension request_mach_exception_raise_t {
  37. mutating func withMsgHeaderPointer<R>(in block: (UnsafeMutablePointer<mach_msg_header_t>) -> R) -> R {
  38. return withUnsafeMutablePointer(to: &self) { p -> R in
  39. return p.withMemoryRebound(to: mach_msg_header_t.self, capacity: 1) { ptr -> R in
  40. return block(ptr)
  41. }
  42. }
  43. }
  44. }
  45. extension reply_mach_exception_raise_state_t {
  46. mutating func withMsgHeaderPointer<R>(in block: (UnsafeMutablePointer<mach_msg_header_t>) -> R) -> R {
  47. return withUnsafeMutablePointer(to: &self) { p -> R in
  48. return p.withMemoryRebound(to: mach_msg_header_t.self, capacity: 1) { ptr -> R in
  49. return block(ptr)
  50. }
  51. }
  52. }
  53. }
  54. /// A structure used to store context associated with the Mach message port
  55. private struct MachContext {
  56. var masks = execTypesCountTuple<exception_mask_t>()
  57. var count: mach_msg_type_number_t = 0
  58. var ports = execTypesCountTuple<mach_port_t>()
  59. var behaviors = execTypesCountTuple<exception_behavior_t>()
  60. var flavors = execTypesCountTuple<thread_state_flavor_t>()
  61. var currentExceptionPort: mach_port_t = 0
  62. var handlerThread: pthread_t? = nil
  63. static func internalMutablePointers<R>(_ m: UnsafeMutablePointer<execTypesCountTuple<exception_mask_t>>, _ c: UnsafeMutablePointer<mach_msg_type_number_t>, _ p: UnsafeMutablePointer<execTypesCountTuple<mach_port_t>>, _ b: UnsafeMutablePointer<execTypesCountTuple<exception_behavior_t>>, _ f: UnsafeMutablePointer<execTypesCountTuple<thread_state_flavor_t>>, _ block: (UnsafeMutablePointer<exception_mask_t>, UnsafeMutablePointer<mach_msg_type_number_t>, UnsafeMutablePointer<mach_port_t>, UnsafeMutablePointer<exception_behavior_t>, UnsafeMutablePointer<thread_state_flavor_t>) -> R) -> R {
  64. return m.withMemoryRebound(to: exception_mask_t.self, capacity: 1) { masksPtr in
  65. return c.withMemoryRebound(to: mach_msg_type_number_t.self, capacity: 1) { countPtr in
  66. return p.withMemoryRebound(to: mach_port_t.self, capacity: 1) { portsPtr in
  67. return b.withMemoryRebound(to: exception_behavior_t.self, capacity: 1) { behaviorsPtr in
  68. return f.withMemoryRebound(to: thread_state_flavor_t.self, capacity: 1) { flavorsPtr in
  69. return block(masksPtr, countPtr, portsPtr, behaviorsPtr, flavorsPtr)
  70. }
  71. }
  72. }
  73. }
  74. }
  75. }
  76. mutating func withUnsafeMutablePointers<R>(in block: @escaping (UnsafeMutablePointer<exception_mask_t>, UnsafeMutablePointer<mach_msg_type_number_t>, UnsafeMutablePointer<mach_port_t>, UnsafeMutablePointer<exception_behavior_t>, UnsafeMutablePointer<thread_state_flavor_t>) -> R) -> R {
  77. return MachContext.internalMutablePointers(&masks, &count, &ports, &behaviors, &flavors, block)
  78. }
  79. }
  80. /// A function for receiving mach messages and parsing the first with mach_exc_server (and if any others are received, throwing them away).
  81. private func machMessageHandler(_ arg: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? {
  82. let context = arg.assumingMemoryBound(to: MachContext.self).pointee
  83. var request = request_mach_exception_raise_t()
  84. var reply = reply_mach_exception_raise_state_t()
  85. var handledfirstException = false
  86. repeat { do {
  87. // Request the next mach message from the port
  88. request.Head.msgh_local_port = context.currentExceptionPort
  89. request.Head.msgh_size = UInt32(MemoryLayout<request_mach_exception_raise_t>.size)
  90. let requestSize = request.Head.msgh_size
  91. try kernCheck { request.withMsgHeaderPointer { requestPtr in
  92. mach_msg(requestPtr, MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0, requestSize, context.currentExceptionPort, 0, UInt32(MACH_PORT_NULL))
  93. } }
  94. // Prepare the reply structure
  95. reply.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.Head.msgh_bits), 0)
  96. reply.Head.msgh_local_port = UInt32(MACH_PORT_NULL)
  97. reply.Head.msgh_remote_port = request.Head.msgh_remote_port
  98. reply.Head.msgh_size = UInt32(MemoryLayout<reply_mach_exception_raise_state_t>.size)
  99. reply.NDR = NDR_record
  100. if !handledfirstException {
  101. // Use the MiG generated server to invoke our handler for the request and fill in the rest of the reply structure
  102. guard request.withMsgHeaderPointer(in: { requestPtr in reply.withMsgHeaderPointer { replyPtr in
  103. mach_exc_server(requestPtr, replyPtr)
  104. } }) != 0 else { throw MachExcServer.code(reply.RetCode) }
  105. handledfirstException = true
  106. } else {
  107. // If multiple fatal errors occur, don't handle subsquent errors (let the program crash)
  108. reply.RetCode = KERN_FAILURE
  109. }
  110. // Send the reply
  111. let replySize = reply.Head.msgh_size
  112. try kernCheck { reply.withMsgHeaderPointer { replyPtr in
  113. mach_msg(replyPtr, MACH_SEND_MSG, replySize, 0, UInt32(MACH_PORT_NULL), 0, UInt32(MACH_PORT_NULL))
  114. } }
  115. } catch let error as NSError where (error.domain == NSMachErrorDomain && (error.code == Int(MACH_RCV_PORT_CHANGED) || error.code == Int(MACH_RCV_INVALID_NAME))) {
  116. // Port was already closed before we started or closed while we were listening.
  117. // This means the controlling thread shut down.
  118. return nil
  119. } catch {
  120. // Should never be reached but this is testing code, don't try to recover, just abort
  121. fatalError("Mach message error: \(error)")
  122. } } while true
  123. }
  124. /// Run the provided block. If a mach "BAD_INSTRUCTION" exception is raised, catch it and return a BadInstructionException (which captures stack information about the throw site, if desired). Otherwise return nil.
  125. /// NOTE: This function is only intended for use in test harnesses – use in a distributed build is almost certainly a bad choice. If a "BAD_INSTRUCTION" exception is raised, the block will be exited before completion via Objective-C exception. The risks associated with an Objective-C exception apply here: most Swift/Objective-C functions are *not* exception-safe. Memory may be leaked and the program will not necessarily be left in a safe state.
  126. /// - parameter block: a function without parameters that will be run
  127. /// - returns: if an EXC_BAD_INSTRUCTION is raised during the execution of `block` then a BadInstructionException will be returned, otherwise `nil`.
  128. public func catchBadInstruction(in block: @escaping () -> Void) -> BadInstructionException? {
  129. // Suppress Swift runtime's direct triggering of the debugger and exclusivity checking which crashes when we throw past it
  130. let previousExclusivity = _swift_disableExclusivityChecking
  131. let previousReporting = _swift_reportFatalErrorsToDebugger
  132. _swift_disableExclusivityChecking = true
  133. _swift_reportFatalErrorsToDebugger = false
  134. defer {
  135. _swift_reportFatalErrorsToDebugger = previousReporting
  136. _swift_disableExclusivityChecking = previousExclusivity
  137. }
  138. var context = MachContext()
  139. var result: BadInstructionException? = nil
  140. do {
  141. var handlerThread: pthread_t? = nil
  142. defer {
  143. // 8. Wait for the thread to terminate *if* we actually made it to the creation point
  144. // The mach port should be destroyed *before* calling pthread_join to avoid a deadlock.
  145. if handlerThread != nil {
  146. pthread_join(handlerThread!, nil)
  147. }
  148. }
  149. try kernCheck {
  150. // 1. Create the mach port
  151. mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &context.currentExceptionPort)
  152. }
  153. defer {
  154. // 7. Cleanup the mach port
  155. mach_port_destroy(mach_task_self_, context.currentExceptionPort)
  156. }
  157. try kernCheck {
  158. // 2. Configure the mach port
  159. mach_port_insert_right(mach_task_self_, context.currentExceptionPort, context.currentExceptionPort, MACH_MSG_TYPE_MAKE_SEND)
  160. }
  161. let currentExceptionPtr = context.currentExceptionPort
  162. try kernCheck { context.withUnsafeMutablePointers { masksPtr, countPtr, portsPtr, behaviorsPtr, flavorsPtr in
  163. // 3. Apply the mach port as the handler for this thread
  164. thread_swap_exception_ports(mach_thread_self(), EXC_MASK_BAD_INSTRUCTION, currentExceptionPtr, Int32(bitPattern: UInt32(EXCEPTION_STATE) | MACH_EXCEPTION_CODES), x86_THREAD_STATE64, masksPtr, countPtr, portsPtr, behaviorsPtr, flavorsPtr)
  165. } }
  166. defer { context.withUnsafeMutablePointers { masksPtr, countPtr, portsPtr, behaviorsPtr, flavorsPtr in
  167. // 6. Unapply the mach port
  168. _ = thread_swap_exception_ports(mach_thread_self(), EXC_MASK_BAD_INSTRUCTION, 0, EXCEPTION_DEFAULT, THREAD_STATE_NONE, masksPtr, countPtr, portsPtr, behaviorsPtr, flavorsPtr)
  169. } }
  170. try withUnsafeMutablePointer(to: &context) { c throws in
  171. // 4. Create the thread
  172. let e = pthread_create(&handlerThread, nil, machMessageHandler, c)
  173. guard e == 0 else { throw PthreadError.code(e) }
  174. // 5. Run the block
  175. result = BadInstructionException.catchException(in: block)
  176. }
  177. } catch {
  178. // Should never be reached but this is testing code, don't try to recover, just abort
  179. fatalError("Mach port error: \(error)")
  180. }
  181. return result
  182. }
  183. #endif