2014年4月21日 星期一

Ubuntu 14.04 與 ASUS UX32VD 的小問題

因為 Ubuntu 12.04 不能正常驅動 ASUS UX32VD 的顯示卡,所以一直以來我都是用 Ubuntu 13.04 的 Kernel 搭配 12.04 的應用程式。這次釋出的 Ubuntu LTS 14.04 初步測試沒有問題就升級了。不過目前有遇一些小問題,暫時用以下方法解決:

內建的喇叭沒有辦法發出聲音


不知道為什麼我不在 audio 群組之內,所以我只能從外接孔聽到聲音。把我自己加到 audio 群組即可。

$ sudo adduser $USER audio

IBUS 變得不太討喜


新版的 IBUS 除了不能用 Ctrl+Space 切換中英輸入之外(需改用 Shift),會跟著插入點亂跑的輸入法狀態視窗真的很礙眼。因此目前暫時改用 GCIN:

$ sudo apt-get install gcin

之後再去「系統設定值 / 語言支援 / 鍵盤輸入法系統」選擇 GCIN。

不過 GCIN 似乎也有一些問題,無法從狀態列看到 GCIN 的圖示。或許是 Unity 或 AppIndicator 的問題,但是初步測試,用 dconf-editor 修改「com / canonical / unity-gtk-module / whitelist」也沒有用。

SVN 格式更新


以前的 Subversion 版本庫要用以下指令轉換檔案格式:

$ svn upgrade

預設似乎沒有辦法用 apt-get 安裝 i386 的軟體


$ sudo dpkg --add-architecture i386
$ sudo dpkg --print-foreign-architectures
i386

Ubuntu 14.04 無法正常讀取部分使用 PTP 協議的相機


部分過去在 12.04 可以正常使用的數位相機,在 14.04 就沒有辦法正常的被偵測到。舉例來說,我使用的 Nikon Coolpix S6200 就中標了。從錯誤回報來看,應該是 gvfs 有點問題。

除了直接改用讀卡機存取照片外,另一個可行的方法是使用 gphoto2 這個 command line 工具。

$ sudo apt-get install gphoto2
$ mkdir ~/Camera
$ cd ~/Camera
$ gphoto2 -P

小結


目前就只遇到這些問題,其他以後想到再補好了。

2013年9月6日 星期五

編譯 Android AOSP

先前曾經寫過一篇怎麼下載 Android AOSP 原始碼的文章。不過還沒說怎麼編譯 Android Open Source Project。雖然官方網頁有很詳細的說明,但我自己有遇到一些無法執行的步驟,而且有很多可選的步驟不太好讀。所以我就花一點時間把我自己的步驟寫下來。希望可以拋磚引玉。

準備編譯環境


安裝套件


根據官方的說明文件,最適合編譯 Android 的開發環境為 Ubuntu 12.04 (x86_64)。以下文章我們假設大家的環境都是 Ubuntu 12.04。首先在開始之前,我們必須要安裝一些必要的套件:

$ sudo apt-get install git gnupg flex bison gperf build-essential \
    zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev \
    libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 \
    libgl1-mesa-dev g++-multilib gcc-multilib mingw32 tofrodos \
    python-markdown libxml2-utils xsltproc zlib1g-dev:i386


另外為了可以讓 Build System 找得到  Mesa3d 函式庫,我們必須為他建立一個 Soft Link:

$ sudo ln -s /usr/lib/i386-linux-gnu/mesa/libGL.so.1 /usr/lib/i386-linux-gnu/libGL.so

如果你在 apt-get install 時,遇到類似下面的錯誤(沒有遇到可以直接跳到 Java 的段落):

E: Unable to locate package mingw32
E: Package 'python-markdown' has no installation candidate

這是因為你的 APT 沒有開啟 Universe 來源(也就是第三方維護的自由軟體)。我們要先把他加進 Ubuntu 軟體來源,然後再更新套件資訊。

$ sudo vim /etc/apt/sources.list
# 找到 "deb http://archive.ubuntu.com/ubuntu precise main" 在後面加上 universe

$ sudo apt-get update

等到 apt-get update 執行完,再從頭開始執行應該就不會有問題了。

安裝 OpenJDK 7 JDK


如果要編譯最新的 Android,你會需要 Java 7。你可以用以下指令安裝 OpenJDK 7:

$ sudo apt-get install openjdk-7-jdk

之後如果我們要編譯 Android 之前,我們必須記得:

$ export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64
$ export PATH=$JAVA_HOME/bin:$PATH


安裝 Java SE 6 JDK


如果你是要編譯較舊的 Android,你會需要 Java SE 6 JDK,如果你是要編譯最新的 Android,可以跳過這一節。我們必需到 Oracle 網站取得 Java SE Development Kit 6u45 (Java SE 6 JDK)。因為我們的環境是 Ubuntu,所以我們要下載的是  jdk-6u45-linux-x64.bin。下載之後,就依下述指令安裝:

$ chmod +x ./jdk-6u45-linux-x64.bin
$ ./jdk-6u45-linux-x64.bin
$ mv jdk1.6.0_45 /opt

之後如果我們要編譯 Android 之前,我們必須記得:

$ export JAVA_HOME=/opt/jdk1.6.0_45
$ export PATH=$JAVA_HOME/bin:$PATH


取得 repo 管理工具


因為 Android 是一個很大的專案,由數百個 Git 版本庫所構成。為了管理這些版本庫,Google 開發了 repo 這個 Git 管理工具。透過 repo 我們可以一次下載整個 Android 原始碼樹。我們必須先取得 repo:

$ mkdir ~/bin
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
$ chmod +x ~/bin/repo
$ export PATH=~/bin:$PATH

取得原始碼與 Prebuilt Binaries


下載 Android 原始碼


和先前那篇一樣,我們現在可以下載 AOSP 的原始碼了!我們先建立一個資料夾,再使用 repo init 取得 Android Source Tree 的設定檔。最後使用 repo sync 把所有 Git 版本庫抓到本地端。

$ mkdir ~/android-src
$ cd ~/android-src
$ repo init -u https://android.googlesource.com/platform/manifest
$ repo sync -j2

下載 Prebuilt Binaries


如果要刷手機,我們還需要一些來自廠商的驅動程式或 Prebuilt Binaries。我們可以到 Binaries for Nexus Devices 找到 Nexus 系列所需的驅動程式。下載之後(以 broadcom-maguro-jdq39-8edbeae8.tgz 為例),我們先把壓縮檔放在 ~/android-src,然後:

$ cd ~/android-src
$ tar zxvf broadcom-maguro-jdq39-8edbeae8.tgz
$ ./extract-broadcom-maguro.sh
# 按下 Enter,再按下 q,輸入 "I ACCEPT" 之後按下 Enter 即可。

編譯 Android 原始碼


取得所有程式碼之後我們就可以開始編譯整個 Android。首先我們必需先設定好環境變數:

$ cd ~/android-src
$ export JAVA_HOME=/opt/jdk1.6.0_45
$ export PATH=$JAVA_HOME/bin:$PATH
$ source build/envsetup.sh

選取你想要編譯的目標。一般來說 full-eng 就可以滿足所需。如果你要刷手機,可能要從 Building for Devices 頁面找到合適的目標。

$ lunch full-eng

接下來我們就可以開始編譯了!

$ make -j16
(...中略...)
Install system fs image: out/target/product/generic/system.img
out/target/product/generic/system.img+ maxsize=588791808 blocksize=2112 total=576716800 reserve=5947392

經過漫長的等待,如果你可以在最後看到類似上面的訊息,那整個 Android 就成功完成編譯工作,我們可以打開模擬器或刷手機了。

開啟模擬器


開啟模擬器的方法非常容易,只要輸入以下指令:

$ ./out/host/linux-x86/bin/emulator

  • 根據我的測試,有些目標例如 full_maguro-eng 沒辦法使用模擬器。如果要使用模擬器要改用 lunch full-eng。
  • 如果你之後要使用模擬器,但是看到:

    emulator: ERROR: You did not specify a virtual device name, and the system directory could not be found.

    那是因為你忘記 lunch full-eng 了。我們必需 lunch full-eng 模擬器才能找到對應的 System Image。

刷手機


在刷手機之前,最好先上網尋找有沒有成功的案例。因為刷機一定會違反保固條款失去保固。其次,如果指令錯誤更有可能讓你的手機變成磚塊。一般來說,Google 推出的 Nexus 系列都沒有什麼大問題。但是因為不同的手機有不同的狀況,本文僅供參考,不為後果負責

首先我們先看 adb 能不能抓到你的手機:

$ adb devices

  • 如果看不到你的裝置,有可能是 udev 設定檔沒有設好。可以參考 Configuring USB Access,建立 /etc/udev/rules.d/51-android.rules。
  • 如果你有看到裝置,但是在 offline 的狀態。這有可能是因為你沒有開啟手機的開發者選項。你可以拿起手機,進入「Setting/About Phone/Build Number」,連續點 7 下。之後在「Developer Options」頁面勾選「USB Debugging」。

接下來我們要開機進入刷機模式:

$ adb reboot bootloader

這時我們可以看到手機進入 Bootloader,我們可以用 fastboot devices 尋找手機:

$ fastboot devices

如果你還沒有 Unlock 手機,這時我們就可以 Unlock 他。注意:這一步會讓保固失效!

$ fastboot oem unlock

最後,我們來刷手機。刷手機的過程絕對不能拔手機線。

$ fastboot flashall

除了把新的映象檔刷進手機,我們還要清空所有 User Data 與 Cache。否則 Android 有一定的機會一直停留在開機畫面。

$ fastboot erase userdata
$ fastboot erase cache

最後我們就可以重新開機!

$ fastboot reboot

我們可以用 adb logcat 觀察開機過程。有時候會遇到一直開不了機的情況。這時可以先試著清空 User Data 與 Cache。如果還是有問題,有時候是 AOSP 有問題,可以先刷回官方的映象檔。

結語


本文簡單的介紹怎麼編譯 Android Open Source Project 與刷機的基本流程。 希望大家看完之後有能力自己 Hack Android。Happy hacking!

2013年7月31日 星期三

dlopen() 與 GCC -rdynamic 選項

之前讀某個 JIT Compiler 的程式碼。只要正確輸入函式原型,這個 JIT Compiler 可以讓使用者呼叫外部的 C 函式。

要實作類似的功能並不困難,我們只要寫一個「名稱—位址」的對照表,JIT Compiler 就能輕易地實作這個功能。舉例來說,如果我希望使用者可以使用 foo 與 bar 二個函式,我們需要定義一個簡單的函式表:

struct { const char *, void * } func_table[] = {
  { "foo", (void *)&foo },
  { "bar", (void *)&bar },
  { NULL, NULL },
};


當使用者輸入 foo(),我們就可以從函式表取得 foo 的位址,進而呼叫 foo 函式。不過我卻沒有辦法在 JIT Compiler 的程式碼找到類似的東西。這激起了我的興趣。

其實 JIT Compiler 的執行檔確實是有這樣的函式表。如果仔細看該 JIT Compiler 的 Makefile,我們就可以發現他在編譯 JIT Compiler 的時候,額外加上 -rdynamic 選項。根據 GCC 使用手冊,-rdynamic 的用途為:
Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of dlopen or to allow obtaining backtraces from within a program.
簡單的說,這個選項會把有的 non-static 函式視為要 export 的 symbol,讓 Linker 把他們都寫入 Dynamic Symbol Table。之後,我們就可以使用 dlsym() 找到這個 symbol。以下是一個簡單的範例:

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>


void foo() {
  printf("called foo()\n");
}

void bar() {
  printf("called bar()\n");
}

int main(int argc, char **argv) {
  if (argc < 2) {
    fprintf(stderr, "USAGE: %s [func]\n", argv[0]);
    return EXIT_FAILURE;
  }

  /* Load the function/variable symbol table from executable. */
  void *handle = dlopen(NULL, RTLD_LAZY);
  if (!handle) {
    fprintf(stderr, "ERROR: Failed to load executable\n");
    return EXIT_FAILURE;
  }

  /* Find the function */
  void *func = dlsym(handle, argv[1]);
  if (!func) {
    fprintf(stderr, "ERROR: Function not found: %s\n", argv[1]);
  } else {
    ((void (*)())func)();
  }

  dlclose(handle);

  return EXIT_SUCCESS;
}


我們可以分別使用以下二個指令編譯這個範例程式:

$ gcc example.c -ldl
$ gcc example.c -ldl -rdynamic

然後使用 objdump -T 指令觀看執行檔的 Dynamic Symbol Table。

$ objdump -T a.out

以下是簡化過的輸出:

a.out:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
... 略 ...
0000000000000000      DF *UND*    0000000000000000  GLIBC_2.2.5 fwrite
0000000000000000      DF *UND*    0000000000000000  GLIBC_2.2.5 dlsym
0000000000601048 g    D  *ABS*    0000000000000000  Base        _edata
0000000000400914 g    DF .text    0000000000000010  Base        bar
0000000000601038 g    D  .data    0000000000000000  Base        __data_start
0000000000400904 g    DF .text    0000000000000010  Base        foo
... 略 ...

0000000000400924 g    DF .text    00000000000000f6  Base        main
0000000000400788 g    DF .init    0000000000000000  Base        _init
0000000000601048 g    DO .bss    0000000000000008  GLIBC_2.2.5 stderr
... 略 ...


其中藍色的字就是加上 -rdynamic 選項多出來的資料。執行以下指令,我們就可以注意到範例程式可以呼叫對應的函式:

$ ./a.out foo
called foo()
$ ./a.out bar
called bar()

我們要怎麼取得 Dynamic Symbol Table 裡面的資料呢?這就必須借助 dlopen()dlsym()。根據 man dlopen 的資料,dlopen() 必須要傳入二個參數,第一個參數是要載入的動態函式庫,而第二個參數是函式庫的載入方式。如果我們以 NULL 作為第一個參數,則代表我們要載入執行檔本身的 Dynamic Symbol Table。接著我們就可以用 dlsym() 尋找我們要執行的函式了。

後注:雖然我覺得這個方法很巧妙也很有趣,不過我覺得這個方法不太安全。因為所有的函式都有可能暴露給 JIT Compiler 使用者。我覺得還是手動建函式表會比較好。這樣我們才可以控制要開放的函式與介面。