在 Linux 下開發 C/C++ 的新手指南
這篇是以 2012/01 寫的文章為底,配合近年來的經驗改寫的。方法大致上和當時差不多,但寫得更有系統和正確,還有增加新資訊。
預期目標讀者會使用 Linux 基本指令,嘗試回答以下問題:
- 如何解決 undefined symbol?
- 如何取得更多資訊除錯?
- 如果有效率地理解程式如何運作?
- 如何增進 Linux 系統程式知識?
- 有那些工具可追踪效能/記憶體/網路問題?
此篇文章可當作一份索引,需要時查閱一下,知道有那些東西可用,有需求時再深入研究。
系統套件管理工具
熟悉作業系統的安裝套件是首要之務,這樣才知道如何補足需要的 header、library,或是安裝含 debug symbol 版的函式庫以執行 gdb 觀察程式或除錯。How To Manage Packages Using apt-get, apt-cache, apt-file and dpkg Commands ( With 13 Practical Examples ) 有提到常用指令,網路上也能找到許多參考資料。
這裡列一些我常用的指令:
apt-get install PACKAGE
: 裝套件。apt-get source PACKAGE
: 取得套件原始碼。apt-cache search SUBSTRING
: 找套件。apt-cache show PACKAGE
: 顯示套件資訊。apt-file search SUBSTRING-OF-PATH
:搜尋含有目標路徑的套件。dpkg -l
:列出已安裝套件。dpkg -L PACKAGE
:列出已安裝套件 PACKAGE 內含的檔案。dpkg --search SUBSTRING-OF-PATH
: 從已安裝套件中搜尋含有目標路徑的套件。locate SUBSTRING-OF-PATH
: 搜尋完整路徑 (例如locate libm.so
→ 找到/usr/lib/x86_64-linux-gnu/libm.so
)。
Ubuntu 16.04 後開始,新的指令 apt 包含許多分散在不同指令的功能,這裡有許多例子。Fedora 對應的指令見《Fedora 和 Ubuntu 指令對照表》。
編譯、連結、執行
這裡有三個角色要分清楚: compiler、static linker (一般說的 linker) 以及 dynamic linker (又稱 runtime linker,負責在執行時載入 shared library)。
- 《解決 Linux 上 C/C++ 的 undefined symbol 或 undefined reference》: 說明編譯原始碼到執行檔的流程。先有觀念才清楚問題的環節,才能選對工具檢查問題。
- 《從 C 呼叫 C++ 函式的過程理解程式編譯、連結的原理》: 實例說明相關觀念。
- 《Linux 編譯 shared library 的方法和注意事項》: 說明 static linker。
- 《Linux 執行時尋找 symbol 的流程以及 shared library 相關知識》: 說明 dynamic linker。
- 《(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫》: 實例說明各個階段遇到的問題和解法。
理解程式運作
不管是除錯還是要借鏡別人的作法,都需要理解程式如何運作。善用工具可以更快了解。
- 《gdb 雜項心得》: 網路上有許多教學可以參考,這是我自己的 gdb 備忘。強烈建議使用 cgdb,簡易安裝 + 無痛上手,省下大量操作和讀碼的時間。
- 《如何在 Ubuntu 除錯第三方函式庫》: 了解如何取得程式碼和 debug symbol,甚至在第三方函式庫的程式裡加入 log 協助除錯。
- 《使用 strace 了解程式讀取資料的來源》: 觀察程式使用 OS 提供的 system call,有時可以直接得到答案,不用重編程式也不用 gdb 停住程式。
- 《用 SystemTap 追踪 user space 程式執行的流程》: SystemTap 通常用來追踪 kernel codes,而不需重編 kernel。用它追踪一般的程式也是相當方便。不用重編程式可以列出函式的 call flow、變數值、函式回傳值等。
- 《Linux 的 bash 找不到指令時,如何作到自動提示安裝指令?》: 實例演練使用一些工具找答案。
- 《在 Linux 理解大型 C/C++ 專案的輔助工具》: 介紹更多讀碼時的工具,包含在百萬行以上的專案瞬間找到 symbol 定義在那個檔案、產生 class hierarchy diagram、輔助手畫 class relation diagram 等。
- 《gcc 展開前置處理的技巧》: 找常數、函式定義的小撇步。
- 《軟體除錯技巧》: 這篇偏重在「內功心法」。長遠來看,這篇的觀念最為重要。
使用函式庫附的除錯功能
開發者通常會留有除錯的 log,可以查閱文件或在程式碼內留意如何開啟。像 ld.so 用環境變數 LD_DEBUG
設定除錯的 log 顯示範圍; 而函式庫 GTK+ 用 GTK_DEBUG
、 fontconfig 用 FC_DEBUG
。
熟悉 Linux 系統程式
《The Linux Programming Interface》 相當實用,讀 ch1 ~ 3 讓我補足不少基礎知識。依需求查閱相關章節相當有幫助。工作多年來,時常翻閱此書解惑。
本書最大的好處是,作者對每個主題循序漸進地提供鉅細靡遺的說明,不只是 POSIX 或 Linux 能做什麼,同時也會提供必要的基礎知識,像在 network programming 的章節,會補充說明 TCP/IP 的運作方式,而且容易吸收。
相較於在網路上搜尋,閱讀本書更能系統地了解 Linux 能與不能做什麼事,有利於判斷可行的方案。本書另一大好處是,每個章節都有附完整的程式碼,輔以執行結果,來說明系統的一些特性。像是「20.12 Signals Are Not Queued」,為了說明 signal 只有保證送出後至少會收到一次,但不是送幾次就收到幾次,作者寫了個小程式 A 送一百萬次 signal 給 B,B 則是在 signal handler 裡計數,結果在作者的這次實驗裡, B 只收到 52 次。
效能分析
- perf: 高效能的 profiling 工具。網路可找到許多教學。
- 我比較常針對要觀察的部份加程式觀察執行的時間。《Chromium 的 time API》提及如何得知目前時間 (TimeTicks) 和 thread 執行的時間 (ThreadTicks) 。執行一段程式前後同時用兩者記錄時間差,可以得知執行太久是 CPU bound (ThreadTicks 差距很大) 或是被其它東西卡住了 (TimeTicks 差距很大但 ThreadTicks 沒什麼變)。
- 簡單的問題可以用
gdb
中斷 process 「取樣程式熱點」,藉此判斷程式卡在那。作法是用gdb
attach process,執行backtrace
看 backtrace。然後執行continue
,等一會兒後打Ctrl+C
中斷,再看 backtrace。這樣重覆幾次,觀察 backtrace 是否有重覆的結構,也可找出瓶頸。 - 《Linux Performance Analysis and Tools》 有提及更多效能工具,像是用 htop 看 OS 的 CPU/memory 使用動態,用 iostat、iotop 看 I/O 使用動態、用 tcpdump 看網路封包等。
記憶體除錯
- AddressSanitizer (ASan) 可以用來偵測 memory leak、buffer overflow、存取已 free 的記憶體位置。不止錯誤指示清楚 (告訴你那個 thread 那個檔案那一行出錯),而且使用後效能只會變兩倍慢。雖然使用 ASan 需要重編所有程式,但是 memory leak / heap-use-after-free 很難除錯,通常選擇開啟 ASan 重編會比較快解決問題。
- gperftools (包含 tcmalloc) 有 heap profiler 可以看各個物件的記憶體使用量。我沒有使用過 heap profiler,留著備忘。
- 可以自己將模組切成使用各自的 worker thread 或覆寫 new/delete 函式 (或可兩者都作),這樣可以自行統計 worker thread 或各模組的使用量。依自己的應用統計會更準。
網路工具
用工具名稱查詢可以找到許多教學,這裡不細提。
- 用
tcpdump
/tshark
看網路封包內容。我自己使用的例子是《Linux 上 TCP QUICKACK 的效果》和《TCP maximum segment size 是什麼以及是如何決定的》。 - 用
ss
看 socket 狀態。像ss -xa
可看所有 UNIX domain socket。 - 用
mtr
看 trace route。這比ping
或traceroute
好用。方便看封包在什麼位置掉包率很高。 - 用
iftop
、iptraf
看網路流量。
Build System
開發專案時,不會直接呼叫 gcc/g++
編譯程式,而會透過某套 Build System 呼叫 gcc/g++
。像是老字號的 makefile。
這部份我沒什麼經驗,就跟著 Chromium 用,歷經 SCons、GYP (產生 Ninja)、GN (產生 Ninja)。GN 相當好用,若需要新寫一個獨立的 C/C++ 專案,我會考慮用 GN。題外話,我個人很喜歡 GN 和 Ninja 這樣的分工方式,簡單易懂好除錯。這篇說明 Ninja 誕生的背景和設計理念,滿有意思的。