2013/11/14

使用 GDB 以樹狀方式將函式流程印出





若在 Linux Kernel 內,想追蹤每個函式的呼叫流程,第一個會想到的工具應該是 ftrace;那針對 user space 的程式是否有類似的東西?用 gdb,再加上一些簡單 script 就可以完成。

以 xdotool 為例,舉例來說,xdotool getmouselocation 將會得當前鼠標的位置,正常狀況下輸出結果如下:

$ xdotool getmouselocation
findclient: 65011716
findclient: 65011716
x:2769 y:656 screen:0 window:65011716

若搭配本文將介紹的方法,輸出結果如下:

(gdb) r
Starting program: /usr/bin/xdotool getmouselocation
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
| main at xdotool.c:287
| _start at ??:0
  | __libc_start_main at ??:0
    | xdotool_main at xdotool.c:290
    | xdotool_main at xdotool.c:316
      | args_main at xdotool.c:456
      | args_main at xdotool.c:493
        | xdo_new at xdo.c:89
        | xdo_new_with_opened_display at xdo.c:106
        | xdo_new_with_opened_display at xdo.c:131
          | xdo_enable_feature at xdo.c:2053
        | xdo_new_with_opened_display at xdo.c:140
          | _xdo_populate_charcode_map at xdo.c:1360
            | _keysym_to_char at xdo.c:1390
      | args_main at xdotool.c:509
        | context_execute at xdotool.c:519
        | context_execute at xdotool.c:536
          | cmd_getmouselocation at cmd_getmouselocation.c:3
          | cmd_getmouselocation at cmd_getmouselocation.c:38
            | consume_args at xdotool.c:46
          | cmd_getmouselocation at cmd_getmouselocation.c:40
            | xdo_mouselocation2 at xdo.c:858
            | xdo_mouselocation2 at xdo.c:886
              | xdo_window_find_client at xdo.c:1222
                | xdo_getwinprop at xdo.c:1541
            | xdo_mouselocation2 at xdo.c:889
              | xdo_window_find_client at xdo.c:1222
                | xdo_getwinprop at xdo.c:1541
              | xdo_window_find_client at xdo.c:1241
                | xdo_window_find_client at xdo.c:1222
                  | xdo_getwinprop at xdo.c:1541
                | xdo_window_find_client at xdo.c:1241
                  | xdo_window_find_client at xdo.c:1222
                    | xdo_getwinprop at xdo.c:1541
findclient: 65011716
findclient: 65011716
            | xdo_mouselocation2 at xdo.c:909
              | _is_success at xdo.c:1533
          | cmd_getmouselocation at cmd_getmouselocation.c:48
            | xdotool_output at xdotool.c:583
x:2262 y:689 screen:0 window:65011716
      | args_main at xdotool.c:511
        | xdo_free at xdo.c:144

是不是整個程式內函式呼叫順序都一目瞭然。


快速開始


先安裝 debug symbol,可以參考這裡將 debug symbol 來源設好。 接著安裝這 2 個套件 libxdo2-dbgsym xdotool-dbgsym

$ sudo apt-get install libxdo2-dbgsym xdotool-dbgsym
$ apt-get source xdotool
$ cd path/to/xdotool/source
$ wget https://gist.github.com/fcwu/7466813/raw/69daa7ad2227eb99e66b87e832e04ad8a8798385/supertrace.gdb
$ wget https://gist.github.com/fcwu/7466823/raw/fb9fee4cf987aaa291bef7fb50959e9eb0f5247b/supertrace.py

準備工作完成。把 gdb 跑起來吧!

$ gdb -x supertrace.gdb --args xdotool getmouselocation
(gdb) c
Starting program: /usr/bin/xdotool getmouselocation
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
| main at xdotool.c:287
| _start at ??:0
  | __libc_start_main at ??:0
    | xdotool_main at xdotool.c:290
    | xdotool_main at xdotool.c:316
      | args_main at xdotool.c:456
      | args_main at xdotool.c:493
以下略...


深入一點


GDB 指令檔:supertrace.gdb



裡頭會載入 supertrace.py (本文末),supertrace.py 內,提供了 2 個指令 breakall and supertrace 和 1 個函式 lastbp。breakall 會把所有有 debug info 的函式當做追蹤目標,指令 xdotool 因為安裝了 xdotool-dbgsym 確定有 debug info 外,shared library 的部分可以用 info sharedlibrary 來觀察:

(gdb) info sharedlibrary 
From                To                  Syms Read   Shared Object Library
0x00007ffff7ddaaf0  0x00007ffff7df4eba  Yes (*)     /lib64/ld-linux-x86-64.so.2
0x00007ffff7bd12d0  0x00007ffff7bd5f28  Yes         /usr/lib/libxdo.so.2
0x00007ffff78d7570  0x00007ffff793ffe8  Yes (*)     /lib/x86_64-linux-gnu/libm.so.6
0x00007ffff75b5a70  0x00007ffff763d6c8  Yes (*)     /usr/lib/x86_64-linux-gnu/libX11.so.6
0x00007ffff73971e0  0x00007ffff739a6b8  Yes (*)     /lib/x86_64-linux-gnu/librt.so.1
0x00007ffff6ff3fa0  0x00007ffff71385d0  Yes (*)     /lib/x86_64-linux-gnu/libc.so.6
0x00007ffff6dd0360  0x00007ffff6dd2d88  Yes (*)     /usr/lib/x86_64-linux-gnu/libXtst.so.6
0x00007ffff6bcc9f0  0x00007ffff6bcd318  Yes (*)     /usr/lib/x86_64-linux-gnu/libXinerama.so.1
0x00007ffff69b7350  0x00007ffff69c36d8  Yes (*)     /usr/lib/x86_64-linux-gnu/libxcb.so.1
0x00007ffff67aade0  0x00007ffff67ab918  Yes (*)     /lib/x86_64-linux-gnu/libdl.so.2
0x00007ffff6592740  0x00007ffff659e358  Yes (*)     /lib/x86_64-linux-gnu/libpthread.so.0
0x00007ffff637f490  0x00007ffff63894f8  Yes (*)     /usr/lib/x86_64-linux-gnu/libXext.so.6
0x00007ffff6179d90  0x00007ffff617aae8  Yes (*)     /usr/lib/x86_64-linux-gnu/libXau.so.6
0x00007ffff5f74090  0x00007ffff5f75aa8  Yes (*)     /usr/lib/x86_64-linux-gnu/libXdmcp.so.6
(*): Shared library is missing debugging information.

值得注意的是若是觀察的 library 太多,會因為 debug info 太大,啟動速度變很慢。接著在 supertrace.gdb 裡用了 lastbp 函式取得最後的 breakpoint number,等會設 breakpoint command 會用。

最後,在 breakpoint command 裡,最重要的就是 supertrace 這個命令,它主要會收集 backtrace 的輸出,並拿來跟之前輸出比較,將最後結果印出。



2013/11/12

使用 Arduino 及 Android 自製萬能遙控器 (4) - Arduino on ASUSTOR's NAS and Demo



(圖:在電視前多放了一塊板,還好不會太突兀)

還是稍微提一下前三篇提到的四隻主要程式

  1. web.py
  2. iremote (Android App)
  3. serial_tx.c
  4. ir-tx.cpp

現在只需再將 Arduino 放到 ASUSTOR NAS 上執行就完成了。

因為我在 NAS 已經放了個 Ubuntu 12.04 的環境了,所以需要完成的只剩 serial driver 的部分。Arduino 所要用的 driver 為 cdc-acm.c。在編成 .ko 時,會發生以前曾經遇到的問題 Unknown symbol and disagree about version of,所有的解決方法可參考以前寫的這篇這篇即可。

這次用很髒的方法來解,直接將有問題的函式用它的位址直接取代,如下:

// doro <<
#undef dev_info
#define dev_info(dev, fmt, arg...) _dev_info2(dev, fmt, ##arg)
int (*_dev_info2)(const struct device *dev, const char *fmt, ...) = (void *)0xffffffff8140a280;
int (*dev_warn2)(const struct device *dev, const char *fmt, ...) = (void *)0xffffffff8140a340;
int (*device_create_file2)(struct device *device, const struct device_attribute *entry) = (void *)0xffffffff8140a150;
void (*device_remove_file2)(struct device *dev, const struct device_attribute *attr) = (void *)0xffffffff8140a060;
int (*dev_err2)(const struct device *dev, const char *fmt, ...) = (void *)0xffffffff8140a3a0;
// doro >>

完整程式可參考 cdc-acm.c

最後 demo 影片:用手機操作電視


2013/11/11

使用 Arduino 及 Android 自製萬能遙控器 (3) - Arduino 環境 及 Serial 程式




接下來終於要進入高潮了,這篇會有點長,要介紹的主題包含硬體架設,在 Ubuntu 上開發 Arduino,Arduino 紅外線發送接收程式,及 Ubuntu 的 Serial 程式。


準備硬體



接法及紅外線簡介可參考這篇,非常詳細。其中要注意的是每個人用的紅外線接收零件可能略有不同,請一定要照規格上的腳位接。

以我這裡用的 TL1838




接收部分,接完長這樣



發送時,接完長這樣


(這張圖好像接反了,總之 LED 長腳接 pin 3,短腳接地)

開發環境


安裝 Arduino 開發環境,在 Ubuntu 12.04 上只需

sudo apt-get install arduino arduino-core arduino-mk

此外,還需要紅外線控制(編解碼)的 library Arduino-IRemote

cd /usr/share/arduino/libraries/
sudo git clone https://github.com/coopermaa/Arduino-IRremote IRemote



接收 Go!


選擇 Arduino IDE 的 File —> Examples —> IRemote —> IRecvDump 程式就跑出來了,在前幾行的地方,有個 RECV_PIN 從 11 改成 4,如下:

int RECV_PIN = 4;

程式上傳後,打開內建的 Serial Monitor,再拿著遙控器對著接收器隨便按個幾個鍵,紅外線的編碼即會顥示出來,接下來要做的就是將等會要用到按鍵都按過一輪,並記下來,如下圖:




發送 Go!


我家電視的遙控器是 NEC 32 bits 編碼,並且加入從 serial 讀進指定字元,傳出對應的紅外線:

if ((d = Serial.read()) != -1) {
        switch (d) {
            case '1': code = 0x1CE3807F; break;
            case '2': code = 0x1CE340BF; break;
            case '3': code = 0x1CE3C03F; break;
            case '4': code = 0x1CE320DF; break;
            case '5': code = 0x1CE3A05F; break;
            case '6': code = 0x1CE3609F; break;
            case '7': code = 0x1CE3E01F; break;
            case '8': code = 0x1CE310EF; break;
            case '9': code = 0x1CE3906F; break;
            case '0': code = 0x1CE300FF; break;
            case 'q': code = 0x1CE3708F; break; // volume up
            case 'a': code = 0x1CE3F00F; break; // volume down
            case 'w': code = 0x1CE350AF; break; // channel up
            case 's': code = 0x1CE3D02F; break; // channel down
            case 'e': code = 0x1CE3C837; break; // source
            case 'd': code = 0x1CE3A25D; break; // enter
            case 'p': code = 0x1CE348B7; break; // power
            default: code = 0;
        }
        if (code != 0) {
            irsend.sendNEC(code, 32);
        }

完整的程式可參考 github 上的 ir-tx.cpp


使用指令編繹、上傳


這支程式計畫是要在 NAS 上跑,NAS 上用指令編譯上傳會比較方便,所以若是需要用指令編譯的話,要先準備一隻 Makefile,內容如下:

BOARD_TAG = uno
ARDUINO_PORT = /dev/ttyACM0
ARDUINO_LIBS = IRremote
ARDUINO_DIR = /usr/share/arduino
include /usr/share/arduino/Arduino.mk

.PHONY: minicom

minicom:
    sudo minicom -D ${ARDUINO_PORT} -b 9600 -o

在原本的 Arduino 程式內需加上一些 header,舉例來說之前的 ir-tx.cpp 需在開頭加入:

#include <arduino.h>
#include <hardwareserial.h>

接著在放程式及 Makefile 目錄下:

make  # 編譯
make upload  #上傳
make minicom  # 打開 minicom

即可編譯,上傳。


Serial 程式


在確定用 minicom 傳接都沒問題後,在上一篇內有提到一隻 web.py 裡頭呼叫了外部程式 serial_tx 來傳送 serial 字元給 arduino,這程式請參考 serial_tx.c

若是發生 minicom 可正常傳接收串列資料,但自己的程式不行,可用 stty 來輔助除錯,它可將串列設定全都印出。

2013/11/05

使用 Arduino 及 Android 自製萬能遙控器 (2) - Android App 及 RESTful HTTP Server



(來源)

前情提要

Android App IRemote 會讓使用者直接藉著這個 App 發送指令給 NAS,NAS 準備了一隻 HTTP Server 接收這些指令。目前,只提供 2 組指令:

  1. 取得按鍵列表: curl -i http://localhost:8850/keys
  2. 按下按鍵,本例為按鍵 1: curl -i http://localhost:8850/keys/1

使用 python 寫 RESTful Service 非常容易,配合 Flask module 整隻程式不到 50 行就解決了,程式可參考這裡,所有的按鍵在程式一開頭的變數 keys 定義了。想要更進一步了解 Flask 的用法,可以參考這篇寫得相當不錯。

接著是 Android App IRemote [github]。這隻程式的重點,沒有重點,大致上來說就是按下按鍵時,去叫一隻繼承 AsyncTask 的 SendCommandTask,裡頭會幫忙送出 HttpRequestBlocking (使用 java.net.HttpURLConnection),最後再用 org.json.JSONObject 來解析回傳的 json。

相關的 IP:Port 設定都放在 AppRemote.java 這個所有 Activities 共用的物件裡。

程式畫面如下,簡單用 TableLayout 畫出,及一些 drawable 的按鈕外觀設定。



這篇真是入門的可以了...

2013/11/04

使用 Arduino 及 Android 自製萬能遙控器 (1) - 前言及架構





前言


前陣子那有趣的案子做完後,又想起以前還有東西想做沒空做的想法:用電腦來控制家庭內的所有紅外線設備。

會想做這個主題是因在前公司華芸時,做了個 Boxee 及那精美的手機 App AiRemote 直接可操作 Boxee。因為員工的關係,家裡的客廳也攞了台相同的東西,平常拿來看看日劇電影什麼的非常爽快。但可惜的是對於像我這種沙發馬鈴薯而言,光要手抬起來拿起電視遙控器,切到 HDMI,再拿起手機操作 Boxee ,就要我的命了。為什麼不直接用手機直接操作電視就好。

當然上網找一下就可以找到類似的解決方案,只是我挑了個工程師最喜歡動手做的方案:自已用 Arduino 及家裡的 NAS 兜一個來玩。

架構


這個系統的架構很簡單,包含了

  • Android App 透過 WIFI 告訴 NAS 使用者按了什麼鍵
  • NAS 會有一隻 HTTP Server 接收 Android App 命令,再由 serial port 將命令傳送給 Arduino
  • Arduino 接收到 serial 傳過來的命令後,再由它的 IR Tx 將紅外線放送個其它電器

以上,總共會寫 4 隻程式,程式全都放在 github https://github.com/fcwu/iremote 上。這 4 隻程式分別為

  1. Android App IRemote
  2. HTTP Server, written in Python, with RESTful API design
  3. Serial Tx
  4. Arduino IR

另外有些其他細節所需程式就等之前講到該章節時,再行介紹。預計接下來還會有以下幾篇介紹:

(1) - 前言及架構
(2) - Android App 及 RESTful HTTP Server
(3) - Arduino 環境 及 Serial 程式
(4) - Arduino on ASUSTOR's NAS and Demo

demo: