Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional Crash Report Data #16

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
full-build:
runs-on: macOS-latest
runs-on: macOS-13
steps:
- name: Checkout
uses: actions/checkout@v1
Expand Down
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

171 changes: 162 additions & 9 deletions SplunkRumCrashReporting/SplunkRumCrashReporting/CrashReporting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ let CrashReportingVersionString = "0.6.0"

var TheCrashReporter: PLCrashReporter?
private var customDataDictionary: [String: String] = [String: String]()
private var allUsedImageNames: Array <String> = []

func initializeCrashReporting() {
let startupSpan = buildTracer().spanBuilder(spanName: "SplunkRumCrashReporting").startSpan()
Expand All @@ -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()
Expand All @@ -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 {
Expand All @@ -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)

Expand Down Expand Up @@ -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 <Any> = []
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 <Any> = []
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)
Expand Down Expand Up @@ -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<String, Any> {

var oneThread: [String:Any] = [:]
var allStackFrames: Array <Any> = []

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<kinfo_proc>.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<Any>) -> Array<Any> {
var outputImages: Array<Any> = []
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<String, String> {
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
}
2 changes: 1 addition & 1 deletion fullbuild.sh
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Loading