Skip to content

Commit 9bdd9e7

Browse files
committed
[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
1 parent 8462105 commit 9bdd9e7

File tree

7 files changed

+202
-28
lines changed

7 files changed

+202
-28
lines changed

stdlib/public/libexec/swift-backtrace/Utils.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
#if canImport(Darwin)
18-
import Darwin.C
18+
import Darwin
1919
#elseif canImport(Glibc)
2020
import Glibc
2121
#elseif canImport(Musl)
@@ -151,6 +151,22 @@ internal func spawn(_ path: String, args: [String]) throws {
151151

152152
#endif // os(macOS)
153153

154+
internal func isDir(_ path: String) -> Bool {
155+
var st = stat()
156+
guard stat(path, &st) == 0 else {
157+
return false
158+
}
159+
return (st.st_mode & S_IFMT) == S_IFDIR
160+
}
161+
162+
internal func exists(_ path: String) -> Bool {
163+
var st = stat()
164+
guard stat(path, &st) == 0 else {
165+
return false
166+
}
167+
return true
168+
}
169+
154170
extension Sequence {
155171
/// Return the first element in a Sequence.
156172
///
@@ -173,6 +189,10 @@ struct CFileStream: TextOutputStream {
173189
public func flush() {
174190
fflush(fp)
175191
}
192+
193+
public func close() {
194+
fclose(fp)
195+
}
176196
}
177197

178198
var standardOutput = CFileStream(fp: stdout)

stdlib/public/libexec/swift-backtrace/main.swift

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ internal struct SwiftBacktrace {
5656
enum OutputTo {
5757
case stdout
5858
case stderr
59+
case file
5960
}
6061

6162
enum Symbolication {
@@ -81,6 +82,7 @@ internal struct SwiftBacktrace {
8182
var cache = true
8283
var outputTo: OutputTo = .stdout
8384
var symbolicate: Symbolication = .full
85+
var outputPath: String = "/tmp"
8486
}
8587

8688
static var args = Arguments()
@@ -97,15 +99,10 @@ internal struct SwiftBacktrace {
9799
}
98100
}
99101

100-
static var outputStream: CFileStream {
101-
switch args.outputTo {
102-
case .stdout: return standardOutput
103-
case .stderr: return standardError
104-
}
105-
}
102+
static var outputStream: CFileStream? = nil
106103

107104
static func write(_ string: String, flush: Bool = false) {
108-
var stream = outputStream
105+
var stream = outputStream!
109106

110107
print(string, terminator: "", to: &stream)
111108
if flush {
@@ -114,7 +111,7 @@ internal struct SwiftBacktrace {
114111
}
115112

116113
static func writeln(_ string: String, flush: Bool = false) {
117-
var stream = outputStream
114+
var stream = outputStream!
118115

119116
print(string, to: &stream)
120117
if flush {
@@ -208,6 +205,10 @@ Generate a backtrace for the parent process.
208205
--output-to <stream> Set which output stream to use. Options are "stdout"
209206
-o <stream> and "stderr". The default is "stdout".
210207
208+
Alternatively, you may specify a file path here. If
209+
the path points to a directory, a unique filename will
210+
be generated automatically.
211+
211212
--crashinfo <addr>
212213
-a <addr> Provide a pointer to a platform specific CrashInfo
213214
structure. <addr> should be in hexadecimal.
@@ -405,10 +406,8 @@ Generate a backtrace for the parent process.
405406
case "stderr":
406407
args.outputTo = .stderr
407408
default:
408-
print("swift-backtrace: unknown output-to setting '\(v)'",
409-
to: &standardError)
410-
usage()
411-
exit(1)
409+
args.outputTo = .file
410+
args.outputPath = v
412411
}
413412
} else {
414413
print("swift-backtrace: missing output-to value",
@@ -540,6 +539,69 @@ Generate a backtrace for the parent process.
540539
currentThread = target!.crashingThreadNdx
541540
}
542541

542+
// Set up the output stream
543+
var didOpenOutput = false
544+
switch args.outputTo {
545+
case .stdout:
546+
outputStream = standardOutput
547+
case .stderr:
548+
outputStream = standardError
549+
case .file:
550+
if isDir(args.outputPath) {
551+
// If the output path is a directory, generate a filename
552+
let name = target!.name
553+
let pid = target!.pid
554+
var now = timespec()
555+
556+
if clock_gettime(CLOCK_REALTIME, &now) != 0 {
557+
now.tv_sec = time(nil)
558+
now.tv_nsec = 0
559+
}
560+
561+
var filename =
562+
"\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec).log"
563+
564+
var fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0o644)
565+
var ndx = 1
566+
while fd < 0 && errno == EEXIST {
567+
ndx += 1
568+
filename = "\(args.outputPath)/\(name)-\(pid)-\(now.tv_sec).\(now.tv_nsec)-\(ndx).log"
569+
fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0o644)
570+
}
571+
572+
if fd < 0 {
573+
print("swift-backtrace: unable to create \(filename) for writing",
574+
to: &standardError)
575+
outputStream = standardError
576+
}
577+
578+
if let cFile = fdopen(fd, "wt") {
579+
didOpenOutput = true
580+
outputStream = CFileStream(fp: cFile)
581+
} else {
582+
close(fd)
583+
unlink(filename)
584+
585+
print("swift-backtrace: unable to fdopen \(filename) for writing",
586+
to: &standardError)
587+
outputStream = standardError
588+
}
589+
} else if let cFile = fopen(args.outputPath, "wt") {
590+
didOpenOutput = true
591+
outputStream = CFileStream(fp: cFile)
592+
} else {
593+
print("swift-backtrace: unable to open \(args.outputPath) for writing",
594+
to: &standardError)
595+
596+
outputStream = standardError
597+
}
598+
}
599+
defer {
600+
if didOpenOutput {
601+
outputStream!.close()
602+
}
603+
}
604+
543605
printCrashLog()
544606

545607
writeln("")
@@ -707,11 +769,18 @@ Generate a backtrace for the parent process.
707769
description = "Program crashed: \(target.signalDescription) at \(hex(target.faultAddress))"
708770
}
709771

710-
// Clear (or complete) the message written by the crash handler
772+
// Clear (or complete) the message written by the crash handler; this
773+
// is always on stdout or stderr, even if you specify a file for output.
774+
var handlerOut: CFileStream
775+
if args.outputTo == .stdout {
776+
handlerOut = standardOutput
777+
} else {
778+
handlerOut = standardError
779+
}
711780
if args.color {
712-
write("\r\u{1b}[0K")
781+
print("\r\u{1b}[0K", terminator: "", to: &handlerOut)
713782
} else {
714-
write(" done ***\n\n")
783+
print(" done ***\n\n", terminator: "", to: &handlerOut)
715784
}
716785

717786
writeln(theme.crashReason(description))
@@ -839,7 +908,7 @@ Generate a backtrace for the parent process.
839908
}
840909

841910
while true {
842-
outputStream.flush()
911+
outputStream!.flush()
843912
write(theme.prompt(">>> "), flush: true)
844913
guard let input = readLine() else {
845914
print("")

stdlib/public/runtime/Backtrace.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,18 @@ BacktraceInitializer::BacktraceInitializer() {
351351
_swift_backtraceSettings.enabled = OnOffTty::Off;
352352
}
353353

354+
// If we're outputting to a file, then the defaults are different
355+
if (_swift_backtraceSettings.outputTo == OutputTo::File) {
356+
if (_swift_backtraceSettings.interactive == OnOffTty::TTY)
357+
_swift_backtraceSettings.interactive = OnOffTty::Off;
358+
if (_swift_backtraceSettings.color == OnOffTty::TTY)
359+
_swift_backtraceSettings.color = OnOffTty::Off;
360+
361+
// Unlike the other settings, this defaults to on if you specified a file
362+
if (_swift_backtraceSettings.enabled == OnOffTty::TTY)
363+
_swift_backtraceSettings.enabled = OnOffTty::On;
364+
}
365+
354366
if (_swift_backtraceSettings.enabled == OnOffTty::TTY)
355367
_swift_backtraceSettings.enabled =
356368
isStdoutATty() ? OnOffTty::On : OnOffTty::Off;
@@ -700,9 +712,15 @@ _swift_processBacktracingSetting(llvm::StringRef key,
700712
else if (value.equals_insensitive("stderr"))
701713
_swift_backtraceSettings.outputTo = OutputTo::Stderr;
702714
else {
703-
swift::warning(0,
704-
"swift runtime: unknown output-to setting '%.*s'\n",
705-
static_cast<int>(value.size()), value.data());
715+
size_t len = value.size();
716+
char *path = (char *)std::malloc(len + 1);
717+
std::copy(value.begin(), value.end(), path);
718+
path[len] = 0;
719+
720+
std::free(const_cast<char *>(_swift_backtraceSettings.outputPath));
721+
722+
_swift_backtraceSettings.outputTo = OutputTo::File;
723+
_swift_backtraceSettings.outputPath = path;
706724
}
707725
} else if (key.equals_insensitive("symbolicate")) {
708726
_swift_backtraceSettings.symbolicate = parseSymbolication(value);
@@ -1036,10 +1054,10 @@ _swift_displayCrashMessage(int signum, const void *pc)
10361054
#if !SWIFT_BACKTRACE_ON_CRASH_SUPPORTED
10371055
return;
10381056
#else
1039-
int fd = STDOUT_FILENO;
1057+
int fd = STDERR_FILENO;
10401058

1041-
if (_swift_backtraceSettings.outputTo == OutputTo::Stderr)
1042-
fd = STDERR_FILENO;
1059+
if (_swift_backtraceSettings.outputTo == OutputTo::Stdout)
1060+
fd = STDOUT_FILENO;
10431061

10441062
const char *intro;
10451063
if (_swift_backtraceSettings.color == OnOffTty::On) {

stdlib/public/runtime/BacktracePrivate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ enum class OutputTo {
9494
Auto = -1,
9595
Stdout = 0,
9696
Stderr = 2,
97+
File = 3
9798
};
9899

99100
enum class Symbolication {
@@ -120,6 +121,7 @@ struct BacktraceSettings {
120121
OutputTo outputTo;
121122
Symbolication symbolicate;
122123
const char *swiftBacktracePath;
124+
const char *outputPath;
123125
};
124126

125127
SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings;

stdlib/public/runtime/CrashHandlerLinux.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,10 @@ handle_fatal_signal(int signum,
264264
if (!run_backtracer(fd)) {
265265
const char *message = _swift_backtraceSettings.color == OnOffTty::On
266266
? " failed\n\n" : " failed ***\n\n";
267-
if (_swift_backtraceSettings.outputTo == OutputTo::Stderr)
268-
write(STDERR_FILENO, message, strlen(message));
269-
else
267+
if (_swift_backtraceSettings.outputTo == OutputTo::Stdout)
270268
write(STDOUT_FILENO, message, strlen(message));
269+
else
270+
write(STDERR_FILENO, message, strlen(message));
271271
}
272272

273273
#if !MEMSERVER_USE_PROCESS
@@ -930,6 +930,9 @@ run_backtracer(int memserver_fd)
930930
case OutputTo::Stderr:
931931
backtracer_argv[30] = "stderr";
932932
break;
933+
case OutputTo::File:
934+
backtracer_argv[30] = _swift_backtraceSettings.outputPath;
935+
break;
933936
}
934937

935938
backtracer_argv[28] = trueOrFalse(_swift_backtraceSettings.cache);
@@ -963,4 +966,3 @@ run_backtracer(int memserver_fd)
963966
} // namespace
964967

965968
#endif // __linux__
966-

stdlib/public/runtime/CrashHandlerMacOS.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,9 @@ run_backtracer()
415415
case OutputTo::Stderr:
416416
backtracer_argv[30] = "stderr";
417417
break;
418+
case OutputTo::File:
419+
backtracer_argv[30] = _swift_backtraceSettings.outputPath;
420+
break;
418421
}
419422

420423
backtracer_argv[28] = trueOrFalse(_swift_backtraceSettings.cache);
@@ -450,4 +453,3 @@ run_backtracer()
450453
#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
451454

452455
#endif // __APPLE__
453-
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/crashlogs)
3+
// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/CrashOutputFile
4+
// RUN: %target-codesign %t/CrashOutputFile
5+
// 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
6+
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,output-to=%t/crashlogs %target-run %t/CrashOutputFile 2>&1 || true) && cat %t/crashlogs/* | %FileCheck %s
7+
8+
// UNSUPPORTED: use_os_stdlib
9+
// UNSUPPORTED: back_deployment_runtime
10+
// UNSUPPORTED: asan
11+
// REQUIRES: executable_test
12+
// REQUIRES: backtracing
13+
// REQUIRES: OS=macosx || OS=linux-gnu
14+
15+
func level1() {
16+
level2()
17+
}
18+
19+
func level2() {
20+
level3()
21+
}
22+
23+
func level3() {
24+
level4()
25+
}
26+
27+
func level4() {
28+
level5()
29+
}
30+
31+
func level5() {
32+
print("About to crash")
33+
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
34+
ptr.pointee = 42
35+
}
36+
37+
@main
38+
struct CrashOutputFile {
39+
static func main() {
40+
level1()
41+
}
42+
}
43+
44+
// CHECK: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 ***
45+
46+
// CHECK: Thread 0 {{(".*" )?}}crashed:
47+
48+
// CHECK: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:34:15
49+
// CHECK-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:28:3
50+
// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:24:3
51+
// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:20:3
52+
// CHECK-NEXT: 4 [ra] 0x{{[0-9a-f]+}} level1() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:16:3
53+
// CHECK-NEXT: 5 [ra] 0x{{[0-9a-f]+}} static CrashOutputFile.main() + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift:40:5
54+
// CHECK-NEXT: 6 [ra] [system] 0x{{[0-9a-f]+}} static CrashOutputFile.$main() + {{[0-9]+}} in CrashOutputFile at {{.*}}/<compiler-generated>
55+
// CHECK-NEXT: 7 [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in CrashOutputFile at {{.*}}/CrashOutputFile.swift
56+
57+
// CHECK: Registers:
58+
59+
// CHECK: Images ({{[0-9]+}} omitted):
60+
61+
// CHECK: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{([0-9a-f]+|<no build ID>)}}{{ +}}CrashOutputFile{{ +}}{{.*}}/CrashOutputFile

0 commit comments

Comments
 (0)