發表文章

目前顯示的是 一月, 2012的文章

gdb: 維持永久的 watchpoint

若想觀察特定變數的變化, watchpoint 比 breakpoint 易於使用。但 watchpoint 有個小問題, 一但離開 scope 後, gdb 會自動刪掉 watchpoint。 若該變數是 member field, 就無法持續追踪不同 method 怎麼讀取或寫入它的值。這篇提供一個小技巧, 取用目標變數的指標, 再觀察指標取值的變數, 藉此讓 gdb 認定它不是區域變數, 借用該文的例子如下:(gdb) p &var1 $1 = (int *) 0x41523c0 (gdb) watch *(int *)0x41523c0 Hardware watchpoint 1: *(int *)0x41523c0 若真的是區域變數的話, 可用 commands 定義簡短的指令, 之後進入函式時, 自動加回 watchpoint, 借用該文提供的例子:(gdb) break func (gdb) commands > watch var > continue > end commands 看來很方便, 還有許多適合應用的情境, 像是追踪網路連線輸出關鍵資訊, 可避免手動操作太久造成 timeout。2012-02-04 更新 gdb 7.4+ 支援 watch -l, 就不用自己取位置再設 watchpoint 解套。在 7.4 之前想用這功能, 可使用 Scott 寫的 gdb-watch-location.py。用法是 $ wget https://raw.github.com/scottt/scottt-gdb/master/gdb-watch-location.py $ gdb -x gdb-watch-location.py PROG 在 gdb 裡會新增幾個指令: watch-l、rwatch-l、awatch-l 對應到 watch -l、rwatch -l、awatch -l。

讀懂函式庫的 man page

初學 Linux 時, 覺得 man page 真不是寫給一般人看的, 到像是寫給已經懂的人備忘用的。即使現在已比較習慣讀 man page 了, 還是這麼覺得。 以前只知道 man function 看要引入什麼 header, 了解函式的參數、傳回值。最近才明白裡面的其它資訊。 背景知識可以搜尋 "how to read man page" 或看 Wikipedia 介紹得知。這裡只針對使用函式庫講基礎知識。另外這裡有顯示彩色 man page 的設定。 以 man 3 sqrt 為例:SQRT(3) Linux Programmer's Manual SQRT(3) NAME sqrt, sqrtf, sqrtl - square root function SYNOPSIS #include <math.h> double sqrt(double x); float sqrtf(float x); long double sqrtl(long double x); Link with -lm. Feature Test Macro Requirements for glibc (see feature_test_macros(7)): sqrtf(), sqrtl(): _BSD_SOURCE || _SVID_SOURCE || _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L; or cc -std=c99 #include 表示需要的 header 下面有函式的 signatureLink with -lm 表示使用 gcc 編譯時要加 -lm, ld 才會找到 libm.so。詳細的運作過程見《ld, ld.so 和 ldconfig 的行為》Feature Test Macro 是 UN…

使用 RTLD_NEXT 實作 wrapper

先貼程式:// RLTD_NEXT is only supported in _GNU_SOURCE. #define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> void *malloc(size_t size) { void* (*m)(size_t); // 「標準」取得 function pointer 的寫法. 見 TLPI 42.1.2 p863 的說明 *(void**)(&m) = dlsym(RTLD_NEXT, "malloc"); printf("local malloc\n"); return m(size); } int main(void) { int *t = malloc(sizeof(int)); *t = 3; printf("%d\n", *t); return 0; } 編譯和執行結果:$ gcc m.c -ldl -o m $ nm m | grep malloc 0000000000400604 T malloc $ ./m local malloc 3 若去掉 malloc(), 加上 #include stdlib.h, 用 nm 看則是:$ nm m | grep malloc U malloc@@GLIBC_2.2.5 表示在 m 裡面沒有定義 malloc, 它存在 glibc 裡。malloc 後面的 "@@GLIBC_2.2.5" 是 version tag。當 shared library 裡有多個版本 malloc 時, 可用 version tag 來區別要用的是那一個 malloc, 詳情見 TLPI 42.3.2。 這裡的關鍵是 glibc 定義了擴充功能 — 假的 library handle: RTLD_DEFAULT 和 RTLD_NEXT。用前者取函式 (變數) 就和原本 ld.so 找 symbol 的方式一樣; 而後者則會找「下一個」, 這是針對實作 wrapper 的需求而定的。 由於是 glibc…

ld, ld.so 和 ldconfig 的行為

TLPI ch41 相當值得一看, 從開發者使用 library 的角度說明 library 的生成、靜態連結、動態連結 (載入) 的行為, 內容不多不少, 正好就是我想知道的, 省了看 linker、loader 的時間。 shared library 的名詞介紹 soname: 記錄在 shared library header 裡的名稱, 格式為 libX.so.MAJOR。要有同名檔案, 供之後程式載入 shared library 時使用 real name: shared library 的檔名, 格式為 libX.so.MAJOR.MINOR.NUMBER linker name: 對 library X 來說, 就是 libX.so, 一般會是 symbolic link 指向最新的 major shared library 以 libjpeg 為例, 對應如下:libjpeg.so -> libjpeg.so.62.0.0 # linker name libjpeg.so.62 -> libjpeg.so.62.0.0 # soname libjpeg.so.62.0.0 # real name 這是我在 Ubuntu 裝好 package 後的樣子, 照理說 libjpeg.so 指向 libjpeg.so.62 應該會更彈性。 讀出 soname:$ readelf -d libjpeg.so | grep SONAME 0x000000000000000e (SONAME) Library soname: [libjpeg.so.62] static 和 dynamic linker ld (ld.bfd) 是 static linker。Google 開發的 gold 是取代 ld.bfd 的 static linker。用 gcc 連結 shared library 或 executable 時就是呼叫 ld, 並將需要的參數傳給它。不論連結的是 static library 或 shared library, 都是 static linking。 ld 在連結 shared library 或 executable 時, 會將需要的 shared …

C99 好用的語法

之前只有用到 C99 的 loop initial declarations (在 for 的初始化部份宣告變數), 看 Scott 提到才知道有其它好東西, 順便來掃一下 C99 的功能stdbool.h定義 bool、true、false, 實際上是將 bool 對應到 C99 定義 _Bool stdint.h定義了整數範圍、int16_t、int32_t、int64_t 等型別, 再也不用查 short/int/long 等在 32/64 bit OS 上的大小為多少。 designated initializers可攜又易讀的初始化 (ref.)// array int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 }; // struct struct point { int x, y; }; struct point p = { .y = yvalue, .x = xvalue }; 像要用建表實作 isspace() 的話, 這樣寫超清楚的:bool myisspace(int ch) { static bool whitespace[256] = { [' '] = true, ['\t'] = true, ['\f'] = true, ['\n'] = true, ['\r'] = true }; if (ch < 0 || ch >= 256) return false; return whitespace[ch]; } 其它像 snprintf、inline、variable-length array (例如 int array[n]) 也很實用。

signal 小知識

TLPI 長知識。 signal 設定和 forkfork 後會繼承 signal 全部設定, 而 exec 後會保留 ignore 的設定, 但因為 address space 不同, 會將有設過的 signal handler 改回 default handler(見 man 7 signal)。先送 SIGTERM 再送 SIGKILL 砍程式若程式有照標準寫, 可能會有 SIGTERM 的 handler, 在收到這 signal 時做些清理動作 (砍暫存檔、釋放資源等), 再自我了結。而 SIGKILL 就直接掛了。所以, 先給人家一個機會掛得優雅一些, 若對方拒絕的話, 再狠一點直接掛了它。 附帶一提, ctrl + c 是送出 SIGINT。 執行 signal handler 的時機 process 得先拿到 CPU 才能執行 signal handler, 換句話說, 若 process P 處於 stop 的狀態 (D、S、T), 或系統太忙撥不出時間給它, 即使已送出 SIGTERM 給 P 了, P 也無法處理。而送 signal 的一方只能由 signal system call 的返回值判斷 P 仍活著, 有成功送出 signal, 無法得知 P 何時能處理它。 特別的是, SIGKILL 一送出對方就掛了, 不用取得 CPU 再掛掉。沒看到有特別的文獻指出 SIGKILL 這點特別的行為。man 7 signal 只有指出「The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored」。就實用角度來看, 讓 SIGKILL、SIGSTOP 立即生效, 確實比較合理。 SIGHUP 的行為terminal 斷線 (hangup) 時, terminal 的主控 process 會收到 SIGHUP。而它的預設行為是終結程式, 所以關掉 terminal 時, 裡面的程式會直接結束。 bash 和 ksh 在結束的時候會送 SIGHUP 給背景程式, 若背景程式也沒寫 SIGHUP handler, 那它們也會一起結束。這是關掉 terminal, 全部程式會一起結束的原因。 要避免這個行為, 可用 nohup 執行程式, 它會做 I/O 重導、執行…

Linux 的 capability

昨天看 TLPI ch39 才知道 Linux 有 capability 可依項目授權, 不用像以前那樣, 使用 setuid 一次大放送。像 passwd 就是 setuid 的典型使用情境, 要讓所有使用者能改自己的密碼, 所以他們要有權限存取密碼檔, 但又不能讓使用者做超出他們該做的事。於是, 使用 setuid 的程式需要很小心地設計, 避免給太多權限, 或是被 cracker 攻破取得 root 權限。 看到 capability 如此地威, 不禁納悶有多少程式有使用 capability, Scott 提供一個不錯的找法: 用 package 管理套件找出有那些程式依賴 capability 的套件, 就知道了。Ubuntu 對應的指令是 apt-rdepends -r libcap2, 也不算少, 有 100 多個套件用到 libcap2。

用 strace 找出 Ubuntu 如何提示未安裝的指令

在 Ubuntu 下執行指令後, 若沒有安裝指令的話, 會出現提示:$ apt-rdepends The program 'apt-rdepends' is currently not installed. You can install it by typing: sudo apt-get install apt-rdepends 但若直接用 bash 執行, 卻不會有這效果:$ bash -c apt-rdepends bash: apt-rdepends: command not found 以前覺得很好奇, Ubuntu 怎麼做到這件事的, 知道 strace 以後, 追這類原因簡單許多, 只要有輸入和輸出訊息, 就可夾擊出一些線索。 在 terminal 1 輸入 echo $$ 取得該 bash 的 PID 在 terminal 2 輸入 sudo strace -obash.trace -f -s512 -p PID -obash.trace 表示將輸出存到 bash.trace, 訊息很多, 通常都會寫到檔案裡 -s512 表示輸出訊息最多到 512, 預設行寬有點短, 之後不方便找輸出的訊息 -f 表示一起追蹤 child process, 這點很重要, 沒加 -f 就追不到 bash 使用的其它子程序, 而關鍵就在 bash 叫起的子程序 -p PID 表示追踪其它 process, 照理說同一個使用者不用 root 權限應該也能看, 不知為啥不通 在 terminal 1 輸入 apt-rdepends, 因為 strace 有用 "-f", 速度會慢很多。等待指令完成 在 terminal 2 按 Ctrl+C 中斷 strace, 觀察 bash.trace 搜一下 "apt-rdepends" 會看到 bash 在嘗試各種 path 後都找不到檔案:16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT... 16520 stat("/usr/local/sbin/apt-rdepends", 0x...) = -1 ENOENT... 16520 stat(&qu…

gdb 如何找到 debug symbol

先前在《追踪 glibc 裡的程式》提到自己如何亂試, 試出讓 gdb 讀到 debug symbol。昨天聽 Scott 說明, 才知道背後是怎麼一回事。 在 Ubuntu 下以 libm 為例, 在 /lib/x86_64-linux-gnu/libm-2.13.so 裡面, 先看一些相關的 section header:$ objdump -h /lib/x86_64-linux-gnu/libm-2.13.so | grep gnu /lib/x86_64-linux-gnu/libm-2.13.so: file format elf64-x86-64 0 .note.gnu.build-id 00000024 0000000000000238 0000000000000238 00000238 2**2 2 .gnu.hash 00000fa4 0000000000000280 0000000000000280 00000280 2**3 5 .gnu.version 00000298 00000000000038da 00000000000038da 000038da 2**1 6 .gnu.version_d 0000005c 0000000000003b78 0000000000003b78 00003b78 2**3 7 .gnu.version_r 00000030 0000000000003bd8 0000000000003bd8 00003bd8 2**3 27 .gnu_debuglink 00000014 0000000000000000 0000000000000000 000840e4 2**0 幾個重點 .note.gnu.build-id 表示 binary id, 之後用來比對 debug symbol 是否出自 shared lib。看起來 Fedora 在找 debug symbol 時, 有用到 binary id; 而 Ubuntu 沒有的樣子, 我用 hexedit 亂改這個 section 的值, 仍能找到 debug symbol .gnu_debuglink 指向包含 debug symbol 的檔案, 若用 hexed…

iPad 升級到 iOS 5

不知是否因為和 command 一樣人品不好, 用 Apple 的產品總遇到一堆鳥事, 讓我對 Apple 產品難以建立好感。 照官網和 google 大部份人的說法來看, 就 iTunes 升到 10.5, 將 iPad 接上去, 在 iTunes 裡按 check for update 即可。但 iTunes 回報 This version of the iPad software (4.2) is the current version。 有人提到可嘗試手動升級, 下載好 firmware 檔後, 照著指示手動選檔案 restore, 結果出現 "this device isn't eligible for the requested build"。google 看到有許多人提到類似問題, 但有些是 jail break 造成的, 提到的解法看來也不適用於我的情況, 況且我用的 iPad 應該沒 JB。 最後在 restore mode 下, 成功地升到 iOS 5.x 了, 太感謝這個影片, 操作方式講解得非常清楚: 《How to Enter DFU Mode | Restore Mode - Get Out of DFU Mode | Restore Mode - Fast, Safe & Easy - YouTube》

自行編譯含 debug symbol 的套件 (package)

對函式庫 X 來說 X-dev 表示讓開發者用的, 有裝 header、文件之類的 X-dbg 內容類似 X, 不過有留 debug symbol比方說 libjpeg, 三種的描述如下: libjpeg62 - The Independent JPEG Group's JPEG runtime library libjpeg62-dbg - Development files for the IJG JPEG library libjpeg62-dev - Development files for the IJG JPEG library裝了 X-dbg 後什麼事也不用做, gdb 自己會優先用含 debug symbol 的版本。不過若官方沒提供供 X-dbg 的話, 就得自己編。參考官方文件《HowToGetABacktrace - Debian Wiki》, 做法如下: $ apt-get install build-essential fakeroot gdb $ apt-get build-dep X $ DEB_BUILD_OPTIONS="nostrip noopt" fakeroot apt-get -b source X $ dpkg -i X.deb針對上述指令, 補充幾點 編好套件後, 可用 dpkg -c X.deb 先看裡面裝了什麼 用 objdump --source FILE 查看裡面有沒有含程式碼, 有的話才表示確實有含 debug symbol。這個作法比用 file FILE 看是否有 strip 更確實。二進位檔有可能沒含 debug symbol 也沒 strip。2016/02/16 更新遇到 "trying to overwrite ..." 的錯誤 時,可以用 sudo dpkg -i --force-overwrite X.deb 強制安裝。

Python debug 的方法

有別於 C/C++, Python 通常會有原始碼, 除非套件提供者無腦地用 egg 包裝, 不然應該滿好改程式碼的 (我討厭 egg!! 其實這有雙關, 不過 ...)。再加上使用 virtualenv 擁有自己安裝的函式庫, 這樣和別人共用 server 時, 不用擔心改函式庫的程式會影響到別人, 相當方便。在這樣方便改程式的情況下, 通常我會採下列三種方法之一來觀察程式 (或除錯):
使用 IPython在要觀察程式的地方寫入
import IPython IPython.Shell.IPShellEmbed(argv=[])() # 舊版 IPython.embed() # 新版 我甚至寫了個 vim 巨集展開這段。
使用 pdbimport pdb; pdb.set_trace() 效果類以上一個, 不過應該是 ipython 更易操作。或許也可考慮用 IPython pdb。習慣用 gdb 看 backtrace 的話, 用 pdb 滿直覺的。 使用 logger配合 decorator 的語法, python 要觀察特定函式容易許多, 如這篇提到的做法, 寫個 decorator "log", 配合 lazy initialization 的方式設定 logging。之後只要在有興趣的幾個函式上加個 @log 即可。
這個作法應用的範圍更大, 像在 WSGI 沒 console 可用, 也不能輸出到 STDOUT, 用這招就沒問題。還有, 這個作法也可在 production 環境記錄關鍵函式的執行速度, 協助 profiling。
題外話若是要除錯的話, 視情況用 unit test 會更好, 原因見《為什麼要寫 unit test?為什麼要先寫測試?》。在見識過 C/C++ 的困難處後, 覺得在 Python 的環境裡寫 test 實在是太輕鬆寫意了, 雖說寫 test 確實還是有它的難處, 不過該寫的東西之前寫得差不多了, 有興趣的人請翻翻舊文。

VirtualBox 改變 vdi 的大小

花了一整個晚上的血淚談。 注意事項 目前 (4.x) 只支援在沒有 snapshots 的情況下改變 vdi 大小 若目前有用 snapshot, VBoxManage 也不會阻止你改變大小, 只是改完就沒救了, 會無法刪掉最早的 snapshot, 推測是因為硬碟上的 vdi 檔案資訊和 snapshot 內的 vdi 不合。可以刪除中間的 snapshot, 因為那沒有涉及硬碟上的 vdi 檔擴大 vdi 關掉 guest OS 砍掉所有 snapshot, 只剩 current state: 在命令列下使用 VBoxManage modifyhd --resize NUM_MB /path/to/vdi 用 Ubuntu CD 開機 (現在已沒有 "live CD" 的名稱了, Ubuntu 安裝光碟本身就可當 "live CD"), 使用 gparted 調整硬碟大小 重開 guest OS, 可用 df -h 確認實際大小確實有改變 (或用 sudo fdisk -l /dev/sda 看 partition 資訊)。若不幸在砍 snapshot 前先用 VBoxManage 改了大小, 就無解了。目前只有看到一位仁兄用 Mac 的 Time Machine 還原到他做蠢事前的狀態, 解決這個砍不掉的問題 ... 。我用另一台電腦的 VirtualBox 實驗以上步驟沒有問題, 確認問題出在我先改了 vdi 大小。
縮小 vdi我沒試過這個, 順便筆記一下 VBoxManage --resize 只支援擴大, 要縮小的話要換指令 先在 guest OS 針對要縮小的 partitoin, 用對應的工具重整 partition (zerofree on Linux) 使用指令 VboxManage modifyhd --compact /path/to/vdi參考資料下次用這類 VM 的指令, 一定要熟讀手冊再下手啊 ... 《VBoxManage modifyhd》《Trivial Proof: Resizing a VirtualBox Virtual Hard Disk》《virtualbox.org: Can't delete top snapshot or expand HD size in VB 4…

gdb 初步心得

對初學者來說, 最好有個針對常用情境的簡單指南, 之後有閒再看落落長的教學。這裡列一下最近常用的功能, 之後再慢慢更新。
前置動作 gcc/g++ 編譯時要加 -g 要觀察用到的函式庫, 則要裝 x-dbg 版 (如 libjpeg62 -> libjpeg62-dbg), gdb 會優先載入 debug 版函式庫。並且需要用 directory 載入原始碼, 詳情見《追踪 glibc 裡的程式》的說明。 若原始碼的位置和當初編 binary 時的位置不同 (常有的事), 可用 set substitute-path from to 做路徑字串取代。 可用 objdump --source FILE 確認是否真的有編到 -g。有的話可以在輸出裡看到程式碼。雖說通篇我都寫 gdb, 但是 cgdb 好用許多, 推薦使用。聽 command 說 vimgdb 更好用, 不過要 patch vim 後才能用, 就 ... 先備忘吧。
執行方式從頭執行 bash> gdb --args PROGRAM PROGRAM-ARG1 ... gdb> start # 進入 main 後停下來檢查掛點原因 (參考《產生 core dump 的方法》確保有 core dump) bash> gdb PROGRAM CORE gdb> bt 20 # 看掛掉的 call stack 最底層 20 個 function call通常載入 PROGRAM 讀 debug symbol 較花時間, 我習慣用 gdb PROGRAM 進 gdb 後, 再用 core CORE 指令看不同的 core dump。
若希望從頭重來, 有設好中斷點就用 r, 沒有則繼續用 start, 不用離開 gdb, 可簡省載入 PROG 的時間。
設中斷點 b LOC # 設中斷點, 或用 cgdb 直接在程式視窗按空白鍵 i b # 列出全部中斷點 d NUM # 移除編號 NUM 的中斷點 save breakpoints FILE # 存下目前設的中斷點到檔案 FILE so FILE # 載入之前設的中斷點dis 1-10: 暫停使用 breakpoints 1-10。ena 1-10: 恢復使用 breakpoints 1-10。關於 LOC: 見《Specify Lo…

追踪 glibc 裡的程式

這篇是一堆試誤心得的中間記錄, 使用的版本是 Ubuntu 11.04。 失敗的作法 在 link 時, 用 -L/usr/lib/debug/lib/x86_64-linux-gnu/ 改變 link 到的 libc.so, 但沒有效果。用 strace -e open 觀察 gcc 做的事, 發現是因為 /usr/lib/debug/lib/x86_64-linux-gnu/ 下沒有 libc.so, 而是 libc-2.13.so。之前沒學清楚 -L 和 -l 的細節, 耍笨。 由 man ld.so 得知可用 LD_LIBRARY_PATH 或 LD_PRELOAD 在執行期換掉 libc.so, 但是也沒有效果。用 LD_PRELOAD 換成 debug 版 libc.so 時, 跑 gdb 會 segmentation fault成功的作法前置作業$ sudo aptitude install libc6-dbg # 取得有 debug symbol 的 libc.so$ apt-get source libc6-dev # 取得原始碼目錄 eglibc-2.13執行 $ gcc myprog.c -g -o myprog $ gdb myprog $ directory /path/to/eglibc-2.13/stdio-common/ $ start # 跑到 main 就停下來然後 gdb 會神奇地去找含 debug symbol 的 libc, 後面就可以用 step 進入 glibc 的函式。不知這個行為寫在那裡, 或許可以從 gdb 原始碼找出來吧。若有進入但說找不到原始碼, 表示沒有告知 gdb 正確的原始碼位置, 到 eglibc-2.13 下找一找, 再回來用 directory 設位置。 另外在用到 sqrt()、log() 時也是如此, 照一樣的編法 gcc myprog.c -g -lm -o myprog, 然後在 start 後, gdb 會去找 debug 版的 libm.so。不過要記得多執行 directory /path/to/eglibc-2.13/math 載入 math 的原始碼, gdb 才能列出原本的程式。用 ldd 觀察 myprog 也驗證原本的執行檔將 libc 和 libm 連到沒有 debu…

寫 blog 的目的

一開始寫這個 blog, 是為了備忘以及分享一些小心得。一兩個月才寫個一篇主站的文章, 很多小東西就這樣流失了, 有點可惜。 昨天和 Scott這裡聊到寫這 blog 的目的, 順手貼過來:我寫 blog 除備忘外, 主要是想記錄「思考解決問題」的路徑。比方說「了解背後運作原理」、「用什麼工具觀察出背後運作機制」、「從什麼角度思考可能的原因並做驗證」。有時是缺少背景知識、有時是缺少關鍵工具, 以致於無法完成一個看似困難實則簡單的事。

以前很想知道某些問題怎麼解, 但是太拉里拉雜, 很難問, 問了別人, 別人只會說答案, 也不太知道怎麼說明背後的思路, 已成為他們的習慣了。 沒想到這樣隨手寫了兩年, 意外地寫出新的心得, 才發覺我想寫的東西為何。

一秒設好慣用的環境

sudo apt-get install git && git clone git://github.com/fcamel/configs.git && cd configs && ./install.sh 不是想發一行文, 而是想強調 open repository 真方便。

熟悉系統工具好處多多

記一下以前很困擾, 現在秒殺的小事。 更新這篇的時候, 忘了函式庫用的 man page 裝在那個 package。以前就會想辦法 google, 運氣好一下會找到, 運氣不好會多找一會兒。 這回我想到新作法:$ strace -e open man 3 printf > /dev/null # 發現是讀 /usr/share/man/man3/printf.3.gz $ dpkg --search /usr/share/man/man3/printf.3.gz # 找到套件名稱 manpages-dev $ aptitude show manpages-dev # 確認描述符合, 收工

從 C 呼叫 C++ 函式的過程理解程式編譯、連結的原理

今天做了一個錯誤的決定, 想說在一堆 C 程式裡呼叫另一堆 C++ 程式。邊弄邊學, 最後發現什麼都沒改的情況, 改用 g++ link 原本的 C 程式就會爆炸。想想還是撿要用的一小部份程式出來, 另寫 C 的程式比較單純。不過也藉這機會, 才讓我真的搞懂這之中發生什麼事。 先附上要用的範例, 再來慢慢廢話, 沒耐心的人直接玩範例可能就懂了。 原始碼/* b.h */ #ifndef _B_H_ #define _B_H_ #ifdef __cplusplus extern "C" { #endif int add(int a, int b); #ifdef __cplusplus } #endif #endif /* b.cpp */ #include "b.h" int add(int a, int b) { return a + b; } /* a.c */ #include <stdio.h> #include "b.h" int main(void) { printf("%d\n", add(3, 5)); return 0; } 編譯方式$ gcc -c a.c $ g++ -c b.cpp $ g++ a.o b.o -o a $ ./a # 輸出 8 說明《How to mix C and C++  Updated! , C++ FAQ》將 C 和 C++ 互相呼叫的各式注意事項寫得相當清楚, 不愧為 FAQ 啊! 其中在 C 呼叫 C++ 的函式時, 要注意幾點 C++ 有 name mangling, 也就是不管是 class method 還是 function, 名稱 X 編出來都不會是 X, 而 C 通常沒有 name mangling。不過不管 C 是否有 name mangling, 關鍵是讓 C 能用 C 的方式認得 C++ 的 symbol。 先明白編譯、連結時發生了什麼事, 從這裡出發, 可知要讓 C 呼叫到 C++ 的函式, 要從幾個地方下手: C、C++ 都需要它們各自的 header, 才能編成 object code。編譯時 symbol 仍不用存在 用 g…

透過 /proc/PID/task/ 觀察 multi-thread 狀態

為了搞清楚程式、用到的函式庫等到底有沒有用到 multi-thread, 以及各 thread 的狀態如何變化等, 查了一下相關資訊。 在 man ps 裡沒看到適合的參數, ps -eLf 沒有列出 thread 的執行狀態。這頁 man page 太複雜, 看一看決定改看 man proc, 反正 ps 也是讀 /proc/PID 的資訊。 順著 "thread" 的關鍵字掃完 man proc, 看到 /proc/PID/task/ 有記錄擁有的 thread 資訊, 其中每一個目錄 /proc/PID/task/TID/有類似 /proc/PID/ 的資訊, 而 /proc/PID/task/TID/status 裡面有我要的東西。所以就寫了個小程式 thstate 來觀察行為, 試用後頗順利地達成階段性目標。/proc 真是好東西, 為啥以前都沒留意到呢 ...。2012/01/05 更新 看到 wens 提到可混用 ps aux -L, 這樣就有 LWP (thread id) 了, 不過沒有 PPID。又查了一下 man page, 發現可用 -o 自訂格式, 這樣用 ps awxo pid,ppid,pgid,nlwp,lwp,stat,comm就有符合我需求的材料了, 再配合 grep PID 即可。不過既然已寫好了客製化的 script, 還是用 script 比較順手一些。 2012/01/05 再更新 看到 Scott 提到可用 ps u -Lp PID, 終於湊出「終極」指令。上面的描述就當記錄吧, 也反映出軟體開發的常態, 工程師常常找不到想要的東西就開始自己重造輪子, 造好後才發現原來其實有更好的方案, 只是之前沒有看到 ...。 ps -Lo pid,ppid,pgid,nlwp,lwp,stat,command -p PID

debug info 和 optimization

要知道程式有沒有編進 debug info, 可用 objdump --source BINARY | less 觀察。若有看到程式碼的內容, 表示有編進去。另外要注意的是, 程式本身和用到的函式庫編法可能不同, 可能程式有加 -g, 但函式庫沒有。造成看得到一些函式內容, 卻看不到另一些的內容。 另一個要注意的點是, 今天聽 wens 說才知道編程式時可以同時用 -O 和 -g。這樣做的好處是, 執行速度快, 接近 production 運作的情況。我自己試用的感覺是, 有些 bug 在 debug mode 可能不方便重制出來, 還有 debug mode 通常跑比較慢, 所以用 -O -g 還滿方便的。但不幸的是, 兩者合用會有一點副作用, 在 man gcc 裡有說明:GCC allows you to use -g with -O. The shortcuts taken by optimized code may occasionally produce surprising results: some variables you declared may not exist at all; flow of control may briefly move where you did not expect it; some statements may not be executed because they compute constant results or their values were already at hand; some statements may execute in different places because they were moved out of loops.

Nevertheless it proves possible to debug optimized output. This makes it reasonable to use the optimizer for programs that might have bugs. 當程式是用 -O 和 -g 編出來的時候, 用 objdump --source 會看到程式碼怪怪的, gdb 執行順序也會怪怪的, 執行行為和程式碼有時有一點出入, 還…