From e79a153131851197d7c290174dad6627d7b82736 Mon Sep 17 00:00:00 2001 From: tom doron Date: Mon, 6 Jun 2022 18:33:51 -0700 Subject: [PATCH 1/2] trap SIGSEGV by default, in addition to SIGILL motivation: print crash trace on segmentaiton fault changes: * include SIGSEGV in the default signals trapped * update and improve tests --- Sources/Backtrace/Backtrace.swift | 2 +- Sources/Sample/main.swift | 21 +++++++- .../BacktraceTests+XCTest.swift | 4 +- Tests/BacktraceTests/BacktraceTests.swift | 54 ++++++++++++++----- docker/docker-compose.yaml | 2 +- 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/Sources/Backtrace/Backtrace.swift b/Sources/Backtrace/Backtrace.swift index 69d30de..1d490b7 100644 --- a/Sources/Backtrace/Backtrace.swift +++ b/Sources/Backtrace/Backtrace.swift @@ -69,7 +69,7 @@ private func printBacktrace(signal: CInt) { public enum Backtrace { /// Install the backtrace handler on `SIGILL`. public static func install() { - Backtrace.install(signals: [SIGILL]) + Backtrace.install(signals: [SIGILL, SIGSEGV]) } /// Install the backtrace handler when any of `signals` happen. diff --git a/Sources/Sample/main.swift b/Sources/Sample/main.swift index 2e1879a..53fd5c0 100644 --- a/Sources/Sample/main.swift +++ b/Sources/Sample/main.swift @@ -13,7 +13,24 @@ //===----------------------------------------------------------------------===// import Backtrace +#if canImport(Darwin) +import Darwin +#elseif os(Linux) +import Glibc +#endif -let reason = CommandLine.arguments.count == 2 ? CommandLine.arguments[1] : "unknown" Backtrace.install() -fatalError(reason) + +func raiseSignal(_ signal: Int32) { + raise(signal) +} + +let reason = CommandLine.arguments.count == 2 ? CommandLine.arguments[1] : "unknown" +switch reason.uppercased() { +case "SIGILL": + raiseSignal(SIGILL) +case "SIGSEGV": + raiseSignal(SIGSEGV) +default: + fatalError(reason) +} diff --git a/Tests/BacktraceTests/BacktraceTests+XCTest.swift b/Tests/BacktraceTests/BacktraceTests+XCTest.swift index 4c5f604..e06cc2a 100644 --- a/Tests/BacktraceTests/BacktraceTests+XCTest.swift +++ b/Tests/BacktraceTests/BacktraceTests+XCTest.swift @@ -25,7 +25,9 @@ import XCTest extension BacktraceTests { public static var allTests: [(String, (BacktraceTests) -> () throws -> Void)] { return [ - ("testBacktrace", testBacktrace), + ("testFatalError", testFatalError), + ("testSIGILL", testSIGILL), + ("testSIGSEGV", testSIGSEGV), ] } } diff --git a/Tests/BacktraceTests/BacktraceTests.swift b/Tests/BacktraceTests/BacktraceTests.swift index 2c7db36..12d5aba 100644 --- a/Tests/BacktraceTests/BacktraceTests.swift +++ b/Tests/BacktraceTests/BacktraceTests.swift @@ -15,22 +15,52 @@ import XCTest public final class BacktraceTests: XCTestCase { - func testBacktrace() { - #if os(Linux) + func testFatalError() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + let expectedError = UUID().uuidString - let pipe = Pipe() - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/swift") - process.arguments = ["run", "Sample", expectedError] - process.standardError = pipe - XCTAssertNoThrow(try process.run()) - if process.isRunning { - process.waitUntilExit() - } - let stderr = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" + let stderr = try runSample(reason: expectedError) print(stderr) + + XCTAssert(stderr.contains("Received signal 4. Backtrace:")) XCTAssert(stderr.contains("Current stack trace:"), "expected stanard error to include backtrace") XCTAssert(stderr.contains("Fatal error: \(expectedError)"), "expected stanard error to include error information") + } + + func testSIGILL() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + + let stderr = try runSample(reason: "SIGILL") + print(stderr) + + XCTAssert(stderr.contains("Received signal 4. Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func testSIGSEGV() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") #endif + + let stderr = try runSample(reason: "SIGSEGV") + print(stderr) + + XCTAssert(stderr.contains("Received signal 11. Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func runSample(reason: String) throws -> String { + let pipe = Pipe() + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/swift") + process.arguments = ["run", "Sample", reason] + process.standardError = pipe + try process.run() + process.waitUntilExit() + return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" } } diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 91ddf2c..1299f6f 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -34,4 +34,4 @@ services: shell: <<: *common - entrypoint: /bin/bash + entrypoint: /bin/bash -l From 4ab201ef4064a047497c4f896c7f98c8ef1bfbb7 Mon Sep 17 00:00:00 2001 From: tom doron Date: Tue, 7 Jun 2022 10:31:41 -0700 Subject: [PATCH 2/2] also trap SIGBUS and SIGFPE --- Sources/Backtrace/Backtrace.swift | 4 +-- Sources/Sample/main.swift | 4 +++ .../BacktraceTests+XCTest.swift | 2 ++ Tests/BacktraceTests/BacktraceTests.swift | 28 +++++++++++++++++-- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Sources/Backtrace/Backtrace.swift b/Sources/Backtrace/Backtrace.swift index 1d490b7..9d08f2e 100644 --- a/Sources/Backtrace/Backtrace.swift +++ b/Sources/Backtrace/Backtrace.swift @@ -67,9 +67,9 @@ private func printBacktrace(signal: CInt) { } public enum Backtrace { - /// Install the backtrace handler on `SIGILL`. + /// Install the backtrace handler on default signals: `SIGILL`, `SIGSEGV`, `SIGBUS`, `SIGFPE`. public static func install() { - Backtrace.install(signals: [SIGILL, SIGSEGV]) + Backtrace.install(signals: [SIGILL, SIGSEGV, SIGBUS, SIGFPE]) } /// Install the backtrace handler when any of `signals` happen. diff --git a/Sources/Sample/main.swift b/Sources/Sample/main.swift index 53fd5c0..b6a0b39 100644 --- a/Sources/Sample/main.swift +++ b/Sources/Sample/main.swift @@ -31,6 +31,10 @@ case "SIGILL": raiseSignal(SIGILL) case "SIGSEGV": raiseSignal(SIGSEGV) +case "SIGBUS": + raiseSignal(SIGBUS) +case "SIGFPE": + raiseSignal(SIGFPE) default: fatalError(reason) } diff --git a/Tests/BacktraceTests/BacktraceTests+XCTest.swift b/Tests/BacktraceTests/BacktraceTests+XCTest.swift index e06cc2a..3e497ed 100644 --- a/Tests/BacktraceTests/BacktraceTests+XCTest.swift +++ b/Tests/BacktraceTests/BacktraceTests+XCTest.swift @@ -28,6 +28,8 @@ extension BacktraceTests { ("testFatalError", testFatalError), ("testSIGILL", testSIGILL), ("testSIGSEGV", testSIGSEGV), + ("testSIGBUS", testSIGBUS), + ("testSIGFPE", testSIGFPE), ] } } diff --git a/Tests/BacktraceTests/BacktraceTests.swift b/Tests/BacktraceTests/BacktraceTests.swift index 12d5aba..dbe3f16 100644 --- a/Tests/BacktraceTests/BacktraceTests.swift +++ b/Tests/BacktraceTests/BacktraceTests.swift @@ -37,7 +37,7 @@ public final class BacktraceTests: XCTestCase { let stderr = try runSample(reason: "SIGILL") print(stderr) - XCTAssert(stderr.contains("Received signal 4. Backtrace:")) + XCTAssert(stderr.contains("Received signal \(SIGILL). Backtrace:")) XCTAssert(stderr.contains("Sample.raiseSignal")) } @@ -49,7 +49,31 @@ public final class BacktraceTests: XCTestCase { let stderr = try runSample(reason: "SIGSEGV") print(stderr) - XCTAssert(stderr.contains("Received signal 11. Backtrace:")) + XCTAssert(stderr.contains("Received signal \(SIGSEGV). Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func testSIGBUS() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + + let stderr = try runSample(reason: "SIGBUS") + print(stderr) + + XCTAssert(stderr.contains("Received signal \(SIGBUS). Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func testSIGFPE() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + + let stderr = try runSample(reason: "SIGFPE") + print(stderr) + + XCTAssert(stderr.contains("Received signal \(SIGFPE). Backtrace:")) XCTAssert(stderr.contains("Sample.raiseSignal")) }