scott_guide

存放Scott給過得意見:)

Book

The Linux Programming Interface

讀這本的話,要挑『對自己重要的子系統』先看,否則會像讀 1500+ 頁的 manpage。

如同我們之前討論過的,作者是 Linux manpages 的 maintainer,對 POSIX 標準、Linux kernel 、glibc 都熟(即又熟標準、又熟 Linux 的實作)。有寫的主題都很詳盡,索引鉅細靡遺。說明觀念用的圖也滿好的(受同領域 Richard Stevens 寫的 Advanced Programming in the Unix Environment 影響)

我對 TLPI 本書最大的批評是『主題的選擇』。它基本上寫『全部 POSIX 有規範的子系統』,但有些主題剛好不在 POSIX 裡面卻又比 POSIX 冷門的部份有用。

舉例一:
寫 C/C++ 的 POSIX Threads 程式,常用 reference counting 管裡記憶體。但 POSIX 裡面沒有用來遞增、遞減 reference count 的 atomic_inc() 等 atomic operations,書中就完全沒講去哪裡找,好像沒這個需求一樣。 篇幅不夠,至少也要說你可以去某 library 之內看它怎麼寫出比較高階的界面。

舉例二:
Unix userspace 最重要的 pattern 就是 select / poll based 的 mainloop。 glib 內的 mainloop, libevent, tevent, boost::asio 都是實作這個觀念。libdbus 與 avahi (DNS service discovery library) 甚至讓你可以置換它們 library 內部使用的 mainloop。書中對 Linux 下的 epoll / select / pselect 每個函式都有講,卻不會跟初學者講說『你在 application 中想用的高階界面要長的像 tevent 那樣』。

我的抱怨基本上就是書中對每一個基本動作有很詳實的描述,但很多重要片語、pattern 需多個基本動作組合,初學者不會自己想出來。 初學者自己從 pthread_create() 開始發明 multithreaded reference counting 甚至可能不知到自己缺了很多觀念,那就很悲劇了。

Android

Remote debugging

For Android remote debugging, I'm disappointed that Google still ships an ancient gdbserver from 2006 that doesn't even support "gdbserver —multi". I recently did some native development on QNX … and was surprised to find that they offer a better remote debugging experience. They have a command "on -h PROGRAM" that starts a program in HELD state until a debugger attaches which is much nicer to use than "(gdb) set remote exec-file", "(gdb) set args" or "gdbserver —wrapper". They also have their own gdbserver replacement (qconn) that provides multi process remote debugging, target process list and file system access in one program. You don't need to remember if gdbserver is running or not, the functionality is always available.

C/C++

Network

Misc

linker

從 BFD ld 轉移到 gold 最常見的問題,是:https://fedoraproject.org/wiki/UnderstandingDSOLinkChange
Fedora 為了未來能將系統 linker 預設成 gold,在 2009 年底將 BFD ld 的預設行為改了(—no-add-needed),並將因此暴露的全部 link failure 也都改了。(當然很可能 Fedora 的這些修改,upstream 還沒收,或者還沒從 upstream 再流到 Ubuntu 裡面)

其他已知不能用 gold 是例如 glibc 與 Linux kernel (以修好?)等。它們都有很棒(!)的 linker script,你大概不會遇到。

Gold 的作者,Ian Lance Taylor 在 binutils mailing list 上回 gold 問題相當積極,如果你在 sourceware.org file bug 他就會看到。但是他會想要你用 binutil CVS 試試看: http://sourceware.org/git/?p=binutils.git;a=summary

我還是覺得 Fedora 下 GNU 開發工具、qemu、OpenJDK 跟 Eclipse 等都維護的比其他 distro 較好。當然這是因為 Red Hat 內部長期有 team 參預這些專案,那些 developer 平日就是用 Fedora 參加 upstream 開發,且 team 中會指定人當 Fedora 下的 package maintainer.

我最常用的就是 /proc/PID/maps 和類似 wens 上面留言中那樣用 strace. 你 strace 的用法有一點點鱉腳:
1. 因為 mmap 需要 file descriptor 所以一定會先呼叫 open
2. 故 strace -eopen -f PROGRAM | grep '\.so' 即可(除非有類似 Python 在 Windows 下將 .dll 改名成 .pyd 的奇怪命名規則)

你看到的 ldd 是 glibc 的版本。
glibc 內的動態連結器 (/lib64/ld-linux.so.2) 可用:
LD_DEBUG=all MY-PROGRAM
看 runtime 每個 symbol 怎麼找的。
但 Anroid 、或用 uClibc 的 Linux 系統上不是用 glibc 所以不適用。
ldd 會遞迴列出 dependency, i.e.: EXE -> libA -> libB 會連間接依賴的 libB 也列出,要分辨直接依賴的可用:
eu-readelf -d MY-LIB | grep NEEDED

在 Linux 下開發程式會用到的 package manager 指令我有張 RPM 與 DPKG 的對照表,寫在:
http://itrs.tw/wiki/RPM_DPKG_Rosetta_Stone

另一個好玩的基礎問題是:一個 .so,/proc/PID/maps 為何會出現多次?

(嚴格說 Posix 中傳回 file descriptor 的函式除了 open 還有:
http://udrepper.livejournal.com/20407.html
但會用來 mmap 的 file descriptor 都是 open 傳回的)

Debugging

  1. 最常用的方法真的是『直接用 gdb 』,最有用的指令還是 backtrace
  2. 我後來把 gdb script 語法和 gdb 的 Python API 學了一下。比較複雜的動作寫到 "gdb -x SCRIPT" 裡面
  3. gdb python API
  4. 有時直接在程式碼中加入 break point: https://github.com/scottt/debugbreak/blob/master/debugbreak.h
    1. 搭配這個http://lackingrhoticity.blogspot.com/2010/05/breakpoints-in-gdb-using-int3.html , 看懂之前你寫得 https://github.com/scottt/debugbreak/blob/master/debugbreak.h , 不過在 http://www.linuxjournal.com/article/6210的例子, 他是直接去改 process instruction (char code[] = {0xcd,0x80,0xcc,0};)
  5. 有時切到 gdb-tui: http://davis.lbl.gov/Manuals/GDB/gdb_21.html
  6. 若壞掉的資料跟好的資料只差一個 bit, ex: memory address ffefffff81c8bca0 ffffffff81c8bca0, 則多半是硬體 bug,且高度懷疑 RAM.

我開發 C/C++ 程式的方法,刻意避開了『很依賴 debugger』的風格。

多數會先擬好除錯計畫,在 code base 中插入好檢查、pretty print 資料結構的程式才開始。在除錯時,不只是另外開 editor 看 code,同時還有除錯計畫的筆記,紀錄如『我懷疑哪些資料有壞掉』,『已經作過哪些實驗』、『目前看到的 corruption 在 stack 還是 heap 上 address 某某附近』等。

除錯到一半,覺得問題不容易解時,我也會先 trace code 把局部的 data flow 與 control flow 寫在筆記裡面才繼續開始除錯,避免要『跳來跳去』看 code。

在處理複雜的 code base 或長時間執行的系統軟體時,上述風格頗有道理 — 最重要的是逼自己先分析問題,而不是先開始單步執行然後見樹不見林。如concurrency 的問題不適合用 debugger 觀察、最佳化編譯的 release build 中,你要看的變數原本已經沒用到的話,暫存器已被拿來存別的值了。

我也同意有經驗的 developer 看較簡單 code base 時,直接在 debugger 邊跑邊讀 code 可能比較快。最極端的如大學『C 程式語言入門』助教改作業。但我在工研社教了多屆 C 入門,也是除了分析 core dump 以外,不用 debugger 的。因為學生人數少,看他們的 code 可以教的更深入。

也有遇過很需要 debugger 的時候:編譯或連結時間很長的 C++ code base 要改 code 追特定問題成本太高 (header only style 的 C++ 配上 binutils 中舊的、非 gold 的那版 linker 就很慘了)。我會盡量將 break point, watch point, 檢查與dump 資料結構的動作寫成 gdb script 或從 Python 用 gdb API,避免在 debugger 中重複同樣操作。

結論:
我同意直接用命令列的 gdb 比起用整合在 IDE 中的除錯界面,開發者要多記住很多資訊,有時感覺起來像多餘負擔。最後,整合 gdb 的 IDE 中,Eclipse CDT 與 QtCreator 是支援 remote debugging (包含 cross debugging)且有好的團隊在維護的;但我平常還是覺得從命令列用 gdb 比較快。

例如除錯器如何實作這些『進階主題』,還是都有邊際效益遞減的問題,花時間研究不一定用的上。但當然很有趣,我個人也很喜歡 :)
時間有限時,還是看個 可能有用的開發技巧 列表,知道『在 source 中像加函式呼叫一樣加 break point』是可能的。即使忘了在 Javascript 下用 debugger statement, Python 下用 pdb.set_trace() 還是可以用 "programmatic break point" 為關鍵字 Google 的到。

觀念上知道:CPU 有提供 software break point 指令、作業系統有提供『存取別的 process 的記憶體、暫存器』的 API 給 debugger 用即可。

例一:

(gdb) p &var1
$1 = (int *) 0x41523c0
(gdb) watch *(int *)0x41523c0
Hardware watchpoint 1: *(int *)0x41523c0

1. 例一中,用 "watch -l var1" 即可 (Stackoverflow 中路人的建議不見得是最好的)

2. 你 watchpoints 與 commands 都有在用的話,可考慮打開 gdb global history: $HOME/.gdbinit
set history save on
set history size 4096
set history filename /home/fcamel/.gdb_history
然後像 ipython 一樣,撈 history 寫成 gdb script.

P.S. Windows 下用 DebugBreak() 加 break point 還滿多人知道的。我的 Windows programming 筆記在:http://itrs.tw/wiki/Windows_Programming

pthread

Linux

debelopment environment

另外分享一點心得:一個開發環境『預設組態』就要很好用,不要讓使用者花很多時間設定。一個 API 若很多人需要就該蒐集進系統 library,不要讓使用者需要東拼西湊蒐集有用程式碼片段;這樣有用的技巧才能流傳廣泛。

Linux API 散落各地,不像一個經過設計的整體,而像 一堆 library 的集合 。 Windows、Mac 下沒有套件管理,小的開發工具與 library 都太難安裝、散佈。

Linux kernel header (API usage)

For API header inclusion
我會先 grep
接著會想想還有哪個工具用到這個 API (ptrace 使用者包括 strace, gdb 等)?他們在新舊版 kernel 都能編譯嗎?並用 git log HEADER-FILE 看看該 kernel 版本管理系統中該檔案的修改紀錄

TCP/IP zero copy

要往 TCP connection 另一端送資料,既有 Linux interface 無法用 mmap()。目標是 減少拷貝 : 投資報酬率最好的是 sendfile() 、次之是 splice(fin, offset, fout, offset, len, flags) 、再來是用 sendmsg() 一次送一個 iovec (常用在加 protocol header 時。如 OSI 七層模型,每層加 header,調整 iovec 即可)

sendfile() 與 splice() 減少 kernel user space 間拷貝,送 iovec 減少 userspace 建構封包時的拷貝。

即使在可以用 mmap 的地方,你還是要付出 interface 複雜度為代價: shared buffer 使用完,要有機制 notify 提供者 ,例如 data_transfer_complete_callback() 。你想想看 TCP packet loss 要 re-transmit 時,資料還在不在?

我在做 Set-Top-Box 時看過一些自己發明的、用來減少拷貝的 API,痛苦指數都滿高的。

Soft Skill

con-call greatest hints:

-1: well you know (太模糊)
-1: 用 … or something like that 結尾 (太長,浪費時間、模糊)
+0: interesting (沒資訊)
個人偏好:
+1: OK ~ 語調提昇,懷疑語氣 (我不懂或不完全認同)
+1: understood
+1: got it 短,斬釘截鐵 (『了解』)
+1: will do 斬釘截鐵 (承諾,接下 action item)

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License