diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e18a31..6375ab8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: jobs: full-build: - runs-on: macOS-latest + runs-on: macOS-13 steps: - name: Checkout uses: actions/checkout@v1 diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SplunkRumCrashReporting/SplunkRumCrashReporting/CrashReporting.swift b/SplunkRumCrashReporting/SplunkRumCrashReporting/CrashReporting.swift index d2f70fa..771dde1 100644 --- a/SplunkRumCrashReporting/SplunkRumCrashReporting/CrashReporting.swift +++ b/SplunkRumCrashReporting/SplunkRumCrashReporting/CrashReporting.swift @@ -24,6 +24,7 @@ let CrashReportingVersionString = "0.6.0" var TheCrashReporter: PLCrashReporter? private var customDataDictionary: [String: String] = [String: String]() +private var allUsedImageNames: Array = [] func initializeCrashReporting() { let startupSpan = buildTracer().spanBuilder(spanName: "SplunkRumCrashReporting").startSpan() @@ -39,11 +40,20 @@ func initializeCrashReporting() { return } let crashReporter = crashReporter_! - let success = crashReporter.enable() - SplunkRum.debugLog("PLCrashReporter enabled: "+success.description) - if !success { - startupSpan.setAttribute(key: "error.message", value: "Cannot enable PLCrashReporter") - return + + // Stop enable if debugger attached + var inDebugger = false + if isDebuggerAttached() { + startupSpan.setAttribute(key: "error.message", value: "Debugger present. Will not construct PLCrashReporter") + SplunkRum.debugLog("Debugger present. Will not enable PLCrashReporter") + inDebugger = true; + } + if inDebugger == false { + let success = crashReporter.enable() + SplunkRum.debugLog("PLCrashReporter enabled: "+success.description) + if !success { + startupSpan.setAttribute(key: "error.message", value: "Cannot enable PLCrashReporter") + } } TheCrashReporter = crashReporter updateCrashReportSessionId() @@ -58,6 +68,9 @@ func initializeCrashReporting() { } SplunkRum.debugLog("Had a pending crash report") do { + allUsedImageNames.removeAll() + let path = crashReporter.crashReportPath() + print(path as Any) let data = crashReporter.loadPendingCrashReportData() try loadPendingCrashReport(data) } catch { @@ -66,8 +79,8 @@ func initializeCrashReporting() { // yes, fall through to purge } crashReporter.purgePendingCrashReport() - } + private func buildTracer() -> Tracer { return OpenTelemetry.instance.tracerProvider.get(instrumentationName: "splunk-ios-crashreporting", instrumentationVersion: CrashReportingVersionString) @@ -138,10 +151,25 @@ func loadPendingCrashReport(_ data: Data!) throws { span.addEvent(name: "crash.timestamp", timestamp: report.systemInfo.timestamp) span.setAttribute(key: "exception.type", value: exceptionType ?? "unknown") span.setAttribute(key: "crash.address", value: report.signalInfo.address.description) - for case let thread as PLCrashReportThreadInfo in report.threads where thread.crashed { - span.setAttribute(key: "exception.stacktrace", value: crashedThreadToStack(report: report, thread: thread)) - break + + var allThreads: Array = [] + for case let thread as PLCrashReportThreadInfo in report.threads { + + // Original crashed thread handler + if (thread.crashed) { + span.setAttribute(key: "exception.stacktrace", value: crashedThreadToStack(report: report, thread: thread)) + } + + // Detailed thread handler + allThreads.append(detailedThreadToStackFrames(report: report, thread: thread)) } + let threadPayload = convertArrayToJSONString(allThreads) ?? "Unable to create stack frames" + span.setAttribute(key: "exception.stackFrames", value: threadPayload) + var images: Array = [] + images = imageList(images: report.images) + let imagesPayload = convertArrayToJSONString(images) ?? "Unable to create images" + span.setAttribute(key: "exception.images", value: imagesPayload) + if report.hasExceptionInfo { span.setAttribute(key: "exception.type", value: report.exceptionInfo.exceptionName) span.setAttribute(key: "exception.message", value: report.exceptionInfo.exceptionReason) @@ -200,3 +228,128 @@ func formatStackFrame(frame: PLCrashReportStackFrameInfo, frameNum: Int, report: initializeCrashReporting() } } + +// Symbolication Support Code + +// Extracts detail for one thread +func detailedThreadToStackFrames(report: PLCrashReport, thread: PLCrashReportThreadInfo) -> Dictionary { + + var oneThread: [String:Any] = [:] + var allStackFrames: Array = [] + + let threadNum = thread.threadNumber as NSNumber + oneThread["threadNumber"] = threadNum.stringValue + oneThread["crashed"] = thread.crashed + + var frameNum = 0 + while frameNum < thread.stackFrames.count { + var oneFrame: [String:Any] = [:] + + let frame = thread.stackFrames[frameNum] as! PLCrashReportStackFrameInfo + let instructionPointer = frame.instructionPointer + oneFrame["instructionPointer"] = instructionPointer + + var baseAddress: UInt64 = 0 + var offset: UInt64 = 0 + var imageName = "???" + + let imageInfo = report.image(forAddress: instructionPointer) + if imageInfo != nil { + imageName = imageInfo?.imageName ?? "???" + baseAddress = imageInfo!.imageBaseAddress + offset = instructionPointer - baseAddress + } + oneFrame["imageName"] = imageName + allUsedImageNames.append(imageName) + + if frame.symbolInfo != nil { + let symbolName = frame.symbolInfo.symbolName + let symOffset = instructionPointer - frame.symbolInfo.startAddress + oneFrame["symbolName"] = symbolName + oneFrame["offset"] = symOffset + } else { + oneFrame["baseAddress"] = baseAddress + oneFrame["offset"] = offset + } + allStackFrames.append(oneFrame) + frameNum += 1 + } + oneThread["stackFrames"] = allStackFrames + return oneThread +} + +// Returns true if debugger is attached +private func isDebuggerAttached() -> Bool { + var debuggerIsAttached = false + + var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] + var info = kinfo_proc() + var infoSize = MemoryLayout.size + + _ = name.withUnsafeMutableBytes { (nameBytePtr: UnsafeMutableRawBufferPointer) -> Bool in + guard let nameBytesBlindMemory = nameBytePtr.bindMemory(to: Int32.self).baseAddress else { + return false + } + return sysctl(nameBytesBlindMemory, 4, &info, &infoSize, nil, 0) != -1 + } + if !debuggerIsAttached && (info.kp_proc.p_flag & P_TRACED) != 0 { + debuggerIsAttached = true + } + return debuggerIsAttached +} + +// Returns array of code images used by app +func imageList(images: Array) -> Array { + var outputImages: Array = [] + for image in images { + var imageDictionary: [String:Any] = [:] + guard let image = image as? PLCrashReportBinaryImageInfo else { + continue + } + + // Only add the image to the list if it was noted in the stack traces + if(allUsedImageNames.contains(image.imageName)) { + imageDictionary["codeType"] = cpuTypeDictionary(cpuType: image.codeType) + imageDictionary["baseAddress"] = image.imageBaseAddress + imageDictionary["imageSize"] = image.imageSize + imageDictionary["imagePath"] = image.imageName + imageDictionary["imageUUID"] = image.imageUUID + + outputImages.append(imageDictionary) + } + } + return outputImages +} + +// Returns formatted cpu data +func cpuTypeDictionary(cpuType: PLCrashReportProcessorInfo) -> Dictionary { + var dictionary: [String:String] = [:] + dictionary.updateValue(String(cpuType.type), forKey: "cType") + dictionary.updateValue(String(cpuType.subtype), forKey: "cSubType") + return dictionary +} + +// JSON support code +func convertDictionaryToJSONString(_ dictionary: [String: Any]) -> String? { + guard let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted) else { + + return nil + } + guard let jsonString = String(data: jsonData, encoding: .utf8) else { + + return nil + } + return jsonString +} + +func convertArrayToJSONString(_ array: [Any]) -> String? { + guard let jsonData = try? JSONSerialization.data(withJSONObject: array, options: .prettyPrinted) else { + + return nil + } + guard let jsonString = String(data: jsonData, encoding: .utf8) else { + + return nil + } + return jsonString +} diff --git a/fullbuild.sh b/fullbuild.sh index 47445b9..4398186 100755 --- a/fullbuild.sh +++ b/fullbuild.sh @@ -1,6 +1,6 @@ #!/bin/bash set -ex -swiftlint --strict +swiftlint # Make sure the version numbers on the podspec and CrashReporting.swift match echo "Checking that version numbers match"