mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Backtracing] Support redirection to a named file.
Add the ability to specify a filename or directory name as the output-to setting in `SWIFT_BACKTRACE`. If the option is set to a directory name, generate a unique filename in that directory using the process name, process ID and timestamp. rdar://136977833
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if canImport(Darwin)
|
||||
import Darwin.C
|
||||
import Darwin
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#elseif canImport(Musl)
|
||||
@@ -151,6 +151,22 @@ internal func spawn(_ path: String, args: [String]) throws {
|
||||
|
||||
#endif // os(macOS)
|
||||
|
||||
internal func isDir(_ path: String) -> Bool {
|
||||
var st = stat()
|
||||
guard stat(path, &st) == 0 else {
|
||||
return false
|
||||
}
|
||||
return (st.st_mode & S_IFMT) == S_IFDIR
|
||||
}
|
||||
|
||||
internal func exists(_ path: String) -> Bool {
|
||||
var st = stat()
|
||||
guard stat(path, &st) == 0 else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
extension Sequence {
|
||||
/// Return the first element in a Sequence.
|
||||
///
|
||||
@@ -173,6 +189,10 @@ struct CFileStream: TextOutputStream {
|
||||
public func flush() {
|
||||
fflush(fp)
|
||||
}
|
||||
|
||||
public func close() {
|
||||
fclose(fp)
|
||||
}
|
||||
}
|
||||
|
||||
var standardOutput = CFileStream(fp: stdout)
|
||||
|
||||
@@ -56,6 +56,7 @@ internal struct SwiftBacktrace {
|
||||
enum OutputTo {
|
||||
case stdout
|
||||
case stderr
|
||||
case file
|
||||
}
|
||||
|
||||
enum Symbolication {
|
||||
@@ -81,6 +82,7 @@ internal struct SwiftBacktrace {
|
||||
var cache = true
|
||||
var outputTo: OutputTo = .stdout
|
||||
var symbolicate: Symbolication = .full
|
||||
var outputPath: String = "/tmp"
|
||||
}
|
||||
|
||||
static var args = Arguments()
|
||||
@@ -97,15 +99,10 @@ internal struct SwiftBacktrace {
|
||||
}
|
||||
}
|
||||
|
||||
static var outputStream: CFileStream {
|
||||
switch args.outputTo {
|
||||
case .stdout: return standardOutput
|
||||
case .stderr: return standardError
|
||||
}
|
||||
}
|
||||
static var outputStream: CFileStream? = nil
|
||||
|
||||
static func write(_ string: String, flush: Bool = false) {
|
||||
var stream = outputStream
|
||||
var stream = outputStream!
|
||||
|
||||
print(string, terminator: "", to: &stream)
|
||||
if flush {
|
||||
@@ -114,7 +111,7 @@ internal struct SwiftBacktrace {
|
||||
}
|
||||
|
||||
static func writeln(_ string: String, flush: Bool = false) {
|
||||
var stream = outputStream
|
||||
var stream = outputStream!
|
||||
|
||||
print(string, to: &stream)
|
||||
if flush {
|
||||
@@ -208,6 +205,10 @@ Generate a backtrace for the parent process.
|
||||
--output-to <stream> Set which output stream to use. Options are "stdout"
|
||||
-o <stream> and "stderr". The default is "stdout".
|
||||
|
||||
Alternatively, you may specify a file path here. If
|
||||
the path points to a directory, a unique filename will
|
||||
be generated automatically.
|
||||
|
||||
--crashinfo <addr>
|
||||
-a <addr> Provide a pointer to a platform specific CrashInfo
|
||||
structure. <addr> should be in hexadecimal.
|
||||
@@ -405,10 +406,8 @@ Generate a backtrace for the parent process.
|
||||
case "stderr":
|
||||
args.outputTo = .stderr
|
||||
default:
|
||||
print("swift-backtrace: unknown output-to setting '\(v)'",
|
||||
to: &standardError)
|
||||
usage()
|
||||
exit(1)
|
||||
args.outputTo = .file
|
||||
args.outputPath = v
|
||||
}
|
||||
} else {
|
||||
print("swift-backtrace: missing output-to value",
|
||||
@@ -540,6 +539,69 @@ Generate a backtrace for the parent process.
|
||||
currentThread = target!.crashingThreadNdx
|
||||
}
|
||||
|
||||
// Set up the output stream
|
||||
var didOpenOutput = false
|
||||
switch args.outputTo {
|
||||
case .stdout:
|
||||
outputStream = standardOutput
|
||||
case .stderr:
|
||||
outputStream = standardError
|
||||
case .file:
|
||||
if isDir(args.outputPath) {
|
||||
// If the output path is a directory, generate a filename
|
||||
let name = target!.name
|
||||
let pid = target!.pid
|
||||
var now = timespec()
|
||||
|
||||
if clock_gettime(CLOCK_REALTIME, &now) != 0 {
|
||||
now.tv_sec = time(nil)
|
||||
now.tv_nsec = 0
|
||||
}
|
||||
|
||||
var filename =
|
||||
"\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec).log"
|
||||
|
||||
var fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0o644)
|
||||
var ndx = 1
|
||||
while fd < 0 && errno == EEXIST {
|
||||
ndx += 1
|
||||
filename = "\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec)-\(ndx).log"
|
||||
fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0o644)
|
||||
}
|
||||
|
||||
if fd < 0 {
|
||||
print("swift-backtrace: unable to create \(filename) for writing",
|
||||
to: &standardError)
|
||||
outputStream = standardError
|
||||
}
|
||||
|
||||
if let cFile = fdopen(fd, "wt") {
|
||||
didOpenOutput = true
|
||||
outputStream = CFileStream(fp: cFile)
|
||||
} else {
|
||||
close(fd)
|
||||
unlink(filename)
|
||||
|
||||
print("swift-backtrace: unable to fdopen \(filename) for writing",
|
||||
to: &standardError)
|
||||
outputStream = standardError
|
||||
}
|
||||
} else if let cFile = fopen(args.outputPath, "wt") {
|
||||
didOpenOutput = true
|
||||
outputStream = CFileStream(fp: cFile)
|
||||
} else {
|
||||
print("swift-backtrace: unable to open \(args.outputPath) for writing",
|
||||
to: &standardError)
|
||||
|
||||
outputStream = standardError
|
||||
}
|
||||
}
|
||||
defer {
|
||||
if didOpenOutput {
|
||||
outputStream!.close()
|
||||
}
|
||||
}
|
||||
|
||||
printCrashLog()
|
||||
|
||||
writeln("")
|
||||
@@ -707,11 +769,18 @@ 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
|
||||
if args.color {
|
||||
write("\r\u{1b}[0K")
|
||||
// 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 {
|
||||
write(" done ***\n\n")
|
||||
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))
|
||||
@@ -839,7 +908,7 @@ Generate a backtrace for the parent process.
|
||||
}
|
||||
|
||||
while true {
|
||||
outputStream.flush()
|
||||
outputStream!.flush()
|
||||
write(theme.prompt(">>> "), flush: true)
|
||||
guard let input = readLine() else {
|
||||
print("")
|
||||
|
||||
@@ -351,6 +351,18 @@ BacktraceInitializer::BacktraceInitializer() {
|
||||
_swift_backtraceSettings.enabled = OnOffTty::Off;
|
||||
}
|
||||
|
||||
// If we're outputting to a file, then the defaults are different
|
||||
if (_swift_backtraceSettings.outputTo == OutputTo::File) {
|
||||
if (_swift_backtraceSettings.interactive == OnOffTty::TTY)
|
||||
_swift_backtraceSettings.interactive = OnOffTty::Off;
|
||||
if (_swift_backtraceSettings.color == OnOffTty::TTY)
|
||||
_swift_backtraceSettings.color = OnOffTty::Off;
|
||||
|
||||
// Unlike the other settings, this defaults to on if you specified a file
|
||||
if (_swift_backtraceSettings.enabled == OnOffTty::TTY)
|
||||
_swift_backtraceSettings.enabled = OnOffTty::On;
|
||||
}
|
||||
|
||||
if (_swift_backtraceSettings.enabled == OnOffTty::TTY)
|
||||
_swift_backtraceSettings.enabled =
|
||||
isStdoutATty() ? OnOffTty::On : OnOffTty::Off;
|
||||
@@ -700,9 +712,15 @@ _swift_processBacktracingSetting(llvm::StringRef key,
|
||||
else if (value.equals_insensitive("stderr"))
|
||||
_swift_backtraceSettings.outputTo = OutputTo::Stderr;
|
||||
else {
|
||||
swift::warning(0,
|
||||
"swift runtime: unknown output-to setting '%.*s'\n",
|
||||
static_cast<int>(value.size()), value.data());
|
||||
size_t len = value.size();
|
||||
char *path = (char *)std::malloc(len + 1);
|
||||
std::copy(value.begin(), value.end(), path);
|
||||
path[len] = 0;
|
||||
|
||||
std::free(const_cast<char *>(_swift_backtraceSettings.outputPath));
|
||||
|
||||
_swift_backtraceSettings.outputTo = OutputTo::File;
|
||||
_swift_backtraceSettings.outputPath = path;
|
||||
}
|
||||
} else if (key.equals_insensitive("symbolicate")) {
|
||||
_swift_backtraceSettings.symbolicate = parseSymbolication(value);
|
||||
@@ -1036,10 +1054,10 @@ _swift_displayCrashMessage(int signum, const void *pc)
|
||||
#if !SWIFT_BACKTRACE_ON_CRASH_SUPPORTED
|
||||
return;
|
||||
#else
|
||||
int fd = STDOUT_FILENO;
|
||||
int fd = STDERR_FILENO;
|
||||
|
||||
if (_swift_backtraceSettings.outputTo == OutputTo::Stderr)
|
||||
fd = STDERR_FILENO;
|
||||
if (_swift_backtraceSettings.outputTo == OutputTo::Stdout)
|
||||
fd = STDOUT_FILENO;
|
||||
|
||||
const char *intro;
|
||||
if (_swift_backtraceSettings.color == OnOffTty::On) {
|
||||
|
||||
@@ -94,6 +94,7 @@ enum class OutputTo {
|
||||
Auto = -1,
|
||||
Stdout = 0,
|
||||
Stderr = 2,
|
||||
File = 3
|
||||
};
|
||||
|
||||
enum class Symbolication {
|
||||
@@ -120,6 +121,7 @@ struct BacktraceSettings {
|
||||
OutputTo outputTo;
|
||||
Symbolication symbolicate;
|
||||
const char *swiftBacktracePath;
|
||||
const char *outputPath;
|
||||
};
|
||||
|
||||
SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings;
|
||||
|
||||
@@ -264,10 +264,10 @@ handle_fatal_signal(int signum,
|
||||
if (!run_backtracer(fd)) {
|
||||
const char *message = _swift_backtraceSettings.color == OnOffTty::On
|
||||
? " failed\n\n" : " failed ***\n\n";
|
||||
if (_swift_backtraceSettings.outputTo == OutputTo::Stderr)
|
||||
write(STDERR_FILENO, message, strlen(message));
|
||||
else
|
||||
if (_swift_backtraceSettings.outputTo == OutputTo::Stdout)
|
||||
write(STDOUT_FILENO, message, strlen(message));
|
||||
else
|
||||
write(STDERR_FILENO, message, strlen(message));
|
||||
}
|
||||
|
||||
#if !MEMSERVER_USE_PROCESS
|
||||
@@ -930,6 +930,9 @@ run_backtracer(int memserver_fd)
|
||||
case OutputTo::Stderr:
|
||||
backtracer_argv[30] = "stderr";
|
||||
break;
|
||||
case OutputTo::File:
|
||||
backtracer_argv[30] = _swift_backtraceSettings.outputPath;
|
||||
break;
|
||||
}
|
||||
|
||||
backtracer_argv[28] = trueOrFalse(_swift_backtraceSettings.cache);
|
||||
@@ -963,4 +966,3 @@ run_backtracer(int memserver_fd)
|
||||
} // namespace
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
|
||||
@@ -415,6 +415,9 @@ run_backtracer()
|
||||
case OutputTo::Stderr:
|
||||
backtracer_argv[30] = "stderr";
|
||||
break;
|
||||
case OutputTo::File:
|
||||
backtracer_argv[30] = _swift_backtraceSettings.outputPath;
|
||||
break;
|
||||
}
|
||||
|
||||
backtracer_argv[28] = trueOrFalse(_swift_backtraceSettings.cache);
|
||||
@@ -450,4 +453,3 @@ run_backtracer()
|
||||
#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
|
||||
61
test/Backtracing/CrashOutputFile.swift
Normal file
61
test/Backtracing/CrashOutputFile.swift
Normal file
@@ -0,0 +1,61 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %empty-directory(%t/crashlogs)
|
||||
// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/CrashOutputFile
|
||||
// RUN: %target-codesign %t/CrashOutputFile
|
||||
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,output-to=%t/crash.log %target-run %t/CrashOutputFile 2>&1 || true) && cat %t/crash.log | %FileCheck %s
|
||||
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,output-to=%t/crashlogs %target-run %t/CrashOutputFile 2>&1 || true) && cat %t/crashlogs/* | %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 CrashOutputFile {
|
||||
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 CrashOutputFile at {{.*}}/CrashOutputFile.swift:34:15
|
||||
// CHECK-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:28:3
|
||||
// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:24:3
|
||||
// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:20:3
|
||||
// CHECK-NEXT: 4 [ra] 0x{{[0-9a-f]+}} level1() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:16:3
|
||||
// CHECK-NEXT: 5 [ra] 0x{{[0-9a-f]+}} static CrashOutputFile.main() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:40:5
|
||||
// CHECK-NEXT: 6 [ra] [system] 0x{{[0-9a-f]+}} static CrashOutputFile.$main() + {{[0-9]+}} in CrashOutputFile at {{.*}}/<compiler-generated>
|
||||
// CHECK-NEXT: 7 [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift
|
||||
|
||||
// CHECK: Registers:
|
||||
|
||||
// CHECK: Images ({{[0-9]+}} omitted):
|
||||
|
||||
// CHECK: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{([0-9a-f]+|<no build ID>)}}{{ +}}CrashOutputFile{{ +}}{{.*}}/CrashOutputFile
|
||||
Reference in New Issue
Block a user