855 lines
24 KiB
Diff
855 lines
24 KiB
Diff
From fcd69dd4537320a25a294c82362cc7abe4fe773c Mon Sep 17 00:00:00 2001
|
|
From: Ye Wen <ywen@google.com>
|
|
Date: Fri, 14 Jul 2006 14:51:45 +0700
|
|
Subject: [PATCH 129/134] [ARM] goldfish: qemutrace: Kernel instrumentation for tracing the events.
|
|
|
|
Like fork, context switch, execve and exit.
|
|
This code is to complement Jack's tracing facility.
|
|
|
|
To turn tracing on:
|
|
echo 1 > /sysfs/qemu_trace/state
|
|
To turn tracing off: echo 0 > /sysfs/qemu_trace/state
|
|
I also added java methods to Debug.java to turn tracing on and off.
|
|
The kernel driver also supports adding dynamic symbols to the trace.
|
|
To add a symbol 'foo' with hex address 'abcd1234' to the trace:
|
|
echo 'abcd1234 foo' > /sysfs/qemu_trace/symbol
|
|
|
|
Signed-off-by: Mike Chan <mike@android.com>
|
|
|
|
[ARM] goldfish: qemutrace: Improved support for tracing thread and process names.
|
|
|
|
Added a new pseudo file /sys/qemu_trace/process_name to allow user
|
|
programs to add a trace record for a process name change. Removed
|
|
the tracing of thread and process names from the exit() system call
|
|
because that was not sufficiently general. Added tracing of thread
|
|
names in set_task_comm() and daemonize(). Added tracing of the
|
|
thread group id to fork() and clone().
|
|
|
|
Signed-off-by: Jack Veenstra <veenstra@google.com>
|
|
Signed-off-by: Mike Chan <mike@android.com>
|
|
---
|
|
arch/arm/kernel/entry-armv.S | 5 +
|
|
drivers/misc/Kconfig | 5 +
|
|
drivers/misc/Makefile | 1 +
|
|
drivers/misc/qemutrace/Makefile | 2 +
|
|
drivers/misc/qemutrace/qemu_trace.c | 386 +++++++++++++++++++++++++++++
|
|
drivers/misc/qemutrace/qemu_trace.h | 22 ++
|
|
drivers/misc/qemutrace/qemu_trace_sysfs.c | 182 ++++++++++++++
|
|
fs/exec.c | 14 +
|
|
kernel/exit.c | 14 +
|
|
kernel/fork.c | 8 +
|
|
kernel/sched.c | 9 +
|
|
mm/mmap.c | 13 +
|
|
12 files changed, 661 insertions(+), 0 deletions(-)
|
|
create mode 100644 drivers/misc/qemutrace/Makefile
|
|
create mode 100644 drivers/misc/qemutrace/qemu_trace.c
|
|
create mode 100644 drivers/misc/qemutrace/qemu_trace.h
|
|
create mode 100644 drivers/misc/qemutrace/qemu_trace_sysfs.c
|
|
|
|
--- a/arch/arm/kernel/entry-armv.S
|
|
+++ b/arch/arm/kernel/entry-armv.S
|
|
@@ -733,6 +733,11 @@ ENTRY(__switch_to)
|
|
ldr r0, =thread_notify_head
|
|
mov r1, #THREAD_NOTIFY_SWITCH
|
|
bl atomic_notifier_call_chain
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+/*
|
|
+ mcr p15, 0, r0, c15, c0, 0 @ signal context switch
|
|
+*/
|
|
+#endif
|
|
mov r0, r5
|
|
ldmia r4, {r4 - sl, fp, sp, pc} @ Load all regs saved previously
|
|
UNWIND(.fnend )
|
|
--- a/drivers/misc/Kconfig
|
|
+++ b/drivers/misc/Kconfig
|
|
@@ -233,6 +233,11 @@ config ISL29003
|
|
This driver can also be built as a module. If so, the module
|
|
will be called isl29003.
|
|
|
|
+config QEMU_TRACE
|
|
+ tristate "Virtual Device for QEMU tracing"
|
|
+ ---help---
|
|
+ This is a virtual device for QEMU tracing.
|
|
+
|
|
source "drivers/misc/c2port/Kconfig"
|
|
source "drivers/misc/eeprom/Kconfig"
|
|
|
|
--- a/drivers/misc/Makefile
|
|
+++ b/drivers/misc/Makefile
|
|
@@ -20,4 +20,5 @@ obj-$(CONFIG_SGI_GRU) += sgi-gru/
|
|
obj-$(CONFIG_HP_ILO) += hpilo.o
|
|
obj-$(CONFIG_ISL29003) += isl29003.o
|
|
obj-$(CONFIG_C2PORT) += c2port/
|
|
+obj-$(CONFIG_QEMU_TRACE) += qemutrace/
|
|
obj-y += eeprom/
|
|
--- /dev/null
|
|
+++ b/drivers/misc/qemutrace/Makefile
|
|
@@ -0,0 +1,2 @@
|
|
+obj-$(CONFIG_QEMU_TRACE) := qemu_trace.o
|
|
+obj-$(CONFIG_QEMU_TRACE) += qemu_trace_sysfs.o
|
|
--- /dev/null
|
|
+++ b/drivers/misc/qemutrace/qemu_trace.c
|
|
@@ -0,0 +1,386 @@
|
|
+/* drivers/misc/qemutrace/qemu_trace.c
|
|
+ *
|
|
+ * Copyright (C) 2007-2008 Google, Inc.
|
|
+ *
|
|
+ * This software is licensed under the terms of the GNU General Public
|
|
+ * License version 2, as published by the Free Software Foundation, and
|
|
+ * may be copied, distributed, and modified under those terms.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <linux/miscdevice.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/proc_fs.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/sched.h>
|
|
+#include <asm/uaccess.h>
|
|
+#include <asm/io.h>
|
|
+#include <asm/sizes.h>
|
|
+#include "qemu_trace.h"
|
|
+
|
|
+/* trace device registers */
|
|
+#define TRACE_DEV_REG_SWITCH 0
|
|
+#define TRACE_DEV_REG_FORK 1
|
|
+#define TRACE_DEV_REG_EXECVE_PID 2
|
|
+#define TRACE_DEV_REG_EXECVE_VMSTART 3
|
|
+#define TRACE_DEV_REG_EXECVE_VMEND 4
|
|
+#define TRACE_DEV_REG_EXECVE_OFFSET 5
|
|
+#define TRACE_DEV_REG_EXECVE_EXEPATH 6
|
|
+#define TRACE_DEV_REG_EXIT 7
|
|
+#define TRACE_DEV_REG_CMDLINE 8
|
|
+#define TRACE_DEV_REG_CMDLINE_LEN 9
|
|
+#define TRACE_DEV_REG_MMAP_EXEPATH 10
|
|
+#define TRACE_DEV_REG_INIT_PID 11
|
|
+#define TRACE_DEV_REG_INIT_NAME 12
|
|
+#define TRACE_DEV_REG_CLONE 13
|
|
+#define TRACE_DEV_REG_UNMAP_START 14
|
|
+#define TRACE_DEV_REG_UNMAP_END 15
|
|
+#define TRACE_DEV_REG_NAME 16
|
|
+#define TRACE_DEV_REG_TGID 17
|
|
+#define TRACE_DEV_REG_DYN_SYM 50
|
|
+#define TRACE_DEV_REG_DYN_SYM_ADDR 51
|
|
+#define TRACE_DEV_REG_REMOVE_ADDR 52
|
|
+#define TRACE_DEV_REG_ENABLE 100
|
|
+
|
|
+static unsigned char __iomem *qt_base;
|
|
+static int init_called;
|
|
+
|
|
+/* PIDs that start before our device registered */
|
|
+#define MAX_INIT_PIDS 2048
|
|
+static int tb_next = 0;
|
|
+static int init_pids[MAX_INIT_PIDS];
|
|
+static DEFINE_SPINLOCK(qemu_trace_lock);
|
|
+
|
|
+void qemu_trace_start(void)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(1, qt_base + (TRACE_DEV_REG_ENABLE << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+
|
|
+void qemu_trace_stop(void)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(0, qt_base + (TRACE_DEV_REG_ENABLE << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+
|
|
+int qemu_trace_get_tracing(void)
|
|
+{
|
|
+ int val = 0;
|
|
+ if (qt_base != NULL)
|
|
+ val = readl(qt_base + (TRACE_DEV_REG_ENABLE << 2));
|
|
+ return val;
|
|
+}
|
|
+
|
|
+void qemu_trace_add_mapping(unsigned int addr, const char *symbol)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ /* Write the address first, then the symbol name. */
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(addr, qt_base + (TRACE_DEV_REG_DYN_SYM_ADDR << 2));
|
|
+ writel(symbol, qt_base + (TRACE_DEV_REG_DYN_SYM << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+
|
|
+void qemu_trace_remove_mapping(unsigned int addr)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(addr, qt_base + (TRACE_DEV_REG_REMOVE_ADDR << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+
|
|
+/* trace the context switch */
|
|
+void qemu_trace_cs(struct task_struct *next)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(task_pid_nr(next), qt_base);
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_cs);
|
|
+
|
|
+/* trace the execve */
|
|
+void qemu_trace_execve(int argc, char __user * __user *argv)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ char page[PAGE_SIZE];
|
|
+ char *ptr = page;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ while (argc-- > 0) {
|
|
+ char __user *str;
|
|
+ int len;
|
|
+ if (get_user(str, argv ++))
|
|
+ return;
|
|
+ len = strnlen_user(str, PAGE_SIZE);
|
|
+ if (len == 0)
|
|
+ return;
|
|
+ if (copy_from_user(ptr, str, len))
|
|
+ return;
|
|
+ ptr += len;
|
|
+ }
|
|
+
|
|
+ if (ptr > page) {
|
|
+ int len = ptr - page;
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(len, qt_base + (TRACE_DEV_REG_CMDLINE_LEN << 2));
|
|
+ writel(page, qt_base + (TRACE_DEV_REG_CMDLINE << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_execve);
|
|
+
|
|
+/* trace the mmap */
|
|
+void qemu_trace_mmap(struct vm_area_struct *vma)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ char page[PAGE_SIZE];
|
|
+ char *p;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ if (vma->vm_file == NULL)
|
|
+ return;
|
|
+
|
|
+ p = d_path(&vma->vm_file->f_path, page, PAGE_SIZE);
|
|
+ if (IS_ERR(p))
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(vma->vm_start, qt_base + (TRACE_DEV_REG_EXECVE_VMSTART << 2));
|
|
+ writel(vma->vm_end, qt_base + (TRACE_DEV_REG_EXECVE_VMEND << 2));
|
|
+ writel(vma->vm_pgoff * PAGE_SIZE, qt_base + (TRACE_DEV_REG_EXECVE_OFFSET << 2));
|
|
+ writel(p, qt_base + (TRACE_DEV_REG_MMAP_EXEPATH << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_mmap);
|
|
+
|
|
+/* trace the munmap */
|
|
+void qemu_trace_munmap(unsigned long start, unsigned long end)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(start, qt_base + (TRACE_DEV_REG_UNMAP_START << 2));
|
|
+ writel(end, qt_base + (TRACE_DEV_REG_UNMAP_END << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_munmap);
|
|
+
|
|
+/* trace the fork */
|
|
+void qemu_trace_fork(struct task_struct *forked, unsigned long clone_flags)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ if (qt_base == NULL) {
|
|
+ if (tb_next >= MAX_INIT_PIDS) {
|
|
+ if (!init_called)
|
|
+ printk(KERN_ERR
|
|
+ "QEMU Trace: too many PIDs before "
|
|
+ "device registered ignoring %d\n",
|
|
+ forked->pid);
|
|
+ } else {
|
|
+ init_pids[tb_next] = task_pid_nr(forked);
|
|
+ tb_next++;
|
|
+ }
|
|
+ } else {
|
|
+ writel(task_tgid_nr(forked), qt_base + (TRACE_DEV_REG_TGID << 2));
|
|
+ if (clone_flags & CLONE_VM)
|
|
+ writel(task_pid_nr(forked), qt_base + (TRACE_DEV_REG_CLONE << 2));
|
|
+ else
|
|
+ writel(task_pid_nr(forked), qt_base + (TRACE_DEV_REG_FORK << 2));
|
|
+ }
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_fork);
|
|
+
|
|
+/* trace the exit */
|
|
+void qemu_trace_exit(int code)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(code, qt_base + (TRACE_DEV_REG_EXIT << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_exit);
|
|
+
|
|
+/* trace the thread name */
|
|
+void qemu_trace_thread_name(const char *name)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(name, qt_base + (TRACE_DEV_REG_NAME << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_thread_name);
|
|
+
|
|
+/* trace the process name */
|
|
+void qemu_trace_process_name(const char *name)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+
|
|
+ if (qt_base == NULL)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(name, qt_base + (TRACE_DEV_REG_NAME << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+}
|
|
+EXPORT_SYMBOL(qemu_trace_process_name);
|
|
+
|
|
+static void qemu_trace_pid_exec(struct task_struct *tsk)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ char page[PAGE_SIZE];
|
|
+ struct mm_struct *mm = get_task_mm(tsk);
|
|
+ if (mm == NULL)
|
|
+ return;
|
|
+ down_read(&mm->mmap_sem);
|
|
+ {
|
|
+ struct vm_area_struct *vma = mm->mmap;
|
|
+ while (vma) {
|
|
+ if ((vma->vm_flags & VM_EXEC) && vma->vm_file) {
|
|
+ char *p;
|
|
+ p = d_path(&vma->vm_file->f_path, page, PAGE_SIZE);
|
|
+ if (!IS_ERR(p)) {
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(vma->vm_start, qt_base + (TRACE_DEV_REG_EXECVE_VMSTART << 2));
|
|
+ writel(vma->vm_end, qt_base + (TRACE_DEV_REG_EXECVE_VMEND << 2));
|
|
+ writel(vma->vm_pgoff * PAGE_SIZE, qt_base + (TRACE_DEV_REG_EXECVE_OFFSET << 2));
|
|
+ writel(p, qt_base + (TRACE_DEV_REG_EXECVE_EXEPATH << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+ }
|
|
+ }
|
|
+ vma = vma->vm_next;
|
|
+ }
|
|
+ }
|
|
+ up_read(&mm->mmap_sem);
|
|
+ mmput(mm);
|
|
+}
|
|
+
|
|
+static void qemu_trace_dump_init_threads(void)
|
|
+{
|
|
+ unsigned long irq_flags;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < tb_next; i++) {
|
|
+ struct task_struct *tsk;
|
|
+ struct pid *pid = find_get_pid(init_pids[i]);
|
|
+ if (pid == NULL)
|
|
+ continue;
|
|
+
|
|
+ if ((tsk = get_pid_task(pid, PIDTYPE_PID)) != NULL) {
|
|
+ /* first give the pid and name */
|
|
+ task_lock(tsk);
|
|
+ spin_lock_irqsave(&qemu_trace_lock, irq_flags);
|
|
+ writel(task_tgid_nr(tsk), qt_base + (TRACE_DEV_REG_TGID << 2));
|
|
+ writel(task_pid_nr(tsk), qt_base + (TRACE_DEV_REG_INIT_PID << 2));
|
|
+ writel(tsk->comm, qt_base + (TRACE_DEV_REG_INIT_NAME << 2));
|
|
+ spin_unlock_irqrestore(&qemu_trace_lock, irq_flags);
|
|
+ task_unlock(tsk);
|
|
+ /* check if the task has execs */
|
|
+ qemu_trace_pid_exec(tsk);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static int qemu_trace_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct resource *r;
|
|
+
|
|
+ /* not thread safe, but this should not happen */
|
|
+ if (qt_base != NULL) {
|
|
+ printk(KERN_ERR "QEMU TRACE Device: already mapped at %p\n", qt_base);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (r == NULL)
|
|
+ return -EINVAL;
|
|
+ qt_base = ioremap(r->start, PAGE_SIZE);
|
|
+ printk(KERN_INFO "QEMU TRACE Device: The mapped IO base is %p\n", qt_base);
|
|
+
|
|
+ qemu_trace_dump_init_threads();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int qemu_trace_remove(struct platform_device *pdev)
|
|
+{
|
|
+ iounmap(qt_base);
|
|
+ qt_base = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver qemu_trace = {
|
|
+ .probe = qemu_trace_probe,
|
|
+ .remove = qemu_trace_remove,
|
|
+ .driver = {
|
|
+ .name = "qemu_trace"
|
|
+ }
|
|
+};
|
|
+
|
|
+static int __init qemu_trace_dev_init(void)
|
|
+{
|
|
+ int ret;
|
|
+ ret = platform_driver_register(&qemu_trace);
|
|
+ init_called = 1;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void qemu_trace_dev_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&qemu_trace);
|
|
+}
|
|
+
|
|
+
|
|
+module_init(qemu_trace_dev_init);
|
|
+module_exit(qemu_trace_dev_exit);
|
|
+
|
|
+MODULE_AUTHOR("Ye Wen <ywen@google.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
--- /dev/null
|
|
+++ b/drivers/misc/qemutrace/qemu_trace.h
|
|
@@ -0,0 +1,22 @@
|
|
+/* drivers/misc/qemutrace/qemu_trace.h
|
|
+ *
|
|
+ * Copyright (C) 2007-2008 Google, Inc.
|
|
+ *
|
|
+ * This software is licensed under the terms of the GNU General Public
|
|
+ *
|
|
+ * License version 2, as published by the Free Software Foundation, and
|
|
+ * may be copied, distributed, and modified under those terms.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+void qemu_trace_start(void);
|
|
+void qemu_trace_stop(void);
|
|
+int qemu_trace_get_tracing(void);
|
|
+void qemu_trace_add_mapping(unsigned int addr, const char *symbol);
|
|
+void qemu_trace_remove_mapping(unsigned int addr);
|
|
+void qemu_trace_process_name(const char *name);
|
|
--- /dev/null
|
|
+++ b/drivers/misc/qemutrace/qemu_trace_sysfs.c
|
|
@@ -0,0 +1,182 @@
|
|
+/* drivers/misc/qemu_sysfs.c
|
|
+ *
|
|
+ * Copyright (C) 2007-2008 Google, Inc.
|
|
+ * Author: Jack Veenstra
|
|
+ *
|
|
+ * This software is licensed under the terms of the GNU General Public
|
|
+ * License version 2, as published by the Free Software Foundation, and
|
|
+ * may be copied, distributed, and modified under those terms.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/list.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/miscdevice.h>
|
|
+#include <linux/sysdev.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/wait.h>
|
|
+#include "qemu_trace.h"
|
|
+
|
|
+MODULE_DESCRIPTION("Qemu Trace Driver");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_VERSION("1.0");
|
|
+
|
|
+static struct kobject *qemu_trace_kobj;
|
|
+
|
|
+static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
|
|
+{
|
|
+ int val = qemu_trace_get_tracing();
|
|
+ buf[0] = '0' + val;
|
|
+ buf[1] = '\n';
|
|
+ return 2;
|
|
+}
|
|
+
|
|
+static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
|
|
+{
|
|
+ if (n <= 0)
|
|
+ return -EINVAL;
|
|
+ if (buf[0] == '0')
|
|
+ qemu_trace_stop();
|
|
+ else if (buf[0] == '1')
|
|
+ qemu_trace_start();
|
|
+ else
|
|
+ return -EINVAL;
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static ssize_t symbol_show(struct kobject *kobj, struct kobj_attribute *attr, char * buf)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+// We are expecting a string of the form "addr symbol" where 'addr' is a hex address
|
|
+// (without the leading '0x') and symbol is a newline-terminated string. This symbol
|
|
+// with its corresponding address will be added to the trace file.
|
|
+//
|
|
+// To remove the mapping for (addr, symbol) in the trace file, write just the
|
|
+// address. As before, the address is in hex without the leading '0x'. It can
|
|
+// be newline-terminated or zero-terminated.
|
|
+static ssize_t symbol_store(struct kobject *kobj, struct kobj_attribute *attr, const char * buf, size_t n)
|
|
+{
|
|
+ const char *cp;
|
|
+ unsigned int addr = 0;
|
|
+ int len;
|
|
+ char *sym;
|
|
+
|
|
+ if (n <= 0 || buf == NULL)
|
|
+ return -EINVAL;
|
|
+ for (cp = buf; *cp != ' '; ++cp) {
|
|
+ unsigned int digit;
|
|
+
|
|
+ if (*cp >= '0' && *cp <= '9')
|
|
+ digit = *cp - '0';
|
|
+ else if (*cp >= 'a' && *cp <= 'f')
|
|
+ digit = *cp - 'a' + 10;
|
|
+ else if (*cp == 0 || *cp == '\n') {
|
|
+ qemu_trace_remove_mapping(addr);
|
|
+ return n;
|
|
+ } else
|
|
+ return -EINVAL;
|
|
+ addr = (addr << 4) + digit;
|
|
+ }
|
|
+ // Move past the space
|
|
+ cp += 1;
|
|
+
|
|
+ // Copy the string to a new buffer so that we can replace the newline
|
|
+ // with '\0'.
|
|
+ len = strlen(cp);
|
|
+ sym = kzalloc(len + 1, GFP_KERNEL);
|
|
+ strcpy(sym, cp);
|
|
+ if (sym[len - 1] == '\n')
|
|
+ sym[len - 1] = 0;
|
|
+
|
|
+ qemu_trace_add_mapping(addr, sym);
|
|
+ kfree(sym);
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static ssize_t process_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* This expects a string that is the process name. If the string contains
|
|
+ * a trailing newline, that is removed in the emulator tracing code because
|
|
+ * it is simpler to do it there.
|
|
+ */
|
|
+static ssize_t process_name_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n)
|
|
+{
|
|
+ if (n <= 0 || buf == NULL)
|
|
+ return -EINVAL;
|
|
+
|
|
+ qemu_trace_process_name(buf);
|
|
+ return n;
|
|
+}
|
|
+
|
|
+
|
|
+#define qemu_trace_attr(_name) \
|
|
+static struct kobj_attribute _name##_attr = { \
|
|
+ .attr = { \
|
|
+ .name = __stringify(_name), \
|
|
+ .mode = 0666, \
|
|
+ }, \
|
|
+ .show = _name##_show, \
|
|
+ .store = _name##_store, \
|
|
+}
|
|
+
|
|
+qemu_trace_attr(state);
|
|
+qemu_trace_attr(symbol);
|
|
+qemu_trace_attr(process_name);
|
|
+
|
|
+static struct attribute * qemu_trace_attrs[] = {
|
|
+ &state_attr.attr,
|
|
+ &symbol_attr.attr,
|
|
+ &process_name_attr.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static struct attribute_group qemu_trace_attr_group = {
|
|
+ .attrs = qemu_trace_attrs,
|
|
+};
|
|
+
|
|
+static int __init qemu_trace_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ qemu_trace_kobj = kobject_create_and_add("qemu_trace", NULL);
|
|
+ if (qemu_trace_kobj == NULL) {
|
|
+ printk("qemu_trace_init: kobject_create_and_add failed\n");
|
|
+ ret = -ENOMEM;
|
|
+ return ret;
|
|
+ }
|
|
+ ret = sysfs_create_group(qemu_trace_kobj, &qemu_trace_attr_group);
|
|
+ if (ret) {
|
|
+ printk("qemu_trace_init: sysfs_create_group failed\n");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err:
|
|
+ kobject_del(qemu_trace_kobj);
|
|
+ qemu_trace_kobj = NULL;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void __exit qemu_trace_exit(void)
|
|
+{
|
|
+ sysfs_remove_group(qemu_trace_kobj, &qemu_trace_attr_group);
|
|
+ kobject_del(qemu_trace_kobj);
|
|
+}
|
|
+
|
|
+core_initcall(qemu_trace_init);
|
|
+module_exit(qemu_trace_exit);
|
|
--- a/fs/exec.c
|
|
+++ b/fs/exec.c
|
|
@@ -59,6 +59,9 @@
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/tlb.h>
|
|
#include "internal.h"
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ void qemu_trace_thread_name(char *name);
|
|
+#endif
|
|
|
|
int core_uses_pid;
|
|
char core_pattern[CORENAME_MAX_SIZE] = "core";
|
|
@@ -922,6 +925,9 @@ void set_task_comm(struct task_struct *t
|
|
task_lock(tsk);
|
|
strlcpy(tsk->comm, buf, sizeof(tsk->comm));
|
|
task_unlock(tsk);
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ qemu_trace_thread_name(buf);
|
|
+#endif
|
|
}
|
|
|
|
int flush_old_exec(struct linux_binprm * bprm)
|
|
@@ -1245,6 +1251,10 @@ void free_bprm(struct linux_binprm *bprm
|
|
kfree(bprm);
|
|
}
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+extern void qemu_trace_execve(int argc, char __user * __user * argv);
|
|
+#endif
|
|
+
|
|
/*
|
|
* sys_execve() executes a new program.
|
|
*/
|
|
@@ -1324,6 +1334,10 @@ int do_execve(char * filename,
|
|
goto out;
|
|
|
|
current->flags &= ~PF_KTHREAD;
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ qemu_trace_execve(bprm->argc, argv);
|
|
+#endif
|
|
+
|
|
retval = search_binary_handler(bprm,regs);
|
|
if (retval < 0)
|
|
goto out;
|
|
--- a/kernel/exit.c
|
|
+++ b/kernel/exit.c
|
|
@@ -60,6 +60,11 @@ DEFINE_TRACE(sched_process_free);
|
|
DEFINE_TRACE(sched_process_exit);
|
|
DEFINE_TRACE(sched_process_wait);
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+void qemu_trace_thread_name(char *name);
|
|
+void qemu_trace_exit(int code);
|
|
+#endif
|
|
+
|
|
static void exit_mm(struct task_struct * tsk);
|
|
|
|
static void __unhash_process(struct task_struct *p)
|
|
@@ -426,6 +431,9 @@ void daemonize(const char *name, ...)
|
|
va_start(args, name);
|
|
vsnprintf(current->comm, sizeof(current->comm), name, args);
|
|
va_end(args);
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ qemu_trace_thread_name(current->comm);
|
|
+#endif
|
|
|
|
/*
|
|
* If we were started as result of loading a module, close all of the
|
|
@@ -1012,6 +1020,12 @@ NORET_TYPE void do_exit(long code)
|
|
preempt_disable();
|
|
/* causes final put_task_struct in finish_task_switch(). */
|
|
tsk->state = TASK_DEAD;
|
|
+
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ /* Emit a trace record for the exit() call. */
|
|
+ qemu_trace_exit(code);
|
|
+#endif
|
|
+
|
|
schedule();
|
|
BUG();
|
|
/* Avoid "noreturn function does return". */
|
|
--- a/kernel/fork.c
|
|
+++ b/kernel/fork.c
|
|
@@ -1323,6 +1323,10 @@ struct task_struct * __cpuinit fork_idle
|
|
return task;
|
|
}
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+extern void qemu_trace_fork(struct task_struct *forked, unsigned long clone_flags);
|
|
+#endif
|
|
+
|
|
/*
|
|
* Ok, this is the main fork-routine.
|
|
*
|
|
@@ -1424,6 +1428,10 @@ long do_fork(unsigned long clone_flags,
|
|
tracehook_report_clone_complete(trace, regs,
|
|
clone_flags, nr, p);
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ qemu_trace_fork(p, clone_flags);
|
|
+#endif
|
|
+
|
|
if (clone_flags & CLONE_VFORK) {
|
|
freezer_do_not_count();
|
|
wait_for_completion(&vfork);
|
|
--- a/kernel/sched.c
|
|
+++ b/kernel/sched.c
|
|
@@ -2748,6 +2748,10 @@ asmlinkage void schedule_tail(struct tas
|
|
put_user(task_pid_vnr(current), current->set_child_tid);
|
|
}
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+void qemu_trace_cs(struct task_struct *next);
|
|
+#endif
|
|
+
|
|
/*
|
|
* context_switch - switch to the new MM and the new
|
|
* thread's register state.
|
|
@@ -2790,6 +2794,11 @@ context_switch(struct rq *rq, struct tas
|
|
spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
|
|
#endif
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ /* Emit a trace record for the context switch. */
|
|
+ qemu_trace_cs(next);
|
|
+#endif
|
|
+
|
|
/* Here we just switch the register state and the stack. */
|
|
switch_to(prev, next, prev);
|
|
|
|
--- a/mm/mmap.c
|
|
+++ b/mm/mmap.c
|
|
@@ -906,6 +906,11 @@ void vm_stat_account(struct mm_struct *m
|
|
}
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+extern void qemu_trace_mmap(struct vm_area_struct * vma);
|
|
+extern void qemu_trace_munmap(unsigned long start, unsigned long end);
|
|
+#endif
|
|
+
|
|
/*
|
|
* The caller must hold down_write(current->mm->mmap_sem).
|
|
*/
|
|
@@ -1212,6 +1217,10 @@ munmap_back:
|
|
pgoff = vma->vm_pgoff;
|
|
vm_flags = vma->vm_flags;
|
|
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ qemu_trace_mmap(vma);
|
|
+#endif
|
|
+
|
|
if (vma_wants_writenotify(vma))
|
|
vma->vm_page_prot = vm_get_page_prot(vm_flags & ~VM_SHARED);
|
|
|
|
@@ -1938,6 +1947,10 @@ int do_munmap(struct mm_struct *mm, unsi
|
|
* Remove the vma's, and unmap the actual pages
|
|
*/
|
|
detach_vmas_to_be_unmapped(mm, vma, prev, end);
|
|
+
|
|
+#ifdef CONFIG_QEMU_TRACE
|
|
+ qemu_trace_munmap(start, end);
|
|
+#endif
|
|
unmap_region(mm, vma, prev, start, end);
|
|
|
|
/* Fix up all other VM information */
|