2009年9月28日 星期一

心得分享 - 重灌 100 次也會很開心的 Linux

Standard
這次受 [ICOS2009(International Conference on Open Source)] 之邀,很榮幸於研討會當天給予了一場 Talk - 『重灌 100 次也會很開心的 Linux』,該議程是將最近手頭正在做的 Open Source Project 做一次具體而微的整理,全程以生命苦短為出發點,用 Linux Distribution 引領人生意義,探討人性之苦痛。

議程簡報可於此下載:

粗略估計,多數電腦依賴者的一生之中,將有超過一年以上之時間不眠不休浪費在重灌以及更新系統。但在取人錢財的系統廠商不供應免費生前契約的前提之下,何苦賠了性命與之軟體生命周期博鬥?因此,藉由實作一個新的 Linux Distribution,以確保三至五分鐘之內重灌一套全功能的 Linux 系統,便是一個能造福人群的出發點。

有感,Mandice Project 便是在人之生命與軟體壽命之自我衝突中被建構起來的 Linux Distribution,以 Emdebian Grip 為基礎,其相容於 Ubuntu 與 Debian,僅 700MB 之硬碟使用大小而不縮減任何功能,忠於使用者以追求更漂亮且簡單易用的介面,且永遠不必再做大版本號的系統更新,以終生線上更新機制,徹底擺脫『六個月,版本加一號,系統死一次』之現代 Linux 摩爾定律的悲哀。

Mandice Screenshots:
整合 Google Chrome 之展示
Support Adobe Flash Player (展示開心農場)
MSN 即時通訊軟體

網路管理
內建所有目前 Linux 所支援的驅動程式(展示 3D 運算)
多媒體功能(展示 PPStream)
OpenOffice 文書處理

此外,更因為相容於 Ubuntu,有許多軟體可以直接安裝,操作如使用 Ubuntu 一般。使用者不必重新適應新的環境。而 Mandice 將 100% 相容於 Debian Sid,可以完全取用 Debian 體系下的所有支援。

後記

原計劃於短暫之議程時間內當場表演安裝一百次 Mandice Linux,但因技術上出了問題而作罷,更完整的 Mandice 安裝程式將於近期內釋出。

2009年9月17日 星期四

CUSE - Userspace Character Device 機制

Standard
因為最近碰上了 KMS(Kernel Mode Setting)的 bug,造成偵測解析度出現問題,於是為了解決這問題,又開始追 Linux Kernel 的 Log,但原先的目標沒有追到,反而有些其它的意外發現。話說,2.6.31 已經在本月 9 日正式釋出,其中有些新的實作和令人興奮的支援,如:USB 3.0、日前提到過的『Fanotify 更全面性的檔案監控機制』,其實另外還有一個重要的機制『CUSE(Character devices in Userspace)』。

如同 FUSE(Filesystem in Userspace),CUSE 目標提供一個機制,讓開發者可在 userspace 實作 character device,而不用撰寫 kernel space 的 module 來達成這項目的。這機制有助於許多驅動程式的開發,甚至是讓 Linux 在未來開發各種支援時,能有更大的彈性以及使核心有更高的安全性。從 patch 來看,由於許多部份已經在過去開發 FUSE 時被實作過,讓開發者可以輕易的延用過去成果實作 CUSE。

這有一個專案『OOSP(Open Sound System Proxy)』,就嘗試著用 CUSE 實作一個假的裝置檔,讓老的音效應用程式可以在不修改的情況下,藉由這個 OOS Proxy 去使用新的音效驅動程式架構(如:Alsa),其做法就是產生 OSS 的 /dev/dsp、/dev/adsp、/dev/mixer 再將這些 character devices 接收到的訊息,處理並轉送到現代的音效驅動程式架構。

2009年9月13日 星期日

如何回報 Debian Bug?

Standard
Debian 對 e-mail 的依賴度相當高,所以大多數 Debian 社群的協作,沒有什麼 Web-based 的系統,一切都使用 e-mail 處理居多,當然,bug report 也是使用 e-mail 來處裡。雖然大家的信件都寄到同一個信箱,但只要遵照格式來寫信,Debian 有一套自動處理的機制,依然會將信轉到負責的 Debian 維護者(Maintainer)手中。至於格式細節可以閱讀官方文件『How to report a bug in Debian using reportbug』,內文中有些範例可以參考。

雖然官方文件洋洋灑灑寫了很多篇幅,實際上,一個最簡單的 bug report 信件大致長這個樣子(以昨天回報的 totem-gstreamer bug 為例):
Package: totem-gstreamer
Version: 2.26.3-1

The core of totem-gstreamer is very simple and small,
package size is about 2,056K without compression. But
if we'd like to install totem-gstreamer successfully,
it must use more than 24MB of disk space.

The problem is that gnome-icon-theme which is dependency
of totem-gstreamer needs 22MB disk space at least, and also
gnome-icon-theme cannot be replaced with other icons packages
for totem-gstreamer.

Actually, the most icons in the gnome-icon-theme are not
needed by totem. It should be replaceable and allows us to use
other icon theme package.
然後可以將信寄到 Debian Bug Tracking System <submit@bugs.debian.org>,且標題輸入成這樣的型式:
<package>: <標題>
totem-gstreamer: Has unnecessary dependancy - gnome-icon-theme
在這封 bug report 的信中省略掉系統資訊的部份(Kernel、Debian Version、Libraries 等)因為這不是什麼極嚴重或是會影響到程式運作的 bug,但多數情況下還是必需要提供完整的資訊供 upstream 參考,畢竟講的越清楚,越有利於開發者修正。

若都準備好,就可以寄出信件了,如果中間沒出什麼問題,送出不久後 Debian Bug-Tracking System 就會通知你已經轉信到維護者的手上,我們的 bug report 也正式就告一段落,接下來只要等待回音就好。

後記

話說對於 totem-gstreamer 這個 bug report,我收到的回應中,有人請我造個假的 gnome-icon-theme package 去騙過 totem-gstreamer,這也未免有點髒。

2009年9月12日 星期六

好用的 Trace 工具 cflow

Standard
自己寫軟體,自己的邏輯,自己的世界,往往不會有什麼困難,這也是很多人從學生時代一路爬上來的歷程,直到進入到職場後撰寫著商業軟體,更是只有自己的一片天。但寫軟體有如寫作文,言之有物前必先廣閱天下文章,否則若是能獨樹一格是好,不能便陳腔濫調且原地踏步。但是看懂別人的程式實在是很困難的一件事,除了要懂他人寫程式的風格和思維外,還要通盤了解架構,這必須要有見山不是山的能力才能勝任。一般人想單靠著程式碼上彎彎曲曲的豆芽菜,反推回去程式的原貌,真有如瞎子摸象。對於做為一般人的我們,這時便要借助些工具,以幫助我們更省時省力的去 Trace 程式碼。

講到閱讀程式碼,『cflow』就是不得不提到的方便工具之一,它能夠幫助我們確認程式的大架構,以及分析程式碼相互的關聯性。這裡是使用 cflow 去演示分析 Android Dalvik VM,分析的檔案是 dalvik/dalvikvm/Main.c:
$ cflow dalvikvm/Main.c
main() <int main (int argc,char *const argv[]) at dalvikvm/Main.c:141>:
setvbuf()
malloc()
memset()
strdup()
strcmp()
fprintf()
assert()
blockSigpipe() <void blockSigpipe () at dalvikvm/Main.c:31>:
sigemptyset()
sigaddset()
sigprocmask()
fprintf()
JNI_CreateJavaVM()
createStringArray() <jobjectArray createStringArray (JNIEnv *env,char *const argv[],int argc) at dalvikvm/Main.c:44>:
FindClass()
ExceptionCheck()
fprintf()
assert()
NewObjectArray()
NewStringUTF()
SetObjectArrayElement()
DeleteLocalRef()
FindClass()
GetStaticMethodID()
methodIsPublic() <int methodIsPublic (JNIEnv *env,jclass clazz,jmethodID methodId) at dalvikvm/Main.c:92>:
ToReflectedMethod()
fprintf()
FindClass()
GetMethodID()
CallIntMethod()
DeleteLocalRef()
CallStaticVoidMethod()
ExceptionCheck()
DetachCurrentThread()
DestroyJavaVM()
free()
一般來說,cflow 會從進入點 main() 開始做解析,可以看到每一個被呼叫的 function 都被 cflow 抓了出來,甚至是出處和行數都清楚被標示,此外,只要是 function name 取得漂亮,我們已經可以從這樣的樹狀結構大致了解到 Dalvik VM 的初始化或執行流程。整個過程簡單易瞭,就算用猜的也能大致猜出許多部份的設計目的。

但是在前一個範例中,很多的 function 並未標示出處,甚至沒有更進一步的資訊,這是因為目前只有對 dalvik/dalvikvm/Main.c 做解析所致,但是我們還是可以一步步將程式碼引入,慢慢尋出一些脈絡,現在假設為了追 JNI_CreateJavaVM(),我們可以多引入其他程式碼檔案(*.c):
$ cflow dalvikvm/Main.c vm/*.c
main() <int main (int argc,char *const argv[]) at dalvikvm/Main.c:141>:
setvbuf()
malloc()
memset()
strdup()
strcmp()
fprintf()
assert()
blockSigpipe() <void blockSigpipe () at dalvikvm/Main.c:31>:
sigemptyset()
sigaddset()
sigprocmask()
fprintf()
JNI_CreateJavaVM() <jint JNI_CreateJavaVM (JavaVM **p_vm,JNIEnv **p_env,void *vm_args) at vm/Jni.c:3501>:
memset()
malloc()
dvmInitMutex()
fprintf()
strcmp()
strncmp()
LOGW()
strchr()
dvmUseCheckedJniVm() <void dvmUseCheckedJniVm (JavaVMExt *pVm) at vm/CheckJni.c:2422>:
assert()
dvmCreateJNIEnv() <JNIEnv *dvmCreateJNIEnv (Thread *self) at vm/Jni.c:492>:
assert()
calloc()
dvmSetJniEnvThreadId()
dvmUseCheckedJniEnv() <void dvmUseCheckedJniEnv (JNIEnvExt *pEnv) at vm/CheckJni.c:2412>:
assert()
dvmLockMutex()
dvmUnlockMutex()
dvmStartup() <int dvmStartup (int argc,const char *const argv[],bool ignoreUnrecognized,JNIEnv *pEnv) at vm/Init.c:910>:
assert()
LOGV()
setCommandLineDefaults() <void setCommandLineDefaults () at vm/Init.c:814>:
getenv()
strdup()
dvmPropertiesStartup() <bool dvmPropertiesStartup (int maxProps) at vm/Properties.c:29>:
malloc()
dvmProcessOptions() <int dvmProcessOptions (int argc,const char *const argv[],bool ignoreUnrecognized) at vm/Init.c:542>:
LOGV()
...(以下省略)
如此,將所有的程式碼檔案引入,就可以得到所有 function 的資訊,而其中便可以得知 JNI_CreateJavaVM() 被定義在 vm/Jni.c(當然也可以使用 grep 達到目的)。不過你或許發現到,一次引入到這麼多檔案所產生的層層資訊太過龐大,並不利於閱讀和分析,因此這樣的做法其實並不被常使用,而通常是知道 JNI_CreateJavaVM() 所在檔案後,單獨引入做簡單的組合分析:
$ cflow dalvikvm/Main.c vm/Jni.c
main() <int main (int argc,char *const argv[]) at dalvikvm/Main.c:141>:
setvbuf()
malloc()
memset()
strdup()
strcmp()
fprintf()
assert()
blockSigpipe() <void blockSigpipe () at dalvikvm/Main.c:31>:
sigemptyset()
sigaddset()
sigprocmask()
fprintf()
JNI_CreateJavaVM() <jint JNI_CreateJavaVM (JavaVM **p_vm,JNIEnv **p_env,void *vm_args) at vm/Jni.c:3501>:
memset()
malloc()
dvmInitMutex()
fprintf()
strcmp()
strncmp()
LOGW()
strchr()
dvmUseCheckedJniVm()
dvmCreateJNIEnv() <JNIEnv *dvmCreateJNIEnv (Thread *self) at vm/Jni.c:492>:
assert()
calloc()
dvmSetJniEnvThreadId()
dvmUseCheckedJniEnv()
dvmLockMutex()
dvmUnlockMutex()
dvmStartup()
free()
dvmChangeStatus()
LOGV()
createStringArray() <jobjectArray createStringArray (JNIEnv *env,char *const argv[],int argc) at dalvikvm/Main.c:44>:
FindClass() <jclass FindClass (JNIEnv *env,const char *name) at vm/Jni.c:1171>:
JNI_ENTER()
dvmGetCurrentJNIMethod() <const Method *dvmGetCurrentJNIMethod (void) at vm/Jni.c:976>:
assert()
dvmThreadSelf()
SAVEAREA_FROM_FP()
dvmIsNativeMethod()
...(以下省略)
或者是只想追 JNI_CreateJavaVM() 的細節並省略掉其他不相干的資訊,可以將進入點從 main() 設為 JNI_CreateJavaVM():
$ cflow -m JNI_CreateJavaVM dalvikvm/Main.c vm/Jni.c
cflow:vm/Jni.c:3144: gNativeInterface/-1 redefined
cflow:vm/Jni.c:221: this is the place of previous definition
JNI_CreateJavaVM() <jint JNI_CreateJavaVM (JavaVM **p_vm,JNIEnv **p_env,void *vm_args) at vm/Jni.c:3501>:
memset()
malloc()
dvmInitMutex()
fprintf()
strcmp()
strncmp()
LOGW()
strchr()
dvmUseCheckedJniVm()
dvmCreateJNIEnv() <JNIEnv *dvmCreateJNIEnv (Thread *self) at vm/Jni.c:492>:
assert()
calloc()
dvmSetJniEnvThreadId()
dvmUseCheckedJniEnv()
dvmLockMutex()
dvmUnlockMutex()
dvmStartup()
free()
dvmChangeStatus()
LOGV()
到此目前為止,大概可以看出 JNI_CreateJavaVM() 對 Mutex 機制做了初始化,又使用 dvmCreateJNIEnv() 初始主要 Thread 的環境。當然以上是猜測,為了確認表面的假設,我們可以大致閱讀 dvmCreateJNIEnv() 的程式碼來證明或是推翻。

當然,以上只是演示 cflow 的用法和方便性,並還沒有真的在 Trace Dalvik。透過 cflow 工具的分析,我們可以先行做假設,再針對目標做一步步證明,相較於面對大海茫茫的程式碼而不知從何下手,這卻是一個突破窘境的方法。

2009年9月8日 星期二

偷拍 Framebuffer 的風流韻事

Standard
Linux 下的螢幕抓圖,多半以 Xorg/X11 底下的實作居多,有數不清的工具可以使用,但這都僅限於一般 PC 上的範疇。有許多 Embedded System 不外乎就是小而美,這種小裝置上若要跑上 Xorg,有時便不是這麼容易,因此在多數情況下 Framebuffer 才是顯示影像的主流方法。那麼我們是否可以,來實作一下 Framebuffer 的抓圖呢?嘗試著偷拍這種針孔型裝置的畫面。 :-P

這是一支簡單的程式(screenshot.c),會將 Framebuffer 的影像資料從記憶體中取出並存成 screenshot.jpg:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <jpeglib.h>

void savetojpeg(unsigned char *img, int width, int height)
{
unsigned char *buffer, *src, *dest;
unsigned char *line;
int length;
int i, line_length;
FILE *fp;
struct jpeg_compress_struct jpeg;
struct jpeg_error_mgr jerr;

/* setting error output */
jpeg.err = jpeg_std_error(&jerr);

/* create jpeg */
jpeg_create_compress(&jpeg);

/* setting image */
jpeg.image_width = width;
jpeg.image_height = height;
jpeg.input_components = 3;
jpeg.in_color_space = JCS_RGB;

/* other settings by default */
jpeg_set_defaults(&jpeg);

/* output */
fp = fopen("screenshot.jpg","w");
jpeg_stdio_dest(&jpeg, fp);

/* processing */
jpeg_start_compress(&jpeg, TRUE);

/* transform 4-byte(framebuffer raw data) to 3-byte */
length = width * height;
buffer = (unsigned char *)malloc(sizeof(unsigned char)*length*3);
for (i=0,src=img,dest=buffer;i<length;i+=3,src+=4,dest+=3)>
*dest = *src;
*(dest+1) = *(src+1);
*(dest+2) = *(src+2);
}

line_length = width * 3;
/* write image */
for (i=0,line=buffer;i<height;i++,line+=line_length)>
jpeg_write_scanlines(&jpeg, &line, 1);

jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);

/* close file */
fclose(fp);

/* release */
free(buffer);
}

int main(int argc, char* argv[], char *envp[])
{
int fb;
caddr_t mem_map;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

/* open framebuffer device */
if ((fb = open("/dev/fb0", O_RDWR))==-1) {
printf("Cannot open framebuffer device.\n");
exit(1);
}

/* get screen information */
if (ioctl(fb, FBIOGET_VSCREENINFO,&vinfo)==-1) {
printf("VSCREENINFO Error.\n");
exit(1);
}

/* get memory information of framebuffer */
if (ioctl(fb, FBIOGET_FSCREENINFO,&finfo)==-1) {
printf("FSCREENINFO Error.\n");
exit(1);
}

/* map physics address to virtual address */
mem_map = (char *)mmap(NULL, vinfo.yres*finfo.line_length,
PROT_READ, MAP_SHARED, fb, 0);
if (mem_map==MAP_FAILED) {
printf("mmap() Error. %s\n", strerror(errno));
exit(1);
}

savetojpeg(mem_map, vinfo.xres, vinfo.yres);

close(fb);

return 0;
}


然後在 comile 時只需要加上 -ljpeg 即可:
gcc -o screenshot screenshot.c -ljpeg


配合 jpeglib 的影像處理是使用 32bits,且避免過多色彩深度的轉換以混淆本文的重點,因此該程式只能在 Depth 為 32bits 的模式下正常執行。