欢迎来真孝善网,为您提供真孝善正能量书籍故事!

当遭遇崩溃时,这些应对策略或许能帮助你

时间:11-07 名人轶事 提交错误

异常触发:EXC_BAD_ACCESS,通过task_exception_notify触发异常处理程序。

接下来是一系列对Exception 的调用。

Mach异常处理程序Exception_triage()负责将异常转换为Mach消息。Exception_triage()尝试通过调用Exception_deliver()将异常传递给线程、任务,最后传递给主机。首先尝试将异常抛到线程端口,然后尝试抛到任务端口,最后抛到主机端口(默认端口)。 exception_deliver 通过调用mach_exception_raise 触发异常。 ux_exception 捕获异常信号,将异常转换为Unix 信号,并将其传递给错误线程。

那么ux_exception是如何捕获信号的呢?查看ux_exception的源代码。其组织方式如下:

当第一个BSD进程调用bsdinit_task()函数启动时,该函数还调用ux_handler_init()函数来设置一个Mach内核线程来运行ux_handler。并且ux_handler设置了一个消息循环来监听异常。

__attribute__((不返回))

静态无效

ux_handler(无效)

{

task_t self=current_task();

mach_port_name_t exc_port_name;

mach_port_name_t exc_set_name;

/* self-kernel_vm_space=TRUE; */

ux_handler_self=自我;

/*

* 分配一个我们将接收的端口集。

*/

if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_PORT_SET, exc_set_name) !=MACH_MSG_SUCCESS)

恐慌("ux_handler: port_set_allocate失败");

/*

* 分配一个异常端口并使用object_copyin来

* 将其翻译为全局名称。将其放入集合中。

*/

if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_RECEIVE, exc_port_name) !=MACH_MSG_SUCCESS)

恐慌("ux_handler: port_allocate失败");

如果(mach_port_move_member(get_task_ipcspace(ux_handler_self),

exc_port_name, exc_set_name) !=MACH_MSG_SUCCESS)

恐慌("ux_handler: port_set_add失败");

if (ipc_object_copyin(get_task_ipcspace(self), exc_port_name,

MACH_MSG_TYPE_MAKE_SEND,

(void *) ux_exception_port) !=MACH_MSG_SUCCESS)

恐慌("ux_handler: object_copyin(ux_exception_port)失败");

proc_list_lock();

thread_wakeup(ux_exception_port);

proc_list_unlock();

/* 消息处理循环。 */

为了(;) {

结构rep_msg {

mach_msg_header_t 头;

NDR_record_t NDR;

kern_return_t RetCode;

} 代表消息;

结构exc_msg {

mach_msg_header_t 头;

/* 内核处理数据的开始*/

mach_msg_body_t msgh_body;

mach_msg_port_descriptor_t 线程;

mach_msg_port_descriptor_t 任务;

/* 内核处理数据结束*/

NDR_record_t NDR;

异常类型_t 异常;

mach_msg_type_number_t codeCnt;

mach_exception_data_t 代码;

/* 有时RCV_TO_LARGE 概率*/

字符垫[512];

exc_msg;

mach_port_name_t 回复端口;

kern_return_t 结果;

exc_msg.Head.msgh_local_port=CAST_MACH_NAME_TO_PORT(exc_set_name);

exc_msg.Head.msgh_size=sizeof(exc_msg);

#如果0

结果=mach_msg_receive(exc_msg.Head);

别的

结果=mach_msg_receive(exc_msg.Head, MACH_RCV_MSG,

sizeof (exc_msg), exc_set_name,

MACH_MSG_TIMEOUT_NONE、MACH_PORT_NULL、

0);

#endif

如果(结果==MACH_MSG_SUCCESS){

回复端口=CAST_MACH_PORT_TO_NAME(exc_msg.Head.msgh_remote_port);

如果(mach_exc_server(exc_msg.Head,rep_msg.Head)){

结果=mach_msg_send(rep_msg.Head, MACH_SEND_MSG,

sizeof (rep_msg),MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL);

if (reply_port !=0 结果!=MACH_MSG_SUCCESS)

mach_port_deallocate(get_task_ipcspace(ux_handler_self),reply_port);

}

}

否则如果(结果==MACH_RCV_TOO_LARGE)

/* 忽略过大的消息*/;

别的

恐慌("异常处理程序");

}

}重点看这一段:

#如果0

结果=mach_msg_receive(exc_msg.Head);

别的

结果=mach_msg_receive(exc_msg.Head, MACH_RCV_MSG,

sizeof (exc_msg), exc_set_name,

MACH_MSG_TIMEOUT_NONE、MACH_PORT_NULL、

0);

#endif

如果(结果==MACH_MSG_SUCCESS){

回复端口=CAST_MACH_PORT_TO_NAME(exc_msg.Head.msgh_remote_port);

如果(mach_exc_server(exc_msg.Head,rep_msg.Head)){

结果=mach_msg_send(rep_msg.Head, MACH_SEND_MSG,

sizeof (rep_msg),MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL);

if (reply_port !=0 结果!=MACH_MSG_SUCCESS)

mach_port_deallocate(get_task_ipcspace(ux_handler_self),reply_port);

}

}当从端口接收到消息(mach_msg_receive)时,通过调用mach_exc_server 来触发处理程序:

catch_mach_exception_raise()catch_mach_exception_raise_state()catch_mach_exception_raise_state_identity()的具体调用是由行为决定的。

#define EXCEPTION_DEFAULT 1 //发送包含标识的catch_exception_raise 消息。

#define EXCEPTION_STATE 2 //发送包含线程状态的catch_exception_raise_state消息。

#define EXCEPTION_STATE_IDENTITY 3 //发送一条catch_exception_raise_state_identity 消息,包括线程标识和状态。另外,Mach层位于BSD层之上。当异常发生时,如果Mach没有对应的handler,就会转入BSD层处理,也就是上面的流程。那么Mach层的处理是怎样的呢?

Mach Port

使用Xcode 进行调试。主要模块是lldb的debugserver。那么它是如何获取程序的异常的呢?这里使用进程间通信:Mach Port。通过Mach Port,debugserver可以拦截程序异常。

以下是debugserver的开源代码,地址在这里

截图中高亮的位置就是关键的地方。里面的注释已经很清楚了,我就不赘述了。通过m_exception_port,debugserver可以拦截程序的异常消息。

为程序添加接收异常的端口的步骤简化如下:

mach_port_t 服务器端口;

kern_return_t kr=mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, server_port);

断言(kr==KERN_SUCCESS);

kr=mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);

断言(kr==KERN_SUCCESS);

kr=task_set_exception_ports(任务, EXC_MASK_BAD_ACCESS, server_port, EXCEPTION_DEFAULT|MACH_EXCEPTION_CODES, THREAD_STATE_NONE);功能说明,建议看一下,很清楚。

mach_port_allocatemach_port_insert_righttask_set_exception_ports.html 如果任务负责处理EXC_MASK_BAD_ACCESS的端口被关闭,是不是就无法接收异常了?

答案:是的

重点来了,崩溃来了,还可以做什么?

重要信息保存并强制救援。重要信息保存崩溃前信息的最终保存完成。这里明确说明了这一点。信息保存要根据个人需求来确定,重点是如何强力抢救程序。

强制救回这里我们首先要了解ucontext_t是什么?简单来说,就是线程运行的上下文。详细信息如下图所示:

通过它可以知道崩溃时线程的上下文。把程序救回来关键一步在:如何调整当前崩溃线程的上下文!包括修改当前寄存器的值等都是可以的。下面的Demo很好地展示了这个过程(必须在真机上运行)。主要流程分为三步:

设置处理崩溃信号的handler来触发崩溃,并修改崩溃时的线程上下文,即:ucontext允许程序继续运行,可以设置当前线程的pc。处理完崩溃信号后,程序可以继续执行,但需要注意的一点是崩溃处理函数不能做太多事情,因为系统认为信号仍在处理中。如果此时再次触发崩溃,则将无法再进入。程序将被杀死。

ucontext-uc_mcontext-__ss.__pc=崩溃处理函数地址。演示代码将lr 寄存器的值直接分配给pc。程序直接执行下一条指令。需要注意的是,此时程序的状态不稳定,因为某些寄存器的值已经被污染,随时可能崩溃。

* pc是当前运行指令的地址

* lr保存函数返回后下一条指令的地址。

ucontext-uc_mcontext-__ss.__pc=ucontext-uc_mcontext-__ss.__lr;//

//ViewController.m

//U上下文

//

//由vedon 于2017 年4 月8 日创建。

//版权所有 2017 vedon。版权所有。

//

#import "ViewController.h"

#include#include#includevoid sig_handler(int sig, siginfo_t *info, void *context)

{

ucontext_t *ucontext=上下文;

NSMutableString *str=[NSMutableString stringWithFormat:@"信号捕获: %d n",sig];

[strappendString:[NSString stringWithFormat:@"pc0x%llxn", ucontext-uc_mcontext-__ss.__pc]];

[strappendString:[NSString stringWithFormat:@"lr0x%llxn", ucontext-uc_mcontext-__ss.__lr]];

[strappendString:[NSString stringWithFormat:@"fp0x%llxn", ucontext-uc_mcontext-__ss.__fp]];

[strappendString:[NSString stringWithFormat:@"sp0x%llxn", ucontext-uc_mcontext-__ss.__sp]];

[str appendString:[NSString stringWithFormat:@"uc_stack 大小0x%lxn", sizeof(ucontext-uc_stack.ss_size)]];

[strappendString:[NSString stringWithFormat:@"uc_stack ss_sp0x%llxn", (long long)ucontext-uc_stack.ss_sp]];

if (ucontext-uc_link !=NULL)

{

[strappendString:@"uc_link :n"];

[strappendString:[NSString stringWithFormat:@"pc0x%llxn", ucontext-uc_link-uc_mcontext-__ss.__pc]];

[strappendString:[NSString stringWithFormat:@"lr0x%llxn", ucontext-uc_link-uc_mcontext-__ss.__lr]];

[strappendString:[NSString stringWithFormat:@"fp0x%llxn", ucontext-uc_link-uc_mcontext-__ss.__fp]];

[strappendString:[NSString stringWithFormat:@"sp0x%llxn", ucontext-uc_link-uc_mcontext-__ss.__sp]];

}

别的

{

[strappendString:@"uc_link为空"];

}

NSLog(@"%@",str);

ucontext-uc_mcontext-__ss.__pc=ucontext-uc_mcontext-__ss.__lr;

}

@interfaceViewController()

@结尾

@实现ViewController

- (void)viewDidLoad {

[超级viewDidLoad];

//设置当前程序接受EXC_BAD_ACCESS的任务的异常端口为空

//这样debugserver就不会捕获异常了。当然这只是为了能够

//也可以在调试环境中单步调试sig_handler。如果没有设置的话只能通过

//通过设备日志查看sig_handler的输出。

int ret=任务集异常端口(

mach_task_self(),

EXC_MASK_BAD_ACCESS,

MACH_PORT_NULL,//m_exception_port,

异常_默认,

0);

if (ret==0)NSLog(@"禁用lldb捕获异常");

结构sigaction sa;

memset(sa, 0, sizeof(struct sigaction));

sa.sa_flags=SA_SIGINFO;

sa.sa_sigaction=sig_handler;

sigaction(SIGSEGV, sa, NULL);

sigaction(SIGINT, sa, NULL);

sigaction(SIGABRT, sa, NULL);

sigaction(SIGKILL, sa, NULL);

sigaction(SIGBUS, sa, NULL);

[自我调用崩溃];

NSLog(@"崩溃后恢复");

}

- (void)invokeCrash

{

void *a=calloc(1, sizeof(void *));

NSLog(@"a:0x%llx 的崩溃地址", (long long)a);

((void(*)())a)();

}

@结尾

异常端口已经关闭,只是Mach层不处理异常。例如

EXC_BAD_ACCESS 这个异常最终会转换为BSD信号,lldb的

调试器还需要屏蔽和处理BSD信号。

接下来对SIGBUS进行屏蔽处理。至此,sig_handler就可以在debug条件下顺利调试了。

(lldb) pro handle SIGBUS -s false

结论

这种极限修复方法可以用于某些特殊的崩溃,比如WebCore中的某些系统bug。

另外,我个人觉得通过记录或者分享,可以大大加深我对相关知识的理解。

参考号:

处理未处理的异常和信号

内核架构概述

苹果开源

LLDB ---调试服务器

以下内容与主题无关,仅供记录。

@startuml

演员CPU

CPU -异常: task_exception_notify

留下注释

通知的类型可以是:

EXC_CRASH/EXC_GUARD/

EXC_BAD_ACCESS等。

其中,如果调用vm_fault

kern_cs 的cs_invalid_page,

然后,最终会传递threadsignal

发出EXC_BAD_ACCESS。

尾注

异常- 异常: 异常分类

异常-异常:异常_triage_thread

异常-异常:异常_交付

异常- 异常: mach_exception_raise

.消息循环.

ux_异常-ux_异常: catch_mach_exception_raise

注意右边

catch_mach_exception_raise 会在调用mach_exc_server 后触发

尾注

ux_异常-ux_异常: ux_异常

注意右边

将异常转换为unix 信号。

EXC_BAD_ACCESS:SIGSEGV/SIGBUS

EXC_BAD_INSTRUCTION:SIGILL

ETC。

尾注

ux_exception -kern_sig : 线程信号

注意右边

将陷阱引起的信号发送到特定线程。

尾注

@enduml

@startuml

bsd_init-bsd_init : bsdinit_task

bsd_init -ux_exception : ux_handler_init

ux_异常-ux_异常: ux_handler

激活异常

注意右边

消息处理循环

尾注

.

当遭遇崩溃时,这些应对策略或许能帮助你和的问题分享结束啦,以上的文章解决了您的问题吗?欢迎您下次再来哦!

用户评论

太难

突然想起来,真的崩溃的时候,做个深呼吸好像是最管用的了。

    有13位网友表示赞同!

淡写薰衣草的香

确实啊,有时想想自己会遇到的情况,提前预判一下危机,避免情绪失控的时候再慌张

    有8位网友表示赞同!

微信名字

我也觉得找点事干很重要!没时间去想那些乱七八糟的事情了

    有13位网友表示赞同!

一点一点把你清空

保持运动和健康的饮食习惯真的好重要,情绪稳定起来确实能感觉出来

    有19位网友表示赞同!

剑已封鞘

我平时会写日记记录自己的感受,后来发现真的可以有效缓解压力啊

    有9位网友表示赞同!

别伤我i

看一部好看的电影或者听一些轻松的音乐,也能迅速转移注意力吧

    有8位网友表示赞同!

虚伪了的真心

有时候找一个朋友倾诉一下也很有用,不要一个人默默承受

    有13位网友表示赞同!

坏小子不坏

我妈说过,“生活就像骑自行车,会跌倒是必然的,重要的是不要停下来”。挺有道理的啊!

    有13位网友表示赞同!

慑人的傲气

学会对自己好一点,给自己一些时间和空间去休息放松

    有8位网友表示赞同!

我一个人

是啊,有时崩溃也是一种正常的反应,不要太过苛责自己

    有14位网友表示赞同!

短发

也许我们应该多关心身边的人,去帮助他们,这样可以分散自己的注意力

    有17位网友表示赞同!

娇眉恨

确实,保持积极的心态很重要,把不好的情绪转化为动力

    有11位网友表示赞同!

抓不住i

我觉得接受现实也能够让我更快地走出来

    有20位网友表示赞同!

何必锁我心

学习一些放松技巧,比如冥想或者瑜伽,真的很有效

    有14位网友表示赞同!

减肥伤身#

有时候简单的生活也能让我们感到满足和幸福

    有16位网友表示赞同!

无望的后半生

我希望能找到一种方法让自己在崩溃的时候更冷静

    有11位网友表示赞同!

箜篌引

谢谢分享!这篇文章给我带来了很多启发

    有7位网友表示赞同!

泡泡龙

是啊,生活难免会遇到各种挑战,重要的是我们怎样去面对

    有13位网友表示赞同!

鹿先森,教魔方

我想学习更多关于情绪管理的知识

    有20位网友表示赞同!

如你所愿

希望大家都能找到属于自己的解压方法

    有18位网友表示赞同!

【当遭遇崩溃时,这些应对策略或许能帮助你】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活