目录
1. Java中打印堆栈
1.1 Throwable类
堆栈信息获取和输出,都可以通过Throwable类的方法实现。通用做法是在java进程出现需要注意的异常时,打印堆栈,然后再决定退出或挽救。一般是使用exception的printStackTrace()方法:
try {
...
} catch (RemoteException e) {
e.printStackTrace();
...
}
也可以只打印堆栈不退出,这样就比较方便分析代码的动态运行情况。Java代码中插入堆栈打印的方法如下:
Log.d(TAG,Log.getStackTraceString(new Throwable()));
1.2 Debug类
在/frameworks/base/core/java/android/os/Debug.java中,定义了Debug类,该类中提供了getCaller的重载方法,用于打印函数的调用堆栈:
/**
* @return a String describing the immediate caller of the calling method.
* {@hide}
*/
public static String getCaller() {
return getCaller(Thread.currentThread().getStackTrace(), 0);
}
/**
* Return a string consisting of methods and locations at multiple call stack levels.
* @param depth the number of levels to return, starting with the immediate caller.
* @return a string describing the call stack.
* {@hide}
*/
public static String getCallers(final int depth) {
final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < depth; i++) {
sb.append(getCaller(callStack, i)).append(" ");
}
return sb.toString();
}
/**
* Return a string consisting of methods and locations at multiple call stack levels.
* @param depth the number of levels to return, starting with the immediate caller.
* @return a string describing the call stack.
* {@hide}
*/
public static String getCallers(final int start, int depth) {
final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
StringBuffer sb = new StringBuffer();
depth += start;
for (int i = start; i < depth; i++) {
sb.append(getCaller(callStack, i)).append(" ");
}
return sb.toString();
}
/**
* Like {@link #getCallers(int)}, but each location is append to the string
* as a new line with <var>linePrefix</var> in front of it.
* @param depth the number of levels to return, starting with the immediate caller.
* @param linePrefix prefix to put in front of each location.
* @return a string describing the call stack.
* {@hide}
*/
public static String getCallers(final int depth, String linePrefix) {
final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < depth; i++) {
sb.append(linePrefix).append(getCaller(callStack, i)).append("\n");
}
return sb.toString();
}
2.C++代码中打印堆栈
C++也是支持异常处理的,异常处理库中,已经包含了获取backtrace的接口,Android也是利用这个接口来打印堆栈信息的。在Android的C++中,已经集成了一个工具类CallStack,在libutils.so中。使用方法:
#include <utils/CallStack.h>
...
CallStack stack;
stack.update();
stack.dump();
使用方式比较简单。目前Andoid4.2版本已经将相关信息解析的很到位,符号表查找,demangle,偏移位置校正都做好了(4.2以后直接显示函数名无需再转换地址)。
3. C代码中打印堆栈
C代码,尤其是底层C库,想要看到调用的堆栈信息,还是比较麻烦的。 CallStack肯定是不能用,一是因为其实C++写的,需要重新封装才能在C中使用,二是底层库反调上层库的函数,会造成链接器循环依赖而无法链接。不过也不是没有办法,可以通过android工具类CallStack实现中使用的unwind调用及符号解析函数来处理。
这里需要注意的是,为解决链接问题,最好使用dlopen方式,查找需要用到的接口再直接调用,这样会比较简单。如下为相关的实现代码,只需要在要打印的文件中插入此部分代码,然后调用getCallStack()即可,无需包含太多的头文件和修改Android.mk文件:
#define MAX_DEPTH 31
#define MAX_BACKTRACE_LINE_LENGTH 800
#define PATH "/system/lib/libcorkscrew.so"
typedef ssize_t (*unwindFn)(backtrace_frame_t*, size_t, size_t);
typedef void (*unwindSymbFn)(const backtrace_frame_t*, size_t, backtrace_symbol_t*);
typedef void (*unwindSymbFreeFn)(backtrace_symbol_t*, size_t);
static void *gHandle = NULL;
static int getCallStack(void){
ssize_t i = 0;
ssize_t result = 0;
ssize_t count;
backtrace_frame_t mStack[MAX_DEPTH];
backtrace_symbol_t symbols[MAX_DEPTH];
unwindFn unwind_backtrace = NULL;
unwindSymbFn get_backtrace_symbols = NULL;
unwindSymbFreeFn free_backtrace_symbols = NULL;
// open the so.
if(gHandle == NULL) gHandle = dlopen(PATH, RTLD_NOW);
// get the interface for unwind and symbol analyse
if(gHandle != NULL) unwind_backtrace = (unwindFn)dlsym(gHandle, "unwind_backtrace");
if(gHandle != NULL) get_backtrace_symbols = (unwindSymbFn)dlsym(gHandle, "get_backtrace_symbols");
if(gHandle != NULL) free_backtrace_symbols = (unwindSymbFreeFn)dlsym(gHandle, "free_backtrace_symbols");
if(!gHandle ||!unwind_backtrace ||!get_backtrace_symbols || !free_backtrace_symbols ){
ALOGE("Error! cannot get unwind info: handle:%p %p %p %p",
gHandle, unwind_backtrace, get_backtrace_symbols, free_backtrace_symbols );
return result;
}
count= unwind_backtrace(mStack, 1, MAX_DEPTH);
get_backtrace_symbols(mStack, count, symbols);
for (i = 0; i < count; i++) {
char line[MAX_BACKTRACE_LINE_LENGTH];
const char* mapName = symbols[i].map_name ? symbols[i].map_name : "<unknown>";
const char* symbolName =symbols[i].demangled_name ? symbols[i].demangled_name : symbols[i].symbol_name;
size_t fieldWidth = (MAX_BACKTRACE_LINE_LENGTH - 80) / 2;
if (symbolName) {
uint32_t pc_offset = symbols[i].relative_pc - symbols[i].relative_symbol_addr;
if (pc_offset) {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s+%u)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName, pc_offset);
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s)",
i, symbols[i].relative_pc, fieldWidth, mapName,
ieldWidth, symbolName);
}
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s",
i, symbols[i].relative_pc, fieldWidth, mapName);
}
ALOGD("%s", line);
}
free_backtrace_symbols(symbols, count);
return result;
}
4.Linux内核中打印堆栈
在Linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入:dump_stack();或 __backtrace();即可。
5.动态方法
adb连上DDMS后,用shell命令查看堆栈。
5.1 dump java调用栈
java调用栈包含了native栈和内核栈。
kill -3 <pid>
由于是虚拟机帮忙,目前只适用于虚拟机进程(即java进程);
5.2 dump native栈
用debuggerd很容易获取native栈信息,可以当采样或者分析卡住或死锁问题。
debuggerd -b <pid>
该命令对java进程,是得不到java调用栈的,不过得到的是虚拟机执行java指令时的虚拟机调用栈,debugger虚拟机必用
5.3 查看内核栈
比较难,大多系统没开放,不过调试版本可以。查看方法:
cat /proc/<pid>/task/<tid>/stack