破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈

破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈(一) 

情况是在不断地变化,要使自己的思想适应新的情况,就得学习。
——毛爷爷

1案情引言

前不久瓶子哥的一位刚入职的同事小马在调试基于三星S6手机平台的7420 SoC(ARM64位处理器)芯片Android驱动的时候遇到了一个linux crash问题,但是panic出来后没有打印出backtrace(函数的调用关系),作为新手的他看到如下的界面,我想大概率都是懵逼的状态吧。

为了彰显我老司机的地位,也为了刷一波存在感,后来瓶子哥帮忙他分析了下ARM64 linux的函数调用关系以及AAPCS64规则,通过手动获取调用堆栈的方式很快定位和解决了此问题。瞬间小马向我投来了异样的眼神,偶想这下应该就直接奠定了我“大婶”的地位把,不过瓶子哥我怎么感觉后背有种瑟瑟发凉的感觉呢?

对于这个问题我又深思了下,然后意识到,早在2013年苹果发布了iPhone 5s,其做为第一款应用了ARMv8架构的64位处理器的手机以来,各大手机厂商也在64位处理器的道路上摩拳擦掌,你苹果既然带头弄了个64位SoC,那么大家也就弄64位吧,不然外行以为64位厉害(似乎苹果总能在某些方面引领科技潮流啊),随后高通发布了骁龙410 64位SoC,MTK 的MT6732,华为的麒麟620等64位处理器相继现身。

现在的移动AP处理器来说基本都已经鸟枪换炮成64位CPU了。似乎手机厂商在搞PPT的时候,如果不宣称自己的处理器是64位的话,码农门表示他都没法混下去了。但不管厂商们上不上64位处理器,我们码农的生活还是要继续的,瓶子哥的课还是要继续的。

言归正传,对于这类没有正常显示backtrace的ARM64位体系结构下的linux crash case,该如何分析解决,如何能够有一套行之有效的破案手法呢?

2 概述

资深的码农们都知道,如果要定位分析PANIC/OOPS等这类crash问题,有如下堆栈的backtrace调用关系的话无疑对于我们快速定位和分析异常问题有很大的帮助(当然那种我等小白一眼就能发现的错误以及本身打印出了正常的backtrace的话,我们当然不需要恢复堆栈,大家直接可以洗洗睡了哈)。

那么问题来了,对于ARM64位SoC来说,开发过程中遇到了linux crash后没有打印出backtrace的话,该如何手动恢复呢?是否还是和32位CPU体系结构下相同的恢复方法呢?答案是否定的。

根据ARM官方的手册,对于AArch64的AAPCS有别于ARM的AAPCS,因此堆栈的组织调用方式也有差异,所以对于在64bit平台上遇到的没有backtrace的crash问题,我们需要根据AAPCS64的规则,找出函数在调用的过程中,相关的参数,寄存器出入栈规律来手动恢复堆栈。

所谓磨刀不误砍柴工,心急吃不了热豆腐,那么问题来了,“狗蛋,你知道对于今天我们要讲的这个神技能,我们都需要掌握哪些基础理论知识才能够愉快的实践呢?”

“呃,这个嘛,就是,那个,恩,###@!#¥%……%&*……,老湿,狗蛋我好像不知道耶,但是...”

“好吧,看你们大家一脸懵逼的样子,那老湿我还是继续吧,前面说的这些AArch64,AAPCS64,Backtrace等名词,大家不懂没有关系,这节课就是针对这些知识点为大家扫盲及答疑解惑的。”

“狗蛋,你要好好的做好笔记啊!做好笔记!做好笔记!重要的事情说3遍。你们明年升职加薪,出去 装X的神技能就靠它咯,不管你信不信,作为老司机的的我来说,反正我是信了,哎呀呀,是不是有点鸡冻呀!”

Ok,言归正传,everybody,码农大讲坛正式开始上课,今天针对这个ARM64位体系构下调试linux crash问题,如果没有打印出函数的调用堆栈关系,要进行手动栈回溯的话,需要大家掌握以下知识点:

1. 栈回溯的认知

2. 基于ARM64 AAPCS64栈帧的组织方式

3. 神兵利器之Crash工具使用

4. 庖丁解牛之实操手动恢复堆栈

(未完待续,干货即将奉上)

分享下ARM64位过程调用规范官方文档,堆栈的调用有详细的说明,如果对此系列文章感兴趣可以结合此文档了解更彻底。 链接:https://pan.baidu.com/s/1CCh6xmgrJSOYkJbs4tOYhg提取码: 2188。

另外AAPCS 32位的链接:链接: https://pan.baidu.com/s/1Wmp7tbGHiA0mie5VHrwClg 提取码: 2kx0。

破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈(二)

极客皮皮

7 人赞同了该文章

引言

上节课我们仅仅谈论了下要学习破获crash案件神技能的导火索,以及要想破获这类案件我们需要学习的无上武林秘籍,这节课我们正式展开秘籍的修炼。在修炼之前我们再回顾下要学会手动恢复堆栈这个神技能需要学会哪些基本功:

1. 栈回溯的认知。

2. 基于ARM64 AAPCS64栈帧的组织方式。

3. 神兵利器之Crash工具。

4. 庖丁解牛之实操手动恢复堆栈。

栈回溯的认知

“好了,同学们,请打酱油的同学安静打酱油,不要影响其他同学,我们正式开始修炼基础功。”

“横看成岭侧成峰,远近高低各不同”,二丫,来说说你对这句诗的理解。”

"老师,我想确认下我们是走对了教室的?这节课是给我们码农门讲神技能的专业课还是艺术哲学课呢?二丫我表示很懵逼啊?"

“程序就是一门艺术嘛。”

“好吧,二丫我表示无语,不过这句诗嘛难不倒我这个集美貌与智慧为一体的新时代码农的。”

“这句诗大概说的就是我们以不同的角度看远处的庐山,会呈现各种不同的样子,结合老师这节课嘛,我猜就是说要认清事物的本质,就必须从各个角度去观察,既要客观,又要全面,这样才能抓住事物的本质。”二丫颇有哲人的风范讲述到。

“恩,不错嘛二丫,老师咋赶脚你好像入错行了啊。”

“好吧老师,就说了人家天资聪慧嘛。”

“不过二丫赶脚攻城狮有意思,关键工资高啊,二丫还得跟着老师继续混下去,多学点本领,这样工资才能涨涨涨呢,最为关键的是对于二丫这个单身狗来说,职场码农的男票多,有得选还靠谱啊,说不定今年二丫我就脱单啦。”

老师我顿时表示三观尽毁#@¥#¥%#¥%&……%。

“好了,虽然二丫是这么的不靠谱,但是关键的问题点已经讲到:要多维度多视角去看待我们要分析的对象,这样才能客观、全面的认清和抓住事物的本质,对于我们进一步分析和解决问题才大有裨益。”

那么我们先正面瞅一瞅啥是栈回溯呢?辩证唯物主义认识论认为,实践决定认识,实践是认识的基础,所以我们先实践,上道具:

图:Linux Crash惊鸿一瞥

基于一个普遍的debug crash逻辑认知,我们在开发过程中用遇到linux kernel crash的期望log基本如上图所示:

箭头1:标识真凶的作案工具是哪种类型?一个NULL pointer、soft/hard lockup 、deadlock或者其他类型的作案凶器。从这里我们基本能看出这个刑事案件的类型,心里大致有一个分析案情的方向。

箭头2/3:案发现场时刻留下的重要分析线索,这些线索保留下了犯罪作案时刻具体行凶的位置(PC指针),逃跑方向(LR链接寄存器),罪犯的特征(pstate状态标识)等。以及各个现场留下的物证(31个64位通用寄存器x0-x30),通过它们我们可以找到案发现场的第一手有用资料,分析这个案件就是从这些证据开始切入。

箭头4:这个很重要,我们都知道,真凶作案肯定都是一环一环的精心策划准备的,比如在家里准备好作案凶器(NULL pointer),通过水路进入案发现场 ,期间可能去过厕所等等,箭头4的堆栈就像是公众场合的摄像头拍摄的视频保存在硬盘里面一样用来保存这些有用的信息点,只要我们拿到罪犯的嫌疑罪证,再加上行之有效的刑侦手段,就能很快的破案。

箭头5:就是我们这节课的最终目的,由于上面第4点保存的罪证信息比较零散没有逻辑,我们需要通过一定的技术刑侦手段把这些零散的证据拼凑并分析出真凶完整的作案流程,最后抓住真凶。

“老师,既然1~5的这些信息内核都打出来了,什么都准备好了,直接就能破案了,那我们还有什么可做的哦?” 狗蛋疑惑。

“狗蛋,老师都说了,上面是一个基本理想的情况,但有时候由于各种原因函数的回溯调用关系(如上箭头5)没有打印出来,或者堆栈被破坏掉了,如果要很容易的分析crash问题,我们就需要这节课总结的神技能啦。好了,没问题的话牛老师就继续了。”

资深的老司机门都知道,对于解决crash问题我们主要是想得到函数的一个调用栈顺序,这样我们才能定位和分析问题,那有同学就表示不理解,难道不是知道最后的一个函数栈的现场不就可以分析了吗,即使不要什么函数调用关系,应该也可以分析定位出问题吧? 非也,请看以下大屏幕:

函数的一个调用关系

假设真凶最后是拿了一个被称为空指针的炸药包炸掉了fun_die这个工厂(crash),那么我们只知道fun_die的现场是没有太大的用处,因为根据现场的信息我们仅仅知道元凶是拿的炸药包并且在fun_die这里点燃了炸药包,但是元凶携带的炸药包(NULL Pointer)最终是从哪条路来呢,空路1?还是陆路2?亦或是水路3?

有可能陆路和空陆都是正常的行人拜访fun_die这个城市,而元凶是带着炸药包从水路进入到fun_die这个城市的,所以我们要抓住真凶,就必须借助前面箭头4保存的信息通过逻辑分析来找到炸药包的源头,一举破获整个团伙,抓住真凶。

即我们需要真正的找到出问题的函数调用路径,如上的水路三:fun1->fun2->fun_die这个调用,查到NULLPointer最终是从fun1传递过来的,而funb1->fun_die,funa1->funa2->funa3->fun_die这两个调用路径都是其他时候的正常调用,不是我们需要关注的。

通过函数的汇编调用以及堆栈的组织方式进行层层回溯,从而找到出问题时函数的调用关系就是我们这节课的目的。

未完待续,干货即将奉上。

第一次在知乎上写文章,有兴趣的麻烦点个赞呗,我就高产似那啥了,O(∩_∩)O哈哈~,另外如果有需要ARM相关SPEC文档的,翻看第一篇文章的下载链接。

破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈(三)

极客皮皮

16 人赞同了该文章

引言

接上文:破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈(二)。

虽说这周难得享受个双休,但是革命尚未成功,瓶子哥还得完成未完之课啊,要不心里老是不踏实啊,我得赶紧劳动,把上节课还没讲完的神技能继续进行着,要不对不起码农朋友们的热情声和呐喊声啊!

基于AAPCS64栈帧的组织方式

那么什么是AAPCS64,和我们的堆栈调用有嘛关系呢?瓶子哥只想说,太有关系了,没有它我们还真只能在这谈谈人生,唠唠家常呢。

AAPCS:ARM Architecture Procedure Call Standard—ARM结构过程调用规范,AAPCS64即为针对64位体系结构的ARM结构过程调用规范,它是《ARM 体系结构的基础标准应用程序二进制接口》 (BSABI) 规范的组成部分。 遵循 AAPCS 编写代码可以确保分别编译和汇编的模块能够协同工作。

GCC有一个编译选项用于指定是否要使用 ARM体系结构的过程调用标准 (AAPCS)。AAPCS定义了各寄存器在函数调用过程中的作用、基础类型的长度、以及函数调用的基本准则,包括栈帧的处理、函数的参数传递法则等。所以理解栈帧的组织方式对于理解汇编代码和定位crash这种bug有重要意义。具体针对linux下的AAPCS/AAPCS64栈帧组织详细分析,瓶子哥后面会专门再拿出来专门讨论。这里限于侧重点我们先挑关键点来看AArch64 arm linux的栈帧组织结构的汇编描述如下:

FP(x29)寄存器保存栈帧地址,LR(x30)保存当前过程的返回地址。栈是从高地址向低地址生长。下边是一个实际的函数汇编栈帧描述例子,对于ARM64 linux平台下函数的开始和结束的汇编组织基本都是如下:

fun1函数以及fun1函数的反汇编代码,汇编的开头2处指令和结尾2处指令即为函数为了保存上下文做的寄存器LR,FP的压栈和出栈操作。

从上图的规律我们可以得出ARM64下栈帧布局如下

从上面的ARM64栈帧的组织结构我们可以总结得出以下几点信息:

1. arm64的lr,fp放在栈顶。

2. arm64中当前fp和sp相同,都是栈顶指针 。

3. 函数返回时,arm64先将栈中的lr值放入当前lr,再ret 。使用gcc编译选项-fomit-frame-pointer,可以使arm64不使用fp寄存器,这时栈帧稍有变化,栈不在保存fp,局部变量寻址过程也不使用该寄存器 。

4. 在caller调用callee的时候,先预留一段栈帧给本函数进行参数传递保存,寄存器压栈等:[sp,#-32]!,即sub sp ,sp,#32。callee在函数调用的时候会把caller的FP,LR压栈到本函数FP/SP指向的栈帧中:stp x29, x30,[sp] 。这样就能方便的进行栈回溯。

因此说了这么多知识点,我们最终得出手动恢复函数堆栈所需要的技能点:

1)根据callee的FP找到caller的FP,也即找到调用者的栈帧。这样通过FP的层层回溯就能把整个函数的调用栈帧找到。

2)根据本函数栈帧保存的LR来间接获取PC指针寄存器,从而根据符号表得到具体的函数名(ARM64没有进行PC的压栈,因此我们没法直接使用PC地址来获取入口函数名)。

那么关键问题来了,PC如何间接获取呢?我们知道在函数调用的时候我们是通过跳转指令B或者BL来进行函数调用的(如下图),在跳转的同时ARM会自动保存函数的返回地址到LR,也即下一条指令的入口地址,函数调用的时候进行LR压栈,函数返回的时候LR出栈,从而保证正确执行程序返回后的后续指令。

如下图所示在执行400570地址处的BL跳转指令的同时,LR的值更新为400574地址。而ARM/ARM64的指令编码长度是32位4字节,也就是说我们知道LR的值,通过LR指向的地址-4字节偏移就可以得到PC值即被调用函数callee的入口地址,再通过符号表即可得到此入口地址对应的函数名。

所有上面的各种知识点,最终是为了引出这两条法则。“怎么样?狗蛋,二丫,是不是觉得很简单豁然开朗呢?”

“老师,为啥不开篇就把这两条法则说出来呢?这样岂不很节约大家时间嘛?”

“二丫,佛家有云,饭是需要一口一口的吃,有很多的知识点是一环扣一环的,即使把结论得到了,但无疑是空中楼阁,程序员的世界,有太多的稀奇古怪的问题,我们只有真正融会贯通了相关的知识点才能够举一反三,游刃有余的解决问题了,不是吗,所以老湿告诉大家,一定要脚踏实地,低调、低调、再低调,重要的事说三遍!”

当然我们能够手动恢复堆栈是有一个前提,栈的内容是完整保存的,但如果栈的内容被冲刷干净或被破话了,我们大家连毛都看不到。所以有时候有必要开启栈保护,至少你还能找到栈顶的函数,gcc有相关的参数: -fstack-protector 和 -fstack-protector-all,强烈建议开启,在kernel源码的顶层Makefile里面通常有如下配置

所以我们只需要在配置内核的时候选择

CONFIG_CC_STACKPROTECTOR_REGULAR/CONFIG_CC_STACKPROTECTOR_STRONG就行了。

神兵利器之Crash工具

OK,有了法宝,还差什么呢?兵器,是的,猴哥的金箍棒,洪七公的打狗棒,张无忌的屠龙刀无一不说明了打仗得有一件趁手的神兵利器啊,这样才能事倍功半。

做AP(如智能手机,平板等)底层开发的工程师都知道,对于crash问题,类似Qualcomm高通,MTK,SAMSUNG等平台ODM厂商的工程师基本都使用TRACE32来进行调试,尤其是使用TRACE32 Simulator软件来进行debug,但是我等三流屌丝哪里买得起TRACE32仿真器嘛,所以也就木有TRACE32 Simulator了。

但是码农的世界真的是很好,很多大神们发布了许多的开源调试内核dump文件的调试工具,我们需要再次向这些大神们致敬,有了他们,我等屌丝才不会苦逼挑灯夜战的使用printk来debug问题点,也能让我们码农门有时间为了下半辈子的幸福,在这狼多肉少的世界里去勾兑勾兑妹子们,也有时间享受美好的人生。在这个案件中 我们要用到的神兵利器就是Crash工具。

crash工具一瞥

瓶子哥这么多年来基本都是使用的它,可谓帮助我破获了无数的要案大案。而对于我们破获的这类要案,主要需要用到crash的 RD,SYM,DIS,STRUCT命令。详细的crash工具实操我也会在后面的码农课堂上奉上。

(未完待续,精彩即将奉上。)

嗯嗯,感兴趣的话麻烦点个赞呗。

 

破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈(大结局)

极客皮皮

16 人赞同了该文章

引言
终于要大结局了,一个字:爽!

后面维护世界和平,锄奸惩恶的重任就交给各位码农君啦。

庖丁解牛之实操手动恢复堆栈

前面我们已经传授了大家神技能法则,以及一件神兵利器crash,所以是时候检验下我们的成果了。

上面是一个空指针crash的实际例子,可以看出内核没有打印出函数的backtrace。而堆栈现场是完整的,因此我们可以通过手动来恢复函数的调用堆栈。

再次回顾下破案的两条法则:

1)根据callee的FP找到caller的FP,也即找到调用者的栈帧。这样通过FP的层层回溯就能把整个函数的调用路径(栈帧)找到。

2)根据本函数栈帧保存的LR来间接获取PC,从而根据符号表得到具体的函数名,在调用子函数的时候,LR指向子函数的返回的下一条指令,通过LR指向的地址-4字节偏移就得到了 被调用函数callee的入口地址,再通过符号表即可得到此入口地址对应的函数名。

程序员的世界当然要用程序语言来代言我们自己,所以让我们再一次用码农的语言来描述下我们的结论吧。

假设我们需要恢复的堆栈调用为:func1->func2->fun_die。

提问:

已知:当前现场fun_die的 FP(die)/SP(die)/LR(die)/PC(die)。

求解:调用fun_die发生crash的函数调用路径?

“狗蛋,看你这节课听得很认真嘛,能否来解答下这个题目,检验下本节课的学习成果呢?老师我需要看下大家的听课情况,好及时查缺补漏,尽量让大家能够听懂,掌握本节课的要点,在实际的职场工作中做到举一反三的效果。”

“老师,通过这段时间课堂上的学习和平时私下的沟通中,狗蛋我的技能已经得到了很大的提升,知道你的专业课干货十足,这节课那是相当的认真,你看下我的解答怎么样吧。”

狗蛋之解答:

“恩,不错,完全正确,看来大家这节课都听得很认真嘛,老师甚感欣慰啊。现在就请同学们以狗蛋的代码来帮助初入职场的小马同事结束这个要案吧。请二丫同学在黑板上来推导,其他同学在座位上完成。”

二丫胸有成竹的来到黑板前开始了她的码砖事业。

当前现场:

Crash现场寄存器

使用crash工具加载内核转储文件即ramdump文件:

1)根据PC获取指向当前挂掉的函数代码现场:

pc : [<ffffffc000343068>]

decon_lpd_block_exit对应的汇编如下:

第一现场已经获取到:ffffffc000343068 (T)decon_lpd_block_exit+12。

“恩,很不错,请二丫同学等一等,这里老师再插播一些知识点”:由于对于这个问题比较简单,其实我们仅仅通过分析第一现场就能知道大致问题是哪里导致的了。代码是死在decon_lpd_block_exit+12,也即上图红色箭头标识的汇编 ldr w3,[x0,#1192] 。这条指令是什么意思呢? “什么,汇编不懂?这节课后赶紧去补习吧。”汇编不懂,那我们先看看对应的源码吧。

通过对应源码再结合汇编,我等码农还是可以猜出个5,6层的汇编含义了。在这里,老王就先直接给出这段代码的汇编解释:

注:arm linux汇编会将内联函数(inline)直接在主代码中展开,不会进行子函数调用。所以decon_lpd_block(decon)直接展开为if (!decon->id) atomic_inc(&decon->lpd_block_cnt),不进行bl调用。在看上面的黄色汇编: ldr w3, [x0,#1192],1192是什么意思呢?其实它就是decon的成员id偏移值。

有时候结构体成员比较多,我们很难数清成员的偏移值是多少,这里我们使用crash利器就灰常方便,直接使用-o参数,像下面这个结构体总共占用了3019000个字节,要是我们人为计算后面几个变量的偏移值,我想大家跳楼的心都要有了。所以crash这个神兵利器很不错吧。

从上图我们也得出[x0,#1192]中的1192就是id的偏移位置,也进一步验证了这句代码是在取值(decon->id)。而结合“Unable tohandle kernel NULL pointer dereference at virtual address 000004a8”

我们知道大概可能decon是一个空指针,而decon的值是通过形参传递进来的,根据子函数传参数传递规则,X0应该是保存的decon的地址,那我们在看下X0的值: x0 : 0000000000000000,果然不是一个有效的地址,所以结论就是decon_lpd_block_exit(structdecon_device *decon)传递了一个空指针。因为代码里面有很多函数调用了decon_lpd_block_exit函数,所以进一步的我们需要找到是最终在哪个调用路径上传递了NULL的 decon指针。

根据前面的法则:“在调用子函数的时候,LR是指向子函数返回的下一条指令”,那我们来验证下这个法则是否正确呢?

通过pc值我们已经找到了第一个栈帧调用。那我们看下lr(0xffffffc000333f4c)是否就是调用decon_lpd_block_exit函数返回的主函数的下一条指令的地址呢?

LR指向dsim_read_data+64,根据法则不出意外的话 LR-4=0xffffffc000333f48 地址处应该对应于decon_lpd_block_exit子函数的调用指令。everybody,it’s show time now!:

怎么样?出现这个结果是不是很鸡冻啊,有木有一种成就感,有木有想拥抱老师?有木有?到此为止,看来老师讲的还算是良心干货啊。

我想,后面的故事就很轻松了。

好,现在请二丫继续。

2)根据第一现场的FP来获取PC。

OK,我们现在在把法则搬出来依葫芦画瓢:

FP = x29 =ffffffc0b611b970

根据FP得到caller的PC:

PC= *(unsigned long*)(FP+ 8) - 4 = *(unsigned long *)LR- 4=*(unsigned long *)ffffffc000333f48

第二现场已经获取到:ffffffc000333f48 (T) dsim_read_data+0x3c

3)根据第一现场的FP来回溯caller的调用栈帧。

FP = x29 = ffffffc0b611b970

根据FP得到caller1的栈帧基指:

FP1 = *FP=ffffffc0b611b980

PC1= *(unsignedlong *)(FP1+ 8) - 4 = *(unsigned long *)ffffffc0b611b988 - 4

PC1=*(unsigned long *)ffffffc0b611b988 - 4 = ffffffc00033432c

第三现场已经获取到:ffffffc00033432c (t)dsim_read_test+0x20

FP2= *FP1=*(unsigned long *)ffffffc0b611b980

PC2= *(unsignedlong *)(FP2+ 8) - 4 = *(unsigned long *)ffffffc0b611b9f8- 4

PC2=*(unsigned long *)ffffffc00033450c- 4 = ffffffc000334508

第四现场已经获取到:ffffffc000334508 (t) dsim_runtime_resume+0xac。

以此类推:通过callee栈帧的FP得到caller的栈帧FP’。然后根据AAPCS64的栈帧组织结构得到caller的PC’,然后通过crash工具结合符号表得到caller的函数名。直到FP的值为0,表示没有更多的调用栈帧而到栈底。

由此得出整个函数的backtrace如下:

到此,我们就完成了整个函数backtrace的恢复调用。在结合强大的crash工具和源码分析,相信破案已经是手到擒来了,是不是有那么一丝丝的鸡冻呢。

通过本节课多个知识点的回顾和学习,老师有足够的理由相信,你可以在职场上和你一样的菜鸟们面前显摆一下(当然老王还是那句话,低调),因为无疑这个案子将使你在crash debug的知识技能点上超越一般的码农,通过在实际的工作再运用此中的技能来解决实际的问题,相信你在码农的道路上又前进了一大步。

“哇塞,老师,这么说来狗蛋,二丫明年升职加薪看来不是问题了呀,想想都鸡冻,我们还要和老师学更多的经验技能,早日晋升老司机,请老师带我们早日走上高大上,白富美的生活呀!”

“好了,就你们丫话多,老师最后再啰嗦一句话:everybody,这次大讲堂之

“破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈”

终于大结局了,感谢大家的聆听,下课!”

瓶子哥终于可以缓口气了,毕竟今天答应了某位知友一并更完。。。

PS:另外本文章所涉及的AAPCA过程调用规范文档我在第一篇文章有分享下载链接,需要的可以下载阅览。另外其他ARM手册相关文档我也整理了下,需要的话可以在公众号“程序员观世界”回复关键字ARM一并下载,感谢大家。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值