在调试Android系统底层函数时,经常需要跟踪函数调用流程,特别在HAL层需要确定参数来源时。使用栈信息逆向跟踪可快速分析函数调用流程,结合使用addr2line工具、绘图工具可绘制函数关系图。本文记录在Android Q 上打印C/C++函数栈信息的方法,以作参考。
Android官网及芯片厂商介绍HAL层的资料不多,有些作为内部文档不会外传。在Android系统层次中,kernel和HAL层与硬件强相关,在不同手机上这两层的差别较大,也最能体现产品特点和性能。
HAL层函数运行于用户态,一般该调用链起始于Native C/C++ Library启动的某个线程中;而App层和Java API Framework层的函数调用链一般起始于Android Runtime维护的某个线程中(由Java虚拟机统一管理)。线程间使用同步机制交互。因此,在使用栈信息追踪了调用链后,仍要结合源码和Log分析数据传递过程,特别要注意进程同步机制,这是考验经验的过程。
以相机Camera2消息处理函数为例,打印栈信息到Log中。
1. Android源码提供了libutils库(源码注释有对库功能的说明,需要具体分析),该库含有CallStack类,提供了栈打印功能。在Android Q源码中该库位于/system/core/libutils内,头文件在/system/core/include/utils软链接指向/system/core/libutils/include/utils,如下所示:
CallStack类声明位于libutils/utils/CallStack.h内,在namespace android中。该类的构造方法(在CallStack.cpp中)内即打印栈信息到Log,构造方法实现如下:
CallStack::CallStack() {
}
CallStack::CallStack(const char* logtag, int32_t ignoreDepth) {
this->update(ignoreDepth+1);
this->log(logtag);
}
CallStack构造方法第一个参数为Log tag,第二个参数为忽略打印的栈深度,默认值为1。
分析libutils编译文件Android.bp,发现该CallStack.cpp最后编译为libutilscallstack.so,依赖于libutils.so和libbacktrace.so文件。
cc_library {
name: "libutilscallstack",
defaults: ["libutils_defaults"],
srcs: [
"CallStack.cpp",
],
arch: {
mips: {
cflags: ["-DALIGN_DOUBLE"],
},
},
shared_libs: [
"libutils",
"libbacktrace",
],
......
}
2. 在camera2相机底层消息处理函数内添加创建android::CallStack对象,包含头文件#include
// #define LOG_NDEBUG 0
#define LOG_TAG "CameraRequest"
#include
#include
#include
#include
#include
#include
#include
namespace android {
namespace hardware {
namespace camera2 {
// These must be in the .cpp (to avoid inlining)
...
status_t CaptureRequest::readFromParcel(const android::Parcel* parcel) {
if (parcel == NULL) {
ALOGE("%s: Null parcel", __FUNCTION__);
return BAD_VALUE;
}
mSurfaceList.clear();
mStreamIdxList.clear();
mSurfaceIdxList.clear();
mPhysicalCameraSettings.clear();
android::CallStack dumpstack("EMUL",1);
status_t err = OK;
int32_t settingsCount;
在编译配置文件Android.bp内添加libutilscallstack.so依赖
cc_library_shared {
name: "libcamera_client",
aidl: {
...
],
},
srcs: [
...
],
shared_libs: [
"libcutils",
"libutils",
"liblog",
"libbinder",
"libgui",
"libcamera_metadata",
"libnativewindow",
"libutilscallstack",
],
...
}
重新编译,等待libcamera_client生成完成。
3. 运行emulator测试栈打印结果。开启emulator模拟器,打开LogCat抓取日志;开启Camera2 App,点击拍照按钮,Log中即可得到栈打印。
Log中搜索EMUL,得到的栈信息如下,#00代表栈顶,从栈底到栈顶依次为调用链上的每个函数。所在文件和位置也一目了然。
4. 有时栈信息只显示指令地址如00046d67和所在文件libcamera_client.so,不显示函数名。
借助addr2line工具可获得函数名称。首先根据编译平台(android lunch所选平台为x86_64_generic)选取对应的addr2line工具(该工具位置根据find指令查找prebuilts文件夹获得),注意addr2line选择解析的so文件必须是包含符号表的文件,指令为:
解析结果如下所示:
可以发现其显示了代码内容和源文件。通过使用addr2line依次获得每个pc 地址对应的源代码,即可完整解析栈信息。
经验证,上述方法在HAL层函数中同样可获取栈信息内容。
在APP和Java Framework层,使用Log.getStackTraceString函数可获得当前位置的线程调用栈信息,该函数返回栈信息字符串,使用Log.d可打印到日志中。在想要获取的位置插入getStackTraceString以获得当前线程的调用栈。Log.d("TAG",Log.getStackTraceString(new Throwable()));
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Log.d("EMUL",Log.getStackTraceString(new Throwable()));
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
...
}
});
}
...
}
插入栈打印后,编译程序并执行,在Logcat中可见栈信息:
该方法同样适用于借助android studio和真机调试Java Framework层(加载android源码ipr工程到android studio内)。调试前务必保持java Framework与手机的SDK版本一致。此外,Java Framework可加入断点用以判断某一函数是否被执行,若被执行再加入getStackTraceString函数。
使用android studio抓取的栈打印信息,从而获取了Activity启动流程。
每层调用都显示了函数名称和源文件位置。
注意:编译android源码时,lunch选择的平台与运行平台一致;若运行于emulator,则最好与主机平台一致,以防止emulator运行缓慢。若emulator需运行于x86_64平台,则lunch选择x86_64_generic,这样可加快emulator的运行速度;若lunch选择arm64,则emulator仍可在x86_64主机上运行,但速度过慢。