1. 為什么要打印函數調用堆棧?
打印調用堆棧可以直接把問題發生時的函數調用關系打出來,非常有利於理解函數調用關系。比如函數A可能被B/C/D調用,如果只看代碼,B/C/D誰調用A都有可能,如果打印出調用堆棧,直接就把誰調的打出來了。
不僅如此,打印函數調用堆棧還有另一個好處。在Android代碼里,函數命名很多雷同的,虛函數調用,幾個類里的函數名相同等,即使用source insight工具看也未必容易看清函數調用關系。如果用了堆棧打印,很容易看到函數調用邏輯。
那么一個問題來了,Android/kernel本身在發生問題(kernel panic, tombstone, …)時,都可以打出詳細的堆棧信息,這里干嘛還要費勁研究打堆棧?答案是發生問題時的堆棧的確很詳細,但這里研究的是不影響(准確說是基本不影響)系統運行的境況下,打印出某個情形下的堆棧信息,這個對源代碼邏輯研究很有幫助。或者是說,在未發生kernel panic, tombstone的時候,我們需要打印出堆棧信息。
2. Linux Kernel
Kernel里最簡單,直接有幾現成的函數可以使用:
dump_stack() 這個函數打出當前堆棧和函數調用backtrace后接着運行
WARN_ON(x) 這個函數跟dump_stack很像,它有個條件,如果條件滿足了就把stack打出來。
打印出來的結果都在kernel log里,一般dmesg命令就可以看到了
3. Native C++
Android在新版(至少5.0, 6.0)里加入了CallStack類,這個類可以打出當前的backtrace。用法很簡單:
前面確保包含頭文件#include <utils/CallStack.h>
Android.mk的庫依賴列表(LOCAL_SHARED_LIBRARIES)里包含libutilscallstack,一般都已經包含了。
然后在要打印堆棧處加入android::CallStack cs(“My CallStack Debug”);
“My CallStack Debug”是在logcat輸出的TAG,這里可以自己定義。如果上下文已經在android namespace里,”android::”前綴就不必加了。
Native C++的輸出log可以在logcat里看到。
注意,在網上的一些文檔里說要這么用:
CallStack stack;
stack.update();
stack.dump();
這樣做已經不行了,在新版Android里編譯不過。
4. Native C
Android對C的堆棧打印支持不太好。過去網上的文章一般是推薦libcorkscrew.so,並加入大段代碼來unwind_backtrace。新版Android上libcorkscrew已經被拿掉了,網上的加載libcorkscrew庫的方法自然就不能用了。
一個簡單方法是用C語言調C++的函數,對,就是extern “C”。
先在項目里加入一個c++文件,比如callstack.cpp,里面是:
#include <utils/CallStack.h> extern "C" void dumping_callstack(void); void dumping_callstack(void) { android::CallStack cs("My CallStack Debug"); }
在項目里再加入一個c++的頭文件,比如callstack.h,里面是:
void dumping_callstack(void);
在Android.mk里源文件列表LOCAL_SRC_FILES里加入callstack.cpp,並確保libutilscallstack在依賴列表里。
在native C里include callstack.h后直接調用dumping_callstack()就可以了。
這個log也可以在logcat里看到。
For Celadon:
callstack.cpp:
#include "callstack.h" #include <utils/CallStack.h> extern "C" { void dump_stack02(void) { //android::CallStack stack; //stack.update(); //stack.dump("INTEL-MESA"); android::CallStack cs("INTEL-MESA"); cs.update(); cs.log("INTEL-MESA", ANDROID_LOG_ERROR, ""); } }
callstack.h:
#ifndef _CALLBACK_H #define _CALLBACK_H #ifdef __cplusplus extern "C" { #endif void dump_stack02(void); #ifdef __cplusplus } #endif #endif
in Android.mk, add libutilscallstack in LOCAL_SHARED_LIBRARIES
LOCAL_SHARED_LIBRARIES := \ + libutilscallstack \
in Makefile.sources, add the callstack.cpp and callstack.h like below
@@ -2,4 +2,6 @@ C_SOURCES := \ virgl_drm_public.h \ virgl_drm_winsys.c \ virgl_drm_winsys.h \ - virtgpu_drm.h + virtgpu_drm.h \ + callstack.cpp \ + callstack.h
If the project uses Android.bp, need to add libutilscallstack in export_header_lib_headers or cc_library_shared as below:
export_header_lib_headers: [ @@ -67,11 +69,14 @@ cc_defaults { "libnativewindow", "libsync", "liblog", + "libutilscallstack", ], @@ -70,6 +71,7 @@ cc_library_shared { "liblog", "libsync", "libutils", + "libutilscallstack", ],
and "callstack.cpp" into src files list like below:
@@ -25,6 +25,7 @@ cc_defaults { "vgem.c", "virtio_gpu.c", "i915_private.c", + "callstack.cpp", ],
if still get error: "error: undefined symbol: android::CallStack::~CallStack()", 需要去check folder下面是不是還有其他Android.bp,子Android.bp沒有添加libutilscallstack庫的話會導致編譯的時候這個錯,意思是找不到實現。
in the file you want to add callstack:
調用 dump_stack02();
還需要
$ adb root
$ adb remount
$ adb push out/target/product/caas/vendor/lib64/egl/libGLES_mesa.so /vendor/lib64/egl/ $ adb push out/target/product/caas/vendor/lib/egl/libGLES_mesa.so /vendor/lib/egl/ $ adb push out/target/product/caas/vendor/lib64/dri/i965_dri.so /vendor/lib64/dri/ $ adb push out/target/product/caas/vendor/lib/dri/i965_dri.so /vendor/lib/dri/
然后
$ adb shell sync
最后把QEMU停掉,再啟動。
5. Java
Java最簡單,它的backtrace最詳細,連文件名和行號都打出來了:
Exception e = new Exception("haha");
e.printStackTrace();
log在logcat里看以看到。
大部分內容轉自: http://blog.sina.com.cn/s/blog_7213e0310102wtge.html