0%

欢迎来到我的博客

学习一些东西,记录一些东西,刚开始搭建,有好的建议联系我 727881945@qq.com

iOS 程序崩溃抓取与分析(后续再写一些其它的东西)

初学者,或再深入了解iOS的朋友,请查看下面《关于iOS基础技术部分》相关知识点。

iOS中,程序崩溃,抓取过程与分析, 点击查看

关于iOS基础技术部分

以后慢慢积累补充

我的公众号

大神博客

需要再解读文章及子文章

阅读全文 »

问:

阅读全文 »

LLDB官方文档地址

LLDB调试器

LLVM源代码树,并在lldb 子目录中找到源代码:

git clone https://github.com/llvm/llvm-project.git
请注意,LLDB通常使用CMake和Ninja从top-of-trunk构建。另外它构建:

  • 在macOS上生成Xcode项目
  • 在Linux和FreeBSD上使用clang和libstdc ++ / libc ++
  • 在NetBSD上使用GCC / clang和libstdc ++ / libc ++
  • 在Windows上生成VS 2017或更高版本的项目
阅读全文 »

前言

Flutter已经面世这么长时间了,不抽空学习一下,实在是对不起自己。也是由于前天面试被问到Flutter的一些东西,一脸懵逼,真不是一个合格的iOS开发猿!!!

分享一下学习经验和心得,纪录一下学习过程的疑问。

官网学习,手敲练习demo源码

学习之前我有几个问题问自己

  • 为什么大家都说Flutter比RN和Weex流畅?
  • 和RN、Weex实现原理对比, Flutter的实现原理是什么?
  • 怎么和Native通讯?
阅读全文 »

iOS App程序崩溃的抓取与分析

备用: xun开源代码地址

  1. Mach异常捕获方式
    • mach_port_allocate 创建一场端口
    • mach_port_insert_right 申请 set_exception_ports 的权限
    • xxx_set_exception_ports 设置异常端口
    • 循环等待异常消息
  2. Unix 信号方式 signal(SIGSEGV, signalHandler)
    • 除了OC层面的异常捕捉之外,很多内存错误、访问错误的地址产生的crash则需要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。该函数中我们可以输出栈信息,版本信息等其他一切我们所想要的。
  3. Mach异常 + Unix信号方式
    • 某个NSException导致程序Crash的,只有拿到这个NSException,获取它的reason,name,callStackSymbols信息才能确定出问题的程序位置。
    • 方法很简单,可通过注册NSUncaughtExceptionHandler捕获异常信息
      1
      2
      3
      4
      static void my_uncaught_exception_handler (NSException *exception) {
      //这里可以取到 NSException 信息
      }
      NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);

多个 Crash 日志收集服务共存

  • 拒绝传递 UncaughtExceptionHandler
  • 开发测试阶段,可以利用 fishhook 框架去hookNSSetUncaughtExceptionHandler方法,这样就可以清晰的看到handler的传递流程断在哪里,快速定位污染环境者。
  • Mach异常端口换出+信号处理Handler覆盖
  • 影响系统崩溃日志准确性
    • 若程序因NSException而Crash,系统日志中的Last Exception Backtrace信息是完整准确的,不会受应用层的胡来而影响,可作为排查问题的参考线索。

概述

Mach: 微内核,负责操作系统中基本职责:进程和线程抽象、虚拟内存管理、任务调度、进程间通信和消息传递机制。

大家熟知的NSSetUncaughtExceptionHandler() + signal() / sigaction()的方式收集Crash,但是stack overflow并不能被此方法扑捉到;

通常我们所说的异常,一般是有处理器陷阱引发的,通用的Mach异常处理程序exception_triage(),负责将异常转换成Mach 消息。exception_triage()通过调用exception_deliver()尝试把异常投递到thread、task最后是host。首先尝试将异常抛给thread端口,然后尝试抛给task端口,最后再抛给host端口(默认端口),如果没有一个端口返回KERN_SUCCESS,那么任务就会被终止。

代码太多,不再贴了,自行下载源码,找到 osfmk/kern/exception.c 文件。 里面有exception_triage()和exception_deliver()函数。关于Exception的相关定义,可以在osfmk/mach/exception_types.h里面查看。下面只贴伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

// 位于 osfmk/kern/exception.c
kern_return_t
xception_triage(
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt
)
{
thread_t thread;
task_t task;
host_priv_t host_priv;
lck_mtx_t *mutex;
kern_return_t kr = KERN_FAILURE;
assert(exception != EXC_RPC_ALERT);
if (panic_on_exception_triage) {
panic("called exception_triage when it was forbidden by the boot environment");
}
thread = current_thread();

// ================ 分别尝试把异常投递到thread、task最后是host。================
/*
* Try to raise the exception at the activation level.
*/
mutex = &thread->mutex;
if (KERN_SUCCESS == check_exc_receiver_dependency(exception, thread->exc_actions, mutex))
{
kr = exception_deliver(thread, exception, code, codeCnt, thread->exc_actions, mutex);
if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
goto out;
}
/*
* Maybe the task level will handle it.
*/
task = current_task();
mutex = &task->lock;
if (KERN_SUCCESS == check_exc_receiver_dependency(exception, task->exc_actions, mutex))
{
kr = exception_deliver(thread, exception, code, codeCnt, task->exc_actions, mutex);
if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
goto out;
}

/*
* How about at the host level?
*/
host_priv = host_priv_self();
mutex = &host_priv->lock;

if (KERN_SUCCESS == check_exc_receiver_dependency(exception, host_priv->exc_actions, mutex))
{
kr = exception_deliver(thread, exception, code, codeCnt, host_priv->exc_actions, mutex);
if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
goto out;
}

out:
if ((exception != EXC_CRASH) && (exception != EXC_RESOURCE) &&
(exception != EXC_GUARD) && (exception != EXC_CORPSE_NOTIFY))
thread_exception_return();
return kr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

// 位于 osfmk/kern/exception.c
kern_return_t
exception_deliver(
thread_t thread,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt,
struct exception_action *excp,
lck_mtx_t *mutex)
{
... // 省略部分代码

switch (behavior) {
case EXCEPTION_STATE: {
mach_msg_type_number_t state_cnt;
thread_state_data_t state;

c_thr_exc_raise_state++;
state_cnt = _MachineStateCount[flavor];
kr = thread_getstatus(thread, flavor,
(thread_state_t)state,
&state_cnt);
if (kr == KERN_SUCCESS) {
if (code64) {
kr = mach_exception_raise_state(exc_port,
exception,
code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
} else {
kr = exception_raise_state(exc_port, exception,
small_code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
}
if (kr == MACH_MSG_SUCCESS && exception != EXC_CORPSE_NOTIFY)
kr = thread_setstatus(thread, flavor,
(thread_state_t)state,
state_cnt);
}
return kr;
}

case EXCEPTION_DEFAULT:
c_thr_exc_raise++;
if (code64) {
kr = mach_exception_raise(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
code,
codeCnt);
} else {
kr = exception_raise(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
small_code,
codeCnt);
}
return kr;

case EXCEPTION_STATE_IDENTITY: {
mach_msg_type_number_t state_cnt;
thread_state_data_t state;

c_thr_exc_raise_state_id++;
state_cnt = _MachineStateCount[flavor];
kr = thread_getstatus(thread, flavor,
(thread_state_t)state,
&state_cnt);
if (kr == KERN_SUCCESS) {
if (code64) {
kr = mach_exception_raise_state_identity(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
} else {
kr = exception_raise_state_identity(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
small_code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
}
if (kr == MACH_MSG_SUCCESS && exception != EXC_CORPSE_NOTIFY)
kr = thread_setstatus(thread, flavor,
(thread_state_t)state,
state_cnt);
}
return kr;
}

default:
panic ("bad exception behavior!");
return KERN_FAILURE;
}/* switch */
}

当第一个BSD进程调用bsdinit_task()函数(源码位于bsd/kern/bsd_init.c)启动时,这函数还调用了ux_handler_init()函数(位于bsd/uxkern/ux_exception.c)设置了一个Mach内核线程跑ux_handler()的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 位于bsd/kern/bsd_init.c
/* Called with kernel funnel held */
void
bsdinit_task(void)
{
// ...省略

ux_handler_init(); // 初始化handler

// 设置port
thread = current_thread();
(void) host_set_exception_ports(host_priv_self(),
EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT),//pilotfish (shark) needs this port
(mach_port_t) ux_exception_port,
EXCEPTION_DEFAULT| MACH_EXCEPTION_CODES,
0);

ut = (uthread_t)get_bsdthread_info(thread);

bsd_init_task = get_threadtask(thread);
init_task_failure_data[0] = 0;

#if CONFIG_MACF
mac_cred_label_associate_user(p->p_ucred);
mac_task_label_update_cred (p->p_ucred, (struct task *) p->task);
#endif
load_init_program(p);
lock_trace = 1;
}

每一个thread、task及host自身都有一个异常端口数组,通过调用xxx_set_exception_ports()(xxx为thread、task或host)可以设置这些异常端口。 xxx_set_exception_ports()第四个参数为exception_behavior_t behavior,这将会使用到与行为相匹配的实现(exc.defs 或 mach_exc.defs)。
各种行为都在host层被catch_[mach]_exception_xxx处理,64位的对应的是有mach函数(可在/bsd/uxkern/ux_exception.c查看)。
这些函数通过调用ux_exception()将异常转换为对应的UNIX信号,并通过threadsignal()将信号投递到出错线程。

所以,如果异常是栈溢出,那么signal是SIGSEGV而不是SIGBUS;如果进程退出了或者线程/进程未准备好处理signal,所注册的signal()是无法接收信号的。

把Mach exception 和 UNIX signal 的转换制表后,如下

exception type signal
EXC_BAD_ACCESS 1、SIGSEGV (KERN_INVALID_ADDRESS)
2、SIGBUS(其它)
EXC_BAD_INSTRUCTION SIGILL
EXC_ARITHMETIC SIGFPE
EXC_EMULATION SIGEMT
EXC_SOFTWARE 1、SIGSYS(EXC_UNIX_BAD_SYSCALL)
2、SIGPIPE(EXC_UNIX_BAD_PIPE)
3、SIGABRT(EXC_UNIX_ABORT)
4、SIGKILL(EXC_SOFT_SIGNAL)
EXC_BREAKPOINT SIGTRAP ()

实战

在Mach中,异常是通过内核中的主要设施-消息传递机制-进行处理的。一个异常与一条消息并无差别,由出错的线程或任务(通过 msg_send())发送,并通过一个处理程(通过 msg_recv())接收。
由于Mach的异常以消息机制处理而不是通过函数调用,exception messages可以被转发到先前注册的Mach exception处理程序。这意味着你可以插入一个exception处理程序,而不干扰现有的无论是调试器或Apple’s crash reporter。可以使用mach_msg() // flag MACH_SEND_MSG发送原始消息到以前注册的处理程序的Mach端口,将消息转发到一个现有的处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
void catchMACHExceptions() {

kern_return_t rc = 0;
exception_mask_t excMask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC |
EXC_MASK_SOFTWARE |
EXC_MASK_BREAKPOINT;

rc = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &myExceptionPort);
if (rc != KERN_SUCCESS) {
fprintf(stderr, "------->Fail to allocate exception port\\\\\\\\n");
return;
}

rc = mach_port_insert_right(mach_task_self(), myExceptionPort, myExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
if (rc != KERN_SUCCESS) {
fprintf(stderr, "-------->Fail to insert right");
return;
}

rc = thread_set_exception_ports(mach_thread_self(), excMask, myExceptionPort, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
if (rc != KERN_SUCCESS) {
fprintf(stderr, "-------->Fail to set exception\\\\\\\\n");
return;
}

// at the end of catchMachExceptions, spawn the exception handling thread
pthread_t thread;
pthread_create(&thread, NULL, exc_handler, NULL);
} // end catchMACHExceptions

static void *exc_handler(void *ignored) {
// Exception handler – runs a message loop. Refactored into a standalone function
// so as to allow easy insertion into a thread (can be in same program or different)
mach_msg_return_t rc;
fprintf(stderr, "Exc handler listening\\\\\\\\n");
// The exception message, straight from mach/exc.defs (following MIG processing) // copied here for ease of reference.
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[144];
} Request;

Request exc;

struct rep_msg {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} rep_msg;


for(;;) {
// Message Loop: Block indefinitely until we get a message, which has to be
// 这里会阻塞,直到接收到exception message,或者线程被中断。
// an exception message (nothing else arrives on an exception port)
rc = mach_msg( &exc.Head,
MACH_RCV_MSG|MACH_RCV_LARGE,
0,
sizeof(Request),
myExceptionPort, // Remember this was global – that's why.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);

if(rc != MACH_MSG_SUCCESS) {
/*... */
break ;
};

// Normally we would call exc_server or other. In this example, however, we wish
// to demonstrate the message contents:

printf("Got message %d. Exception : %d Flavor: %d. Code %lld/%lld. State count is %d\\\\\\\\n" ,
exc.Head.msgh_id, exc.exception, exc.flavor,
exc.code[0], exc.code[1], // can also print as 64-bit quantity
exc.old_stateCnt);

rep_msg.Head = exc.Head;
rep_msg.NDR = exc.NDR;
rep_msg.RetCode = KERN_FAILURE;

kern_return_t result;
if (rc == MACH_MSG_SUCCESS) {
result = mach_msg(&rep_msg.Head,
MACH_SEND_MSG,
sizeof (rep_msg),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
}
}

return NULL;

} // end exc_handler

接下来,我们测试一下。

1
2
3
4
- (void)test
{
[self test];
}

结果如下:

1
2
3
Exc handler listening
Got message 2401. Exception : 1 Flavor: 0. Code 2/1486065656. State count is 8
(lldb)

我们可以查看mach/exception_types.h 对exception type的定义

Exception: 1, 即为EXC_BAD_ACCESS
code: 2, 即KERN_PROTECTION_FAILURE

而ux_exception() 函数告诉我们Code与signal是怎么转换的。
也就是 Exception = EXC_BAD_ACCESS, code = 2 对应的是SIGBUS信号,又因为为是stack overflow,信号应该是SIGSEGV。
那么结这个exception就是:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_PROTECTION_FAILURE

再试试其他的:

1
2
int *pi = (int*)0x00001111;
*pi = 17;

Exc handler listening
Got message 2401. Exception : 1 Flavor: 0. Code 1/4369. State count is 8(lldb)

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS

最后

除了上面硬件产生的信号,另外还有软件产生的信号。软件产生的信号来自kill()、pthread_kill()两个函数的调用,大概过程是这样的:kill()/pthread_kill() –> … –> psignal_internal() –> act_set_astbsd()。最终也会调用act_set_astbsd()发送信号到目标线程。这意味着Mach exception流程是不会走的。
另外,在abort()源码注释着:<rdar://problem/7397932> abort() should call pthread_kill to deliver a signal to the aborting thread , 它是这样调用的(void)pthread_kill(pthread_self(), SIGABRT);。Apple’s Crash Reporter 把SIGABRT的Mach exception type记为EXC_CRASH,不同与上面转变表。

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000

所以尽管Mach exception handle 比 UNIX signal handle 更有优势,但我们还是须要注册signal handle用于处理EXC_SOFTWARE/EXC_CRASH。

阅读全文 »

Mach

OS X内核的基本服务和基本类型都基于Mach 3.0。苹果对Mach进行了修改和扩展,以更好地满足OS X的功能和性能目标。

Mach 3.0最初被认为是一个简单、可扩展的通信微内核。它能够作为一个独立的内核运行,而其他传统的操作系统服务(如I/O、文件系统和网络堆栈)作为用户模式服务器运行。

阅读全文 »

官方文档地址

OS X简介

OS X为Macintosh用户和开发人员社区提供了许多好处。这些优点包括改进的可靠性和性能、增强的网络特性、基于对象的系统编程接口以及对行业标准的更多支持。

在创建OS X的过程中,苹果彻底改造了Mac OS核心操作系统。形成OS X的基础是内核。下图说明了OS X体系结构。

阅读全文 »

进程

  • 1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
  • 2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
  • 3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

线程

  • 1.程序执行流的最小单元,线程是进程中的一个实体.
  • 2.一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程
    阅读全文 »

Python

Linux和Mac环境自带了Python 2.x版本,但是如果要更新到3.x的版本,可以在Python的官方网站下载安装

注释

  1. 单行注释 - 以#和空格开头的部分
  2. 多行注释 - 三个引号开头,三个引号结尾

语言元素

变量和类型

  • 整型:Python中可以处理任意大小的整数
    • (Python 2.x中有int和long两种类型的整数,但这种区分对Python来说意义不大,因此在Python 3.x中整数只有int这一种了),而且支持二进制(如0b100,换算成十进制是4)、八进制(如0o100,换算成十进制是64)、十进制(100)和十六进制(0x100,换算成十进制是256)的表示法。
  • 浮点型:浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如123.456)之外还支持科学计数法(如1.23456e2)。
  • 字符串型:字符串是以单引号或双引号括起来的任意文本,比如'hello'"hello",字符串还有原始字符串表示法、字节字符串表示法、Unicode字符串表示法,而且可以书写成多行的形式(用三个单引号或三个双引号开头,三个单引号或三个双引号结尾)。
  • 布尔型:布尔值只有TrueFalse两种值,要么是True,要么是False,在Python中,可以直接用TrueFalse表示布尔值(请注意大小写),也可以通过布尔运算计算出来(例如3 < 5会产生布尔值True,而2 == 1会产生布尔值False)。
  • 复数型:形如3+5j,跟数学上的复数表示一样,唯一不同的是虚部的i换成了j
阅读全文 »

本文使用的源码是libdispatch-1008.220.2版本。

once.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//第一部分 dispatch_once 的定义,其实是一个_dispatch_once
void
dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block);

void
_dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once(predicate, block);
} else {
dispatch_compiler_barrier();
}
DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}
#undef dispatch_once
#define dispatch_once _dispatch_once


//第二部分 dispatch_once_f 的定义,其实是一个 ——dispatch_once_f
void
dispatch_once_f(dispatch_once_t *predicate, void *_Nullable context,
dispatch_function_t function);
void
_dispatch_once_f(dispatch_once_t *predicate, void *_Nullable context,
dispatch_function_t function)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once_f(predicate, context, function);
} else {
dispatch_compiler_barrier();
}
DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}
#undef dispatch_once_f
#define dispatch_once_f _dispatch_once_f
阅读全文 »