發表文章

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

在 x86-64 上對 system call 使用 conditional break

在了解目標模組的動態行為時, 最近覺得從模組的 public interface 設中斷點是滿不錯的作法。廣意來說, system call 則是最一般化的 public interface, 從 system call 回頭夾擊目標也滿有效的。比方像這篇用 strace 找到切入點。 若想獲得更多資訊, 可以用 gdb 在 system call 上設中斷點, 一步步從退回來找到呼叫者、傳遞的資料等更多訊息。但在尋找 write、send 這類廣為使用的 system call 時, 被呼叫的次數過於頻繁, 就有點難找了。 查了一下, 看到這篇提到從 register 設定 conditional break 的方法:(gdb) b write if 1==$rdi # stop only when write(1, ...) Scott補充了以下的資訊: 要在 syscall argument 上設條件,且要 portable 以目前的工具不容易。用 gdb 的話需能背出『x86-64 上參數依序擺在 rdi, rsi, rdx, rcx, r8d, r9d』。 x86-64 上特別好記,因 syscall 與 一般 function call 被設計成將參數擺在同位置。ARM 上還有 64bit 參數開頭一定擺在偶數暫存器如 r0-r1, r2-r3 而不擺在 r1-r2 等規則。 目前 gdb 沒有將 syscall parameter 存在如 $syscall_arg0 的 convenience variable 中,否則寫: catch syscall write if $syscall_arg0 == 1 即可。 用 "system call calling convention" 當關鍵字查到《What are the calling conventions for UNIX & Linux system calls on x86-64》, 文中有許多相關資訊, 就先備忘吧。

善用 prctl(PR_SET_NAME, name) 協助 debug multi-thread

參考文章: Name your threads man 2 prtcl 說 prctl(PR_SET_NAME, char*) 會改變 process name, 但是它其實改的是 thread name。使用 gdb 的 info thread 會顯示 thread name, 這對於除錯 multi-thread 很有幫助。需要注意的是, 小心別改到 main thread 的名稱, 避免其它相關 kill 指令或自己寫的 script 失效。2012-11-25 更新經 Scott 提醒, 使用 pthread_setname_np(pthread_t, const char*) 更頗當, 且可用來設別的 thread 的名字。注意參數是 pthread_t 不是 tid, 還有不知為何, Ubuntu 12.04 下沒有它的 man page, 但 /usr/include/pthread.h 有它的宣告, 且編譯連結試用後, 也沒有問題。

善用 shared library visibility 減少程式之間的衝突

關於 static library 和 shared library 的基本知識: fcamel 技術隨手記: 解決 undefined symbol / referencefcamel 技術隨手記: ld, ld.so 和 ldconfig 的行為 static library 沒有特別的, 但是 shared library 有不少神奇的功能可用。 想像一個情境, 有四個獨立的專案 A, B, X, Y 四者, 其中 A 用到 B, X 用到 Y。若 A 也想用到 X, 但 B 和 Y 有重覆的 symbol, 得在編譯和連結時動點手腳, 才能過關。 上面情境的示意圖:A -> B | v X -> Y 假設編譯 B, X, Y 時產生 static library, 在產生執行檔 A 時, ld 會抱怨某些 symbol 衝突 (在 B 和 Y 裡面)。 static library 只是一堆 object file 的集合體, 就像 tar 包覆一堆檔案一般, 只是 static library 裝的是 binary 並且可以加入 index 檔。也因此 static library 通常比 shared library 大包, 因為 static library 不管 (也無法知道) 每個 symbol 最後是否會被用到, 總之就先留著它。 shared library 多了不少功能可用, 其中一個實用的功能是 visibility。可透過編譯 (非連結) 時下參數隱藏不需要的 symbol。以上面的例子來說, 若 A 只用到 X 且不會用到 Y, 那麼, 改用 shared library 的 visibility, 可以避開 B 和 Y 衝突的 symbol, 作法如下所述。 為簡化描述, 以下用單一檔案表示一個專案: 1. 產生 libXY.so$ g++ -c Y.cpp -fPIC -fvisibility=hidden $ g++ -c X.cpp -fPIC $ g++ -shared -o libXY.so X.o Y.o 注意: 編譯 Y.cpp 時多了 -fvisibility=hidden, 表示除非程式內有用 g++ 特有的語法指定 visibility (__attribute__((__visibi…

GNU Makefile 雜項語法備忘

一般的 tutorial 教得都差不多卻少了一些我想知道的語法, 以下是自己備忘用的語法, 對於讀別人的 Makefile 時有幫助 $ cat Makefile var ?= xxx # assign var = xxx if var is not assigned .PHONY: all # tell make that "all" is not a file all: # first target is the default target @echo make-all # @ means do not display the cmd @echo all: x $(x) all: b # all depends on b b: @echo make-b # must use TAB to indent actions @echo b: var $(var) all: a # now all depends on a and b a: x:= 3 # set x = 3 only in this context a: @echo make-a @echo a: x $(x) reverse = $(2) $(1) # define a function c: @echo c: x y @echo c: $(call reverse, x, y) # use "call" to use # defined functions 範例輸出$ make make-a a: x 3 make-b b: var xxx make-all all: x $ var=ooo make b # override var make-b b: var ooo $ x=9 make # set x = 9 globally make-a a: x 3 # note that x is still 3 make-b b: var xxx make-all al…

查詢 Ubuntu package 編譯時的參數

GDB Python API 提到要在編譯 gdb 時有加 --with-python才會支援此功能, 想確認編譯 Ubuntu 12.04 的 gdb 時, 是否有下參數 --with-python, 參考資料: compiling - Where can I find the configure options used to build a package? - Ask Ubuntu 摘要回覆的作法如下 抓原始碼看編譯規則$ apt-get source gdb $ cd gdb-7.4-2012.04/ $ vi debian/rules 然後看到兩組 configure rule, 一個對到 --without-python, 另一個對到 --with-python, 嗯..., 雖然看起來比較像後者, 還是有些懷疑 查官方 build log$ apt-cache showpkg gdb 得知名稱是 7.4-2012.04-0ubuntu2, 接著到 https://launchpad.net/ubuntu/+source/gdb 查詢: click 7.4-2012.04-0ubuntu2 updates (main) 2012-05-15 click Builds 下的 amd64 click Build status 下的 buildlog 查詢 ./configure 找到 --with-python, 所以應該是有加上 --with-python 才是

以使用 libsqlite 為例說明如何找到程式的進入點

一直對於如何善用 runtime 資訊找到程式進入點很有興趣, 終於有個不錯的小例子。 目標 假設要觀察的專案有用到 sqlite 儲存資料, 從使用方式知道一開始會先載入 sqlite 的內容, 現在想找到程式讀取第一筆資料的進入點。 第一步先觀察程式如何使用 sqlite, 是用 shared lib 或是直接包含在程式裡:$ ldd PROG | grep sqlite Case 1: 沒有結果, 表示 sqlite 的實作直接含在 PROG 裡面找出和 sqlite 相關的 API:$ nm PROG | grep sqlite | awk '{print $NF}' | xargs c++filt 或是查官網文件也成, 不過個人覺得 nm + c++filt 這招比較方便也比較酷。 觀察一下後, 得知開檔的 API 有: sqlite3_open, sqlite3_open16, sqlite3_open_v2, 之後用 cgdb 執行程式, 都設中斷點, 就結案了。 Case 2: 有 grep 結果, 表示在 shared lib 裡若不想看官方文件, 也想來個動態搜集 API, 可用 ltrace:$ ltrace -l /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 PROG 其中 libsqlite3 的路徑是從 ldd PROG 得知的。 安裝需要的 debug symbol, 以做進一步觀察:$ aptitude search sqlite | grep dbg # 找到 libsqlite3-0-dbg $ sudo aptitude install libsqlite3-0-dbg 再來就用 cgdb 執行程式, 準備設中斷點, 結案。 相關參考資料:追踪 glibc 裡的程式gdb 如何找到 debug symbol

以 abstract class 為例說明 C++ 編譯器和連結器的運作

ps. 經 Scott 提醒發覺內容有誤, 已更新內文。Effective C++ item 7 提到, 若想使用 abstract class A, 建議這麼做:// a.h class A { public: virtual ~A() = 0; // 注意只有宣告沒有實作, 是 pure virtual }; 但是還是得另外提供 ~A() 的實作:// a.cpp #include "a.h" A::~A() {} 不然連結時會出錯 以 class B 繼承 A 為例, 假設程式如下:// b.h #include "a.h" class B : public A { }; // b.cpp #include "b.h" // Nothing. 先來看少了 a.cpp 的情況。由於編譯時只會看 header, 編譯器會阻止任何嘗試生成 A 的程式, 這是我們想要的好結果, 利用宣告含有至少一個 pure virtual 函式的作法, 得到 abstract class 的性質。用 destructor 是不錯的選擇, 不會因此增加不必要的 virtual method, 況且 「base class + 使用多型」的情況要有 virtual destructor, 和使用 abstract class 的目的相合。 然後, 來看編譯 b.cpp 得到的 b.o。不論是否有呼叫到 B() 或 ~B(), 編譯器都會產生 B() 和 ~B() 的 binary code。編譯器對照 a.h 和 b.h 覺得 access level 沒有問題, 不會有編譯錯誤, 其中 ~B() 會呼叫 ~A(), ~A() 的 symbol 還未定義, 等待連結時補上。 但是, 若少了在 a.cpp 內 ~A() 的實作的話, 連結時會發覺找不到 ~A() 的 symbol, 於是有 link error。就語法來說這結果挺怪異的, 宣告為 pure virtual 卻又得提供實作才行。反之, 從實作面來看 compiler 和 linker 怎麼運作, 就不會覺得奇怪。又一次讓我覺得要理解 C++ 的語法, 得從運作的方式來理解才行。只看語法的話, 不太容易理解和記憶。 關聯文章:從 C 呼叫 C++ 函…

編譯或連結錯誤的檢錯流程 (初版)

開發環境是 Ubuntu。先寫篇草稿, 日後慢慢補完。 找不到 xxx.h檢查是否有 xxx.h$ sudo updatedb && locate xxx.h 若 OS 內沒有的話, 看看要裝什麼套件才有$ sudo apt-file update && apt-file search xxx.h 確定有檔案後, 檢查使用 libxxx 需要用的編譯參數為何$ pkg-config --cflags xxx 若不確定 pkg-config 參數的名稱, 使用 apt-file search 查到的 package 名稱 "PKG-X", 查詢 PKG-X 包含的檔案$ dpkg -L PKG-X | grep pkgconfig 比對編譯時用的參數, 是否有含到正確的 include path (參數 -I), 沒有的話, 可能是 makefile 出錯, 檢查產生 makefile 的設定檔是否正確 若 OS 內沒有 xxx.h, 也沒有任何一個套件含有 xxx.h, 可能目前 OS 太舊, 用 Ubuntu Packages Search 查詢 xxx.h, 確認是否新版的 OS 才有 xxx.h 相關文章解決 undefined symbol / reference

C++ 隱藏共用的 helper class

以前寫 Java 時滿習慣用 helper class 分擔主 class 的一些工作, 簡化主 class 本體的複雜度, 或是提供輔助用的 data object, 但只在內部使用, 不讓外部使用。在 Java 裡滿直覺的, helper class 宣告成 package-private 即可。但在 C++ 的情況, 明白 compiler 怎麼編譯 C++ 程式後, 才想通作法。 程式如下: a.hclass X; class A { public: A(); void print(); private: X* x; }; class B { public: B(); void print(); private: X* x; }; a.cpp#include <iostream> #include "a.h" class X { public: void print(const char *s) { std::cout << s << std::endl; } }; A::A() : x(new X()) {} B::B() : x(new X()) {} void A::print() { x->print("A"); } void B::print() { x->print("B"); } int main(void) { A a; B b; a.print(); b.print(); return 0; } 兩個關鍵: a.h 需要知道 X 是 class, 不是天外飛來的不知名符號, 必須有 forward declaration class X。include a.h 的程式沒有 X 的完整宣告, 自然也無法使用 X class A 和 B 不能宣告 member field 為 X x, 必須用指標, 因為在宣告 A 或 B 的物件時, compiler 要知道該在 stack 上配置多少空間, 但 a.h 裡沒提到, 所以除了 a.cpp 以外引入 a.h 的程式也不會知道, 這和 Pimpl 宣告實作物件為指標是同樣的原因。用指標的話則無此問題, 指標…