通过Signal handling(信号处理)获取任意线程调用栈

获取任意线程调用栈目前有两种方式。第一方式拿到栈的指针(StackPointer)以及栈帧指针(FramePointer),递归到栈底。

系统提供了 task_threads 方法,可以获取到所有的线程,注意这里的线程是最底层的 mach 线程.

对于每一个线程,可以用 thread_get_state 方法获取它的所有信息,信息填充在 _STRUCT_MCONTEXT 类型的参数中(这个方法中有两个参数随着 CPU 架构的不同而改变).

我们需要存储线程的StackPointer以及 顶部的FramePointer, 通过递归获取到整个调用栈.

根据栈帧的 Frame Pointer 获取到这个函数调用的符号名

实现思路:

  1. 获取线程的StackPointer 以及 FramePointer
  2. 找到FramePointer属于哪一个镜像文件(.m)
  3. 获取镜像文件的符号表
  4. 在符号表中找到函数调用地址对应的符号名
  5. return 到上一级调用函数的FramePointer, 重复第2步
  6. 到达栈底, 退出

这种方式是KSCrash的作者想到的,他曾提过一个问题Printing a stack trace from another thread,不过最后他自己想出这种方式给解决了。bestswifter基于此写了BSBacktraceLogger,在OC中还是很好用的,但是在Swift没法很好的打印出结果,不知道为什么,有知道的还希望能告知一下。

在这个提问下Printing a stack trace from another thread,有人通过Signal handling实现了。

Signal

这里介绍一下大致需要了解的知识点。

信号的本质
是软件层次上对中断的一种模拟。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。

信号来源:

  1. 程序错误,如非法访问内存
  2. 外部信号,如按下了CTRL+C
  3. 通过kill或sigqueue向另外一个进程发送信号

信号处理函数的过程

  1. 注册信号处理函数
    信号的处理是由内核来代理的,首先程序通过sigal或sigaction函数为每个信号注册处理函数,而内核中维护一张信号向量表,对应信号处理机制。这样,在信号在进程中注销完毕之后,会调用相应的处理函数进行处理。
  2. 信号的检测与响应时机
  3. 处理过程

基本的信号处理函数

信号操作最常用的方法是信号的屏蔽,信号屏蔽主要用到以下几个函数:

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)

  1. 然后遍历通过dladdr获得某个地址符号信息
  2. 使用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
Screen Shot 2019-08-19 at 10.34.25 PM.png

参考:

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
获取任意线程调用栈的那些事

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,517评论 0 38
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 计算机系统漫游 代码从文本到可执行文件的过程(c语言示例):预处理阶段,处理 #inlcude , #defin...
    willdimagine阅读 3,506评论 0 5
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,334评论 0 4
  • 亲子日记第四天 3月21日阴转晴星期三 昨天下午放学回家晚说了她几句,今下午早早回家了,高兴的和我说,语文老师在课...
    程文颖阅读 180评论 0 0