獲取任意線程調用棧目前有兩種方式。第一方式拿到棧的指針(StackPointer)以及棧幀指針(FramePointer),遞歸到棧底。html
系統提供了 task_threads 方法,能夠獲取到全部的線程,注意這裏的線程是最底層的 mach 線程.git
對於每個線程,能夠用 thread_get_state 方法獲取它的全部信息,信息填充在 _STRUCT_MCONTEXT 類型的參數中(這個方法中有兩個參數隨着 CPU 架構的不一樣而改變).github
咱們須要存儲線程的StackPointer以及 頂部的FramePointer, 經過遞歸獲取到整個調用棧.swift
根據棧幀的 Frame Pointer 獲取到這個函數調用的符號名bash
實現思路:markdown
這種方式是KSCrash的做者想到的,他曾提過一個問題Printing a stack trace from another thread,不過最後他本身想出這種方式給解決了。bestswifter基於此寫了BSBacktraceLogger,在OC中仍是很好用的,可是在Swift無法很好的打印出結果,不知道爲何,有知道的還但願能告知一下。架構
在這個提問下Printing a stack trace from another thread,有人經過Signal handling實現了。異步
這裏介紹一下大體須要瞭解的知識點。函數
信號的本質
是軟件層次上對中斷的一種模擬。它是一種異步通訊的處理機制,事實上,進程並不知道信號什麼時候到來。oop
信號來源:
信號處理函數的過程
基本的信號處理函數
信號操做最經常使用的方法是信號的屏蔽,信號屏蔽主要用到如下幾個函數:
int sigemptyset(sigset_t *set): 函數初始化信號集set並將set設置爲空
int sigfillset(sigset_t *set):函數初始化信號集,但將信號集set設置爲全部信號的集合。
int sigaddset(sigset_t *set,int signo):將信號signo加入到信號集中去
int sigdelset(sigset_t *set,int signo):從信號集中刪除signo信號。
int sigismemeber(sigset_t* set,int signo):檢測信號是否被掛起。
int sigprocmask(int how,const sigset_t*set,sigset_t *oset):將指定的信號集合加入到進程的信號阻塞集合中去。若是提供了oset,那麼當前的信號阻塞集合將會保存到oset集全中去。
對於信號集的初始化有兩種方法: 一種是用sigemptyset使信號集中不包含任何信號,而後用sigaddset把信號加入到信號集中去。 另外一種是用sigfillset讓信號集中包含全部信號,而後用sigdelset刪除信號來初始化。
1.經過sigaction註冊信號處理函數
private func setupCallStackSignalHandler() { let action = __sigaction_u(__sa_sigaction: signalHandler) var sigActionNew = sigaction(__sigaction_u: action, sa_mask: sigset_t(), sa_flags: SA_SIGINFO) if sigaction(SIGUSR2, &sigActionNew, nil) != 0 { return } } private func signalHandler(code: Int32, info: UnsafeMutablePointer<__siginfo>?, uap: UnsafeMutableRawPointer?) -> Void { guard pthread_self() == targetThread else { return } callstack = frame() } 複製代碼
2.經過pthread_kill()向指定線程發送某個信號
if pthread_kill(threadId, SIGUSR2) != 0 { return nil } 複製代碼
3.在信號處理函數中經過backtrace得到函數調用棧(也可使用NSThread.callstackSymbols)
4. 而後遍歷經過dladdr得到某個地址符號信息
5. 使用swift_demangle函數進行符號名重整,這個是Swift特有的,能夠看看Swift Name Mangling
6.用sigfillset讓信號集中包含全部信號,而後用sigdelset刪除信號來初始化
var mask = sigset_t()
sigfillset(&mask)
sigdelset(&mask, SIGUSR2)
複製代碼
3,4,5的代碼比較多,我就不貼了,能夠看這裏backtrace-swift,純Swift寫的,代碼也不是不少。
注意在Xcode的時候,由於Xcode屏蔽了signal的回調,咱們須要在lldb中輸入如下命令,signal的回調就能夠進來了
pro hand -p true -s false SIGUSR2 複製代碼
Getting a backtrace of other thread
Synchronization issue with usage of pthread_kill() to terminate thread blocked for I/O
Printing a stack trace from another thread
獲取任意線程調用棧的那些事