[Fault-injection-developer] [RFC] kmmio mechanism
Status: Alpha
Brought to you by:
rustyl
From: Zhuang, L. <lou...@in...> - 2002-12-18 12:12:04
|
> We'd like split instruction analysis code and general mmio > probe code in pf > module. That is, we'd like support a general mmio probe > mechanism interface > as the infrastructure of pf module. the interface is like > > struct kmmio_probe { > kmmio_addr_t *addr; > kmmio_pre_handler_t pre_handler; > kmmio_post_handler_t post_handler; > void *data; //opaque data structure > } > > kmmio_probe_register(struct kmmio_probe *); > kmmio_probe_unregister(struct kmmio_probe *); > > Then, pf module place probe on the address and analysis instruction in > pre/post_handler and feedback to fi_core. > I've developed a *very* primarily patch against our tip BK tree. This is only a compileable code, *NOT TEST AT ALL*. All comments? - Louis ---------------------------------------------------------------------------- ---------------- # This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.887 -> 1.888 # arch/i386/mm/fault.c 1.23 -> 1.24 # arch/i386/Kconfig 1.18 -> 1.19 # arch/i386/kernel/traps.c 1.38 -> 1.39 # arch/i386/kernel/Makefile 1.33 -> 1.34 # kernel/Makefile 1.26 -> 1.27 # (new) -> 1.2 include/linux/kmmio.h # (new) -> 1.1 arch/i386/kernel/kmmio.c # (new) -> 1.2 kernel/kmmio.c # (new) -> 1.1 include/asm-i386/kmmio.h # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/12/18 lo...@ha... 1.888 # add kmmio mechanism. # *NO TEST AT ALL* # -------------------------------------------- # diff -Nru a/arch/i386/Kconfig b/arch/i386/Kconfig --- a/arch/i386/Kconfig Wed Dec 18 20:02:43 2002 +++ b/arch/i386/Kconfig Wed Dec 18 20:02:43 2002 @@ -1506,6 +1506,13 @@ register_kprobe(), and providing a callback function. This is useful for kernel debugging, non-intrusive instrumentation and testing. If in doubt, say "N". +config KMMIO + bool "KMMIO" + depends on KPROBES + help + KMMIO is a Kprobes add-ons for placing a probe on MMIO access, using + register_kmmio(), and providing a callback function. This is useful + for monitoring driver access specific MMIO address. config FI bool "Fault Injection (EXPERIMENTAL)" diff -Nru a/arch/i386/kernel/Makefile b/arch/i386/kernel/Makefile --- a/arch/i386/kernel/Makefile Wed Dec 18 20:02:43 2002 +++ b/arch/i386/kernel/Makefile Wed Dec 18 20:02:43 2002 @@ -31,6 +31,7 @@ obj-$(CONFIG_EDD) += edd.o obj-$(CONFIG_MODULES) += module.o obj-$(CONFIG_KPROBES) += kprobes.o +obj-$(CONFIG_KMMIO) += kmmio.o obj-y += sysenter.o EXTRA_AFLAGS := -traditional diff -Nru a/arch/i386/kernel/kmmio.c b/arch/i386/kernel/kmmio.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/arch/i386/kernel/kmmio.c Wed Dec 18 20:02:43 2002 @@ -0,0 +1,134 @@ +/* + * Support for kernel probes. + * (C) 2002 Vamsi Krishna S <vam...@in...>. + */ + +#include <linux/config.h> +#include <linux/kmmio.h> +#include <linux/ptrace.h> +#include <linux/spinlock.h> +#include <linux/preempt.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <asm/io.h> + +static struct kmmio_probe *current_kmmio = NULL; +static int is_trigger; +static unsigned long kmmio_saved_eflags; +/* + * Interrupts are disabled on entry as trap3 is an interrupt gate and they + * remain disabled thorough out this function. + */ +int kmmio_handler(struct pt_regs *regs, unsigned long addr) +{ + struct kmmio_probe *p; + struct kmmio_fault_page *f; + + /* We're in an interrupt, but this is clear and BUG()-safe. */ + preempt_disable(); + + lock_kmmio(); + + is_trigger = 1; + f = get_kmmio_fault_page((void *)addr); + if (!f) { + /* this page fault is not caused by kmmio */ + /* XXX some pending fault on other cpu may cause problem! */ + unlock_kmmio(); + goto no_kmmio; + } + + p = get_kmmio_probe((void *)addr); + if (!p) { + + /* The fault is caused by kmmio, but no trigger care the adress */ + is_trigger = 0; + goto no_trigger; + } + + current_kmmio = p; + kmmio_saved_eflags = (regs->eflags & (TF_MASK|IF_MASK)); + + p->pre_handler(p, regs, addr); + +no_trigger: + regs->eflags |= TF_MASK; + regs->eflags &= ~IF_MASK; + + /* We hold lock, now we set present bit in PTE and single step. */ + disarm_kmmio_fault_page(f->page); + + + return 1; + +no_kmmio: + preempt_enable_no_resched(); + return 0; +} + +/* + * Interrupts are disabled on entry as trap1 is an interrupt gate and they + * remain disabled thorough out this function. And we hold kmmio lock. + */ +int post_kmmio_handler(unsigned long condition, struct pt_regs *regs) +{ + if (!is_kmmio_active()) + return 0; + if (!current_kmmio) + return 0; + + if (is_trigger && current_kmmio->post_handler) + current_kmmio->post_handler(current_kmmio, condition, regs); + + arm_kmmio_fault_page(get_kmmio_fault_page(current_kmmio->addr)->page ); + regs->eflags &= ~TF_MASK; + regs->eflags |= kmmio_saved_eflags; + + unlock_kmmio(); + preempt_enable_no_resched(); + + /* + * if somebody else is singlestepping across a probe point, eflags + * will have TF set, in which case, continue the remaining processing + * of do_debug, as if this is not a probe hit. + */ + if (regs->eflags & TF_MASK) + return 0; + + return 1; +} + +static pte_t *get_pte(unsigned long address) { + pgd_t *pgd = pgd_offset_k(address); + pmd_t *pmd = pmd_offset(pgd, address); + if (pmd_large(*pmd)) + return (pte_t *)pmd; + return pte_offset_kernel(pmd, address); +}; + +/** + * Set/Clear pte bits + */ +static void clr_pte_bits(unsigned long addr, unsigned long bitmask) { + pte_t *pte; + pte = get_pte(addr); + set_pte( pte, __pte( pte_val(*pte) & ~bitmask) ); +}; + +static void set_pte_bits(unsigned long addr, unsigned long bitmask) { + pte_t *pte; + pte = get_pte(addr); + set_pte( pte, __pte( pte_val(*pte) | bitmask) ); +}; + +void arm_kmmio_fault_page(kmmio_addr_t page) +{ + (unsigned long)page &= PAGE_MASK; + clr_pte_bits(page, _PAGE_PRESENT); +} + +void disarm_kmmio_fault_page(kmmio_addr_t page) +{ + (unsigned long)page &= PAGE_MASK; + set_pte_bits(page, _PAGE_PRESENT); +} diff -Nru a/arch/i386/kernel/traps.c b/arch/i386/kernel/traps.c --- a/arch/i386/kernel/traps.c Wed Dec 18 20:02:43 2002 +++ b/arch/i386/kernel/traps.c Wed Dec 18 20:02:43 2002 @@ -25,6 +25,7 @@ #include <linux/highmem.h> #include <linux/kallsyms.h> #include <linux/kprobes.h> +#include <linux/kmmio.h> #ifdef CONFIG_EISA #include <linux/ioport.h> @@ -47,7 +48,6 @@ #include <asm/smp.h> #include <asm/pgalloc.h> #include <asm/arch_hooks.h> -#include <asm/fi.h> #include <linux/irq.h> #include <linux/module.h> @@ -599,7 +599,7 @@ if (post_kprobe_handler(regs)) return 1; - if (fi_post_page_fault(condition, regs)) + if (post_kmmio_handler(condition, regs)) return 1; /* Interrupts not disabled for normal trap handling. */ diff -Nru a/arch/i386/mm/fault.c b/arch/i386/mm/fault.c --- a/arch/i386/mm/fault.c Wed Dec 18 20:02:43 2002 +++ b/arch/i386/mm/fault.c Wed Dec 18 20:02:43 2002 @@ -20,6 +20,7 @@ #include <linux/tty.h> #include <linux/vt_kern.h> /* For unblank_screen() */ #include <linux/kprobes.h> +#include <linux/kmmio.h> #include <asm/system.h> #include <asm/uaccess.h> @@ -166,7 +167,7 @@ if (kprobe_running() && kprobe_fault_handler(regs, 14)) return; - if (fi_page_fault(regs, address)) + if (is_kmmio_active() && kmmio_handler(regs, address)) return; /* It's safe to allow irq's after cr2 has been saved */ diff -Nru a/include/asm-i386/kmmio.h b/include/asm-i386/kmmio.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/include/asm-i386/kmmio.h Wed Dec 18 20:02:43 2002 @@ -0,0 +1,24 @@ +#ifndef _ASM_KMMIO_H +#define _ASM_KMMIO_H +/* + * Dynamic Probes (kprobes) support + * Vamsi Krishna S <vam...@in...>, July, 2002 + * Mailing list: dp...@ww... + */ +#include <linux/types.h> +#include <linux/ptrace.h> + +struct pt_regs; + +typedef void* kmmio_addr_t; + +#ifdef CONFIG_KMMIO +extern void arm_kmmio_fault_page(kmmio_addr_t page); +extern void disarm_kmmio_fault_page(kmmio_addr_t page); +extern int post_kmmio_handler(unsigned long condition, struct pt_regs *regs); +extern int kmmio_handler(struct pt_regs *regs, unsigned long addr); +#else /* !CONFIG_KMMIO */ +static inline int post_kmmio_handler(unsigned long condition, struct pt_regs *regs) { return 0; } +static inline int kmmio_handler(struct pt_regs *regs, unsigned long addr) { return 0; } +#endif +#endif /* _ASM_KMMIO_H */ diff -Nru a/include/linux/kmmio.h b/include/linux/kmmio.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/include/linux/kmmio.h Wed Dec 18 20:02:43 2002 @@ -0,0 +1,65 @@ +#ifndef _LINUX_KMMIO_H +#define _LINUX_KMMIO_H +#include <linux/config.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/smp.h> +#include <asm/kmmio.h> + +struct kmmio_probe; +struct kmmio_fault_page; +struct pt_regs; + +typedef void (*kmmio_pre_handler_t)(struct kmmio_probe *, struct pt_regs *, unsigned long addr); +typedef void (*kmmio_post_handler_t)(struct kmmio_probe *, unsigned long condition, struct pt_regs *); +struct kmmio_probe { + struct list_head list; + + /* location of the probe point */ + kmmio_addr_t addr; + + /* Called before addr is executed. */ + kmmio_pre_handler_t pre_handler; + + /* Called after addr is executed, unless... */ + kmmio_post_handler_t post_handler; + + /* opaque data structure used by register */ + void *data; +}; + +struct kmmio_fault_page { + struct list_head list; + + /* location of the fault page */ + kmmio_addr_t page; + + int count; +}; + +#ifdef CONFIG_KMMIO +/* Locks kmmio: irq must be disabled */ +void lock_kmmio(void); +void unlock_kmmio(void); + +/* kmmio is active by some kmmio_probes? */ +static inline int is_kmmio_active(void) +{ + extern unsigned int kmmio_count; + return kmmio_count; +} + +/* Get the kmmio at this addr (if any). Must have called lock_kmmio */ +struct kmmio_probe *get_kmmio_probe(kmmio_addr_t addr); +struct kmmio_fault_page *get_kmmio_fault_page(kmmio_addr_t page); +int add_kmmio_fault_page(kmmio_addr_t page); +void release_kmmio_fault_page(kmmio_addr_t page); + +int register_kmmio_probe(struct kmmio_probe *p); +void unregister_kmmio_probe(struct kmmio_probe *p); +#else +static inline int is_kmmio_active(void) { return 0; } +static inline int register_kmmio_probe(struct kmmio_probe *p) { return -ENOSYS; } +static inline void unregister_kmmio_probe(struct kmmio_probe *p) { } +#endif +#endif /* _LINUX_KMMIO_H */ diff -Nru a/kernel/Makefile b/kernel/Makefile --- a/kernel/Makefile Wed Dec 18 20:02:43 2002 +++ b/kernel/Makefile Wed Dec 18 20:02:43 2002 @@ -23,6 +23,7 @@ obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend.o obj-$(CONFIG_COMPAT) += compat.o obj-$(CONFIG_KPROBES) += kprobes.o +obj-$(CONFIG_KMMIO) += kmmio.o obj-$(CONFIG_FI) += fi_core.o ifneq ($(CONFIG_IA64),y) diff -Nru a/kernel/kmmio.c b/kernel/kmmio.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/kernel/kmmio.c Wed Dec 18 20:02:43 2002 @@ -0,0 +1,141 @@ +/* Support for MMIO probes. + * Stolen many code from kprobes + * (C) 2002 Louis Zhuang <lou...@in...>. +*/ + +#include <linux/kmmio.h> +#include <linux/spinlock.h> +#include <linux/hash.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/cacheflush.h> +#include <asm/errno.h> + +#define KMMIO_HASH_BITS 6 +#define KMMIO_TABLE_SIZE (1 << KMMIO_HASH_BITS) + +static struct list_head kmmio_table[KMMIO_TABLE_SIZE]; + +#define KMMIO_FAULT_HASH_BITS 4 +#define KMMIO_FAULT_HASH_SIZE (1 << KMMIO_FAULT_HASH_BITS) +static struct list_head kmmio_fault_table[KMMIO_FAULT_HASH_SIZE]; + +unsigned int kmmio_count = 0; +static spinlock_t kmmio_lock = SPIN_LOCK_UNLOCKED; + +/* Locks kmmio: irqs must be disabled */ +void lock_kmmio(void) +{ + spin_lock(&kmmio_lock); +} + +void unlock_kmmio(void) +{ + spin_unlock(&kmmio_lock); +} + +/* You have to be holding the kmmio_lock */ +struct kmmio_probe *get_kmmio_probe(void *addr) +{ + struct list_head *head, *tmp; + + head = &kmmio_table[hash_ptr(addr, KMMIO_HASH_BITS)]; + list_for_each(tmp, head) { + struct kmmio_probe *p = list_entry(tmp, struct kmmio_probe, list); + if (p->addr == addr) + return p; + } + return NULL; +} + +int register_kmmio_probe(struct kmmio_probe *p) +{ + int ret = 0; + + kmmio_count++; + spin_lock_irq(&kmmio_lock); + if (get_kmmio_probe(p->addr)) { + ret = -EEXIST; + goto out; + } + list_add(&p->list, &kmmio_table[hash_ptr(p->addr, KMMIO_HASH_BITS)]); + + add_kmmio_fault_page(p->addr); + + out: + spin_unlock_irq(&kmmio_lock); + return ret; +} + +void unregister_kmmio_probe(struct kmmio_probe *p) +{ + spin_lock_irq(&kmmio_lock); + release_kmmio_fault_page(p->addr); + list_del(&p->list); + spin_unlock_irq(&kmmio_lock); + kmmio_count--; +} + +struct kmmio_fault_page *get_kmmio_fault_page(kmmio_addr_t page) +{ + struct list_head *head, *tmp; + + (unsigned long)page &= PAGE_MASK; + head = &kmmio_fault_table[hash_ptr(page, KMMIO_HASH_BITS)]; + list_for_each(tmp, head) { + struct kmmio_fault_page *p = list_entry(tmp, struct kmmio_fault_page, list); + if (p->page == page) + return p; + } + return NULL; +} + +int add_kmmio_fault_page(kmmio_addr_t page) +{ + struct kmmio_fault_page *f; + + (unsigned long)page &= PAGE_MASK; + f = get_kmmio_fault_page(page); + if (f) { + f->count++; + return 0; + } + f = (struct kmmio_fault_page *)kmalloc(sizeof(*f), GFP_KERNEL); + f->count = 1; + f->page = page; + list_add(&f->list, &kmmio_fault_table[hash_ptr(f->page, KMMIO_HASH_BITS)]); + + arm_kmmio_fault_page(f->page); + return 0; +} + +void release_kmmio_fault_page(kmmio_addr_t page) +{ + struct kmmio_fault_page *f; + + (unsigned long)page &= PAGE_MASK; + f = get_kmmio_fault_page(page); + if (!f) return; + f->count--; + if(!f->count) { + disarm_kmmio_fault_page(f->page); + list_del(&f->list); + } +} + +static int __init init_kmmio(void) +{ + int i; + + /* FIXME allocate the probe table, currently defined statically */ + /* initialize all list heads */ + for (i = 0; i < KMMIO_TABLE_SIZE; i++) + INIT_LIST_HEAD(&kmmio_table[i]); + + return 0; +} +__initcall(init_kmmio); + +EXPORT_SYMBOL_GPL(register_kmmio_probe); +EXPORT_SYMBOL_GPL(unregister_kmmio_probe); |