├── .github └── workflows │ ├── nightly.yml │ ├── swiftlint.yml │ └── swiftpm.yml ├── .gitignore ├── .swiftlint.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── CSwiftBacktrace │ ├── CSwiftBacktrace.cpp │ └── include │ │ ├── CSwiftBacktrace.h │ │ └── module.modulemap ├── Clibunwind │ ├── Clibunwind.cpp │ └── include │ │ ├── Clibunwind.h │ │ ├── libunwind.h │ │ └── module.modulemap └── SwiftBacktrace │ ├── BacktraceFormatter.swift │ ├── Demangle.swift │ ├── DynamicLinkLibrary.swift │ ├── String+extension.swift │ ├── SwiftBacktrace.swift │ ├── Unwind.swift │ ├── shim.swift │ └── sigaction.swift └── Tests ├── LinuxMain.swift └── SwiftBacktraceTests ├── SwiftBacktraceTests.swift └── XCTestManifests.swift /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - '.github/workflows/nightly.yml' 8 | - 'Package*' 9 | - 'Sources/**' 10 | - 'Tests/**' 11 | schedule: 12 | - cron: '0 4 * * *' 13 | 14 | jobs: 15 | Nightly: 16 | runs-on: ubuntu-latest 17 | container: 18 | image: norionomura/swift:nightly 19 | steps: 20 | - run: apt-get update && apt-get install -y libunwind8 21 | - uses: actions/checkout@v1 22 | - run: swift test --parallel 23 | -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | name: SwiftLint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/workflows/swiftlint.yml' 7 | - '.swiftlint.yml' 8 | - '**/*.swift' 9 | 10 | jobs: 11 | SwiftLint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: GitHub Action for SwiftLint 16 | uses: norio-nomura/action-swiftlint@3.0.1 17 | -------------------------------------------------------------------------------- /.github/workflows/swiftpm.yml: -------------------------------------------------------------------------------- 1 | name: SwiftPM 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - '.github/workflows/swiftpm.yml' 8 | - 'Package*' 9 | - 'Sources/**' 10 | - 'Tests/**' 11 | pull_request: 12 | paths: 13 | - '.github/workflows/swiftpm.yml' 14 | - 'Package*' 15 | - 'Sources/**' 16 | - 'Tests/**' 17 | 18 | jobs: 19 | macOS: 20 | strategy: 21 | matrix: 22 | xcode_version: ['9.4.1','10','10.1','10.2','10.2.1','10.3','11'] 23 | runs-on: macOS-10.14 24 | env: 25 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode_version }}.app 26 | steps: 27 | - uses: actions/checkout@v1 28 | - run: swift -version 29 | - run: swift test 30 | 31 | Linux: 32 | strategy: 33 | matrix: 34 | tag: ['4.0', '4.1', '4.2', '5.0', '5.1'] 35 | runs-on: ubuntu-latest 36 | container: 37 | image: norionomura/swift:${{ matrix.tag }} 38 | steps: 39 | - run: apt-get update && apt-get install -y libunwind8 40 | - uses: actions/checkout@v1 41 | - run: swift test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | - Tests 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Norio Nomura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftBacktrace", 8 | products: [ 9 | .library( 10 | name: "SwiftBacktrace", 11 | targets: ["Clibunwind", "CSwiftBacktrace", "SwiftBacktrace"]) 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/apple/swift-nio.git", Version("1.0.0") ..< Version("3.0.0")) 15 | ], 16 | targets: [ 17 | .target(name: "Clibunwind"), 18 | .target(name: "CSwiftBacktrace"), 19 | .target( 20 | name: "SwiftBacktrace", 21 | dependencies: ["Clibunwind", "CSwiftBacktrace", "NIOConcurrencyHelpers"]), 22 | .testTarget( 23 | name: "SwiftBacktraceTests", 24 | dependencies: ["SwiftBacktrace"]) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftBacktrace 2 | [![SwiftPM](https://github.com/norio-nomura/SwiftBacktrace/workflows/SwiftPM/badge.svg)](https://launch-editor.github.com/actions?workflowID=SwiftPM&event=pull_request&nwo=norio-nomura%2FSwiftBacktrace) 3 | [![Nightly](https://github.com/norio-nomura/SwiftBacktrace/workflows/Nightly/badge.svg)](https://launch-editor.github.com/actions?workflowID=Nightly&event=pull_request&nwo=norio-nomura%2FSwiftBacktrace) 4 | 5 | Stack traces for Swift on Mac and Linux using `libunwind`. 6 | 7 | ## Installation 8 | 9 | `SwiftBacktrace` depends on `libunwind`. 10 | 11 | ### On macOS 12 | compatible with pre-installed `/usr/lib/system/libunwind.dylib`. 13 | 14 | ### On Linux 15 | `libunwind8` installaion is required. 16 | ``` 17 | apt-get update && apt-get install -y libunwind8 18 | ``` 19 | 20 | ## Getting started 21 | ```swift 22 | import SwiftBacktrace 23 | 24 | print(backtrace().joined(separator: "\n")) // backtrace() 25 | print(demangledBacktrace().joined(separator: "\n")) // demangled backtrace 26 | ``` 27 | 28 | [Output example on Linux CI](https://circleci.com/gh/norio-nomura/SwiftBacktrace/16): 29 | ``` 30 | /root/project/.build/x86_64-unknown-linux/debug/SwiftBacktracePackageTests.xctest(SwiftBacktrace.callStackSymbols(_: Swift.Int, transform: ((module: Swift.String, name: Swift.String, offset: Swift.UInt64, address: Swift.Optional)) -> A) -> Swift.Array+0x87) [0x55f78f85c0b7] 31 | /root/project/.build/x86_64-unknown-linux/debug/SwiftBacktracePackageTests.xctest(SwiftBacktrace.demangledBacktrace(Swift.Int) -> Swift.Array+0x80) [0x55f78f85ae10] 32 | /root/project/.build/x86_64-unknown-linux/debug/SwiftBacktracePackageTests.xctest(SwiftBacktraceTests.SwiftBacktraceTests.test_backtrace() -> ()+0x4a5) [0x55f78f8608d5] 33 | /root/project/.build/x86_64-unknown-linux/debug/SwiftBacktracePackageTests.xctest(partial apply forwarder for SwiftBacktraceTests.SwiftBacktraceTests.test_backtrace() -> ()+0x9) [0x55f78f861119] 34 | ``` 35 | 36 | ## Author 37 | 38 | Norio Nomura 39 | 40 | ## License 41 | 42 | This package is available under the MIT license. See the LICENSE file for more info. 43 | -------------------------------------------------------------------------------- /Sources/CSwiftBacktrace/CSwiftBacktrace.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "CSwiftBacktrace.h" 4 | 5 | char* _Nullable cxx_demangle(const char* _Nonnull mangledName, 6 | char* _Nullable outputBuffer, 7 | size_t* _Nullable outputBufferSize, 8 | int* status) 9 | { 10 | return abi::__cxa_demangle(mangledName, outputBuffer, outputBufferSize, status); 11 | } 12 | 13 | const char* _Nullable dli_fname(const void* _Null_unspecified addr) { 14 | Dl_info info; 15 | return dladdr(addr, &info) ? info.dli_fname : NULL; 16 | } 17 | 18 | int cswift_backtrace_anchor() { 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /Sources/CSwiftBacktrace/include/CSwiftBacktrace.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | // Calls dladdr(3) and returns `Dl_info.dli_fname` 10 | const char* _Nullable dli_fname(const void* _Null_unspecified addr); 11 | 12 | // demangle c++ mangled name by using `abi::__cxa_demangle()` 13 | char* _Nullable cxx_demangle(const char* _Nonnull mangledName, 14 | char* _Nullable outputBuffer, 15 | size_t* _Nullable outputBufferSize, 16 | int* _Nullable status); 17 | 18 | // `swift_demangle` is implemented in libswiftCore 19 | extern char* _Nullable swift_demangle(const char* _Nonnull mangledName, 20 | size_t mangledNameLength, 21 | char* _Nullable outputBuffer, 22 | size_t* _Nullable outputBufferSize, 23 | uint32_t flags); 24 | 25 | #ifdef __cplusplus 26 | } // extern "C" 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/CSwiftBacktrace/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CSwiftBacktrace { 2 | umbrella "." 3 | link "stdc++" 4 | link "swiftCore" 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Clibunwind/Clibunwind.cpp: -------------------------------------------------------------------------------- 1 | #include "Clibunwind.h" -------------------------------------------------------------------------------- /Sources/Clibunwind/include/Clibunwind.h: -------------------------------------------------------------------------------- 1 | #define __OSX_AVAILABLE_STARTING(_osx, _ios) 2 | #include "libunwind.h" 3 | 4 | #if defined __i386__ 5 | #define UNW_TARGET x86 6 | 7 | typedef enum 8 | { 9 | UNW_X86_EIP = 8, /* frame-register */ 10 | 11 | UNW_TDEP_IP = UNW_X86_EIP, 12 | UNW_TDEP_SP = UNW_X86_ESP, 13 | UNW_TDEP_EH = UNW_X86_EAX 14 | } 15 | x86_regnum_t; 16 | 17 | 18 | #elif defined __x86_64__ 19 | #define UNW_TARGET x86_64 20 | 21 | typedef enum 22 | { 23 | UNW_X86_64_RIP = 16, 24 | 25 | UNW_TDEP_IP = UNW_X86_64_RIP, 26 | UNW_TDEP_SP = UNW_X86_64_RSP, 27 | UNW_TDEP_BP = UNW_X86_64_RBP, 28 | UNW_TDEP_EH = UNW_X86_64_RAX 29 | } 30 | x86_64_regnum_t; 31 | 32 | #else 33 | # error "Unsupported arch" 34 | #endif 35 | 36 | #define UNW_LOCAL_ONLY 37 | 38 | #define UNW_PASTE2(x,y) x##y 39 | #define UNW_PASTE(x,y) UNW_PASTE2(x,y) 40 | #define UNW_OBJ(fn) UNW_PASTE(UNW_PREFIX, fn) 41 | #define UNW_ARCH_OBJ(fn) UNW_PASTE(UNW_PASTE(UNW_PASTE(_U,UNW_TARGET),_), fn) 42 | 43 | #ifdef UNW_LOCAL_ONLY 44 | # define UNW_PREFIX UNW_PASTE(UNW_PASTE(_UL,UNW_TARGET),_) 45 | #else /* !UNW_LOCAL_ONLY */ 46 | # define UNW_PREFIX UNW_PASTE(UNW_PASTE(_U,UNW_TARGET),_) 47 | #endif /* !UNW_LOCAL_ONLY */ 48 | -------------------------------------------------------------------------------- /Sources/Clibunwind/include/libunwind.h: -------------------------------------------------------------------------------- 1 | /* -*- mode: C; c-basic-offset: 4; -*- 2 | * 3 | * Copyright (c) 2008-2011 Apple Inc. All rights reserved. 4 | * 5 | * @APPLE_LICENSE_HEADER_START@ 6 | * 7 | * This file contains Original Code and/or Modifications of Original Code 8 | * as defined in and that are subject to the Apple Public Source License 9 | * Version 2.0 (the 'License'). You may not use this file except in 10 | * compliance with the License. Please obtain a copy of the License at 11 | * http://www.opensource.apple.com/apsl/ and read it before using this 12 | * file. 13 | * 14 | * The Original Code and all software distributed under the License are 15 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 16 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 17 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 19 | * Please see the License for the specific language governing rights and 20 | * limitations under the License. 21 | * 22 | * @APPLE_LICENSE_HEADER_END@ 23 | * 24 | * 25 | * C interface to libuwind 26 | * 27 | * Source compatible with libuwind API documented at: 28 | * http://www.nongnu.org/libunwind/man/libunwind(3).html 29 | * 30 | */ 31 | 32 | 33 | #ifndef __LIBUNWIND__ 34 | #define __LIBUNWIND__ 35 | 36 | #include 37 | #include 38 | // #include 39 | // #include 40 | 41 | /* error codes */ 42 | enum { 43 | UNW_ESUCCESS = 0, /* no error */ 44 | UNW_EUNSPEC = -6540, /* unspecified (general) error */ 45 | UNW_ENOMEM = -6541, /* out of memory */ 46 | UNW_EBADREG = -6542, /* bad register number */ 47 | UNW_EREADONLYREG = -6543, /* attempt to write read-only register */ 48 | UNW_ESTOPUNWIND = -6544, /* stop unwinding */ 49 | UNW_EINVALIDIP = -6545, /* invalid IP */ 50 | UNW_EBADFRAME = -6546, /* bad frame */ 51 | UNW_EINVAL = -6547, /* unsupported operation or bad value */ 52 | UNW_EBADVERSION = -6548, /* unwind info has unsupported version */ 53 | UNW_ENOINFO = -6549 /* no unwind info found */ 54 | }; 55 | 56 | 57 | struct unw_context_t { uint64_t data[128]; }; 58 | typedef struct unw_context_t unw_context_t; 59 | 60 | struct unw_cursor_t { uint64_t data[140]; }; 61 | typedef struct unw_cursor_t unw_cursor_t; 62 | 63 | typedef struct unw_addr_space* unw_addr_space_t; 64 | 65 | typedef int unw_regnum_t; 66 | typedef uint64_t unw_word_t; 67 | typedef double unw_fpreg_t; 68 | 69 | struct unw_proc_info_t 70 | { 71 | unw_word_t start_ip; /* start address of function */ 72 | unw_word_t end_ip; /* address after end of function */ 73 | unw_word_t lsda; /* address of language specific data area, or zero if not used */ 74 | unw_word_t handler; /* personality routine, or zero if not used */ 75 | unw_word_t gp; /* not used */ 76 | unw_word_t flags; /* not used */ 77 | uint32_t format; /* compact unwind encoding, or zero if none */ 78 | uint32_t unwind_info_size; /* size of dwarf unwind info, or zero if none */ 79 | unw_word_t unwind_info; /* address of dwarf unwind info, or zero if none */ 80 | unw_word_t extra; /* mach_header of mach-o image containing function */ 81 | }; 82 | typedef struct unw_proc_info_t unw_proc_info_t; 83 | 84 | 85 | #ifdef __cplusplus 86 | extern "C" { 87 | #endif 88 | 89 | #if !__arm__ 90 | extern int unw_getcontext(unw_context_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 91 | extern int unw_init_local(unw_cursor_t*, unw_context_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 92 | extern int unw_step(unw_cursor_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 93 | extern int unw_get_reg(unw_cursor_t*, unw_regnum_t, unw_word_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 94 | extern int unw_get_fpreg(unw_cursor_t*, unw_regnum_t, unw_fpreg_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 95 | extern int unw_set_reg(unw_cursor_t*, unw_regnum_t, unw_word_t) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 96 | extern int unw_set_fpreg(unw_cursor_t*, unw_regnum_t, unw_fpreg_t) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 97 | extern int unw_resume(unw_cursor_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 98 | 99 | extern const char* unw_regname(unw_cursor_t*, unw_regnum_t) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 100 | extern int unw_get_proc_info(unw_cursor_t*, unw_proc_info_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 101 | extern int unw_is_fpreg(unw_cursor_t*, unw_regnum_t) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 102 | extern int unw_is_signal_frame(unw_cursor_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 103 | extern int unw_get_proc_name(unw_cursor_t*, char*, size_t, unw_word_t*) __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_5_0); 104 | //extern int unw_get_save_loc(unw_cursor_t*, int, unw_save_loc_t*); 105 | #endif 106 | 107 | 108 | #if UNW_REMOTE 109 | /* 110 | * Mac OS X "remote" API for unwinding other processes on same machine 111 | * 112 | */ 113 | extern unw_addr_space_t unw_local_addr_space; 114 | extern unw_addr_space_t unw_create_addr_space_for_task(task_t); 115 | extern void unw_destroy_addr_space(unw_addr_space_t); 116 | extern int unw_init_remote_thread(unw_cursor_t*, unw_addr_space_t, thread_t*); 117 | #endif 118 | 119 | 120 | /* 121 | * traditional libuwind "remote" API 122 | * NOT IMPLEMENTED on Mac OS X 123 | * 124 | * extern int unw_init_remote(unw_cursor_t*, unw_addr_space_t, thread_t*); 125 | * extern unw_accessors_t unw_get_accessors(unw_addr_space_t); 126 | * extern unw_addr_space_t unw_create_addr_space(unw_accessors_t, int); 127 | * extern void unw_flush_cache(unw_addr_space_t, unw_word_t, unw_word_t); 128 | * extern int unw_set_caching_policy(unw_addr_space_t, unw_caching_policy_t); 129 | * extern void _U_dyn_register(unw_dyn_info_t*); 130 | * extern void _U_dyn_cancel(unw_dyn_info_t*); 131 | */ 132 | 133 | 134 | #ifdef __cplusplus 135 | } 136 | #endif 137 | 138 | 139 | // architecture independent register numbers 140 | enum { 141 | UNW_REG_IP = -1, // instruction pointer 142 | UNW_REG_SP = -2, // stack pointer 143 | }; 144 | 145 | 146 | // 32-bit x86 registers 147 | enum { 148 | UNW_X86_EAX = 0, 149 | UNW_X86_ECX = 1, 150 | UNW_X86_EDX = 2, 151 | UNW_X86_EBX = 3, 152 | UNW_X86_EBP = 4, 153 | UNW_X86_ESP = 5, 154 | UNW_X86_ESI = 6, 155 | UNW_X86_EDI = 7 156 | }; 157 | 158 | 159 | // 64-bit x86_64 registers 160 | enum { 161 | UNW_X86_64_RAX = 0, 162 | UNW_X86_64_RDX = 1, 163 | UNW_X86_64_RCX = 2, 164 | UNW_X86_64_RBX = 3, 165 | UNW_X86_64_RSI = 4, 166 | UNW_X86_64_RDI = 5, 167 | UNW_X86_64_RBP = 6, 168 | UNW_X86_64_RSP = 7, 169 | UNW_X86_64_R8 = 8, 170 | UNW_X86_64_R9 = 9, 171 | UNW_X86_64_R10 = 10, 172 | UNW_X86_64_R11 = 11, 173 | UNW_X86_64_R12 = 12, 174 | UNW_X86_64_R13 = 13, 175 | UNW_X86_64_R14 = 14, 176 | UNW_X86_64_R15 = 15 177 | }; 178 | 179 | 180 | // 32-bit ppc register numbers 181 | enum { 182 | UNW_PPC_R0 = 0, 183 | UNW_PPC_R1 = 1, 184 | UNW_PPC_R2 = 2, 185 | UNW_PPC_R3 = 3, 186 | UNW_PPC_R4 = 4, 187 | UNW_PPC_R5 = 5, 188 | UNW_PPC_R6 = 6, 189 | UNW_PPC_R7 = 7, 190 | UNW_PPC_R8 = 8, 191 | UNW_PPC_R9 = 9, 192 | UNW_PPC_R10 = 10, 193 | UNW_PPC_R11 = 11, 194 | UNW_PPC_R12 = 12, 195 | UNW_PPC_R13 = 13, 196 | UNW_PPC_R14 = 14, 197 | UNW_PPC_R15 = 15, 198 | UNW_PPC_R16 = 16, 199 | UNW_PPC_R17 = 17, 200 | UNW_PPC_R18 = 18, 201 | UNW_PPC_R19 = 19, 202 | UNW_PPC_R20 = 20, 203 | UNW_PPC_R21 = 21, 204 | UNW_PPC_R22 = 22, 205 | UNW_PPC_R23 = 23, 206 | UNW_PPC_R24 = 24, 207 | UNW_PPC_R25 = 25, 208 | UNW_PPC_R26 = 26, 209 | UNW_PPC_R27 = 27, 210 | UNW_PPC_R28 = 28, 211 | UNW_PPC_R29 = 29, 212 | UNW_PPC_R30 = 30, 213 | UNW_PPC_R31 = 31, 214 | UNW_PPC_F0 = 32, 215 | UNW_PPC_F1 = 33, 216 | UNW_PPC_F2 = 34, 217 | UNW_PPC_F3 = 35, 218 | UNW_PPC_F4 = 36, 219 | UNW_PPC_F5 = 37, 220 | UNW_PPC_F6 = 38, 221 | UNW_PPC_F7 = 39, 222 | UNW_PPC_F8 = 40, 223 | UNW_PPC_F9 = 41, 224 | UNW_PPC_F10 = 42, 225 | UNW_PPC_F11 = 43, 226 | UNW_PPC_F12 = 44, 227 | UNW_PPC_F13 = 45, 228 | UNW_PPC_F14 = 46, 229 | UNW_PPC_F15 = 47, 230 | UNW_PPC_F16 = 48, 231 | UNW_PPC_F17 = 49, 232 | UNW_PPC_F18 = 50, 233 | UNW_PPC_F19 = 51, 234 | UNW_PPC_F20 = 52, 235 | UNW_PPC_F21 = 53, 236 | UNW_PPC_F22 = 54, 237 | UNW_PPC_F23 = 55, 238 | UNW_PPC_F24 = 56, 239 | UNW_PPC_F25 = 57, 240 | UNW_PPC_F26 = 58, 241 | UNW_PPC_F27 = 59, 242 | UNW_PPC_F28 = 60, 243 | UNW_PPC_F29 = 61, 244 | UNW_PPC_F30 = 62, 245 | UNW_PPC_F31 = 63, 246 | UNW_PPC_MQ = 64, 247 | UNW_PPC_LR = 65, 248 | UNW_PPC_CTR = 66, 249 | UNW_PPC_AP = 67, 250 | UNW_PPC_CR0 = 68, 251 | UNW_PPC_CR1 = 69, 252 | UNW_PPC_CR2 = 70, 253 | UNW_PPC_CR3 = 71, 254 | UNW_PPC_CR4 = 72, 255 | UNW_PPC_CR5 = 73, 256 | UNW_PPC_CR6 = 74, 257 | UNW_PPC_CR7 = 75, 258 | UNW_PPC_XER = 76, 259 | UNW_PPC_V0 = 77, 260 | UNW_PPC_V1 = 78, 261 | UNW_PPC_V2 = 79, 262 | UNW_PPC_V3 = 80, 263 | UNW_PPC_V4 = 81, 264 | UNW_PPC_V5 = 82, 265 | UNW_PPC_V6 = 83, 266 | UNW_PPC_V7 = 84, 267 | UNW_PPC_V8 = 85, 268 | UNW_PPC_V9 = 86, 269 | UNW_PPC_V10 = 87, 270 | UNW_PPC_V11 = 88, 271 | UNW_PPC_V12 = 89, 272 | UNW_PPC_V13 = 90, 273 | UNW_PPC_V14 = 91, 274 | UNW_PPC_V15 = 92, 275 | UNW_PPC_V16 = 93, 276 | UNW_PPC_V17 = 94, 277 | UNW_PPC_V18 = 95, 278 | UNW_PPC_V19 = 96, 279 | UNW_PPC_V20 = 97, 280 | UNW_PPC_V21 = 98, 281 | UNW_PPC_V22 = 99, 282 | UNW_PPC_V23 = 100, 283 | UNW_PPC_V24 = 101, 284 | UNW_PPC_V25 = 102, 285 | UNW_PPC_V26 = 103, 286 | UNW_PPC_V27 = 104, 287 | UNW_PPC_V28 = 105, 288 | UNW_PPC_V29 = 106, 289 | UNW_PPC_V30 = 107, 290 | UNW_PPC_V31 = 108, 291 | UNW_PPC_VRSAVE = 109, 292 | UNW_PPC_VSCR = 110, 293 | UNW_PPC_SPE_ACC = 111, 294 | UNW_PPC_SPEFSCR = 112 295 | 296 | }; 297 | 298 | 299 | 300 | 301 | #endif 302 | 303 | -------------------------------------------------------------------------------- /Sources/Clibunwind/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module Clibunwind [extern_c] { 2 | umbrella header "Clibunwind.h" 3 | module * { export * } 4 | } 5 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/BacktraceFormatter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - BacktraceFormatter 4 | 5 | public struct BacktraceFormatter { 6 | let symbolFormatter: SymbolFormatter 7 | let postProcessor: PostProcessor 8 | 9 | public init(_ symbolFormatter: SymbolFormatter = .default, _ postProcessor: PostProcessor = .default) { 10 | self.symbolFormatter = symbolFormatter 11 | self.postProcessor = postProcessor 12 | } 13 | 14 | // MARK: - Predefined formatter 15 | public static let demangled = BacktraceFormatter(.demangledDefault) 16 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 17 | public static let simplifiedDemangled = BacktraceFormatter(.simplifiedDemangledDefault) 18 | #endif // os(macOS) || (os(Linux) && swift(>=4.1)) 19 | } 20 | 21 | // MARK: - Converter 22 | 23 | public struct Converter { 24 | public typealias Handler = (Input) -> Output 25 | let handler: Handler 26 | 27 | public init(_ handler: @escaping Handler) { 28 | self.handler = handler 29 | } 30 | 31 | public func compose(_ other: Converter) -> Converter { 32 | return .init { self.handler(other.handler($0)) } 33 | } 34 | } 35 | 36 | // MARK: - Demangler 37 | 38 | /// Demangler = (Symbol) -> Symbol 39 | public typealias Demangler = Converter 40 | 41 | extension Converter where Input == Symbol, Output == Symbol { 42 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 43 | public static let `default` = simplified.compose(cxxDemangle) 44 | #else 45 | public static let `default` = demangle.compose(cxxDemangle) 46 | #endif 47 | 48 | /// Demangle `Symbol.name` as C++ function names. 49 | public static let cxxDemangle = Converter { symbol -> Symbol in 50 | var symbol = symbol 51 | symbol.name = cxxDemangleName(symbol.name) 52 | return symbol 53 | } 54 | 55 | /// Demangle `Symbol.name` as Swift function names. 56 | public static let demangle = Converter { symbol -> Symbol in 57 | var symbol = symbol 58 | symbol.name = swiftDemangleName(symbol.name) 59 | return symbol 60 | } 61 | 62 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 63 | /// Demangle `Symbol.name` as Swift function names with module names and implicit self 64 | /// and metatype type names in function signatures stripped. 65 | public static let simplified = Converter { symbol -> Symbol in 66 | var symbol = symbol 67 | symbol.name = swiftSimplifiedDemangleName(symbol.name) 68 | return symbol 69 | } 70 | #endif // os(macOS) || (os(Linux) && swift(>=4.1)) 71 | } 72 | 73 | // MARK: - SymbolFormatter 74 | 75 | /// Convert `Symbol` to `String` 76 | public typealias SymbolFormatter = Converter 77 | 78 | extension Converter where Input == Symbol, Output == String { 79 | // Swift 4.0.3 or earlier can't infer types of `default` without type annotation 80 | public static let `default`: SymbolFormatter = defaultStyle.compose(.default) 81 | 82 | public static let demangledDefault = defaultStyle.compose(.demangle) 83 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 84 | public static let simplifiedDemangledDefault = defaultStyle.compose(.simplified) 85 | #endif // os(macOS) || (os(Linux) && swift(>=4.1)) 86 | 87 | #if os(macOS) 88 | public static let defaultStyle = darwinStyleFormat 89 | #elseif os(Linux) 90 | public static let defaultStyle = linuxStyleFormat 91 | #endif 92 | 93 | /// Format `Symbol` into darwin style backtrace 94 | public static let darwinStyleFormat = Converter { symbol -> String in 95 | let (module, name, offset, address) = symbol 96 | let basename = URL(fileURLWithPath: module).lastPathComponent.ljust(35) 97 | return "\(basename) \(address?.debugDescription ?? "") \(name) + \(offset)" 98 | } 99 | 100 | /// Format `Symbol` into linux style backtrace 101 | public static let linuxStyleFormat = Converter { symbol -> String in 102 | let (module, name, offset, address) = symbol 103 | func hex(_ int: T) -> String { 104 | return "0x" + .init(int, radix: 16, uppercase: false) 105 | } 106 | return "\(module)(\(name)+\(hex(offset))) [\(address?.debugDescription ?? "0x0")]" 107 | } 108 | } 109 | 110 | // MARK: - PostProcessor 111 | 112 | /// Convert `[String]` to `[String]` 113 | public typealias PostProcessor = Converter<[String], [String]> 114 | 115 | extension Converter where Input == [String], Output == [String] { 116 | #if os(macOS) 117 | public static let `default` = prefixNumber 118 | #elseif os(Linux) 119 | public static let `default` = Converter { $0 } 120 | #endif 121 | 122 | /// Prefix line number to each lines in `[String]` 123 | public static let prefixNumber = Converter { lines -> [String] in 124 | let countStringLength = max(String(lines.count).count + 1, 4) 125 | return lines.enumerated().map { String($0.offset).ljust(countStringLength) + $0.element } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/Demangle.swift: -------------------------------------------------------------------------------- 1 | import CSwiftBacktrace 2 | import Foundation 3 | 4 | public func cxxDemangleName(_ mangledName: String) -> String { 5 | let utf8CString = mangledName.utf8CString 6 | return utf8CString.withUnsafeBufferPointer { buffer in 7 | guard let demangled = cxx_demangle(buffer.baseAddress!, nil, nil, nil) else { return nil } 8 | defer { free(demangled) } 9 | return String(cString: demangled) 10 | } ?? mangledName 11 | } 12 | 13 | public func swiftDemangleName(_ mangledName: String) -> String { 14 | let utf8CString = mangledName.utf8CString 15 | return utf8CString.withUnsafeBufferPointer { buffer in 16 | guard let demangled = swift_demangle(buffer.baseAddress!, buffer.count - 1, nil, nil, 0) else { return nil } 17 | defer { free(demangled) } 18 | return String(cString: demangled) 19 | } ?? mangledName 20 | } 21 | 22 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 23 | 24 | public func swiftSimplifiedDemangleName(_ mangledName: String) -> String { 25 | let size = swift_demangle_getSimplifiedDemangledName(mangledName, nil, 0) + 1 26 | guard size > 1 else { return mangledName } 27 | let buffer = UnsafeMutablePointer.allocate(capacity: size) 28 | #if swift(>=4.1) 29 | defer { buffer.deallocate() } 30 | #else 31 | defer { buffer.deallocate(capacity: size) } 32 | #endif 33 | _ = swift_demangle_getSimplifiedDemangledName(mangledName, buffer, size) 34 | return String(cString: buffer) 35 | } 36 | 37 | #if os(macOS) 38 | let searchPaths: [String] = { 39 | let process = Process(), pipe = Pipe() 40 | process.launchPath = "/usr/bin/env" 41 | process.arguments = ["xcrun", "--find", "swift"] 42 | process.standardOutput = pipe 43 | process.launch() 44 | process.waitUntilExit() 45 | let output = pipe.fileHandleForReading.readDataToEndOfFile() 46 | guard let swiftURL = String(data: output, encoding: .utf8).map(URL.init(fileURLWithPath:)), 47 | let libPath = URL(string: "../lib", relativeTo: swiftURL)?.path else { return [] } 48 | return [libPath] 49 | }() 50 | let libswiftDemangle = Loader(searchPaths: searchPaths).load(path: "libswiftDemangle.dylib") 51 | #elseif os(Linux) 52 | let libswiftDemangle = Loader(searchPaths: []).load(path: "libswiftDemangle.so") 53 | #endif 54 | 55 | // swiftlint:disable:next identifier_name 56 | let swift_demangle_getSimplifiedDemangledName: @convention(c) ( 57 | _ MangledName: UnsafePointer?, 58 | _ OutputBuffer: UnsafeMutablePointer?, 59 | _ Length: Int) -> Int = libswiftDemangle.load(symbols: ["swift_demangle_getSimplifiedDemangledName"]) 60 | 61 | #endif // os(macOS) || (os(Linux) && swift(>=4.1)) 62 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/DynamicLinkLibrary.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct DynamicLinkLibrary { 4 | let path: String 5 | let handle: UnsafeMutableRawPointer 6 | 7 | func load(symbols: [String]) -> T { 8 | for symbol in symbols { 9 | if let sym = dlsym(handle, symbol) { 10 | return unsafeBitCast(sym, to: T.self) 11 | } 12 | } 13 | let errorString = String(validatingUTF8: dlerror()) 14 | fatalError("Finding symbol \(symbols.joined(separator: ",")) failed: \(errorString ?? "unknown error")") 15 | } 16 | } 17 | 18 | struct Loader { 19 | let searchPaths: [String] 20 | 21 | func load(path: String) -> DynamicLinkLibrary { 22 | let fullPaths = searchPaths 23 | .map { URL(fileURLWithPath: $0).appendingPathComponent(path).path } 24 | .filter(FileManager.default.fileExists(atPath:)) 25 | 26 | // try all fullPaths that contains target file, 27 | // then try loading with simple path that depends resolving to DYLD 28 | for fullPath in fullPaths + [path] { 29 | if let handle = dlopen(fullPath, RTLD_LAZY) { 30 | return DynamicLinkLibrary(path: path, handle: handle) 31 | } 32 | } 33 | let errorString = String(validatingUTF8: dlerror()) 34 | fatalError("Loading \(path) failed: \(errorString ?? "unknown error")") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/String+extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | /// Return the string left justified in a string of length width. 5 | /// Padding is done using the specified fillchar (default is an ASCII space). 6 | /// The original string is returned if width is less than or equal to count. 7 | func ljust(_ width: Int, _ fillChar: Character = " ") -> String { 8 | guard count < width else { return self } 9 | return appending(String(repeating: fillChar, count: width - count)) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/SwiftBacktrace.swift: -------------------------------------------------------------------------------- 1 | /// Produce backtrace 2 | public func backtrace(_ maxSize: Int = 32, formatter: BacktraceFormatter = BacktraceFormatter()) -> [String] { 3 | return formatter.postProcessor.handler(callStackSymbols(maxSize, transform: formatter.symbolFormatter.handler)) 4 | } 5 | 6 | public func demangledBacktrace(_ maxSize: Int = 32) -> [String] { 7 | return backtrace(maxSize, formatter: .demangled) 8 | } 9 | 10 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 11 | public func simplifiedDemangledBacktrace(_ maxSize: Int = 32) -> [String] { 12 | return backtrace(maxSize, formatter: .simplifiedDemangled) 13 | } 14 | #endif // os(macOS) || (os(Linux) && swift(>=4.1)) 15 | 16 | public func enablePrettyStackTrace() { 17 | _enablePrettyStackTrace 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/Unwind.swift: -------------------------------------------------------------------------------- 1 | import Clibunwind 2 | import CSwiftBacktrace 3 | 4 | public typealias Symbol = (module: String, name: String, offset: UInt64, address: UnsafeRawPointer?) 5 | 6 | public func callStackSymbols(_ maxSize: Int = 32, transform: (Symbol) -> T) -> [T] { 7 | let symbols = CallStackSymbols() 8 | _ = unw_getcontext(symbols.context) 9 | _ = unw_init_local(symbols.cursor, symbols.context) 10 | return symbols.prefix(maxSize).map(transform) 11 | } 12 | 13 | class CallStackSymbols: Sequence, IteratorProtocol { 14 | var context = UnsafeMutablePointer.allocate(capacity: 1) 15 | var cursor = UnsafeMutablePointer.allocate(capacity: 1) 16 | var ended = false 17 | 18 | deinit { 19 | #if swift(>=4.1) 20 | cursor.deallocate() 21 | context.deallocate() 22 | #else 23 | cursor.deallocate(capacity: 1) 24 | context.deallocate(capacity: 1) 25 | #endif 26 | } 27 | 28 | func next() -> Symbol? { 29 | return symbol() 30 | } 31 | 32 | func symbol() -> Symbol? { 33 | guard !ended else { return nil } 34 | let (name, offset) = nameAndOffset() 35 | let address = self.address() 36 | let module = dli_fname(address).map(String.init(cString:)) ?? CommandLine.arguments[0] 37 | ended = !(step() > 0) 38 | return (module: module, name: name, offset: offset, address: address) 39 | } 40 | 41 | func address() -> UnsafeRawPointer? { 42 | var addressNumber = unw_word_t() 43 | #if os(macOS) 44 | _ = unw_get_reg(cursor, unw_regnum_t(UNW_REG_IP), &addressNumber) 45 | #elseif os(Linux) 46 | _ = unw_get_reg(cursor, unw_regnum_t(UNW_TDEP_IP.rawValue), &addressNumber) 47 | #endif 48 | return UnsafeRawPointer(bitPattern: UInt(addressNumber)) 49 | } 50 | 51 | func nameAndOffset() -> (name: String, offset: UInt64) { 52 | var buffer = UnsafeMutablePointer.allocate(capacity: 1024) 53 | #if swift(>=4.1) 54 | defer { buffer.deallocate() } 55 | #else 56 | defer { buffer.deallocate(capacity: 1024) } 57 | #endif 58 | var offset = unw_word_t() 59 | _ = unw_get_proc_name(cursor, buffer, 1024, &offset) 60 | return (String(cString: buffer), offset) 61 | } 62 | 63 | func step() -> Int32 { 64 | return unw_step(cursor) 65 | } 66 | } 67 | 68 | // swiftlint:disable identifier_name 69 | 70 | // MARK: - Dynamic loading `libunwind` 71 | 72 | #if os(macOS) 73 | let libunwind = Loader(searchPaths: ["/usr/lib/system"]).load(path: "libunwind.dylib") 74 | #elseif os(Linux) 75 | let libunwind = Loader(searchPaths: []).load(path: "libunwind.so.8") 76 | #endif 77 | 78 | #if arch(x86_64) 79 | let UNW_TARGET = "x86_64" 80 | #elseif arch(i386) 81 | let UNW_TARGET = "x86" 82 | #else 83 | 84 | #endif 85 | 86 | func UNW_ARCH_OBJ(_ fn: String) -> T { 87 | return libunwind.load(symbols: ["unw_" + fn, "_U\(UNW_TARGET)_\(fn)"]) 88 | } 89 | func UNW_OBJ(_ fn: String) -> T { 90 | return libunwind.load(symbols: ["unw_" + fn, "_UL\(UNW_TARGET)_\(fn)"]) 91 | } 92 | 93 | let unw_getcontext: @convention(c) ( 94 | _ ucp: UnsafeMutablePointer) -> Int32 = UNW_ARCH_OBJ("getcontext") 95 | 96 | let unw_init_local: @convention(c) ( 97 | _ cp: UnsafeMutablePointer?, 98 | _ ucp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("init_local") 99 | 100 | let unw_step: @convention(c) ( 101 | _ cp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("step") 102 | 103 | let unw_get_reg: @convention(c) ( 104 | _ cp: UnsafeMutablePointer?, 105 | _ reg: unw_regnum_t, 106 | _ valp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("get_reg") 107 | 108 | let unw_get_fpreg: @convention(c) ( 109 | _ cp: UnsafeMutablePointer?, 110 | _ reg: unw_regnum_t, 111 | _ valp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("get_fpreg") 112 | 113 | let unw_set_reg: @convention(c) ( 114 | _ cp: UnsafeMutablePointer?, 115 | _ reg: unw_regnum_t, 116 | _ val: unw_word_t) -> Int32 = UNW_OBJ("set_reg") 117 | 118 | let unw_set_fpreg: @convention(c) ( 119 | _ cp: UnsafeMutablePointer?, 120 | _ reg: unw_regnum_t, 121 | _ val: unw_fpreg_t) -> Int32 = UNW_OBJ("set_fpreg") 122 | 123 | let unw_resume: @convention(c) ( 124 | _ cp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("resume") 125 | 126 | let unw_regname: @convention(c) ( 127 | _ cp: UnsafeMutablePointer?, 128 | _ reg: unw_regnum_t) -> UnsafePointer? = UNW_ARCH_OBJ("regname") 129 | 130 | let unw_get_proc_info: @convention(c) ( 131 | _ cp: UnsafeMutablePointer?, 132 | _ pip: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("get_proc_info") 133 | 134 | let unw_is_fpreg: @convention(c) ( 135 | _ cp: UnsafeMutablePointer?, _ reg: unw_regnum_t) -> Int32 = UNW_ARCH_OBJ("is_fpreg") 136 | 137 | let unw_is_signal_frame: @convention(c) ( 138 | _ cp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("is_signal_frame") 139 | 140 | let unw_get_proc_name: @convention(c) ( 141 | _ cp: UnsafeMutablePointer?, 142 | _ bufp: UnsafeMutablePointer?, 143 | _ len: Int, 144 | _ offp: UnsafeMutablePointer?) -> Int32 = UNW_OBJ("get_proc_name") 145 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/shim.swift: -------------------------------------------------------------------------------- 1 | #if !swift(>=4.1) 2 | 3 | extension Sequence { 4 | func compactMap( 5 | _ transform: (Self.Element 6 | ) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { 7 | return try flatMap(transform) 8 | } 9 | } 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /Sources/SwiftBacktrace/sigaction.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | import Foundation 3 | import NIOConcurrencyHelpers 4 | 5 | // swiftlint:disable identifier_name 6 | 7 | // MARK: - enablePrettyStackTrace 8 | 9 | let _enablePrettyStackTrace: Void = { 10 | addSignalHandler { 11 | fputs(backtrace().joined(separator: "\n"), stderr) 12 | } 13 | }() 14 | 15 | // MARK: - Signals 16 | 17 | let intSignals = [SIGHUP, SIGINT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2] 18 | let killSignals: [Int32] = { 19 | var signals = [SIGILL, SIGTRAP, SIGABRT, SIGFPE, SIGBUS, SIGSEGV, SIGQUIT] 20 | #if os(macOS) || os(Linux) 21 | signals.append(SIGSYS) 22 | signals.append(SIGXCPU) 23 | signals.append(SIGXFSZ) 24 | #endif 25 | #if os(macOS) 26 | signals.append(SIGEMT) 27 | #endif 28 | return signals 29 | }() 30 | 31 | // MARK: - Alt Stack 32 | extension stack_t { 33 | static func allocate(size: Int) -> stack_t { 34 | var stack = stack_t() 35 | #if swift(>=4.1) 36 | stack.ss_sp = UnsafeMutableRawPointer.allocate(byteCount: numericCast(size), alignment: 1) 37 | #else 38 | stack.ss_sp = UnsafeMutableRawPointer.allocate(bytes: numericCast(size), alignedTo: 1) 39 | #endif 40 | stack.ss_size = numericCast(size) 41 | return stack 42 | } 43 | 44 | func deallocate() { 45 | #if swift(>=4.1) 46 | ss_sp.deallocate() 47 | #else 48 | ss_sp.deallocate(bytes: numericCast(ss_size), alignedTo: 1) 49 | #endif 50 | } 51 | } 52 | 53 | private func createAltStack() { 54 | let altStackSize = MINSIGSTKSZ + 64 * 1024 55 | var oldAltStack = stack_t() 56 | 57 | guard sigaltstack(nil, &oldAltStack) == 0 && 58 | oldAltStack.ss_flags & numericCast(SS_ONSTACK) == 0 && // Thread is not currently executing on oldAltStack 59 | !(oldAltStack.ss_sp != nil && oldAltStack.ss_size > altStackSize) // oldAltStack does not have sufficient size 60 | else { return } 61 | 62 | var altStack = stack_t.allocate(size: numericCast(altStackSize)) 63 | if sigaltstack(&altStack, &oldAltStack) != 0 { 64 | altStack.deallocate() 65 | } 66 | } 67 | 68 | // MARK: - Register Handlers 69 | 70 | extension sigaction { 71 | init(_ action: @escaping @convention(c) (Int32) -> Void, _ sa_flags: Int32 = 0) { 72 | #if _runtime(_ObjC) 73 | self.init() 74 | self.__sigaction_u.__sa_handler = action 75 | #elseif os(Linux) 76 | self.init() 77 | self.__sigaction_handler.sa_handler = action 78 | #else 79 | self.init() 80 | #if swift(>=4.1.50) 81 | #warning("unsupported platform") 82 | #endif 83 | #endif 84 | } 85 | } 86 | 87 | private struct SignalInfo { 88 | var oldAction = sigaction() 89 | var signal: Int32 90 | init(_ signal: Int32) { 91 | self.signal = signal 92 | } 93 | } 94 | 95 | private var registeredSignalInfo = [SignalInfo]() 96 | private let queue = DispatchQueue(label: "registerHandlers()") 97 | 98 | private func registerHandlers() { 99 | queue.sync { 100 | // If the handlers are already registered, we're done. 101 | guard registeredSignalInfo.isEmpty else { return } 102 | 103 | // Create an alternate stack for signal handling. This is necessary for us to 104 | // be able to reliably handle signals due to stack overflow. 105 | createAltStack() 106 | 107 | (intSignals + killSignals).forEach { signal in 108 | let sa_flags = Int32(SA_NODEFER) | Int32(bitPattern: UInt32(SA_RESETHAND)) | Int32(SA_ONSTACK) 109 | var newAction = sigaction(signalHandler, sa_flags) 110 | var signalInfo = SignalInfo(signal) 111 | // Install the new handler, save the old one in RegisteredSignalInfo. 112 | sigaction(signal, &newAction, &signalInfo.oldAction) 113 | registeredSignalInfo.append(signalInfo) 114 | } 115 | } 116 | } 117 | 118 | private func unregisterhandlers() { 119 | queue.sync { 120 | // Restore all of the signal handlers to how they were before we showed up. 121 | registeredSignalInfo.forEach { signalInfo in 122 | var signalInfo = signalInfo 123 | sigaction(signalInfo.signal, &signalInfo.oldAction, nil) 124 | } 125 | registeredSignalInfo.removeAll(keepingCapacity: true) 126 | } 127 | } 128 | 129 | private func signalHandler(signal: Int32) { 130 | // Restore the signal behavior to default, so that the program actually 131 | // crashes when we return and the signal reissues. This also ensures that if 132 | // we crash in our signal handler that the program will terminate immediately 133 | // instead of recursing in the signal handler. 134 | unregisterhandlers() 135 | 136 | // Unmask all potentially blocked kill signals. 137 | var sigMask = sigset_t() 138 | sigfillset(&sigMask) 139 | sigprocmask(SIG_UNBLOCK, &sigMask, nil) 140 | 141 | if intSignals.contains(signal) { 142 | if let oldInterruptFunction = removeInterruptFunction() { 143 | oldInterruptFunction(signal) 144 | } 145 | raise(signal) // Execute the default handler. 146 | return 147 | } 148 | 149 | runSignalHandlers() 150 | } 151 | 152 | // MARK: - addSignalHandler 153 | 154 | /// AddSignalHandler - Add a function to be called when an abort/kill signal 155 | /// is delivered to the process. 156 | public func addSignalHandler(_ callback: @escaping () -> Void) { 157 | insertSignalHandler(callback) 158 | registerHandlers() 159 | } 160 | 161 | final class Callback { 162 | enum State: Int { case empty, initializing, initialized, executing } 163 | var state = Atomic(value: State.empty.rawValue) 164 | var callback: (() -> Void)? 165 | 166 | private var _valuePtr: UnsafeMutablePointer { 167 | return _getUnsafePointerToStoredProperties(self).assumingMemoryBound( 168 | to: Int.self) 169 | } 170 | 171 | func storeState(_ desired: State) { 172 | return state.store(desired.rawValue) 173 | } 174 | 175 | func compareExchangeState(expected: inout State, desired: State) -> Bool { 176 | let expectedVar = expected.rawValue 177 | let result = state.compareAndExchange(expected: expectedVar, desired: desired.rawValue) 178 | expected = State(rawValue: expectedVar)! 179 | return result 180 | } 181 | } 182 | 183 | let maxSignalHandlerCallbacks = 8 184 | var callbacksToRun = Array(repeating: Callback(), count: maxSignalHandlerCallbacks) 185 | 186 | private func runSignalHandlers() { 187 | for index in callbacksToRun.indices { 188 | var expected = Callback.State.initialized 189 | if !callbacksToRun[index].compareExchangeState(expected: &expected, desired: .executing) { 190 | continue 191 | } 192 | callbacksToRun[index].callback?() 193 | callbacksToRun[index].callback = nil 194 | callbacksToRun[index].storeState(.empty) 195 | } 196 | } 197 | 198 | private func insertSignalHandler(_ callback: @escaping () -> Void) { 199 | for index in callbacksToRun.indices { 200 | var expected = Callback.State.empty 201 | if !callbacksToRun[index].compareExchangeState(expected: &expected, desired: .initializing) { 202 | continue 203 | } 204 | callbacksToRun[index].callback = callback 205 | callbacksToRun[index].storeState(.initialized) 206 | return 207 | } 208 | fatalError("too many signal callbacks already registered") 209 | } 210 | 211 | //// Interrupt Function 212 | public typealias InterruptFunction = @convention(c) (Int32) -> Void 213 | var interruptFunctionPtr = Atomic(value: 0) 214 | 215 | public func setInterruptFunction(_ desired: InterruptFunction?) { 216 | interruptFunctionPtr.store(unsafeBitCast(desired, to: Int.self)) 217 | registerHandlers() 218 | } 219 | 220 | func removeInterruptFunction() -> InterruptFunction? { 221 | let result = interruptFunctionPtr.exchange(with: 0) 222 | return unsafeBitCast(result, to: InterruptFunction?.self) 223 | } 224 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftBacktraceTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftBacktraceTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SwiftBacktraceTests/SwiftBacktraceTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import SwiftBacktrace 4 | 5 | final class SwiftBacktraceTests: XCTestCase { 6 | func test_backtrace() { 7 | #if os(macOS) || os(Linux) && swift(>=4.1) 8 | print("--- Thread.callStackSymbols") 9 | print(Thread.callStackSymbols.joined(separator: "\n")) 10 | #endif // os(macOS) || os(Linux) && swift(>=4.1) 11 | print("--- backtrace()") 12 | print(backtrace().joined(separator: "\n")) 13 | print("--- demangledBacktrace()") 14 | print(demangledBacktrace().joined(separator: "\n")) 15 | #if os(macOS) || (os(Linux) && swift(>=4.1)) 16 | print("--- simplifiedDemangledBacktrace()") 17 | print(simplifiedDemangledBacktrace().joined(separator: "\n")) 18 | #endif // os(macOS) || (os(Linux) && swift(>=4.1)) 19 | } 20 | 21 | func test_cxxDemangle() { 22 | XCTAssertEqual(cxxDemangleName("_ZN10sourcekitd13handleRequestEPvSt8functionIFvS0_EE"), 23 | "sourcekitd::handleRequest(void*, std::function)") 24 | } 25 | 26 | func test_enablePrettyStackTrace() { 27 | enablePrettyStackTrace() 28 | raise(SIGABRT) 29 | } 30 | 31 | func test_setInterruptFunction() { 32 | setInterruptFunction { _ in 33 | print("interrupted") 34 | } 35 | // we can't test interrupt function yet. 36 | // raise(SIGTERM) 37 | } 38 | 39 | static var allTests = [ 40 | ("test_backtrace", test_backtrace), 41 | ("test_cxxDemangle", test_cxxDemangle), 42 | ("test_enablePrettyStackTrace", test_enablePrettyStackTrace), 43 | ("test_setInterruptFunction", test_setInterruptFunction) 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /Tests/SwiftBacktraceTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftBacktraceTests.allTests) 7 | ] 8 | } 9 | #endif 10 | --------------------------------------------------------------------------------