Skip to content

[Backtracing] Support redirection to a named file. #79007

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion stdlib/public/libexec/swift-backtrace/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===//

#if canImport(Darwin)
import Darwin.C
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
Expand Down Expand Up @@ -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.
///
Expand All @@ -173,6 +189,10 @@ struct CFileStream: TextOutputStream {
public func flush() {
fflush(fp)
}

public func close() {
fclose(fp)
}
}

var standardOutput = CFileStream(fp: stdout)
Expand Down
103 changes: 87 additions & 16 deletions stdlib/public/libexec/swift-backtrace/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ internal struct SwiftBacktrace {
enum OutputTo {
case stdout
case stderr
case file
}

enum Symbolication {
Expand All @@ -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()
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -540,6 +539,71 @@ 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 || errno == EINTR) {
if errno != EINTR {
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("")
Expand Down Expand Up @@ -707,11 +771,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
// 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 {
write("\r\u{1b}[0K")
print("\r\u{1b}[0K", terminator: "", to: &handlerOut)
} else {
write(" done ***\n\n")
print(" done ***\n\n", terminator: "", to: &handlerOut)
}

writeln(theme.crashReason(description))
Expand Down Expand Up @@ -839,7 +910,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("")
Expand Down
Loading