2014年7月10日 星期四

如何在 Android 各 level ( 包含 user space 與 kernel space ) 使用dump call stack的方法

dump call stack


[文章重點]

了解 Android 各 level ( UI, framework 與 HAL) 與 kernel 間, 如何印出 call stack, 方便追 code 與 debug

[文章目錄]
  1. kernel call stack
  2. Android Java layer
  3. Android framework ( written by c++)
  4. Android HAL ( written by c )
  5. Call Stack 沒有出現 function name

kernel call stack

如果想知道call stack,也就是說, 我們想知道是誰call到func_foo(). 此時,我們可以利用 dump_stack(),放在你想dump back trace的地方就OK囉.
 
void func_foo(void){
 
  int a=3;
  ...
  
  dump_stack();

  ...

}

Java layer call stack
在Java檔案, 可以使用下述方法得到dump call stack


public void foo(boolean state, int flags) {
 ...
 Log.d(TAG,"xxxx", new Throwable());
 ...
}


C++ layer call stack

在C/C++ 檔案, Android 已經有寫了frameworks/native/libs/utils/CallStack.cpp 供我們使用


#include <utils/CallStack.h>
...
void foo(void) {
...
   android::CallStack stack;
   stack.update();
   stack.dump("XXX");

...
}


如果你所使用是Android 4.4 之後
請改用


#include <utils/CallStack.h>
...
void foo(void) {
...
   android::CallStack stack;
   stack.update( );
   stack.log("XXX");

...
}

在Android.mk 記得要加


LOCAL_SHARED_LIBRARIES += libutils


在 Android Pie 之後, CallStack 是屬於 libutilscallstack.so, 所以

在 Android.mk 要改成

LOCAL_SHARED_LIBRARIES += libutilscallstack

或是在 Android.db 中

 shared_libs: [
      ...
        "libutilscallstack",

    ],

C layer call stack

由於C去call C++需要做一些宣告, 所以將它獨立出來方便使用(dump_stack.cpp與 dump_stack.h)


dump_stack.h

#ifdef __cplusplus
extern "C" {
#endif

 void dump_stack_android(void);
 
#ifdef __cplusplus
}
#endif


dump_stack.cpp


#include "dump_stack.h"
#include <utils/CallStack.h>

using namespace android;
extern "C"{
 void dump_stack_android(void)
 {
CallStack stack;
stack.update();
stack.dump("XXX");
 }
}


如果你所使用是Android 4.4 之後
請改用


#include "dump_stack.h"
#include <utils/CallStack.h>

using namespace android;
extern "C"{
 void dump_stack_android(void)
 {
CallStack stack;
stack.update();
stack.log("XXX");
 }
}


同樣地, Android.mk也需要修改


LOCAL_SRC_FILES := \
        …... \
        dump_stack.cpp

LOCAL_SHARED_LIBRARIES += libutils

# for Android Pie
LOCAL_SHARED_LIBRARIES += libutilscallstack


接下來在C file中要使用時只要


extern void dump_stack_android();


void function_a()
{
 …
 dump_stack_android();
 …
}


[ Call Stack 沒有出現 function name]
有時我們會發現在C++ 或 C 語言中使用 CallStack , 在 call dump 中並沒有出現 function name


D/XXX (  147): #00  pc 00001b90  /system/lib/hw/audio.primary.mrvl.so (dump_stack_android+19)
D/XXX (  147): #01  pc 00004b56  /system/lib/hw/audio.primary.mrvl.so
D/XXX (  147): #02  pc 0001f828  /system/lib/libaudioflinger.so
D/XXX (  147): #03  pc 00019138  /system/lib/libaudioflinger.so
D/XXX (  147): #04  pc 00023bb6  /system/lib/libaudioflinger.so
D/XXX (  147): #05  pc 0000e9fe  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+213)
D/XXX (  147): #06  pc 0000e530  /system/lib/libutils.so
D/XXX (  147): #07  pc 0000d208  /system/lib/libc.so (__thread_entry+72)
D/XXX (  147): #08  pc 0000d3a4  /system/lib/libc.so (pthread_create+240)


我們追一下 CallStack 是如何被實作
先回顧一下 CallStack 是如何被使用 (以 Android 4.4 為例)
 CallStack stack;  
 stack.update();  
 stack.log();  

先看一下 update( ) function 的定義 ( it is under system/core/include/utils/CallStack.h)
   // Immediately collect the stack traces for the specified thread.  
   void update(int32_t ignoreDepth=1, int32_t maxDepth=MAX_DEPTH, pid_t tid=CURRENT_THREAD);  
所以透過 update( ) function, 我們可以設定想看哪一個 thread 並 dump 出多少層的 call stack, 如果都沒寫, 就是以當前的 thread 去做 call stack dump, update( ) function 會將實際可以 dump 多少的 frame 給抓出來, 其中 frame 的數量記錄在 mCount 變數, 各 frame 的資訊則記錄在 mStack[ ] 裡面, 接下來再透過 log( ) function 把 call stack 裡的 program counter 所記載的記憶體位址去把相對應的 function name 給解析出來.

 log( )  
 |--> print( )  
    |--> get_backtrace_symbols( )  

看一下 get_backtrace_symbols( ) 在做些什麼
void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
    backtrace_symbol_t* backtrace_symbols) {

   ... 
for (size_t i = 0; i < frames; i++) {
       ...
           Dl_info info;
           if (dladdr((const void*)frame->absolute_pc, &info) && info.dli_sname) {
            symbol->relative_symbol_addr = (uintptr_t)info.dli_saddr
                    - (uintptr_t)info.dli_fbase;
            symbol->symbol_name = strdup(info.dli_sname);
            symbol->demangled_name =
                       demangle_symbol_name(symbol->symbol_name);
           }
      ...
}
release_my_map_info_list(milist);
}


這是因為它是使用 dladdr() 去讀取該share lib的 dynamic symbol 而獲取 function name

但是如果該 function 是宣告成 static, 該 function name 就不會出現在 dynamic symbol 裡 (你可以使用 arm-linux-androideabi-nm -D xxxx.so | grep the_function_name , 如果沒有出現, 就表示該 funciton name 並不在 dynamic symbol 裡),  遇到這情況就只好使用 add2line 指令去讀 out folder 下的 symbol 了, 各位可以參考我另一篇文章 http://janbarry0914.blogspot.tw/2011/07/android-crash-tombstone.html . 感謝.

2014年5月6日 星期二

Machine driver route map 的客製化 -- 以 SND_SOC_DAPM_SPK 為例

[文章重點] 透過此文章會了解到, Machine driver 中的 SND_SOC_DAPM_SPK 是如何與 DAPM 做結合並提供客製化的功能

[文章目錄]
  1. route map 在 machine driver 中的功能
  2. SND_SOC_DAPM_SPK 如何與 DAPM 做結合
[ route map 在 machine driver 中的功能]
  除了 codec driver 中會有 snd_soc_dapm_route 的宣告外, 在 machine driver 也有同樣的宣告.

1:  static const struct snd_soc_dapm_widget rt5639_dapm_widgets[] = {  
2:       SND_SOC_DAPM_MIC("Mic Jack", NULL),  
3:       SND_SOC_DAPM_MIC("Headset Jack", NULL),       
4:       SND_SOC_DAPM_SPK("Ext Spk", NULL),  
5:       SND_SOC_DAPM_HP("Headphone Jack", NULL),  
6:  };  
7:  static const struct snd_soc_dapm_route audio_map[]={  
8:       /* Mic Jack --> MIC_IN*/  
9:        {"micbias1", NULL, "Mic Jack"},  
10:       {"MIC1", NULL, "micbias1"},  
11:       // HP MIC  
12:       {"micbias1", NULL, "Headset Jack"},  
13:       {"MIC3", NULL, "micbias1"},  
14:       {"Ext Spk", NULL, "SPOLP"},  
15:       {"Ext Spk", NULL, "SPOLN"},  
16:       {"Ext Spk", NULL, "SPORP"},  
17:       {"Ext Spk", NULL, "SPORN"},  
18:       {"Headphone Jack", NULL, "HPOL"},  
19:       {"Headphone Jack", NULL, "HPOR"},  
20:  } ;  
21:  static const struct snd_kcontrol_new rk_controls[] = {  
22:       SOC_DAPM_PIN_SWITCH("Mic Jack"),  
23:       SOC_DAPM_PIN_SWITCH("Headset Jack"),  
24:       SOC_DAPM_PIN_SWITCH("Ext Spk"),  
25:       SOC_DAPM_PIN_SWITCH("Headphone Jack"),  
26:  };  

這是因為Machine driver 也要負責描述 machine 實際的配置狀況, 例如: speaker 是接到 audio codec 的 SPOL/SPOR 還是 LOUTL/LOUTR ... 等等. 藉由 snd_soc_dapm_route 的宣告就可以達到客製化的彈性.  先複習一下 route map 的宣告方式,
{ sink, control, source},

1:  {"Ext Spk", NULL, "SPOLP"},  
2:  {"Ext Spk", NULL, "SPOLN"},  
3:  {"Ext Spk", NULL, "SPORP"},  
4:  {"Ext Spk", NULL, "SPORN"},  

因為 speaker 是當 sink 的角色, 所以要放在 sink 的位置上, 又由於 speaker 是直接接到 SPOL/SPOR , 所以 control 是 "NULL" . 那我們再看一下 mic 的宣告

1:  {"micbias1", NULL, "Mic Jack"},  
2:  {"MIC1", NULL, "micbias1"},       
3:       // HP MIC  
4:  {"micbias1", NULL, "Headset Jack"},  
5:  {"MIC3", NULL, "micbias1"},  

同理, 因為 mic 是當 source 的角色, 所以要放在 source 位置上.

[ SND_SOC_DAPM_SPK 如何與 DAPM 做結合]
先看一下 widget 的宣告方式
1:  static const struct snd_soc_dapm_widget rt3261_dapm_widgets[] = {  
2:       SND_SOC_DAPM_MIC("Mic Jack", NULL),  
3:       SND_SOC_DAPM_MIC("Headset Jack", NULL),       
4:       SND_SOC_DAPM_SPK("Ext Spk", NULL),  
5:       SND_SOC_DAPM_HP("Headphone Jack", NULL),  
6:  };  

1:  #define SND_SOC_DAPM_SPK(wname, wevent) \  
2:  {     .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \  
3:       .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \  
4:       .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}  

這裡最重要的就是註冊了event callback function. 而這 event callback function 最常做的事就是當speaker 有外掛 amp 就可以在這做 amp enable/disable 的工作. 不過在這例子是 NULL, 因為 audio codec 本身就內建 2 w class D amp, ( codec driver 會將 這 class D amp 用 SND_SOC_DAPM_PGA_S 去註冊一個 widget, 並加到 route map 裡. 當然啦, 這要設計在 speaker 會通過的路徑上) 所以不需要額外的 callback function 去做這件事.

       我們希望在 user space 可以控制 speaker 的行為, 所以要利用 kcontrol 來達到此目的.
1:  static const struct snd_kcontrol_new rk_controls[] = {  
2:       SOC_DAPM_PIN_SWITCH("Mic Jack"),  
3:       SOC_DAPM_PIN_SWITCH("Headset Jack"),  
4:       SOC_DAPM_PIN_SWITCH("Ext Spk"),  
5:       SOC_DAPM_PIN_SWITCH("Headphone Jack"),  
6:  };  

所以, 如果要只從 speaker 發出聲音
1:  alsa_amixer cset iface=MIXER,name='Ext Spk Switch' 1  

如果要同時從 speaker 與 headset 同時發出聲音
1:  alsa_amixer cset iface=MIXER,name='Ext Spk Switch' 1  
2:  alsa_amixer cset iface=MIXER,name='Headphone Jack Switch' 1  

那在 user space 做了這樣動作有什麼影響呢 ?  先看一下, SOC_DAPM_PIN_SWITCH 的定義
1:  #define SOC_DAPM_PIN_SWITCH(xname) \  
2:  {    .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \  
3:       .info = snd_soc_dapm_info_pin_switch, \  
4:       .get = snd_soc_dapm_get_pin_switch, \  
5:       .put = snd_soc_dapm_put_pin_switch, \  
6:       .private_value = (unsigned long)xname }  

1:  snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, ... )  
2:     const char *pin = (const char *)kcontrol->private_value;  
3:  --> snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, const char *pin )  
4:     --> snd_soc_dapm_set_pin(dapm, pin, 1);  
5:        struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true);  
6:        w->connected = status;  

當我們透過 alsa_amixer 向 SOC_DAPM_PIN_SWITCH 這類 kcontrol 去做設定時, 它會去搜尋跟自己同名的 widget, 並對該 widget 的 connected 屬性做設定. 換句話說, 我們對SOC_DAPM_PIN_SWITCH("Ext Spk") 這 kcontrol 做設定時, 就是相當於對SND_SOC_DAPM_SPK("Ext Spk", NULL), 這 widget 的 connected 做設定

接下來進入主題, 它是如何與 DAPM 做結合 ? 首先,在 widget 註冊時, 其 connected 屬性都被設成 1.
1:  int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,  
2:       const struct snd_soc_dapm_widget *widget)  
3:  {  
4:       ...  
5:       /* machine layer set ups unconnected pins and insertions */  
6:       w->connected = 1;  
7:       return 0;  
8:  }  
9:  EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);  
從註解得知, 如果要修改該 widget 的 connected 屬性可以從 machine layer 去著手. 在這兒提醒一下, 這邊的 connected 屬性不要跟 path 的 connect 屬性搞混囉. 我猜 widget 的 connected 屬性是為了專門 提供 SND_SOC_DAPM_SPK 這類 widget ( 直接連接到 audio codec 所以無法從 path 去控制) 所提供的設計.

又由於無法從 path 上去控制, 所以在設計 route map 時, 針對這些 input end point ( snd_soc_dapm_input ) 與 output end point ( snd_soc_dapm_output ) 用 ext 屬性來標記這些 end point 外面是否還有外接 widget.
1:  static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,  
2:                       const struct snd_soc_dapm_route *route)  
3:  {  
4:    ...  
5:    /* check for external widgets */  
6:       if (wsink->id == snd_soc_dapm_input) {  
7:            if (wsource->id == snd_soc_dapm_micbias ||  
8:                 wsource->id == snd_soc_dapm_mic ||  
9:                 wsource->id == snd_soc_dapm_line ||  
10:                 wsource->id == snd_soc_dapm_output)  
11:                 wsink->ext = 1;  
12:       }  
13:       if (wsource->id == snd_soc_dapm_output) {  
14:            if (wsink->id == snd_soc_dapm_spk ||  
15:                 wsink->id == snd_soc_dapm_hp ||  
16:                 wsink->id == snd_soc_dapm_line ||  
17:                 wsink->id == snd_soc_dapm_input)  
18:                 wsource->ext = 1;  
19:       }  
20:      ...  
21:      switch (wsink->id) {  
22:       ...  
23:        case snd_soc_dapm_hp:  
24:        case snd_soc_dapm_mic:  
25:        case snd_soc_dapm_line:  
26:        case snd_soc_dapm_spk:  
27:            list_add(&path->list, &dapm->card->paths);  
28:            list_add(&path->list_sink, &wsink->sources);  
29:            list_add(&path->list_source, &wsource->sinks);  
30:            path->connect = 0;  
31:            return 0;  
32:       }  
33:      ...  
34:  }  

我們以 speaker 作為例子, 當 wsource->id == snd_soc_dapm_output 的情況下, wsink->id == snd_soc_dapm_spk , 那 wsource->ext = 1;

再回顧一下 audio codec driver 對於 SPOL/SPOR 的宣告
widget 的宣告
1:  static const struct snd_soc_dapm_widget rt5639_dapm_widgets[] = {  
2:      ...       
3:      /* Output Lines */  
4:       SND_SOC_DAPM_OUTPUT("SPOLP"),  
5:       SND_SOC_DAPM_OUTPUT("SPOLN"),  
6:       SND_SOC_DAPM_OUTPUT("SPORP"),  
7:       SND_SOC_DAPM_OUTPUT("SPORN"),  
8:      ...  
9:  }  

SND_SOC_DAPM_OUTP 的定義
1:  #define SND_SOC_DAPM_OUTPUT(wname) \  
2:  {     .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \  
3:       .num_kcontrols = 0, .reg = SND_SOC_NOPM }  

route map 的宣告
1:  {"Ext Spk", NULL, "SPOLP"},  
2:  {"Ext Spk", NULL, "SPOLN"},  
3:  {"Ext Spk", NULL, "SPORP"},  
4:  {"Ext Spk", NULL, "SPORN"},  

所以, 當 Machine driver 在做 rout map 宣告時 ( snd_soc_dapm_add_route( ) ), 會對 wsource -> ext = 1  ( 例如:{"Ext Spk", NULL, "SPOLP"},   那 "SPOLP" widget 的 ext 屬性會被設定成1)

在 is_connected_input_ep( ) 與 is_connected_output_ep( ) 中, 當遇到 input end pointer 與 output end pointer 就會去檢查 ext 屬性來決定是否繼續走下去, 就以 is_connected_output_ep( ) 來說, ( Line 6 ~ Line 7)如果走訪到 snd_soc_dapm_output ( 也就是 output end pointer) 它沒有 external widget ( widget->ext == 0 ) 也不是處於 suspend mode 就 return 1, 表示有連到 output end pointer. 如果該 output end pointer 有 external widget, 那它會繼續往下走 ( Line 13 ~ Line 18) 遞迴地呼叫 is_connected_output_ep( ), 所以, 輪到檢查 SND_SOC_DAPM_SPK , 如果 widget->connected 是 0, 那 return  0 ( con 預設值是 0 ), 表示沒有連到 output end pointer. 反之, 如果 widget->connected 是 1又不是處於suspend mode 就 return 1, 表示有連到 output end pointer.

1:  static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)  
2:  {  
3:      ...  
4:       if (widget->connected) {  
5:            /* connected pin ? */  
6:            if (widget->id == snd_soc_dapm_output && !widget->ext)  
7:                 return snd_soc_dapm_suspend_check(widget);  
8:            /* connected jack or spk ? */  
9:            if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||  
10:              (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))  
11:                 return snd_soc_dapm_suspend_check(widget);  
12:       }  
13:       list_for_each_entry(path, &widget->sinks, list_source) {  
14:            if (path->walked)  
15:                 continue;  
16:            if (path->sink && path->connect) {  
17:                 path->walked = 1;  
18:                 con += is_connected_output_ep(path->sink);  
19:            }  
20:       }  
21:       return con;  
22:  }  

[結論]
DAPM 透過 widget->connected 來表示 SND_SOC_DAPM_SPK 這類 widget 是否有要發出聲音( 透過 kcontrol - SOC_DAPM_PIN_SWITCH 來搭配), 此外, 用 widget->ext 屬性讓 output end pointer 與 input end pointer 有外接 external widget 的可能性.

2014年4月28日 星期一

Soc的board file中多組的DT_MACHINE_START與device tree之間的對應關係

[本文章重點] 您將會學到 kernel 在開機時如何藉由 device tree 在多組 board file 裡的DT_MACHINE_START 中找到正確的 machine description.

Ps. 本文章以 Marvell PXA1L88 的 source code 為例子

例如: (我以 Marvell PXA1L88 的 board file - mmpx-dt.c 作為例子)
1:  DT_MACHINE_START(PXA1088_DT, "PXA1088")  
2:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
3:       .map_io          = mmp_map_io,  
4:       .init_irq     = pxa988_dt_irq_init,  
5:       .init_time     = mmpx_timer_init,  
6:       .init_machine     = pxa988_dt_init_machine_1,  
7:       .dt_compat     = pxa1088_dt_board_compat,  
8:       .reserve     = pxa988_reserve,  
9:       .restart     = mmp_arch_restart,  
10:  MACHINE_END  
11:  DT_MACHINE_START(PXA1L88_DT, "PXA1L88")  
12:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
13:       .map_io          = mmp_map_io,  
14:       .init_irq     = pxa988_dt_irq_init,  
15:       .init_time     = mmpx_timer_init,  
16:       .init_machine     = pxa988_dt_init_machine_2,  
17:       .dt_compat     = pxa1L88_dt_board_compat,  
18:       .reserve     = pxa988_reserve,  
19:       .restart     = mmp_arch_restart,  
20:  MACHINE_END  
21:  DT_MACHINE_START(PXA1U88_DT, "PXA1U88")  
22:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
23:       .map_io          = mmp_map_io,  
24:       .init_irq     = pxa988_dt_irq_init,  
25:       .init_time     = mmpx_timer_init,  
26:       .init_machine     = pxa988_dt_init_machine_3,  
27:       .dt_compat     = pxa1U88_dt_board_compat,  
28:       .reserve     = pxa988_reserve,  
29:       .restart     = mmp_arch_restart,  
30:  MACHINE_END  

在這麼多組的 machine description 中 , 它是如何正確地選中DT_MACHINE_START(PXA1L88_DT, "PXA1L88") ?
此例一共有三組
DT_MACHINE_START(PXA1088_DT, "PXA1088")
DT_MACHINE_START(PXA1L88_DT, "PXA1L88")
DT_MACHINE_START(PXA1U88_DT, "PXA1U88")

[文章目錄]
  1. 什麼是 DT_MACHINE_START , MACHINE_END?
  2. kernel 如何在多組 machine description 中找到正確的匹配
[什麼是DT_MACHINE_START , MACHINE_END?]
   Device tree 的引進主要就是要解決 ARM Linux 中, arch/arm/plat-xxx和arch/arm/mach-xxx 中有太多不重要的 board level 的描述, 我們希望盡量在單一的 board file 中能夠支援更多 board level 的描述,在透過一些機制去找到正確的 machine description.
   那支援 device tree 的 machine description 是如何宣告?
(kernel/arch/arm/include/asm/mach/arch.h)
1:  #define DT_MACHINE_START(_name, _namestr)          \  
2:  static const struct machine_desc __mach_desc_##_name     \  
3:   __used                                   \  
4:   __attribute__((__section__(".arch.info.init"))) = {     \  
5:       .nr          = ~0,                    \  
6:       .name          = _namestr,  
7:  #endif  
從這巨集得知, 它將 struct machine_desc 的物件放在 .arch.info.init 這個 section 中, DT_MACHINE_START(PXA1L88_DT, "PXA1L88") 展開就會是

1:  [.arch.info.init]  
2:    struct machine_desc __mach_desc_PXA1L88_DT = {  
3:          .nr       = ~0,  
4:          .name   = "PXA1L88",  
5:          ...  
6:    }  

我們可以透過 System.map 去驗證
1:  c0a21598 T __arch_info_begin  
2:  c0a21598 t __mach_desc_PXA1U88_DT  
3:  c0a215e8 t __mach_desc_PXA1L88_DT  
4:  c0a21638 t __mach_desc_PXA1088_DT  
5:  c0a21688 T __arch_info_end  
所以從 System.map 得知, board file 裡的所有 DT_MACHINE_START( ) 都被 build 進去並放在arch.info.init 這 section 內
(補充: kernel/arch/arm/kernel/vmlinux.lds.S)
1:  .init.arch.info : {  
2:            __arch_info_begin = .;  
3:            *(.arch.info.init)  
4:            __arch_info_end = .;  
5:       }d  

[kernel 如何在多組 machine description 中找到正確的匹配]
   從上一段文章得知所有的 DT_MACHINE_START( )宣告的 machine description 都被 build 進去了, 那 kernel 如何做出正確的選擇呢?  答案就透過 "compatible" 這關鍵字.
 
    我們先談一下 device tree , 當我們在 build Android images 時就會選擇 TARGET_PRODUCT , 接下來就會知道
1:  KERNEL_DEFCONFIG := pxa988_defconfig  
2:  ###!!!! the order of the dtb file here need to align with the code in u-boot###  
3:  KERNEL_DTB_FILE := pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-l7-v10.dtb  
Linux kernel 會利用 dtc (device tree compiler) build 出 .dtb 檔 (device tree blob)
(kernel/arch/arm/boot/dts/Makefile)
1:  dtb-$(CONFIG_ARCH_MMP) += edenconcord.dtb edenfpga.dtb pxa988-dkb.dtb pxa1088dkb.dtb\  
2:                  pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-v10.dtb\  
3:                  pxa1U88-dkb.dtb pxa1986sdk.dtb pxa1L88-tablet-l7-v10.dtb  
雖然 kernel 會 build 出很多 .dtb 檔, 但是只有以下特定的 .dtb 檔才會被放進 images 裡
1:  KERNEL_DTB_FILE := pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-l7-v10.dtb  
Marvell的作法是將這些 .dtb 檔直接放在 u-boot.bin 的尾端, u-boot 會告訴 kernel 關於 Flattened Device Tree blob 的 address, kernel 再從這 address 將這些 device tree blob載到memory 特定的位址上.

    在 .dts 檔中, 我們會看到 compatible 這些關鍵字, 例如:
在 pxa1L88-dkb-v10.dts 中, compatible = "mrvl,pxa1L88-dkb-v10", "mrvl,pxa1L88";
在 pxa1L88-dkb-v20.dts 中, compatible = "mrvl,pxa1L88-dkb-v20", "mrvl,pxa1L88";
在 pxa1L88-tablet-l7-v10.dts 中, compatible = "mrvl,pxa1L88-tablet-l7-v10", "mrvl,pxa1L88";

   我們再回到 board file - mmpx-dt.c ,
1:  static const char *pxa1L88_dt_board_compat[] __initdata = {  
2:       "mrvl,pxa1L88-dkb-v10",  
3:       "mrvl,pxa1L88-dkb-v20",  
4:       "mrvl,pxa1L88-tablet-v10",  
5:       "mrvl,pxa1L88-tablet-l7-v10",  
6:       NULL,  
7:  };  
8:  DT_MACHINE_START(PXA1L88_DT, "PXA1L88")  
9:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
10:       .map_io          = mmp_map_io,  
11:       .init_irq     = pxa988_dt_irq_init,  
12:       .init_time     = mmpx_timer_init,  
13:       .init_machine     = pxa988_dt_init_machine_2,  
14:       .dt_compat     = pxa1L88_dt_board_compat,  
15:       .reserve     = pxa988_reserve,  
16:       .restart     = mmp_arch_restart,  
17:  MACHINE_END  

      我們可以看到 .dt_compat = pxa1L88_dt_board_compat, 而 pxa1L88_dt_board_compat[] 裡面的字串是不是跟剛才的那些 .dts 檔案的 compatible 裡的 value 值一樣 ( .dts 檔案裡面就是紀錄一堆 key = value 這樣的資料並排成樹狀結構). Linux kernel 在開機做初始化時, 會將 u-boot 告訴它的 device tree 與 kernel image 本身 build 進去的 machine description ( 就是 DT_MACHINE_START( ) 所宣告並放在 .arch.info.init section中) 去做字串比較, 進而找到相符的 machine description. 之後在Linux kernel 在做後續的初始化時,就會執行該 machine description 的 .init_machine 所指的function去展開 device tree. 所以, 此例就會去展開 DT_MACHINE_START(PXA1L88_DT, "PXA1L88").
1:  start_kernel( ) [kernel/init/main.c]  
2:    -> setup_arch( ) [kernel/arch/arm/kernel/setup.c]  
3:      -> setup_machine_fdt( ) [kernel/arch/arm/kernel/devtree.c]  
4:           devtree = phys_to_virt(dt_phys); /*這時候MMU已經起來了,所以要轉成 virtual mem addr 才可以存取的到*/  
5:           /* check device tree validity */  
6:           if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)  
7:              return NULL;  
8:            /* Search the mdescs for the 'best' compatible value match */  
9:            initial_boot_params = devtree;  
10:           dt_root = of_get_flat_dt_root(); /*指到 u-boot 傳給 kernel 的 device three*/  
11:           for_each_machine_desc(mdesc) {  /*歷遍 .arch.info.init 中的 struct machine_desc*/  
12:              score = of_flat_dt_match(dt_root, mdesc->dt_compat);  
13:              if (score > 0 && score < mdesc_score) {  
14:                  mdesc_best = mdesc; /*如果找到了, 就將 mdesc_best 指到該結構物件*/  
15:                  mdesc_score = score;  
16:              }  
17:            }  
18:            -> of_flat_dt_match( ) [kernel/drivers/of/fdt.c]  
19:              -> of_fdt_match( )  
20:                -> of_fdt_is_compatible( )  
21:                    cp = of_fdt_get_property(blob, node, "compatible", &cplen); /*取出 compatible 的 value 值*/  
22:                    ...  
23:                    while (cplen > 0) {  
24:                          score++;  
25:                          if (of_compat_cmp(cp, compat, strlen(compat)) == 0) /*透過 strncmp() 做字串的比較*/  
26:                               return score;  
27:                          ...  
28:                     }  
29:  
補充: 關於device tree 會用到的functions 大都可以在 kernel/drivers/of 目錄下找到, 而且這些functions 都是以 of_XXX( ) 命名. ( 例如: of_fdt_match( ), of_fdt_is_compatible( ) ...)

2014年4月14日 星期一

DAPM - snd_soc_dapm_add_route() 與 snd_soc_dapm_path 的建立

此系列文章主要是在閱讀 http://blog.csdn.net/azloong 之後,再針對我有興趣的部分,再做深入探討.

[本篇文章重點] 希望各位能透過本篇文章了解到path (struct snd_soc_dapm_path)的建立,以及它與dapm kcontrol之間的關係,並為未來dapm觸發的流程鋪路.

在snd_soc_dapm_add_route()中會一一將route map轉換成struct snd_soc_dapm_path並新增至snd_soc_card裡的paths串列(list)裡面. 當dapm觸發時,會去走遍所有widget,並透過paths串列找出同時有連到output end point與input end point的widget,並將該widget的電源打開. 換句話說,dapm希望達到在音訊通道上的所有widget電源是開著,其餘用不到的widget電源是關閉的. 在音訊通道上,最常看到的widget型態就是非switch與mixer莫屬了. 所以接下將利用mixer來說明path的建立過程.


1:  static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {  
2:  SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
3:  SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
4:  SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
5:  SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
6:  SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
7:  };   

1:  static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {  
2:  ...  
3:  SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,  
4:              wm8900_loutmix_controls,  
5:              ARRAY_SIZE(wm8900_loutmix_controls)),  
6:  ...  
7:  };  


經過一連串的宣告後會建立此關係,接下來是route map的宣告

1:  /* Target, Path, Source */  
2:  static const struct snd_soc_dapm_route wm8900_dapm_routes[] = {  
3:  ...  
4:  {"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},  
5:  {"Left Output Mixer", "AUX Bypass Switch", "AUX"},  
6:  {"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},  
7:  {"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},  
8:  {"Left Output Mixer", "DACL Switch", "DACL"},  
9:  ...  
10:  }  

我們可以看到routes順序是 { sink, control, source}, 所以,以這為例
{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
sink = "Left Output Mixer"
control = "LINPUT3 Bypass Switch"
source = "LINPUT3"
換成文字上的說明就是 audio stream 從 "LINPUT3"過來,經過 "LINPUT3 Bypass Switch", 接著流向 "Left Output Mixer"

1:  snd_soc_dapm_add_routes()  
2:   -->snd_soc_dapm_add_route() (添加一個snd_soc_dapm_mixer類型的route)  
3:      -->配置snd_soc_dapm_path記憶體,令path->source=source; path->sink=sink  
4:      -->如果sink是switch或mixer這類型的widget  
5:        -->dapm_connect_mixer(dapm, source, sink, path, control);  
6:          --> 檢查control是否也可以在sink->kcontol_news[i].name中找到  
7:             if (!strcmp(control_name, sink->kcontrol_news[i].name))  
8:          -->如果找到,則將path分別加到card->paths,sink與source widget串列中,
                    並將path的name設定成sink->kcontrol_news[i].name
9:                  list_add(&path->list, &dapm->card->paths);  
10:                 list_add(&path->list_sink, &sink->sources);  
11:                 list_add(&path->list_source, &source->sinks);  
12:                 path->name = sink->kcontrol_news[i].name;  
13:                 --> dapm_set_path_status(sink, path, i);  
14:                    -->取出 sink->kcontrol_news[i].private_value 的值,  
15:                       如果該值表示是unmute時(對於mixer來說,unmute就是代表connected),
16:                       則 path->connect = 1,反之亦然
17:      /*NOTE:為了與route map的解說有一貫性,我將souece code裡的dest換成sink;src換成了source.*/  













(為了方便說明與排版,將不會精準地畫上指標(pointer)真正指向的記憶體位置, 例如: prev指標是指向snd_soc_dapm_path->list->next開頭的記憶體位置上,而不是snd_soc_dapm_path->list->prev, 此外,像name指標是指到存到字池(String Literal Pool)裡的字串)上圖說明了snd_soc_dapm_path建立好之後都全都被加進了snd_soc_card裡的paths串列裡面.













上圖說明了sink widget也把snd_soc_dapm_path的list_sink加到自己的sources串列裡面.換句話說,widget (此時當sink角色時)會知道可以連到它的source path資訊.再回顧開頭所說的,當要判斷該widget是否有連到input end point時(is_connected_input_ep( )),那該widget的sources串列就扮演資訊來源的角色




同樣地,上圖說明了source widget也把snd_soc_dapm_path的list_source加到自己的sinks串列裡面.換句話說,widget (此時當source角色時)會知道它可以連到的sink path資訊.當要判斷該widget是否有連到output end point時(is_connected_output_ep( )),那該widget的sinks串列就扮演資訊來源的角色.

    至於snd_soc_dapm_path的long_name是如何產生的? 以及它與dapm kcontrol之間的關係是如何?這篇文章已有說明 http://blog.csdn.net/azloong/article/details/6334922. 到目前為止,都是在codec probe的時候所做的事,當audio codec probe時的audio codec register此時的值就直接影響了snd_soc_dapm_path ->connect的狀態,所以,大部分的audio codec driver都會maintain一個initial table,在執行audio codec probe時會將initial table裡的值先寫到audio codec register裡,再做kcontrol,widget與route map的註冊.之後就會依use case透過amixer去控制kcontrol而改變相對應的snd_soc_dapm_path ->connect的狀態,在http://blog.csdn.net/azloong/article/details/6428885中有說明.

    關於kcontrol, widget與route map的註冊,有些audio codec driver已經移到soc-core.c裡的snd_soc_instantiate_card( )或soc_probe_codec( )或其他相關的function去做了.
所以有些audio codec driver改成下述方式達成
1:  static struct snd_soc_codec_driver soc_codec_dev_wm8900 = {  
2:       .probe =     wm8900_probe,  
3:       .remove =     wm8900_remove,  
4:       .suspend =     wm8900_suspend,  
5:       .resume =     wm8900_resume,  
6:       .set_bias_level = wm8900_set_bias_level,  
7:       .volatile_register = wm8900_volatile_register,  
8:       .reg_cache_size = ARRAY_SIZE(wm8900_reg_defaults),  
9:       .reg_word_size = sizeof(u16),  
10:      .reg_cache_default = wm8900_reg_defaults,  
11:      .controls = wm8900_snd_controls,  
12:      .num_controls = ARRAY_SIZE(wm8900_snd_controls),  
13:      .dapm_widgets = wm8900_dapm_widgets,  
14:      .num_dapm_widgets = ARRAY_SIZE(wm8900_dapm_widgets),  
15:      .dapm_routes = wm8900_dapm_routes,  
16:      .num_dapm_routes = ARRAY_SIZE(wm8900_dapm_routes),  
17:  };  

 但是關於snd_soc_dapm_path的long_name的產生還是由snd_soc_dapm_new_widget( )來負責,在我的code base裡,它的call stack是snd_soc_register_card( )->snd_soc_instantiate_card( )-> soc_post_component_init( ) ->snd_soc_dapm_new_widget( ),它會走訪所有widget,如果是mixer或switch類型的widget就會call dapm_new_mixer( ),而dapm_new_mixer( )有兩個重要的工作,就是為連到該widget的所有path賦予long_name與建立kcontrol.

1:  snd_soc_dapm_new_widgets()  
2:       它會走訪所有widget,如果是mixer或switch類型的widget就會call   
3:       dapm_new_mixer( ) , 如果是其他型態,會call相對應的function  
4:    -->dapm_new_mixer( )  
5:       1.每個widget都有sources串列記錄著連到它的所有path的資訊,而每個  
6:        path->name都是分別指到相對應的control的name  
7:        (回顧一下: path->name = sink->kcontrol_news[i].name; )  
8:        所以,在這裡再把這關係找出來  
9:        snd_soc_dapm_mixer : path->long_name=sink->name + sink->kcontrol_news[i].name  
10:       snd_soc_dapm_mixer_named_ctl: path->long_name= sink->kcontrol_news[i].name  
11:      2. path->kcontrol = snd_soc_cnew(&sink->kcontrol_news[i],  
12:                                   sink, path->long_name, prefix);  
13:        為該path建立一個snd_kcontrol物件,而這物件保有了sink->kcontrol_news[i]的資訊. 再回顧一下,kcontrol的宣告  
14:        SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
15:        這暗示了該path將會控制到相對應的kcontrol的register的值 .  
16:      3. snd_ctl_add(card, path->kcontrol);  
17:        把path->kcontrol加到card (snd_card)的controls串列裡面  

經過上述步驟之後,我們就可以透過alsa_amixer來切換path. 先透過alsa_amixer controls列出所有的kcontrols
 ~ # alsa_amixer controls  
 ......省略......  
 numid=57,iface=MIXER,name='Left Output Mixer AUX Bypass Switch'  
 numid=60,iface=MIXER,name='Left Output Mixer DACL Switch'  
 numid=56,iface=MIXER,name='Left Output Mixer LINPUT3 Bypass Switch'  
 numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch'  
 numid=59,iface=MIXER,name='Left Output Mixer Right Input Mixer Switch'  

再透過alsa_amixer cset去切換path的狀態
 alsa_amixer cset numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch' 1   

接下來,我們看一下alsa_amix如何改變kcontrol相對應的register
1:  alsa_amixer - @user space   
2:    (alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1)   
3:    |-> snd_ctl_ioctl( ) cmd ==SNDRV_IOCTL_ELEM_WRITE   
4:      |-> snd_ctl_elem_write_user( )   
5:        |-> snd_ctl_elem_write( )   
6:          |-> snd_ctl_find_id( ) 透過 list_for_each_entry(kctl, &card->controls, list)  
7:            遍歷kcontrol,找到相對應的kcontrol (其中包含透過kctl->id.name的字串比較與numid的比較)    
8:            |-> kctl->put( ) 呼叫當初kcontrol宣告時,所註冊的put( )函式   
9:              |-> snd_soc_dapm_put_volsw( ) 根據user所設定的值去判斷相對   
10:                應的register是否需要 更新(透過nsd_soc_test_bits( )去檢查) 與   
11:                connect狀態, 如果需要更新   
12:               |-> dapm_mixer_update_power(widget, kcontrol, connect)   
13:                 |-> 遍歷所有path, 如果該path->kcontrol與user修改的kcontrol  
14:                   一致, 則 path->connect = connect;   
15:                   |-> dapm_power_widgets( )    
16:                     |-> dapm_generic_check_power( ) 找出是否有同時連接到  
17:                       output end point與input end point的widget, 如果有,則打  
18:                       開電源. 不過,如果現在user沒有在播音樂, audio interface   
19:                       active為 0,則不會有打開電源的情況發生, 這我們之後再討論.  

再回顧一下,當初dapm kcontrol是如何註冊的,
1:  static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {  
2:  SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
3:  SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
4:  SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
5:  SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
6:  SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
7:  };  
所以當初在註冊時,就把相對應的register給帶進去了

SOC_DAPM_SINGLE在soc-dapm.h中的定義, 我們可以看到 .put = snd_soc_dapm_put_volsw
1:  /* dapm kcontrol types */  
2:  #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \  
3:  {     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \  
4:       .info = snd_soc_info_volsw, \  
5:       .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \  
6:       .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert)  
7:   }  

[結論]從這篇文章,我們可以知道path是如何被建立, path與kcontrol之間的關係, user如何透過alsa_amixer控制dapm kcontrol並更新kcontrol對應的register,以及如何影響path的connect狀態.

2011年10月14日 星期五

如何寫Android.mk,使得可以copy 某目錄下所有檔案到image裡

我先說明一下情境
Vender/a_folder/Android.mk
Vender/a_folder/files/1.dat
Vender/a_folder/files/2.dat
Vender/a_folder/files/99.dat
Vender/a_folder/files/a.ini
Vender/a_folder/files/b.ini
Vender/a_folder/files/z.ini

我們想把Vender/a_folder/files/目錄下的所有檔案copy out/target/product/target_product/system/data/example/目錄下該如血Android.mk?
答案是:
PRODUCT_COPY_FILES += \
        $(call find-copy-subdir-files,*,$(LOCAL_PATH)/files,system/data/example)


find-copy-subdir-files function是定義在 build/core/product_config.mk
###########################################################
## List all of the files in a subdirectory in a format
## suitable for PRODUCT_COPY_FILES and
## PRODUCT_SDK_ADDON_COPY_FILES
##
## $(1): Glob to match file name
## $(2): Source directory
## $(3): Target base directory
###########################################################

define find-copy-subdir-files
$(shell find $(2) -name "$(1)" | $(SED_EXTENDED) "s:($(2)/?(.*)):\\1\\:$(3)/\\2:" | sed "s://:/:g")
endef

2011年7月11日 星期一

當Android crash 該如何有效利用tombstone

當Android發生crash時會產生一個類似core dump的檔案在 /data/tombstones/tombstone_XX where XX is a number increased by one with each crash. 我們要如何使用該檔案呢? 
tombstones_XX 檔案內容如下, 我們可以利用addr2line將pc所指的位置的function name給找出來
pid: 153, tid: 161  >>> system_server <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 43bf3000
 r0 003dc4a0  r1 43bf3000  r2 00000400  r3 00000000
 r4 002cbc98  r5 002cbc98  r6 00000000  r7 00000000
 r8 00000001  r9 00000140  10 00000019  fp 00301e04
 ip 8090a1e0  sp 43f1eb68  lr 80c92cbb  pc afd0cdfc  cpsr 20000010
 d0  003b3a700033c9f8  d1  00301280002eb23b
 d2  000000000000003b  d3  0000000000000000
 d4  43bbc00043bb8000  d5  43d4800043bb8000
 d6  3f80000000000000  d7  000001e03f000000
 d8  0000000000000000  d9  0000000000000000
 d10 0000000000000000  d11 0000000000000000
 d12 0000000000000000  d13 0000000000000000
 d14 0000000000000000  d15 0000000000000000
 d16 3effff003effff00  d17 3f80000041808889
 d18 3f80000041c4cccd  d19 0701070100700798
 d20 0000000000000c27  d21 0000043f00890000
 d22 0000000000000000  d23 0000000000000008
 d24 3fc74721cad6b0ed  d25 3fc39a09d078c69f
 d26 0000000000000000  d27 0000000000000000
 d28 0000000000000000  d29 0000000000000000
 d30 0000000000000000  d31 0000000000000000
 scr 20000010

         #00  pc 0000cdfc  /system/lib/libc.so
         #01  pc 00092cb8  /system/lib/egl/libGLESv2_adreno200.so
         #02  pc 00093814  /system/lib/egl/libGLESv2_adreno200.so
         #03  pc 00093890  /system/lib/egl/libGLESv2_adreno200.so
         #04  pc 000938bc  /system/lib/egl/libGLESv2_adreno200.so
         #05  pc 00095cce  /system/lib/egl/libGLESv2_adreno200.so
         #06  pc 0006526a  /system/lib/egl/libGLESv2_adreno200.so
         #07  pc 000655cc  /system/lib/egl/libGLESv2_adreno200.so
         #08  pc 000188c4  /system/lib/egl/libGLESv1_CM_adreno200.so
         #09  pc 000265ca  /system/lib/libsurfaceflinger.so
         #10  pc 0001b04c  /system/lib/libsurfaceflinger.so
         #11  pc 0001bda8  /system/lib/libsurfaceflinger.so
         #12  pc 0001bf52  /system/lib/libsurfaceflinger.so
         #13  pc 00020f3c  /system/lib/libsurfaceflinger.so
         #14  pc 00023df6  /system/lib/libsurfaceflinger.so
         #15  pc 000259de  /system/lib/libsurfaceflinger.so
         #16  pc 0001c52c  /system/lib/libutils.so
         #17  pc 0001ca8a  /system/lib/libutils.so
         #18  pc 00011bc4  /system/lib/libc.so
         #19  pc 00011790  /system/lib/libc.so
1. Android 產生出來的還沒進行strip的執行檔或shared libraries 是放在$android_root/out/target/product/YOUR_PRODUCT_NAME/symbols/system/bin 與 $android_root/out/target/product/YOUR_PRODUCT_NAME/symbols/system/lib目錄下
2. Android所使用的toolchain是放在$android_root/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/ 下面
3. 假設我們要看 #09  pc 000265ca  /system/lib/libsurfaceflinger.so 是 call 到哪一個function
 (假設tombstone_XX 是在 $android_root 目錄下)
    $ ./prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-addr2line -f -e ./out/target/product/YOUR_PRODUCT_NAME/symbols/system/lib/libsurfaceflinger.so  0x000265ca  
  它會show出_ZN7android14TextureManager12initEglImageEPNS_5ImageEPvRKNS_2spINS_13GraphicBufferEEE
  $android_root/frameworks/base/services/surfaceflinger/TextureManager.cpp:164 這樣的訊息