Re: [PATCH] UML utrace support, step 1

From: Jeff Dike
Date: Fri Mar 16 2007 - 13:35:13 EST


On Wed, Mar 07, 2007 at 12:01:59AM -0800, Roland McGrath wrote:
> > Below is a tracehook patch for UML which goes right after
> > utrace-utrace-tracehook.patch.
>
> It does not. That patch has regset and ptrace stuff in it. It indeed
> applies fine before or after utrace patches. But it doesn't close to
> compile without utrace-regset.patch and utrace-core.patch applied,
> and even with utrace-ptrace-compat.patch it still doesn't compile for me
> with a lot of complaints about TIF_SINGLESTEP not being defined where
> it's used in asm/tracehook.h.

Did I send the right patch? The one I meant to send (appended below),
indeed builds and runs without utrace-regset.patch and
utrace-core.patch applied. It's utrace-1 in the following:

+ utrace-prep.patch
+ utrace-prep-2.patch
+ utrace-utrace-tracehook.patch
= utrace-1
utrace-utrace-tracehook-ia64.patch
utrace-utrace-tracehook-sparc64.patch
utrace-utrace-tracehook-s390.patch
utrace-utrace-regset.patch
utrace-utrace-regset-ia64.patch
utrace-utrace-regset-sparc64.patch
utrace-utrace-regset-s390.patch
utrace-utrace-core.patch
utrace-utrace-ptrace-compat.patch
utrace-utrace-ptrace-compat-ia64.patch
utrace-utrace-ptrace-compat-sparc64.patch
utrace-utrace-ptrace-compat-s390.patch
revert-utrace-prep-2.patch
utrace-vs-reduce-size-of-task_struct-on-64-bit-machines.patch

Jeff

--
Work email - jdike at linux dot intel dot com



This is the tracehook part of the UML utrace work, enough to get UML
building with the utrace prep patches applied.

Checks of task->ptrace & PT_DTRACE were replaced with
test_thread_flag(TIF_SINGLESTEP, or removed, in the case of execve.

Most of arch/um/kernel/ptrace.c is gone, to be reinstated in future
utrace work.

Similarly, calls to syscall_trace and ptrace notifications in the
signal delivery code are gone.

Signed-off-by: Jeff Dike <jdike@xxxxxxxxxxx>
--
arch/um/kernel/exec.c | 1
arch/um/kernel/process.c | 6
arch/um/kernel/ptrace.c | 333 +++++-----------------------------------
arch/um/kernel/signal.c | 5
arch/um/kernel/skas/syscall.c | 4
arch/um/sys-i386/signal.c | 4
include/asm-um/ptrace-generic.h | 3
include/asm-um/ptrace-i386.h | 2
include/asm-um/ptrace-x86_64.h | 2
include/asm-um/tracehook.h | 64 +++++++
10 files changed, 117 insertions(+), 307 deletions(-)

Index: linux-2.6.21-rc2/arch/um/kernel/exec.c
===================================================================
--- linux-2.6.21-rc2.orig/arch/um/kernel/exec.c 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/arch/um/kernel/exec.c 2007-03-05 13:24:57.000000000 -0500
@@ -51,7 +51,6 @@ static long execve1(char *file, char __u
error = do_execve(file, argv, env, &current->thread.regs);
if (error == 0){
task_lock(current);
- current->ptrace &= ~PT_DTRACE;
#ifdef SUBARCH_EXECVE1
SUBARCH_EXECVE1(&current->thread.regs.regs);
#endif
Index: linux-2.6.21-rc2/arch/um/kernel/process.c
===================================================================
--- linux-2.6.21-rc2.orig/arch/um/kernel/process.c 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/arch/um/kernel/process.c 2007-03-05 13:24:57.000000000 -0500
@@ -458,11 +458,11 @@ int singlestepping(void * t)
{
struct task_struct *task = t ? t : current;

- if ( ! (task->ptrace & PT_DTRACE) )
- return(0);
+ if (!test_thread_flag(TIF_SINGLESTEP))
+ return 0;

if (task->thread.singlestep_syscall)
- return(1);
+ return 1;

return 2;
}
Index: linux-2.6.21-rc2/arch/um/kernel/ptrace.c
===================================================================
--- linux-2.6.21-rc2.orig/arch/um/kernel/ptrace.c 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/arch/um/kernel/ptrace.c 2007-03-05 13:47:30.000000000 -0500
@@ -3,261 +3,26 @@
* Licensed under the GPL
*/

-#include "linux/sched.h"
-#include "linux/mm.h"
-#include "linux/errno.h"
-#include "linux/smp_lock.h"
-#include "linux/security.h"
-#include "linux/ptrace.h"
-#include "linux/audit.h"
-#ifdef CONFIG_PROC_MM
-#include "linux/proc_mm.h"
-#endif
-#include "asm/ptrace.h"
-#include "asm/uaccess.h"
-#include "kern_util.h"
-#include "skas_ptrace.h"
-#include "sysdep/ptrace.h"
-#include "os.h"
-
-static inline void set_singlestepping(struct task_struct *child, int on)
-{
- if (on)
- child->ptrace |= PT_DTRACE;
- else
- child->ptrace &= ~PT_DTRACE;
- child->thread.singlestep_syscall = 0;
-
-#ifdef SUBARCH_SET_SINGLESTEPPING
- SUBARCH_SET_SINGLESTEPPING(child, on);
-#endif
-}
+#include <linux/audit.h>
+#include <linux/elf.h>
+#include <linux/module.h>
+#include <linux/ptrace.h>
+#include <linux/tracehook.h>

/*
* Called by kernel/ptrace.c when detaching..
*/
void ptrace_disable(struct task_struct *child)
{
- set_singlestepping(child,0);
}

-extern int peek_user(struct task_struct * child, long addr, long data);
-extern int poke_user(struct task_struct * child, long addr, long data);
-
long arch_ptrace(struct task_struct *child, long request, long addr, long data)
{
- int i, ret;
- unsigned long __user *p = (void __user *)(unsigned long)data;
-
- switch (request) {
- /* when I and D space are separate, these will need to be fixed. */
- case PTRACE_PEEKTEXT: /* read word at location addr. */
- case PTRACE_PEEKDATA: {
- unsigned long tmp;
- int copied;
-
- ret = -EIO;
- copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
- if (copied != sizeof(tmp))
- break;
- ret = put_user(tmp, p);
- break;
- }
-
- /* read the word at location addr in the USER area. */
- case PTRACE_PEEKUSR:
- ret = peek_user(child, addr, data);
- break;
-
- /* when I and D space are separate, this will have to be fixed. */
- case PTRACE_POKETEXT: /* write the word at location addr. */
- case PTRACE_POKEDATA:
- ret = -EIO;
- if (access_process_vm(child, addr, &data, sizeof(data),
- 1) != sizeof(data))
- break;
- ret = 0;
- break;
-
- case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
- ret = poke_user(child, addr, data);
- break;
-
- case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */
- case PTRACE_CONT: { /* restart after signal. */
- ret = -EIO;
- if (!valid_signal(data))
- break;
-
- set_singlestepping(child, 0);
- if (request == PTRACE_SYSCALL) {
- set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
- }
- else {
- clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
- }
- child->exit_code = data;
- wake_up_process(child);
- ret = 0;
- break;
- }
-
-/*
- * make the child exit. Best I can do is send it a sigkill.
- * perhaps it should be put in the status that it wants to
- * exit.
- */
- case PTRACE_KILL: {
- ret = 0;
- if (child->exit_state == EXIT_ZOMBIE) /* already dead */
- break;
-
- set_singlestepping(child, 0);
- child->exit_code = SIGKILL;
- wake_up_process(child);
- break;
- }
-
- case PTRACE_SINGLESTEP: { /* set the trap flag. */
- ret = -EIO;
- if (!valid_signal(data))
- break;
- clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
- set_singlestepping(child, 1);
- child->exit_code = data;
- /* give it a chance to run. */
- wake_up_process(child);
- ret = 0;
- break;
- }
-
- case PTRACE_DETACH:
- /* detach a process that was attached. */
- ret = ptrace_detach(child, data);
- break;
-
-#ifdef PTRACE_GETREGS
- case PTRACE_GETREGS: { /* Get all gp regs from the child. */
- if (!access_ok(VERIFY_WRITE, p, MAX_REG_OFFSET)) {
- ret = -EIO;
- break;
- }
- for ( i = 0; i < MAX_REG_OFFSET; i += sizeof(long) ) {
- __put_user(getreg(child, i), p);
- p++;
- }
- ret = 0;
- break;
- }
-#endif
-#ifdef PTRACE_SETREGS
- case PTRACE_SETREGS: { /* Set all gp regs in the child. */
- unsigned long tmp = 0;
- if (!access_ok(VERIFY_READ, p, MAX_REG_OFFSET)) {
- ret = -EIO;
- break;
- }
- for ( i = 0; i < MAX_REG_OFFSET; i += sizeof(long) ) {
- __get_user(tmp, p);
- putreg(child, i, tmp);
- p++;
- }
- ret = 0;
- break;
- }
-#endif
-#ifdef PTRACE_GETFPREGS
- case PTRACE_GETFPREGS: /* Get the child FPU state. */
- ret = get_fpregs(data, child);
- break;
-#endif
-#ifdef PTRACE_SETFPREGS
- case PTRACE_SETFPREGS: /* Set the child FPU state. */
- ret = set_fpregs(data, child);
- break;
-#endif
-#ifdef PTRACE_GETFPXREGS
- case PTRACE_GETFPXREGS: /* Get the child FPU state. */
- ret = get_fpxregs(data, child);
- break;
-#endif
-#ifdef PTRACE_SETFPXREGS
- case PTRACE_SETFPXREGS: /* Set the child FPU state. */
- ret = set_fpxregs(data, child);
- break;
-#endif
- case PTRACE_GET_THREAD_AREA:
- ret = ptrace_get_thread_area(child, addr,
- (struct user_desc __user *) data);
- break;
-
- case PTRACE_SET_THREAD_AREA:
- ret = ptrace_set_thread_area(child, addr,
- (struct user_desc __user *) data);
- break;
-
- case PTRACE_FAULTINFO: {
- /* Take the info from thread->arch->faultinfo,
- * but transfer max. sizeof(struct ptrace_faultinfo).
- * On i386, ptrace_faultinfo is smaller!
- */
- ret = copy_to_user(p, &child->thread.arch.faultinfo,
- sizeof(struct ptrace_faultinfo));
- if(ret)
- break;
- break;
- }
-
-#ifdef PTRACE_LDT
- case PTRACE_LDT: {
- struct ptrace_ldt ldt;
-
- if(copy_from_user(&ldt, p, sizeof(ldt))){
- ret = -EIO;
- break;
- }
-
- /* This one is confusing, so just punt and return -EIO for
- * now
- */
- ret = -EIO;
- break;
- }
-#endif
-#ifdef CONFIG_PROC_MM
- case PTRACE_SWITCH_MM: {
- struct mm_struct *old = child->mm;
- struct mm_struct *new = proc_mm_get_mm(data);
-
- if(IS_ERR(new)){
- ret = PTR_ERR(new);
- break;
- }
-
- atomic_inc(&new->mm_users);
- child->mm = new;
- child->active_mm = new;
- mmput(old);
- ret = 0;
- break;
- }
-#endif
-#ifdef PTRACE_ARCH_PRCTL
- case PTRACE_ARCH_PRCTL:
- /* XXX Calls ptrace on the host - needs some SMP thinking */
- ret = arch_prctl_skas(child, data, (void *) addr);
- break;
-#endif
- default:
- ret = ptrace_request(child, request, addr, data);
- break;
- }
-
- return ret;
+ return -ENOSYS;
}

-void send_sigtrap(struct task_struct *tsk, union uml_pt_regs *regs,
- int error_code)
+static void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs,
+ int error_code)
{
struct siginfo info;

@@ -266,56 +31,39 @@ void send_sigtrap(struct task_struct *ts
info.si_code = TRAP_BRKPT;

/* User-mode eip? */
- info.si_addr = UPT_IS_USER(regs) ? (void __user *) UPT_IP(regs) : NULL;
+ info.si_addr = UPT_IS_USER(&regs->regs) ?
+ (void __user *) UPT_IP(&regs->regs) : NULL;

/* Send us the fakey SIGTRAP */
force_sig_info(SIGTRAP, &info, tsk);
}

-/* XXX Check PT_DTRACE vs TIF_SINGLESTEP for singlestepping check and
- * PT_PTRACED vs TIF_SYSCALL_TRACE for syscall tracing check
+/* notification of system call entry/exit
+ * - triggered by current->work.syscall_trace
*/
-void syscall_trace(union uml_pt_regs *regs, int entryexit)
+void do_syscall_trace(struct pt_regs *regs, int entryexit)
{
- int is_singlestep = (current->ptrace & PT_DTRACE) && entryexit;
- int tracesysgood;
+ /* do the secure computing check first */
+ if (!entryexit)
+ secure_computing(PT_REGS_SYSCALL_NR(regs));

- if (unlikely(current->audit_context)) {
- if (!entryexit)
- audit_syscall_entry(HOST_AUDIT_ARCH,
+ if (unlikely(current->audit_context) && entryexit)
+ audit_syscall_exit(AUDITSC_RESULT(UPT_SYSCALL_RET(regs)),
+ UPT_SYSCALL_RET(regs));
+
+ if (test_thread_flag(TIF_SYSCALL_TRACE))
+ tracehook_report_syscall(regs, entryexit);
+
+ if (test_thread_flag(TIF_SINGLESTEP) && entryexit) {
+ send_sigtrap(current, regs, 0); /* XXX */
+ tracehook_report_syscall_step(regs);
+ }
+
+ if (unlikely(current->audit_context) && !entryexit)
+ audit_syscall_entry(HOST_AUDIT_ARCH,
UPT_SYSCALL_NR(regs),
UPT_SYSCALL_ARG1(regs),
UPT_SYSCALL_ARG2(regs),
UPT_SYSCALL_ARG3(regs),
UPT_SYSCALL_ARG4(regs));
- else audit_syscall_exit(AUDITSC_RESULT(UPT_SYSCALL_RET(regs)),
- UPT_SYSCALL_RET(regs));
- }
-
- /* Fake a debug trap */
- if (is_singlestep)
- send_sigtrap(current, regs, 0);
-
- if (!test_thread_flag(TIF_SYSCALL_TRACE))
- return;
-
- if (!(current->ptrace & PT_PTRACED))
- return;
-
- /* the 0x80 provides a way for the tracing parent to distinguish
- between a syscall stop and SIGTRAP delivery */
- tracesysgood = (current->ptrace & PT_TRACESYSGOOD);
- ptrace_notify(SIGTRAP | (tracesysgood ? 0x80 : 0));
-
- if (entryexit) /* force do_signal() --> is_syscall() */
- set_thread_flag(TIF_SIGPENDING);
-
- /* this isn't the same as continuing with a signal, but it will do
- * for normal use. strace only continues with a signal if the
- * stopping signal is not SIGTRAP. -brl
- */
- if (current->exit_code) {
- send_sig(current->exit_code, current, 1);
- current->exit_code = 0;
- }
}
Index: linux-2.6.21-rc2/arch/um/kernel/signal.c
===================================================================
--- linux-2.6.21-rc2.orig/arch/um/kernel/signal.c 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/arch/um/kernel/signal.c 2007-03-05 13:24:57.000000000 -0500
@@ -14,6 +14,7 @@
#include "linux/tty.h"
#include "linux/binfmts.h"
#include "linux/ptrace.h"
+#include "linux/tracehook.h"
#include "asm/signal.h"
#include "asm/uaccess.h"
#include "asm/unistd.h"
@@ -93,6 +94,8 @@ static int handle_signal(struct pt_regs
sigaddset(&current->blocked, signr);
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
+
+ tracehook_report_handle_signal(signr, ka, oldset, regs);
}

return err;
@@ -148,7 +151,7 @@ static int kern_do_signal(struct pt_regs
* on the host. The tracing thread will check this flag and
* PTRACE_SYSCALL if necessary.
*/
- if(current->ptrace & PT_DTRACE)
+ if(test_thread_flag(TIF_SYSCALL_TRACE))
current->thread.singlestep_syscall =
is_syscall(PT_REGS_IP(&current->thread.regs));

Index: linux-2.6.21-rc2/arch/um/kernel/skas/syscall.c
===================================================================
--- linux-2.6.21-rc2.orig/arch/um/kernel/skas/syscall.c 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/arch/um/kernel/skas/syscall.c 2007-03-05 13:24:57.000000000 -0500
@@ -19,8 +19,6 @@ void handle_syscall(union uml_pt_regs *r
long result;
int syscall;

- syscall_trace(r, 0);
-
current->thread.nsyscalls++;
nsyscalls++;

@@ -38,6 +36,4 @@ void handle_syscall(union uml_pt_regs *r
else result = EXECUTE_SYSCALL(syscall, regs);

REGS_SET_SYSCALL_RETURN(r->skas.regs, result);
-
- syscall_trace(r, 1);
}
Index: linux-2.6.21-rc2/arch/um/sys-i386/signal.c
===================================================================
--- linux-2.6.21-rc2.orig/arch/um/sys-i386/signal.c 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/arch/um/sys-i386/signal.c 2007-03-05 13:24:57.000000000 -0500
@@ -267,8 +267,6 @@ int setup_signal_stack_sc(unsigned long
PT_REGS_EDX(regs) = (unsigned long) 0;
PT_REGS_ECX(regs) = (unsigned long) 0;

- if ((current->ptrace & PT_DTRACE) && (current->ptrace & PT_PTRACED))
- ptrace_notify(SIGTRAP);
return 0;

err:
@@ -324,8 +322,6 @@ int setup_signal_stack_si(unsigned long
PT_REGS_EDX(regs) = (unsigned long) &frame->info;
PT_REGS_ECX(regs) = (unsigned long) &frame->uc;

- if ((current->ptrace & PT_DTRACE) && (current->ptrace & PT_PTRACED))
- ptrace_notify(SIGTRAP);
return 0;

err:
Index: linux-2.6.21-rc2/include/asm-um/ptrace-generic.h
===================================================================
--- linux-2.6.21-rc2.orig/include/asm-um/ptrace-generic.h 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/include/asm-um/ptrace-generic.h 2007-03-05 13:24:57.000000000 -0500
@@ -44,9 +44,6 @@ extern int set_fpxregs(unsigned long buf

extern void show_regs(struct pt_regs *regs);

-extern void send_sigtrap(struct task_struct *tsk, union uml_pt_regs *regs,
- int error_code);
-
extern int arch_copy_tls(struct task_struct *new);
extern void clear_flushed_tls(struct task_struct *task);

Index: linux-2.6.21-rc2/include/asm-um/ptrace-i386.h
===================================================================
--- linux-2.6.21-rc2.orig/include/asm-um/ptrace-i386.h 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/include/asm-um/ptrace-i386.h 2007-03-05 13:24:57.000000000 -0500
@@ -6,6 +6,8 @@
#ifndef __UM_PTRACE_I386_H
#define __UM_PTRACE_I386_H

+#define ARCH_HAS_SINGLE_STEP (1)
+
#define HOST_AUDIT_ARCH AUDIT_ARCH_I386

#include "linux/compiler.h"
Index: linux-2.6.21-rc2/include/asm-um/ptrace-x86_64.h
===================================================================
--- linux-2.6.21-rc2.orig/include/asm-um/ptrace-x86_64.h 2007-03-05 13:24:49.000000000 -0500
+++ linux-2.6.21-rc2/include/asm-um/ptrace-x86_64.h 2007-03-05 13:24:57.000000000 -0500
@@ -14,6 +14,8 @@
#define __FRAME_OFFSETS /* Needed to get the R* macros */
#include "asm/ptrace-generic.h"

+#define ARCH_HAS_SINGLE_STEP (1)
+
#define HOST_AUDIT_ARCH AUDIT_ARCH_X86_64

/* Also defined in sysdep/ptrace.h, so may already be defined. */
Index: linux-2.6.21-rc2/include/asm-um/tracehook.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.21-rc2/include/asm-um/tracehook.h 2007-03-05 13:24:57.000000000 -0500
@@ -0,0 +1,64 @@
+/*
+ * Tracing hooks, i386 CPU support
+ *
+ * Copyright (C) 2006, 2007 Red Hat, Inc. All rights reserved.
+ *
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU General Public License v.2.
+ *
+ * Red Hat Author: Roland McGrath.
+ *
+ * Munged for UML - jdike@{addtoit,linux.intel}.com
+ */
+
+#ifndef _ASM_TRACEHOOK_H
+#define _ASM_TRACEHOOK_H 1
+
+#include <linux/sched.h>
+#include <asm/ptrace.h>
+#include <asm/thread_info.h>
+
+/*
+ * See linux/tracehook.h for the descriptions of what these need to do.
+ */
+
+static inline void tracehook_enable_single_step(struct task_struct *tsk)
+{
+ set_tsk_thread_flag(tsk, TIF_SINGLESTEP);
+}
+
+static inline void tracehook_disable_single_step(struct task_struct *tsk)
+{
+ clear_tsk_thread_flag(tsk, TIF_SINGLESTEP);
+}
+
+static inline int tracehook_single_step_enabled(struct task_struct *tsk)
+{
+ return test_tsk_thread_flag(tsk, TIF_SINGLESTEP);
+}
+
+static inline void tracehook_enable_syscall_trace(struct task_struct *tsk)
+{
+ set_tsk_thread_flag(tsk, TIF_SYSCALL_TRACE);
+}
+
+static inline void tracehook_disable_syscall_trace(struct task_struct *tsk)
+{
+ clear_tsk_thread_flag(tsk, TIF_SYSCALL_TRACE);
+}
+
+static inline void tracehook_abort_syscall(struct pt_regs *regs)
+{
+ PT_REGS_SYSCALL_NR(regs) = -1;
+}
+
+extern const struct utrace_regset_view utrace_um_native;
+static inline const struct utrace_regset_view *
+utrace_native_view(struct task_struct *tsk)
+{
+ return &utrace_um_native;
+}
+
+
+#endif
Index: linux-2.6.21-rc2/include/asm-um/thread_info.h
===================================================================
--- linux-2.6.21-rc2.orig/include/asm-um/thread_info.h 2007-02-27 23:59:12.000000000 -0500
+++ linux-2.6.21-rc2/include/asm-um/thread_info.h 2007-03-05 15:11:39.000000000 -0500
@@ -69,6 +69,8 @@ static inline struct thread_info *curren
#define TIF_MEMDIE 5
#define TIF_SYSCALL_AUDIT 6
#define TIF_RESTORE_SIGMASK 7
+#define TIF_SINGLESTEP 8 /* restore singlestep on return to user
+ * mode */

#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)
#define _TIF_SIGPENDING (1 << TIF_SIGPENDING)
@@ -77,5 +79,6 @@ static inline struct thread_info *curren
#define _TIF_MEMDIE (1 << TIF_MEMDIE)
#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT)
#define _TIF_RESTORE_SIGMASK (1 << TIF_RESTORE_SIGMASK)
+#define _TIF_SINGLESTEP (1 << TIF_SINGLESTEP)

#endif
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/