谁在CALL我 -- callstack的实现原理

博客围绕callstack的实现原理展开,涉及编译器、嵌入式、Linux等信息技术相关内容,虽未详细阐述具体原理,但点明了核心主题。

 转载时请注明出处和作者联系方式
作者联系方式:李先静 <xianjimli at hotmail dot com>


开发嵌入式软件通常是比较麻烦的事,一些常用的工具往往无法使用,在开发PC软件时简单的任务,此时变得很复杂。今天就遇到了这样一件事,折腾了几个小时,仅仅是为知道call stack。

我编译了一个程序放到PDA(ARM9+LINUX+UCLIBC)上面运行,出现了一个ASSERT,并显示了文件名和行号,原来是调用了一个没有实现的函数,我很想知道是谁调用了它,这看似简单的问题却让我很头疼,如果有gdb,那好办-用bt命令就可以搞定,如果用的libc,那也好办-用backtrace函数就可以搞定,问题是两者都没有。

想来想去只有自己写一个backtrace,要实现这个功能并不难,如果我们知道调用堆栈的格式,就可以很容易取出上层调用者的指令地址,有了这些上层调用者的指令地址,我们可以通过MAP文件找到指令地址对应的源文件名和行号。

下面简要介绍一下实现原理:

要获得调用者的地址,有必要介绍一下堆栈的格式:

+---------------------------+ (高地址)
+_参数1__________+
+---------------------------+
+_参数2__________+
+---------------------------+ 参数的顺序依赖于调用方式
+_参数.__________+
+---------------------------+
+_参数N__________+
+---------------------------+
+_eip____________+ 返回本次调用后,下一条指令的地址
+----------------------------+
+_ebp____________+ 这里保存的调用者的ebp
+----------------------------+
(ebp 指向这里:相当于调用者和被调用者的分界线)
+----------------------------+
+_临时变量1_______+
+----------------------------+
+_临时变量2_______+
+----------------------------+
+_临时变量.________+
+----------------------------+
+----------------------------+
+_临时变量N_______+
+----------------------------+(低地址)
由于优化、调用方式、编译器的不同,上述布局部可能有所不同,但一般来说,第一个局部变量前是调用者的ebp,ebp前是返回后下一条指令的地址。

知道了这个结构,要获得上层调用的者指令地址就容易了,我们可以用如下代码模拟glibc提供的backtrace的功能:
int backtrace (void **BUFFER, int SIZE)
{
    int n = 0;
    int *p = &n;
    int i = 0;

    int ebp = p[1];
    int eip = p[2];

    for(i = 0; i < SIZE; i++)
    {
        BUFFER[i] = (void*)eip;
        p = (int*)ebp;
        ebp = p[0];
        eip = p[1];
    }

    return SIZE;
}

附:
  通过addr2line可以找到地址对应的文件名和行号,不用手动去查MAP文件了。


<think>嗯,用户想在BufferQueueProducer的dequeueBuffer函数中添加调用栈记录,用于调试或日志。首先,我需要回忆一下Android图形系统中BufferQueue的工作原理。根据引用[3],dequeueBuffer会向BufferQueueProducer申请内存,并通过GraphicBufferAlloc分配缓冲区。这时候用户可能遇到了问题,比如缓冲区分配失败或者调用流程异常,需要跟踪调用路径来排查。 接下来,我需要考虑如何在C++代码中捕获调用栈。常用的方法有libunwind、backtrace函数或者Android专用的CallStack工具。记得在源码中,BufferQueueProducer.cpp属于系统底层,可能需要特定的权限或者方法。例如,引用[2]提到IGraphicBufferProducer通过Binder跨进程通信,服务端实现BufferQueueProducer.cpp,所以调用栈可能涉及跨进程,需要确认记录的是服务端还是客户端的栈。 然后,具体实现步骤应该包括引入头文件,比如<utils/CallStack.h>,然后创建CallStack对象并更新。不过需要注意Android版本差异,不同版本的CallStack可能有不同用法。另外,日志输出要使用ALOGE或__android_log_print,确保日志可见。 还有,用户可能需要控制调用栈的深度,避免日志过多。另外,考虑日志频率,可能需要在调试完成后移除代码,或者使用条件编译。根据引用[1],queueBuffer最终调用BufferQueueProducer的queueBuffer,这说明BufferQueueProducer在队列操作中的重要性,所以添加调用栈能帮助理解整个流程。 最后,验证方法需要编译模块,推送库文件,抓取日志。用户可能需要知道如何编译和调试,这部分需要简要提及。同时,提醒用户注意性能和日志量,避免影响系统稳定性。总之,步骤是引入调用栈库,在dequeueBuffer中添加记录代码,处理Android版本兼容性,输出日志,并验证结果。</think>在Android图形系统中,`BufferQueueProducer::dequeueBuffer`函数中添加调用栈记录可通过以下步骤实现: ### 一、实现调用栈记录 1. **引入调用栈工具** 在`BufferQueueProducer.cpp`文件头部添加头文件: ```cpp #include <utils/CallStack.h> // Android专用调用栈工具 ``` 2. **添加调用栈记录代码** 在`dequeueBuffer`函数起始位置插入: ```cpp android::CallStack callStack; callStack.update(); // 捕获当前调用栈 callStack.log("BQP_DEBUG", ANDROID_LOG_ERROR); // 通过logcat输出 ``` ### 二、Android版本适配 - **Android 11+** 需改用`Unwinder`工具: ```cpp #include <unwindstack/Unwinder.h> void logStack() { unwindstack::AndroidLocalUnwinder unwinder; unwindstack::AndroidUnwinderData data; unwinder.Unwind(data.GetMaps(), data.GetRegs()); ALOGE("Call stack:\n%s", data.Format().c_str()); } ``` 在`dequeueBuffer`中调用`logStack()` ### 三、日志输出控制 1. **通过ALOG宏过滤** 使用`ALOGE("BQP_DEBUG: %s", callStack.toString().c_str());`输出带TAG的日志,可通过`logcat -s BQP_DEBUG`过滤 2. **动态控制开关** 添加属性控制: ```cpp char value[PROPERTY_VALUE_MAX]; property_get("debug.bq.stacktrace", value, "0"); if (atoi(value) > 0) { // 记录调用栈代码 } ``` ### 四、实现原理 1. **CallStack实现机制** Android的`CallStack`类通过`libunwind`库解析堆栈帧,遍历`/proc/self/maps`获取内存映射信息,结合DWARF调试符号解析函数名[^3] 2. **跨进程调用场景** 当应用进程通过`IGraphicBufferProducer`发起`dequeueBuffer`请求时,实际执行在`surfaceflinger`或`HWComposer`进程的`BufferQueueProducer`实例中[^2]。调用栈会显示从Binder线程池到具体业务逻辑的完整路径。 ### 五、验证方法 1. 编译修改后的`libgui`模块: ```bash mmm frameworks/native/libs/gui/ ``` 2. 推送更新后的库文件: ```bash adb push libgui.so /system/lib64/ ``` 3. 抓取日志: ```bash adb logcat -c && adb logcat | grep BQP_DEBUG ``` ### 六、注意事项 1. 性能影响:单次调用栈捕获耗时约2-5ms,建议仅在调试时启用 2. 符号依赖:需使用`eng`或`userdebug`版本系统镜像 3. 跨版本兼容:不同Android版本中BufferQueueProducer的实现位置可能变化(如Android 12中部分逻辑迁移到`libsurfaceflinger`)
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值