获取任意函数调用栈目前有两种方式。第一方式拿到栈的指针(StackPointer)以及栈帧指针(FramePointer),递归到栈底。
系统提供了 task_threads 方法,可以获取到所有的线程,注意这里的线程是最底层的 mach 线程.
对于每一个线程,可以用 thread_get_state 方法获取它的所有信息,信息填充在 _STRUCT_MCONTEXT 类型的参数中(这个方法中有两个参数随着 CPU 架构的不同而改变).
我们需要存储线程的StackPointer以及 顶部的FramePointer, 通过递归获取到整个调用栈.
根据栈帧的 Frame Pointer 获取到这个函数调用的符号名
实现思路:
这种方式是 KSCrash 的作者想到的,他曾提过一个问题 Printing a stack trace from another thread ,不过最后他自己想出这种方式给解决了。bestswifter基于此写了 BSBacktraceLogger ,在OC中还是很好用的,但是在Swift没法很好的打印出结果,不知道为什么,有知道的还希望能告知一下。
在这个提问下 Printing a stack trace from another thread ,有人通过Signal handling实现了。
这里介绍一下大致需要了解的知识点。
信号的本质
是软件层次上对中断的一种模拟。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。
信号操作最常用的方法是信号的屏蔽,信号屏蔽主要用到以下几个函数:
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 复制代码