修改内核模块中的控制寄存器
Modifying control register in kernel module
以下简单内核模块在加载后设置 cr4 寄存器 (CR4.VMXE
) 的第 13 位,并在退出时清除该位。
vmx.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
static inline uint64_t getcr4(void) {
register uint64_t ret = 0;
asm volatile (
"movq %%cr4, %0\n"
:"=r"(ret)
);
return ret;
}
static inline void setcr4(register uint64_t val) {
asm volatile (
"movq %0, %%cr4\n"
:
:"r"(val)
);
}
static int __init init_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "VTX Test loaded: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
cr4 |= (1 << 13);
setcr4(cr4);
cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
return 0;
}
static void __exit exit_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
cr4 &= ~(1 << 13);
setcr4(cr4);
cr4 = getcr4();
printk(KERN_INFO "VTX Test exited: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
}
module_init(init_routine);
module_exit(exit_routine);
生成文件
obj-m += vmx.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
到运行我使用的模块make clean && make && sudo insmod vmx.ko && sudo rmmod vmx && sudo dmesg -c
。这有时会给我以下(预期的)输出
[ 2295.121537] VTX Test loaded: 1312736 (0).
[ 2295.121540] cr4: 1320928 (1).
[ 2295.123975] cr4: 1320928 (1).
[ 2295.123977] VTX Test exited: 1312736 (0).
有时还有以下内容:
[ 2296.256982] VTX Test loaded: 1320928 (1).
[ 2296.256984] cr4: 1320928 (1).
[ 2296.259481] cr4: 1312736 (0).
[ 2296.259483] VTX Test exited: 1312736 (0).
第二次输出的第二行和第三行我觉得很奇怪,因为修改后的控制寄存器cr4似乎在离开init_routine
后被重置了。此外,奇怪的是,在第一行中似乎设置了 VMXE 位,这实际上没有任何意义。这种行为正常吗?怎么解释呢?是否有另一个修改 CR4 的内核模块 运行ning?这看起来很奇怪,因为我见过几个 VTX 实现,它们都在初始化例程中设置 VMXE 位,并以与本模块相同的方式在退出例程中清除该位。
您可能加载了 kvm
模块(以及关联的 kvm-intel
或 kvm-amd
)内核模块。这些模块已经管理处理器的 VT 状态,如果您在别处开始修改它们,可能会非常困惑。
查看内核源代码中的 arch/x86/kvm
目录,了解已经存在的内容。
事实证明,问题是寄存器没有在所有 CPU 内核上被修改。为了确保修改发生在所有内核上,调用 on_each_cpu
似乎就足够了。固定代码如下,Makefile不变。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
static inline uint64_t getcr4(void) {
register uint64_t ret = 0;
asm volatile (
"movq %%cr4, %0\n"
:"=r"(ret)
);
return ret;
}
static inline void setcr4(register uint64_t val) {
asm volatile (
"movq %0, %%cr4\n"
:
:"r"(val)
);
}
static void setvmxe(void* info) {
uint64_t cr4 = getcr4();
cr4 |= (1 << 13);
setcr4(cr4);
}
static void clearvmxe(void* info) {
uint64_t cr4 = getcr4();
cr4 &= ~(1 << 13);
setcr4(cr4);
}
static int __init init_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "VTX Test loaded: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
on_each_cpu(setvmxe, NULL, 0);
cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
return 0;
}
static void __exit exit_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
on_each_cpu(clearvmxe, NULL, 0);
cr4 = getcr4();
printk(KERN_INFO "VTX Test exited: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
}
module_init(init_routine);
module_exit(exit_routine);
以下简单内核模块在加载后设置 cr4 寄存器 (CR4.VMXE
) 的第 13 位,并在退出时清除该位。
vmx.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
static inline uint64_t getcr4(void) {
register uint64_t ret = 0;
asm volatile (
"movq %%cr4, %0\n"
:"=r"(ret)
);
return ret;
}
static inline void setcr4(register uint64_t val) {
asm volatile (
"movq %0, %%cr4\n"
:
:"r"(val)
);
}
static int __init init_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "VTX Test loaded: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
cr4 |= (1 << 13);
setcr4(cr4);
cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
return 0;
}
static void __exit exit_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
cr4 &= ~(1 << 13);
setcr4(cr4);
cr4 = getcr4();
printk(KERN_INFO "VTX Test exited: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
}
module_init(init_routine);
module_exit(exit_routine);
生成文件
obj-m += vmx.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
到运行我使用的模块make clean && make && sudo insmod vmx.ko && sudo rmmod vmx && sudo dmesg -c
。这有时会给我以下(预期的)输出
[ 2295.121537] VTX Test loaded: 1312736 (0).
[ 2295.121540] cr4: 1320928 (1).
[ 2295.123975] cr4: 1320928 (1).
[ 2295.123977] VTX Test exited: 1312736 (0).
有时还有以下内容:
[ 2296.256982] VTX Test loaded: 1320928 (1).
[ 2296.256984] cr4: 1320928 (1).
[ 2296.259481] cr4: 1312736 (0).
[ 2296.259483] VTX Test exited: 1312736 (0).
第二次输出的第二行和第三行我觉得很奇怪,因为修改后的控制寄存器cr4似乎在离开init_routine
后被重置了。此外,奇怪的是,在第一行中似乎设置了 VMXE 位,这实际上没有任何意义。这种行为正常吗?怎么解释呢?是否有另一个修改 CR4 的内核模块 运行ning?这看起来很奇怪,因为我见过几个 VTX 实现,它们都在初始化例程中设置 VMXE 位,并以与本模块相同的方式在退出例程中清除该位。
您可能加载了 kvm
模块(以及关联的 kvm-intel
或 kvm-amd
)内核模块。这些模块已经管理处理器的 VT 状态,如果您在别处开始修改它们,可能会非常困惑。
查看内核源代码中的 arch/x86/kvm
目录,了解已经存在的内容。
事实证明,问题是寄存器没有在所有 CPU 内核上被修改。为了确保修改发生在所有内核上,调用 on_each_cpu
似乎就足够了。固定代码如下,Makefile不变。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
static inline uint64_t getcr4(void) {
register uint64_t ret = 0;
asm volatile (
"movq %%cr4, %0\n"
:"=r"(ret)
);
return ret;
}
static inline void setcr4(register uint64_t val) {
asm volatile (
"movq %0, %%cr4\n"
:
:"r"(val)
);
}
static void setvmxe(void* info) {
uint64_t cr4 = getcr4();
cr4 |= (1 << 13);
setcr4(cr4);
}
static void clearvmxe(void* info) {
uint64_t cr4 = getcr4();
cr4 &= ~(1 << 13);
setcr4(cr4);
}
static int __init init_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "VTX Test loaded: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
on_each_cpu(setvmxe, NULL, 0);
cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
return 0;
}
static void __exit exit_routine(void) {
uint64_t cr4 = getcr4();
printk(KERN_INFO "cr4: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
on_each_cpu(clearvmxe, NULL, 0);
cr4 = getcr4();
printk(KERN_INFO "VTX Test exited: %llu (%u).\n", cr4, (unsigned char)((cr4 >> 13) & 1));
}
module_init(init_routine);
module_exit(exit_routine);