Commit 726ca7e6 authored by Roland McGrath's avatar Roland McGrath Committed by James Toy

This adds the utrace facility, a new modular interface in the kernel for

implementing user thread tracing and debugging.  This fits on top of the
tracehook_* layer, so the new code is well-isolated.

The new interface is in <linux/utrace.h> and the DocBook utrace book
describes it.  It allows for multiple separate tracing engines to work in
parallel without interfering with each other.  Higher-level tracing
facilities can be implemented as loadable kernel modules using this layer.

The new facility is made optional under CONFIG_UTRACE.  When this is not
enabled, no new code is added.  It can only be enabled on machines that
have all the prerequisites and select CONFIG_HAVE_ARCH_TRACEHOOK.

In this initial version, utrace and ptrace do not play together at all. 
If ptrace is attached to a thread, the attach calls in the utrace kernel
API return -EBUSY.  If utrace is attached to a thread, the PTRACE_ATTACH
or PTRACE_TRACEME request will return EBUSY to userland.  The old ptrace
code is otherwise unchanged and nothing using ptrace should be affected by
this patch as long as utrace is not used at the same time.  In the future
we can clean up the ptrace implementation and rework it to use the utrace
API.

[oleg@redhat.com: kill exclude_xtrace logic]
Signed-off-by: default avatarRoland McGrath <roland@redhat.com>
Signed-off-by: default avatarOleg Nesterov <oleg@redhat.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent de4aeef8
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \
kernel-hacking.xml kernel-locking.xml deviceiobook.xml \ kernel-hacking.xml kernel-locking.xml deviceiobook.xml \
procfs-guide.xml writing_usb_driver.xml networking.xml \ procfs-guide.xml writing_usb_driver.xml networking.xml \
kernel-api.xml filesystems.xml lsm.xml usb.xml kgdb.xml \ kernel-api.xml filesystems.xml lsm.xml usb.xml kgdb.xml utrace.xml \
gadget.xml libata.xml mtdnand.xml librs.xml rapidio.xml \ gadget.xml libata.xml mtdnand.xml librs.xml rapidio.xml \
genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \ genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \
mac80211.xml debugobjects.xml sh.xml regulator.xml \ mac80211.xml debugobjects.xml sh.xml regulator.xml \
......
This diff is collapsed.
...@@ -81,6 +81,7 @@ ...@@ -81,6 +81,7 @@
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/pid_namespace.h> #include <linux/pid_namespace.h>
#include <linux/ptrace.h> #include <linux/ptrace.h>
#include <linux/utrace.h>
#include <linux/tracehook.h> #include <linux/tracehook.h>
#include <linux/swapops.h> #include <linux/swapops.h>
...@@ -189,6 +190,8 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns, ...@@ -189,6 +190,8 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
cred->uid, cred->euid, cred->suid, cred->fsuid, cred->uid, cred->euid, cred->suid, cred->fsuid,
cred->gid, cred->egid, cred->sgid, cred->fsgid); cred->gid, cred->egid, cred->sgid, cred->fsgid);
task_utrace_proc_status(m, p);
task_lock(p); task_lock(p);
if (p->files) if (p->files)
fdt = files_fdtable(p->files); fdt = files_fdtable(p->files);
......
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
#include <linux/file.h> #include <linux/file.h>
#include <linux/fdtable.h> #include <linux/fdtable.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/utrace.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/namei.h> #include <linux/namei.h>
#include <linux/mnt_namespace.h> #include <linux/mnt_namespace.h>
......
...@@ -186,6 +186,7 @@ extern struct cred init_cred; ...@@ -186,6 +186,7 @@ extern struct cred init_cred;
[PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \ [PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \
}, \ }, \
.dirties = INIT_PROP_LOCAL_SINGLE(dirties), \ .dirties = INIT_PROP_LOCAL_SINGLE(dirties), \
INIT_UTRACE(tsk) \
INIT_IDS \ INIT_IDS \
INIT_PERF_COUNTERS(tsk) \ INIT_PERF_COUNTERS(tsk) \
INIT_TRACE_IRQFLAGS \ INIT_TRACE_IRQFLAGS \
......
...@@ -61,6 +61,7 @@ struct sched_param { ...@@ -61,6 +61,7 @@ struct sched_param {
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/nodemask.h> #include <linux/nodemask.h>
#include <linux/mm_types.h> #include <linux/mm_types.h>
#include <linux/utrace_struct.h>
#include <asm/system.h> #include <asm/system.h>
#include <asm/page.h> #include <asm/page.h>
...@@ -1397,6 +1398,11 @@ struct task_struct { ...@@ -1397,6 +1398,11 @@ struct task_struct {
#endif #endif
seccomp_t seccomp; seccomp_t seccomp;
#ifdef CONFIG_UTRACE
struct utrace utrace;
unsigned long utrace_flags;
#endif
/* Thread group tracking */ /* Thread group tracking */
u32 parent_exec_id; u32 parent_exec_id;
u32 self_exec_id; u32 self_exec_id;
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/ptrace.h> #include <linux/ptrace.h>
#include <linux/security.h> #include <linux/security.h>
#include <linux/utrace.h>
struct linux_binprm; struct linux_binprm;
/** /**
...@@ -63,6 +64,8 @@ struct linux_binprm; ...@@ -63,6 +64,8 @@ struct linux_binprm;
*/ */
static inline int tracehook_expect_breakpoints(struct task_struct *task) static inline int tracehook_expect_breakpoints(struct task_struct *task)
{ {
if (unlikely(task_utrace_flags(task) & UTRACE_EVENT(SIGNAL_CORE)))
return 1;
return (task_ptrace(task) & PT_PTRACED) != 0; return (task_ptrace(task) & PT_PTRACED) != 0;
} }
...@@ -111,6 +114,9 @@ static inline void ptrace_report_syscall(struct pt_regs *regs) ...@@ -111,6 +114,9 @@ static inline void ptrace_report_syscall(struct pt_regs *regs)
static inline __must_check int tracehook_report_syscall_entry( static inline __must_check int tracehook_report_syscall_entry(
struct pt_regs *regs) struct pt_regs *regs)
{ {
if ((task_utrace_flags(current) & UTRACE_EVENT(SYSCALL_ENTRY)) &&
utrace_report_syscall_entry(regs))
return 1;
ptrace_report_syscall(regs); ptrace_report_syscall(regs);
return 0; return 0;
} }
...@@ -134,6 +140,8 @@ static inline __must_check int tracehook_report_syscall_entry( ...@@ -134,6 +140,8 @@ static inline __must_check int tracehook_report_syscall_entry(
*/ */
static inline void tracehook_report_syscall_exit(struct pt_regs *regs, int step) static inline void tracehook_report_syscall_exit(struct pt_regs *regs, int step)
{ {
if (task_utrace_flags(current) & UTRACE_EVENT(SYSCALL_EXIT))
utrace_report_syscall_exit(regs);
ptrace_report_syscall(regs); ptrace_report_syscall(regs);
} }
...@@ -194,6 +202,8 @@ static inline void tracehook_report_exec(struct linux_binfmt *fmt, ...@@ -194,6 +202,8 @@ static inline void tracehook_report_exec(struct linux_binfmt *fmt,
struct linux_binprm *bprm, struct linux_binprm *bprm,
struct pt_regs *regs) struct pt_regs *regs)
{ {
if (unlikely(task_utrace_flags(current) & UTRACE_EVENT(EXEC)))
utrace_report_exec(fmt, bprm, regs);
if (!ptrace_event(PT_TRACE_EXEC, PTRACE_EVENT_EXEC, 0) && if (!ptrace_event(PT_TRACE_EXEC, PTRACE_EVENT_EXEC, 0) &&
unlikely(task_ptrace(current) & PT_PTRACED)) unlikely(task_ptrace(current) & PT_PTRACED))
send_sig(SIGTRAP, current, 0); send_sig(SIGTRAP, current, 0);
...@@ -211,6 +221,8 @@ static inline void tracehook_report_exec(struct linux_binfmt *fmt, ...@@ -211,6 +221,8 @@ static inline void tracehook_report_exec(struct linux_binfmt *fmt,
*/ */
static inline void tracehook_report_exit(long *exit_code) static inline void tracehook_report_exit(long *exit_code)
{ {
if (unlikely(task_utrace_flags(current) & UTRACE_EVENT(EXIT)))
utrace_report_exit(exit_code);
ptrace_event(PT_TRACE_EXIT, PTRACE_EVENT_EXIT, *exit_code); ptrace_event(PT_TRACE_EXIT, PTRACE_EVENT_EXIT, *exit_code);
} }
...@@ -254,6 +266,7 @@ static inline int tracehook_prepare_clone(unsigned clone_flags) ...@@ -254,6 +266,7 @@ static inline int tracehook_prepare_clone(unsigned clone_flags)
static inline void tracehook_finish_clone(struct task_struct *child, static inline void tracehook_finish_clone(struct task_struct *child,
unsigned long clone_flags, int trace) unsigned long clone_flags, int trace)
{ {
utrace_init_task(child);
ptrace_init_task(child, (clone_flags & CLONE_PTRACE) || trace); ptrace_init_task(child, (clone_flags & CLONE_PTRACE) || trace);
} }
...@@ -278,6 +291,8 @@ static inline void tracehook_report_clone(struct pt_regs *regs, ...@@ -278,6 +291,8 @@ static inline void tracehook_report_clone(struct pt_regs *regs,
unsigned long clone_flags, unsigned long clone_flags,
pid_t pid, struct task_struct *child) pid_t pid, struct task_struct *child)
{ {
if (unlikely(task_utrace_flags(current) & UTRACE_EVENT(CLONE)))
utrace_report_clone(clone_flags, child);
if (unlikely(task_ptrace(child))) { if (unlikely(task_ptrace(child))) {
/* /*
* It doesn't matter who attached/attaching to this * It doesn't matter who attached/attaching to this
...@@ -310,6 +325,9 @@ static inline void tracehook_report_clone_complete(int trace, ...@@ -310,6 +325,9 @@ static inline void tracehook_report_clone_complete(int trace,
pid_t pid, pid_t pid,
struct task_struct *child) struct task_struct *child)
{ {
if (unlikely(task_utrace_flags(current) & UTRACE_EVENT(CLONE)) &&
(clone_flags & CLONE_VFORK))
utrace_finish_vfork(current);
if (unlikely(trace)) if (unlikely(trace))
ptrace_event(0, trace, pid); ptrace_event(0, trace, pid);
} }
...@@ -344,6 +362,7 @@ static inline void tracehook_report_vfork_done(struct task_struct *child, ...@@ -344,6 +362,7 @@ static inline void tracehook_report_vfork_done(struct task_struct *child,
*/ */
static inline void tracehook_prepare_release_task(struct task_struct *task) static inline void tracehook_prepare_release_task(struct task_struct *task)
{ {
utrace_release_task(task);
} }
/** /**
...@@ -358,6 +377,7 @@ static inline void tracehook_prepare_release_task(struct task_struct *task) ...@@ -358,6 +377,7 @@ static inline void tracehook_prepare_release_task(struct task_struct *task)
static inline void tracehook_finish_release_task(struct task_struct *task) static inline void tracehook_finish_release_task(struct task_struct *task)
{ {
ptrace_release_task(task); ptrace_release_task(task);
BUG_ON(task->exit_state != EXIT_DEAD);
} }
/** /**
...@@ -379,6 +399,8 @@ static inline void tracehook_signal_handler(int sig, siginfo_t *info, ...@@ -379,6 +399,8 @@ static inline void tracehook_signal_handler(int sig, siginfo_t *info,
const struct k_sigaction *ka, const struct k_sigaction *ka,
struct pt_regs *regs, int stepping) struct pt_regs *regs, int stepping)
{ {
if (task_utrace_flags(current))
utrace_signal_handler(current, stepping);
if (stepping) if (stepping)
ptrace_notify(SIGTRAP); ptrace_notify(SIGTRAP);
} }
...@@ -396,6 +418,8 @@ static inline void tracehook_signal_handler(int sig, siginfo_t *info, ...@@ -396,6 +418,8 @@ static inline void tracehook_signal_handler(int sig, siginfo_t *info,
static inline int tracehook_consider_ignored_signal(struct task_struct *task, static inline int tracehook_consider_ignored_signal(struct task_struct *task,
int sig) int sig)
{ {
if (unlikely(task_utrace_flags(task) & UTRACE_EVENT(SIGNAL_IGN)))
return 1;
return (task_ptrace(task) & PT_PTRACED) != 0; return (task_ptrace(task) & PT_PTRACED) != 0;
} }
...@@ -415,6 +439,9 @@ static inline int tracehook_consider_ignored_signal(struct task_struct *task, ...@@ -415,6 +439,9 @@ static inline int tracehook_consider_ignored_signal(struct task_struct *task,
static inline int tracehook_consider_fatal_signal(struct task_struct *task, static inline int tracehook_consider_fatal_signal(struct task_struct *task,
int sig) int sig)
{ {
if (unlikely(task_utrace_flags(task) & (UTRACE_EVENT(SIGNAL_TERM) |
UTRACE_EVENT(SIGNAL_CORE))))
return 1;
return (task_ptrace(task) & PT_PTRACED) != 0; return (task_ptrace(task) & PT_PTRACED) != 0;
} }
...@@ -429,6 +456,8 @@ static inline int tracehook_consider_fatal_signal(struct task_struct *task, ...@@ -429,6 +456,8 @@ static inline int tracehook_consider_fatal_signal(struct task_struct *task,
*/ */
static inline int tracehook_force_sigpending(void) static inline int tracehook_force_sigpending(void)
{ {
if (unlikely(task_utrace_flags(current)))
return utrace_interrupt_pending();
return 0; return 0;
} }
...@@ -458,6 +487,8 @@ static inline int tracehook_get_signal(struct task_struct *task, ...@@ -458,6 +487,8 @@ static inline int tracehook_get_signal(struct task_struct *task,
siginfo_t *info, siginfo_t *info,
struct k_sigaction *return_ka) struct k_sigaction *return_ka)
{ {
if (unlikely(task_utrace_flags(task)))
return utrace_get_signal(task, regs, info, return_ka);
return 0; return 0;
} }
...@@ -485,6 +516,8 @@ static inline int tracehook_get_signal(struct task_struct *task, ...@@ -485,6 +516,8 @@ static inline int tracehook_get_signal(struct task_struct *task,
*/ */
static inline int tracehook_notify_jctl(int notify, int why) static inline int tracehook_notify_jctl(int notify, int why)
{ {
if (task_utrace_flags(current) & UTRACE_EVENT(JCTL))
utrace_report_jctl(notify, why);
return notify ?: (current->ptrace & PT_PTRACED) ? why : 0; return notify ?: (current->ptrace & PT_PTRACED) ? why : 0;
} }
...@@ -517,6 +550,8 @@ static inline void tracehook_finish_jctl(void) ...@@ -517,6 +550,8 @@ static inline void tracehook_finish_jctl(void)
static inline int tracehook_notify_death(struct task_struct *task, static inline int tracehook_notify_death(struct task_struct *task,
void **death_cookie, int group_dead) void **death_cookie, int group_dead)
{ {
*death_cookie = task_utrace_struct(task);
if (task_detached(task)) if (task_detached(task))
return task->ptrace ? SIGCHLD : DEATH_REAP; return task->ptrace ? SIGCHLD : DEATH_REAP;
...@@ -553,6 +588,9 @@ static inline void tracehook_report_death(struct task_struct *task, ...@@ -553,6 +588,9 @@ static inline void tracehook_report_death(struct task_struct *task,
int signal, void *death_cookie, int signal, void *death_cookie,
int group_dead) int group_dead)
{ {
smp_mb();
if (task_utrace_flags(task) & _UTRACE_DEATH_EVENTS)
utrace_report_death(task, death_cookie, group_dead, signal);
} }
#ifdef TIF_NOTIFY_RESUME #ifdef TIF_NOTIFY_RESUME
...@@ -582,10 +620,20 @@ static inline void set_notify_resume(struct task_struct *task) ...@@ -582,10 +620,20 @@ static inline void set_notify_resume(struct task_struct *task)
* asynchronously, this will be called again before we return to * asynchronously, this will be called again before we return to
* user mode. * user mode.
* *
* Called without locks. * Called without locks. However, on some machines this may be
* called with interrupts disabled.
*/ */
static inline void tracehook_notify_resume(struct pt_regs *regs) static inline void tracehook_notify_resume(struct pt_regs *regs)
{ {
struct task_struct *task = current;
/*
* This pairs with the barrier implicit in set_notify_resume().
* It ensures that we read the nonzero utrace_flags set before
* set_notify_resume() was called by utrace setup.
*/
smp_rmb();
if (task_utrace_flags(task))
utrace_resume(task, regs);
} }
#endif /* TIF_NOTIFY_RESUME */ #endif /* TIF_NOTIFY_RESUME */
......
This diff is collapsed.
/*
* 'struct utrace' data structure for kernel/utrace.c private use.
*
* Copyright (C) 2006-2009 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.
*/
#ifndef _LINUX_UTRACE_STRUCT_H
#define _LINUX_UTRACE_STRUCT_H 1
#ifdef CONFIG_UTRACE
#include <linux/list.h>
#include <linux/spinlock.h>
/*
* Per-thread structure private to utrace implementation. This properly
* belongs in kernel/utrace.c and its use is entirely private to the code
* there. It is only defined in a header file so that it can be embedded
* in the struct task_struct layout. It is here rather than in utrace.h
* to avoid header nesting order issues getting too complex.
*
*/
struct utrace {
struct task_struct *cloning;
struct list_head attached, attaching;
spinlock_t lock;
struct utrace_engine *reporting;
unsigned int stopped:1;
unsigned int report:1;
unsigned int interrupt:1;
unsigned int signal_handler:1;
unsigned int vfork_stop:1; /* need utrace_stop() before vfork wait */
unsigned int death:1; /* in utrace_report_death() now */
unsigned int reap:1; /* release_task() has run */
};
# define INIT_UTRACE(tsk) \
.utrace_flags = 0, \
.utrace = { \
.lock = __SPIN_LOCK_UNLOCKED(tsk.utrace.lock), \
.attached = LIST_HEAD_INIT(tsk.utrace.attached), \
.attaching = LIST_HEAD_INIT(tsk.utrace.attaching), \
},
#else
# define INIT_UTRACE(tsk) /* Nothing. */
#endif /* CONFIG_UTRACE */
#endif /* linux/utrace_struct.h */
...@@ -1208,6 +1208,15 @@ config STOP_MACHINE ...@@ -1208,6 +1208,15 @@ config STOP_MACHINE
help help
Need stop_machine() primitive. Need stop_machine() primitive.
menuconfig UTRACE
bool "Infrastructure for tracing and debugging user processes"
depends on EXPERIMENTAL
depends on HAVE_ARCH_TRACEHOOK
help
Enable the utrace process tracing interface. This is an internal
kernel interface exported to kernel modules, to track events in
user threads, extract and change user thread state.
source "block/Kconfig" source "block/Kconfig"
config PREEMPT_NOTIFIERS config PREEMPT_NOTIFIERS
......
...@@ -73,6 +73,7 @@ obj-$(CONFIG_AUDITSYSCALL) += auditsc.o ...@@ -73,6 +73,7 @@ obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
obj-$(CONFIG_AUDIT_WATCH) += audit_watch.o obj-$(CONFIG_AUDIT_WATCH) += audit_watch.o
obj-$(CONFIG_AUDIT_TREE) += audit_tree.o obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
obj-$(CONFIG_GCOV_KERNEL) += gcov/ obj-$(CONFIG_GCOV_KERNEL) += gcov/
obj-$(CONFIG_UTRACE) += utrace.o
obj-$(CONFIG_KPROBES) += kprobes.o obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_KGDB) += kgdb.o obj-$(CONFIG_KGDB) += kgdb.o
obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment