2017-07-10 9 views
2

カーネルプログラミングが新しく、なぜこれが起こるのかを知るには十分な情報が見つかりませんでした。基本的に私は、カーネルのIDTのページフォルトハンドラを、最後に元のハンドラを呼び出す単純なものに置き換えようとしています。私はこの関数が呼び出されたという通知を出力し、その内部にprintk()を呼び出すと、常にカーネルパニックが発生します。そうでなければうまくいく。単純なIRQハンドラで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

私はprintkがいくつかのレジスタを変更している可能性があるので、呼び出しを行う前にスタック上のすべてのレジスタを保存しようとしました。結果は同じでした。 –

+1

* "常にカーネルパニックになります" * - 良いコードを投稿しましたが、クラッシュダンプとバックトレースはどこにありますか? – sawdust

+2

* printk()*は使わないでください。あなたのケースでは、適切なデバッグメカニズムではないようです。 – 0andriy

答えて

-1

なぜページフォールトハンドラを交換しようとしていますか?ページフォールトをトレースしたい場合は、kprobesを使用します。

クラッシュの様子を見せていないにもかかわらず、問題の原因はここにはありません。ほとんどの場合、ユーザスペースでページフォールトが発生している可能性がありますが、これは、gsには、CPU単位のカーネルのデータではなく、ユーザスペースのクラップが含まれていることを意味します。おそらくそれは後でアクセスされ、うまく終わらないでしょう。すべてのカーネルエントリポイントにはswapgが含まれています。

通常、独自のハンドラをインストールするということは、レベルのものをたくさん手に入れなければならないということです。あなたは私が見ることができる理由のためにそれを難し​​くしています。

はのは、見てみましょう:

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 

、そこにそれがなぜあなたはこのようなもので遊んでいる、と述べた

0xffffffffbb804f8b <error_entry+91>: data32 xchg %ax,%ax 
0xffffffffbb804f8e <error_entry+94>: jmpq 0xffffffffbb804f98 <error_entry+104> 

のですか?私は、今のところユーザー空間のいくつかのアセンブリに固執することを強くお勧めします。

0

私が知る限り、printk()はftraceよりも多くのリソースと複雑さ(コンソール/ファイルシステム/ストレージ)を必要とします。 printk()を使用した場合にクラッシュする場合のみ、printk()の代わりにftraceを使用しないでください。

多くのLinuxカーネルエキスパートがftraceを愛しています。

関連する問題