使用vim+gtags阅读内核源码
gtags
gtags全称是GNU Global Source Code Tag System
官网简称为global
global这词太普遍,为了不产生歧义,我还是称呼它为gtags吧
gtags定位
简而言之,gtags是替换cscope的,不是代替ctags的
gtags vs cscope
上一篇,我分享过使用vim+cscope来阅读内核源码,cscope已经能很好地满足需求。
那么为什么我还要把cscope升级成gtags呢?
我罗列一下gtags的优劣之处,读者们自行判断。
gtags的优势
- 支持C++。这是它很大的一个优势,但是我们都知道内核是纯C写的,这优势用不上。
- 增量添加索引比较快。这在内核模块编程的场景下,优势比较大,因为每修改一个文件就要增量添加索引。但在阅读源码的场景下,是无需修改源文件的,所以这优势也用不上。
- gtags支持补全,即可以查询部分函数名。这也确实是个优势,但是嘛,cscope也是支持grep功能的,所以补全的功能并不常用。
- 内核源码有脚本可以自动生成gtags的索引db。这可是一个方便的东西,但是嘛,手工自己生成索引也没多复杂。
gtags的劣势
- 很多linux发行版未必自带gtags,要自己编译安装。似乎有点麻烦,但其实编译一下也很快。
- 不支持cscope的“Find functions called by this function”功能,但是这功能一般也不怎么用
- C++的支持其实已经deprecated,要使用universal ctags来作为插件完成这功能,好吧,就算用上universal ctags加持,对于C++的新标准,也不是支持得很好,还是得上lsp。
总结一下:
在阅读内核源码场景下,gtags的优势不是很优,劣势也不是太劣。它是比cscope是好一些,但也不多。如果用惯了cscope,又没有心痒痒,就不必上了。
如果你像我一样,就是想玩,愿意折腾,gtags也不会让你失望。
gtags的安装
编译安装
- 去下载源码。https://www.gnu.org/software/global/download.html
- sh reconf.sh
- make
- sudo make install
为内核源码产生索引
方法一:使用源码Makefile生成索引db
去到源码目录,执行:make gtags arch=x86
方法二:自行生成索引db
#!/bin/bash
CWD=`pwd`
TMPFILE=${CWD}/list.files
# gen the gtags db
find $CWD -name "*.[chxsS]" -o \( -path "${CWD}/Documentation*" -a -type f \) > $TMPFILE
gtags -f $TMPFILE
rm -f $TMPFILE
去到源码目录,执行一下以上脚本即可
方法三:使用gtags-cscope自动生成
去到源码目录,执行gtags-cscope
自动生成
上面三种办法生成的索引其实是有细微差别的,日后用多了就能自行调整
使用gtags
使用gtags有四种办法
- gtags-cscope -d
- 在命令行使用global命令
- globash
- 在vim中使用
其实四种办法中,用到的gtags的功能都是一样的,只是交互方式不一样罢了,你懂了一种办法也就懂了其他办法。
gtags-cscope -d
对于入门者,或者cscope升级过来的人,推荐用这种方法,它有一个TUI界面,和cscope一模一样,操作直观。
在项目的根目录中,直接输入gtags-cscope -d
就可以进入这个界面
操作办法也和cscope一模一样。
这个屏幕分成上下两个部分,上面的部分显示搜索结果,下面是菜单,可以进行各种搜索。使用tab键进行上下两个部分的切换。
在下面的菜单部分,使用ctrl+p
和ctrl+n
上下移动
使用ctrl+d
退出界面
它默认vim来打开搜索结果所在的文件,如果要修改,可以设定全局变量EDITOR,比如
EDITOR=nvim gtags-cscope -d #使用neovim编辑器来打开搜索结果所在的文件
在命令行使用global命令
只要在当前目录中含有gtags索引文件,就可以使用global命令了
查找函数定义
这个功能就是gtags-cscope的Find this global definition
❯ global -x xfs_ilock
xfs_ilock 153 fs/xfs/xfs_inode.c xfs_ilock(
查找某个函数被调用的地方
这个功能就是gtags-cscope的Find references of this function
❯ global -rx xfs_ilock
xfs_ilock 270 fs/xfs/libxfs/xfs_attr.c xfs_ilock(dp, XFS_ILOCK_EXCL);
xfs_ilock 435 fs/xfs/libxfs/xfs_attr.c xfs_ilock(dp, XFS_ILOCK_EXCL);
xfs_ilock 1059 fs/xfs/libxfs/xfs_bmap.c xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_ilock 4955 fs/xfs/libxfs/xfs_bmap.c xfs_ilock(mp->m_rbmip, XFS_ILOCK_EXCL|XFS_ILOCK_RTBITMAP);
xfs_ilock 4957 fs/xfs/libxfs/xfs_bmap.c xfs_ilock(mp->m_rsumip, XFS_ILOCK_EXCL|XFS_ILOCK_RTSUM);
xfs_ilock 5484 fs/xfs/libxfs/xfs_bmap.c xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_ilock 5739 fs/xfs/libxfs/xfs_bmap.c xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_ilock 249 fs/xfs/xfs_aops.c xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_ilock 393 fs/xfs/xfs_aops.c xfs_ilock(ip, XFS_ILOCK_SHARED);
xfs_ilock 778 fs/xfs/xfs_aops.c xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_ilock 1297 fs/xfs/xfs_aops.c xfs_ilock(ip, lockmode);
xfs_ilock 1571 fs/xfs/xfs_aops.c xfs_ilock(ip, XFS_ILOCK_EXCL);
...
egrep某个函数,比如查找某个syscall
这个功能就是gtags-cscope的Find this egrep pattern
❯ global -gx "SYSCALL_DEF.*fstatat"
SYSCALL_DEF.*fstatat 272 fs/stat.c SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
SYSCALL_DEF.*fstatat 416 fs/stat.c SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
不过,老实说,这功能和grep命令差别不大,而且速度也不见得快:
❯ grep -Enr "SYSCALL_DEF.*fstatat"
fs/stat.c:272:SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
fs/stat.c:416:SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
既然和grep差别不大,那么我用rg命令(ripgrep)就能查的更快
❯ rg "SYSCALL_DEF.*fstatat"
fs/stat.c
272:SYSCALL_DEFINE4(newfstatat, int, dfd, const char __user *, filename,
416:SYSCALL_DEFINE4(fstatat64, int, dfd, const char __user *, filename,
查找某个函数在特定路径下被调用的情况
这是内核阅读的时候经常用到的搜索
比如我只想看xfs调用submit_bio()的地方,那么就可以:
❯ global -rx -S fs/xfs submit_bio
submit_bio 540 fs/xfs/xfs_aops.c submit_bio(wbc->sync_mode == WB_SYNC_ALL ? WRITE_SYNC : WRITE,
submit_bio 600 fs/xfs/xfs_aops.c submit_bio(wbc->sync_mode == WB_SYNC_ALL ? WRITE_SYNC : WRITE,
submit_bio 1345 fs/xfs/xfs_buf.c submit_bio(rw, bio);
global命令的优点和缺点
global命令的优势在于:它是个命令。这意味着你可以使用其他任何的linux命令行工具来加工搜索结果,比如grep、sort、awk啥的,随心所欲。
缺点在于:它无法方便地打开某个搜索结果所在的文件。
如果你既要它的优势又要方便地打开搜索结果,那么就可以使用globash
globash
globash是另一种交互式使用gtags的办法
globash首先也是个shell,在它里面可以使用其他命令行工具
globash比bash特殊的地方在于它能记住搜索结果,输入搜索结果的编号,就可以打开该文件。
❯ globash # 进入globash
Globash started. When you need help, please type 'ghelp <ENTER>'.
[/MyEBook/kernel/kernel-3.10.0-1160/linux-3.10.0-1160.el7]
在这里面d
、r
、 g
三个命令分别对应搜索定义
、reference(被调用)
、grep
三个功能,和前文所述gtags-cscope、global命令的功能一样。
[/MyEBook/kernel/kernel-3.10.0-1160/linux-3.10.0-1160.el7] g "xfs.*inobt" -S fs/xfs # 在fs/xfs路径下grep "xfs.*inobt"
会列出这么多
> 1 xfs.*inobt 120 fs/xfs/libxfs/xfs_alloc.c if (xfs_sb_version_hasfinobt(&mp->m_sb))
2 xfs.*inobt 44 fs/xfs/libxfs/xfs_btree.h xfs_inobt_key_t inobt;
3 xfs.*inobt 51 fs/xfs/libxfs/xfs_btree.h xfs_inobt_rec_t inobt;
4 xfs.*inobt 189 fs/xfs/libxfs/xfs_btree.h xfs_inobt_rec_incore_t i;
5 xfs.*inobt 520 fs/xfs/libxfs/xfs_format.h static inline int xfs_sb_version_hasfinobt(xfs_sb_t *sbp)
6 xfs.*inobt 1249 fs/xfs/libxfs/xfs_format.h static inline xfs_inofree_t xfs_inobt_maskn(int i, int n)
7 xfs.*inobt 1264 fs/xfs/libxfs/xfs_format.h typedef struct xfs_inobt_rec {
8 xfs.*inobt 1277 fs/xfs/libxfs/xfs_format.h } xfs_inobt_rec_t;
9 xfs.*inobt 1279 fs/xfs/libxfs/xfs_format.h typedef struct xfs_inobt_rec_incore {
10 xfs.*inobt 1285 fs/xfs/libxfs/xfs_format.h } xfs_inobt_rec_incore_t;
11 xfs.*inobt 1287 fs/xfs/libxfs/xfs_format.h static inline bool xfs_inobt_issparse(uint16_t holemask)
12 xfs.*inobt 1296 fs/xfs/libxfs/xfs_format.h typedef struct xfs_inobt_key {
13 xfs.*inobt 1298 fs/xfs/libxfs/xfs_format.h } xfs_inobt_key_t;
14 xfs.*inobt 1301 fs/xfs/libxfs/xfs_format.h typedef __be32 xfs_inobt_ptr_t;
15 xfs.*inobt 1315 fs/xfs/libxfs/xfs_format.h (xfs_sb_version_hasfinobt(&((mp)->m_sb)) ? \
...
如果你看到所要的结果,按q可以结束分页输出
按第一列的数字和回车就可以打开该文件,非常方便快捷。
如果要重复查找,只需输入l和回车,就可以又看到上次的输出了。
l其实是list命令,它会列出搜索结果缓存。
按ctrl+d
或者输出exit命令退出globash
在vim中使用
如果你想在vim中使用gtags,要先做两件事
- 安装gtags插件
- 在.vimrc中配置好该插件
安装gtags插件
# 在上面的源码包的根目录中有一个gtags.vim,这就是gtags的vim插件了
# 源码编译安装后,这个文件也可以在/usr/local/share/gtags/gtags.vim找到
# cp到vim的插件目录即可
cp /usr/local/share/gtags/gtags.vim $HOME/.vim/plugin
配置.vimrc
vim ~/.vimrc
,增加下面几行,可以看我注释按自己喜好配置
set cscopeprg='gtags-cscope' " 使用 gtags-cscope 代替 cscope
" let Gtags_VerticalWindow = 1 " gtags的搜索结果会在quick fix窗口中显示,设置这个就会把quick fix窗口显示在右侧,我喜欢把它放在下面(默认值)
" let GtagsCscope_Auto_Load = 1 " using default key mapping,是否使用default的快捷键
let Gtags_Close_When_Single = 1 " 如果搜索结果只有一行,就直接跳转,不要打开quick fix窗口
map <C-\><C-]> :GtagsCursor<CR> " 以当前光标所在的单词为关键词,搜索它的定义
" 下面是gtags几个常用搜索的快捷键定义
nmap <C-\>d :Gtags -d <C-R>=expand("<cword>")<CR>
nmap <C-\>r :Gtags -r <C-R>=expand("<cword>")<CR>
nmap <C-\>g :Gtags -g <C-R>=expand("<cword>")<CR>
nmap <C-\>f :Gtags -f <C-R>=expand("<cword>")<CR>
在看懂上面的配置之后,就可以使用最后五行定义的快捷键在vim中来进行搜索了,几种搜索办法还是对应之前说的那几种。
注意我在配置快捷键的时候,最后四种快捷键是故意省略了一个快捷键,这是为了你在搜索开始之前有机会修正搜索的关键字。
使用
进到项目的根目录,打开vim它就会自动加载当前路径下的gtags索引文件。
除了上面定义的快捷键,当然可以直接输入Gtags命令来搜索
比如我输入了Gtags -g bio_alloc
命令后,就会跳出这样的搜索结果窗口
如果你想重复搜索,可以输入命令:@:
如果想跳转到下一条结果的位置,输入命令:cn
如果想跳转到上一条结果的位置,输入命令:cp
如果想跳转到第8条结果的位置,输入命令:cc 8
如果要关闭搜索窗口,输入命令:ccl
写在最后
IT行业,技术更新换代快,人们都喜欢追求最新的技术,对老技术和产品多少有些轻视。
这一点在编辑器中也是这样,cscope、gtags这些以tag为基础的产品,由于不是真的懂得C代码,在现在的编程环境中,已经比不上lsp了。
但是,在C代码阅读的场景中,特别是内核源码阅读的场景中,这些技术还是有一席用武之地,在历史的长河中继续发光发热。