[Backtracing] Added JSON crash log option.

Also made it so the `sanitize` option causes the crash logs to not
include memory dumps.

Fixes #71057
rdar://121430255
This commit is contained in:
Alastair Houghton
2025-01-07 11:01:26 +00:00
parent 45277d2884
commit b5461aeb22
9 changed files with 656 additions and 43 deletions

View File

@@ -194,6 +194,30 @@ public struct Backtrace: CustomStringConvertible, Sendable {
return "..."
}
}
/// A JSON description of this frame, less the surrounding braces.
/// This is useful if you want to add extra information.
@_spi(Internal)
public var jsonBody: String {
switch self {
case let .programCounter(addr):
return "\"kind\": \"programCounter\", \"address\": \"\(addr)\""
case let .returnAddress(addr):
return "\"kind\": \"returnAddress\", \"address\": \"\(addr)\""
case let .asyncResumePoint(addr):
return "\"kind\": \"asyncResumePoint\", \"address\": \"\(addr)\""
case let .omittedFrames(count):
return "\"kind\": \"omittedFrames\", \"count\": \(count)"
case .truncated:
return "\"kind\": \"truncated\""
}
}
/// A JSON description of this frame.
@_spi(Internal)
public var jsonDescription: String {
return "{ \(jsonBody) }"
}
}
/// Represents an image loaded in the process's address space

View File

@@ -416,7 +416,8 @@ private func untabify(_ s: String, tabWidth: Int = 8) -> String {
/// @param path The path to sanitize.
///
/// @returns A string containing the sanitized path.
private func sanitizePath(_ path: String) -> String {
@_spi(Formatting)
public func sanitizePath(_ path: String) -> String {
#if os(macOS)
return CRCopySanitizedPath(path,
kCRSanitizePathGlobAllTypes

View File

@@ -33,6 +33,7 @@ set(BACKTRACING_COMPILE_FLAGS
set(BACKTRACING_SOURCES
main.swift
AnsiColor.swift
JSON.swift
TargetMacOS.swift
TargetLinux.swift
Themes.swift

View File

@@ -0,0 +1,362 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 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
//
//===----------------------------------------------------------------------===//
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(CRT)
import CRT
#endif
@_spi(Contexts) import Runtime
@_spi(Registers) import Runtime
@_spi(Formatting) import Runtime
@_spi(Internal) import Runtime
@_spi(MemoryReaders) import Runtime
// ###TODO: Escape JSON strings
extension SwiftBacktrace {
static func outputJSONCrashLog() {
guard let target = target else {
print("swift-backtrace: unable to get target",
to: &standardError)
return
}
let crashingThread = target.threads[target.crashingThreadNdx]
let description: String
if case let .symbolicated(symbolicated) = crashingThread.backtrace,
let failure = symbolicated.swiftRuntimeFailure {
description = failure
} else {
description = target.signalDescription
}
write("""
{ \
"timestamp": "\(formatISO8601(now))", \
"kind": "crashReport", \
"description": "\(escapeJSON(description))", \
"faultAddress": "\(hex(target.faultAddress))"
""")
var mentionedImages = Set<Int>()
var capturedBytes: [UInt64:Array<UInt8>] = [:]
func outputJSONRegister<T: FixedWidthInteger>(
name: String, value: T, first: Bool = false
) {
if !first {
write(", ")
}
write("\"\(name)\": \"\(hex(value))\"")
if let bytes = try? target.reader.fetch(
from: RemoteMemoryReader.Address(value),
count: 16,
as: UInt8.self) {
capturedBytes[UInt64(truncatingIfNeeded: value)] = bytes
}
}
func outputJSONRegister<C: Context>(
name: String, context: C, register: C.Register, first: Bool = false
) {
let value = context.getRegister(register)!
outputJSONRegister(name: name, value: value, first: first)
}
func outputJSONGPRs<C: Context, Rs: Sequence>(_ context: C, range: Rs)
where Rs.Element == C.Register
{
var first = true
for reg in range {
outputJSONRegister(name: "\(reg)", context: context, register: reg,
first: first)
if first {
first = false
}
}
}
func outputJSONRegisterDump(_ context: X86_64Context) {
outputJSONGPRs(context, range: .rax ... .r15)
outputJSONRegister(name: "rip", value: context.programCounter)
outputJSONRegister(name: "rflags", context: context, register: .rflags)
outputJSONRegister(name: "cs", context: context, register: .cs)
outputJSONRegister(name: "fs", context: context, register: .fs)
outputJSONRegister(name: "gs", context: context, register: .gs)
}
func outputJSONRegisterDump(_ context: I386Context) {
outputJSONGPRs(context, range: .eax ... .edi)
outputJSONRegister(name: "eip", value: context.programCounter)
outputJSONRegister(name: "eflags", context: context, register: .eflags)
outputJSONRegister(name: "es", context: context, register: .es)
outputJSONRegister(name: "cs", context: context, register: .cs)
outputJSONRegister(name: "ss", context: context, register: .ss)
outputJSONRegister(name: "ds", context: context, register: .ds)
outputJSONRegister(name: "fs", context: context, register: .fs)
outputJSONRegister(name: "gs", context: context, register: .gs)
}
func outputJSONRegisterDump(_ context: ARM64Context) {
outputJSONGPRs(context, range: .x0 ..< .x29)
outputJSONRegister(name: "fp", context: context, register: .x29)
outputJSONRegister(name: "lr", context: context, register: .x30)
outputJSONRegister(name: "sp", context: context, register: .sp)
outputJSONRegister(name: "pc", context: context, register: .pc)
}
func outputJSONRegisterDump(_ context: ARMContext) {
outputJSONGPRs(context, range: .r0 ... .r10)
outputJSONRegister(name: "fp", context: context, register: .r11)
outputJSONRegister(name: "ip", context: context, register: .r12)
outputJSONRegister(name: "sp", context: context, register: .r13)
outputJSONRegister(name: "lr", context: context, register: .r14)
outputJSONRegister(name: "pc", context: context, register: .r15)
}
func outputJSONThread(ndx: Int, thread: TargetThread) {
write("{ ")
if !thread.name.isEmpty {
write("\"name\": \"\(escapeJSON(thread.name))\", ")
}
if thread.id == target.crashingThread {
write(#""crashed": true, "#)
}
if args.registers! == .all {
if let context = thread.context {
write(#""registers": {"#)
outputJSONRegisterDump(context)
write(" }, ")
}
}
write(#""frames": ["#)
var first = true
switch thread.backtrace {
case let .raw(backtrace):
for frame in backtrace.frames {
if first {
first = false
} else {
write(",")
}
write(" { \(frame.jsonBody) }")
}
case let .symbolicated(backtrace):
for frame in backtrace.frames {
if first {
first = false
} else {
write(",")
}
write(" { ")
write(frame.captured.jsonBody)
if frame.inlined {
write(#", "inlined": true"#)
}
if frame.isSwiftRuntimeFailure {
write(#", "runtimeFailure": true"#)
}
if frame.isSwiftThunk {
write(#", "thunk": true"#)
}
if frame.isSystem {
write(#", "system": true"#)
}
if let symbol = frame.symbol {
write("""
, "symbol": "\(escapeJSON(symbol.rawName))"\
, "offset": \(symbol.offset)
""")
if args.demangle {
let formattedOffset: String
if symbol.offset > 0 {
formattedOffset = " + \(symbol.offset)"
} else if symbol.offset < 0 {
formattedOffset = " - \(symbol.offset)"
} else {
formattedOffset = ""
}
write("""
, "description": \"\(escapeJSON(symbol.name))\(formattedOffset)\"
""")
}
if symbol.imageIndex >= 0 {
write(", \"image\": \"\(symbol.imageName)\"")
}
if var sourceLocation = symbol.sourceLocation {
if args.sanitize ?? false {
sourceLocation.path = sanitizePath(sourceLocation.path)
}
write(#", "sourceLocation": { "#)
write("""
"file": "\(escapeJSON(sourceLocation.path))", \
"line": \(sourceLocation.line), \
"column": \(sourceLocation.column)
""")
write(" } ")
}
}
write(" }")
}
}
write(" ] ")
write("} ")
if args.showImages! == .mentioned {
switch thread.backtrace {
case let .raw(backtrace):
for frame in backtrace.frames {
let address = frame.adjustedProgramCounter
if let imageNdx = target.images.firstIndex(
where: { address >= $0.baseAddress
&& address < $0.endOfText }
) {
mentionedImages.insert(imageNdx)
}
}
case let .symbolicated(backtrace):
for frame in backtrace.frames {
if let symbol = frame.symbol, symbol.imageIndex >= 0 {
mentionedImages.insert(symbol.imageIndex)
}
}
}
}
}
if args.threads! {
write(#", "threads": [ "#)
for (ndx, thread) in target.threads.enumerated() {
outputJSONThread(ndx: ndx, thread: thread)
}
write("]")
} else {
outputJSONThread(ndx: target.crashingThreadNdx,
thread: crashingThread)
}
if args.registers! == .crashedOnly {
if let context = target.threads[target.crashingThreadNdx].context {
write(#", "registers": { "#)
outputJSONRegisterDump(context)
write(" }")
}
}
if !capturedBytes.isEmpty && !(args.sanitize ?? false) {
write(#"", capturedMemory": {"#)
var first = true
for (address, bytes) in capturedBytes {
let formattedBytes = bytes
.map{ hex($0, withPrefix: false) }
.joined(separator: "")
if first {
first = false
} else {
write(",")
}
write(" \"\(hex(address))\": \"\(formattedBytes)\"")
}
write(" }, ")
}
func outputJSONImage(_ image: Backtrace.Image, first: Bool) {
if !first {
write(", ")
}
write("{ ")
if let name = image.name {
write("\"name\": \"\(escapeJSON(name))\", ")
}
if let bytes = image.uniqueID {
let buildID = hex(bytes)
write("\"buildId\": \"\(buildID)\", ")
}
if var path = image.path {
if args.sanitize ?? false {
path = sanitizePath(path)
}
write("\"path\": \"\(path)\", ")
}
write("""
"baseAddress": "\(image.baseAddress)", \
"endOfText": "\(image.endOfText)"
""")
write(" }")
}
switch args.showImages! {
case .none:
break
case .mentioned:
let images = mentionedImages.sorted().map{ target.images[$0] }
let omitted = target.images.count - images.count
write(", \"omittedImages\": \(omitted), \"images\": [ ")
var first = true
for image in images {
outputJSONImage(image, first: first)
if first {
first = false
}
}
write(" ] ")
case .all:
write(#", "images": [ "#)
var first = true
for image in target.images {
outputJSONImage(image, first: first)
if first {
first = false
}
}
write(" ] ")
}
let secs = Double(backtraceDuration.tv_sec)
+ 1.0e-9 * Double(backtraceDuration.tv_nsec)
write(", \"backtraceTime\": \(secs) ")
writeln("}")
}
}

View File

@@ -34,15 +34,32 @@ typealias CrashInfo = swift.runtime.backtrace.CrashInfo
typealias thread = swift.runtime.backtrace.thread
#endif
extension String {
init<T: BinaryInteger>(_ value: T,
width: Int,
radix: Int = 10,
uppercase: Bool = false) {
let digits = String(value, radix: radix, uppercase: uppercase)
if digits.count >= width {
self = digits
return
}
let padding = String(repeating: "0",
count: width - digits.count)
self = padding + digits
}
}
internal func hex<T: FixedWidthInteger>(_ value: T,
withPrefix: Bool = true) -> String {
let digits = String(value, radix: 16)
let padTo = value.bitWidth / 4
let padding = digits.count >= padTo ? "" : String(repeating: "0",
count: padTo - digits.count)
let prefix = withPrefix ? "0x" : ""
let formatted = String(value, width: value.bitWidth / 4, radix: 16)
return "\(prefix)\(padding)\(digits)"
if withPrefix {
return "0x" + formatted
} else {
return formatted
}
}
internal func hex(_ bytes: [UInt8]) -> String {
@@ -104,6 +121,7 @@ internal func recursiveRemoveContents(_ dir: String) throws {
}
}
/// Remove a directory and its contents.
internal func recursiveRemove(_ dir: String) throws {
try recursiveRemoveContents(dir)
@@ -112,8 +130,25 @@ internal func recursiveRemove(_ dir: String) throws {
}
}
internal func withTemporaryDirectory(pattern: String, shouldDelete: Bool = true,
body: (String) throws -> ()) throws {
/// Run a closure, passing in the name of a temporary directory that will
/// (optionally) be deleted automatically when the closure returns.
///
/// Parameters:
///
/// - pattern: A string with some number of trailing 'X's giving the name
/// to use for the directory; the 'X's will be replaced with a
/// unique combination of alphanumeric characters.
/// - shouldDelete: If `true` (the default), the directory and its contents
/// will be removed automatically when the closure returns.
/// - body: The closure to execute.
///
/// Returns:
///
/// This function returns whatever the closure returns.
internal func withTemporaryDirectory<R>(
pattern: String, shouldDelete: Bool = true,
body: (String) throws -> R
) throws -> R {
var buf = Array<UInt8>(pattern.utf8)
buf.append(0)
@@ -132,9 +167,10 @@ internal func withTemporaryDirectory(pattern: String, shouldDelete: Bool = true,
}
}
try body(dir)
return try body(dir)
}
/// Start a program with the specified arguments
internal func spawn(_ path: String, args: [String]) throws {
var cargs = args.map{ strdup($0) }
cargs.append(nil)
@@ -151,6 +187,7 @@ internal func spawn(_ path: String, args: [String]) throws {
#endif // os(macOS)
/// Test if the specified path is a directory
internal func isDir(_ path: String) -> Bool {
var st = stat()
guard stat(path, &st) == 0 else {
@@ -159,6 +196,7 @@ internal func isDir(_ path: String) -> Bool {
return (st.st_mode & S_IFMT) == S_IFDIR
}
/// Test if the specified path exists
internal func exists(_ path: String) -> Bool {
var st = stat()
guard stat(path, &st) == 0 else {
@@ -197,3 +235,52 @@ struct CFileStream: TextOutputStream {
var standardOutput = CFileStream(fp: stdout)
var standardError = CFileStream(fp: stderr)
/// Format a timespec as an ISO8601 date/time
func formatISO8601(_ time: timespec) -> String {
var exploded = tm()
var secs = time.tv_sec
gmtime_r(&secs, &exploded)
let isoTime = """
\(String(exploded.tm_year + 1900, width: 4))-\
\(String(exploded.tm_mon + 1, width: 2))-\
\(String(exploded.tm_mday, width: 2))T\
\(String(exploded.tm_hour, width: 2)):\
\(String(exploded.tm_min, width: 2)):\
\(String(exploded.tm_sec, width: 2)).\
\(String(time.tv_nsec / 1000, width: 6))Z
"""
return isoTime
}
/// Escape a JSON string
func escapeJSON(_ s: String) -> String {
var result = ""
let utf8View = s.utf8
var chunk = utf8View.startIndex
var pos = chunk
let end = utf8View.endIndex
while pos != end {
let scalar = utf8View[pos]
switch scalar {
case 0x22, 0x5c, 0x00...0x1f:
result += s[chunk..<pos]
result += "\\"
result += String(Unicode.Scalar(scalar))
pos = utf8View.index(after: pos)
chunk = pos
default:
pos = utf8View.index(after: pos)
}
}
if chunk != end {
result += s[chunk..<pos]
}
return result
}

View File

@@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Copyright (c) 2023-25 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
@@ -65,6 +65,11 @@ internal struct SwiftBacktrace {
case full
}
enum OutputFormat {
case text
case json
}
struct Arguments {
var unwindAlgorithm: UnwindAlgorithm = .precise
var demangle = false
@@ -82,6 +87,7 @@ internal struct SwiftBacktrace {
var cache = true
var outputTo: OutputTo = .stdout
var symbolicate: Symbolication = .full
var format: OutputFormat = .text
var outputPath: String = "/tmp"
}
@@ -91,6 +97,9 @@ internal struct SwiftBacktrace {
static var target: Target? = nil
static var currentThread: Int = 0
static var now = timespec(tv_sec: 0, tv_nsec: 0)
static var backtraceDuration = timespec(tv_sec: 0, tv_nsec: 0)
static var theme: any Theme {
if args.color {
return Themes.color
@@ -209,6 +218,9 @@ Generate a backtrace for the parent process.
the path points to a directory, a unique filename will
be generated automatically.
--format <format> Set the output format. Options are "text" and "json";
the default is "text".
--crashinfo <addr>
-a <addr> Provide a pointer to a platform specific CrashInfo
structure. <addr> should be in hexadecimal.
@@ -447,6 +459,23 @@ Generate a backtrace for the parent process.
} else {
args.symbolicate = .full
}
case "--format":
if let v = value {
switch v.lowercased() {
case "text":
args.format = .text
case "json":
args.format = .json
default:
print("swift-backtrace: unknown output format '\(v)'",
to: &standardError)
}
} else {
print("swift-backtrace: missing format value",
to: &standardError)
usage()
exit(1)
}
default:
print("swift-backtrace: unknown argument '\(arg)'",
to: &standardError)
@@ -530,7 +559,7 @@ Generate a backtrace for the parent process.
// Target's initializer fetches and symbolicates backtraces, so
// we want to time that part here.
let duration = measureDuration {
backtraceDuration = measureDuration {
target = Target(crashInfoAddr: crashInfoAddr,
limit: args.limit, top: args.top,
cache: args.cache,
@@ -539,6 +568,13 @@ Generate a backtrace for the parent process.
currentThread = target!.crashingThreadNdx
}
// Grab the current wall clock time; if clock_gettime() fails, get a
// lower resolution version instead.
if clock_gettime(CLOCK_REALTIME, &now) != 0 {
now.tv_sec = time(nil)
now.tv_nsec = 0
}
// Set up the output stream
var didOpenOutput = false
switch args.outputTo {
@@ -553,21 +589,25 @@ Generate a backtrace for the parent process.
let pid = target!.pid
var now = timespec(tv_sec: 0, tv_nsec: 0)
if clock_gettime(CLOCK_REALTIME, &now) != 0 {
now.tv_sec = time(nil)
now.tv_nsec = 0
let ext: String
switch args.format {
case .text:
ext = "log"
case .json:
ext = "json"
}
var filename =
"\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec).log"
"\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec).\(ext)"
var fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0o644)
var ndx = 1
while fd < 0 && (errno == EEXIST || errno == EINTR) {
if errno != EINTR {
ndx += 1
}
filename = "\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec)-\(ndx).log"
filename = "\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec)-\(ndx).\(ext)"
fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0o644)
}
@@ -604,14 +644,33 @@ Generate a backtrace for the parent process.
}
}
printCrashLog()
// Clear (or complete) the message written by the crash handler; this
// is always on stdout or stderr, even if you specify a file for output.
var handlerOut: CFileStream
if args.outputTo == .stdout {
handlerOut = standardOutput
} else {
handlerOut = standardError
}
if args.color {
print("\r\u{1b}[0K", terminator: "", to: &handlerOut)
} else {
print(" done ***\n\n", terminator: "", to: &handlerOut)
}
writeln("")
switch args.format {
case .text:
printCrashLog()
let formattedDuration = format(duration: duration)
writeln("")
writeln("Backtrace took \(formattedDuration)s")
writeln("")
let formattedDuration = format(duration: backtraceDuration)
writeln("Backtrace took \(formattedDuration)s")
writeln("")
case .json:
outputJSONCrashLog()
}
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
// On Darwin, if Developer Mode is turned off, or we can't tell if it's
@@ -771,20 +830,6 @@ Generate a backtrace for the parent process.
description = "Program crashed: \(target.signalDescription) at \(hex(target.faultAddress))"
}
// Clear (or complete) the message written by the crash handler; this
// is always on stdout or stderr, even if you specify a file for output.
var handlerOut: CFileStream
if args.outputTo == .stdout {
handlerOut = standardOutput
} else {
handlerOut = standardError
}
if args.color {
print("\r\u{1b}[0K", terminator: "", to: &handlerOut)
} else {
print(" done ***\n\n", terminator: "", to: &handlerOut)
}
writeln(theme.crashReason(description))
var mentionedImages = Set<Int>()
@@ -1329,11 +1374,15 @@ Generate a backtrace for the parent process.
from: RemoteMemoryReader.Address(value),
count: 16,
as: UInt8.self) {
let formattedBytes = theme.data(bytes.map{
hex($0, withPrefix: false)
}.joined(separator: " "))
let printedBytes = printableBytes(from: bytes)
writeln("\(reg) \(hexValue) \(formattedBytes) \(printedBytes)")
if args.sanitize ?? false {
writeln("\(reg) \(hexValue) <memory>")
} else {
let formattedBytes = theme.data(bytes.map{
hex($0, withPrefix: false)
}.joined(separator: " "))
let printedBytes = printableBytes(from: bytes)
writeln("\(reg) \(hexValue) \(formattedBytes) \(printedBytes)")
}
} else {
let decValue = theme.decimalValue("\(value)")
writeln("\(reg) \(hexValue) \(decValue)")

View File

@@ -120,7 +120,7 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = {
// top
16,
// sanitize,
// sanitize
SanitizePaths::Preset,
// preset
@@ -129,7 +129,7 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = {
// cache
true,
// outputTo,
// outputTo
OutputTo::Auto,
// symbolicate
@@ -138,6 +138,9 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = {
// suppressWarnings
false,
// format
OutputFormat::Text,
// swiftBacktracePath
NULL,
@@ -769,6 +772,16 @@ _swift_processBacktracingSetting(llvm::StringRef key,
}
} else if (key.equals_insensitive("symbolicate")) {
_swift_backtraceSettings.symbolicate = parseSymbolication(value);
} else if (key.equals_insensitive("format")) {
if (value.equals_insensitive("text")) {
_swift_backtraceSettings.format = OutputFormat::Text;
} else if (value.equals_insensitive("json")) {
_swift_backtraceSettings.format = OutputFormat::JSON;
} else {
swift::warning(0,
"swift runtime: unknown backtrace format '%.*s'\n",
static_cast<int>(value.size()), value.data());
}
#if !defined(SWIFT_RUNTIME_FIXED_BACKTRACER_PATH)
} else if (key.equals_insensitive("swift-backtrace")) {
size_t len = value.size();
@@ -1088,6 +1101,8 @@ const char *backtracer_argv[] = {
"stdout", // 30
"--symbolicate", // 31
"full", // 32
"--format", // 33
"text", // 34
NULL
};
@@ -1225,6 +1240,15 @@ _swift_spawnBacktracer(CrashInfo *crashInfo)
break;
}
switch (_swift_backtraceSettings.format) {
case OutputFormat::Text:
backtracer_argv[34] = "text";
break;
case OutputFormat::JSON:
backtracer_argv[34] = "json";
break;
}
_swift_formatUnsigned(_swift_backtraceSettings.timeout, timeout_buf);
if (_swift_backtraceSettings.limit < 0)

View File

@@ -104,6 +104,11 @@ enum class Symbolication {
Full = 2,
};
enum class OutputFormat {
Text = 0,
JSON = 1
};
struct BacktraceSettings {
UnwindAlgorithm algorithm;
OnOffTty enabled;
@@ -122,6 +127,7 @@ struct BacktraceSettings {
OutputTo outputTo;
Symbolication symbolicate;
bool suppressWarnings;
OutputFormat format;
const char *swiftBacktracePath;
const char *outputPath;
};

View File

@@ -0,0 +1,59 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/JSON
// RUN: %target-codesign %t/JSON
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/JSON 2>&1 || true) | %FileCheck %s
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: asan
// REQUIRES: executable_test
// REQUIRES: backtracing
// REQUIRES: OS=macosx || OS=linux-gnu
func level1() {
level2()
}
func level2() {
level3()
}
func level3() {
level4()
}
func level4() {
level5()
}
func level5() {
print("About to crash")
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
ptr.pointee = 42
}
@main
struct JSON {
static func main() {
level1()
}
}
// CHECK: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 ***
// CHECK: Thread 0 {{(".*" )?}}crashed:
// CHECK: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in JSON at {{.*}}/JSON.swift:42:15
// CHECK-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in JSON at {{.*}}/JSON.swift:36:3
// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in JSON at {{.*}}/JSON.swift:32:3
// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in JSON at {{.*}}/JSON.swift:28:3
// CHECK-NEXT: 4 [ra] 0x{{[0-9a-f]+}} level1() + {{[0-9]+}} in JSON at {{.*}}/JSON.swift:24:3
// CHECK-NEXT: 5 [ra] 0x{{[0-9a-f]+}} static JSON.main() + {{[0-9]+}} in JSON at {{.*}}/JSON.swift:48:5
// CHECK-NEXT: 6 [ra] [system] 0x{{[0-9a-f]+}} static JSON.$main() + {{[0-9]+}} in JSON at {{.*}}/<compiler-generated>
// CHECK-NEXT: 7 [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in JSON at {{.*}}/JSON.swift
// CHECK: Registers:
// CHECK: Images ({{[0-9]+}} omitted):
// CHECK: {{0x[0-9a-f]+}}{{0x[0-9a-f]+}}{{ +}}{{([0-9a-f]+|<no build ID>)}}{{ +}}JSON{{ +}}{{.*}}/JSON