July 31, 2008

夢想、希冀、實踐

早上在書房隨性閱讀時,想到美國現代火箭先驅 Robert Hutchings Goddard 在 1904 年畢業生致詞時,道出一句精闢入裡的格言:
    "It has often proved true that the dream of yesterday is the hope of today, and the reality of tomorrow."

誠然,昨日的夢想,成為今日的希冀所在,而更具體落實於明日,這幾乎是人類歷史最生動的描述。Robert H. Goddard 十六歲那年,拜讀 H.G. Wells 的科幻小說名著《世界大戰》(War Of The Worlds),就一心嚮往太空旅行。在家鄉美國麻州,閱讀小說而獲得靈感的他,爬上一顆櫻桃樹,望著一望無際的寬廣草原,幻想著能操控得以登錄火星的裝置,自此,生命大受啟發,就訂定該日,也就是十月 19 日為紀念日,時值 1899 年。

科幻大師 Arthur C. Clarke 曾說過,人們談論對新興的事物,通常要經歷以下三階段的變化:
  • 「胡扯!癡人說夢,別提了」
  • 「這或許是可行,但值得嗎?」
  • 「我早就說這是個好主意!」
無疑地,宇宙航行正是如此。從人類有知覺開始,仰望天際的星宿,對穹廬的高度神秘保有敬意,卻在內心建構著探索太空的幻想。無論是中國淒美的牛郎織女、嫦娥奔月傳說,抑或希臘羅馬的神話,再再都反映著,這是最深刻又浪漫的夢想。工業革命後,人類技術快速發展,過去許多不可能之事,逐漸有改觀,在二十世紀初,仍有一些科學家撰文,定論太空航行是徹底的空想,但 Robert Goddard 不這麼想,以堅毅不拔的精神,獨自在二十世紀上半葉,進行火箭飛行的試驗與著手理論的研究。

劃時代的里程碑,是在 1926 年三月 16 日,Robert Goddard 在姑媽 Effie 的農場發射史上第一個液態燃料火箭,儘管飛行時間只有 2.5 秒,最大的高度僅 12 公尺,飛行距離為 55 公尺,但這完全具備現在火箭的特徵:為保持缺乏尾翼的火箭得以穩定飛行,沈重的引擎位在頭部,而攜帶液態氧和汽油的燃料筒位在尾部,靠管線以輸送液態燃料。在 1945 年去世前,他已累積超過兩百個關於火箭技術的專利,爾後,人類甚至在 1969 年踏上了月球表面,這當然是奠基於他與後繼者勇於挑戰而執著自我信念的研發。

閱讀這位「太空航行技術之父」的事蹟時,我們很難想像,過去報章媒體是如何不斷抨擊訕笑這位畢生都在宣揚火箭飛行與星體探測的 Robert Goddard,這些,不過就是幾十年前的事。1921 年《紐約時報》的編輯駁斥 Robert Goddard 對於火箭在太空飛行的火箭的提案,認為真空的環境下,沒有空氣的反作用力,火箭不可能航行,自此,對這位古怪的教授,各方的抨擊此起彼落。甚至,到了 1956 年,Robert Goddard 死後,英國皇家天文學家還表示:「太空旅行完全是胡扯」。至此,不禁想到愛因斯坦的名言:「常識就是人到十八歲為止所累積的各種偏見」,僅用「常識」去看待這群憑靠著昨日的夢想、追逐今日美好的期待的實踐者,是不是太果斷了?1957 年,蘇聯人造衛星 Sputnik I 就開始環繞地球,1961 年載人航行器也進入太空,這說明了什麼?

無獨有偶,歷史上,處處可見人們對科技發展的錯誤評估,就在 Robert Goddard 爬上櫻桃樹而矢志太空航行的 1899 年,當時的美國專利局局長都爾就說:
    「所有能被發明的東西,都早已被發明出來了」
上天安排如此的巧合,豈不是人類歷史上,讓人拍案叫絕的笑話嗎?Robert Goddard 等人,在飽受質疑與抨擊的年代,仍堅定自己的信念,因為他們瞭解關於流體力學、燃料,發動機,和機械結構等廣泛又深入的背景知識,藉此設計現代多節火箭。「過去、現在、未來」又使命性地對照於「夢想、希冀、實踐」,是我們都該瞭然於心的,這也將是人生最美好的體驗。

1994 年,美國人文心理學之父 Rollo May 過世後,一篇發表於《人文心理期刊》的追悼文章提到,他生前耕褥思緒的研究室,在牆上貼有一首詩,部份試譯如下:
    有隱士般的靈魂
    蟄伏寄居於
    自足的靜謐裡
    有星星般的靈魂
    疏離錯落於
    寂寥的蒼穹上
    有拓荒者般的靈魂
    篳路藍縷的作為新路標
    立於高速公路未曾鋪設的地段
    但讓我站於道路的一旁
    成為人類的
    一位朋友
Rollo May 的生活備忘錄,不是沙特的證詞,也非佛洛伊德的文集,更不是尼采的語錄,而僅是一位平凡詩人福斯的詩。這貌似不凡的平凡,可說是 Rollo May 一生的寫照,更廣泛來說,先驅如 Robert H. Goddard 者,不正拓荒於這嶄新的地帶?儘管路途是如此艱辛又孤獨執著,而他們穩健的步伐,隱約透露其天真純美的初衷。佇立於書房的窗口,喃喃道著:
    "the dream of yesterday is the hope of today,
    and the reality of tomorrow."
參考資料:
由 jserv 發表於 05:51 PM | 迴響 (3)

July 30, 2008

建立 Gtk+/WebKit 的 API 文件以加速開發流程

上週在教育訓練 [Gtk+ 程式設計初體驗] 中,花了一些時間探討 Gtk+/WebKit,可相當輕鬆地透過既有的 Web Applications,強化 Gtk+ 應用程式的顯示。那麼,該如何擴充給定的範例檔案呢?當然,首先得搞清楚像 WebKitWebView 物件中,到底有哪些 signal、相關繼承關係等。透過 [DevHelp] 這個工具,可視覺化的呈現簡單扼要的類別與 API 資訊,但預設沒有 Gtk+/WebKit 的文件文件支援,所以我們得自力救濟。

我們必先取得系統中 Gtk+/WebKit 的原始程式碼,可透過 "apt-get source" 一類的指令,這裡就不贅述。接著,我們需要 gtk-doc 的工具集合,在 Debian/Ubuntu 中,套件名稱為 "gtk-doc-tools",這些工具可針對給定的組態,自動生成 DevHelp 可讀取的文件格式 (基本上也是 HTML),筆者已事先做好一份參考設定 [webkit-gtkdoc-stub.tar.bz2],具體操作方式如下: (以 Ubuntu 8.10 為例)
  • 切換到 Gtk+/WebKit 原始檔目錄,注意,這裡指的是 top level 的 "WebKit" 目錄,所以目錄下應該要有 "JavaScriptCore", "WebCore", "WebKit" 等子目錄
  • 解開筆者打包的組態設定:
      $ tar jxvf webkit-gtkdoc-stub.tar.bz2
  • 切換到解開的 doc 目錄:
      $ cd WebKit/gtk/doc
  • 由給定的 Makefile,呼叫 gtk-doc 工具組去產生必要的文件檔案:
      $ make
等待一段時間,讓 gtk-doc 慢慢執行,結束時,應該會看到類似以下輸出:
...
Computing chunks...
Writing WebKitWebSettings.html for refentry(WebKitWebSettings)
Writing WebKitWebFrame.html for refentry(WebKitWebFrame)
Writing WebKitWebHistoryItem.html for refentry(WebKitWebHistoryItem)
Writing WebKitWebView.html for refentry(WebKitWebView)
Writing WebKitWebBackForwardList.html for refentry(WebKitWebBackForwardList)
Writing WebKitNetworkRequest.html for refentry(WebKitNetworkRequest)
Writing ch01.html for chapter
Writing index.html for book(index)
Writing index.sgml for book(index)
Writing webkit.devhelp for book(index)
Writing webkit.devhelp2 for book(index)
有了文件檔之後,就來看如何讓 DevHelp 得以讀取。由於我們不打算更動 /usr 目錄下的文件檔,所以就直接放在 $HOME 目錄下,具體操作方式如下:
$ mkdir -p ~/.local/share/gtk-doc/html
$ cp -af html ~/.local/share/gtk-doc/html/webkit
此時,開啟 DevHelp,在視窗左側搜尋 "WebKitWebView" 一類的關鍵字,就應該會有結果出現。另外,也可將 DevHelp 線上查詢的功能整合到 vim 中,使用情境類似下圖:

由圖可見,下方的視窗是 vim 正在編輯程式,而編輯游標停留在 "webkit_web_view_new" 這個函式上,我們可按下 Esc-h,DevHelp 就會查詢這個 Gtk+/WebKit API,相當方便。只要對 vim 稍微作點設定,就可有如此的功能,方法是,修改 ~/.vimrc,加入以下巨集:
" vim macro to jump to devhelp topics.
function! DevHelpCurrentWord()
        let word = expand("<cword>")
        exe "!devhelp -s " . word
endfunction

" Example: bind <ESC>h to start devhelp and search for the word under the
" cursor
nmap <ESC>h :call DevHelpCurrentWord()<CR>
再搭配前文 [在 vim 凸顯 Gtk+ 特有語法],應該就可更舒適的撰寫程式了。
由 jserv 發表於 05:46 PM | 迴響 (8)

Who Call Me (更新版)

兩週前在亞洲大學講 [快快樂樂學 GNU Debugger (gdb) Part I + II] 時,與一位同業的工程師談到,不透過 gdb 而能自我建立 backtrace,也就是在必要時建立函式呼叫的階層資訊,當時即提到 TimHsu 兄四年前的作品 [Who Call Me],實在是極好的參考資訊。可惜,TimHsu 兄文中所使用的工具與 API 稍微過時,所以,取得他的同意下,筆者將該文改寫並更新,本文以 GNU/Linux 在 IA32 平台的運作為主。

Who Call Me?

原作:徐千洋 (TimHsu) 於 March 30, 2004
改作:Jim Huang (jserv) 於 July 30, 2008

誰呼叫我?使用過 gdb 對 core 檔作 bt (backtrace) 嗎?所謂 backtrace 是用來回溯檢查函式呼叫的關聯性, 以便得知執行時期有哪個函式呼叫的動作,尢其是在許多錯綜複雜的龐大程式碼中,backtrace 是相當有用的 debug 技巧,而這個題目則是用來討論如何在程式執行中作 backtrace。

在實作這個技術前,有兩個關鍵點要先解決:
  • 如何取得此 function 返回位址
  • 如何依據返回位址查知函式名稱
關於第一點, 須先了解堆疊 (stack) 和函式呼叫的處理關係。堆疊是一個後進先出 (LIFO, Last-In-First-Out) 的資料結構,當呼叫某個函式時,相關的暫存器 (register) 就會被存入堆疊。而當函式返回時,便會從堆疊裡取出返回位址,以便回到原來呼叫的下一個指令繼續執行。以 x86 暫存器組來說,其中 EIP 是 Instruction Pointer 之意,用以指出 CPU 將要執行指令的位址;ESP 暫存器則是用來指向目前堆疊的位址。

我們先寫個小程式來觀察: (test.c)
1
2
3
4
5
6
7
8
void test()
{
}

int main()
{
	test();
}
透過 gdb 分析其執行行為:
jserv@venux:~/whocallme$ gcc -o test test.c
jserv@venux:~/whocallme$ gdb ./test
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) b test
Breakpoint 1 at 0x8048397
(gdb) r
Starting program: /home/jserv/whocallme/test 

Breakpoint 1, 0x08048397 in test ()
Current language:  auto; currently asm
(gdb) info reg
eax            0xbff05fb4	-1074765900
ecx            0xbff05f30	-1074766032
edx            0x1	1
ebx            0xb7fceff4	-1208160268
esp            0xbff05f08	0xbff05f08
ebp            0xbff05f08	0xbff05f08
esi            0x80483d0	134513616
edi            0x80482e0	134513376
eip            0x8048397	0x8048397 <test+3>
eflags         0x282	[ SF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) disas test
Dump of assembler code for function test:
0x08048394 <test+0>:	push   %ebp
0x08048395 <test+1>:	mov    %esp,%ebp
0x08048397 <test+3>:	pop    %ebp
0x08048398 <test+4>:	ret    
End of assembler dump.
(gdb)
由上可見,ebp 暫存器值為 0xbff05f08,也就是原來的堆疊位址。以 IA32 來說,函式呼叫 (對應機械指令為 "call") 的過程,CPU 會將返回位址存入堆疊,因此可從 ebp 暫存器的位址裡面,找到我們需要的返回位址。繼續透過 gdb 觀察:
(gdb) p/x *0xbff05f08
$1 = 0xbff05f18
別忘了,一進入此函式時,機械指令 "push $ebp" 已被執行 (詳見 test 函式反組譯的結果,也就是位址 0x08048394),因此堆疊位址已被減 4,故,若要取得正確的值,需要再將位址加回 4,才可,也就是:
(gdb) p/x *(0xbff05f08+4)
$2 = 0x80483af
此值應該就是 test() 正確的返回位址,繼續透過 gdb 檢查看看:
(gdb) disas main
Dump of assembler code for function main:
0x08048399 <main+0>:	lea    0x4(%esp),%ecx
0x0804839d <main+4>:	and    $0xfffffff0,%esp
0x080483a0 <main+7>:	pushl  -0x4(%ecx)
0x080483a3 <main+10>:	push   %ebp
0x080483a4 <main+11>:	mov    %esp,%ebp
0x080483a6 <main+13>:	push   %ecx
0x080483a7 <main+14>:	sub    $0x4,%esp
0x080483aa <main+17>:	call   0x8048394 <test>
0x080483af <main+22>:	add    $0x4,%esp
0x080483b2 <main+25>:	pop    %ecx
0x080483b3 <main+26>:	pop    %ebp
0x080483b4 <main+27>:	lea    -0x4(%ecx),%esp
0x080483b7 <main+30>:	ret    
End of assembler dump.
(gdb)
果然在 "call <test>" 完後的下個指令,位址就是位於 0x80483af,這也就是 test() 返回位址。接下來,我們將前述的程式,透過 inline assembly 印出一些有用的訊息: (test-1.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
void test()
{
	unsigned int *stack;
	asm ("movl %%ebp, %0\n"
	     : "=g"(stack));
	printf("Return address = 0x%x\n", *(stack+1));
}

int main()
{
	test();
}
編譯並執行:
jserv@venux:~/whocallme$ gcc -o test-1 test-1.c
jserv@venux:~/whocallme$ ./test-1 
Return address = 0x80483fd
再次,透過 gdb 來驗證 test() 函式的返回位址與機械指令 "call" 的關聯:
$ gdb ./test-1
(gdb) disas main
Dump of assembler code for function main:
0x080483e7 <main+0>:	lea    0x4(%esp),%ecx
0x080483eb <main+4>:	and    $0xfffffff0,%esp
0x080483ee <main+7>:	pushl  -0x4(%ecx)
0x080483f1 <main+10>:	push   %ebp
0x080483f2 <main+11>:	mov    %esp,%ebp
0x080483f4 <main+13>:	push   %ecx
0x080483f5 <main+14>:	sub    $0x4,%esp
0x080483f8 <main+17>:	call   0x80483c4 <test>
0x080483fd <main+22>:	add    $0x4,%esp
0x08048400 <main+25>:	pop    %ecx
0x08048401 <main+26>:	pop    %ebp
0x08048402 <main+27>:	lea    -0x4(%ecx),%esp
0x08048405 <main+30>:	ret    
End of assembler dump.
(gdb)
果然如此,所以我們已對本文一開始提出「如何取得此 function 返回位址」的問題,有了初步的解答,再來,就要思索,該如何依據記憶體位址,查知所處的函式名稱。

我們可透過 GNU binutils 的 objdump 工具程式分析 ELF 執行檔的重要資訊,首先觀察執行檔的符號表:
jserv@venux:~/whocallme$ objdump -t ./test-1 | awk '{print $1" "$3" "$6}'|grep "F"
08048340 F __do_global_dtors_aux
080483a0 F frame_dummy
080484e8 O __FRAME_END__
08048480 F __do_global_ctors_aux
08048410 F __libc_csu_fini
08048310 F _start
080484ac F _fini
08048420 F __libc_csu_init
080483c4 F test
0804847a F .hidden
080483e7 F main
08048298 F _init
既然 "objdump -t" 可印出程式的函式名稱和記憶體位址,不就是我們預期的動作嗎?所以,我們將重心擺在該工具程式背後的原理。objdump 是利用 BFD Library (Binary File Descriptor Library) 來實作的,底下的小程式也利用 BFD Library 來讀取符號表 (bfd.c)。注意:在 Debian/Ubuntu 下,需安裝套件 "binutils-dev",方可編譯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>
#include <bfd.h>

int main(int argc, char *argv[])
{
	bfd *abfd;
	long storage_needed;  
	asymbol **symbol_table;
	long number_of_symbols;
	long i;
	char **matching;
	sec_ptr section;
	char *symbol_name; 
	long symbol_offset, section_vma, symbol_address;

	if (argc < 2)
		return 0;
	printf("Open %s\n", argv[1]);
	bfd_init();
	abfd = bfd_openr(argv[1],NULL);
	if (abfd == (bfd *) 0) {
		bfd_perror("bfd_openr");
		return -1; 
	}
	if (!bfd_check_format_matches(abfd, bfd_object, &matching)) {
		return -1;
	}      
	if (!(bfd_get_file_flags (abfd) & HAS_SYMS)) {
		printf("ERROR flag!\n");
		return -1;
	}
	/* 取得符號表大小 */
	storage_needed = bfd_get_symtab_upper_bound(abfd);
	if (storage_needed < 0)
		return -1;
	symbol_table = (asymbol **) xmalloc(storage_needed);
	/* 將符號表讀進所配置的記憶體裡(symbol_table), 並傳回符號表個數 */
	number_of_symbols = bfd_canonicalize_symtab(abfd, symbol_table);
	if (number_of_symbols < 0)
		return -1;
	for (i = 0; i < number_of_symbols; i++) {
		/* 檢查此符號是否為函式 */
		if (symbol_table[i]->flags & (BSF_FUNCTION|BSF_GLOBAL))	{
			/* 反查此函式所處的區段(section) 及
			                   區段位址(section_vma) */
			section = symbol_table[i]->section;
			section_vma = bfd_get_section_vma(abfd, section);
			/* 取得此函式的名稱(symbol_name)、
			               偏移位址(symbol_offset) */
			symbol_name = symbol_table[i]->name;
			symbol_offset = symbol_table[i]->value;
			/* 將此函式的偏移位址加上區段位址,則為此函式在執行時
			   的記憶體位址(symbol_address */
			symbol_address = section_vma + symbol_offset;
			/* 檢查此函式是否處在程式本文區段 */
			if (section->flags & SEC_CODE)
				printf("<%s> 0x%x 0x%x 0x%x\n",
				         symbol_name,  
				               section_vma,  
				                    symbol_offset,
				                         symbol_address);
		}
	}
	bfd_close(abfd);
}
編譯並執行:
jserv@venux:~/whocallme$ gcc -o bfd bfd.c -lbfd
jserv@venux:~/whocallme$ ./bfd test-1
Open test-1
<__do_global_dtors_aux> 0x8048310 0x30 0x8048340
<frame_dummy> 0x8048310 0x90 0x80483a0
<__do_global_ctors_aux> 0x8048310 0x170 0x8048480
<__libc_csu_fini> 0x8048310 0x100 0x8048410
<_start> 0x8048310 0x0 0x8048310
<_fini> 0x80484ac 0x0 0x80484ac
<__libc_csu_init> 0x8048310 0x110 0x8048420
<test> 0x8048310 0xb4 0x80483c4
<__i686.get_pc_thunk.bx> 0x8048310 0x16a 0x804847a
<main> 0x8048310 0xd7 0x80483e7
<_init> 0x8048298 0x0 0x8048298
觀察由 objdump 工具程式與我們撰寫的小程式 bfd,對於 test() 函式的位址,有著相同的輸出,也就是 0x80483c4。現在, 我們依照函式名稱及記憶體位址作對照表,即可立即查詢.,不過這其中仍有個小問題,就是,雖然知道個別函式的起始位址,但並不知道函式的結束位址,也不知道各函式程式內容的大小。要解決這個小問題,就必須在建立對照表時,先作排序,將位址越高的函式排在越後面,並將下一個函式的起始位址當作結束位址。於是筆者建立於前面的 bfd.c 程式,提出新的工具程式 (bfd_dumpfun.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* bfd_dumpfun.c (GPL)
 *
 * Usage: ./bfd_dumpfun [binary]
 * Note: Dump functions infomation of ELF-binary with BFD Library.
 *     
 * by TimHsu(timhsu@info.sayya.org) 2004/03/31
 * Modified by Jim Huang <jserv.tw@gmail.com>, 2008/07/22
 *   - Bump bfd APIs and build fixes.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bfd.h>

typedef struct function_table FUN_TABLE;

/* 宣告一個包含函式名稱和位址的結構 */
struct function_table {
	char name[80];
	unsigned long addr;
};

static FUN_TABLE *fun_table;
static int table_count = 0;   /* 函式個數 */

static int compare_function(const void *a, const void *b)
{
	FUN_TABLE *aa = (FUN_TABLE *) a, *bb = (FUN_TABLE *) b;
	if (aa->addr > bb->addr)
		return 1;
	else if (aa->addr < bb->addr)
		return -1;
	return 0;  
}

/* 增加一個函式資料至對照表 */
static void add_function_table(char *name, unsigned long address)
{
	strncpy(fun_table[table_count].name, name, 80);
	fun_table[table_count].addr = address;
	table_count++;  
}

static void dump_function_table(void)
{
	int i;
	for (i = 0; i < table_count; i++) {
		printf("%-30s 0x%x\n",
		       fun_table[i].name, fun_table[i].addr);
	}
}

int main(int argc, char *argv[])
{
	bfd *abfd;
	asection *text;
	long storage_needed;
	asymbol **symbol_table;
	long number_of_symbols;
	long i;
	char **matching;
	sec_ptr section;
	char *symbol_name;
	long symbol_offset, section_vma, symbol_address;

	if (argc < 2) 
		return 0;
	printf("Open %s\n", argv[1]);
	bfd_init();
	abfd = bfd_openr(argv[1],NULL);
	if (abfd == (bfd *) 0) {
		bfd_perror("bfd_openr");
		return -1;
	}
	if (!bfd_check_format_matches(abfd, bfd_object, &matching)) {
		return -1;
	}      
	if (!(bfd_get_file_flags (abfd) & HAS_SYMS)) {
		printf("ERROR flag!\n");
		return -1;
	}
	if ((storage_needed = bfd_get_symtab_upper_bound(abfd)) < 0)
		return -1;
	symbol_table = (asymbol **) xmalloc(storage_needed);
	number_of_symbols = bfd_canonicalize_symtab(abfd, symbol_table);
	if (number_of_symbols < 0)
		return -1;
	fun_table = (FUN_TABLE **)malloc(sizeof(FUN_TABLE)*number_of_symbols);
	bzero(fun_table, sizeof(FUN_TABLE)*number_of_symbols);

	for (i = 0; i < number_of_symbols; i++) {
		if (symbol_table[i]->flags & (BSF_FUNCTION|BSF_GLOBAL)) {
			section = symbol_table[i]->section;
			section_vma = bfd_get_section_vma(abfd, section);
			symbol_name = symbol_table[i]->name;
			symbol_offset = symbol_table[i]->value;
			symbol_address = section_vma + symbol_offset;
			if (section->flags & SEC_CODE) {
				add_function_table(symbol_name,
				                   symbol_address);
			}
		}
	}
	bfd_close(abfd);
	/* 將函式對照表作排序 */
	qsort(fun_table, table_count, sizeof(FUN_TABLE), compare_function);
	dump_function_table();
}
編譯並執行:
jserv@venux:~/whocallme$ gcc -o bfd_dumpfun bfd_dumpfun.c -lbfd
jserv@venux:~/whocallme$ ./bfd_dumpfun ./test-1
Open ./test-1
_init                          0x8048298
_start                         0x8048310
__do_global_dtors_aux          0x8048340
frame_dummy                    0x80483a0
test                           0x80483c4
main                           0x80483e7
__libc_csu_fini                0x8048410
__libc_csu_init                0x8048420
__i686.get_pc_thunk.bx         0x804847a
__do_global_ctors_aux          0x8048480
_fini                          0x80484ac
現在,我們已將技術的關鍵點都處理好,為能實用化,最好是作成函式庫,得以日後隨時呼叫。我們的函式庫包含兩部份: whocallme.[ch],首先是標頭檔部份: (whocallme.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

#define	FUNCTION_NAME_MAXLEN		80

#define who_call_me() \
	do { \
        	unsigned int *stack; \
		asm ("movl %%ebp, %0\n"  \
		     : "=g"(stack));  \
		fprintf(stderr, \
			"<whocallme>: function <%s> call me <%s>!\n", \
			find_function_by_addr(*(stack+1)), who_am_i()); \
	} while(0)

extern int init_function_table(char *);
留意到巨集定義中的自訂函式 who_am_i(),目的自然就是取得執行中的函式名稱,整個實做如下: (whocallme.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* whocallme.c (GPL)
 *
 * A runtime backtrace of function. 
 * 
 * by Timhsu(timhsu@chroot.org) 2004/03/31
 * Modified by Jim Huang <jserv.tw@gmail.com>, 2008/07/22
 *   - Bump bfd APIs.
 *   - Eliminate compiler errors.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bfd.h>
#include "whocallme.h"

/* forward declarations */
char *find_function_by_addr(unsigned long addr);

typedef struct function_table FUN_TABLE;
/* 宣告一個包含函式名稱和位址的結構 */
struct function_table {
	char name[FUNCTION_NAME_MAXLEN];
  	unsigned long addr;
};

static FUN_TABLE *fun_table;
static int table_count = 0;	/* 函式個數 */

static int compare_function(const void *a, const void *b)
{
        FUN_TABLE *aa = (FUN_TABLE *) a;
        FUN_TABLE *bb = (FUN_TABLE *) b;
	if (aa->addr > bb->addr)
		return 1;
	else if (aa->addr < bb->addr)
		return -1;
	return 0;
}

/* 增加一個函式資料至對照表 */
static void add_function_table(char *name, unsigned long address)
{
	strncpy(fun_table[table_count].name, name, FUNCTION_NAME_MAXLEN);
	fun_table[table_count].addr = address;
	table_count++;
}

/* 取得目前正在執行的函式名稱 */
char * who_am_i(void)
{
        unsigned long *stack;   \
	asm ("movl %%ebp, %0\n"  \
		: "=g"(stack));         
	return find_function_by_addr(*(stack + 1));
}

/* 依照位址取得函式名稱 */
char *find_function_by_addr(unsigned long addr)
{
	int i;
	for (i = 0; i < table_count; i++) {
		if (addr > fun_table[i].addr) {
			if (addr < fun_table[i + 1].addr)
				return fun_table[i].name;
		}
	}
	return NULL;
}

/* 初始化函式對照表 */
int init_function_table(char *file)
{
	bfd *abfd;
	long storage_needed;
	asymbol **symbol_table;
	long number_of_symbols;
	long i;
	char **matching;
	sec_ptr section;
	char *symbol_name;
	long symbol_offset, section_vma, symbol_address;

	bfd_init();
	abfd = bfd_openr(file, NULL);
	if (abfd == (bfd *) 0) {
		bfd_perror("bfd_openr");
		return -1;
	}
	if (!bfd_check_format_matches(abfd, bfd_object, &matching)) {
		return -1;
	}	
	if (!(bfd_get_file_flags (abfd) & HAS_SYMS)) {
		printf("ERROR flag!\n");
		return -1;
	}
	/* 取得符號表大小 */
	storage_needed = bfd_get_symtab_upper_bound(abfd);
	if (storage_needed < 0)
		return -1;
	symbol_table = (asymbol **) malloc(storage_needed);
	/* 將符號表讀進所配置的記憶體裡(symbol_table), 並傳回符號表個數 */
	number_of_symbols = bfd_canonicalize_symtab(abfd, symbol_table);
	if (number_of_symbols < 0)
		return -1;
	/* 配置空間給函式對照表 */
	fun_table = (FUN_TABLE *) malloc(sizeof(FUN_TABLE) * number_of_symbols);
	bzero(fun_table, sizeof(FUN_TABLE)*number_of_symbols);

	for (i = 0; i < number_of_symbols; i++) {
		/* 檢查此符號是否為函式 */
		if (symbol_table[i]->flags & (BSF_FUNCTION|BSF_GLOBAL)) {
			/* 反查此函式所處的區段(section) 及區段位址(section_vma)*/ */
			section = symbol_table[i]->section;
			section_vma = bfd_get_section_vma(abfd, section);
			/* 取得此函式的名稱(symbol_name), 偏移位址(symbol_offset) */
			symbol_name = (char *) symbol_table[i]->name;
			symbol_offset = symbol_table[i]->value;
			/* 將此函式的偏移位址加上區段位址,則為此函式
			 * 在執行時的記憶體位址 (symbol_address) */
			symbol_address = section_vma + symbol_offset;
			/* 檢查此函式是否處在程式本文區段 */
			if (section->flags & SEC_CODE) {
				/* 將此函式名稱和位址加入至對照表 */
			add_function_table(symbol_name, 
						   symbol_address);
			}
		}
	}
	free(symbol_table);
	bfd_close(abfd);
	/* 將函式對照表作排序 */
	qsort(fun_table, table_count, sizeof(FUN_TABLE), compare_function);
	return 0;
}
建構此函式庫方式如下:
jserv@venux:~/whocallme$ gcc -c whocallme.c
jserv@venux:~/whocallme$ ar -q libwhocallme.a whocallme.o
寫個簡短的測試程式,看看執行的效果: (test-2.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "whocallme.h"

void test()
{
	who_call_me(); 
}
void test_a()
{
	test_b();
	test_c();
}
void test_b()
{
	test();
}
void test_c()
{
	who_call_me(); 
}
int main(int argc, char *argv[])
{
	init_function_table(argv[0]);
	test();
	test_a();      
	test_b();
	test_c();
}
編譯並執行:
jserv@venux:~/whocallme$ gcc -o test-2 test-2.c -lbfd -L. -lwhocallme
jserv@venux:~/whocallme$ ./test-2
: function <main> call me <test>!
: function <test_b> call me <test>!
: function <test_a> call me <test_c>!
: function <test_b> call me <test>!
: function <main> call me <test_c>!
下載本文的範例程式: [whocallme.tar.bz2]
由 jserv 發表於 02:28 PM | 迴響 (10)

July 28, 2008

在 vim 凸顯 Gtk+ 特有語法

上週在 [教育訓練:Gtk+ 程式設計初體驗] 提到可透過外掛的 vim 巨集,讓 vim 凸顯 Gtk+ 特有語法,對於 Gtk+ 程式設計大有幫助,效果大致如下圖:

可參考 David Necas 所提交的 [gtk-vim-syntax],其簡介為 "A collection of C extension syntax files for xlib, glib, gobject, gdk, gdk-pixbuf, gtk+, atk, pango, cairo, gimp, libgnome, libgnomecanvas, libgnomeui, libglade, gtkglext, vte, linc, gconf, and ORBit.",實在誘人。安裝方式也很單純,解開後參閱裡頭的 README 檔案,將套件中的 *.vim 檔案複製到 "~/.vim/after/syntax/" 目錄下,並將參考性的 "c.vim.example" 複製到 "~/.vim/after/syntax/c.vim"。另外,實際運用時,我們或許會期望 gtk-vim-syntax 切換 deprecated symbols 的顯示,那麼,可在 vim 設定如下:
:let glib_enable_deprecated = 1
這樣就差不多了,預祝 coding 愉快!
由 jserv 發表於 10:29 PM | 迴響 (3)

「Gtk+ 程式設計初體驗」簡報上線

上週六 (July 26) 到台大進修推廣部,做了主題為 [Gtk+ 程式設計初體驗] 的教育訓練,簡報已上線,可參閱以下網址: 至於議程所需的範例程式,可參閱前文 ["Gtk+ 程式設計初體驗" 參考範例上線] 取得。

這六個小時的教育訓練,拆成兩部份,早上兩小時探討 Gtk+ 關鍵技術、圖形系統的設計考量、Gtk+ 核心概念等;下午則探討三個範例,並從中體驗 Gtk+ 程式設計的慣例與典型模式。預期聽者應可得知 Gtk+ 相關技術的概要、物件導向設計、Model-View 的落實,以及 GStreamer 的核心概念。

感謝前來指教的朋友們,也歡迎討論,此次獲得頗多寶貴的建議,正評估未來是否繼續本主題的教育訓練。
由 jserv 發表於 02:13 AM | 迴響 (2)

July 22, 2008

自由軟體教育訓練與演講規劃

幾周前,與 [Free Electrons] 的一位貢獻者談及若干想法,教育是人無法避免之物,這一輩子我們難免會有為人學生或為人師表之時,而對於 Free Software 與 Open standards 更是如此,往往就在角色的交錯與教學相長中,推進到下一個里程碑。換句話說,自由軟體發展與教育訓練其實就是一體兩面,只是說,後者的形式不單僅是傳統職訓補習機制,而是訴諸 mailing-list, wiki, blog, tracker, ... 等截然不同的面貌,這也是在今年初談 [開放源碼的第五項修煉:淺談社群經營與商業應用],其核心價值所在。

也因此,不僅複雜度提高,我們也更難彙整這繁瑣的資訊,從嵌入式系統開發切入的 [Free Electrons] 正視這些問題,從協助公司行號、團體、個人去使用或開發在嵌入式系統中的自由軟體開始,探討如何借助社群的力量,讓知識匯集,並轉換為更大的驅力。經過這幾年,Free Electrons 已有相當可觀的成果,如 [Embedded Linux Training],而又因為台灣產業的走向,可說是巨大的海綿,汲汲於吸納各式技術與規格,再透過品牌產品、代工生產、專業分工等模式,將海綿中蘊藏的能量釋放出來。就我個人的觀察,以系統整合的產業來說,基本上兩年就是一個週期,會將特定領域的技術,由垂直與水平多面向地彙整起來,而 Free Software 的角色也越來越重,但,往往工程人員不被允許可在充裕的時間內,深入耕耘特定領域的技術,因為,下一個週期又將到來,專案的時程緊迫逼人。

台灣人的工作環境,不免會基於某些考量,發生專業技能的「藏私」狀況。在 [由賽局理論看知識分享與藏私] 一文中,作者透過經濟學中的賽局理論 (game theory),思考企業知識管理的策略式互動行為 (interactive behavior),提出貌似單純卻值得思索的論點:
    「在一個企業裡,大多數員工其實都知道,多分享多學習是提高競爭力最好的方式,但在個人利益衝突與缺乏互信的考量下,員工卻因為害怕自己競爭力會因分享而降低,故選擇了藏私一途。對企業員工而言,員工選擇藏私的自利行為,並不會因此降低員工個體的競爭力,但對企業而言,越多藏私型的員 工,其整體競爭力將遠低具有互信與分享文化的企業。」
因「藏私」而相對「優秀」的員工,短期內或許會佔優勢,但長遠來說,唯有互信與分享文化,才能促使整體環境保持競爭力,進而讓全體都可「優秀」。同樣地,思考我們所見的 Free Software 與 Embedded Linux 亦然,何以 Linux 僅僅用幾年的時間 (真正大幅變化,僅是這十年內的事情),就讓執產業牛耳三十多年的大廠紛紛調整步伐,要敞開雙手接納,要不傾公司資本之力,予以抗衡,何也?就是奠基於自由軟體分享互信文化的本質,聚焦而激盪的火花。

就我個人來說,在稍早 [關於演講邀約] 一文即提過,因緣際會,得以分享過去的開發經驗與針對若干領域的想法,這過程中,專業技術與對技術本身的自信,也在反覆地琢磨,顯得更加洗練,果真如胡適說:「發表是最好的記憶」。第五個年頭,這種分享當然會持續進行,同時也保持幾乎免費、開放指教的模式,去進行演講或教育訓練。前文已簡要帶過原則性的聲明,那麼,本文就來談談小弟最近的規劃,希望能涵蓋以下主題:
  • 系統層面: GNU/Linux 系統概況與低階處理、Realtime Linux、系統層級開發工具、NetBSD 核心設計與開發、RTOS 設計與實做、x86/ARM/MIPS 硬體平台之開發、機器人平台
  • 桌面技術與圖形處理: X Window System 概況與關鍵技術、桌面環境整合與客製化開發、輕量級桌面系統、3D/OpenGL 應用、引入 Web 技術之桌面環境設計、嵌入式系統中的圖形處理與應用、GNU/Linux 圖形系統、Qt/Gtk+ 開發
  • VM (Virtual Machine): Java VM 內部設計與實做考量、動態編譯器 (JIT/Hotspot)、效能調整與安全性強化
  • 自由軟體授權與適用性範疇: 自由軟體與對應法律的疑慮、迴避 GNU GPL 授權衝擊的途徑、應用於消費性電子產品的自由軟體考量
  • 開發工具: GNU Toolchains 概況與探討、版本控制與專案開發、整合開發工具的建構
  • 多國語文處理: 國際化與區域化的資訊系統考量、CJK 通用漢語系統 / 中東語文資訊系統設計、輸入法架構
  • 多媒體音訊視訊處理: MPEG 系統概況、視訊音訊編碼導論、VC-1 / China AVS 技術
看起來有些冗長,但其實任何一個資訊系統整合的從業人員,都在上述領域打轉,唯有透過多分享、多學習,才是提高競爭力最好的方式,既然台灣南北距離不大,行有餘力,進行這些分享,不啻也是一種學習,也可結識潛在的工作夥伴或商業合作機會,何樂不為呢?

基本上小弟敢提出來談的項目就區區那幾項,所以範疇大概定出,日後就將針對深度與交叉的整合度作探討,在此也感謝過去當面討論、會後來信指教、指正重大缺失、提供豐富的參考資訊、促成合作機會等先進與朋友們,希望我們都可一同成長。
由 jserv 發表於 03:45 PM | 迴響 (0)

透過 FreeType 繪製 Unicode ASCII Art

在 hellwolf 那邊讀到有趣的文章 [玩具 : 用 freetype2 顯示 ascii-art 中文],給了範例程式碼,以 FreeType 2 的 API 描繪 glyph (字元對應的字型字體資料),所以只要知曉 Unicode 的索引值,就可輕鬆描繪字體外觀,不失為簡潔明暸的入門。

筆者將原本的程式碼簡化並標注彩色輸出,可參考 [ftart.c]:
/* Taken from http://blog.chinaunix.net/u/8057/showart_335549.html
 * with slight modifications */
#include <ft2build.h>
#include FT_FREETYPE_H
#include <strings.h>

int main(int argc, char **argv)
{
	int psize;
	FT_Library library;
	FT_Face face;
	unsigned int ucode;
	FT_UInt glyph_index;
	int row, pixel;
	char *fp;

	if (argc != 4) {
		return 10;
	}

	ucode = strtol(argv[2], NULL, 16);
	psize = strtol(argv[3], NULL, 10);

	printf("unicode +%X size %d \n", ucode, psize);
	printf("font %s\n", (fp = rindex(argv[1], '/')) != NULL ?
	             fp  + 1 : argv[1]);
	if (FT_Init_FreeType(&library) ||
	    FT_New_Face(library, argv[1], 0, &face) ||
	    FT_Set_Pixel_Sizes(face, psize, 0)) {
		return 1;
	}

	glyph_index = FT_Get_Char_Index(face, ucode);
	if (glyph_index == 0) {
		return 2;
	}
	if (FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT)) {
		return 3;
	}
	if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO)) {
		return 4;
	}

	/* FIXME: we shall reserve space for low length
	 * (face->glyph->bitmap.rows - face->glyph->bitmap_top)
	 */
	for (row = 0; row < face->glyph->bitmap.rows; ++row) {
		for (pixel = 0; pixel < face->glyph->bitmap_left; ++pixel)
			printf("_");
		for (pixel = 0; pixel < face->glyph->bitmap.width; ++pixel){
			if (face->glyph->bitmap.buffer
					[row * face->glyph->bitmap.pitch +
					 pixel / 8] & (0xC0 >> (pixel % 8)))
				printf("\033[44;37m" " " "\033[m");
			else
				printf("_");
		}
		printf("\n");
	}
	return 0;
}
由上,我們可發現以下六個 FreeType API,對應的描述如下:
  • FT_Init_FreeType : 初始化 FreeType library 物件
  • FT_New_Face : 呼叫 FT_Open_Face 開啟指定路徑的字型,建立 face 物件
  • FT_Set_Pixel_Sizes : 呼叫 FT_Request_Size 以索取象徵性 (可能與實際描繪有出入) 的字型大小,單位 pixel
  • FT_Get_Char_Index : 傳回給定字元碼的 glyph,本函數使用 charmap 物件去進行編碼的映射
  • FT_Load_Glyph : 載入單一 glyph 資料到 face 物件中
  • FT_Render_Glyph : 轉換給定的 glyph 圖形為點陣圖
再回頭看程式碼,就很清楚了,根據給定的 Unicode 索引值去找到指定字型檔裡頭的 glyph,然後建立 FreeType 的 face 物件,並將找到的 glyph 指定並要求描繪出來,依據輸出的點陣圖,透過 ASCII 符號輸出。為了簡化程式設計,這裡僅用兩種字元來作區隔。

編譯方式如下:
$ gcc `pkg-config freetype2 --cflags --libs` -o ftart ftart.c -Wall
執行時,需給定三個參數,分別為 TrueType 字型檔名、Unicode 索引值,與期望的點數 (ASCII 的行列數量),以小弟的名裡頭的「群」為例,可依以下方式執行:
$ ./ftart \
    /usr/share/fonts/truetype/arphic/uming.ttc \
    `echo -n "群" | iconv -t ucs2 | od -tx1 | head -n1 | awk '{print $3$2}'` \
    24
這邊用了 shell script 的「魔法」,透過 iconv 取得 UCS2 編碼的表示值,倘若分別用 [CJKUnifonts] 與 [文泉驛] 帶入,可得類似以下輸出:

環肥燕瘦,各有姿色 :-)
由 jserv 發表於 12:25 PM | 迴響 (2)

July 21, 2008

「Gtk+ 程式設計初體驗」參考範例上線

即將於本月 26 日 (週六) 舉辦的 [教育訓練:Gtk+ 程式設計初體驗],以專案目標導向的形式,探討 [Gtk+] 與相關技術,其參考範例已預先上線,可參考 [gtk-examples0.tar.bz2] (授權:public domain)。這幾個範例都是貨真價實的完整應用程式,不單跑個 "Hello World" 而已,主要是希望帶著與會者去熟悉 Gtk+ 方式的編寫程式慣例,期許能充分利用這些開放發展、可累積的技術。

在這個「Google 時代的 Programming」模式 (詳情可參閱 [qing] 老大的文章) 之下,其實我們最該思考的關鍵點,就是如何「借力使力」,尋求透過成熟的自由軟體技術,達成我們所期望的應用或系統設計。以 Gtk+/GNOME 來說,隨便一個技術細節都可以談上好幾天,遑論徹底知曉整個 framework 設計或個別應用程式的特性,但隱約中,系統卻保持若干一貫的設計準則與發展模式,這就是我們該去探究與揣摩的,這也是本教育訓練一開始就丟出具體而微的範例的緣故。廢話不多說,咱們看看在「初體驗」課程中,裡頭的三個範例檔:
  • [webapp.c] (使用 Gtk+/WebKit 的應用程式)

    由圖可見,這是個左邊一個按鈕,右邊一個馬力歐遊戲的小程式,當然,遊戲本身是可以玩的,這需要幾行程式呢?
  • [editor.c] (Gtk+ widget 展示)

    如上圖,這就是一個文字編輯器,完全支援 Unicode 與多行編輯,觀察看看,用幾行程式碼達成?
  • [player.c] (使用 GStreamer 技術的範例)
    這是個可播放多媒體聲音 / 影像的小程式,雖然沒有華麗的介面,但就跟 MPlayer 一樣,忠實地處理指定網址 (包含 file:// ) 的多媒體檔案,這又需要多少程式碼呢?
議程中,還會給定另一個範例,將上述項目一併整合起來,預期可透過 Gtk+/WebKit 繪製程式碼介面、Gtk+ widget 來作整體外觀布局,再用 [GStreamer] 嵌入華麗的多媒體內容。過去,我們聽到這樣的需求,不免就會聯想到商業軟體豐富視覺呈現,但透過 Gtk+ 一系列的技術,只要掌握若干關鍵點,我們也得以做出相仿甚至更好的效果,這也是強調「作中學」的精神所在。

透過以上範例,Gtk+ 若干設計的核心想法也將被伴隨揭露於與會者之前,試看:
  • 物件導向的思維與實做:表面上都是 C 語言的 Gtk+,其實蘊含豐富的 OOP 機制,而物件導向更是深植於設計本體
  • 視窗系統中的事件與其對應的操作處理:藉由範例程式的修改與調整,我們可即時窺見這些抽象概念的視覺具體呈現
  • 圖形元件的設計概念與組合、互動形式:無疑地,這是 Gtk+/GNOME 技術引人入勝之處,只要施點魔力,就會浮現於我們面前
  • MVC (Model-View-Control) 設計模式的引入:Gtk+ 到處可見到 Design Patterns,並以優雅的形式去將若干技術融會其中
另外,感謝各位朋友的捧場,開放報名的後兩天,竟然就立刻額滿,實在是始料未及,所以,現在的補救方式有兩個,一是請您耐心等待議程錄影的上線,另一是充分利用當天會場的空間,造成困擾,請多見諒。

編譯與執行上述範例程式的基本需求,以 Debian/Ubuntu 為例:
  • apt-get install build-essential
    很顯然,我們需要編譯 C/C++ 應用程式
  • apt-get install libgtk2.0-dev
    主題是 Gtk+,當然要安裝其對應的開發套件
  • apt-get install libwebkit-dev
    依據 debian 描述,該套件為 "Web content engine library for Gtk+",注意,Qt 4.4 之後,也切割了 libqt4-webkit,表示 WebKit module for Qt,不要弄混淆了
  • apt-get install gstreamer0.10-x gstreamer0.10-tools gstreamer0.10-ffmpeg gstreamer0.10-plugins-base gstreamer0.10-plugins-good gnome-media
    這是運作與開發 GStreamer 應用程式所需的套件,預先被妥善地切割,所以稍微多了些,不礙事,反正當天會講解
即便是歐美,其實以 Gtk+ 為主軸的教育訓練也相當少,不若 Qt 背後有 Trolltech/Nokia 商業力量撐腰,這也讓小弟在構思這系列的教育訓練時,著實推敲頗久。希望能先試著在「初體驗」課程中,分享過去開發 Gtk+ 為基礎應用程式或系統設計的經驗,並收集與會者的意見,再將後續更完整的內容推出。當然,仍是保持免費、開放參加的形式來進行,期待您的指教,謝謝!
由 jserv 發表於 04:26 PM | 迴響 (10)

July 20, 2008

GNU Bison 與 GPL 授權的適用範疇


探討標題前,先看看一個答問。本月初在 ptt 的 LinuxDev 看板瞥見討論 GPL 授權效力的議題時,TroyLee 問及:
    「如果我使用到 G++ STL,有必要特別加入 GPL 的宣告嗎?」
要回答這個議題,關鍵之處在於,即使 gcc-4.3 以後 (含) 全面從 GNU GPLv2 移轉到 GPLv3,但其中若干軟體元件或組成,並非依據 GPLv3 授權發行,相反地,可能仍是 GPLv2 或「GPLv2 + 例外條款」,甚至為 public domain 的形式,完全視適用範疇而定,這點需要特別注意。而 TroyLee 的提問,就觸及 gcc/g++ 中,C++ STL (Standard Template Library) 的「適用性」,其中一個背景知識為,STL 在許多 C++ 編譯器系統的實做,是以 header file 的形式存在。以筆者系統中的 gcc 4.3.2 來說 (整個 gcc 4.3 branch 也是如此),安裝於 GNU/Debian Linux 的 header file /usr/include/c++/4.3.2/iostream 即給予明確的聲明,以下摘錄授權宣告部份:
// Standard iostream objects -*- C++ -*-
                                                                                
// Copyright (C) 1997, 1998, 1999, 2001, 2002, 2005
// Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option)
// any later version.
[... 略,談及 GPL 與 FSF 的聯絡資訊 ...]

// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
從上,我們可得知:
  • GNU GCC 的開發者簽署 Copyright Assignment,將著作權歸屬一致給予 FSF,且 STL 實做的著作權聲明標示從 1997 年到 2005 年
  • g++ 的 STL 實做以 GNU GPLv2 with exceptions 模式發行
  • GPLv2 的額外例外條款描述了使用 STL 的合理範疇,規範編譯、連結,與執行的行為,避免原本 GPLv2 對此語焉不詳的模糊
所以,這澄清了我們的疑慮,基本上只用 g++/STL 開發應用程式,儘管有 inclusion 的動作,但由於 header 已有例外條款,允許不受 GPL 感染,因此不特別釋出這部份的程式碼,是被允許的。關於 C++ template 與潛在的授權議題,可參考筆者之前的文章 [LGPL 與 C++ Template Library]。

誠如 [教堂與市集] 所指出,多數商業化軟體所採用「教堂」般集中且單一開發的模式;而自由軟體則普遍採用「市集」般分散且多元的模式,g++/STL 的例子告訴我們,即使 gcc 4.3 整體採取 GNU GPLv3 授權條款,但個別組成與適用對象,值得我們思量,畢竟擁有二十年歷史的 gcc,早已摻入太多本質各異的元素,類似的情況也發生於 gcc 中 Fortran, Ada, Objective-C 等 runtime 中。

GNU [bison] 與 gcc 類似,歸屬於編譯系統的範疇,嚴格來說 GNU bison 與 Yacc 一樣,是 "compiler compiler",也就是建構 compiler 所需的 compiler,對於電腦科學系的學生來說,應該是再熟悉不過了。bison 與 gcc 都算是 FSF 的 Richard Stallman 的心血,至於為何命名如此,端看 "bison" 的英文即可窺知一二:歐陸噸位最大的動物是「歐洲野牛」,其學名就是 Bison bonasus,自然與 gnu (牛羚) 能詼諧地對比。既然 GNU bison 是 "compiler compiler",用途就是自動生成語法分析器,而其輸出是「分析器的程式碼」,可減少不必要的人工介入。進一步來說,把 LALR 形式的 context-free 語法描述,轉換為可做語法分析的 C 或 C++ 程式。

GNU bison 的應用很廣泛,基本上需要複雜的語法分析的環境,都能見到其蹤跡,不只限定於 compiler,像是應用程式的系統配置模組、網頁瀏覽器的引擎 (如 WebKit),甚至是作業系統核心,都不乏仰賴 bison。既然是 GNU/FSF 出品的專案,採用 GNU GPL 發行就是理所當然,就 bison 2.3 版來說 (2006-06-05,截至目前為止的最新版本),授權是 GPLv2。行文至此,或許讀者還是一頭霧水,這樣設計良好的工具,到底有什麼授權疑慮呢?先回到之前 C++/STL 的例子,技術面來看,C++ template 在編譯器的實做上,其實就是依據一定的規則,將 template 本體予以「展開」並轉譯成對應的機械碼,何以 g++/STL 要特別標注例外條款,就是避免採用其 STL 實做的 C++ 應用程式,因為「被迫」接收了 g++ 所「展開」出來、以 GNU GPL 發行的 template 程式碼,導致該應用程式本體「感染」為 GPL 授權的程式,這就違反了 FSF 四大自由的首項:「執行的自由」,開發者使用這些編譯器來編譯非自由軟體並執行的「自由」也該被考慮。

是此,GNU gcc 已在 g++/STL 的 header file 標注例外條款,而這情況也發生於 GNU bison,是的,就是因為 bison 的輸出是「分析器的程式碼」,後者的邏輯與實做,是仰賴原本以 GNU GPL 授權發行的 bison 程式碼。於是乎,若我們讓 bison 輸出的程式碼「整合」到原本的專案,比方說 [WebKit],原本預期以 LGPL + BSD 授權來發行這個網頁瀏覽器引擎,但一旦編譯連結後,GPL 的程式碼就如病毒一般,感染整個專案,讓授權條款沒有其他選擇,只能乖乖地採用 GPL,這實在造成很多不必要的困擾。所以,在 GNU bison 2.2 版開始,FSF 加入了一項例外條款,以釐清上述疑慮。以下摘錄 WebKit 編譯過程中 bison 的輸出 ( generated_sources/kjs/grammar.cpp ) 檔案授權宣告:
/* Skeleton implementation for Bison's Yacc-like parsers in C

   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
   Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.  (...省略,同樣是 FSF 聯絡資訊) */

/* As a special exception, you may create a larger work that contains
   part or all of the Bison parser skeleton and distribute that work
   under terms of your choice, so long as that work isn't itself a
   parser generator using the skeleton or a modified version thereof
   as a parser skeleton.  Alternatively, if you modify or redistribute
   the parser skeleton itself, you may (at your option) remove this
   special exception, which will cause the skeleton and the resulting
   Bison output files to be licensed under the GNU General Public
   License without this special exception.

   This special exception was added by the Free Software Foundation in
   version 2.2 of Bison.  */
由上,我們可知:
  • GNU bison 輸出的分析器程式碼,就是貨真價實的 GPL'd 程式,所以會隨著編譯環境 (build system) 整合到我們的專案中,這是顯性的 GPL 感染
  • FSF 提出的例外條款聲明,只要不修改 GNU bison 輸出的程式碼 (術語為 "Bison parser skeleton"),單純就當作中間輸出碼一般處理,那麼,可依據任何偏好的授權去套用於我們的專案,當然,包含商業化軟體
  • 但若修改或重新散佈 Bison parser skeleton 時,FSF 也允許移除前一條的例外條款,使其成為純粹以 GNU GPL 發行的程式碼
這就是自由軟體迷人之處,不僅有攤在陽光下,允許眾人檢視、批評,與不斷革新變更的技術,還有圍繞在技術外,作為貫徹自由軟體原則的授權條款,我們得顧及適用範疇,也得思考各式各樣的使用情境,正如本文點出 g++/STL 與 GNU bison 的案例一般。
由 jserv 發表於 04:13 AM | 迴響 (0)

July 19, 2008

演講:我是軟體 -- 那些處理器教我的事 (COSCUP)


下個月將受邀到 [COSCUP 2008] 研討會,發表自由軟體在嵌入式應用的題材,時間是在八月 23 與 24 其中一日,題目命名為「我是軟體 -- 那些處理器教我的事」,副標題則是「以 ARM/Linux 為例,探討嵌入式系統開發所面臨軟體移植的考量點」。

過去,嵌入式系統一直給人高深難以捉摸的神秘感,儘管與我們的生活習習關關。近年來相關產品有如雨後春筍問世,其範疇更是五花八門,光是硬體處理器的種類就好幾打,遑論其中的軟體設計。引入自由軟體,有助於提昇產品開發時程與系統完整度,但涉及跨平台開發,其潛藏的問題卻總是令工程人員難以安寢的未爆彈,本議程以 ARM/Linux 為例,嘗試整理在跨平台整合開發時,軟體移植的若干考量點,期望有助於自由軟體的廣泛應用與深入琢磨。

進行的方式預期直接丟出問題,讓與會者思考真相,透過上圖女子的自白道出真心話:
    「我好天真,現在才看清,其實這一切並非只是 cross compilation 這麼單純」
具體來說,我們會面臨到 ARM 的 alignment, ABI, packed data, char signed, soft/hard fp, data abort exception 等議題。而自由軟體雖然如 jjhou 所言:「源碼之前了無秘密」,但往往僅在主流平台如 x86 與 PowerPC 上開發測試,要運用於 ARM 或 MIPS 一類的嵌入式系統大宗的微處理器時,需要考量硬體的差異、軟體規劃的歧異性,再者就是整體設計的考量。是此,小弟就希望,本議題並不著墨個別細節,而是點出跨平台開發的盲點,ARM/Linux 在許多層面都存有我們難以察覺的特徵,咱們就來看看五花八門的「處理器」到底教了「軟體」哪些事情?

最後,廣告一下。COSCUP 今年是第三年舉辦,望能讓更多開放原始碼的同好參與,進而相互激盪出更多的創作或應用,期待您的共襄盛舉!另外,小弟也預計在 COSCUP 2008 上,發表新的迷你 RTOS,未來將藉由這個具體而微的系統,作為探討 ARM 系統設計之範例,也會有對應的教育訓練課程。
由 jserv 發表於 06:10 PM | 迴響 (1)

July 18, 2008

《Qtopia 编程之道》即將出版

來自大陸網友 [苗忠良] 的大作《Qtopia 编程之道》,依據作者表示,預計於今年的九月份可望出版,在這之前,苗兄完成了一份同名的電子書,可在 CSDN 下載取得,這裡作一份複製:[qtopia-book.pdf] (3.4 Mb)。該介紹在 Embedded Linux 環境下 Qtopia 整合開發的背景知識,提及開發環境的建構與使用、Qtopia 的應用開發與 Qtopia 內的 design patterns 等範疇,值得一看的中文好書 (當然,簡體字)。

電子書的版本 (版次為 0.5,作於 2007 年底) 中,目錄大致如下:
  • Linux 基礎
  • 移動終端基礎
  • Qtopia 基礎
  • Qtopia 核心技術
  • 編譯與配置
  • C/S 模型
  • 風格與主題
  • 安裝與整合
  • 人機互動
  • Qtopia 設計
  • 書籍簡介
  • 參考文獻
而正式書籍問世的這十個月,據作者的說法,也多作更新與修訂,頗令人期待。
由 jserv 發表於 02:43 PM | 迴響 (2)

透過虛擬化技術體驗 kgdb (1)

延續去年 [深入淺出 Hello World - Part III] 的內容,打算於今年分享 Part IV,秉持 "Everything can be hacked" 的原則,正式要從 user-space 的 "Hello World" 應用程式,一口氣鑽進 Linux Kernel 的「黑盒子」,並揭開其中的奧秘。當然,在這之前,我們需要良好的工具,稍早在 [快快樂樂學 GNU Debugger] 的教育訓練提過 gdb 的重要概念與用法,而對於 kernel,則需要 [kgdb]。過去,Linux + kgdb 的繁複過程讓人望之卻步,我們只能眼巴巴看著 BSD kernel hacker 玩弄著 bsd kgdb,游刃於巧妙的指令與系統變化間,如今,Linux 2.6.26 就內建了延宕多年的 kgdb,且讓我們試著操作。

今年五月 3 日,Linus Torvalds 宣佈 [釋出 2.6.26-rc1] 並關閉 Linux-2.6.26 的 merge window,除了大量的驅動程式更新外,首次對主要的架構引入了 kgdb,作為內建的 source-level debugger,當然,稍早在 linux-2.6.25 時,其實像是 ARM 架構就加入 kgdb,但這次是連同 x86, powerpc/ppc, sparc, sparc64, sh, blackfin 等硬體架構都納入支援。此舉終結了長達一年的 kgdb merge 討論,這過程中,Ingo Molnar 甚至改寫原本由 LinSysSoft Technologies 所維護的 [kgdb],而建立名為 "kgdb light" git tree 分支以精簡 kgdb 的設計並導入新版核心中,最後,Linux-2.6.26 終於正式整合 kgdb,並提供主流硬體架構的支援,Linus Torvalds 本人對此表示:
    "because people have tried to get me to merge it for some long is kgdb support. Which really turned out pretty small and clean, once people started putting their effort into making it so."
我們需要取得 Linux kernel 2.6.26 (已於 Jul 13, 2008 釋出),而在 x86 平台上,全然不需要施加 patch,大致操作如下:
$ tar jxvf linux-2.6.26.tar.bz2
$ cd linux-2.6.26
剩下就是 make menuconfig 即可,過去的繁瑣動作,全都免了!需要留意的是 kernel configuration 的方式,關鍵部份如下圖:

也就是 Kernel hacking -> Kernel debugging -> Compile the kernel...,注意,需要同時讓 "Compile the kernel with debug info" 與 "Compile the kernel with frame pointers" 生效,如下圖:

這樣,kgdb 的項目才會出現:

確認 kgdb 正確被編譯到核心的方式,為檢查 .config 的配置項目,應該要包含以下:
  • CONFIG_DEBUG_INFO=y
  • CONFIG_KGDB=y
  • CONFIG_KGDB_SERIAL_CONSOLE=y
透過 kvm / qemu 與 Xen,我們可很輕鬆地以虛擬化技術,在單機上操控 kgdb,而不必像過去得準備兩台以上的電腦,並需要張羅網路與 RS-232 線路傳輸,本文中,筆者將探討 kvm / qemu + kgdb,至於 Xen + kgdb,其實也相當類似,只是核心組態與啟動方式不同罷了。比照前文 [深入理解 Linux 2.6 的 initramfs 機制 (上)],我們可透過 initramfs + busybox 弄個輕巧的 kernel image,筆者的參考組態為 [.config],預先編入 qemu 系統模擬所需的 device drivers,若看倌的系統跟筆者一樣用 IA32,在設定檔中的 source path 也可直接指向筆者準備的 [busybox-initramfs-dist.tar.bz2 ] 壓縮檔所解開的目錄。將 initramfs 的 source path 略作修改後,即可建構核心:
$ make bzImage
建構好核心後,是否躍躍欲試呢?不,必須考慮到 kgdb 的「非本地端」偵錯、除錯本質,要作些額外準備。首先,來看看整個系統示意:

由圖可見,我們將會邏輯上,區分兩個硬體系統,其中目標端、待除錯分析的那台自然稱為 Target,相對來說,提供開發環境的系統,就是 Host,這兩台系統透過 RS-232 (serial) 或 Ethernet 來通訊。不過,透過 Xen/qemu,我們可在單一實體環境中,模擬出這兩台。當我們透過 GNU Debugger 時,系統示意如下圖:

當我們要對 target program (可能是 kernel 本身,或者單一的 process) 作除錯分析時,需透過特製的額外小程式,稱為 GDB stub,連透過 RS-232 / Ethernet 建立 GDB Remote Debugging Protocol 上的通訊,如此一來,Host 上才可透過 GNU Debugger 來遠端除錯。依據此等情境,kgdb 可說是涵蓋了 GDB stub 與 Linux kernel 銜接的部份,讓執行於 Target 端的 kernel,得以透過 GDB Protocol 來除錯。

既然用了 kgdb,當然要體驗強悍的 source-level debugging,所以,我們也應該將網路環境預先設定好,以讓 target-host 能便利分享資源。kvm / qemu 都支援 TAP/TUN 的網路連結模式,其示意圖如下:

qemu 中模擬的 guest OS 可透過 TAP/TUN 所建立的通道,經由虛擬的 NIC (網路裝置),輾轉透過實體網路進行通訊。vtun 則是比較特別的變形,採用 user-space socket 重新導向 TCP/IP,所以,筆者就設想以下情境:

也就是將虛擬與實體網路裝置一併建立橋接 (bridge),這樣,無論在 Target 抑或 Host 端,都能夠對外連線,詳情可參閱 [Host-only networking (or high-speed net-connection to host)] 一文的組態設定,在 Debian/Ubuntu 上,我們需要先安裝以下套件:
$ sudo apt-get install bridge-utils uml-utilities vtun
並且為了簡化後續操作,建立一個專屬的設定 script,名稱為 "/etc/init.d/kvm-network",以下是其內容:
#!/bin/bash-x

# id of the user running qemu (kvm). Make sure you change it appropriately.
USERID=1000

# number of TUN/TAP devices to setup
NUM_OF_DEVICES=1

case $1 in
start)
        modprobe tun
        /etc/init.d/vtun start
        chmod a+rw /dev/net/tun
        echo -n "Setting up bridge device br0"
        brctl addbr br0
        ifconfig br0 192.168.99.1 netmask 255.255.255.0 up
        for ((i=0; i < NUM_OF_DEVICES ; i++)); do
                echo -n "Setting up "
                tunctl -b -u $USERID -t qtap$i
                brctl addif br0 qtap$i
                ifconfig qtap$i up 0.0.0.0 promisc
        done
        ;;
stop)
        for ((i=0; i < NUM_OF_DEVICES ; i++)); do
                ifconfig qtap$i down
                brctl delif br0 qtap$i
                tunctl -d qtap$i
        done
        ifconfig br0 down
        brctl delbr br0
        /etc/init.d/vtun stop
        ;;
*)
        echo "Usage: $(basename $0) (start|stop)"
        ;;
esac
需注意的是,以上的 "USERID" 值要正確設定,端視現在的 user ID 而定,若採用 Ubuntu 預設安裝,應該都是 "1000",表示第一位系統使用者。編輯此檔後,作 chmod +x 的動作,然後以 root 身份啟動 TAP/VTUN/Bridge 的服務:
# /etc/init.d/kvm-network stop
# /etc/init.d/kvm-network start
也應依據實際網路狀態,避開 192.168.99.1/24 的網段,這要保留給 vtun/bridge 所用。準備就緒後,我們就可試著啟動剛剛編譯出的 kernel image,指令如下:
qemu -kernel arch/i386/boot/bzImage \
     -hda /dev/zero \
     -net nic -net tap,ifname=qtap0,script=no
其中 "-net tap,ifname=qtap0" 就是指定 qemu 透過 TAP/TUN 的模式存取網路,其執行畫面如下圖:

當 qemu 裡面的 guest OS (當然就是 Linux + initramfs) 啟動完畢後,需要在 qemu 模擬的環境中執行網路配置:
# ifconfig eth0 192.168.99.2/24
# route add default gw 192.168.99.1 eth0
# ping -c 5 192.168.99.1
圖中可見,筆者為確保網路可正確運作,以 ping 查驗虛擬與實體網路裝置間的橋接 (bridge),也該從 Host 端反過來 ping 192.168.99.2 (Target 端的 IP)。下一步就是多傳遞啟動 kgdb 所需的參數,如下:
$ qemu \
	-no-acpi \
	-kernel arch/i386/boot/bzImage -hda /dev/zero \
	-net nic \
	-net tap,ifname=qtap0,script=no \
	-serial "stdio" -serial "pty" \
	-append '$console kgdbwait kgdboc=ttyS1'
注意到上述指令列多了一行:
	-serial "stdio" -serial "pty"
執行畫面大致如下:

透過 -serial 時,qemu 會輸出其(被)重導的裝置,就如上圖出現的 "/dev/ttyp9" (以紅線標注),咱們看看 qemu 線上說明:
    -serial dev

    Redirect the virtual serial port to host character device dev. The default device is "vc" in graphical mode and "stdio" in non graphical mode. This option can be used several times to simulate up to 4 serials ports.
所以前述的指令就是將 (被模擬的硬體) 文字模式的 stdio 予以導向到本機 (執行 qemu 的環境) 上的 pty (p = pseduo,虛擬之意),也就是 "/dev/ttyp?",而稍早也提過系統示意的遠端除錯裡頭,建立連線的動作,即透過 serial 或 Ethernet,這裡的 pty 就視為一種 serial 介面,所以,只要 kgdb 生效後,就可依據此介面來讓 host 端進行除錯。有了這概念後,自然對啟動 qemu 的另一行指令不感到訝異:
	-append '$console kgdbwait kgdboc=ttyS1'
這行就是告訴 kgdb,欲開啟的 serial 裝置為 ttyS1,並要求完成必要的初始化後,等待來自 host 端的 gdb 連線,所以,我們可在上圖看到系統執行後,印出以下訊息並等待:
kgdb: Registered I/O driver kgdboc...
kgdb: Waiting for connection from remote gdb...
看來一切就緒,就待我們從 host 端來作遠端除錯了。建議開啟新的終端機,並切換到編譯核心的 linux-2.6.26 目錄下,我們要載入包含 debug info 的 Linux kernel image 是 "vmlinux",而非壓縮過的 "arch/x86/boot/bzImage" (qemu 內建 kernel loader,所以要提供已壓縮過的 kernel image),很容易從檔案長度看出彼此的差異:
$ ls -lh arch/x86/boot/bzImage vmlinux
-rw-r--r-- 1 jserv jserv 1.6M 2008-06-02 01:37 arch/x86/boot/bzImage
-rwxr-xr-x 1 jserv jserv  21M 2008-06-02 01:37 vmlinux
載入 gdb 的方式如下:
$ gdb ./vmlinux
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
[...]
This GDB was configured as "i486-linux-gnu"...
(gdb) shell echo -e "\003" > /dev/ttyp9
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyp9
Remote debugging using /dev/ttyp9
kgdb_register_io_module (new_kgdb_io_ops=0xc028f914) at kernel/kgdb.c:1677
1677		wmb(); /* Sync point after breakpoint */
(gdb) 
其中粗體字表示將由我們鍵入的指令 (以下皆以此標示),首先是要求 gdb 載入包含除錯資訊的 "vmlinux",再來,測試 serial 連線是否通暢,即以輸入 ASCII 0x03 字元處理、設定 baud rate 為 115200 bps,不過,這對於 pty 來說,總是會成功,但真實情況下就難說了,在此仍列入模擬的情境。最後一個指令就是重點:
(gdb) target remote /dev/ttyp9
這要求 host gdb 經由 serial 送達遠端除錯的指令,這時候,原本暫停執行的 target/kgdb 就會依據 remote gdb 通訊協定 (即 GDB Remote Serial Protocol,縮寫為 RSP) 之中的指令,做出因應的動作,而由上可知,就是取得 breakpoint 的資訊,當然,這裡不細談其具體動作。我們可查看目前的程式行為:
(gdb) list
1672	void kgdb_breakpoint(void)
1673	{
1674		atomic_set(&kgdb_setting_breakpoint, 1);
1675		wmb(); /* Sync point before breakpoint */
1676		arch_kgdb_breakpoint();
1677		wmb(); /* Sync point after breakpoint */
1678		atomic_set(&kgdb_setting_breakpoint, 0);
1679	}
1680	EXPORT_SYMBOL_GPL(kgdb_breakpoint);
1681	
(gdb)
由 gdb 列出的程式碼即 kgdb 設定 breakpoint 的行為,我們可單步執行看看:
(gdb) n
[New Thread 1]
1678		atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) n
1632	}
至此,我們執行過 kgdb_breakpoint() 函式。透過 kgdb 得以窺見運作中 Linux kernel 的每一個面向,筆者挑選一個自 0.0.1 版的 Linux 即有的變數 "jiffies" 作為觀察。jiffies 為 Linux 的核心變數,長度為 32 位元 (unsigned long),用以紀錄系統自開幾以來,已過多少的 tick 數量。當系統觸發一次 timer interrupt 時,jiffies 變數值即會遞增一。需要留意的是,系統開機時,jiffies 變數值並非為零,而是一個特定的負數值 (稍後再詳述),而在 x86 架構下,另行定義一個與 jiffies 相關的變數為 "jiffies_64",顧名思義,此變數為 64 位元。以下就來觀察這兩個變數的值:
(gdb) p jiffies
$1 = -74817
(gdb) p jiffies_64
$2 = 4294892479
(gdb)
(待續)
由 jserv 發表於 01:23 AM | 迴響 (8)

July 17, 2008

上衣口袋夾帶原子筆的故事

四月份受邀去 OSDC.tw 分享 [許我們一個 Keroro 的桌面] 議程,那時隱約聽見 [cclien] 問及為何上衣口袋放兩隻筆,因為忙著張羅設備,就沒有立即回應,而剛整理個人目錄時,瞥見下圖,想起那個問題:

如圖,可瞥見上衣口袋的兩隻原子筆,故事的由來是,高中常跑去找教官或總務處,負責班級或學生宿舍的業務,隨身攜帶原子筆,理所當然就成為好習慣,不過認真貫徹,是上了大學後。大學對我來說,美其名是在追求深入的學問,不如說是一種社會身份,掩護我作些當時覺得值得作的外務。於是,接了若干案子、設計軟體的架構、與客戶斡旋等等,免不了要面對面接觸,印象深刻的一次在大學一年級下學期,需要到金主那邊解說自己所設計的系統並實體展示,壓力甚大,儘管前一晚已充分準備,但仍忐忑不安。

蹺課 (才大一就已習慣到沒有罪惡感) 搭乘電車與計程車到指定的地點,在高雄烈陽的曝曬,頓時忘了該如何進行,而過去的努力就將付諸流水之際,握著口袋中那隻原子筆,隱約感受到劇烈的心跳,緩緩吸一口氣,竟然就慢慢浮現想法,雖不甚順暢,但差強人意的完成四十分鐘的簡報,展示時似乎得到頗正面的回應,就這樣拿到一個案子。爾後,購買原子筆的機率變成頗頻繁,夾在上衣口袋使用,或因業務所需,或遊覽參觀,或外出尋求靈感,甚至到咖啡店作閱讀紀錄等,都保持這樣的習慣,特別在作演講或簡報時,不免會下意識伸手搆上衣口袋的原子筆,似乎除了隨手紀錄用途外,還多了一份安定的作用。

真要探討,或許日常許多習慣背後都有故事,而觀察習慣的變遷,或許也意味著成長的故事。
由 jserv 發表於 10:58 PM | 迴響 (3)

July 15, 2008

「快快樂樂學 GNU Debugger (gdb) Part I + II」簡報上線

上週六,應 [酷!學園] 之邀,至台中的亞洲大學,給予以 [快快樂樂學 GNU Debugger (gdb)] 為題的演講,這次探討 Part I (概念與初體驗) 與 Part II (實務與應用),預計於 Part III (GDB 技巧與整合性技術) 時為本系列演講劃下句點。演講的簡報與範例檔案已上線,可參閱: 簡報檔案以 Creative Commons (CC)-SA (Attribution-ShareAlike) 的方式釋出,承蒙前來指教的朋友提供寶貴意見,未來將在 remote debugging 與分析偵錯的技巧部份多作著墨,歡迎指教,謝謝!
由 jserv 發表於 01:13 PM | 迴響 (6)

July 04, 2008

不再囉唆:NetBSD 簡化 BSD 授權條款

儘管絕大多數的開發者都知曉 BSD 授權條款較為寬鬆,沒有 GNU GPL 本質上 "copyleft" 的「病毒式」感染性,但實務應用仍有值得推敲之處。NetBSD 基金會近日甚至宣佈,進一步簡化其使用多年的 BSD 授權條款,相較於繁瑣的 GNU GPLv3,NetBSD 此舉可說追趕 FreeBSD 與 OpenBSD,樹立 BSD 授權的「自由派」訴求。

BSD License 為 "Berkeley Software Distribution license" 的縮寫,顧名思義,源於加州柏克萊大學的 BSD 一系列的軟體,最早的著作權人為 Regents of the University of California'(加州大學董事會),BSD 授權條款本身相當清楚,最初的版本有四個條款,也就是規範被授權人的條件限制,如下:
  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors.
  • Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
相較於冗長的 GNU GPL,上述四個條款相當清晰,簡單來說就是:
  • 規範以原始程式碼形式的再散播,需保有同樣的授權聲明
  • 要求二進位可執行形式的再散播,需在檔案或所屬的媒介中,附上授權、免責條款等資訊
  • 軟體本身應提供如廣告般的機制,附帶提及告知研發軟體的加州大學
  • 未獲得許可前,禁止以原作者或加州大學之名,行衍生軟體之背書、推廣、促銷等行為
至於授權條款被提出的時空背景,很值得思量。最早的 UNIX 緣起於 1970 年代的 Bell Labs,1974 年起,UNIX 被安裝至加州柏克萊大學的 PDP-11 電腦上,引來研究人員的高度興趣,並提供若干功能的補強與修正。1977 年,柏克萊大學的研究生 Bill Joy 整理這些師生的貢獻、彙整新的程式等,存放到磁帶上,作為 first Berkeley Software Distribution (1BSD) 發行,當然,這裡的 "Distribution" 意義非常明確,就是將磁帶經過人工搬移、複製存取的動作。BSD 見證了 Bill Joy 那個時代 hacker 的風采,至 3BSD 時,已是大幅重寫而具備嶄新作業系統的特徵,諸如虛擬記憶體。儘管「青出於藍而勝於藍」,但 BSD 源自 AT&T UNIX 的事實卻無法抹滅,甚至在 4BSD 後的版本不能依據慣例發佈為 "5BSD",而以 4.1BSD, 4.2BSD, 4.3BSD 這樣於後方加入小數點與版號的方式發佈,而非原本依據發佈版次 (1st, 2nd, 3rd, 4th) 的模式,就是為避免與 AT&T 的 UNIX System V 混淆而生侵權議題。

隨著 BSD 的成熟,人們意識到摻雜 AT&T UNIX 的程式碼,意味著高價的授權 (AT&T License) 與使用上的限制,沒辦法透過當時開始發展的網路 (Ethernet) 作廣泛的散播,是此,在許多貢獻者的投入下,1989 年六月,Networking Release 1 (Net/1) 誕生了,不需 AT&T 授權也使用,當時一個劃時代的變革就是將上述四個條款的 BSD 授權聲明含入,只要在這個新的遊戲規則下,都可自由再發佈。而我們也可從這原始的四項被授權人的條件限制中,看出對於原始程式與二進位執行檔的散播形式 (需保有授權與免責聲明)、於程式本體提及 Net/1 研發背後的加州大學、禁止以貢獻者或加州大學之名,行衍生軟體之背書、推廣、促銷等行為,意思就是為這嶄新的系統,制定得以自由使用的規則。而到了 1991 年六月,Net/2 已幾近全新的作業系統,不含 AT&T 的程式碼,而 Net/2 在 Intel 80386 硬體架構的移植,由 William Jolitz 負責下進行,也被稱為 386BSD,後來,由握有 System V 版權、Unix 商標的 UNIX Systems Laboratories (USL,為 AT&T 附屬子公司) 對 Net/2 在 80386 硬體的封閉專屬 BSD/OS 背後研發的 Berkeley Software Design (BSDi) 公司,提出法律訴訟,延宕了多年,直到 1994 年一月份才落幕,並引來 BSD 原始程式碼重整的動作,顯然,對系出同門的 386BSD 來說,不免也受到影響,而無法在法律爭端休止前,作自由的開發與使用。

也因此,386BSD 無疑是短命的作業系統,但卻奠定 NetBSD 與 FreeBSD 的基礎,同時,這兩個專案也繼承 BSD 授權條款,作為發行的規則,而,BSD 授權也因其簡單明確,帶來廣泛的成功,許多非加州大學開發的軟體也紛紛採用這個授權,將著作歸屬人的加州大學,改為自己從屬的單位,就透過網路或等價媒介作廣泛散播。不過,GNU/FSF 發現 BSD 授權的問題,導致無法與 GNU GPL 保持相容,從而將原本四個條款的 BSD 授權之中的一項,稱為 [obnoxious BSD advertising clause],也就是上述的第三條:
    "All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors."
英文 "obnoxious" 一詞即「令人不舒服」的意思, 我們可見到,當貢獻者數量劇增時,這樣帶有廣告性聲明貢獻者的條款,會使得軟體的檔案變得很冗長,再者,也是最重要的,這與不允許增加額外限制 (強制性廣告本質上就是新的限制) 的 GNU GPL 設計相牴觸,所以,也無法與 GNU GPL 授權條款所相容,帶來諸多遺憾,詳細描述見 [The BSD License Problem]。經過 Richard Stallman 等人的呼籲和陳情,加州柏克萊分校於 1999 年,做出正式回應,刪去原始 BSD 授權條款的第三條「廣告條款」,是此,原本的授權許可被稱為 "old-BSD" 或 "4-clause BSD",而相對新的 BSD 授權條款自然就稱為 "new-BSD"、"revised-BSD",或者 "3-clause BSD" 授權了,關於這樣的 BSD 授權樣本,可參見 Open Source Initiative (OSI) 的 [The BSD License:Licensing]。

在 2008 年六月上旬,我們可看到自由的 BSD 家族,採用的 BSD 授權條款如下:
  • NetBSD 仍使用原始的 BSD 授權條款 (4-clause)
  • FreeBSD 使用兩句版授權條款 (2-clause),另行於末尾添加非 FreeBSD 基金會的貢獻者列表
  • DragonFlyBSD 使用 3-clause/new- BSD 授權條款,與 FreeBSD 4 分家前同
  • OpenBSD 對所有添加的軟體,採用 Internet Systems Consortium, Inc 的版權條款,就其功能 來說,也就是 2-clause BSD 授權
這裡的 2-clause BSD license 是將「未獲得許可前,禁止以原作者或加州大學之名,行衍生軟體之背書、推廣、促銷等行為」這一條也刪去:(old-BSD 的第四條、new-BSD 的第三條)
    "Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission."
如此一來,2-clause BSD license 幾乎等價於 MIT X license,現由 FreeBSD 與 OpenBSD 所導入。而,在六月 20 日,NetBSD 基金會主席 Alistair Crooks 宣佈道 [The NetBSD Foundation Moves to a Two Clause BSD License],宣示 NetBSD 建議的授權條款,也將改用 2-clause BSD 授權許可,同時,在聲明中也提及現有軟體的授權變遷狀況。我們可見,Alistair Crooks 提到更改授權的考量點:
  • we have seen organisations and people concerned about the old clause 3 (the advertising clause) in the license, to the extent where NetBSD code could not be used in commercial products; the new license means that these concerns are no longer valid
  • UCB moved some time ago to remove clause 3 from the code contributed to UCB; this change mirrors that one
  • we have seen some instances where clause 3 was ignored by groups and organisations
  • the members of the NetBSD Foundation (i.e. its developers) no longer considered clause 4 (the "endorsement" clause) to be useful in today's software world
所以,從善如流,不再囉唆!NetBSD 簡化 BSD 授權條款的動作,幾乎意味著 BSD 世界的授權一致性,剩下 DragonFly BSD 採用 3-clause BSD license。不過,需要留意的是,其他採用 BSD-style 的 free / open source software,並未立即更新,比方說我們可在 gslin 的 [BSD 4-clause license 的問題] 一文,見到相當有名的 GPL-incompatible 案例:[OpenSSL License],與新 Gentoo/FreeBSD 裡頭 libkvm 的授權相容性議題。

大環境來看,現在 copyleft 的代表為 GNU GPLv3,而自由派 (也稱為 copycenter) 的代表幾乎就是 2-clause BSD license,兩者的分野就更鮮明了。另外,[hubertf] 提到 GPL 與 BSDL 改版的字數比較圖,頗有意思:

GNU GPL 就好像法律條文,冗長令人生懼,但捍衛自由,又不得不如此;BSD License 則像女人的迷你裙,越短越受歡迎。
由 jserv 發表於 11:53 AM | 迴響 (2)

July 03, 2008

取得 GNU/Linux 行程的執行檔路徑

本文試著探討 GNU/Linux 於執行時期 (run-time) 的行程 (Process) 如何取得執行檔路徑,並探討 /proc/self/exe 的機制與其應用。

進入主題前,我們該來思考本文標題:
    「取得 Linux 行程的執行檔路徑,有什麼好處?在什麼場合需要?」
這個問題最好的答案,就是看看真實需求。筆者七年前曾撰寫過一篇短文 [親手打造 Floppy Linux 環境],在談及 GNU/Linux 剪裁的過程中,提到 [busybox] 得以將若干工具透過 symbolic link 到 /bin/busybox、且能於執行時期正確依據名稱挑選 applet 並執行的原理,就是透過 argv[0],也就是「執行時期的名稱」。具體來說,當我們在 shell 中執行 cp、cat、chown 等指令時,busybox 會將包含「名稱」的 arvg[0] 丟給 run_applet_by_name() 去解析名稱,再去找對應的實做,並儘可能讓各工具程式達到最大的程式碼可重用性,甚至免去動態連結的負擔。

由於該文年代久遠 (注意:筆者認為現在將 Floppy Linux 當作嵌入式系統的實習對象,已不合時宜,畢竟軟硬體進步與需求的幅度變動極大,實在沒必要削足適履,故,該文不予更新,但歡迎在原本的基礎上再行擴充,比方說打造 Disk-On-Chip/Linux 的新主題),busybox 早已經過多次翻修 (當時用 0.6.x 版本,而且贊助此專案的 Lineo 公司也早遭收購),對應的程式碼也經過大幅改寫,所以本文順便更新前文描述的實做部份。busybox 將可重複使用的部份拆到名為 libbb (bb 即 busybox 簡稱,乃為該專案的程式碼命名慣例),其中包含解析 argv[0],以下列出 main() 函式的實做,位於 busybox 1.7+ 原始程式碼的 libbb/appletlib.c 檔案中,簡化的列表如下:(本文的程式碼列表皆為簡化版本)
int main(int argc ATTRIBUTE_UNUSED, char **argv)
{
	applet_name = argv[0];
	if (applet_name[0] == '-')
		applet_name++;
	applet_name = bb_basename(applet_name);

	parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */

	run_applet_and_exit(applet_name, argv);

	/*bb_error_msg_and_die("applet not found"); - sucks in printf */
	full_write2_str(applet_name);
	full_write2_str(": applet not found\n");
	xfunc_die();
}
很顯然,周遊於自訂的函式中,這行 "run_applet_and_exit(applet_name, argv)" 讓我們感到興趣,這就攸關 busybox 的原理,所以咱們看看此函式如何將 argv[0] 作處置,在同一檔案的實做碼如下:
void FAST_FUNC run_applet_and_exit(const char *name, char **argv)
{
	int applet = find_applet_by_name(name);                                 
	if (applet >= 0)
		run_applet_no_and_exit(applet, argv);
	if (!strncmp(name, "busybox", 7))
		exit(busybox_main(argv));
}
所以重點就是 find_applet_by_name() 函式,顧名思義,就是解析的動作,繼續看實做碼:
int FAST_FUNC find_applet_by_name(const char *name)
{
	/* Do a binary search to find the applet entry given the name. */

	const char *p;
	p = bsearch(name, applet_names, ARRAY_SIZE(applet_main), 1, applet_name_compare);
	if (!p)
		return -1;
	return p - applet_names;
}
看到這裡,其實就不必再追下去了,都使出標準函式庫的 binary search 函式,顯然就是從內建的 applet 列表中比對,抓出合適的 applet 實做的索引值,內部勢必有對應的執行機制。所以,以 "ls" 來說,在 busybox 的設計中,無論我們在 shell 下執行 "../bin/ls"、"/bin/ls"、"./ls" 等等,只要能在檔案系統中找到 symbolic link 的 "ls" symbol name 與 busybox 執行檔主體,當執行時,argv[0] 就被賦予絕對或相對路徑的字串,busybox 程式透過以上機制,解析名稱,找到 (精簡的) ls applet 實做並將參數代入執行,而,「取得執行時期的執行檔路徑」對於實做 shell 本身,如 ash, msh 來說,就相當重要,因為在 shell (當然,也是由 busybox 提供實做) 下執行程式,其實就是做了 fork()/vfork() 一類的系統呼叫。

實際上,基於安全性考量、程式碼重用性,busybox 內部實做變得更為複雜,讓我們不禁得想想,argv[0] 的應用有哪些限制?看看 argv[0] 可能的內容會有:
  • 絕對路徑:基本上只要去除多餘的字串表示,如 "//",即可使用
  • 相對路徑:需要配合目前執行程式的路徑,在檔案系統中,找出最終的絕對路徑
  • 只有程式名稱、沒有路徑:因為 shell 透過 $PATH 環境變數找到程式,但 argv[0] 接收時僅有名稱,這時候,得循序依 $PATH 字串內容,逐一於檔案系統中找到程式的絕對路徑
  • 其他:當透過 exec 系列的系統呼叫時,什麼名稱都有可能,端視呼叫的形式,這時候就得找出方法 (沒有一定準則)
提到這,或許我們才驚覺,原來貌似單純的設計,還有以上陷阱,最麻煩的就是最後一種可能性,偏偏又是最常見的,因為 /etc/init.d/* 底下那些 shell script 一開機就啟動可觀的程序了,所以,我們勢必得尋求其他機制。既然要思索執行時期的行為,最好的方式就是參閱 /proc 檔案系統。當透過 ls 列印 /proc 檔案系統時,會發現有許多數字,這些都是執行中行程的 pid (process ID),而 /proc/_pid_/exe 就是指向對應執行檔的 symbolic link。pid = 1 的行程就是第一個 user-space 的程式,也就是 init,我們可觀察一下,先切換成 superuser/root,執行以下指令:
# ls -l /proc/1/exe
lrwxrwxrwx 1 root root 0 2008-07-03 22:10 /proc/1/exe -> /sbin/init
當然,目錄 /proc/1/ 底下有很多檔案可探索,筆者就不詳述了,只要知曉 Linux /proc 如此的設計即可。再者,當一個執行中的行程存取目錄 /proc/self/ 時,其效力等同於 /proc/目前行程的 pid/,這也就是說,"/proc/self/exe" 就是對應到「目前行程對應的執行檔」。看來 /proc/self/exe 似乎可解決前述議題,但注意,這僅適用於應用程式本身,不包含其動態函式庫,對於後者,一個普遍的作法是,查閱 /proc/self/maps 的內容,即可依據位址找尋對應的函式庫名稱,或者,透過 GNU/Linux 專有的 DL_info + dladdr 組合,可取得 DL_info::dli_fname 的值,即可指出到底位於動態函式庫或應用程式主體,熟悉 Win32 API 的朋友,大概就會想到等效的 GetModuleFileName()。

在 UNIX 家族中,也提供對應於 GNU/Linux 的 /proc/self/exe 機制,FreeBSD 上是 "/proc/curproc/file",Solaris 是 "/proc/self/auxv"。咱們來作個小實驗,看看「自我重複執行的行程」會怎麼設計與呈現,以下是簡單的測試程式 (fork-self.c)
#include <sys/types.h>
#include <unistd.h>

#define MSG_1 "child ends.\n"
#define MSG_2 "parent about to fork self.\n"
extern char **environ;
int main(int argc, char *argv[])
{
	char *fn[] = { "/proc/self/exe", 0 };
	pid_t pid = fork();
	if (0 == pid) {
		write(1, MSG_1, sizeof(MSG_1));
	}
	else if (-1 == pid) { /* unable to fork */
		_exit(-1);
	}
	else {
		write(1, MSG_2, sizeof(MSG_2));
		execve(*fn, fn, environ);
	}
	return 0;
}
上述程式碼列表有若干重點,如下:
  • "/proc/self/exe" 與 exec 函式的搭配
  • fork() 後,我們試著 exec "/proc/self/exe",也就是本行程對應的執行檔名,以取代 parent process
  • 由於重複該動作,fork() 可能會失敗,務必作確認與例外情況處理 (_exit)
編譯並執行:
$ gcc -o fork-self fork-self.c
$ ./fork-self
child ends.
parent about to fork self.
child ends.
parent about to fork self.
child ends.
parent about to fork self.
... (重複) ...
這過程中,若打開 top 或 htop 一類的工具查看記憶體,會發現可用的記憶體很快會被消耗,不過別擔心,最後失敗就作 _exit 以離開。在未結束前,用 pstree 觀察看看 fork-self 的呈現:
init-+-NetworkManager---{NetworkManager}
     |-NetworkManagerD
     |-acpid
	...
     |-rxvt-unicode--bash---exe-+-11599*[exe]
     |                          `-fork-self
	...
前述論及 argv[0] 在 exec 系列系統呼叫的限制即在此,上述 pid 是會跳動的,而名稱又貌似一致,不過,Linux 內部會有機制將 /proc/self/exe 找出,是此,取得 GNU/Linux 行程的執行檔路徑,大致如此。不過,我們還是會有疑惑,既然 argv[0] 的限制這麼多、處理複雜又可能不正確,那為何 busybox 經歷多次改版,為何還保留 argv[0] 複雜的處理呢?要注意的是,busybox 可在許多不同硬體的 GNU/Linux 上運作,包含沒有 MMU 的平台,也就是 ucLinux,而,後者 (預設) 沒有 /proc/self/exe 的機制,所以,一般的作法,就是寫死執行檔路徑,當然,這樣的嵌入式系統設計就得相當小心。
由 jserv 發表於 10:53 PM | 迴響 (9)

July 01, 2008

推薦「E-GI建築師事務所實習報告」

最近,放了從求學、職場生涯以來,最長的一次休假。這整整半年的時間內,試著去接觸不同層面的人事物,認識了 egi、閱讀了她豐富的 [E-GI建築師事務所實習報告],從旁探訪她的內心世界,謹此推薦給各位朋友。

有個頗有成就的舅舅任教於台大土木系,小時曾多次耳聞他參與北二高設計與整體規劃的花絮,當時每回都聽得很入迷,但或許覺得自己比較適合敲鍵盤,而不是碰尺規,所以選擇資訊系就讀。大學時代,曾與一位多才多藝且出眾的建築系女生交往,自此交融著對於建築設計的愛好,去建築系館約會時,往往則在這古色古香的建築與多元的設計呈現所傾倒。

事實上,軟體業界的 Architect 這個名詞就是從建築業界借用來的,整個軟體工程的目標更可說是建築設計的抽象呈現:軟體開發如同建築設計,過程中,得針對整體需求、分析、設計、實作、佈署等各項工作流程之不同觀點予以呈現,也就是軟體系統之「塑模」(modeling)。至今,我還記得當時在成大附近的住所,翻閱她帶來 Christopher Alexander 在 1970 年代的大作《A Pattern Language: Town, Building,. Construction》,予我極大的共鳴。

但建築不全然是工程,當談及建築,不免會聯想到「空間感」一詞,是為人們的精神世界鋪就的一條通向終極的理想道路,駐足於此,心境頓時得到永恆的寧靜與祥和,好似,一門之隔卻的有兩個世界。也因此,建築設計是人們內心世界的體驗、感情的記錄,予以具象呈現,偶然在 funP 上瞥見 egi 的文章,按圖索驥尋出其文章的脈絡,不乏詼諧的趣事、情感的轉折與猶豫徘徊,而隱約有著設計人一貫的執著與敏感,或許不顯現於圖文,但存於斯。

從舅舅與前女友身上,大概知曉建築業的生活方式,所以對 egi 的文章偶投以會心一笑與祝福,畢竟這個行業是如此嚴肅又切近我們的生活。其中,最有感觸的文章大概是 [ 230公里的風景] 一文,標題取自於台南到雲林麥寮、花費四個多小時的 230 公里路程。人生際遇往往可用煽情的字句,去誇飾情緒的波瀾壯闊,一如被連續劇養成的感官表現,但平凡中見真情,生命的可貴因此美麗,也因而厚實,我想,egi 的文字就屬於後者,再搭配獨特的圖片更具張力。[ 230公里的風景] 一文取鏡頗耐人尋味,營造出遠離塵囂、反璞歸真之感,又適時以「無政府狀態的張貼模式」凸顯文化與自然環境的對比。很喜歡「波光嶙峋,恰是閑美」前後幾張圖,或許讀者可得以如南台灣普照的陽光一般。

egi 的 [自我介紹] 大概說明了一切:
    喜歡畫畫,但是不是藝術家。
    喜歡發呆,但是腦袋總塞爆想法。
    喜歡,一切。
總之,[E-GI建築師事務所實習報告] 絕對是很值得造訪之處,希望 egi 能早日達到她小小的心願,也感謝 egi 的分享。
由 jserv 發表於 06:18 PM | 迴響 (1)

觀察 Linux 的虛擬記憶體

延續 [尋幽訪勝話系統--以 Linux 探索軟硬體整合設計] 的演講,要觀察 GNU/Linux 運作時期的虛擬記憶體 (Virtaul Memory,以下簡稱 vm),其實沒有想像中的難。首先,可透過 vmstat 工具程式,執行方式如下:
$ vmstat -S m
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 1  0      0     81    110    895    0    0    11    36  160  161 62 17 21  1
可列印 vm 的切割與狀態資訊。在 Linux 2.4 轉變到 2.6 的過程中,vm 經過很大的調整,cache 的設計也因此導致不同的表現,在上述 vmstat 的輸出可看到 "cache" 一欄的值,可透過 htop 工具程式作即時顯示系統的變化。不過,就算不安裝外部的工具程式,其實也有「窮人記憶體觀察工具」,那就是 /proc/meminfo ,可搭配 watch 使用:
$ watch  cat /proc/meminfo
顯示的效果如下圖:

每兩秒在終端機上更新一次,可清楚看到 vm 內各種類別的消長。

在 Linux 2.6.16 以後,引入 drop caches 的機制,所以咱們來看看 cache 的變化,如下圖:

htop 的視窗中,黃色部份就是 cache,當 /proc/sys/vm/drop_caches 的值被設定為 1 時,表示要求核心捨棄沒在使用的 cache (一般的),而被設定為 2 時則要求將 dentry, inode 所用的 cache memory 一併釋放,而設定為 3 時,就是 (1) + (2) 的效果,也就是幾乎把非作用的 cache 都釋放了。所以,在上圖中,很明顯可見,原本運作中的 GNU/Linux 的 vm 配置了可觀的 cache,在要求釋放後,就幾乎都清空。
由 jserv 發表於 04:03 PM | 迴響 (0)

httping : 針對 HTTP Request 的仿 ping 程式

[httping] 這個程式非常有意思,能夠以類似 ping 工具程式的方式,將 HTTP Request 的回應情況,呈現給操作者,簡介如下:
    Give it an url, and it'll show you how long it takes to connect, send a request and retrieve the reply (only the headers). Be aware that the transmission across the network also takes time!
跑個實際的例子,對象用 Google:
$ httping -g http://www.google.com -c 5
PING www.google.com:80 (http://www.google.com):
connected to www.google.com:80, seq=0 time=641.30 ms 
connected to www.google.com:80, seq=1 time=672.34 ms 
connected to www.google.com:80, seq=2 time=692.74 ms 
connected to www.google.com:80, seq=3 time=660.11 ms 
connected to www.google.com:80, seq=4 time=654.99 ms 
--- http://www.google.com ping statistics ---
5 connects, 5 ok, 0.00% failed
round-trip min/avg/max = 641.3/664.3/692.7 ms
由上可見,對著 Google 的某幾台機器的 port 80 作 HTTP request,並等待 header 的回應情況,我們可比對 ping 工具程式的輸出:
$ ping www.google.com -c 5
PING www.l.google.com (74.125.19.104) 56(84) bytes of data.
64 bytes from cf-in-f104.google.com (74.125.19.104): icmp_seq=1 ttl=241 time=258 ms
64 bytes from cf-in-f104.google.com (74.125.19.104): icmp_seq=2 ttl=241 time=258 ms
64 bytes from cf-in-f104.google.com (74.125.19.104): icmp_seq=3 ttl=241 time=257 ms
64 bytes from cf-in-f104.google.com (74.125.19.104): icmp_seq=4 ttl=241 time=257 ms

--- www.l.google.com ping statistics ---
5 packets transmitted, 4 received, 20% packet loss, time 4019ms
rtt min/avg/max/mdev = 257.860/258.053/258.395/0.656 ms
另外,[httping] 支援 https (使用 OpenSSL 函式庫),也可以要求只做一次 DNS resolve,對於 roundrobin DNS 的環境來說很重要。
由 jserv 發表於 11:11 AM | 迴響 (1)

演講:快快樂樂學 GNU Debugger (gdb) Part I + II [台中]

幾個月前,在台南舉辦演講:快快樂樂學 GNU Debugger (gdb) [Part I] 與 [Part II],現在也有台中的場次,詳情可見酷!學園的 [討論區],摘錄如下:
  • 簡介:期望使聽者得以透過 GNU Debugger (gdb) 來加速系統開發與分析,讓這個臥在硬碟深處許久的強力程式,激發其威力。本議程捨棄過往教條式的介紹形式,延續「深入淺出 HelloWorld」系列的「作中學」途徑逐一探討使用情境與案例分析
  • 議程主題:
    • Why - 為何我們該善用 Debugger?
    • Who - 誰會因為熟悉 Debugger 而受益?
    • What - 史上最強大的 Debugger -- gdb 與一系列系統軟體
    • Where - 利用 Debugger 的場合
    • How - 還用說嗎?來就對了!
  • 涵蓋項目:
    • 實用 GDB 指令
    • macro 與技巧
    • 當 GDB 遇上 C/C++:具體的範例
  • 參考資訊:
  • 時間:七月 12 日 10:00-12:00 (Part I), 13:30-17:00 (Part II)
  • 地點:亞洲大學 (台中縣霧峰鄉柳豐路500號) 管理大樓 B1 M009 教室
  • 費用: 0 –
  • 人數限制:無限制
  • 報名網站:http://registrano.com/events/sataichung080712
  • 交通: 自行開車; 搭乘客運或公車至台中火車站--前站(建國路)對面搭乘100號公車往省諮議會方向--最後一站 亞洲大學
歡迎指教,也請多利用討論區事先提問或者分享建言,謝謝!
由 jserv 發表於 10:05 AM | 迴響 (0)