在调试Android系统底层函数时,经常需要跟踪函数调用流程,特别在HAL层需要确定参数来源时。使用栈信息逆向跟踪可快速分析函数调用流程,结合使用addr2line工具、绘图工具可绘制函数关系图。本文记录在Android Q 上打印C/C++函数栈信息的方法,以作参考。

       Android官网及芯片厂商介绍HAL层的资料不多,有些作为内部文档不会外传。在Android系统层次中,kernel和HAL层与硬件强相关,在不同手机上这两层的差别较大,也最能体现产品特点和性能。

(图片来源:Android developer cn网站)

       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主机上运行,但速度过慢。

 

更多相关文章

  1. 安全篇 - 隐式配置 KeyStore 签名信息
  2. Android中Dialog对话框的调用及监听
  3. OSG for Android新手教程系列(三)——HelloWorld,第一个示例
  4. 翻译:Tutorial1:透明面板(LinearLayout)定义
  5. Android(安卓)编程技巧之 ----- 自定义 View 踩坑总结
  6. Android(安卓)MVP设计架构:Model层数据传递到View层
  7. 关于android版本spice协议tls端口链接方式的bug问题
  8. 说明Android应用调用全屏方式
  9. 史上最全的Android常规知识点面试题集锦

随机推荐

  1. 关于android设备的分辨率
  2. 【Android多屏适配】动态改变Listview it
  3. Android应用程序级变量(全局变量)
  4. Android中选取并绑定AppWidget
  5. 【Android】JSONArray的合并
  6. Android(安卓)Dalvik虚拟机初识
  7. Android带文字的ImageButton实现
  8. android之PackageManager简单介绍
  9. android rom修改小白有福了
  10. Android(安卓)popupwindow弹出对话框