Skip to content

Instantly share code, notes, and snippets.

@hironytic
Created September 9, 2018 13:15
Show Gist options
  • Select an option

  • Save hironytic/c053ea12a0a05d22d54640b2e9e69efa to your computer and use it in GitHub Desktop.

Select an option

Save hironytic/c053ea12a0a05d22d54640b2e9e69efa to your computer and use it in GitHub Desktop.
//
// CallStackReporter.swift
// DramaticCrash
//
// Copyright (c) 2018 Hironori Ichimiya <hiron@hironytic.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import MachO.dyld
#if arch(x86_64) || arch(arm64)
private typealias MachHeader = mach_header_64
#else
private typealias MachHeader = mach_header
#endif
public func reportCallStackSymbols() {
var report = ""
report += "Hardware Model: \(modelName())\n"
report += "\n"
report += "OS Version: \(osVersion())\n"
report += "Report Version: 104\n";
report += "\n"
let threadNo = Thread.isMainThread ? "0" : "9999"
report += "Thread \(threadNo):\n"
for symbol in Thread.callStackSymbols.map(adjustCallStackLine) {
report += symbol + "\n"
}
report += "\n"
report += "Binary Images:\n"
for ix in 0 ..< _dyld_image_count() {
report += makeBinaryImageLine(ix)
}
report += "\nEOF\n\n\n"
print(report)
}
private func modelName() -> String {
var systemInfo = utsname()
uname(&systemInfo)
return withUnsafePointer(to: &systemInfo.machine) {
$0.withMemoryRebound(to: CChar.self, capacity: 256) {
String(utf8String: $0) ?? ""
}
}
}
private func osVersion() -> String {
// "Version 11.4.1 (Build 15G77)" operatingSystemVersionStringは
// こんな感じの文字列を返す
// "11.4.1 (15G77" または "11.4.1 (Build 15G77" といった形式を
// 含んでいればOK (→ see parse_OSVersion in symbolicatecrash)
let versionString = ProcessInfo().operatingSystemVersionString
return "iPhone OS \(versionString)"
}
private func adjustCallStackLine(_ line: String) -> String {
// ↓こんな感じでアドレスが2つ並ぶ必要あり
// "0 ModuleName 0x00000001012b61a4 0x1012B5CEC + 1208"
// ↓こんなのではダメなので、2つ目のアドレスが並ぶように書き換える
// "0 ModuleName 0x00000001012b61a4 _T013ModuleName22fooBarBaxyyF + 1208"
let re = try! NSRegularExpression(pattern: "^\\d+\\s+\\S.*?\\s+0x(\\w+)\\s+([^\\+]*)(?:\\+\\s+(\\d+))?", options: [])
let results = re.matches(in: line, options: [], range: NSRange(location: 0, length: line.count))
if results.count > 0 && results[0].numberOfRanges >= 3 {
let addrString = (line as NSString).substring(with: results[0].range(at: 1))
let offsetString = (line as NSString).substring(with: results[0].range(at: 3))
let libAddr = "0x" + String(UInt(addrString, radix: 16)! - UInt(offsetString)!, radix:16) + " "
return (line as NSString).replacingCharacters(in: results[0].range(at: 2), with: libAddr)
}
return line
}
private func makeBinaryImageLine(_ index: UInt32) -> String {
let header = unsafeBitCast(_dyld_get_image_header(index), to: UnsafePointer<MachHeader>.self)
let arch = makeArchString(header: header)
var size: UInt = 0
let address = getsegmentdata(header, "__TEXT", &size)
let base = UInt(bitPattern: address)
let extent = base + size - 1
let uuid = searchUUID(header: header)
let path = String(utf8String: _dyld_get_image_name(index)!)!
let bundleName = (path as NSString).lastPathComponent
return String(format: "0x%llx - 0x%llx %@ %@ <%@> %@\n", Int64(base), Int64(extent), bundleName, arch, uuid, path)
}
private func makeArchString(header: UnsafePointer<MachHeader>) -> String {
switch (header.pointee.cputype, header.pointee.cpusubtype) {
case (CPU_TYPE_I386, _):
return "i386"
case (CPU_TYPE_X86_64, _):
return "x86_64"
case (CPU_TYPE_ARM64, _):
return "arm64"
case (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7):
return "armv7"
case (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S):
return "armv7s"
default:
return "unknown"
}
}
private func searchUUID(header: UnsafePointer<MachHeader>) -> String {
var ptr = UnsafeRawPointer(header).advanced(by: MemoryLayout<MachHeader>.size)
for _ in 0 ..< header.pointee.ncmds {
let loadCommand = ptr.bindMemory(to: load_command.self, capacity: 1)
if loadCommand.pointee.cmd == LC_UUID {
let uuidPointer = ptr
.advanced(by: MemoryLayout<load_command>.size)
.bindMemory(to: UInt8.self, capacity: 16)
return String(format: "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
uuidPointer[0], uuidPointer[1], uuidPointer[2], uuidPointer[3],
uuidPointer[4], uuidPointer[5], uuidPointer[6], uuidPointer[7],
uuidPointer[8], uuidPointer[9], uuidPointer[10], uuidPointer[11],
uuidPointer[12], uuidPointer[13], uuidPointer[14], uuidPointer[15])
}
ptr = ptr.advanced(by: Int(loadCommand.pointee.cmdsize))
}
return ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment