KML: 启用Kernel Model Linux

 Linux  Kernel  Kernel Linux Mode  QEMU 󰈭 4586字

KML简介

Kernel Mode Linux: Execute user processes in kernel mode 是一种允许用户程序运行于内核态的技术. 处于内核态的程序可以直接访问任意的内核空间, 无需再使用软中断和上下文切换等手段进行系统调用. 此外, 这些程序也会正常的参与分页和调度, 这样哪怕这些程序死循环, 整个系统也不会因此卡死.

如何使用KML? 编译了打好补丁的内核后, 在/trusted/目录下的程序将自动进入内核态运行.

如何判断程序运行状态

目前我收集到两种办法:

initrd发展史

启用KML技术

版本选择?

选择什么版本的内核/软件版本比较重要, 这里我做了很多很多次尝试, 最终在如下环境成功:

注意! 并不是说其他的架构/内核版本不能做到KML! 只是我在尝试的时候恰好在该环境下成功了, 借鉴其中的经验可能可以在其他的环境下同样修改成功! 不过我这里肯定懒得一个个都去测试了!

下面将说明一下一路过来做过的尝试…

从Github仓库开始

最开始尝试的是Github上给出的教程: Kernel-Mode-Linux.

相比于官网, 其最大的特点是支持的内核版本较新, 但也仅支持一个版本: Linux-4.4.12; 官网的版本只到Linux-4.0.

朴素的开始:x86_64

最开始没有想太多, 照着一路打上patch后运行menuconfig, 结果里面没有Kernel Mode Linux这个内核选项…

我大为不解, 不过也没有想太多, 认为可能是这份Github代码没有和menuconfig衔接好, 导致里面没有这个选项…

那么我就手动加上这个参数: 进入到Makefile中, 加上-DCONFIG_KERNEL_MODE_LINUX -DCONFIG_KML_CHECK_CHROOT -DCONFIG_VDSO到编译选项里…

不过我也不知道加在哪里, 毕竟里面这么多xxx_FLAGS, 当时放在了KBUILD_CFLAGS下, 也就是之前改-fno-pie的地方… 不知道对不对.. Makefile一千多行不太会读…

编译, 运行, 未果.

ARM架构尝试

失败后我当时比较怀疑是-Dxxxx没有起到作用? 也不知道怎么改, 就用grep想看看到底哪里有CONFIG_KERNEL_MODE_LINUX这几个宏..

 1[ 15:37:42 ]   grep CONFIG_KERNEL_MODE_LINUX
 2security/tomoyo/network.c
 3611:#ifndef CONFIG_KERNEL_MODE_LINUX
 4
 5lib/iov_iter.c
 6351:#ifndef CONFIG_KERNEL_MODE_LINUX
 7
 8fs/binfmt_elf.c
 912:#if defined(CONFIG_KERNEL_MODE_LINUX) && defined(INCLUDED_FOR_COMPAT)
1013:#undef CONFIG_KERNEL_MODE_LINUX
11157:#ifdef CONFIG_KERNEL_MODE_LINUX
12673:#ifdef CONFIG_KERNEL_MODE_LINUX
13709:#ifdef CONFIG_KERNEL_MODE_LINUX
141088:#ifdef CONFIG_KERNEL_MODE_LINUX
151093:#ifdef CONFIG_KERNEL_MODE_LINUX
161137:#ifndef CONFIG_KERNEL_MODE_LINUX
17
18fs/compat_binfmt_elf.c
19145:#ifdef CONFIG_KERNEL_MODE_LINUX
20
21init/do_mounts.c
2247:#ifdef CONFIG_KERNEL_MODE_LINUX
23609:#ifdef CONFIG_KERNEL_MODE_LINUX
24
25arch/arm/lib/uaccess_with_memcpy.c
2693:#ifndef CONFIG_KERNEL_MODE_LINUX
27170:#ifndef CONFIG_KERNEL_MODE_LINUX
28
29arch/arm/vfp/vfphw.S
3087:#ifndef CONFIG_KERNEL_MODE_LINUX
3199:#ifdef CONFIG_KERNEL_MODE_LINUX
32
33arch/arm/kernel/entry-armv.S
341106:#ifdef CONFIG_KERNEL_MODE_LINUX
351133:#ifdef CONFIG_KERNEL_MODE_LINUX
361160:#ifdef CONFIG_KERNEL_MODE_LINUX
371187:#ifdef CONFIG_KERNEL_MODE_LINUX
38
39arch/arm/include/asm/thread_info.h
40153:#ifdef CONFIG_KERNEL_MODE_LINUX
41155:#endif /* CONFIG_KERNEL_MODE_LINUX */
42167:#ifdef CONFIG_KERNEL_MODE_LINUX
43169:#endif /* CONFIG_KERNEL_MODE_LINUX */
44183:#ifdef CONFIG_KERNEL_MODE_LINUX
45189:#endif /* CONFIG_KERNEL_MODE_LINUX */
46
47arch/arm/include/asm/ptrace.h
4820:#ifdef CONFIG_KERNEL_MODE_LINUX
4970:#ifdef CONFIG_KERNEL_MODE_LINUX
5073:#endif /* CONFIG_KERNEL_MODE_LINUX */
51
52arch/arm/include/asm/processor.h
5326:#ifdef CONFIG_KERNEL_MODE_LINUX
5459:#ifdef CONFIG_KERNEL_MODE_LINUX
5583:#ifdef CONFIG_KERNEL_MODE_LINUX
56
57mm/memory.c
583840:#ifndef CONFIG_KERNEL_MODE_LINUX

tmd这个Github的补丁只对arm做了补充!

下面就开始尝试做交叉编译了… 目标是arm架构, 期间还做了一次aarch64的, 不过aarch64和arm居然是两个架构… arm64通常才是aarch64, 而arm好像就是单纯的只是arm32…

关于aarch64的就不说了, 跟arm基本一样流程, 都是大便, 而且编译出来的内核也没有KML选项, 因为补丁也不支持aarch64…

此前从未做过arm的交叉编译, 去archlinux找了点编译器:

编译内核(参考代码, aarch64)的:

1cp ./arch/arm64/configs/defconfig .config
2make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
3make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64

参考: linux平台arm64内核编译 - 牧 天 - 博客园

此时, 在arm架构下可以看到KML的内核选项了, 多么令人振奋:

有一些编译时期遇到的坑:

  • 如果在编译的时候遇到了这样的错误:
1usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x10): multiple definition of 'yylloc'; scripts/dtc/dtc-lexer.lex.o:(.bss+0x0): first defined here
2
3collect2: error: ld returned 1 exit status

可以参考Solution for “‘multiple definition of yylloc’ error · Issue #4 · BPI-SINOVOIP/BPI-M4-bsp提到的, 对内核源码做一些修改:

 1$ git diff
 2diff --git a/scripts/dtc/dtc-lexer.lex.c_shipped b/scripts/dtc/dtc-lexer.lex.c_shipped
 3index 2d30f41778b..d0eb405cb81 100644
 4--- a/scripts/dtc/dtc-lexer.lex.c_shipped
 5+++ b/scripts/dtc/dtc-lexer.lex.c_shipped
 6@@ -637,7 +637,7 @@ char *yytext;
 7 #include "srcpos.h"
 8 #include "dtc-parser.tab.h"
 9 
10-YYLTYPE yylloc;
11+extern YYLTYPE yylloc;
12 
13 /* CAUTION: this will stop working if we ever use yyless() or yyunput() */
14 #define        YY_USER_ACTION \
1cc1: sorry, unimplemented: code model 'large' with '-fPIC'
2make[1]: *** [scripts/Makefile.build:264: crypto/echainiv.o] Error 1
3make: *** [Makefile:949: crypto] Error 2
4make: *** Waiting for unfinished jobs....

编译完成内核后, 同样也要编译一套相应架构的busybox!

不过我已经把aarch64和arm的答辩gcc卸载了, 所以具体的步骤就不予记录了. 但是编译参数可以完全参考kernel的指令, 修改CROSS_COMPILE=以及ARCH=即可. 可以参考交叉编译安卓busybox - 腾讯云开发者社区-腾讯云

qemu运行指令为:

1qemu-system-arm  -M  virt -cpu cortex-a53 -smp 2 -m 4096M \
2	-kernel ./Image.gz   -nographic \
3	-append "console=ttyAMA0 init=/linuxrc ignore_loglevel" \
4	-initrd ./rootfs.cpio.gz

好消息是一路上没有太多问题, 可以成功运行起来.

坏消息是不知道为什么, /trusted下的程序还是在用户态…

因为实在对arm不熟悉, 完全没有调试的欲望.. 因而直接弃坑…

官方版本与i386

吃过一次版本的亏后, 这次打算首先用官方的版本调试一次i386的内核架构.

使用官方版本的好处是其支持的版本更加丰富, 只需要找到拥有KML补丁和RT补丁的内核版本即可.

满足上述条件的最新的版本是Linux-4.0.4(但这也是8年前了!), 但更老的我也做了尝试, 主要有几个坑点:

  • 一些版本(主要是老版本才有的)的include/linux/compiler-gcc.h文件会检查gcc版本, 以4.0.4为例:
1#define __gcc_header(x) #x
2#define _gcc_header(x) __gcc_header(linux/compiler-gcc##x.h)
3#define gcc_header(x) _gcc_header(x)
4#include gcc_header(__GNUC__)

看一下支持哪些:

1ll include/linux/ | grep gcc
2-rw-r--r--  1 rqdmap rqdmap    635 May 18  2015 compiler-gcc3.h
3-rw-r--r--  1 rqdmap rqdmap   3089 May 18  2015 compiler-gcc4.h
4-rw-r--r--  1 rqdmap rqdmap   2484 May 18  2015 compiler-gcc5.h
5-rw-r--r--  1 rqdmap rqdmap   4280 May 18  2015 compiler-gcc.h

拜托现在都gcc-12了!

为了成功编译老内核, 我这边选择安装gcc-4.4; 其实哪个更好我不知道, 但是4.4版本是aur中votes最多的老gcc, 想必有一些过人之处吧!

使用gcc-4.4编译的方式也是传参给make, 不过这次传的是CC=gcc-4.4:

1make ARCH=i386 CC=gcc-4.4 defconfig
2make ARCH=i386 CC=gcc-4.4 menuconfig
3make ARCH=i386 CC=gcc-4.4 -j 20

i386架构也需要配备i386的rootfs, 因而为busybox编译一下i386的版本, 不过也有坑:

  • 还记得静态链接busybox时gcc处理不力吗? 这里也是一样, 需要使用i386的musl进行编译! 为此安装kernel-headers-musl-i386以及i386-musl包, 即可获得i386-musl-工具链.

  • busybox的menuconfig中需要指定一些参数, 使得编译出来的是32位的程序:

1Busybox Settings  --->
2	Build Options  --->
3		[*] Build BusyBox as a static binary (no shared libs)
4		(-m32 -march=i386 -mtune=i386) Additional CFLAGS
5		(-m32) Additional LDFLAGS

老内核以及i386-busybox的编译可以做一些参考:

最终编译完成, 但悲催的是, 无法引导进去系统. 其问题不是无法Decompress内核也不是无法Boot内核, 而是内核加载的过程中不知道哪里出错了, 结果导致无法进入. 下面截图做了个对比, 其中左边是将busybox本体文件放在了rootfs的/trusted下, /bin下的是指向该位置的软链接; 而右边的是busybox本体就在/bin下, 没有放任何东西在/trusted.

但这里可以确定的是, 不是KML模块的锅, 也好像不是rootfs的锅. 我为了控制变量, 专门编译了一个完全没有RT和KML补丁的内核, 同样也无法启动进入内核. 如果没有文件系统的话内核正常也无法启动, 我为其分配了一个hello kernel 的initrd后也无法启动, 因而大概可以排除是busybox的锅, 也就不是文件系统的锅. 因而感觉还是内核过老, 与现在不知道什么东西起了冲突…

不过好像有个好消息? 左右两边的内核是一样的, 区别只是有没有将所有的应用(因为所有的应用其实都是链接到busybox的), 明显可以看到有没有trusted对与内核日志的输出有很大的变化. 这可能说明在这里trusted确实起到了作用, 只要修一下为什么i386架构的内核无法启动可能即可开箱即用kml机制.

另外, 在尝试通过grep定位到报错的位置并企图使用gdb调试时, 发现了README中就有一些与该报错相关的内容:

emm… 感觉是很有用, 但是也就意味着对我这种外行人来说没啥用… 除非我不辞辛苦地dive进去看…

因此对于i386的尝试也到此为止.

都市传说

好像有一说i386在linux-3.8以后就不再支持了? Does Arch Linux work on i386 cpu architecture - Unix & Linux Stack Exchange.

Linux kernel - Wikipedia 也说道:

On 11 December 2012, Torvalds decided to reduce kernel complexity by removing support for i386 processors, making the 3.7 kernel series the last one still supporting the original processor.[60][61] The same series unified support for the ARM processor.[62]

但我不确定是这个i386是所有32位的intel架构还是指i386这一种处理器(与i486, i586, i686相对)?

i386 –> means it’s designed specifically for basic intel architecture based on the 80386 (or 386 computers)

i486 –> goes up to architecture for 80486 (486 and above computers). NB: 386s might have problems with some of this

i586 –> as above but designed for Pentiums and above. Pre-Pentiums may have problems.

i686 –> Pentium II and above.

感觉是可能是偏指的后者, 因为不太可能说Linux自此无法再在32位机器上跑了…

那么qemu是不是真的可能有问题… 因为这个指令是qemu-system-i386… 他的i386指的是…?

不过也浅浅尝试过linux-2.6? 当时忘记为啥也没跑起来, 好像是因为当时没意识到gcc-4.4…

不记得了, 留待进一步考据…

回归到x86_64

最终回归到x86_64, 使用版本选择?中的环境. 其实大部分的配置指令都与之前的类似, 不过这里几个注意的地方是:

  • linux-x.y.z, x和y分别是大小版本号, z是补丁号. linux-3.18.1首发于14年12月, 但是最新的linux-3.18.140一直到19年5月.. 这也导致了该版本的内核可以引导, 不会像官方版本与i386中一样无法进入内核. 或许调整i386的补丁号也可以进入? 未经考证

  • 另一个地方是patch. 直接打kml的3.18的补丁并不能全部匹配上, 经过测试发现主要有4个rej文件, 这里需要逐行去对照修改! 其中代码的顺序或位置可能发生较大的变化, 但是经过测试, 只要将这些语句照着kml的补丁改过来即可, 不需要考虑位置的问题.

    • syscall_32.tbl.rej是个汇编类似的文件, rej里直接把所有的都展示出来了, 但是其实不匹配的只有一行. 但我并不太知道怎么能快速地检索到具体哪些地方不匹配, 用到的办法是: 将补丁文件中的新增部分删掉, 只留下它认为的原文件的内容, :%s/^+//用Vim全局命令即可快速删除以+开头的行, 再用多列选择把开头的-删掉, 随后使用diff比较其与目前的文件的区别即可. 参考: How can I use vim to remove first few characters of the selected lines using commands?
 110848  2023-03-09 19:41  vim arch/x86/kernel/cpu/common.c.rej
 210852  2023-03-09 19:44  vim arch/x86/syscalls/syscall_32.tbl.rej
 310888  2023-03-09 20:06  vim arch/x86/kernel/head64.c.rej
 410889  2023-03-09 20:06  vim fs/binfmt_elf.c.rej
 5
 6# diff syscall_32.tbl syscall_32.tbl.rej
 7297c297
 8< 288	i386	keyctl			sys_keyctl			compat_sys_keyctl
 9---
10> 288	i386	keyctl			sys_keyctl

将这几个patch对应的文件修好, 运行qemu, 即可顺利运行kml机制!

嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核, 后端开发, Python, Rust 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改记录:
  • 2023-05-29 23:05:14大幅重构了python脚本的目录结构,实现了若干操作博客内容、sqlite的助手函数;修改原本的文本数 据库(ok)为sqlite数据库,通过嵌入front-matter的page_id将源文件与网页文件相关联
  • 2023-05-08 21:44:36博客架构修改升级
  • 2023-04-16 22:03:59修改了KML博客的标题
  • 2023-03-16 23:30:03修改了部分文件的标题与配图
  • 2023-03-13 21:00:01kml: 完善x64部分
  • 2023-03-12 20:28:15KML: 完成了大部分的过程, 剩余x86_64的构建过程
KML: 启用Kernel Model Linux