iphone8iphone崩溃日志解析怎么看

今天在微信公众号上看到一篇文章,做一下简化整理,大家可以尝试一起来做一下自己的Crash日志记录
开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常,我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。
一. 系统Crash
对于系统Crash而引起的程序异常退出,可以通过UncaughtExceptionHandler机制捕获;也就是说在程序中catch以外的内容,被系统自带的错误处理而捕获。我们要做的就是用自定义的函数替代该ExceptionHandler即可。
二. 处理signal
使用Objective-C的异常处理是不能得到signal的,如果要处理它,我们还要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。该函数中我们可以输出栈信息,版本信息等其他一切我们所想要的。
下面是一些信号说明:1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。
登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录, wget也 能继续下载。
此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。
6) SIGABRT
调用abort函数生成的信号。
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10) SIGUSR1
留给用户使用
11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12) SIGUSR2
留给用户使用
13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17) SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。
18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23) SIGURG
有”紧急”数据或out-of-band数据到达socket时产生.
24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。
26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28) SIGWINCH
窗口大小改变时发出.
文件描述符准备就绪, 可以开始进行输入/输出操作.
30) SIGPWR
Power failure
31) SIGSYS
非法的系统调用。
在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。
【具体原因为啥,我也不知道
iOS Crash日志收集与解析
Crash日志收集与解析一、本地crash日志收集1、 把发生crash的设备连接到你的电脑上,用 iTunes 或 itools Mac OS X:~/Library/Logs/CrashRepor...
上线APPcrash收集方案
最近在找工作,但是实习工作真的很难,没有面试的时候,就会自己研究一些新的知识,今天研究的是关于线上APP的错误收集。参考的是《Android开发艺术》如有侵权,请联系QQ。开始正题。...
iOS开发之Crash日志获取与分析
iOS crash log分析
iOS Crash 崩溃日志总结
iOS Crash 崩溃日志总结
iOS崩溃捕获以及收集原理
通过崩溃捕获和收集,可以收集到已发布应用(游戏)的异常,以便开发人员发现和修改bug,对于提高软件质量有着极大的帮助...
[iOS Crashr日志分析一] Crash日志分析 工具准备
//crash 报告解释
http://www.cnblogs.com/smileEvday/p/Crash1.htmlhttp://blog.csdn.net/ys/articl...
iOS应用的crash日志的分析基础
如何获得crash日志
如何解析crash日志
如何分析crash日志
1. iOS策略相关
2. 常见错误标识
3. 代码bug
在ios开发中怎么获取应用崩溃日志
如何获得crash日志  当一个iOS应用程序崩溃时,系统会创建一份crash日志保存在设备上。crash日志记录着应用程序崩溃信息,通常包含着每个执行线程的栈调用信息(低内存闪退日志例外),对于开发...
iOS 获取crash日志
在项目上架到App Store以后,谁也不能保证没有一个错误,可能在测试期间连测试人员都没有发现的隐藏小bug在上线之后被用户发现了,这就无疑给用户造成了困扰。所以作为开发人员,要及时的收集这些cra...
app运行中的crash崩溃异常日志收集
在Android开发中,一个app在推广后。我们怎么才能知道这个app运行的如何,有没有出现崩溃等问题。这也就是app数据监控的一部分。下面的这个就是介绍关于crash日志的收集。这个就是核心代码:
...
没有更多推荐了,崩溃日志记录 - 简书
崩溃日志记录
1、在执行NSString *result = [[NSString alloc] initWithData:response
encoding:NSUTF8StringEncoding];的时候引起的崩溃,后台没有将数据加密直接发过来的,所以会引起崩溃
崩溃日志信息:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryM bytes]: unrecognized selector sent to instance 0x7f'
1、 进程信息第一部分是闪退进程的相关信息。Incident Identifier : 是崩溃报告的唯一标识符。CrashReporter Key: 是与设备标识相对应的唯一键值。虽然它不是真正的设备标识符,但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不同的CrashReport值,说明这不是一个普遍的问题,只发生在一个或少数几个设备上。Hardware Model :标识设备类型。 如果很多崩溃日志都是来自相同的设备类型,说明应用只在某特定类型的设备上有问题。上面的日志里,崩溃日志产生的设备是iPhone 4s。接下来几行不言自明,无需赘述。(2) 基本信息这部分给出了一些基本信息,包括闪退发生的日期和时间,设备的iOS版本。(3) 异常Exception Type:异常的类型。Exception Codes :异常错误码Termination Reason:闪退的原因,比如常见的数组越界啊,什么的。Triggered by Thread:出现问题在哪个线程,这个比较重要,首先确定在哪个线程中出了问题,然后再去定位。(4) 线程回溯这部分提供应用中所有线程的回溯日志。 线程调用的一些,堆栈信息,压根看不懂,所有需要进行符号化处理。用Xcode自带的 symbolicatecrash 工具来解析的.crash文件1、获取crash文件:a、直接从设备中获取方法如下图
b、苹果审核那边发给你的(我的就是)2、找到app包所对应的.dSYM文件,强调要对应.dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件。测试给过来的.crash文件中,关于崩溃的确切语句和函数部分只有16进制地址符号,并不是我们在xcode调试时看到的xxx.cpp(xxfunction xx行)这样的。一般发布一个版本需要保存好对应的.dSYM和.app包,方便分析crash
3、就是找到Xcode中的symbolicatecrash工具咯,我用的Xcode8,路径为/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash其他版本的xcode,自己百度咯,反正都差不多。把我们需要的工具直接复制到桌面上来(方便用)。4、.crash文件的分析a、.配置环境变量DEVELOPER_DIR,(配置好了就不再需要)exportDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developerb、新建一个文件夹,将第二步中获取的两个文件放在同一个文件夹中
注意:上面指的是其所在的路径。经过上面的简单几步我们就能得到格式化好的crash日志,这样就方便我们定位bug的位置了。注:本文系转载 ,作者:为谁而鸣
靡有不初,鲜有克终
转自http://www.raywenderlich.com/zh-hans/30818/ios应用崩溃日志揭秘 本文作者是Soheil Moayedi Azarpour, 他是一名独立iOS开发者。 作为一名应用开发者,你是否有过如下经历? 为确保你的应用正确无误,在将其...
写在前面:本文会在最开头将苹果官方的文档Understanding and Analyzing Application Crash Reports进行翻译,但这不仅仅是一篇翻译的文章,本文会让大家更加全面的了解ios的崩溃报告的获取、分析、用途。翻译的时候我会结合自己以往的...
前言 崩溃是让发人员比较头痛的事情,app崩溃了,说明代码写的有问题,这时如何快速定位到崩溃的地方很重要。调试阶段是比较容易找到出问题的地方的,但是已经上线的app并分析崩溃报告就比较麻烦了。最终,我们可以通过iOS崩溃日志在大多数情况下,你能从中了解到关于闪退的详尽、有用...
本文就捕获iOS Crash、Crash日志组成、Crash日志符号化、异常信息解读、常见的Crash五部分介绍。 一、捕获iOS Crash 1、设置异常断点并运行 说明:设置Xcode异常断点后运行程序,发生Crash时,断点会定位到出错的代码行,但仅适用于开发阶段。线...
iOS大全 (点击上方公众号,可快速关注) 来源:南华Coder
自荐投稿 www.jianshu.com/p/d9e 如有好文章投稿,请点击 → 这里了解详情 一、捕获iOS Crash 1、设置异常断点并运行 设置异常断点.pn...
世界著名球星马拉多纳说:“告诉孩子们,让他们崇拜有学问的人,不要崇拜我。我只读过小学,只读过小学的人是一头驴子。” 马拉多纳是谦虚的,也是清醒的,因为他并没有沉醉于他在足球上的成就,而是意识到了他没有多少文化知识,清醒地认识到:足球是刺激人心的,但整个社会的发展靠的还是科学...
自从打卡机兴起以后,在很多公司就一下子爆发了起来。各种各样的打卡机出现,纸卡式、指纹式、刷脸式、软件打卡,一下子成为了公司管理的“神器”。很多公司高管以为解放了管理,开始沾沾自喜。但是长远的来看,这样的氛围真的是我们所想要的么? 首先我们必须明确的是,真正努力为公司工作的员...
我以为,炎炎夏日的周末,外出游玩并不是很好的选择,于是和以往的周末一样,又理所当然的宅在家里。与以往不同的是屋子里多了一个人,已经整整上班加班一个月没有过休息的孙小韬也窝在家里。三天的假期,对于暂时得到缓刑的孙小韬而言,无疑是一个漫长的放风过程。再想到后面可能还要整整上班加...
曾几何时,在互联网上兴起了一些列的非议我国传统文化、民族崇拜、历史名人、民族英雄的歪风邪气,好象我国历史上一切都是落后的,好象我们的古人一个个都是笨蛋窝囊废。有的人张着臭哄哄的大嘴,唾沫星子飞溅,掰着指头骂了太监骂皇帝,骂了文人骂将军,骂了历史非今人,骂了中医骂迷信,上至古...发布于 08/08 15:23
iOS汇编教程:理解ARM
当你写Objective-C代码时,它们最终转换成机器码---ARM处理器能理解的原始的0和1指令。在Objective-C和机器码之间,还有一种可直接理解的汇编语言。
理解汇编会让你在调试和优化时更加深入了解你的代码,破译Objective-C运行时,而且能满足如呆子般痴迷于内核的好奇心。
在这篇iOS汇编教程中,你将学到:
汇编是什么及为什么你要关心它;
看懂汇编。特别是Objective-C方法生成的汇编;
在调试时如何使用汇编。对于看清代码运行状态及为什么bug或者崩溃发生非常有用;
为了更好的理解这篇教程,你应该熟悉Objective-C编程。你也要理解一些简单的计算机术语,比如栈,处理器(CPU)及他们是如何工作的。如果你不熟悉CPU,你可能需要预先了解下再继续。
打开你的XCode4(作者的文章写于2013年,当时还是4),让我们准备探索ARM的内部世界吧~
开始:什么是汇编
众所周知,Objective-C是一个高级语言。Objective-C语言通过编译器编译成汇编语言,它虽然是低级语言,但也不是最低级的。
汇编语言通过汇编器转译成CPU可识别的01机器码。幸运的是,你不需要关心机器码,但理解汇编细节有时候非常有用。
每个汇编指令都是告诉CPU去执行一项任务,比如将两个数相加,从内存中加载数据。
除了主要的内存外---iPhone5是1GB,Mac上可能是8GB,CPU也有一些能够快速访问的小块工作内存。这小块工作内存被分割成寄存器,就像能持有值的变量一样。
所有的iOS设备(事实上,目前几乎所有的手机设备)使用基于的CPU。幸运的是,这个指令集很容易读懂,不仅仅因为它是(RISC)---意味着更少的指令。总之,它比更易读。
一个汇编指令看起来像这样:
mov r0, #42
汇编语言有许多指令或操作符。其中之一的mov操作符,用于移动数据。在ARM汇编里,第一个参数是移动数据的目的地,因此上面的指令就是把值42赋值给了r0寄存器。看看下面这个例子:
ldr r2, [r0] ldr r3, [r1] add r4, r2, r3
别担心,我并没有期望你能理解上面代码。但是你可能已经猜到了代码大致意思。指令的意思是从内存中加载数据并把他们存储到寄存器r2,r3中,然后将他们俩相加存储到寄存器r4中。
现在你已经发现其实并没有那么恐怖,让我们来更深入详细些吧~
理解汇编首先且最重要的事就是明白汇编代码与代码之间是如何交互的。这里我的意思是函数调用其他函数。包括参数是如何传递给函数,及函数返回值是如何返回的。
这些事情执行的过程与实现被称为调用约定。编译器必须遵循它预定义的标准,这样才能让编译后的代码能和其他不同编译器编译出的代码能够交互。没有标准,编译器能编译出不相配的代码。
如上讨论,寄存器是和CPU联系非常紧密的一小块内存,经常用于存储一些正在使用的数据。ARM处理器有16个寄存器,从r0到r15,每一个都是32位比特。调用约定指定他们其中的一些寄存器有特殊的用途,例如:
r0-r3:用于存放传递给函数的参数;
r4-r11:用于存放函数的本地参数;
r12:是内部程序调用暂时寄存器。这个寄存器很特别是因为可以通过函数调用来改变它;
r13:栈指针sp(stack pointer)。在计算机科学内栈是非常重要的术语。寄存器存放了一个指向栈顶的指针。看了解更多关于栈的信息;
r14:是链接寄存器lr(link register)。它保存了当目前函数返回时下一个函数的地址;
r15:是程序计数器pc(program counter)。它存放了当前执行指令的地址。在每个指令执行完成后会自动增加;
你可以在里了解更多关于ARM调用约定的信息。苹果也在文档内有做过详细描述。
OK,前戏已足,是时候开始真正的码代码了~
在这个汇编教程里,你不会创建一个应用,但还是得使用Xcode工程来说明其中含义。启动Xcode,再新建工程,选择iOS\应用\单个view的工程( iOS\Application\Single View Application)点击下一步,工程最终如下:
Product name: ARMAssembly;
Company Identifier: 你的通常的反向域名标识;
Class Prefix:置空;
Devices:iPhone;
Use StoryBoards:否
使用ARC:是
Include Unit Tests:否
最后点击下一步,选择一个保存你工程的地址。
你要做的第一件事就是写一个两个数相加后并返回的函数。没有比这更简单的事了。
事实上,你可以先写一个C语言函数,因为Objective-C会是我们的复杂度增加。在Supporting Files里打开main.m,并把下面的代码贴到文件内:
int addFunction(int a, int b) { & & int c = a + & & }
现在保证你选择的Scheme是iOS设备(或者你的设备名),比如,如果你连上了设备就是xx的iPhone。选择Scheme是设备的原因是这样才会用ARM汇编,而选择模拟器的话就会用x86的方式汇编。Scheme选择后的类似于这样:
现在去Product\Generate Output\Assembly File生成汇编码(其实现在Xcode7是Product\Perform Action\Assemble "main.m")。Xcode会展示一个奇怪的文件。在文件最顶部,你可以看到许多以.section开头的行。如果有那说明你生成成功了!
注意:你默认运行的Scheme是Debug配置。在Debug模式下,编译器不会开启优化。你想看到无优化版本的汇编,这样你才能真正看清发生了什么。
文件中搜索_addFunction。你会发现像以下的代码:
& & .globl & &_addFunction & & .align & &2 & & .code & &16 & & & & & & & & & & &@ @addFunction & & .thumb_func & &_addFunction _addFunction: & & .cfi_startproc Lfunc_begin0: & & .loc & &1 13 0 & & & & & & & & &@ main.m:13:0 @ BB#0: & & sub & &sp, #12 & & str & &r0, [sp, #8] & & str & &r1, [sp, #4] & & .loc & &1 14 18 prologue_end & &@ main.m:14:18 Ltmp0: & & ldr & &r0, [sp, #8] & & ldr & &r1, [sp, #4] & & add & &r0, r1 & & str & &r0, [sp] & & .loc & &1 15 5 & & & & & & & & &@ main.m:15:5 & & ldr & &r0, [sp] & & add & &sp, #12 & & bx & &lr Ltmp1: Lfunc_end0: & & .cfi_endproc
这代码看起来让人有点沮丧,但其实没那么难看懂。首先,所有以.号开始的行都不是汇编指令而是作用于汇编器的。你可以忽略所有这样的代码。
这些以冒号为结束的行,例如:_addFunction:和Ltmp0:,被称为标签。他们是这部分的汇编代码名字。称为_addFunction:标签实际上是这个函数的入口。
这个标签是必须的,这样其他代码可以通过使用addFunction标签来路由函数而不需要知道函数的位置。当最终应用二进制生成的时候,将这些标签转换成实际内存地址是链接器的工作。
注意到编译器通常在函数名的前面添加一个下划线,这也是一个约定。其他的所有以L.开头的叫本地标签,这些标签只能用于函数内部。在这个简单的例子里,没有任何一个本地标签真正被使用,但编译器仍然生成了,因为这个代码并没有做任何编译优化。
注意是以@字符开头的。在汇编代码后边注释上对应的main.c文件行数对我们看懂汇编代码非常有用。
因此,忽略掉注释和标签,重要的代码如下:
_addFunction: @ 1: & & sub & &sp, #12 @ 2: & & str & &r0, [sp, #8] & & str & &r1, [sp, #4] @ 3: & & ldr & &r0, [sp, #8] & & ldr & &r1, [sp, #4] @ 4: & & add & &r0, r1 @ 5: & & str & &r0, [sp] & & ldr & &r0, [sp] @ 6: & & add & &sp, #12 @ 7: & & bx & &lr
首先,分配栈所需的所有临时存储空间。栈是一大块函数随时想使用的内存。ARM中的栈内存是高地址向低地址分布的,意味着你必须从栈指针开始减。在这里,分配了12个字节。
r0和r1存放传给函数的参数。如果入参有四个参数,那么r2和r3就会分别存放第三和第四个参数。如果函数超过四个参数,或者一些例如结构体的参数超过了32位比特,那么参数将会通过栈来传递。 这里,两个参数被存入栈中。这是通过存储寄存指令(str)实现的。第一个参数是要存储的寄存器,第二个是存储的位置。方括号代表里面值是内存地址。 这个方括号指令允许你为一个值指定偏移量,因此[sp, #8]的意思『在栈指针的地址上加上8字节偏移量』。同样地,str r0, [sp, #8]意味着『存储r0寄存器的值到栈指针地址加上8字节内存的位置』。
刚被保存到栈的值又被读取到相同的寄存器内。和str指令相反的,ldr指令是从一个内存中加载内容到寄存器。两者语法非常相似。因此,ldr r0, [sp, #8]意思是『读取出在栈指针地址加上8字节内存的位置的内容,并将内容赋值给寄存器r0』。 如果你好奇为何r0和r1刚被存储又被加载出来,对,它是很奇怪,这两行明明就是多余的嘛!如果编译器允许基本的编译优化,那么这多余的就会被消除。
这是该函数最重要的指令了:做加法。意思是将r0和r1中的内容相加,并将相加的值赋值给r0。&add指令入参可以是两个或者三个,如果是三个,那第一个参数就是存储后两个参数相加的值的寄存器。所以,这行指令也可以写成:add r0, r0, r1。
再一次,编译器生成了一些多余的代码:将相加的结果存储起来,又读取到相同的位置。
函数即将终止,因此栈指针放回原来的地方。函数开始时从sp(栈指针)上减去了12个字节而得到12个字节内存使用。现在它把12个字节还回去。函数必须保证栈指针操作平衡,否则栈指针可能漂移,最终可能超出了已分配的内存。你应该不希望那样...
最后,间接分支调度指令bx被执行,用于返回到调用函数(调用本函数的函数)。lr(link register)存放了调用函数执行完当前函数的下一条指令。注意到,在addFunction执行返回后,r0保存了相加的值。这是调用约定的另一部分。函数的返回值总是r0,除非一个寄存器不够存储,这种情况下才会使用r1-r3。
其实并没有那么难,对不?想知道更多关于指令的信息,可以看看,或者.
你发现了这个函数很多汇编代码是多余的。因为一开始,我们的编译器就是Debug(调试)模式,没有任何编译器优化的。如果你把编译优化打开,你会得到一个非常精简的代码。
改变 Show Assembly Output For 到选择器到 存档(Xcode7不一样,区别是如果你想获取精简的汇编,需要Edit Scheme成Release模式,另外切换后记得clean下工程)。搜索_addFunction:,你可以看到如下代码:
_addFunction: & & add & &r0, r1 & & bx & &lr
这非常简洁! 可以看到仅仅两个指令就写完了这个函数。你可能没想到仅用两个指令就完成了~ 当然,你平时写的函数一般更长也更有趣点~
现在你已经有一个以返回到调用者分支为结束的函数。那么作为一个相互关系的另一个函数,调用该函数的调用者呢?
首先,你需要给addFunction函数添加一个让编译器不做优化的属性。你已经发现如果我们开启优化,那么代码会移除掉不必要的指令,甚至连函数调用都会被移除,或者可能直接将函数作为内联函数使用。
例如,编译器可能直接用add指令代替函数调用。实际上,编译器是非常强大智能的,它可能直接帮你计算好了相加后的值,连add指令都不需要生成。
这个教程,我们不希望编译器做优化或者将函数内联。回到main.m文件,修改函数成如下:
__attribute__((noinline)) int addFunction(int a, int b) { & & int c = a + & & }
继续在下方添加另一个函数如下:
void fooFunction() { & & int add = addFunction(12, 34); & & printf("add = %i", add); }
fooFunction函数简单地用addFunction让12和34相加并且打印出值。这里使用的是C语言的printf而不是Objective-C的NSLog函数的原因是后者的汇编结果更加复杂。
再一次生成汇编代码,搜索_fooFunction,你可以看到如下代码:
_fooFunction: @ 1: & & push & &{r7, lr} @ 2: & & movs & &r0, #12 & & movs & &r1, #34 @ 3: & & mov & &r7, sp @ 4: & & bl & &_addFunction @ 5: & & mov & &r1, r0 @ 6: & & movw & &r0, :lower16:(L_.str-(LPC1_0+4)) & & movt & &r0, :upper16:(L_.str-(LPC1_0+4)) LPC1_0: & & add & &r0, pc @ 7: & & blx & &_printf @ 8: & & pop & &{r7, pc}
这里引入了一些教程之前没有介绍过的指令,但不用担心,他们并不复杂,我们来看:
这个指令跟我们之前的add sp, #12指令做的事情差不多。这里,r7和lr被推入到栈,意味着sp(栈指针)减掉了8字节(栈指针始终指向栈顶,所以在push的时候会变小),因为r7和lr都是4字节。注意到栈指针变小了而且通过一个指令存储了两个值。r7的值需要存储起来的原始是之后函数执行时它会被使用到并重写。lr被存储的原因在函数最后将会知晓;
这两个指令(mov)是Move组的成员之一。有时候你会看到movs,或者movw,或者其他,但他们的作用都是用一个值来填充寄存器。你可以将一个寄存器的值移动到另一个寄存器,因此mov r0, r1会将r1寄存器内容填充到r0,r1的值不变。在这两行代码中,r0和r1是用函数中定义的两个常量赋值的。注意到他们是被加载到r0和r1中,刚好是addFunction的入参。
在函数调用边界时,栈指针应该被保存起来,因此作为可存储本地变量的寄存器之一r7被使用了。你会发现剩余的函数代码中并没有使用栈指针或者r7,因此这是个小小的多余处理。有时候开启了优化也优化不掉。
指令bl意味着函数调用。记得函数的入参已经放入相关的寄存器r0及r1了吧。这个指令执行的代码我们称之为分支。因为是指令bl而不是指令b,指令bl全称是『branch with link』,意味着在执行分支代码之前,需要将lr(链接寄存器)置为当前函数的下一个指令。回想下,当addFunction方法返回时,lr就是指向下一个要执行的指令。
这是将两个数相加的addFunction分支返回的节点。记得之前说明过函数的返回值是存放在r0的吧~ 这个值会作为printf函数的第二个参数,因此mov指令用于将r0的内容赋值给r1。
printf函数的第一个参数是一个字符串。这三条指令加载指向所需的字符串的开始地址的指针到r0寄存器。字符串存储在我们称之为二机制『数据段』的位置。但只有最终二进制被链接时才能知道该数据的具体位置。 字符串可以在main.m生成的目标文件例找到。如果你在生成的汇编代码内搜索『L.str』,便可找到它。前两个指令加载常量的地址,并减去标签的地址(LPC1_0加上4字节)。看到第三个指令这么做的目的就很明显了。r0加上pc(程序计数器),这样无论L.str在二进制文件的什么位置都能够准确的存放字符串的位置。 下面的图展示了内存分布。L_.str - (LPC1_0 + 4)差值可以随便变动,但是加载r0的代码却不用变化。
这条指令是调用printf函数。这里的blx跟bl指令有点不同,x代表交换,当有需要时,处理器可以改变当前运行模式。处理器运行模式有点超越了本教程的范围,ARM处理器有两种运行模式:ARM和Thumb。Thumb指令集是16位宽,而ARM是32位。Thumb指令比ARM少,使用Thumb意味着更少的代码大小及更好的CPU缓存。通常使用有限的Thumb指令集可以让你从更小的包大小中获益。想了解更多Thumb的知识
最后一条指令是推出第一条指令推入的值。这次列表中的寄存器的值是用栈中的值填充的,且栈指针增加了。回想下,r7和lr之前是被推入到栈中,那么此时为何是推出来的值存入到了r7和pc中,而不是r7和lr呢? 好的,记得lr是存储当前函数执行完成后的下一个指令地址吧。当你把lr推出栈赋值给pc后,执行将会从本函数调用的地方继续执行。这通常是一个函数返回的实现方式,而不是像addFunction那样切分支的方式。
以上是对ARM指令集大致的介绍。还有很多其他指令集,但这些是开始理解指令集最重要的的指令。让我们来用伪代码快速回忆一下代码做的事情:
mov r0, r1 =& r0 = r1 mov r0, #10 =& r0 = 10 ldr r0, [sp] =& r0 = *sp str r0, [sp] =& *sp = r0 add r0, r1, r2 =& r0 = r1 + r2 add r0, r1 =& r0 = r0 + r1 push {r0, r1, r2} =& r0, r1 r2 入栈 pop {r0, r1, r2} =& 栈顶出三个, 并赋值给r0, r1 and r2. b _label =& pc = _label bl _label =& lr = pc + 4; pc = _label
哇哦~ 现在你可以读懂一些ARM汇编代码了~
Objective-C 汇编
至此,你看到的函数都是C语言的。Objective-C代码要复杂点,不过让我们来检验一下。在ViewController.m代码中添加以下代码实现:
- (int)addValue:(int)a toValue:(int)b { & & int c = a + & & }
让我们再次重复之前精简的汇编方式,搜索addValue:toValue:函数,可以看到:
"-[ViewController addValue:toValue:]": & & adds & &r0, r3, r2 & & bx & &lr
首先你会注意到标签名字。这次便签名字包含了类名及全部的方法名。
如果你和之前的addFunction汇编代码相比较,你会发现两个入参存储在了r2及r3而不是r0和r1。为什么呢?
OK,因为Objective-C函数在C函数的基础上多传了两个隐式的参数。addValue:toValue:方法语法上和以下方法相同:
int ViewController_addValue_toValue(id self, SEL _cmd, int a, int b) { & & int c = a + & & }
这就是为什么a和b变量分别存储在r2和r3内了。现在你大概知道了前两个隐式参数的含义了,你总是可以使用self这个变量。
但是,_cmd可能之前你没有见过。像self变量一样,在Objective-C代码中它是可获取的,而且代表着当前函数的selector。你一般从不会用到它,这就是你为何没听过的原因了。
为了看清Objective-C函数是如何被调用的,我们在ViewController中添加如下代码:
- (void)foo { & & int add = [self addValue:12 toValue:34]; & & NSLog(@"add = %i", add); }
生成代码并找到该方法,你可以看到类似下面的代码(Xcode7生成的有点不一样了):
"-[ViewController foo]": @ 1: & & push & &{r7, lr} @ 2: & & movw & &r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4)) & & movt & &r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4)) LPC1_0: & & add & &r1, pc @ 3: & & ldr & &r1, [r1] @ 4: & & movs & &r2, #12 & & movs & &r3, #34 @ 5: & & mov & &r7, sp @ 6: & & blx & &_objc_msgSend @ 7: & & mov & &r1, r0 @ 8: & & movw & &r0, :lower16:(L__unnamed_cfstring_-(LPC1_1+4)) & & movt & &r0, :upper16:(L__unnamed_cfstring_-(LPC1_1+4)) LPC1_1: & & add & &r0, pc @ 9: & & blx & &_NSLog @ 10: & & pop & &{r7, pc}
再次,和之前我们看到的C语言函数差不多。分解它:
将r7及lr入栈;
使用同样加载字符串的方式,加载在名为L_OBJC_SELECTOR_REFERENCES_的标签处的字符串指针到r1。像便签名字一样,它是一个selector的引用。其实selector就是存储在数据段的字符串。
如果你在汇编代码里查找L_OBJC_SELECTOR_REFERENCES_,你会发现:L_OBJC_SELECTOR_REFERENCES_:.long L_OBJC_METH_VAR_NAME_,这说明r1指向的是L_OBJC_METH_VAR_NAME_标签的地址。如果你继续查看该便签,你将找到addValue:toValue:字符串。 指令ldr r1, [r1]表示加载存储在r1指针内的内容并赋值给r1。用伪代码是这么表述的r1 = *r1。再仔细想想,r1其实已经指向addValue:toValue:字符串地址。
加载常量到r2和r3。
保存栈指针。
以保存lr指针且可更换模式的方式切分支到objc_msgSend。这个方式是Objective-C语言的核心。它调用它的入参selector的实现。参数最终和传给这个方法的参数一样,r0是self,r1是_cmd,r2和r3是剩下的参数。这就是为何selector要赋值给r1,剩余参数赋值给r2和r3,r0是隐式加载的,因为self变量已经存在了。
addValue:toValue:方法的返回值还是r0。这个指令将r0的值赋值给r1,因为r0之后要作为C函数NSLog的参数。
加载NSLog需要的字符串给r0,像printf函数一样。
以保存lr指针且可更换模式的方式切分支到NSLog。
两个值被推出来,一个赋值给r7一个给pc。这个指令将使函数返回。
如你所见,当生成汇编代码时,C函数和Objective-C没有多大差别。两者的主要差别在于,Objective-C隐式的传递了两个参数,且selector是在保存在数据段内的。
Objective-C函数执行过程
你已经大致看到了objc_msgSend函数,你可能也在Crash日志内见过它。这个函数是Objective-C运行时的核心。运行时是胶合Objective-C应用的代码,包括所有的内存管理方法及类处理。
每一次Objective-C函数调用,都需要objc_msgSendC函数来派发消息。它会去对应的对象方法列表内搜索方法的实现。objc_msgSend函数签名如下:id objc_msgSend(id self, SEL _cmd, ...)
在函数执行当中的第一个参数是self对象。因此当我们写诸如self.someProperty代码时,self就是这么来的。
第二个参数是少有人知的隐藏参数。你可以试试,在Objective-C方法里写这样一句代码:NSLog(@"%@", NSStringFromSelector(_cmd));,你可以看到当前的方法被打印出来。明白了不?
剩下的参数就是方法所需的参数了。像addValue:toValue:方法有两个参数的方法,初次外,还有另外两个参数。因此,不调用Objective-C函数,你可以直接这样写也可达到同样的效果:
- (void)foo { & & int add = (int)objc_msgSend(self, NSSelectorFromString(@"addValue:toValue:"), 12, 34); & & NSLog(@"add = %i", add); }
注:objc_msgSend函数的返回值是id类型,但被强转成int类型。这没问题是因为他们的大小都是一样的。如果方法返回不同大小的返回值,实际上是另一个方法被调用了。你可以了解更多信息。如果返回值是浮点型,那么另一个objc_msgSend方法的.
回想下上面Objective-C方法生成的等量C函数的签名如下:
int ViewController_addValue_toValue(id self, SEL _cmd, int a, int b)
现在对于这个写法应该没什么惊讶的。可以看出它跟我们objc_msgSend的签名非常匹配!意味着当objc_msgSend方法找到了对应方法实现时,调用的各个参数都正确了。你可以阅读更多关于objc_msgSend方法的信息。
现在,你可以逆向工程
获得了ARM汇编的一些知识,你应该对程序中一些中断,崩溃或者运行不正确有种感觉了。为何你会想去看汇编代码?因为你找到更多信息看清到底是哪一步导致bug发生。
有时候,你并没有源码,例如,你崩溃发生在第三方库或者系统框架内。若能通过汇编调查将会让你快速找到问题所在。iOS SDK所有的框架都装在这个目录下:
&Path_to_Xcode&/Contents/Developer/Platforms/iPhoneOS.platform/Developer/ SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks
为调查这些库,我建议你买这个软件,该软件可以反汇编既而你可以查看。例如,打开UIKit库,你可以看到每个方法做了啥,看清来像这样:
这是http://www.raywenderlich.com/wp-content/uploads/-HopperApp-1.png方法的汇编代码。运用你新得到的ARM汇编知识,你应该可以知道方法做了什么。
第一个selector指针加载到r1,为objc_msgSend方法做准备。注意到并没有动过其他寄存器,那么r0中的self就和shouldAutorotateToInterfaceOrientation方法一样。
同样地,你也发现被调用的函数也是只有一个参数,因为他的名字里只有一个冒号。因为只剩下r2未处理了,那么传给shouldAutorotateToInterfaceOrientation的第一个参数就是我们需要传给调用函数的参数。
最后,调用函数后,r0没有动过。那么调用函数的返回值,就是当前函数的返回值。
因此你可以推论出这个函数是这么实现的:
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { & & return [self _doesTopViewControllerSupportInterfaceOrientation:interfaceOrientation]; }
哇哦!太简单了!通常一个函数的逻辑比这个复杂一些,但通常你都可以把他们组织起来,并快速的想明白一些代码做了什么。
何去何从?
iOS汇编教程给了你一些洞察ARM内核的方法。你也学会了一些关于C和Objective-C的一些调用约定。
装备了这些知识,当你的应用崩溃在系统库深部时,你就有了理解这些随机代码的方法。或者你只想通过看汇编代码了解你的代码到底是如何运行的。
如果你对深入研究ARM感兴趣,我建议你买一个. 这些晓得设备拥有一个和iOS设备非常类似的ARM处理器,而且有许多文档教你如何为它编写代码。
另一个值得关注的是NEON。这是在所有除了iPhone 3GS之前的处理器上的额外的指令集。它提供单指令多数据流(SIMD)能力让你非常高效地处理数据。运用这些指令的应用一般是图像处理的。如果你需要高效地处理事情,学习如何写NEON指令使用内联汇编将会对你有益。这真的非常高端~
这个事情应该够你忙一会了,让我们在论坛上了解下你的ARM探索之旅吧~
本文转载自:https://blog.csdn.net/u/article/details/
人打赏支持
码字总数 69241
Free and open source mobile deep learning framework, deploying by Baidu. This research aims at simply deploying CNN on mobile devices, with low complexity and high speed. It sup......
随着iPhone 5S的推出,大家开始关心5S上所使用的64位CPU A7。 除了关心A7的性能以外,大家还会关心一个问题,那就是使用A7的64位系统对应用有没有什么要求。特别是应用开发者,大家都比较关心...
如果iOS中谈到取属性,相信大家都会夸夸其谈,不就是get方法吗?或者大谈kvc取属性的机制。不得不说这些也是对的。这时大家可能就疑惑了,那你还要说啥的!!大家不妨想想,这些都是代码层的...
FlyOceanFish
如果你自己开发iOS应用,你肯定会发现网上有很多资源。学习编程的一个最好的方法就是自己写代码,而开始写代码的最快的方式就是看其他人怎么写。我们从海量视频和学习网站中整理出了我们认为...
IOS编程浅蓝教程,这是博客地址http://www.cnblogs.com/haichao/category/425378.html IOS编程浅蓝教程:锲子 IOS编程浅蓝教程(一)先决条件:开始iOS编程的必要准备 IOS编程浅蓝教程(二) Hel...
andy521zhu
没有更多内容
加载失败,请刷新页面
12.13 Nginx防盗链 12.14 Nginx访问控制 12.15 Nginx解析php相关配置(502的问题) 12.16 Nginx代理 扩展 502问题汇总 http://ask.apelearn.com/question/9109 location优先级 http://blog....
一、Nginx防盗链 1. 编辑虚拟主机配置文件 vim /usr/local/nginx/conf/vhost/test.com.conf 2. 在配置文件中添加如下的内容 { expires 7d; valid_referers none blocked server_names *.tes......
芬野de博客
资源调用 import org.springframework.beans.factory.annotation.Vimport org.springframework.context.annotation.PropertySimport org.springframework.core.io.R......
一、memcached命令行 yum装telnet yum install telent 进入memcached telnet 127.0.0.1 11211 命令最后的2表示,两位字节,30表示过期时间(秒) 查看key1 get key1 删除:ctrl+删除键 二、m...
Zhouliang6
做项目有时候要备份数据库,手动备份太麻烦,所以找了一下定时备份数据库的方法 Linux里有一个 crontab 命令被用来提交和管理用户的需要周期性执行的任务,就像Windows里的定时任务一样,用这...
月夜中徘徊
没有更多内容
加载失败,请刷新页面
文章删除后无法恢复,确定取消删除此文章吗?
亲,自荐的博客将通过私信方式通知管理员,优秀的博客文章审核通过后将在博客推荐列表中显示
确定推荐此文章吗?
确定推荐此博主吗?
聚合全网技术文章,根据你的阅读喜好进行个性推荐
指定官方社区
深圳市奥思网络科技有限公司版权所有

我要回帖

更多关于 iOS怎么看崩溃日志 的文章

 

随机推荐