mach crash 处理流程

1. 注册mach异常处理回调
- 保存原先的端口,用来处理完后回调给原来的端口
- mach_port_allocate 已receive rights新建一个端口
- 为新建端口添加发送消息权限
- task_set_exception_ports 将新建端口作为异常接收端口
- pthread_create创建一个备用线程
- 将备用线程将pthread_t的类型转换成mach_port_t类型
- pthread_create 创建初始处理异常的线程
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
117static bool installExceptionHandler()
{
KSLOG_DEBUG("Installing mach exception handler.");
bool attributes_created = false;
pthread_attr_t attr;
kern_return_t kr;
int error;
/**
任务(task)是一种容器(container)对象,
虚拟内存空间和其他资源都是通过这个容器对象管理的,
这些资源包括设备和其他句柄。资源进一步被抽象为端口。
因而资源的共享实际上相当于允许对对应端口的访问
链接:https://www.jianshu.com/p/cc655bfdac13
*/
const task_t thisTask = mach_task_self(); //获得任务的端口,带有发送权限的名称
exception_mask_t mask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC |
EXC_MASK_SOFTWARE |
EXC_MASK_BREAKPOINT;
// 保存原先的端口,用来处理完后回调给原来的端口
KSLOG_DEBUG("Backing up original exception ports.");
kr = task_get_exception_ports(thisTask,
mask,
g_previousExceptionPorts.masks,
&g_previousExceptionPorts.count,
g_previousExceptionPorts.ports,
g_previousExceptionPorts.behaviors,
g_previousExceptionPorts.flavors);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("task_get_exception_ports: %s", mach_error_string(kr));
goto failed;
}
if(g_exceptionPort == MACH_PORT_NULL)
{
KSLOG_DEBUG("Allocating new port with receive rights.");
kr = mach_port_allocate(thisTask,
MACH_PORT_RIGHT_RECEIVE,
&g_exceptionPort);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr));
goto failed;
}
KSLOG_DEBUG("Adding send rights to port.");
kr = mach_port_insert_right(thisTask,
g_exceptionPort,
g_exceptionPort,
MACH_MSG_TYPE_MAKE_SEND);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("mach_port_insert_right: %s", mach_error_string(kr));
goto failed;
}
}
KSLOG_DEBUG("Installing port as exception handler.");
kr = task_set_exception_ports(thisTask,
mask,
g_exceptionPort,
(int)(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
THREAD_STATE_NONE);
if(kr != KERN_SUCCESS)
{
KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr));
goto failed;
}
KSLOG_DEBUG("Creating secondary exception thread (suspended).");
pthread_attr_init(&attr);
attributes_created = true;
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
error = pthread_create(&g_secondaryPThread,
&attr,
&handleExceptions,
(void*)kThreadSecondary);
if(error != 0)
{
KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
goto failed;
}
g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
ksmc_addReservedThread(g_secondaryMachThread);
KSLOG_DEBUG("Creating primary exception thread.");
error = pthread_create(&g_primaryPThread,
&attr,
&handleExceptions,
(void*)kThreadPrimary);
if(error != 0)
{
KSLOG_ERROR("pthread_create: %s", strerror(error));
goto failed;
}
pthread_attr_destroy(&attr);
g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
ksmc_addReservedThread(g_primaryMachThread);
KSLOG_DEBUG("Mach exception handler installed.");
return true;
failed:
KSLOG_DEBUG("Failed to install mach exception handler.");
if(attributes_created)
{
pthread_attr_destroy(&attr);
}
uninstallExceptionHandler();
return false;
}2. 处理异常
- 在for(;;)循环中等待mach消息,处理异常的线程不会一直执行 mach_msg会等待消息
- 抓取异常
- ksmc_suspendEnvironment 挂起所有线程除异常处理线程之外
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176/** Our exception handler thread routine.
* Wait for an exception message, uninstall our exception port, record the
* exception information, and write a report.
*/
/ ============================================================================
/** A mach exception message (according to ux_exception.c, xnu-1699.22.81).
*/
#pragma pack(4)
typedef struct
{
/** Mach header. */
mach_msg_header_t header;
// Start of the kernel processed data.
/** Basic message body data. */
mach_msg_body_t body;
/** The thread that raised the exception. */
mach_msg_port_descriptor_t thread;
/** The task that raised the exception. */
mach_msg_port_descriptor_t task;
// End of the kernel processed data.
/** Network Data Representation. */
NDR_record_t NDR;
/** The exception that was raised. */
exception_type_t exception;
/** The number of codes. */
mach_msg_type_number_t codeCount;
/** Exception code and subcode. */
// ux_exception.c defines this as mach_exception_data_t for some reason.
// But it's not actually a pointer; it's an embedded array.
// On 32-bit systems, only the lower 32 bits of the code and subcode
// are valid.
mach_exception_data_type_t code[0];
/** Padding to avoid RCV_TOO_LARGE. */
char padding[512];
} MachExceptionMessage;
// 处理异常
static void* handleExceptions(void* const userData)
{
MachExceptionMessage exceptionMessage = {{0}};
MachReplyMessage replyMessage = {{0}};
char* eventID = g_primaryEventID;
const char* threadName = (const char*) userData;
pthread_setname_np(threadName);
if(threadName == kThreadSecondary)
{
KSLOG_DEBUG("This is the secondary thread. Suspending.");
thread_suspend((thread_t)ksthread_self());
eventID = g_secondaryEventID;
}
for(;;)
{
KSLOG_DEBUG("Waiting for mach exception");
// Wait for a message.
kern_return_t kr = mach_msg(&exceptionMessage.header,
MACH_RCV_MSG,
0,
sizeof(exceptionMessage),
g_exceptionPort,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if(kr == KERN_SUCCESS)
{
break;
}
// Loop and try again on failure.
KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
}
KSLOG_DEBUG("Trapped mach exception code 0x%llx, subcode 0x%llx",
exceptionMessage.code[0], exceptionMessage.code[1]);
if(g_isEnabled)
{
thread_act_array_t threads = NULL;
mach_msg_type_number_t numThreads = 0;
///挂起所有的线程
ksmc_suspendEnvironment(&threads, &numThreads);
g_isHandlingCrash = true;
kscm_notifyFatalExceptionCaptured(true);
KSLOG_DEBUG("Exception handler is installed. Continuing exception handling.");
// Switch to the secondary thread if necessary, or uninstall the handler
// to avoid a death loop.
if(ksthread_self() == g_primaryMachThread)
{
KSLOG_DEBUG("This is the primary exception thread. Activating secondary thread.");
// TODO: This was put here to avoid a freeze. Does secondary thread ever fire?
restoreExceptionPorts();
if(thread_resume(g_secondaryMachThread) != KERN_SUCCESS)
{
KSLOG_DEBUG("Could not activate secondary thread. Restoring original exception ports.");
}
}
else
{
KSLOG_DEBUG("This is the secondary exception thread.");// Restoring original exception ports.");
// restoreExceptionPorts();
}
// Fill out crash information
KSLOG_DEBUG("Fetching machine state.");
KSMC_NEW_CONTEXT(machineContext);
KSCrash_MonitorContext* crashContext = &g_monitorContext;
crashContext->offendingMachineContext = machineContext;
kssc_initCursor(&g_stackCursor, NULL, NULL);
if(ksmc_getContextForThread(exceptionMessage.thread.name, machineContext, true))
{
kssc_initWithMachineContext(&g_stackCursor, KSSC_MAX_STACK_DEPTH, machineContext);
KSLOG_TRACE("Fault address %p, instruction address %p",
kscpu_faultAddress(machineContext), kscpu_instructionAddress(machineContext));
if(exceptionMessage.exception == EXC_BAD_ACCESS)
{
crashContext->faultAddress = kscpu_faultAddress(machineContext);
}
else
{
crashContext->faultAddress = kscpu_instructionAddress(machineContext);
}
}
KSLOG_DEBUG("Filling out context.");
crashContext->crashType = KSCrashMonitorTypeMachException;
crashContext->eventID = eventID;
crashContext->registersAreValid = true;
crashContext->mach.type = exceptionMessage.exception;
crashContext->mach.code = exceptionMessage.code[0] & (int64_t)MACH_ERROR_CODE_MASK;
crashContext->mach.subcode = exceptionMessage.code[1] & (int64_t)MACH_ERROR_CODE_MASK;
if(crashContext->mach.code == KERN_PROTECTION_FAILURE && crashContext->isStackOverflow)
{
// A stack overflow should return KERN_INVALID_ADDRESS, but
// when a stack blasts through the guard pages at the top of the stack,
// it generates KERN_PROTECTION_FAILURE. Correct for this.
crashContext->mach.code = KERN_INVALID_ADDRESS;
}
crashContext->signal.signum = signalForMachException(crashContext->mach.type, crashContext->mach.code);
crashContext->stackCursor = &g_stackCursor;
kscm_handleException(crashContext);
KSLOG_DEBUG("Crash handling complete. Restoring original handlers.");
g_isHandlingCrash = false;
ksmc_resumeEnvironment(threads, numThreads);
}
KSLOG_DEBUG("Replying to mach exception message.");
// Send a reply saying "I didn't handle this exception".
replyMessage.header = exceptionMessage.header;
replyMessage.NDR = exceptionMessage.NDR;
replyMessage.returnCode = KERN_FAILURE;
mach_msg(&replyMessage.header,
MACH_SEND_MSG,
sizeof(replyMessage),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
return NULL;
}
LLDB Debugger
__unsafe_unretained NSObject *objc = [[NSObject alloc] init];
NSLog(@”✳️✳️✳️ objc: %@”, objc)
ARC 下会发生 EXC_BAD_ACCESS 异常,这里要注意一下,只有我们关闭 xcode 的 Debug executable 选项才能收到 exc_handler 回调
Q&A
- Q:添加mac异常自定义处理是如何将线程和端口绑定
在异常处理线程等待异常端口的消息1
2
3
4
5
6
7
8
9
10
11
12KSLOG_DEBUG("Installing port as exception handler.");
kr = task_set_exception_ports(thisTask,
mask,
g_exceptionPort,
(int)(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
THREAD_STATE_NONE);
KSLOG_DEBUG("Creating primary exception thread.");
error = pthread_create(&g_primaryPThread,git
&attr,
&handleExceptions,
(void*)kThreadPrimary); -->
参考资料
MAC OS 的mach_port_t和pthread_self()
iOS Mach异常和signal信号
获取线程ID有更好的方式吗