前言
工欲善其事 必先利其器。
本篇將會搭建一個完美的Linux Kernel及其內核模塊的源碼閱讀開發環境。
當我們開始研究Android Kernel,想要優雅的閱讀源碼好像是一件費勁的事情。
因為Kernel源碼實在是太龐大了,打開一個c文件,想要詳細的研究研究,甚至上手寫兩句代碼,即沒有高亮提示,也沒有代碼跳轉。在這種情況下,想要理清楚內核源碼,相當不易。
用CLion?貌似不太行。
Kernel的構建體系是make而不是類似于LLVM的CMake。Clion直接打開Kernel源碼是無法被CLion解析的。
用Source Insight?貌似不是非常完美。對于Kernel源碼來說,很多函數symbols一樣,只是適用于不同架構罷了。Source Insight在跳轉的時候,全源引索,并不會幫我們加以區分。諸如此類的問題,Source Insight還有很多...強迫癥患者表示很難受~
用VsCode?貌似更不行?
VsCode對C/C++代碼的高亮提示,依托于C/C++ Extension Pack這個插件,我不清楚別人體驗如何,至少對我來說,這個插件有多爛,我都不想多做評價....
淺談代碼索引
1、為什么IDE集成開發環境可以完美的索引項目代碼?
參考于微軟的一篇文檔(https://learn.microsoft.com/zh-cn/visualstudio/extensibility/language-server-protocol?view=vs-2022#what-is-the-language-server-protocol),我了解到了語言服務器這個東西。
2、什么是語言服務器,有什么作用?
簡單來說,VsCode,VIM等等,都是文本編輯器的性質,可以讓我們愉快的編輯代碼。而clang,gcc等等,都是代碼編譯套件,可以將我們的源碼文件編譯出來。最后clangd,rust-analyzer等等,就屬于語言服務器,就是他們的工作,才使我們的源碼具備了高亮提示和代碼跳轉方面的功能。
其實,各大IDE可以完美的對代碼做解析服務,多半也是內部集成了語言服務器。
CLion對代碼索引的功能 貌似就是通過clangd實現的。
1、如何讓語言服務器運作起來?
我們現在知道了,想要對c語言的代碼做解析,就需要clangd這個語言服務器。那么,如何讓clangd運作起來呢?clangd對代碼做解析,需要compile_commands.json文件。
2、該文件是什么作用?
這個文件記錄著我們對源碼編譯時候的每個命令,用的什么編譯器,提供的什么編譯參數,鏈接了哪些庫,設置了什么宏,編譯的什么源碼文件等等。
如此,clangd就可以通過編譯命令,詳細的了解到整個編譯流程。
3、如何生成該文件?
對于make項目來說,常規來講,可以使用Bear來對源碼生成compile_commands.json文件。
make ## 原編譯命令 --> 不生成compile_commands.json
bear make ## 使用bear --> 生成compile_commands.json
對于CMake項目來說,這個相當簡單。
參考于CMake官方文檔(https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html),只需要設置CMAKE_EXPORT_COMPILE_COMMANDS為True即可在編譯后生成compile_commands.json文件。
set(CMAKE_EXPORT_COMPILE_COMMANDS True)
這里特別提一嘴,對于mk體系編譯的的項目 mk腳本,其本質上是make,但是對于安卓的來說,生成編譯描述文件非常簡單。官方有文檔(https://developer.android.com/ndk/guides/ndk-build#json)提供命令。
ndk-build GEN_COMPILE_COMMANDS_DB=true ## 構建的時候順便生成compile_commands.json
所以,理論上來說,搭建的這套代碼解析方案,不僅僅適用于Kernel源碼閱讀。也適用于各種交叉編譯。
敲定方案
毫無疑問 Kernel的源碼大多是c文件,以及少部分的匯編文件,設備樹文件......
1、明確一點,我們需要clangd支持。
首先看看VsCode有沒有clangd插件,搜了一下,果然有clangd插件(https://github.com/clangd/vscode-clangd)。
2、我們需要對內核編譯的流程,生成描述文件。
這里我找到一個項目,可以把已經完整編譯好了的內核解析出編譯描述文件出來:vscode-linux-kernel(https://github.com/amezin/vscode-linux-kernel)。
如何使用將在下面細說。
Kernel正好是make體系,理論上是可以用Bear的。由于我做教程的時候,已經把內核編譯結束了,實在不想重新編譯內核,理論上選哪個都一樣,我們只是要個編譯描述文件罷。
使用套件最終定型:
VsCode + clangd + 編譯描述文件 + C/C++ Extension Pack
雖然C/C++ Extension Pack相當的爛,但是部分C/C++方面的東西,還需他提供支持。
所以這里依然需要安裝該插件,后續在設置中禁用掉他的代碼提示服務,轉交給clangd插件即可。
關于clangd插件
這個插件會網絡下載clangd的bin文件,很多人可能網絡不佳,會下載失敗。
這里不需要擔心這個,我們只管安裝插件即可,ndk里面自帶的clangd就可以提供語言解析服務了,VsCode設置里面指定clangd路徑即可。
而且,貌似高版本clangd有什么大???
開始動手
1、完整編譯內核
這里以ACK(Android Common Kernel)為例。因為ACK的編譯最簡單,不會出亂七八糟的錯誤,Google基本上完善好了一鍵編譯體系。
參考Google官方文檔(https://source.android.google.cn/docs/setup/build/building-kernels)
這里默認讀者會換源或者其他手法保持網絡通暢。
步驟簡述:
① 初始化repo庫。
## 這里選擇common-android12-5.10分支
repo init -u https://android.googlesource.com/kernel/manifest -b common-android12-5.10
② 同步repo庫代碼。
repo sync
漫長的等待...
③ 選擇自己需要的config文件,開啟build.sh腳本。
這里以build.config.gki.aarch64為例。
BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
④ 等待一段時間后,內核即可編譯完成。
2、生成編譯描述文件
仔細觀察我們編譯的out目錄,會發現有很多后綴為cmd的文件,這些其實就是編譯過程中的臨時文件,包含了編譯命令等。
vscode-linux-kernel其實就是利用編譯后的cmd等文件做解析,生成編譯描述文件compile_commands.json。
步驟簡述:
① 進入內核源碼的根目錄,將vscode-linux-kernel項目拉下來:
cd common # 進入內核源碼根目錄
# clone vscode-linux-kernel項目到.vscode文件夾
# 該文件夾為vscode配置文件夾 類似于.idea
git clone --depth=1 https://github.com/amezin/vscode-linux-kernel.git .vscode
② 運行python腳本,并指定-O參數到編譯產出目錄。
python .vscode/generate_compdb.py -O ../out/android12-5.10/common/
ls -al | grep compile_commands.json
# -rw-r--r-- 1 kali kali 8888862 Nov 30 01:07 compile_commands.json
可以看到,源碼目錄下的compile_commands.json文件已經生成。
對于Out-of-tree編譯的內核模塊
詳細見原項目地址的Out-of-tree module development (https://github.com/amezin/vscode-linux-kernel#out-of-tree-module-development)
這一套代碼解析方案也完美的適用于Out-of-tree的內核模塊
3、配置VsCode
對于VsCode來說.vscode目錄下,是一些配置文件。
我們修改原項目的setting.json文件,內容修改成如下:
{
"files.associations": {
"iostream": "cpp",
"intrinsics.h": "c",
"ostream": "cpp",
"vector": "cpp"
},
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.formatOnType": true,
// 關閉 C/C++ Extension Pack 插件的提示 防止其與clangd沖突
"C_Cpp.errorSquiggles": "Disabled",
"C_Cpp.intelliSenseEngineFallback": "Disabled",
"C_Cpp.intelliSenseEngine": "Disabled",
"C_Cpp.autocomplete": "Disabled", // So you don't get autocomplete from both extensions.
// 指向clangd路徑
"clangd.path": "/tmp/NDK/ndk-r25b/toolchains/llvm/prebuilt/linux-x86_64/bin/clangd",
"clangd.arguments": [
// compelie_commands.json 文件的目錄位置
"--compile-commands-dir=${workspaceFolder}/",
// 讓 Clangd 生成更詳細的日志
"--log=verbose",
// 輸出的 JSON 文件更美觀
"--pretty",
// 全局補全
"--all-scopes-completion",
// 建議風格:打包(重載函數只會給出一個建議)相反可以設置為detailed
"--completion-style=bundled",
// 跨文件重命名變量
"--cross-file-rename",
// 允許補充頭文件
"--header-insertion=iwyu",
// 輸入建議中,已包含頭文件的項與還未包含頭文件的項會以圓點加以區分
"--header-insertion-decorators",
// 在后臺自動分析文件 基于 complie_commands
"--background-index",
// 啟用 Clang-Tidy 以提供「靜態檢查」
"--clang-tidy",
// Clang-Tidy 靜態檢查的參數,指出按照哪些規則進行靜態檢查
// 參數后部分的*表示通配符
// 在參數前加入-,如-modernize-use-trailing-return-type,將會禁用某一規則
"--clang-tidy-checks=cppcoreguidelines-*,performance-*,bugprone-*,portability-*,modernize-*,google-*",
// 默認格式化風格: 谷歌開源項目代碼指南
"--fallback-style=file",
// 同時開啟的任務數量
"-j=2",
// pch優化的位置(memory 或 disk,選擇memory會增加內存開銷,但會提升性能)
"--pch-storage=disk",
// 啟用這項時,補全函數時,將會給參數提供占位符
// 我選擇禁用
"--function-arg-placeholders=false"
],
}
注釋已經寫到相當清楚,這里不再贅述。
4、打開Kernel根目錄,等待索引結束。
我這里是ssh遠程連接的Linux虛擬機。
打開源碼根目錄后,隨便打開一個c文件,觸發VsCode的插件后,他會提示你,你的默認配置會被.vscode目錄下的setting文件覆蓋,是否確認:
這里當然選擇YES,使用我們配置的setting。
停留在c文件上面,等待一段時間,則會出現如下畫面:
左邊會出現.cache緩存目錄,里面不斷緩存著整個內核源碼的index索引。
左下角有一個indexing,意味著clangd解析整個源碼的進度。
當indexing解析結束后,打開內核源碼的任意c文件,都可以自由的跳轉頭文件,跳轉到函數定義和實現,智能提示函數和結構體等等。就和用IDE寫代碼一樣,非常絲滑舒服。
所有的索引都在一秒不到內可以完成,而且也不會高額占用機器性能。
甚至他還會結合編譯的config,對未開啟的config代碼塊不高亮顯示:
做一些潤滑
你發現會報很多無關痛癢的警告:
1、內核源碼根目錄下,新建一個文件.clangd,內容如下:
CompileFlags:
Add: [-Wno-declaration-after-, -Wno-int-conversion, -Wno-all]
Diagnostics:
ClangTidy:
Remove: bugprone-sizeof-expression
2、Reload Windows后,Warning消失不見。
小技巧:
1、cmd + T 全內核源碼搜索Symbols
2、cmd + Shift + o 當前文件搜索Symbols