Java知识分享网 - 轻松学习从此开始!    

Java知识分享网

Java1234官方群25:java1234官方群17
Java1234官方群25:838462530
        
SpringBoot+SpringSecurity+Vue+ElementPlus权限系统实战课程 震撼发布        

最新Java全栈就业实战课程(免费)

springcloud分布式电商秒杀实战课程

IDEA永久激活

66套java实战课程无套路领取

锋哥开始收Java学员啦!

Python学习路线图

锋哥开始收Java学员啦!
当前位置: 主页 > Java文档 > 安卓技术相关 >

Native下如何获取调用栈? PDF 下载


分享到:
时间:2020-08-08 10:09来源:http://www.java1234.com 作者:小锋  侵权举报
Native下如何获取调用栈? PDF 下载
失效链接处理
Native下如何获取调用栈? PDF 下载

本站整理下载:
 
相关截图:
 
主要内容:

Native下如何获取调用栈?
2019-02-02 simsun
Android开发高手课 进入课程
讲述:冯永吉
时长 00:54 大小 854.15K
你好,我是 simsun,曾在微信从事 Android 开发,也是开源爱好者、Rust 语言“铁
粉”。应绍文邀请,很高兴可以在“高手课”里和你分享一些编译方面的底层知识。
当我们在调试 Native 崩溃或者在做 profiling 的时候是十分依赖 backtrace 的,高质量的
backtrace 可以大大减少我们修复崩溃的时间。但你是否了解系统是如何生成 backtrace
的呢?今天我们就来探索一下 backtrace 背后的故事。
下面是一个常见的 Native 崩溃。通常崩溃本身并没有任何 backtrace 信息,可以直接获得
的就是当前寄存器的值,但显然 backtrace 才是能够帮助我们修复 Bug 的关键。
 1 pid: 4637, tid: 4637, name: crasher >>> crasher <<<
 复制代码
  下载APP 
2 signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
3 Abort message: 'some_file.c:123: some_function: assertion "false" failed'
4 r0 00000000 r1 0000121d r2 00000006 r3 00000008
5 r4 0000121d r5 0000121d r6 ffb44a1c r7 0000010c
6 r8 00000000 r9 00000000 r10 00000000 r11 00000000
7 ip ffb44c20 sp ffb44a08 lr eace2b0b pc eace2b16
8 backtrace:
9 #00 pc 0001cb16 /system/lib/libc.so (abort+57)
10 #01 pc 0001cd8f /system/lib/libc.so (__assert2+22)
11 #02 pc 00001531 /system/bin/crasher (do_action+764)
12 #03 pc 00002301 /system/bin/crasher (main+68)
13 #04 pc 0008a809 /system/lib/libc.so (__libc_init+48)
14 #05 pc 00001097 /system/bin/crasher (_start_main+38)
在阅读后面的内容之前,你可以先给自己 2 分钟时间,思考一下系统是如何生成
backtrace 的呢?我们通常把生成 backtrace 的过程叫作 unwind,unwind 看似和我们平
时开发并没有什么关系,但其实很多功能都是依赖 unwind 的。举个例子,比如你要绘制
火焰图或者是在崩溃发生时得到 backtrace,都需要依赖 unwind。
书本中的 unwind
1. 函数调用过程
如果你在大学时期修过汇编原理这门课程,相信你会对下面的内容还有印象。下图就是一个
非常标准的函数调用的过程。
首先假设我们处于函数 main() 并准备调用函数 foo(),调用方会按倒序压入参数。此时
第一个参数会在调用栈栈顶。
调用 invoke foo() 伪指令,压入当前寄存器 EIP 的值到栈,然后载入函数 foo() 的地址
到 EIP。
此时,由于我们已经更改了 EIP 的值(为 foo() 的地址),相当于我们已经进入了函数
foo()。在执行一个函数之前,编译器都会给每个函数写一段序言(prologue),这里会
压入旧的 EBP 值,并赋予当前 EBP 和 ESP 新的值,从而形成新的一个函数栈。
下一步就行执行函数 foo() 本身的代码了。
结束执行函数 foo() 并准备返回,这里编译器也会给每个函数插入一段尾声
(epilogue)用于恢复调用方的 ESP 和 EBP 来重建之前函数的栈和恢复寄存器。
执行返回指令(ret),被调用函数的尾声(epilogue)已经恢复了 EBP 和 ESP,然后我
们可以从被恢复的栈中依次 pop 出 EIP、所有的参数以及被暂存的寄存器的值。
读到这里,相信如果没有学过汇编原理的同学肯定会有一些懵,我来解释一下上面提到的寄
存器缩写的具体含义,上述命名均使用了 x86 的命名方式。讲这些是希望你对函数调用有
一个初步的理解,其中有很多细节在不同体系结构、不同编译器上的行为都有所区别,所以
请你放松心情,跟我一起继续向后看。
EBP:基址指针寄存器,指向栈帧的底部。
在 ARM 体系结构中,R11(ARM code)或者 R7(Thumb code)起到了
类似的作用。在 ARM64 中,此寄存器为 X29。
ESP:栈指针寄存器,指向栈帧的栈顶 , 在 ARM 下寄存器为 R13。
EIP:指令寄存器,存储的是 CPU 下次要执行的指令的地址,ARM 下为
PC,寄存器为 R15。
2. 恢复调用帧
如果我们把上述过程缩小,站在更高一层视角去看,所有的函数调用栈都会形成调用帧
(stack frame),每一个帧中都保存了足够的信息可以恢复调用函数的栈帧。


 

------分隔线----------------------------

锋哥公众号


锋哥微信


关注公众号
【Java资料站】
回复 666
获取 
66套java
从菜鸡到大神
项目实战课程

锋哥推荐