發表文章

目前顯示的是 十二月, 2011的文章

C/C++ 檢查和打開 debug 功能的小技巧

打開 debug flag 有助於了解模組的行為。工程師開發時應該都有幫自己除錯留後路, 所以找到他們開發的方式, 可省下自己瞎試的時間。 以前寫 Python 或 Java 時, 都有近乎標準的 logging module 可用, 所以要找到開啟 logging 機制的方式滿簡單的, 看是用那家的 logging module, 針對它的設定檔設 log file, 或在程式開始時塞段程式啟動 logging 即可。不過 C/C++ 好像沒有那麼一致。 留意程式碼會看到輸出訊息的巨集, 像是 P("What the F**k!!");, 然後在某些 debug 相關 header 檔裡會看到類似這樣的東西:#ifdef DEBUG #define P(x) ... #else #define P(x) ; #endif 其中 "..." 的部份是該模組用來輸出除錯訊息的機制, 可能是寫到檔案, 或輸出到 stdout/stderr 等。這樣在沒有 define DEBUG 的情況下, 完全不會產生除錯訊息的程式碼, 減少執行負擔。 要確定程式真的有編進除錯訊息的話, 隨便找個程式內有的除錯訊息, 然後用 strings 檢查: $ strings PROGRAM | grep "What the F**k!!" 看有沒有結果。有的話大概就 ok (找個少見的字串最穩), 不然就是沒編進去, 檢查看看 #define DEBUG 1 有沒有設好, 或是有沒有在 makefile 的 CFLAGS 設好 -DDEBUG。 再來, 也可以用 strace 檢查執行期間到底有沒有執行到: $ strace -e write PROGRAM | grep "What the F**k!!" 不過 write 寫入的字串不見得會一次剛好符合一整串, 這和檔案 flush 機制有關。找子字串, 或用眼睛注意一下比較穩一些。懶得找方法打開 log 檔的話, 用 strace -e write 將就看也還可以, 不過會拖滿執行速度, 且 strace 有時會影響 process 的狀態, 常期用的話還是打開 log 檔最穩。

打開 core dump 和使用 cgdb 檢查程式掛點原因

前置動作首先, 用 gcc/g++ 編程式時記得加 -g 以加入除錯資訊。 接著參考《產生 core dump 的方法》設成可以產生 core dump。 檢查 backtrace$ gdb PROGRAM 然後在 gdb 內執行core core.PROGRAM.PID.TIMESTAMP 接著就能用 bt N 看最底層 N 個 call stack 為何, 也就是所謂的命案現場啦。然後可用 up, do, l 等指令切換 call stack 和列出週圍程式。 在和 gdb 相處一段時間後, 覺得這樣在 stack 之間移動很方便, 但看週圍的程式太辛苦了, 還是會開另一個視窗用 VIM 看完整一些的程式。 看到 jserv 建議使用 cgdb, 試了發現, 人生...啊不, 是視窗變彩色的了!! 不只有彩色的程式碼, 還外加類 VI 的瀏覽方式, 相當順手。目前有用到的功能如下: 按 i、ESC 在程式碼視窗和 gdb 命令列之間切換。 程式碼視窗裡可用 VI 的部份指令移動行數; 回到命令列後和 gdb 完全相容, 可按上下鍵選用之前的指令。 在程式碼視窗按空白鍵加減 break point。官網文件有詳細的說明, 之後來掃一遍, 看有什麼好東西可用。

善用 strace、debugger 從執行期間找出問題根源

最近被迫在短時間內學會 strace、gdb 這些之前一直用不到的重兵器, 都還不熟練就是了。剛好使用 hgsubversion 時有些困擾, 雖說它和 svn 整合得很好, 無縫接好 pull / push, 但它不會顯示 mercurial 對應到的 svn 版本, 平時看其它和 svn 整合的工具 (如 issue tracking) 會很困擾, 用得都是 svn 版號。 剛剛想到可以學 Strace -- The Sysadmin's Microscope 的做法, 用 strace 找出關聯的程式, 再來看怎麼修正它。 我推測 hg 一定有記錄 svn 相關版本的方式, 不然無法和 svn server 同步資料。於是挑個會讀到 svn 資料的指令來試:strace -o trace.log -s 512 -e read,open hg svn info 用顯示的 svn 版號 123 來翻 trace.log, 發現這兩行:open("/path/to/project/.hg/svn/lastpulled", O_RDONLY) = 3 read(3, "123\n", 4096) = 5 於是找到 meta data 存在 .hg/svn/ 下。 到該目錄下找到 .hg/svn/rev_map 這個檔案, 裡面存 hg 和 svn 的版號對應表。至少這樣就有足夠的材料寫個 script 來轉換 hg、svn 的版號。 不過若能直接加到 hg 裡, 應該會更方便也較可攜。要做這點相對容易, 可以到 hgsubversion 原始碼目錄下用 ack 找相關程式。 先用 ack rev_map 找到存 meta data 的物件 revmap, 再用 ack revmap 找到 wrappers.py 是換掉 hg 指令的程式。 再來用 pdb 觀察 revmap 如何被使用。先在 wrappers.py 裡設中斷點, 然後執行 pdb /usr/local/bin/hg parents --svn 找出 meta data 如何被讀出來。於是明白可在函式 parents 的部份塞入幾行顯示 svn 版本:--- a/hgsubversion/wrappers.p…

加速 linking time

頻繁修改一點程式又要執行看結果時, 主要的時間都花在 linking。這時才體會到省下 linking time 也是很重要的事。 這篇提到兩個減少 linking time 的做法 使用 ramdisk 用 gold 我本來就有用 SSD, 改將編譯結果全放到 ramdisk 後, 提昇的效果不怎麼明顯。到是用 gold 後減少了一半的 linking 時間。 不過 gold 也不是那麼完美, 這篇提到一些問題。我自己用的時候發覺滿常遇到 ld 可以編, 但 gold 不行。最後的解套方式是寫個小 script 切換 /usr/bin/ld 連到的程式。不常編的東西就暫時換回 ld 連結個一次就好。 另外 jserv 提到 gcc 有命令列參數可直接指定用那個 linker (ref.)。wens 則查到 Debian/Ubuntu changelog 裡有寫 下可用 -B/usr/lib/compat-ld 和 -B/usr/lib/gold-ld for ld.gold。 順便 google 一下看怎麼查 changelog, 似乎是看 /usr/share/doc/binutils/changelog.Debian.gz 或用 aptitude changelog PKG, 不過這兩個作法都只有最近的 changelog, 沒有完整記錄, 像查 binutils 時就沒有找到上面提的參數說明。

iPad: waiting for sync to start 的解法

沒想到我也會有寫這種 3C 裝置文章的一天。 今天用 Windows7 的 iTunes 連 iPad 要安裝新的 app, 結果一直出現 "waiting for sync to start", 等很久後就說失敗。聽同事說可能是在不同台電腦用 iTunes sync, 有時會錯亂, 他以前也遇過。google 到不少人有類似經驗, 不過沒什麼好解法, 大概的做法是 將 iPad 的連接線拔掉重插看看 按住上方電源鈕和 home button 關機, 再重開看看 將 iPad 回復到原廠設定看看 移掉 iTunes, 再重裝看看 很不幸的, 最後是做完最後兩步才 ok。重裝後有些東西可能會不見, 軟體是認 Apple ID, 影響不大。聽說有些 in-app purchase 的東西 (如遊戲道具?), 不會重發的。 btw, 雖說 Apple 的產品包裝得相當貼近消費者, 對我這種習慣用 console 的人來說, 有錯誤卻看不到任何詳細資訊, 實在是很悶的事。只能亂 google 和無腦亂試。

列出用到的 shared library

本文整理大家的建議和自己實際操作的心得。新手上路, 有錯還請指正。 static library 就是一包 object file, 沒什麼需要提的, static library 沒有記錄其它資訊。所以, 編 shared library 或 executable 時要自行處理好 static library 的相依性, 在前篇有提到一點資訊。 shared library 有兩種, 一種在 linking 時要指定好 shared library 用到的 undefined symbol 放在那些 shared library 裡, 待執行時再載入到記憶體使用; 另一種用 dlopen() 和 dlsym() 載入 (這兩個函式存在 libdl 內)。 前者比較單純, 可用 ldd 透過靜態分析了解用到那些 shared library, 且各自實際指到的檔案。ldd 本身是一個 shell script, 用到 ld.so 事先定義的一些機制 (LD_TRACE_LOADED_OBJECTS) 來讀取資料, man ld.so 裡有相關的說明。其中 LD_LIBRARY_PATH 和 LD_PRELOAD 相當實用, 無法在連結時解決問題時, 至少還有這招可在載入時處理。若想分析透過 dlopen() 載入的動態函式庫, 有幾個做法在程式執行中觀察 /proc/PID/maps, 這個檔案記錄 process 用到的各區段記憶體為何, 可從對應到的檔案看出有載入的 shared library。必要時可配合 gdb 在想觀察的部份停住, 再從外部看 /proc/PID/maps。這裡或 man proc 有相關說明。用 strace 執行程式, 觀察開啟的檔案: strace -f -e open PROGRAM 2>&1 | grep "\.so"就我自己小試的心得, 看 /proc/PID/maps 最穩, 且方便看各別 process、thread 載入的函式庫, 也不會拖慢觀察目標的執行程式。不過 strace 不需配合 gdb 停在該停的地方, 就「快篩」的角度來看, 也滿有用的, 加上 -f 後方便追蹤 multi-process、multi-thread, 不過執行速度好像有慢一些, 不太確定。之後再比較看看兩…

用 file 查看文字檔編碼或執行檔為 32bit 或 64bit

實用的例子:file -i TEXT_FILE: 會顯示 charsetfile BINARY_FILE: 會顯示 32-bit 或 64-bit 當然還有其它更多資訊, 目前這兩個對我來說很實用, 希望這樣寫過一次多少會記久一點 ...。

解決 undefined symbol / reference

C++ 新手上路, 有錯還請幫忙指正。 基本觀念相較於 script language 或 Java 來說, C/C++ 有完整的「編譯 -> 連結 -> 執行」三個階段, 各階段都可能發生 undefined symbol。在解決惱人的 undefined symbol 前, 得先明白整個編譯流程: 編譯 .c / .cpp 為 .o (object file) 時, 需要提供 header 檔 (用到 gcc 參數 -I)。事實上, 在編譯單一檔案時, gcc/g++ 根本不在意真正的 symbol 是否存在, 反正有宣告它就信了, 所以有引對 header 即可。這也是可分散編譯的原因 (如 distcc ), 程式之間在編譯成 .o 檔時, 並沒有相依性。 用 linker (ld 或 gold) 將 *.o 連結成 dynamic library 或執行檔時, 需要提供要連結的 library (用到 gcc 參數 -L 指定目錄位置, 用 -l 指定要連什麼函式庫)。不同於前一步, 此時 symbol 一定要在。 執行的時候, 會再動態開啟 shared library 讀出 symbol。換句話說, 前一個步驟只是檢查是否有。檢查通過也連結成 executable 或 shared library 後, 若執行時對應的檔案不見了, 仍會在執行期間找不到 symbol。若位置沒設好, 可能需要用 LIB_LIBRARY_PATH 指定動態函式的位置, 但不建議這麼做, 最好在執行 linker 時就指定好位置。原因見《Why LD_LIBRARY_PATH is bad》。明白這點後, 就看 undefined symbol 發生在那個階段, 若是編 object file 時發生, 就是沒和編譯器說 header 檔在那, 記得用 -I 告訴它。若在 linking 時發生, 就要同時設好 -L 和 -l。不過難就難在要去那找 undefined symbol 的出處。 解決問題的流程首先是判斷 symbol 是不是自己用到的原始碼裡, 可配合 id-utils 找看看 (我是用 gj, 比較方便一點)。或是看有沒有 man page, 有 man page 的話, 裡面會記錄用到的 header 和該怎麼下連結參數。若在專案裡找不到, …

用 Eclipse CDT 讀 C/C++ 原始碼

上篇, 記一下 Eclipse CDT 的用法, 全部加起來耗去我不少時間啊。 1-1. 匯入舊的非 Eclipse CDT 專案: File -> New -> Makefile Project with Existing Code 之所以選用沿用舊的檔案位置, 而非開新專案再匯入程式碼, 是因為這樣比較方便和 VCS 共存。可先用 VCS ( git/hg/svn/... ) 取出專案, 再用 Eclipse CDT 來讀碼。VCS plugin 再怎麼成熟, 都不會比直接用 command line 或專屬的 GUI 來得完整。 1-2. 選 Toolchain for Indexer Settings 選 GNU Autotools Toolchain。 少勾這個, 之後匯入程式碼後會有 Type 'std::string' could not be resolved 這類鳥錯誤。因為少加了預設 header。實際要選那個 toolchain 要看開發環境而定, 沒有仔細研究, 至少在 Ubuntu 上這樣做 ok。 2. 將 Indexer 範圍改為全部 Window -> Preference -> C/C++ -> Indexer 將 Index unused headers 和 Index source and header files opened in editor 勾起來。剩下加減看看, 我有調大 Cache limits, 不知影響多大。 雖然 Eclipse CDT 會猜測 include 那些檔案而只 index 那些檔案的 symbol, 但 C/C++ 的 include 似乎是很混亂的世界, 有 macro, ifdef 等東西混在裡面, 很難光用靜態分析搞定。對我這一直活在安全的 Java 和 Python 世界的人來說, 真是晴天霹靂, 花了不少時間才明白這事。 所以結論是, 都 index 就是了, 多花些時間總比找不到來得好。還有, C++ 的世界是很危險的, 能活在 Java 或 Python 的世界的話不要過來。 3. 建 index 在左側的 Project Explorer 的 project 名稱上按右鍵, 選 Index -> Rebuil…

閱讀 C/C++ 原始碼的好幫手

最近有需求讀 C/C++ 的東西, 試了 ctags, cscope 覺得不理想。問了一下收到許多回應 (G+plurk ), 真是太感謝大家了, 減少入門摸索的時間。 試用的感想如下: grep優點: 好上手缺點: 陽春安裝: 內建於 Linuxgtags優點: 可找 caller 和 callee缺點: 因為索引檔是由 ctags 來的, 會漏東西; 執行方式也有些不便安裝: 程式很久沒人更新了, 要做一些修正才裝得起來 參照官網指示 make 時看少了什麼 header, 手動補一下 header 然後 make 還是會失敗, 將 gas.py 的 "import as" 改為 "import asm", 下面用到的模組名也要跟著改, as.py 也要改為 asm.py。python 2.6 後 as 是 keyword 編好後將幾個用到的 python scripts 第一行由 python2.4 改為 pythonack優點: 比 grep 容易使用, 省得配合一些 command 過濾檔案, 見官網的《Top 10 reasons to use ack instead of grep.》。而且還有彩色的輸出!!缺點: 因為沒建 index 的關係, 速度較 id-utils 慢, 我的測試情境要 3s, 而 id-utils 只要 0.006s安裝: curl http://betterthangrep.com/ack-standalone > ~/bin/ack && chmod 0755 !#:3id-utils優點: 速度快, 和測 ack 同樣的情況, 建索引 5.3s, 之後搜尋瞬殺缺點: 介面沒有 ack 直覺易用, 我寫了個小程式 gj 以 id-utils 為底, 提供彩色輸出和進一步過濾檔名的功能。 安裝: Ubuntu 超容易, aptitude install id-utilsEclipse CDT優點: 方便開新視窗看 caller、callee缺點: 不方便搭 vim 使用 (對 vim 重度使用者才有差); 建 index 有點久, 我的測試情境要數分鐘到十分鐘吧安裝: 結果這個是我試最久的, 因為不知怎麼建 index。參考官網 FAQ, 建索引前要先設…

Web basic access authentication

之前都沒注意到, 原來用 apache2 做使用者身份認證 (如使用 htaccess) 時, 跳出來詢問使用者名稱和密碼的對話框並不是網頁, 而是 client 軟體提供的輸入框。 《Basic access authentication》對此有詳細說明, 或用 Firefox 的 HttpFox 觀察 http header request 和 response 也不錯。幾個重點: basic authentication 的傳輸沒有加密, 最好在 SSL 下使用 server 發覺沒通過身份認證時會傳 401 並在 header 裡附上 WWW-Authenticate: Basic ... 的訊息 client 送出 Authorization: Basic ... 的訊息, 其中 "..." 是 base64(USERNAME + ":" + PASSWORD) 除關掉瀏覽器外, 沒有明確的方式「登出」這種登入方式

VirtualBox 設 shared folder

《How To Share Files In VirtualBox With Vista Guest And Ubuntu Host》設定 host OS。若 client 是 Linux, 見 《HOWTO: Use Shared Folders》, 就一行指令啦:sudo mount -t vboxsf SHARE_FOLDER_NAME /mnt/share SHARE_FOLDER_NAME 是在 host OS 設定時打的名稱。

在 Windows 上安裝和設定 VirtualBox 和 Ubuntu

CPython 的 garbage collection

來消化之前有讀沒時間寫的東西。 長期使用 python 後, 會很納悶為啥記憶體一直漲, 明明已沒用到了, 卻沒有減少。這篇提到 CPython 的 gc 機制採用 reference counting, 另有備案的 mark and sweep, 用來解決 circular reference。btw, 強烈推薦 Back to basic: Series on dynamic memory management, 淺顯地介紹各種 gc 運作的方式。 照 Scott 的說法, reference counting 算是苦工的半自動化管理記憶體, 因為實作細節交給實作者處理, 用 Python API 寫 CPython 的 extension 時, 開發者得自己管理物件的 reference count, 是件很辛苦的事。好處是在 reference count 為 0 的時候, 會立即回收記憶體。但缺點是, 若 a、b 兩物件互相參照到對方, 卻沒被任何人用到的話, a 和 b 都無法被回收, 因為 reference count 永遠會是 1。 為解決這個問題, 只好多提供 gc.collect 喚起 mark and sweep 的演算法清掉 a、b。但 mark and sweep 也有自己的問題, 主要有兩點: 不像 reference counting 會在沒用到的第一時間點立即回收, 而是 mark and sweep 執行後才回收。 執行 mark and sweep 的時候為確保沒有算錯 reference graph, 必須暫停目前所有執行中的 python process/thread, 結束後才可繼續執行。可想而知, 記憶體用量大後, 這個暫停時間也會太久, 不適合需要不斷有回應的程式 (如 GUI、Web)。 所以又有了 generational garbage collection, 關鍵的想法是: 觀察到大部份物件都很早死 (英年早逝啊~), 所以只要回收年輕的物件即可回收大部份的記憶體。於是將使用到的物件分不同「年代」, 預設只回收最年輕的一代, 沒回收到的物件就放到下一代。下回要再回收的時候, 就不動這個舊一代的物件, 減少要建立的 reference graph。 回想平時寫程式的結構, 可以想見, 像頻繁使用的 l…