300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 软件测试之SDK开发(ios)——Mach捕获

软件测试之SDK开发(ios)——Mach捕获

时间:2019-01-21 19:12:22

相关推荐

软件测试之SDK开发(ios)——Mach捕获

Darwin是Mac OS和IOS的操作系统,而XNU是Darwin操作系统的内核部分。XNU是混合内核,兼具宏内核和微内核的特性,而Mach即为其微内核,如图所示:

所以,mach异常是内核层发生的异常。

1、Mach异常的触发和调用流程

Mach异常是最底层的内核异常,用捕获OC异常的方法无法拿到,Mach异常的触发和调用流程如下图所示:

1、硬件产生的信号被Mach层捕获2、Mach异常处理程序exception_triage()通过调用exception_deliver()首先尝试将异常抛给thread端口、然后尝试抛给task端口,最后再抛给host端口(默认端口),exception_deliver通过调用mach_exception_raise,触发异常;3、异常在内核中以消息机制进行处理,通过task_set_exception_posrts()设置自定义的接收Mach异常消息的端口,相当于插入了一个exception处理程序。4、Mach 异常在Mach层被捕获并抛出后,会在BSD层被catch_mach_exception_raise处理,并通过ux_exception()将异常转换为对应的UNIX信号,并通过threadsignal()将信号投递到出错线程,iOS中的 POSIX API 就是通过 Mach 之上的 BSD 层实现的。

2、mach异常的捕获

mach的部分api暴露给了用户态,用户态的开发者可以直接通过Mach API设置thread、task和host的异常端口,来捕获Mach异常,抓取Mach Crash,捕获流程如下:

代码如下:

#pragma mark - Primary-(void)registerCatch{NSLog(@"haleli >>> switch_mach_crash : %@",@"开始") ;[[LLDebugTool sharedTool] saveMachCrashSwitch:YES];/**Masks for exception definitions**/exception_mask_t mask = EXC_MASK_BAD_ACCESS |EXC_MASK_BAD_INSTRUCTION |EXC_MASK_ARITHMETIC |EXC_MASK_SOFTWARE |EXC_MASK_BREAKPOINT;kern_return_t kr ;const task_t thisTask = mach_task_self() ;//分配端口if(exceptionPort == MACH_PORT_NULL){kr = mach_port_allocate(thisTask,MACH_PORT_RIGHT_RECEIVE,&exceptionPort) ;if(kr != KERN_SUCCESS){NSLog(@"haleli >>> failed to allocate exception port : %s",mach_error_string(kr)) ;return ;}else{NSLog(@"haleli >>> allocate exception port : %d",exceptionPort) ;}kr = mach_port_insert_right(thisTask, exceptionPort, exceptionPort, MACH_MSG_TYPE_MAKE_SEND);if(kr != KERN_SUCCESS){NSLog(@"haleli >>> failed to insert rights : %s",mach_error_string(kr)) ;return ;}}//设置端口监听异常kr = task_set_exception_ports(thisTask,mask,exceptionPort,EXCEPTION_DEFAULT,THREAD_STATE_NONE);if(kr != KERN_SUCCESS){NSLog(@"haleli >>> failed to set exception : %s",mach_error_string(kr)) ;return ;}int error = pthread_create(&thread,NULL,&handleExceptions,NULL) ;if(error !=0){NSLog(@"haleli >>> failed to create thread : %s",strerror(error)) ;return ;}}static void* handleExceptions(void *const userData){NSLog(@"haleli >>> exception handler is listining....") ;MachExceptionMessage exceptionMessage = {{0}} ;MachReplyMessage replyMessage = {{0}} ;mach_msg_return_t mr ;for(; ;){//等待mach exception,否则会阻塞mr = mach_msg(&exceptionMessage.Head, MACH_RCV_MSG, 0, sizeof(exceptionMessage), exceptionPort, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL) ;//捕获到mach exception,跳出循环if(mr == MACH_MSG_SUCCESS){break ;}NSLog(@"haleli >>> failed to handle exception : %s",mach_error_string(mr)) ;}NSString *callStackSymbols = [BSBacktraceLogger bs_backtraceOfThread:exceptionMessage.thread.name];NSString *date = [LLTool stringFromDate:[NSDate date]];NSArray *appInfos = [LLRoute appInfosForMach];NSLog(@"haleli >>> %d",exceptionMessage.code[0]) ;mach_exception_code_t code = exceptionMessage.code[0] ;if( code == KERN_PROTECTION_FAILURE && [BSBacktraceLogger bs_isStackOverflow:exceptionMessage.thread.name]){// 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.code = KERN_INVALID_ADDRESS;}LLCrashModel *model = [[LLCrashModel alloc] initWithName:machName(exceptionMessage.exception, code) reason:[NSString stringWithFormat:@"Catch Mach(抛出信号%@)",signalNameForMachException(exceptionMessage.exception, code)] userInfo:nil stackSymbols:@[callStackSymbols] date:date userIdentity:[LLConfig sharedConfig].userIdentity appInfos:appInfos launchDate:[NSObject LL_launchDate]];[LLCrashSignalHelper sharedHelper].crashModel = model;[[LLStorageManager sharedManager] saveModel:model complete:^(BOOL result) {NSLog(@"Save mach model success");} synchronous:YES];NSLog(@"replying to mach exception message .") ;//send a reply saying "I didn't handle this exception"replyMessage.header = exceptionMessage.Head ;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 ;}-(void)unregisterCatch{NSLog(@"haleli >>> switch_mach_crash : %@",@"关闭") ;[[LLDebugTool sharedTool] saveMachCrashSwitch:NO] ;pthread_cancel(thread) ;thread = 0 ;exceptionPort = MACH_PORT_NULL ;}

3、mach异常和signal的关系

mach异常(EXC开头)会被转换成对应的signal信号,对应关系如下所示:

对应代码如下:

#define EXC_UNIX_BAD_SYSCALL 0x10000 /* SIGSYS */#define EXC_UNIX_BAD_PIPE 0x10001 /* SIGPIPE */#define EXC_UNIX_ABORT 0x10002 /* SIGABRT */static NSString* signalNameForMachException(exception_type_t exception, mach_exception_code_t code){switch(exception){case EXC_ARITHMETIC:return @"SIGFPE";case EXC_BAD_ACCESS:return code == KERN_INVALID_ADDRESS ? @"SIGSEGV" : @"SIGBUS";case EXC_BAD_INSTRUCTION:return @"SIGILL";case EXC_BREAKPOINT:return @"SIGTRAP";case EXC_EMULATION:return @"SIGEMT";case EXC_SOFTWARE:{switch (code){case EXC_UNIX_BAD_SYSCALL:return @"SIGSYS";case EXC_UNIX_BAD_PIPE:return @"SIGPIPE";case EXC_UNIX_ABORT:return @"SIGABRT";case EXC_SOFT_SIGNAL:return @"SIGKILL";}break;}}return @"Unknown signal";}

EXC_BAD_ACCESS (Bad Memory Access)

EXC_BAD_ACCESS 通常是由于访问了不该访问的内存导致,它会产生两种类型的信号:SIGSEGVSIGBUG

SIGSEGV:对应的mach code 是KERN_INVALID_ADDRESS(无效地址访问),即通常我们说的野指针。通常是试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。*((int*)(0x1234)) = 122 ;可以制造一个EXC_BAD_ACCESS(SIGSEGV)异常

SIGBUG:非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。char *s = "hello world"; *s = 'H';可以制造一个EXC_BAD_ACCESS(SIGBUS)异常

栈溢出会导致SIGSEGV信号,但是在mach层捕获的时候对应的mach code是KERN_PROTECTION_FAILURE(地址保护错误),因为栈溢出会访问到栈顶部的保护页。在mach层捕获异常转换到对应的信号需要考虑这种情况。

EXC_BAD_INSTRUCTION

EXC_BAD_INSTRUCTION是指令相关的异常,通常是尝试执行非法的指令,对应的信号是SIGILL表示Illeague instruction

EXC_ARITHMETIC

EXC_ARITHMETIC是算术相关的异常,在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。对应的信号是SIGFPE

EXC_EMULATION

EXC_EMULATION是硬件相关的异常,对应的信号是SIGEMT,几乎碰不到这种信号

EXC_SOFTWARE

EXC_EMULATION是软件相关的异常,它会产生四种类型的信号SIGSYSSIGPIPESIGABRTSIGKILL

SIGSYS:对应的mach code是EXC_UNIX_BAD_SYSCALL,通常是非法的系统调用

SIGPIPE:管道破裂,这个信号通常在进程间通信产生。比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。对应的mach code是EXC_UNIX_BAD_PIPE

SIGABRT:是调用abort()生成的信号。通常是因为应用层发生NSException异常,并且没有被捕获,导致程序向自身发送了SIGABRT信号而崩溃。abort函数最终会调用(void)pthread_kill(pthread_self(), SIGABRT);。对应的mach code是EXC_UNIX_ABORT

SIGKILL:用来立即结束程序的运行,该信号不能被阻塞、处理和忽略。对应的mach code是EXC_SOFT_SIGNAL

EXC_BREAKPOINT

EXC_BREAKPOINT是由断点指令或其它trap指令产生,由debugger使用。对应的信号是SIGKILL

4、NSException异常和signal的关系

NSException:应用级异常,它是未被捕获的Objective-C异常,导致程序向自身发送了SIGABRT信号而崩溃,对于未捕获的Objective-C异常,是可以通过try catch来捕获的,或者通过NSSetUncaughtExceptionHandler()机制来捕获。

所以,如果发生NSException异常并且没有被捕获都会产生一个 SIGBART信号,因为是应用级异常,所以mach exception流程是不会走的。

ps:不论是mach异常还是nsexception异常最终都以信号的方式进行投递,那么是否可以直接触发信号呢。可以以软件的方式直接产生信号。软件产生的信号来自kill()、pthread_kill()两个函数的调用,该函数会直接将信号发送到目标线程,所以Mach exception流程也是不会走的。使用方法如下kill(0, SIGTRAP);,表示将SIGTRAP信号发送到当前线程。

5、效果演示

5.1 stack overflow

栈溢出只能通过mach层进行捕获,无法通过nsexception和signal的方式进行捕获。测试代码如下:

-(void)testStackOverflow{[self testStackOverflow] ;}

已经成功捕获了EXC_BAD_ACCESS并正确转换了信号SIGSEGV,该功能已经集成到了我开发的SDK里面,效果如下图所示:

5.2 同时捕获Mach和Signal

测试代码如下:

-(void)testMachCrash{*((int*)(0x1234)) = 122 ;}

效果如下图所示:

已经成功捕获EXC_BAD_ACCESS点击Signal的Cell,展示同时捕获的SIGSEGV信号:

5.3 同时捕获NSException和Signal

测试代码如下:

- (void)testArrayOutRangeCrash {NSArray *array = @[@"a",@"b"];__unused NSString *str = array[3];}

效果如下图所示:

已经成功捕获运行时异常NSRangeException,点击Signal的Cell,展示同时捕获的SIGABRT信号:

5.4 单独捕获Signal信号

测试代码如下:

- (void)testSignalCrash {kill(0, SIGTRAP);}

效果如下图所示:

参考文章

1、/p/725e7d69272c

2、/p/04f822f929f0

3、/p/5b17d78b9c7b

4、/p/133fd6f20563

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。