This is the mail archive of the systemtap@sourceware.org mailing list for the systemtap project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC PATCH 2/2] check safety in stop_machine_run


Hi,

This patch contains full set of a djprobe patch for linux-2.6.12 that uses stop_machine_run() to check safety of all cpus. This method has some performance issues.
- It must create threads for each CPU.
- It might sleep.
- If we want to insert a number of probes (this is usual situation in the module initialization), we must wait completion of all insertion.


So, I think this is an optional method of check routine.

--
Masami HIRAMATSU
2nd Research Dept.
Hitachi, Ltd., Systems Development Laboratory
E-mail: hiramatu@sdl.hitachi.co.jp



diff -Narup linux-2.6.12/arch/i386/Kconfig.debug linux-2.6.12-djprobe.3/arch/i386/Kconfig.debug
--- linux-2.6.12/arch/i386/Kconfig.debug	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/arch/i386/Kconfig.debug	2005-08-04 16:58:55.000000000 +0900
@@ -29,6 +29,14 @@ config KPROBES
 	  for kernel debugging, non-intrusive instrumentation and testing.
 	  If in doubt, say "N".
 
+config DJPROBE
+	bool "Direct Jump Probe"
+	depends on KPROBES && !PREEMPT
+	help
+	  Djprobe is ultra-light probing system. This uses a jmp opecode 
+	  instead of an int3 trap opecode. Djprobe is useful for probing
+	  kernel tight timing problems.
+
 config DEBUG_STACK_USAGE
 	bool "Stack utilization instrumentation"
 	depends on DEBUG_KERNEL
diff -Narup linux-2.6.12/arch/i386/kernel/Makefile linux-2.6.12-djprobe.3/arch/i386/kernel/Makefile
--- linux-2.6.12/arch/i386/kernel/Makefile	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/arch/i386/kernel/Makefile	2005-08-04 16:58:55.000000000 +0900
@@ -27,6 +27,7 @@ obj-$(CONFIG_X86_REBOOTFIXUPS)	+= reboot
 obj-$(CONFIG_X86_NUMAQ)		+= numaq.o
 obj-$(CONFIG_X86_SUMMIT_NUMA)	+= summit.o
 obj-$(CONFIG_KPROBES)		+= kprobes.o
+obj-$(CONFIG_DJPROBE)		+= stub_djprobe.o
 obj-$(CONFIG_MODULES)		+= module.o
 obj-y				+= sysenter.o vsyscall.o
 obj-$(CONFIG_ACPI_SRAT) 	+= srat.o
diff -Narup linux-2.6.12/arch/i386/kernel/kprobes.c linux-2.6.12-djprobe.3/arch/i386/kernel/kprobes.c
--- linux-2.6.12/arch/i386/kernel/kprobes.c	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/arch/i386/kernel/kprobes.c	2005-08-05 20:32:12.000000000 +0900
@@ -32,6 +32,7 @@
 #include <linux/preempt.h>
 #include <asm/kdebug.h>
 #include <asm/desc.h>
+#include <asm/cacheflush.h>
 
 /* kprobe_status settings */
 #define KPROBE_HIT_ACTIVE	0x00000001
@@ -390,3 +391,126 @@ int longjmp_break_handler(struct kprobe 
 	}
 	return 0;
 }
+
+#ifdef CONFIG_DJPROBE
+/*
+ * DJProbe (Direct Jump Probe): Insertion and Recovery
+ * Insertion:
+ * 1. Djprobe inserts a kprobe at the specified address.
+ * 2. It uses stop_machine_run() to check safety of the all cpus.
+ * 3. After it finish checking all cpus, it replace int3 to relative jump 
+ *    instruction.
+ * Recovery:
+ * 1. Djprobe recovers a kprobe's int3 and original code except the first byte
+ *    at the specified address.
+ * 2. It uses stop_machine_run() to check safety of the all cpus.
+ * 3. After it finish checking all cpus, it uninstalls the kprobe.
+ */
+
+/* jmp code manipulators */
+struct __arch_jmp_op {
+	char op;
+	long raddr;
+} __attribute__((packed));
+/* insert jmp code */
+static inline void __set_jmp_op(void *from, void *to)
+{
+	struct __arch_jmp_op *jop;
+	jop = (struct __arch_jmp_op *)from;
+	jop->raddr=(long)(to) - ((long)(from) + 5);
+	smp_flush_icache_all();
+	jop->op = RELATIVEJUMP_INSTRUCTION;
+}
+/* switch back to the kprobe */
+static inline void __set_breakpoint_op(void *dest, void *orig)
+{
+	struct __arch_jmp_op *jop = (struct __arch_jmp_op *)dest, 
+		*jop2 = (struct __arch_jmp_op *)orig;
+
+	jop->op = BREAKPOINT_INSTRUCTION;
+	smp_flush_icache_all();
+	jop->raddr = jop2->raddr;
+}
+/*
+ * djprobe call back function: called from stub code.
+ */
+static void asmlinkage djprobe_callback(struct djprobe_instance * djpi,
+					struct pt_regs *regs)
+{
+	/*TODO: use list*/
+	if (djpi->djp && djpi->djp->handler)
+		djpi->djp->handler(djpi->djp, regs);
+}
+
+/*
+ * Copy post processing instructions
+ * Target instructions MUST be relocatable.
+ */
+int arch_prepare_djprobe_instance(struct djprobe_instance *djpi,
+				  unsigned long size)
+{
+	kprobe_opcode_t *stub;
+	stub = djpi->stub.insn;
+
+	/* copy arch-dep-instance from template */
+	memcpy((void*)stub, (void*)&arch_tmpl_stub_entry, ARCH_STUB_SIZE);
+
+	/* set probe information */
+	*((long*)(stub + ARCH_STUB_VAL_IDX)) = (long)djpi;
+	/* set probe function */
+	*((long*)(stub + ARCH_STUB_CALL_IDX)) = (long)djprobe_callback;
+
+	/* copy instructions into the middle of axporbe instance */
+	memcpy((void*)(stub + ARCH_STUB_INST_IDX),
+	       (void*)djpi->kp.addr, size);
+	djpi->stub.size = size;
+
+	/* set returning jmp instruction at the tail of axporbe instance*/
+	__set_jmp_op(stub + ARCH_STUB_END_IDX, 
+		     (void*)((long)djpi->kp.addr + size));
+
+	return 0;
+}
+
+/* Insert "jmp" instruction into the probing point. */
+void arch_install_djprobe_instance(struct djprobe_instance *djpi)
+{
+	kprobe_opcode_t *stub;
+	stub = djpi->stub.insn;
+	__set_jmp_op((void*)djpi->kp.addr, (void*)stub);
+}
+/* Write back original instructions & kprobe */
+void arch_uninstall_djprobe_instance(struct djprobe_instance *djpi)
+{
+	kprobe_opcode_t *stub;
+	stub = &djpi->stub.insn[ARCH_STUB_INST_IDX];
+	__set_breakpoint_op((void*)djpi->kp.addr, (void*)stub);
+}
+
+/*
+ * safety check handler
+ */
+int djprobe_bypass_handler(struct kprobe * kp, struct pt_regs * regs)
+{
+	struct djprobe_instance *djpi = 
+		container_of(kp,struct djprobe_instance, kp);
+	kprobe_opcode_t *stub = djpi->stub.insn;
+
+	if (DJPI_EMPTY(djpi)) {
+		/* fixup dummy instruction */
+		kp->ainsn.insn[0] = djpi->stub.insn[ARCH_STUB_INST_IDX];
+		return 0;
+	} else {
+		regs->eip = (unsigned long)stub;
+		regs->eflags |= TF_MASK;
+		regs->eflags &= ~IF_MASK;
+		/*
+		 * dummy return code :
+		 * This code is to avoid to be changed eip value by 
+		 * resume_execute() of kprobes
+		 */
+		kp->ainsn.insn[0] = RETURN_INSTRUCTION;
+		return 1; /* already prepared */
+	}
+}
+#endif /*DJPROBE*/
diff -Narup linux-2.6.12/arch/i386/kernel/stub_djprobe.S linux-2.6.12-djprobe.3/arch/i386/kernel/stub_djprobe.S
--- linux-2.6.12/arch/i386/kernel/stub_djprobe.S	1970-01-01 09:00:00.000000000 +0900
+++ linux-2.6.12-djprobe.3/arch/i386/kernel/stub_djprobe.S	2005-08-04 16:58:55.000000000 +0900
@@ -0,0 +1,78 @@
+/*
+ *  linux/arch/i386/stub_djprobe.S
+ *
+ *  Copyright (C) HITACHI,LTD. 2005
+ *  Created by Masami Hiramatsu <hiramatu@sdl.hitachi.co.jp>
+ */
+
+#include <linux/config.h>
+
+# jmp into this function from other functions.
+.global arch_tmpl_stub_entry
+arch_tmpl_stub_entry:
+	nop
+	subl $8, %esp	#skip segment registers.
+	pushf
+	subl $20, %esp	#skip segment registers.
+	pushl %eax
+	pushl %ebp
+	pushl %edi
+	pushl %esi
+	pushl %edx
+	pushl %ecx
+	pushl %ebx
+
+	movl %esp, %eax
+	pushl %eax
+	addl $60, %eax
+	movl %eax, 56(%esp)
+.global arch_tmpl_stub_val
+arch_tmpl_stub_val:
+	movl $0xffffffff, %eax
+	pushl %eax
+.global arch_tmpl_stub_call
+arch_tmpl_stub_call:
+	movl $0xffffffff, %eax
+	call *%eax
+	addl $8, %esp
+
+	popl %ebx
+	popl %ecx
+	popl %edx
+	popl %esi
+	popl %edi
+	popl %ebp
+	popl %eax
+	addl $20, %esp
+	popf
+	addl $8, %esp
+.global arch_tmpl_stub_inst
+arch_tmpl_stub_inst:
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+
+.global arch_tmpl_stub_end
+arch_tmpl_stub_end:
+	nop
+	nop
+	nop
+	nop
+	nop
diff -Narup linux-2.6.12/arch/i386/mm/pageattr.c linux-2.6.12-djprobe.3/arch/i386/mm/pageattr.c
--- linux-2.6.12/arch/i386/mm/pageattr.c	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/arch/i386/mm/pageattr.c	2005-08-04 16:58:55.000000000 +0900
@@ -217,5 +217,15 @@ void kernel_map_pages(struct page *page,
 }
 #endif
 
+static void local_flush_icache(void * info) 
+{
+	cpuid_eax(0);
+}
+
+void smp_flush_icache_all(void)
+{
+	on_each_cpu(local_flush_icache, NULL, 1,1);
+}
+
 EXPORT_SYMBOL(change_page_attr);
 EXPORT_SYMBOL(global_flush_tlb);
diff -Narup linux-2.6.12/include/asm-i386/cacheflush.h linux-2.6.12-djprobe.3/include/asm-i386/cacheflush.h
--- linux-2.6.12/include/asm-i386/cacheflush.h	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/include/asm-i386/cacheflush.h	2005-08-04 16:58:55.000000000 +0900
@@ -31,4 +31,6 @@ int change_page_attr(struct page *page, 
 void kernel_map_pages(struct page *page, int numpages, int enable);
 #endif
 
+void smp_flush_icache_all(void);
+
 #endif /* _I386_CACHEFLUSH_H */
diff -Narup linux-2.6.12/include/asm-i386/kprobes.h linux-2.6.12-djprobe.3/include/asm-i386/kprobes.h
--- linux-2.6.12/include/asm-i386/kprobes.h	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/include/asm-i386/kprobes.h	2005-08-04 16:58:55.000000000 +0900
@@ -31,6 +31,8 @@ struct pt_regs;
 
 typedef u8 kprobe_opcode_t;
 #define BREAKPOINT_INSTRUCTION	0xcc
+#define RELATIVEJUMP_INSTRUCTION 0xe9
+#define RETURN_INSTRUCTION 0xc3
 #define MAX_INSN_SIZE 16
 #define MAX_STACK_SIZE 64
 #define MIN_STACK_SIZE(ADDR) (((MAX_STACK_SIZE) < \
@@ -46,6 +48,31 @@ struct arch_specific_insn {
 	kprobe_opcode_t insn[MAX_INSN_SIZE];
 };
 
+#ifdef CONFIG_DJPROBE
+/* stub template code */
+extern long arch_tmpl_stub_entry;
+extern long arch_tmpl_stub_val;
+extern long arch_tmpl_stub_call;
+extern long arch_tmpl_stub_inst;
+extern long arch_tmpl_stub_end;
+
+#define ARCH_STUB_VAL_IDX ((long)&arch_tmpl_stub_val - (long)&arch_tmpl_stub_entry + 1)
+#define ARCH_STUB_CALL_IDX ((long)&arch_tmpl_stub_call - (long)&arch_tmpl_stub_entry + 1)
+#define ARCH_STUB_INST_IDX ((long)&arch_tmpl_stub_inst - (long)&arch_tmpl_stub_entry)
+#define ARCH_STUB_END_IDX ((long)&arch_tmpl_stub_end - (long)&arch_tmpl_stub_entry)
+#define ARCH_STUB_SIZE ((long)&arch_tmpl_stub_end - (long)&arch_tmpl_stub_entry + 5)
+#endif
+
+#define ARCH_STUB_INSN_SIZE 80
+#define ARCH_STUB_INSN_MAX 20
+#define ARCH_STUB_INSN_MIN 5
+
+struct arch_djprobe_stub {
+	kprobe_opcode_t insn[ARCH_STUB_INSN_SIZE];
+	int size;
+};
+
+#define arch_serialize_cache() cpuid_eax(0)
 
 /* trap3/1 are intr gates for kprobes.  So, restore the status of IF,
  * if necessary, before executing the original int3/1 (trap) handler.
diff -Narup linux-2.6.12/include/linux/kprobes.h linux-2.6.12-djprobe.3/include/linux/kprobes.h
--- linux-2.6.12/include/linux/kprobes.h	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/include/linux/kprobes.h	2005-08-05 00:37:03.000000000 +0900
@@ -25,6 +25,8 @@
  *		Rusty Russell).
  * 2004-July	Suparna Bhattacharya <suparna@in.ibm.com> added jumper probes
  *		interface to access function arguments.
+ * 2005-June	Masami HIRAMATSU <hiramatu@sdl.hitachi.co.jp> added direct 
+ * 		jump probe (djprobe) interface to reduce overhead.
  */
 #include <linux/config.h>
 #include <linux/list.h>
@@ -85,6 +87,40 @@ struct jprobe {
 	kprobe_opcode_t *entry;	/* probe handling code to jump to */
 };
 
+/* djprobe's instance (internal use)*/
+struct djprobe_instance {
+	struct djprobe *djp;
+	struct arch_djprobe_stub stub;
+
+	struct kprobe kp;
+	struct list_head list; /* list of djprobe_instances */
+	cpumask_t checked_cpus;
+};
+#define DJPI_EMPTY(djpi)  (djpi->djp==NULL)
+#define DJPI_CHECKED(djpi) (cpus_equal(djpi->checked_cpus, cpu_online_map))
+
+struct djprobe;
+typedef void (*djprobe_handler_t)(struct djprobe *, struct pt_regs *);
+/*
+ * Direct Jump probe interface structure
+ */
+struct djprobe {
+#ifndef CONFIG_DJPROBE
+	struct kprobe kp;
+#endif
+	/* location of the probe point */
+	void * addr;
+	
+	/* sum of length of the replacing codes */
+	int size;
+	
+	/* probing handler (pre-executed) */
+	djprobe_handler_t handler;
+	
+	/* pointer for instance */
+	struct djprobe_instance * inst;
+};
+
 #ifdef CONFIG_KPROBES
 /* Locks kprobe: irq must be disabled */
 void lock_kprobes(void);
@@ -113,6 +149,31 @@ int register_jprobe(struct jprobe *p);
 void unregister_jprobe(struct jprobe *p);
 void jprobe_return(void);
 
+#ifdef CONFIG_DJPROBE
+extern int arch_prepare_djprobe_instance(struct djprobe_instance *djpi,
+					 unsigned long size);
+extern int djprobe_bypass_handler(struct kprobe * kp, struct pt_regs * regs);
+extern void arch_install_djprobe_instance(struct djprobe_instance *djpi);
+extern void arch_uninstall_djprobe_instance(struct djprobe_instance *djpi);
+
+int register_djprobe(struct djprobe *p);
+void unregister_djprobe(struct djprobe *p);
+#else
+static inline int register_djprobe(struct djprobe *p)
+{
+	if (p!=NULL) {
+		p->kp.addr = p->addr;
+		p->kp.pre_handler = (kprobe_pre_handler_t)p->handler;
+		return register_kprobe(&p->kp);
+	}
+	return -EINVAL;
+}
+static inline void unregister_djprobe(struct djprobe *p)
+{
+	unregister_kprobe(&p->kp);
+}
+#endif
+
 #else
 static inline int kprobe_running(void)
 {
@@ -135,5 +196,14 @@ static inline void unregister_jprobe(str
 static inline void jprobe_return(void)
 {
 }
+static inline int register_djprobe(struct djprobe *p)
+{
+	return -ENOSYS;
+}
+static inline void unregister_djprobe(struct djprobe *p)
+{
+}
 #endif
+
+
 #endif				/* _LINUX_KPROBES_H */
diff -Narup linux-2.6.12/kernel/kprobes.c linux-2.6.12-djprobe.3/kernel/kprobes.c
--- linux-2.6.12/kernel/kprobes.c	2005-06-18 04:48:29.000000000 +0900
+++ linux-2.6.12-djprobe.3/kernel/kprobes.c	2005-08-05 16:29:17.000000000 +0900
@@ -27,12 +27,16 @@
  *		interface to access function arguments.
  * 2004-Sep	Prasanna S Panchamukhi <prasanna@in.ibm.com> Changed Kprobes
  *		exceptions notifier to be first on the priority list.
+ * 2005-June	Masami HIRAMATSU <hiramatu@sdl.hitachi.co.jp> added direct 
+ * 		jump probe (djprobe) interface to reduce overhead.
  */
 #include <linux/kprobes.h>
 #include <linux/spinlock.h>
 #include <linux/hash.h>
 #include <linux/init.h>
 #include <linux/module.h>
+#include <linux/stop_machine.h>
+#include <linux/hardirq.h>
 #include <asm/cacheflush.h>
 #include <asm/errno.h>
 #include <asm/kdebug.h>
@@ -257,6 +261,147 @@ void unregister_jprobe(struct jprobe *jp
 	unregister_kprobe(&jp->kp);
 }
 
+#ifdef CONFIG_DJPROBE
+/*
+ * The djprobe do not refer it's list when probe function called.
+ * This list is operated on registering and unregistering djprobe.
+ * Thus, It is not required processing speed. I decided using a 'list'.
+ */
+static DEFINE_SPINLOCK(djprobe_lock);
+static LIST_HEAD(djprobe_list);
+static int nr_instances = 0;
+
+static void free_djprobe_instance_nl(struct djprobe_instance *djpi)
+{
+	list_del(&djpi->list);
+	nr_instances -- ;
+	unregister_kprobe(&(djpi->kp));
+	kfree(djpi);
+}
+
+static int task_check_djprobe_instance(void *data)
+{
+	struct djprobe_instance *djpi = data;
+	int cpu;
+	/* all other cpus are running in stop_machine(),
+	 so we can check all cpus */
+	for_each_online_cpu(cpu)
+		cpu_set(cpu, djpi->checked_cpus);
+	return 0;
+}
+
+void smp_check_safety_djprobe_instance(struct djprobe_instance *djpi)
+{
+	stop_machine_run(task_check_djprobe_instance,
+			 djpi, NR_CPUS);
+	spin_lock(&djprobe_lock);
+	if (DJPI_EMPTY(djpi)) {
+		free_djprobe_instance_nl(djpi);
+	} else {
+		arch_install_djprobe_instance(djpi);
+	}
+	spin_unlock(&djprobe_lock);
+}
+
+/*Use kprobe to check safety and install */
+static int install_djprobe_instance(struct djprobe_instance *djpi)
+{
+	int ret;
+	djpi->kp.pre_handler = djprobe_bypass_handler;
+	ret = register_kprobe(&(djpi->kp));
+	cpus_clear(djpi->checked_cpus);
+	return ret;
+}
+/* Use kprobe to check safety and release */
+static void uninstall_djprobe_instance(struct djprobe_instance *djpi)
+{
+	arch_uninstall_djprobe_instance(djpi);
+	cpus_clear(djpi->checked_cpus);
+}
+
+int register_djprobe(struct djprobe * djp)
+{
+	struct djprobe_instance *djpi;
+	int ret = 0;
+
+	if (djp == NULL || djp->addr == NULL || 
+	    djp->size > ARCH_STUB_INSN_MAX || 
+	    djp->size < ARCH_STUB_INSN_MIN || 
+	    djp->inst != NULL)
+		return -EINVAL;
+	
+	if (in_interrupt())
+		return -EINVAL;
+
+	spin_lock(&djprobe_lock);
+
+	list_for_each_entry(djpi, &djprobe_list, list) {
+		if (djpi->kp.addr == djp->addr) {
+			if (!DJPI_EMPTY(djpi)) {
+				ret = -EBUSY; /* already used ... */
+				goto out;
+			}
+			djp->inst = djpi;
+			djpi->djp = djp; /*TODO: use list*/
+			cpus_clear(djpi->checked_cpus);
+			goto out;
+		}
+	}
+	/* could not find */
+	djpi = kmalloc(sizeof(struct djprobe_instance),GFP_KERNEL);
+	if (djpi == NULL) {
+		ret = -ENOMEM; /* memory allocation error */
+		goto out;
+	}
+
+	/* initialize */
+	memset(djpi, 0, sizeof(struct djprobe_instance));
+	INIT_LIST_HEAD(&djpi->list);
+	djpi->kp.addr = djp->addr;
+	arch_prepare_djprobe_instance(djpi, djp->size); /*TODO : remove size*/
+	nr_instances ++ ;
+	list_add(&djpi->list, &djprobe_list);
+
+	/* attach */
+	djp->inst = djpi;
+	djpi->djp = djp; /*TODO: use list*/
+	
+	ret = install_djprobe_instance(djpi);
+	if (ret < 0) { /* failed to attach */
+		djp->inst = NULL;
+		list_del(&djpi->list);
+		nr_instances --;
+		kfree(djpi);
+	}
+out:
+	spin_unlock(&djprobe_lock);
+	if (ret == 0) smp_check_safety_djprobe_instance(djpi);
+	return ret;
+}
+
+void unregister_djprobe(struct djprobe * djp)
+{
+	struct djprobe_instance *djpi;
+	if (djp == NULL || djp->inst == NULL) 
+		return ;
+
+	WARN_ON(in_interrupt());
+
+	djpi = djp->inst;
+	spin_lock(&djprobe_lock);
+	lock_kprobes();
+	djp->inst = NULL;
+	djpi->djp = NULL; /*TODO: use list*/
+	if (DJPI_EMPTY(djpi)) {
+		uninstall_djprobe_instance(djpi);
+	}
+	unlock_kprobes();
+	spin_unlock(&djprobe_lock);
+	smp_check_safety_djprobe_instance(djpi);
+}
+
+#endif /*DJPROBE*/
+
 static int __init init_kprobes(void)
 {
 	int i, err = 0;
@@ -277,3 +422,7 @@ EXPORT_SYMBOL_GPL(unregister_kprobe);
 EXPORT_SYMBOL_GPL(register_jprobe);
 EXPORT_SYMBOL_GPL(unregister_jprobe);
 EXPORT_SYMBOL_GPL(jprobe_return);
+#ifdef CONFIG_DJPROBE
+EXPORT_SYMBOL_GPL(register_djprobe);
+EXPORT_SYMBOL_GPL(unregister_djprobe);
+#endif

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]