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 的可能性.