Jamin's zone


  • 首页

  • 归档

  • 标签

优化应用启动

发表于 2016-09-06   |   分类于 写作

应用启动

应用启动时间长短对用户第一次体验至关重要,应用的快速启动,能够给你的用户一个好的体验。

应用最理想的启动时间在400毫秒,但永远不能超过20秒,如果超过20秒,操作系统(watchdog)会杀了你的应用程序,然后产生0x8badf00d的crash report。值得注意的是,Xcode在Debug的时候,会禁止“watchdog”。

应用包括冷启动和热启动两种:

  • 冷启动。指的是当应用还没准备好运行时,我们必须加载和构建整个应用。这包括设置屏幕底部的分栏菜单,确保用户是否被合适地登录,以及处理其他更多的事情。“引导”程序是在applicationDidFinishLaunching:withOptions:方法中开始的。

  • 热启动。指的是应用已经运行但是在后台被挂起(比如用户点击了 home 健),我们只需要知道在何时应用进入后台。在这种情况下,我们的应用通过 applicationWillEnterForeground: 接收到前台的事件,紧接着应用恢复。

可见冷启动测量更为重要。

时间测量

应用启动过程:parse images, map images, rebase images, bind images, run image initializers, call main(), call UIApplicationMain()、Call applicationWillFinishLaunching. 但这里只讲 call main()之前的时间测量、优化。

下面使用DYLD环境变量 DYLD_PRINT_STATISTICS 测量 main()之前的时间。

DYLD_PRINT_STATISTICS

点击’Eidt schemes’,添加DYLD_PRINT_STATISTICS环境变量。然后重新运行,可以看到console的输出:

1
2
3
4
5
6
7
8
Total pre-main time: 204.00 milliseconds (100.0%)
dylib loading time: 30.85 milliseconds (15.1%)
rebase/binding time: 152.04 milliseconds (74.5%)
ObjC setup time: 12.40 milliseconds (6.0%)
initializer time: 8.63 milliseconds (4.2%)
slowest intializers :
libSystem.dylib : 1.75 milliseconds (0.8%)
AppLaunchTest : 4.49 milliseconds (2.2%)

附表: 其他DYLD_PRINT环境变量

Environment variable Description
DYLD_PRINT_LIBRARIES Logs when images are loaded.
DYLD_PRINT_LIBRARIES_POST_LAUNCH Logs when images are loaded as a result of a dlopen call. Includes a dynamic libraries’ dependent libraries.
DYLD_PRINT_STATISTICS Logs statistical information on an application’s launch process, such as how many images were loaded, when the application finishes launching.
DYLD_PRINT_INITIALIZERS Logs when the dynamic loader calls initializer and finalizer functions.
DYLD_PRINT_SEGMENTS Logs when the dynamic loader maps a segment of a dynamic library to the current process’s address space.
DYLD_PRINT_BINDINGS Logs when the dynamic loader binds an undefined external symbol with its definition.

Dylib 加载

平均应用有100到400个dylibs。其中大部分是系统dylibs,且系统dylibs加载是非常快速的。另外从Xcode6开始支持开发者创建使用自己的动态库了。

在这里,我们只需使用更少的dylibs,合并现有dylibs,使用静态库;或者使用dlopen()延迟加载,但它会造成性能问题,它实际上导致了后来做更多的工作,但它推迟加载了。

Rebase/Binding

减少 __DATA pointers
减少 Objective C 元数据(Classes, selectors, and categories)
减少使用 C++ 虚拟函数
使用 Swift的structs
对于 machine generated code,使用 offsets 代替 pointers

ObjC Setup

这一过程完成以下事情,而且都是必须的:
Class registration
Non-fragile ivars offsets updated
Category registration
Selector uniquing

Initializers

ObjC 使用 +initiailize() 代替 +load()
C/C++ 使用 attribute((constructor)),它会在 main()函数执行之前被自动的执行。
使用dispatch_once()、pthread_once()、std::once()懒初始化

Do not call dlopen() in initializers
Do not create threads in initializers


我们通过减少dylibs的数量,降低ObjC类的数量,并消除static initializers,来减少应用程序启动时间,还可以使用Swift改善它。更多内容在Optimizing App Startup Time

浅谈Mach Exceptions

发表于 2016-09-01   |   分类于 写作

前言

大家都可能都在自己的应用中集成Crash收集服务,通常使用NSSetUncaughtExceptionHandler() + signal() / sigaction()的方式。它可以帮助我们收集到大部分Crash,直到后来发现stack overflow并不能被以上方法扑捉到,而且其它一些SDK也未能收集。那这篇文章简单介绍下Mach异常与signal的联系。


OS X 、iOS系统架构

osx_architecture-kernels_drivers

这张图片来之苹果Mac Technology Overview,除了用户体验层,OS X与iOS架构大体上是一直的。它们内核核心都是XNU(包含Mach、BSD)。Mach是微内核,负责操作系统中基本职责:进程和线程抽象、虚拟内存管理、任务调度、进程间通信和消息传递机制。BSD层简历在Mach上,提供一套可靠且更现代的API,提供了POSIX兼容性。
本文用到的XNU的版本号为3248.60.10的源码,下载地址。
你也可以在http://opensource.apple.com 中下载历史版本。

Mach exceptions 与 POSIX signals

Exception Type项通常会包含两个元素: Mach异常 和 Unix信号。
Mach exceptions: 允许在进程里或进程外处理,处理程序通过Mach RPC调用。
POSIX signals: 只在进程中处理,处理程序总是在发生错误的线程上调用。

Mach

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

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
// 位于 osfmk/kern/exception.c
/*
* Routine: exception_triage
* Purpose:
* The current thread caught an exception.
* We make an up-call to the thread's exception server.
* Conditions:
* Nothing locked and no resources held.
* Called from an exception context, so
* thread_exception_return and thread_kdb_return
* are possible.
* Returns:
* KERN_SUCCESS if exception is handled by any of the handlers.
*/
kern_return_t
exception_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 this behavior has been requested by the the kernel
* (due to the boot environment), we should panic if we
* enter this function. This is intended as a debugging
* aid; it should allow us to debug why we caught an
* exception in environments where debugging is especially
* difficult.
*/
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
/*
* Machine-independent exception behaviors
*/
# define EXCEPTION_DEFAULT 1 // Send a catch_exception_raise message including the identity.
# define EXCEPTION_STATE 2 // Send a catch_exception_raise_state message including the thread state.
# define EXCEPTION_STATE_IDENTITY 3 // Send a catch_exception_raise_state_identity message including the thread identity and state.
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
112
113
114
115
116
117
118
119
120
121
122
// 位于 osfmk/kern/exception.c
/*
* Routine: exception_deliver
* Purpose:
* Make an upcall to the exception server provided.
* Conditions:
* Nothing locked and no resources held.
* Called from an exception context, so
* thread_exception_return and thread_kdb_return
* are possible.
* Returns:
* KERN_SUCCESS if the exception was handled
*/
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

当第一个BSD进程调用bsdinit_task()函数启动时,这函数还调用了ux_handler_init()函数设置了一个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
31
32
33
// 位于bsd/kern/bsd_init.c
/* Called with kernel funnel held */
void
bsdinit_task(void)
{
proc_t p = current_proc();
struct uthread *ut;
thread_t thread;
process_name("init", p);
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 位于bsd/uxkern/ux_exception.c
void
ux_handler_init(void)
{
thread = THREAD_NULL;
ux_exception_port = MACH_PORT_NULL;
(void) kernel_thread_start((thread_continue_t)ux_handler, NULL, &thread);
thread_deallocate(thread);
proc_list_lock();
if (ux_exception_port == MACH_PORT_NULL) {
(void)msleep(&ux_exception_port, proc_list_mlock, 0, "ux_handler_wait", 0);
}
proc_list_unlock();
}
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
static void
ux_handler(void)
{
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 = self;
... // 省略部分
/* Message handling loop. */
// 消息处理循环
for (;;) {
struct rep_msg {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} rep_msg;
struct exc_msg {
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;
mach_exception_data_t code;
/* some times RCV_TO_LARGE probs */
char pad[512];
} exc_msg;
mach_port_name_t reply_port;
kern_return_t result;
exc_msg.Head.msgh_local_port = CAST_MACH_NAME_TO_PORT(exc_set_name);
exc_msg.Head.msgh_size = sizeof (exc_msg);
#if 0
result = mach_msg_receive(&exc_msg.Head);
#else
result = mach_msg_receive(&exc_msg.Head, MACH_RCV_MSG,
sizeof (exc_msg), exc_set_name,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
0);
#endif
if (result == MACH_MSG_SUCCESS) {
reply_port = CAST_MACH_PORT_TO_NAME(exc_msg.Head.msgh_remote_port);
// 消息处理
if (mach_exc_server(&exc_msg.Head, &rep_msg.Head)) {
result = mach_msg_send(&rep_msg.Head, MACH_SEND_MSG,
sizeof (rep_msg),MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL);
if (reply_port != 0 && result != MACH_MSG_SUCCESS)
mach_port_deallocate(get_task_ipcspace(ux_handler_self), reply_port);
}
}
else if (result == MACH_RCV_TOO_LARGE)
/* ignore oversized messages */;
else
panic("exception_handler");
}
}

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

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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// EXCEPTION_DEFAULT行为 64位处理函数
kern_return_t
catch_mach_exception_raise(
__unused mach_port_t exception_port,
mach_port_t thread,
mach_port_t task,
exception_type_t exception,
mach_exception_data_t code,
__unused mach_msg_type_number_t codeCnt
)
{
task_t self = current_task();
thread_t th_act;
ipc_port_t thread_port;
struct proc *p;
kern_return_t result = MACH_MSG_SUCCESS;
int ux_signal = 0;
mach_exception_code_t ucode = 0;
struct uthread *ut;
mach_port_name_t thread_name = CAST_MACH_PORT_TO_NAME(thread);
mach_port_name_t task_name = CAST_MACH_PORT_TO_NAME(task);
/*
* Convert local thread name to global port.
*/
if (MACH_PORT_VALID(thread_name) &&
(ipc_object_copyin(get_task_ipcspace(self), thread_name,
MACH_MSG_TYPE_PORT_SEND,
(void *) &thread_port) == MACH_MSG_SUCCESS)) {
if (IPC_PORT_VALID(thread_port)) {
th_act = convert_port_to_thread(thread_port);
ipc_port_release_send(thread_port);
} else {
th_act = THREAD_NULL;
}
/*
* Catch bogus ports
*/
if (th_act != THREAD_NULL) {
/*
* Convert exception to unix signal and code.
* 调用 ux_exception将exception转成UNIX信号
*/
ux_exception(exception, code[0], code[1], &ux_signal, &ucode);
ut = get_bsdthread_info(th_act);
p = proc_findthread(th_act);
/* Can't deliver a signal without a bsd process reference */
// 如果未找到进程,那么这个信号就不会投递了
if (p == NULL) {
ux_signal = 0;
result = KERN_FAILURE;
}
/*
* Stack overflow should result in a SIGSEGV signal
* on the alternate stack.
* but we have one or more guard pages after the
* stack top, so we would get a KERN_PROTECTION_FAILURE
* exception instead of KERN_INVALID_ADDRESS, resulting in
* a SIGBUS signal.
* Detect that situation and select the correct signal.
*/
if (code[0] == KERN_PROTECTION_FAILURE &&
ux_signal == SIGBUS) {
user_addr_t sp, stack_min, stack_max;
int mask;
struct sigacts *ps;
sp = code[1];
stack_max = p->user_stack;
stack_min = p->user_stack - MAXSSIZ;
if (sp >= stack_min &&
sp < stack_max) {
/*
* This is indeed a stack overflow. Deliver a
* SIGSEGV signal.
* 因为栈溢出需返回的是SIGSEGV,这里把SIGBUS替换成SIGSEGV
*/
ux_signal = SIGSEGV;
/*
* If the thread/process is not ready to handle
* SIGSEGV on an alternate stack, force-deliver
* SIGSEGV with a SIG_DFL handler.
*/
mask = sigmask(ux_signal);
ps = p->p_sigacts;
if ((p->p_sigignore & mask) ||
(ut->uu_sigwait & mask) ||
(ut->uu_sigmask & mask) ||
(ps->ps_sigact[SIGSEGV] == SIG_IGN) ||
(! (ps->ps_sigonstack & mask))) {
p->p_sigignore &= ~mask;
p->p_sigcatch &= ~mask;
ps->ps_sigact[SIGSEGV] = SIG_DFL;
ut->uu_sigwait &= ~mask;
ut->uu_sigmask &= ~mask;
}
}
}
/*
* Send signal. 发送信号
*/
if (ux_signal != 0) {
ut->uu_exception = exception;
//ut->uu_code = code[0]; // filled in by threadsignal
ut->uu_subcode = code[1];
threadsignal(th_act, ux_signal, code[0]);
}
if (p != NULL)
proc_rele(p);
thread_deallocate(th_act);
}
else
result = KERN_INVALID_ARGUMENT;
}
else
result = KERN_INVALID_ARGUMENT;
/*
* Delete our send rights to the task port.
*/
(void)mach_port_deallocate(get_task_ipcspace(ux_handler_self), task_name);
return (result);
}

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

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
// 位于bsd/uxkern/ux_exception.c
/*
* ux_exception translates a mach exception, code and subcode to
* a signal and u.u_code. Calls machine_exception (machine dependent)
* to attempt translation first.
*/
static
void ux_exception(
int exception,
mach_exception_code_t code,
mach_exception_subcode_t subcode,
int *ux_signal,
mach_exception_code_t *ux_code)
{
/*
* Try machine-dependent translation first.
*/
if (machine_exception(exception, code, subcode, ux_signal, ux_code))
return;
switch(exception) {
case EXC_BAD_ACCESS:
if (code == KERN_INVALID_ADDRESS)
*ux_signal = SIGSEGV;
else
*ux_signal = SIGBUS;
break;
case EXC_BAD_INSTRUCTION:
*ux_signal = SIGILL;
break;
case EXC_ARITHMETIC:
*ux_signal = SIGFPE;
break;
case EXC_EMULATION:
*ux_signal = SIGEMT;
break;
case EXC_SOFTWARE:
switch (code) {
case EXC_UNIX_BAD_SYSCALL:
*ux_signal = SIGSYS;
break;
case EXC_UNIX_BAD_PIPE:
*ux_signal = SIGPIPE;
break;
case EXC_UNIX_ABORT:
*ux_signal = SIGABRT;
break;
case EXC_SOFT_SIGNAL:
*ux_signal = SIGKILL;
break;
}
break;
case EXC_BREAKPOINT:
*ux_signal = SIGTRAP;
break;
}
}

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

用下图来展示而Mach exception转换成signal的流程(图截自 Mac OS X and iOS Internals)
Mach exception转化成signal的流程


实践

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

知道以上这些,那我们来尝试扑捉一下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
108
109
110
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];
}

调用这个方法,结果如下

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 exception。


参考文献:
Apple Kernel Programming Guide
漫谈iOS Crash收集框架)
Mac OS X and iOS Internals:To the Apple’s Core(中文译名:深入解析 MAC OS X & IOS 操作系统)
Mach Exception Handlers
xnu-3248.60.10.tar.gz
Understanding Crash Reports on iPhone OS(wwdc)
Understanding and Analyzing iOS Application Crash Reports

Hexo+Github搭建个人博客

发表于 2016-08-24   |   分类于 写作

最近正好有空琢磨琢磨琢磨搭建个人博客。于是乎就看到这边文章:5分钟 搭建免费个人博客,但也再搭建过程中遇到一些问题,如果你也正想用这种方式搭建,那么我觉得我有必要把我遇到的问题及决绝办法说一下,免得大家踩同样的坑。

step 1. 创建Github域名和空间

step 2. 安装Nodejs的时候,按照文章中的方法用curl安装方式安装问nvm后,重启终端,再执行nvm install 4时,却提示:
nvm:command not find。到这里,在这里看到,用open .bash_profile命令打开.bash_profile文件(如果不存在,用touch ~/.bash_profile命令创建),然后在最后加上source ~/.bashrc,保存关闭。这时,.bash_profile文件的内容应该是这样的

export NVM_DIR=”/Users/Jamin/.nvm”
[ -s “$NVM_DIR/nvm.sh” ] && . “$NVM_DIR/nvm.sh” # This loads nvm
source ~/.bashrc

接下来再试下$ nvm install就可以找到命令了。

step 3. 创建博客,安装主题,配置博客。

…

也就是,应该遇到的问题都在安装Nodejs的时候nvm命令找不到,导致浪费了很多时间,其实解决办法很简单,如上述,具体在creationix/nvm。OK,可以开始写我们的文章了。

Jamin

Jamin

3 日志
1 分类
2 标签
简书 联系
© 2016 Jamin
由 Hexo 强力驱动
主题 - NexT.Muse