Messages in this thread Patch in this message | | | Date | Sat, 29 Dec 2001 11:31:46 +0100 (CET) | From | Frank Cornelis <> | Subject | [PATCH] i386 extended ptrace |
| |
Hey,
This patch extends the ptrace system call by providing new requests to trace memory differences made by system calls in a much cleaner way than just to scan the entire user process memory area. I think it's safe to merge with 2.4.17 because, when you don't enable it in the config it won't compile into the kernel image. It would save me a lot of time not to continually have to patch it against the latest kernel my own; I've got other things to do.
Feedback is very welcome, Frank. diff -Nur linux-2.4.17/arch/i386/config.in linux/arch/i386/config.in --- linux-2.4.17/arch/i386/config.in Sat Dec 22 09:35:39 2001 +++ linux/arch/i386/config.in Tue Dec 25 18:36:25 2001 @@ -254,6 +254,9 @@ "ELF CONFIG_KCORE_ELF \ A.OUT CONFIG_KCORE_AOUT" ELF fi +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + bool 'Extended ptrace support (EXPERIMENTAL)' CONFIG_EXPTRACE +fi tristate 'Kernel support for a.out binaries' CONFIG_BINFMT_AOUT tristate 'Kernel support for ELF binaries' CONFIG_BINFMT_ELF tristate 'Kernel support for MISC binaries' CONFIG_BINFMT_MISC diff -Nur linux-2.4.17/Documentation/Configure.help linux/Documentation/Configure.help --- linux-2.4.17/Documentation/Configure.help Sat Dec 22 09:35:56 2001 +++ linux/Documentation/Configure.help Tue Dec 25 18:36:25 2001 @@ -3668,6 +3668,18 @@ Not necessary unless you're using a very out-of-date binutils version. You probably want KCORE_ELF. +Extended ptrace support +CONFIG_EXPTRACE + This adds extra functionality to the ptrace system call in order + to be able to trace memory differences caused by system calls + much better. (i386 architecture only) + This causes little overhead on write accesses to the user memory + space since recording only takes place when tracing a process. + Note that enabling this option will enlarge the kernel image + with about 10 KB. + + If in doubt, say N. + Kernel support for ELF binaries CONFIG_BINFMT_ELF ELF (Executable and Linkable Format) is a format for libraries and diff -Nur linux-2.4.17/include/asm-i386/processor.h linux/include/asm-i386/processor.h --- linux-2.4.17/include/asm-i386/processor.h Sat Dec 22 09:35:17 2001 +++ linux/include/asm-i386/processor.h Tue Dec 25 19:15:02 2001 @@ -362,6 +362,16 @@ unsigned long __cacheline_filler[5]; }; +/* + * We have to declare list_head here (again) because GCC + * only allows us to use a pointer to list_head here 'cause + * of cross-compilation between list.h and processor.h, and + * I really want a list_head struct, not a pointer to one. + */ +struct _list_head { + struct list_head *next, *prev; +}; + struct thread_struct { unsigned long esp0; unsigned long eip; @@ -381,6 +391,12 @@ /* IO permissions */ int ioperm; unsigned long io_bitmap[IO_BITMAP_SIZE+1]; +/* extended ptrace memdiff data */ +#if defined(CONFIG_EXPTRACE) + struct _list_head memdiff_list; + struct memdiff_struct *memdiff_nlist; + int memdiff_error; +#endif }; #define INIT_THREAD { \ diff -Nur linux-2.4.17/include/asm-i386/ptrace.h linux/include/asm-i386/ptrace.h --- linux-2.4.17/include/asm-i386/ptrace.h Sat Dec 22 09:35:17 2001 +++ linux/include/asm-i386/ptrace.h Tue Dec 25 23:52:34 2001 @@ -1,6 +1,8 @@ #ifndef _I386_PTRACE_H #define _I386_PTRACE_H +#include <linux/config.h> + #define EBX 0 #define ECX 1 #define EDX 2 @@ -51,13 +53,54 @@ #define PTRACE_SETOPTIONS 21 +#define PTRACE_RAWMEMDIFF 50 +#define PTRACE_DIFFMEM 51 +#define PTRACE_PATCHMEM 52 + /* options set using PTRACE_SETOPTIONS */ #define PTRACE_O_TRACESYSGOOD 0x00000001 +#define MEMDIFF_ID__ANONYMOUS 0 +#define MEMDIFF_ID__PUT_USER_U64 1 +#define MEMDIFF_ID__PUT_USER_ASM 2 +#define MEMDIFF_ID__COPY_USER 3 +#define MEMDIFF_ID__CONSTANT_COPY_USER 4 +#define MEMDIFF_ID__DO_CLEAR_USER 5 + +#if defined(CONFIG_EXPTRACE) +extern void __record_memdiff(void *ptr, unsigned long size, + unsigned long id); +#define record_memdiff(ptr, size, id) \ +do { \ + if (current->ptrace & PT_PTRACED) \ + __record_memdiff((ptr), (size), (id)); \ +} while (0) +#else +#define record_memdiff(ptr, size, id) do { } while (0) +#endif + #ifdef __KERNEL__ #define user_mode(regs) ((VM_MASK & (regs)->eflags) || (3 & (regs)->xcs)) #define instruction_pointer(regs) ((regs)->eip) extern void show_regs(struct pt_regs *); -#endif + +#include <linux/list.h> + +struct memdiff_struct { + struct list_head list; + unsigned long begin_addr; + unsigned long size; + unsigned long id; +}; + +#if defined(CONFIG_EXPTRACE) +void init_memdiff_lists(struct task_struct *task); +void clear_memdiff_lists(struct task_struct *task); +#else +#define init_memdiff_lists(task) do { } while (0) +#define clear_memdiff_lists(task) do { } while (0) +#endif /* CONFIG_EXPTRACE */ + +#endif /* __KERNEL__ */ #endif diff -Nur linux-2.4.17/arch/i386/kernel/ptrace.c linux/arch/i386/kernel/ptrace.c --- linux-2.4.17/arch/i386/kernel/ptrace.c Sat Dec 22 09:35:40 2001 +++ linux/arch/i386/kernel/ptrace.c Wed Dec 26 20:49:14 2001 @@ -3,6 +3,8 @@ /* * Pentium III FXSR, SSE support * Gareth Hughes <gareth@valinux.com>, May 2000 + * Extended ptrace support + * Frank Cornelis <Frank.Cornelis@elis.rug.ac.be>, December 2001 */ #include <linux/kernel.h> @@ -13,6 +15,8 @@ #include <linux/errno.h> #include <linux/ptrace.h> #include <linux/user.h> +#include <linux/slab.h> +#include <linux/config.h> #include <asm/uaccess.h> #include <asm/pgtable.h> @@ -21,6 +25,18 @@ #include <asm/i387.h> #include <asm/debugreg.h> +#undef PTRACE_DEBUG + +#ifdef PTRACE_DEBUG +#define DEBUG(stuff...) do { \ +printk(KERN_DEBUG "ptrace_debug: current->pid = %d (%s)\n", \ + current->pid, current->comm); \ +printk(KERN_DEBUG stuff); \ +} while (0) +#else +#define DEBUG(stuff...) do { } while (0) +#endif + /* * does not yet catch signals sent when the child dies. * in exit.c or in signal.c. @@ -38,6 +54,251 @@ */ #define EFL_OFFSET ((EFL-2)*4-sizeof(struct pt_regs)) +#if defined(CONFIG_EXPTRACE) +static kmem_cache_t *memdiff_cachep; + +static int __init memdiff_cache_init(void) +{ + memdiff_cachep = kmem_cache_create("memdiff_cache", + sizeof(struct memdiff_struct), + 0, SLAB_HWCACHE_ALIGN, + NULL, NULL); + if (!memdiff_cachep) + panic("memdiff_cache: Couldn't alloc memory."); + return 0; +} +__initcall(memdiff_cache_init); + +static inline struct memdiff_struct *memdiff_alloc(struct task_struct *task) +{ + struct memdiff_struct *memdiff; + memdiff = kmem_cache_alloc(memdiff_cachep, SLAB_KERNEL); + if (!memdiff) { + printk(KERN_CRIT "memdiff_cache: out of memory\n"); + task->thread.memdiff_error = -ENOMEM; + } + return memdiff; +} + +static inline void copy_memdiff_record(struct memdiff_struct *dest, + struct memdiff_struct *src) +{ + if (!dest || !src) + return; + dest->begin_addr = src->begin_addr; + dest->size = src->size; + dest->id = src->id; +} + +/* + * Builds the memdiff record and adds it to the memdiff_list of + * the current process. + */ +void __record_memdiff(void *ptr, unsigned long size, + unsigned long id) +{ + struct memdiff_struct *memdiff; + memdiff = memdiff_alloc(current); + if (!memdiff) + return; + memdiff->begin_addr = (unsigned long)ptr; + memdiff->size = size; + memdiff->id = id; + list_add_tail(&memdiff->list, + (struct list_head *)¤t->thread.memdiff_list); +} + +/* + * Next code also gets invoked by process.c:copy_thread + */ +void init_memdiff_lists(struct task_struct *task) +{ + DEBUG("init_memdiff_lists called\n"); + INIT_LIST_HEAD((struct list_head *)&task->thread.memdiff_list); + task->thread.memdiff_nlist = NULL; + task->thread.memdiff_error = 0; +} + +/* + * Clears the memdiff lists (the raw and normalized one) of the + * given process. + * Next code also gets invoked by process.c:exit_thread + */ +void clear_memdiff_lists(struct task_struct *task) +{ + struct list_head *cache_obj, *head; + /* clear the raw memdiff list */ + head = (struct list_head *)&task->thread.memdiff_list; + cache_obj = head->next; + while (cache_obj != head) { + struct list_head *kill_me; + kill_me = cache_obj; + cache_obj = cache_obj->next; + kmem_cache_free(memdiff_cachep, kill_me); + } + /* clear the (double linked) normalized memdiff list */ + head = &task->thread.memdiff_nlist->list; + if (head) + cache_obj = head->next; + else + cache_obj = NULL; + while (cache_obj != head) { + struct list_head *kill_me; + kill_me = cache_obj; + cache_obj = cache_obj->next; + kmem_cache_free(memdiff_cachep, kill_me); + } + if (head) + kmem_cache_free(memdiff_cachep, head); + init_memdiff_lists(task); +} + +/* + * Next function constructs the normalized memdiff list + * using the information from the raw memdiff list. + */ +static void normalize(struct task_struct *task) +{ + struct memdiff_struct *nlist, *curr_entry, *next_entry; + struct list_head *tmp, *curr, *next; + if (task->thread.memdiff_nlist) + return; /* already normalized */ + if (!(nlist = memdiff_alloc(task))) + return; + /* first 'insertion sort' the raw memdiffs into the normalized list */ + INIT_LIST_HEAD(&nlist->list); + list_for_each(tmp, (struct list_head *)&task->thread.memdiff_list) { + struct memdiff_struct *raw_entry; + struct list_head *ntmp; + raw_entry = list_entry(tmp, struct memdiff_struct, list); + list_for_each_prev(ntmp, &nlist->list) { + struct memdiff_struct *n_entry; + n_entry = list_entry(ntmp, struct memdiff_struct, list); + if (n_entry->begin_addr < raw_entry->begin_addr) { + struct memdiff_struct *new; + if (!(new = memdiff_alloc(task))) + return; + copy_memdiff_record(new, raw_entry); + raw_entry = NULL; + list_add(&new->list, &n_entry->list); + break; + } + } + if (raw_entry) { + struct memdiff_struct *new; + if (!(new = memdiff_alloc(task))) + return; + copy_memdiff_record(new, raw_entry); + list_add(&new->list, &nlist->list); + } + } + /* now normalize the already sorted nlist */ + curr = nlist->list.next; + next = curr->next; + while (next != &nlist->list) { + curr_entry = list_entry(curr, struct memdiff_struct, list); + next_entry = list_entry(next, struct memdiff_struct, list); + if (curr_entry->begin_addr + curr_entry->size >= next_entry->begin_addr) { + curr_entry->size = next_entry->begin_addr + next_entry->size + - curr_entry->begin_addr; + list_del(next); + kmem_cache_free(memdiff_cachep, next); + next = curr->next; + continue; + } + curr = curr->next; + next = curr->next; + } + task->thread.memdiff_nlist = nlist; +} + +static inline int count_memdiffs(struct list_head *list) +{ + int count = 0; + struct list_head *tmp; + list_for_each(tmp, list) + count++; + return count; +} + +static int count_raw_writes(struct task_struct *task) +{ + return count_memdiffs((struct list_head *)&task->thread.memdiff_list); +} + +static int count_normalized_writes(struct task_struct *task) +{ + if (!task->thread.memdiff_nlist) + return 0; + return count_memdiffs((struct list_head *)&task->thread.memdiff_nlist->list); +} + +static unsigned long get_raw_record_size(struct task_struct *task) +{ + unsigned long size = 0; + struct list_head *list, *tmp; + list = (struct list_head *)&task->thread.memdiff_list; + size += sizeof(unsigned long); + list_for_each(tmp, list) { + size += 3 * sizeof(unsigned long); + size += list_entry(tmp, struct memdiff_struct, list)->size; + } + return size; +} + +static void write_raw_record(struct task_struct *task, long ptr) +{ + struct list_head *list, *tmp; + __put_user(count_raw_writes(task), ((unsigned long *)ptr)++); + list = (struct list_head *)&task->thread.memdiff_list; + list_for_each(tmp, list) { + struct memdiff_struct *memdiff; + memdiff = list_entry(tmp, struct memdiff_struct, list); + __put_user(memdiff->begin_addr, ((unsigned long *)ptr)++); + __put_user(memdiff->id, ((unsigned long *)ptr)++); + __put_user(memdiff->size, ((unsigned long *)ptr)++); + if (ptrace_readdata(task, memdiff->begin_addr, + (char *)ptr, memdiff->size) < 0) + printk(KERN_CRIT "write_raw_record: readdata error\n"); + ptr += memdiff->size; + } +} + +static unsigned long get_normalized_record_size(struct task_struct *task) +{ + unsigned long size = 0; + struct list_head *list, *tmp; + if (!task->thread.memdiff_nlist) + return 0; + list = (struct list_head *)&task->thread.memdiff_nlist->list; + size += sizeof(unsigned long); + list_for_each(tmp, list) { + size += 2 * sizeof(unsigned long); + size += list_entry(tmp, struct memdiff_struct, list)->size; + } + return size; +} + +static void write_normalized_record(struct task_struct *task, long ptr) +{ + struct list_head *list, *tmp; + __put_user(count_normalized_writes(task), ((unsigned long *)ptr)++); + if (!task->thread.memdiff_nlist) + return; + list = (struct list_head *)&task->thread.memdiff_nlist->list; + list_for_each(tmp, list) { + struct memdiff_struct *memdiff; + memdiff = list_entry(tmp, struct memdiff_struct, list); + __put_user(memdiff->begin_addr, ((unsigned long *)ptr)++); + __put_user(memdiff->size, ((unsigned long *)ptr)++); + if (ptrace_readdata(task, memdiff->begin_addr, + (char *)ptr, memdiff->size) < 0) + printk(KERN_CRIT "write_normalized_record: readdata error\n"); + ptr += memdiff->size; + } +} +#endif /* CONFIG_EXPTRACE */ + /* * this routine will get a word off of the processes privileged stack. * the offset is how far from the base addr as stored in the TSS. @@ -161,6 +422,7 @@ goto out; /* set the ptrace bit in the process flags. */ current->ptrace |= PT_PTRACED; + clear_memdiff_lists(current); ret = 0; goto out; } @@ -179,6 +441,7 @@ if (request == PTRACE_ATTACH) { ret = ptrace_attach(child); + clear_memdiff_lists(child); goto out_tsk; } @@ -282,7 +545,8 @@ else child->ptrace &= ~PT_TRACESYS; child->exit_code = data; - /* make sure the single step bit is not set. */ + clear_memdiff_lists(child); + /* make sure the single step bit is not set. */ tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; put_stack_long(child, EFL_OFFSET,tmp); wake_up_process(child); @@ -305,6 +569,7 @@ /* make sure the single step bit is not set. */ tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG; put_stack_long(child, EFL_OFFSET, tmp); + clear_memdiff_lists(child); wake_up_process(child); break; } @@ -323,6 +588,7 @@ tmp = get_stack_long(child, EFL_OFFSET) | TRAP_FLAG; put_stack_long(child, EFL_OFFSET, tmp); child->exit_code = data; + clear_memdiff_lists(child); /* give it a chance to run. */ wake_up_process(child); ret = 0; @@ -331,6 +597,7 @@ case PTRACE_DETACH: /* detach a process that was attached. */ + clear_memdiff_lists(child); ret = ptrace_detach(child, data); break; @@ -428,6 +695,91 @@ break; } +#if defined(CONFIG_EXPTRACE) + /* + * RAWMEMDIFF: Returns the number of raw writes. + * When addr!=NULL it writes the size of the + * raw memdiff record to addr. + * When data!=NULL it writes the raw memdiff + * record to data. + */ + case PTRACE_RAWMEMDIFF: { + unsigned long record_size; + ret = -EIO; + if (child->thread.memdiff_error) + break; + if (addr && !access_ok(VERIFY_WRITE, (unsigned *)addr, sizeof(unsigned long))) + break; + record_size = get_raw_record_size(child); + if (data && !access_ok(VERIFY_WRITE, (unsigned *)data, record_size)) + break; + if (addr) + __put_user(record_size, (unsigned long *)addr); + if (data) + write_raw_record(child, data); + ret = count_raw_writes(child); + break; + } + + /* + * DIFFMEM: Returns the number of normalized writes. + * When addr!=NULL it writes the size of the normalized + * memdiff record to addr. + * When data!=NULL it writes the normalized memdiff + * record to data. + */ + case PTRACE_DIFFMEM: { + unsigned long record_size; + ret = -EIO; + normalize(child); + if (child->thread.memdiff_error) + break; + if (addr && !access_ok(VERIFY_WRITE, (unsigned *)addr, sizeof(unsigned long))) + break; + record_size = get_normalized_record_size(child); + if (data && !access_ok(VERIFY_WRITE, (unsigned *)data, record_size)) + break; + if (addr) + __put_user(record_size, (unsigned long *)addr); + if (data) + write_normalized_record(child, data); + ret = count_normalized_writes(child); + break; + } + + /* + * PATCHMEM: Applies a (normalized) memdiff record + * given by addr to the child memory space. + * Return the number of bytes written to the child + * memory. + */ + case PTRACE_PATCHMEM: { + unsigned long nrecords, nwritten = 0; + ret = -EIO; + if (!addr) + break; + if (!access_ok(VERIFY_READ, (unsigned *)addr, sizeof(unsigned long))) + break; + __get_user(nrecords, ((unsigned long *)addr)++); + while (nrecords) { + unsigned long begin_addr, size; + if (!access_ok(VERIFY_READ, addr, 2 * sizeof(unsigned long))) + goto out_tsk; + __get_user(begin_addr, ((unsigned long *)addr)++); + __get_user(size, ((unsigned long *)addr)++); + if (!access_ok(VERIFY_READ, addr, size)) + goto out_tsk; + if (ptrace_writedata(child, (char *)addr, begin_addr, size) < 0) + goto out_tsk; + nwritten += size; + addr += size; + nrecords--; + } + ret = nwritten; + break; + } +#endif /* CONFIG_EXPTRACE */ + default: ret = -EIO; break; diff -Nur linux-2.4.17/arch/i386/kernel/process.c linux/arch/i386/kernel/process.c --- linux-2.4.17/arch/i386/kernel/process.c Sat Dec 22 09:35:40 2001 +++ linux/arch/i386/kernel/process.c Wed Dec 26 20:55:01 2001 @@ -515,7 +515,7 @@ */ void exit_thread(void) { - /* nothing to do ... */ + clear_memdiff_lists(current); } void flush_thread(void) @@ -597,6 +597,8 @@ unlazy_fpu(current); struct_cpy(&p->thread.i387, ¤t->thread.i387); + init_memdiff_lists(p); + return 0; } diff -Nur linux-2.4.17/include/asm-i386/uaccess.h linux/include/asm-i386/uaccess.h --- linux-2.4.17/include/asm-i386/uaccess.h Sat Dec 22 09:35:17 2001 +++ linux/include/asm-i386/uaccess.h Wed Dec 26 20:53:23 2001 @@ -8,6 +8,7 @@ #include <linux/sched.h> #include <linux/prefetch.h> #include <asm/page.h> +#include <asm/ptrace.h> #define VERIFY_READ 0 #define VERIFY_WRITE 1 @@ -157,6 +158,10 @@ }) #define __put_user_u64(x, addr, err) \ +do { \ + __typeof__(*(addr)) *__puu64_addr = (addr); \ + record_memdiff(__puu64_addr, 8, \ + MEMDIFF_ID__PUT_USER_U64); \ __asm__ __volatile__( \ "1: movl %%eax,0(%2)\n" \ "2: movl %%edx,4(%2)\n" \ @@ -171,7 +176,8 @@ " .long 2b,4b\n" \ ".previous" \ : "=r"(err) \ - : "A" (x), "r" (addr), "i"(-EFAULT), "0"(err)) + : "A" (x), "r" (__puu64_addr), "i"(-EFAULT), "0"(err)); \ +} while (0) #define __put_user_size(x,ptr,size,retval) \ do { \ @@ -194,6 +200,10 @@ * aliasing issues. */ #define __put_user_asm(x, addr, err, itype, rtype, ltype) \ +do { \ + __typeof__(*(addr)) *__pua_addr = (addr); \ + record_memdiff(__pua_addr, sizeof(*__pua_addr), \ + MEMDIFF_ID__PUT_USER_ASM); \ __asm__ __volatile__( \ "1: mov"itype" %"rtype"1,%2\n" \ "2:\n" \ @@ -206,7 +216,8 @@ " .long 1b,3b\n" \ ".previous" \ : "=r"(err) \ - : ltype (x), "m"(__m(addr)), "i"(-EFAULT), "0"(err)) + : ltype (x), "m"(__m(__pua_addr)), "i"(-EFAULT), "0"(err)); \ +} while (0) #define __get_user_nocheck(x,ptr,size) \ @@ -255,6 +266,7 @@ #define __copy_user(to,from,size) \ do { \ int __d0, __d1; \ + record_memdiff((to), (size), MEMDIFF_ID__COPY_USER); \ __asm__ __volatile__( \ "0: rep; movsl\n" \ " movl %3,%0\n" \ @@ -324,6 +336,8 @@ #define __constant_copy_user(to, from, size) \ do { \ int __d0, __d1; \ + record_memdiff((to), (size), \ + MEMDIFF_ID__CONSTANT_COPY_USER); \ switch (size & 3) { \ default: \ __asm__ __volatile__( \ diff -Nur linux-2.4.17/arch/i386/lib/usercopy.c linux/arch/i386/lib/usercopy.c --- linux-2.4.17/arch/i386/lib/usercopy.c Sat Dec 22 09:35:40 2001 +++ linux/arch/i386/lib/usercopy.c Tue Dec 25 18:36:25 2001 @@ -8,6 +8,7 @@ #include <linux/config.h> #include <asm/uaccess.h> #include <asm/mmx.h> +#include <asm/ptrace.h> #ifdef CONFIG_X86_USE_3DNOW_AND_WORKS @@ -120,6 +121,7 @@ #define __do_clear_user(addr,size) \ do { \ int __d0; \ + record_memdiff((addr), (size), MEMDIFF_ID__DO_CLEAR_USER); \ __asm__ __volatile__( \ "0: rep; stosl\n" \ " movl %2,%0\n" \ diff -Nur linux-2.4.17/arch/i386/kernel/i386_ksyms.c linux/arch/i386/kernel/i386_ksyms.c --- linux-2.4.17/arch/i386/kernel/i386_ksyms.c Sat Dec 22 09:35:40 2001 +++ linux/arch/i386/kernel/i386_ksyms.c Tue Dec 25 18:36:25 2001 @@ -28,6 +28,7 @@ #include <asm/desc.h> #include <asm/pgtable.h> #include <asm/pgalloc.h> +#include <asm/ptrace.h> extern void dump_thread(struct pt_regs *, struct user *); extern spinlock_t rtc_lock; @@ -173,3 +174,7 @@ extern int is_sony_vaio_laptop; EXPORT_SYMBOL(is_sony_vaio_laptop); + +#if defined(CONFIG_EXPTRACE) +EXPORT_SYMBOL_NOVERS(__record_memdiff); +#endif | |