大家都很熟悉Exception.printStackTrace,而且也非常的常用。不过,各位有没有想过应该如何实现呢?
想象一下,当我们调试一个C/C++程序的时候,我们也能看到函数的调用堆栈,一定会想,这究竟是怎么做到呢?因为ART运行的也是机器码,那么它们也一定有共同的地方吧。
当然,我们并不是真正的去了解一个C/C++程序是如何实现调试的,我们只是借鉴一下它的原理。
以ARM为例,我们都知道,当一个函数运行时,CPU的寄存器PC保存了当前指令的地址,而寄存器SP保存了当前的堆栈栈顶地址。当我们调用一个函数时,需要用指令BL <地址偏移> ,这个时候,CPU会将BL后面的指令地址放入寄存器LR中,然后跳转到子函数的入口。
BL 是Branch Link的缩写,LR 是Link Register的缩写。对于子函数,如果要正确返回,它必须在入口处 PUSH LR,当函数返回时,POP LR,然后再 B LR。 (更简洁的方法是 POP PC)。如果我们知道函数所使用的栈帧大小,那么我们就可以知道上个函数的调用地址了。
至于栈帧的大小,有很多方法,比如,将它们和函数的入口地址做个映射,根据函数的入口地址获取栈帧的大小,配合保存在栈固定位置的LR寄存器的值,就可以获得完整的调用栈了。
根据《ART如何传递参数》这一章节,我们知道,一个普通java函数在被转换为本地函数后,它的栈结构是这样的。
PUSH LR |
.... <其他寄存器> .... |
.... <内部变量/参数调用必须> .... |
ArtMethod * |
我们知道栈顶元素是ArtMethod的指针,也就是这个栈帧所代表的函数。
那么,ArtMethod能够获取栈帧的大小吗?我想是肯定的。
仔细阅读以下源代码,让我们找到看: