KML: 启用Kernel Model Linux
KML简介
Kernel Mode Linux: Execute user processes in kernel mode 是一种允许用户程序运行于内核态的技术. 处于内核态的程序可以直接访问任意的内核空间, 无需再使用软中断和上下文切换等手段进行系统调用. 此外, 这些程序也会正常的参与分页和调度, 这样哪怕这些程序死循环, 整个系统也不会因此卡死.
如何使用KML? 编译了打好补丁的内核后, 在/trusted/
目录下的程序将自动进入内核态运行.
如何判断程序运行状态
目前我收集到两种办法:
-
通过寄存器的内容, 比如在x86架构下通过cs(代码段寄存器)访问段选择符的低2位, 即可得知请求者的特权级; 或者在arm架构下通过cpsr寄存器也可以知道其所处的模式. 具体的读取方式用一个C的内联汇编进行读取即可, 参考:
-
通过
awk '{print $14, $15}' /proc/<pid>/stat
动态读取程序运行时间, 两个数字分别代表用户态和内核态. 这个方法感觉很不错, 因为通过内联汇编访问寄存器其实有诸多不便, 特别是aarch64好像还把一些给隐藏起来了还是什么, 不对用户可见… 总之如何判断arm架构下是否在内核态还挺费劲, 不如这个方法, 参考:
initrd发展史
启用KML技术
版本选择?
选择什么版本的内核/软件版本比较重要, 这里我做了很多很多次尝试, 最终在如下环境成功:
-
linux-3.18.140(Index of /pub/linux/kernel/v3.x/), with:
-
Real-Time补丁(Index of /pub/linux/kernel/projects/rt/3.18/)
-
gcc-4.4
-
-
busybox-1.33.2
- musl-gcc 12.2.1
-
QEMU emulator version 7.2.0
-
x86_64架构
注意! 并不是说其他的架构/内核版本不能做到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 \
- 或者遇到了
code model 'large' with '-fPIC
: 可以参考TaiShan 2280 Ubuntu-20.4安装stream编译报错cc1: sorry, unimplemented: code model ’large’ with -fPIC- 华为, 在Makefile中加一个fno-PIC
即可.
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.
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机制!