[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:
Alastair Houghton
2025-01-02 17:25:22 +00:00
parent 8462105e29
commit 9bdd9e73dc
7 changed files with 203 additions and 29 deletions

View File

@@ -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)

View File

@@ -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("")

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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__

View File

@@ -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__

View 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