我是内核编程的新手,我找不到足够的信息来了解为什么会这样。基本上我正在尝试用最简单的调用原始处理程序的内容替换内核IDT中的页面错误处理程序。我只是想让这个函数打印一个被调用的通知,并在其中调用printk()
总是会导致内核崩溃。否则它运行正常。
#include <asm/desc.h>
#include <linux/mm.h>
#include <asm/traps.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/desc_defs.h>
#include <linux/moduleparam.h>
#define PAGEFAULT_INDEX 14
// Old and new IDT registers
static struct desc_ptr old_idt_reg, new_idt_reg;
static __attribute__((__used__)) unsigned long old_pagefault_pointer, new_page;
// The function that replaces the original handler
asmlinkage void isr_pagefault(void);
asm(" .text");
asm(" .type isr_pagefault,@function");
asm("isr_pagefault:");
asm(" callq print_something");
asm(" jmp *old_pagefault_pointer");
void print_something(void) {
// This printk causes the kernel to crash!
printk(KERN_ALERT "Page fault handler called\n");
return;
}
void my_idt_load(void *ptr) {
printk(KERN_ALERT "Loading on a new processor...\n");
load_idt((struct desc_ptr*)ptr);
return;
}
int module_begin(void) {
gate_desc *old_idt_addr, *new_idt_addr;
unsigned long idt_length;
store_idt(&old_idt_reg);
old_idt_addr = (gate_desc*)old_idt_reg.address;
idt_length = old_idt_reg.size;
// Get the pagefault handler pointer from the IDT's pagefault entry
old_pagefault_pointer = 0
| ((unsigned long)(old_idt_addr[PAGEFAULT_INDEX].offset_high) << 32 )
| ((unsigned long)(old_idt_addr[PAGEFAULT_INDEX].offset_middle) << 16 )
| ((unsigned long)(old_idt_addr[PAGEFAULT_INDEX].offset_low) )
;
printk(KERN_ALERT "Saved pointer to old pagefault handler: %p\n", (void*)old_pagefault_pointer);
// Allocate a new page for the new IDT
new_page = __get_free_page(GFP_KERNEL);
if (!new_page)
return -1;
// Copy the original IDT to the new page
memcpy((void*)new_page, old_idt_addr, idt_length);
// Set up the new IDT
new_idt_reg.address = new_idt_addr = new_page;
new_idt_reg.size = idt_length;
pack_gate(
&new_idt_addr[PAGEFAULT_INDEX],
GATE_INTERRUPT,
(unsigned long)isr_pagefault, // The interrupt written in assembly at the start of the code
0, 0, __KERNEL_CS
);
// Load the new table
load_idt(&new_idt_reg);
smp_call_function(my_idt_load, (void*)&new_idt_reg, 1); // Call load_idt on the rest of the cores
printk(KERN_ALERT "New IDT loaded\n\n");
return 0;
}
void module_end(void) {
printk(
KERN_ALERT
"Exit handler called now. Reverting changes and exiting...\n\n"
);
load_idt(&old_idt_reg);
smp_call_function(my_idt_load, (void*)&old_idt_reg, 1);
if (new_page)
free_page(new_page);
}
module_init(module_begin);
module_exit(module_end);
非常感谢能告诉我这里我做错了什么的人。
答案 0 :(得分:3)
对不起,请您死者复活,
挂钩IDT条目时,我遇到了类似的问题;一种可能是堆栈空间不足。在64位模式下,当调用陷阱或故障处理程序时,CPU会根据相应中断描述符的“中断堆栈表”(IST)字段(第32位至第34位)和处理器来确定新的堆栈指针核心的任务状态段(TSS
)。从第3A卷的《英特尔软件开发人员手册》第6.14.5节:
在IA-32e模式下,可以使用新的中断堆栈表(IST)机制来替代上述经过修改的传统堆栈切换机制。启用此机制后,将无条件切换堆栈。可以使用IDT条目中的字段在单个中断向量的基础上启用它。这意味着某些中断向量可以使用修改后的遗留机制,而其他中断向量可以使用IST机制。
IST机制仅在IA-32e模式下可用。它是64位模式TSS的一部分。 IST机制的动机是为特定的中断(例如NMI,双重故障和机器检查)提供一种始终在已知良好堆栈上执行的方法。在传统模式下,中断可以通过位于IDT中的任务门访问中断服务程序,从而使用任务切换机制来建立已知良好的堆栈。但是,IA-32e模式不支持传统的任务切换机制。
IST机制在TSS中最多提供七个IST指针。指针由中断描述符表(IDT)中的中断门描述符引用;参见图6-8。门描述符包含一个3位IST索引字段,该字段为TSS的IST部分提供了偏移量。处理器使用IST机制,将IST指针指向的值加载到RSP中。
...如果IST索引为零,则使用上述经过修改的旧式堆栈切换机制。
同一章的6.14.2节中描述了“修改后的传统堆栈切换”机制,最重要的是,仅将RSP0
的{{1}}条目作为新堆栈指针加载。这是描述TSS
的图:
因此,总而言之,如果中断描述符的IST字段为0,则TSS
的{{1}}条目将被加载为新的堆栈指针,并且如果IST字段为non -零,则它指示的RSP0
的条目将作为新的堆栈指针加载。在TSS
Linux中,页面错误的IST字段为0,因此,每当发生页面错误时,TSS
就会切换到x64
的{{1}}条目。不幸的是,这里分配的堆栈空间很小。在与内核调试器一起玩耍时发现,Linux仅为此堆栈分配512个字节,而我怀疑rsp
可能需要更大的堆栈空间。
对此的一种可能的解决方案是,在页面错误挂钩的开头,手动将堆栈指针切换到RSP0
的{{1}}条目,该条目应包含当前的内核堆栈,因此TSS
有足够的空间。这是一个非常笨拙且不雅致的解决方案,但以我的经验,它可以解决问题。 (要查找printk
的地址,您应该使用RSP1
获取任务寄存器(TSS
),然后从GDT的相应条目中获取基地址,该地址称为“ printk
描述符。有关详细信息,请参见第3A卷的7.2.3节。)
免责声明:但是,今天有一个重要的警告与您提出该问题时不相关;针对Meltdown引入的新内核页表隔离缓解将在这种挂钩中引起另一个致命的问题。特别是,TSS
的用户模式值将无法访问新的中断描述符表,因此,一旦您加载了新的IDT,用户土地上的任何故障实际上都会导致三重故障(首先是原始错误,然后是页面错误,因为用户模式页表中不存在IDT地址,然后是三次错误,因为同样无法访问IDT的双重错误条目)。没有手动更改所有用户模式页表,这将使您的IDT挂钩方法变得不可能。
唯一的解决方案是手动覆盖您将在用户模式页表中显示的内存区域。例如,IDT中引用的原始IRQ处理程序将指向用户模式页表中始终存在的一小段代码,其作用是将str
更改为内核模式变体。 Linux通过清除tr
的第11位和第12位来完成此操作,因此您可以使用一个小的汇编存根(stub)覆盖该代码区域,该存根会清除这些位,然后跳转到您的钩子。作为概念证明,请参见here。
答案 1 :(得分:0)
据我所知,printk()比ftrace需要更多的资源和复杂性(控制台/文件系统/存储)。 如果崩溃只发生在你使用了printk()的情况下,为什么不使用ftrace而不是printk()?
许多Linux内核专家都喜欢ftrace。
答案 2 :(得分:-1)
为什么要尝试替换页面错误处理程序?如果你想跟踪页面错误,那么可以采用kprobes。
至于手头的问题,这里可能没有什么神秘之处,即使你没有展示崩溃的样子。很可能在用户空间中出现页面错误,但这意味着gs包含用户空间垃圾而不是内核每个CPU数据。据推测,它可以在以后访问,并且不会很好地结束。所有内核入口点都包含swapgs。
一般情况下,安装自己的处理程序意味着你必须获得足够的关卡。你无缘无故地让我很难看到。
我们来看看:
Out-file C:\Users\xx\Desktop\12.csv -Append -InputObject “Computer Name,KB Patch,Hotfix”
就在那里
crash> dis page_fault
0xffffffffbb804da0 <page_fault>: data32 xchg %ax,%ax
0xffffffffbb804da3 <page_fault+3>: data32 xchg %ax,%ax
0xffffffffbb804da6 <page_fault+6>: data32 xchg %ax,%ax
0xffffffffbb804da9 <page_fault+9>: add $0xffffffffffffff88,%rsp
0xffffffffbb804dad <page_fault+13>: callq 0xffffffffbb804f30 <error_entry>
0xffffffffbb804db2 <page_fault+18>: mov %rsp,%rdi
0xffffffffbb804db5 <page_fault+21>: mov 0x78(%rsp),%rsi
0xffffffffbb804dba <page_fault+26>: movq $0xffffffffffffffff,0x78(%rsp)
0xffffffffbb804dc3 <page_fault+35>: callq 0xffffffffbb062840 <do_page_fault>
0xffffffffbb804dc8 <page_fault+40>: jmpq 0xffffffffbb804ff0 <error_exit>
0xffffffffbb804dcd <page_fault+45>: nopl (%rax)
crash> dis error_entry
0xffffffffbb804f30 <error_entry>: cld
0xffffffffbb804f31 <error_entry+1>: mov %r11,0x38(%rsp)
0xffffffffbb804f36 <error_entry+6>: mov %r10,0x40(%rsp)
0xffffffffbb804f3b <error_entry+11>: mov %r9,0x48(%rsp)
0xffffffffbb804f40 <error_entry+16>: mov %r8,0x50(%rsp)
0xffffffffbb804f45 <error_entry+21>: mov %rax,0x58(%rsp)
0xffffffffbb804f4a <error_entry+26>: mov %rcx,0x60(%rsp)
0xffffffffbb804f4f <error_entry+31>: mov %rdx,0x68(%rsp)
0xffffffffbb804f54 <error_entry+36>: mov %rsi,0x70(%rsp)
0xffffffffbb804f59 <error_entry+41>: mov %rdi,0x78(%rsp)
0xffffffffbb804f5e <error_entry+46>: mov %r15,0x8(%rsp)
0xffffffffbb804f63 <error_entry+51>: mov %r14,0x10(%rsp)
0xffffffffbb804f68 <error_entry+56>: mov %r13,0x18(%rsp)
0xffffffffbb804f6d <error_entry+61>: mov %r12,0x20(%rsp)
0xffffffffbb804f72 <error_entry+66>: mov %rbp,0x28(%rsp)
0xffffffffbb804f77 <error_entry+71>: mov %rbx,0x30(%rsp)
0xffffffffbb804f7c <error_entry+76>: xor %ebx,%ebx
0xffffffffbb804f7e <error_entry+78>: testb $0x3,0x90(%rsp)
0xffffffffbb804f86 <error_entry+86>: je 0xffffffffbb804f9a <error_entry+106>
0xffffffffbb804f88 <error_entry+88>: swapgs
那就是说,你为什么要玩这个东西?我强烈建议暂时坚持用户空间中的某些程序集。