0
点赞
收藏
分享

微信扫一扫

[每日AI·0506]巴菲特谈 AI,李飞飞创业,苹果或将推出 AI 功能,ChatGPT 版搜索引擎

清冷的蓝天天 2024-05-07 阅读 12
android

文章目录


在Android系统中,我们有时需要获取Native层的堆栈信息,例如在进行性能分析、问题定位和调试等场景。本文将介绍如何在Android Native层获取堆栈信息,并提供示例代码。

工具和方法

在Android系统中,我们可以使用backtrace函数来获取当前线程的堆栈信息。backtrace函数是GNU libc库提供的一个函数,它可以获取当前线程的调用堆栈。此外,Android NDK还提供了unwind.h头文件,其中定义了unwind函数,可以用于获取任意线程的堆栈信息。

获取当前线程的堆栈信息

我们可以使用backtrace函数获取当前线程的堆栈信息。以下是使用backtrace函数获取堆栈信息的示例代码:

#include <execinfo.h>
#include <stdio.h>

void print_backtrace() {
    const int max_frames = 128;
    void* frame[max_frames];
    int frame_count = backtrace(frame, max_frames);

    char** symbols = backtrace_symbols(frame, frame_count);
    if (symbols) {
        for (int i = 0; i < frame_count; ++i) {
            printf("%s\n", symbols[i]);
        }
        free(symbols);
    }
}

在上述代码中,backtrace函数会将当前线程的调用堆栈的地址保存到frame数组中,然后我们使用backtrace_symbols函数将这些地址转换为对应的函数名。

获取任意线程的堆栈信息

如果我们需要获取任意线程的堆栈信息,可以使用Android NDK中的unwind函数。以下是使用unwind函数获取堆栈信息的示例代码:

#include <unwind.h>
#include <dlfcn.h>
#include <stdio.h>

struct BacktraceState {
    void** current;
    void** end;
};

_Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, void* arg) {
    BacktraceState* state = static_cast<BacktraceState*>(arg);
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc) {
        if (state->current == state->end) {
            return _URC_END_OF_STACK;
        } else {
            *state->current++ = reinterpret_cast<void*>(pc);
        }
    }
    return _URC_NO_REASON;
}

void capture_backtrace(void** buffer, int max) {
    BacktraceState state = {buffer, buffer + max};
    _Unwind_Backtrace(unwind_callback, &state);
}

void print_backtrace(void** buffer, int count) {
    for (int idx = 0; idx < count; ++idx) {
        const void* addr = buffer[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) {
            symbol = info.dli_sname;
        }

        printf("%-3d %p %s\n", idx, addr, symbolvoid print_backtrace(void** buffer, int count) {
    for (int idx = 0; idx < count; ++idx) {
        const void* addr = buffer[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) {
            symbol = info.dli_sname;
        }

        // 计算相对地址
        void* relative_addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) - reinterpret_cast<uintptr_t>(info.dli_fbase));

        printf("%-3d %p %s (relative addr: %p)\n", idx, addr, symbol, relative_addr);
    }
});
    }
}

在上述代码中,capture_backtrace函数使用_Unwind_Backtrace函数获取堆栈信息,然后我们使用dladdr函数获取到函数所在的SO库的基地址(info.dli_fbase),然后计算出函数的相对地址(relative_addr)。然后在打印堆栈信息时,同时打印出函数的相对地址。

GCC生成符号的规律

Native堆栈的符号信息跟代码中定义的函数名字相比,可能会有一些差别,因为GCC生成的符号表有一些修饰规则。GCC在编译C/C++等语言源代码时,会生成相应的符号。这些符号主要用于链接时解析和调试等目的。GCC生成的符号遵循一定的规则,主要包括以下几点:

  1. 名字修饰(Name Mangling):
    C++支持函数重载,即同一个函数名可以有不同的参数类型和个数。为了在编译时区分这些函数,GCC会对函数名进行修饰,生成独特的符号名称。修饰后的名称包含了函数名、参数类型等信息。例如,对于如下C++函数:
namespace test {
  int foo(int a, double b);
}

经过GCC修饰后,生成的符号可能类似于:_ZN4test3fooEid,其中:

  • _ZNE是修饰前缀和后缀,用于标识这是一个C++符号。
  • 4test表示命名空间名为test4表示命名空间名的长度。
  • 3foo表示函数名为foo3表示函数名的长度。
  • id表示函数的参数类型,i代表intd代表double
  1. 变量符号:
    全局变量和静态变量的符号名称通常与其在源代码中的名称相同。局部变量的符号名称会包含函数名和变量名,以及作用域信息。

  2. 符号可见性(Symbol Visibility):
    根据符号在源代码中的声明,GCC会为生成的符号分配不同的可见性。例如:

  • static:局部符号,只在当前源文件内可见。
  • extern:外部符号,可以在其他源文件中使用。
  • 默认:如果没有指定staticextern,则符号的可见性取决于编译器的默认设置。
  1. 弱符号和强符号:
    弱符号(Weak Symbol)是可以被其他同名符号覆盖的符号,而强符号(Strong Symbol)是不会被覆盖的。当链接器在链接过程中遇到同名的强符号和弱符号时,会选择强符号。在GCC中,可以使用__attribute__((weak))修饰符来声明弱符号。

了解GCC生成符号的规律有助于我们在分析和调试程序时快速定位函数和变量,以及理解链接器在链接过程中如何处理这些符号。

总结

获取Android Native堆栈信息是Android性能分析和问题定位的重要手段。通过使用backtraceunwind函数,我们可以方便地获取到堆栈信息,并进行后续的分析和处理。

举报

相关推荐

0 条评论