首页
社区
课程
招聘
[原创] Android Kernel 源码环境
2022-11-30 15:59 34556

[原创] Android Kernel 源码环境

2022-11-30 15:59
34556

工欲善其事 必先利其器

前言

本篇将会搭建一个完美的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集成开发环境可以完美的索引项目代码?
    参考于微软的一篇文档,我了解到了语言服务器这个东西。

  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文件。
      1
      2
      make ## 原编译命令 --> 不生成compile_commands.json
      bear make ## 使用bear --> 生成compile_commands.json
    • 对于CMake项目来说,这个相当简单。
      参考于CMake官方文档,只需要设置CMAKE_EXPORT_COMPILE_COMMANDS为True即可在编译后生成compile_commands.json文件。
      1
      set(CMAKE_EXPORT_COMPILE_COMMANDS True)

这里特别提一嘴,对于mk体系编译的的项目
mk脚本,其本质上是make,但是对于安卓的来说,生成编译描述文件非常简单
官方有文档提供命令

1
ndk-build GEN_COMPILE_COMMANDS_DB=true ## 构建的时候顺便生成compile_commands.json

所以,理论上来说,搭建的这套代码解析方案,不仅仅适用于Kernel源码阅读。也适用于各种交叉编译

敲定方案

毫无疑问 Kernel的源码大多是c文件,以及少部分的汇编文件,设备树文件......

  1. 明确一点,我们需要clangd支持。
    首先看看VsCode有没有clangd插件,搜了一下,果然有clangd插件
  2. 我们需要对内核编译的流程,生成描述文件。
    • 这里我找到一个项目,可以把已经完整编译好了的内核解析出编译描述文件出来: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官方文档

      这里默认读者会换源或者其他手法保持网络通畅

    • 步骤简述:
      1. 初始化repo库
        1
        2
        ## 这里选择common-android12-5.10分支
        repo init -u https://android.googlesource.com/kernel/manifest -b common-android12-5.10
      2. 同步repo库代码
        1
        repo sync
        漫长的等待...
      3. 选择自己需要的config文件,开启build.sh脚本
        这里以build.config.gki.aarch64为例
        1
        BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
      4. 等待一段时间后,内核即可编译完成:
        内核编译完成
  2. 生成编译描述文件

    • 仔细观察我们编译的out目录,会发现有很多后缀为cmd的文件,这些其实就是编译过程中的临时文件,包含了编译命令等。
    • vscode-linux-kernel其实就是利用编译后的cmd等文件做解析,生成编译描述文件compile_commands.json
    • 步骤简述:
      1. 进入内核源码的根目录,将vscode-linux-kernel项目拉下来:
        1
        2
        3
        4
        cd common # 进入内核源码根目录
        # clone vscode-linux-kernel项目到.vscode文件夹
        # 该文件夹为vscode配置文件夹 类似于.idea
        git clone --depth=1 https://github.com/amezin/vscode-linux-kernel.git .vscode
      2. 运行python脚本,并指定-O参数到编译产出目录:
        1
        2
        3
        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
        这一套代码解析方案也完美的适用于Out-of-tree的内核模块

  3. 配置VsCode
    对于VsCode来说.vscode目录下,是一些配置文件。
    我们修改原项目的setting.json文件,内容修改成如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    {
        "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代码块不高亮显示:
      config解析

做一些润滑

  • 你发现会报很多无关痛痒的警告:
    Warning

    1. 内核源码根目录下,新建一个文件.clangd,内容如下:

      1
      2
      3
      4
      5
      6
      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
      Symbols搜索

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-11-30 21:52 被Ssage泓清编辑 ,原因: 修正
收藏
点赞20
打赏
分享
最新回复 (12)
雪    币: 147
活跃值: (165)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wuli鸭蛋君 2022-11-30 19:30
2
0
好文默默点个赞
雪    币: 3789
活跃值: (5555)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 2022-12-1 02:36
3
0
内核学习中,好文章
雪    币: 126
活跃值: (489)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Bombs 1 2022-12-1 11:08
4
0
linux内核本身就有脚本生成compile_commands.json文件的

https://github.com/torvalds/linux/blob/master/scripts/clang-tools/gen_compile_commands.py
雪    币: 616
活跃值: (1310)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Ssage泓清 2022-12-1 11:57
5
0
Bombs linux内核本身就有脚本生成compile_commands.json文件的 https://github.com/torvalds/linux/blob/master/scripts/cla ...
看了一下,确实如此
感谢,那就是又多一种方案
雪    币: 1720
活跃值: (8676)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
你瞒我瞒 2022-12-1 15:37
6
0
给大佬点赞
雪    币: 2917
活跃值: (2123)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
奋斗小菜鸟 2022-12-1 18:58
7
0
学习一下 对这个很有需求
雪    币: 3350
活跃值: (3372)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2022-12-4 13:10
8
0
感谢分享!
雪    币: 1360
活跃值: (2093)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xdnice 2022-12-6 14:05
9
0
谢谢楼主的分享。
雪    币: 2597
活跃值: (4748)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
Thehepta 3 2022-12-7 14:38
10
0
谢谢大佬的新姿势
雪    币: 278
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
无相孤君 2022-12-15 17:19
11
0
感谢大佬
雪    币: 201
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jutang 2022-12-27 22:03
12
0
"Kernel正好是make体系,理论上是可以用Bear的。"

求教怎么用啊?

雪    币: 616
活跃值: (1310)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Ssage泓清 2022-12-30 00:19
13
0
jutang "Kernel正好是make体系,理论上是可以用Bear的。" 求教怎么用啊?

1- Bear的使用极其简单 在make命令之前加上bear即可
2- 我没试过去用bear 所以只是理论可行 对于庞大复杂的内核源码来说 bear能否承担重任 是个问题
3- 建议使用文章所说的py脚本 或者使用内核源码自带的gen_compile_commands.py脚本 这两种选择最优

最后于 2022-12-30 00:20 被Ssage泓清编辑 ,原因:
游客
登录 | 注册 方可回帖
返回