FreeBSD Bugzilla – Attachment 18554 Details for
Bug 33300
-STABLE ptrace(2) implementation for i386 linux emulator
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
file.diff
file.diff (text/plain), 16.43 KB, created by
ak03
on 2001-12-29 16:40:01 UTC
(
hide
)
Description:
file.diff
Filename:
MIME Type:
Creator:
ak03
Created:
2001-12-29 16:40:01 UTC
Size:
16.43 KB
patch
obsolete
>Index: conf/files.i386 >=================================================================== >RCS file: /home/ncvs/src/sys/conf/files.i386,v >retrieving revision 1.307.2.19 >diff -u -r1.307.2.19 files.i386 >--- conf/files.i386 19 Dec 2001 20:59:28 -0000 1.307.2.19 >+++ conf/files.i386 29 Dec 2001 15:46:30 -0000 >@@ -352,6 +352,7 @@ > i386/linux/linux_locore.s optional compat_linux \ > dependency "linux_assym.h" > i386/linux/linux_machdep.c optional compat_linux >+i386/linux/linux_ptrace.c optional compat_linux > i386/linux/linux_sysent.c optional compat_linux > i386/linux/linux_sysvec.c optional compat_linux > svr4/imgact_svr4.c optional compat_svr4 >Index: kern/sys_process.c >=================================================================== >RCS file: /home/ncvs/src/sys/kern/sys_process.c,v >retrieving revision 1.51.2.2 >diff -u -r1.51.2.2 sys_process.c >--- kern/sys_process.c 3 Oct 2001 06:55:42 -0000 1.51.2.2 >+++ kern/sys_process.c 29 Dec 2001 15:51:23 -0000 >@@ -328,7 +328,7 @@ > case PT_STEP: > case PT_CONTINUE: > case PT_DETACH: >- if ((uap->req != PT_STEP) && ((unsigned)uap->data >= NSIG)) >+ if ((uap->req != PT_STEP) && ((unsigned)uap->data > _SIG_MAXSIG)) > return EINVAL; > > PHOLD(p); >Index: i386/linux/linux_ptrace.c >=================================================================== >RCS file: i386/linux/linux_ptrace.c >diff -N i386/linux/linux_ptrace.c >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ i386/linux/linux_ptrace.c 26 Dec 2001 15:57:52 -0000 >@@ -0,0 +1,502 @@ >+/* >+ * Copyright (c) 2001 Alexander Kabaev >+ * All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer >+ * in this position and unchanged. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * 3. The name of the author may not be used to endorse or promote products >+ * derived from this software without specific prior written permission. >+ * >+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR >+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES >+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. >+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, >+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT >+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF >+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ * >+ * $FreeBSD:$ >+ */ >+ >+#include <sys/param.h> >+#include <sys/systm.h> >+#include <sys/lock.h> >+#include <sys/proc.h> >+#include <sys/ptrace.h> >+#include <sys/sysproto.h> >+ >+#include <machine/md_var.h> >+#include <machine/npx.h> >+#include <machine/reg.h> >+ >+#include <vm/vm.h> >+#include <vm/pmap.h> >+#include <vm/vm_map.h> >+#include <sys/user.h> >+ >+#include <i386/linux/linux.h> >+#include <i386/linux/linux_proto.h> >+#include <compat/linux/linux_util.h> >+ >+/* >+ * Linux ptrace requests numbers. Mostly identical to FreeBSD, >+ * except for MD ones and PT_ATTACH/PT_DETACH. >+ */ >+#define PTRACE_TRACEME 0 >+#define PTRACE_PEEKTEXT 1 >+#define PTRACE_PEEKDATA 2 >+#define PTRACE_PEEKUSR 3 >+#define PTRACE_POKETEXT 4 >+#define PTRACE_POKEDATA 5 >+#define PTRACE_POKEUSR 6 >+#define PTRACE_CONT 7 >+#define PTRACE_KILL 8 >+#define PTRACE_SINGLESTEP 9 >+ >+#define PTRACE_ATTACH 16 >+#define PTRACE_DETACH 17 >+ >+#define PTRACE_SYSCALL 24 >+ >+#define PTRACE_GETREGS 12 >+#define PTRACE_SETREGS 13 >+#define PTRACE_GETFPREGS 14 >+#define PTRACE_SETFPREGS 15 >+#define PTRACE_GETFPXREGS 18 >+#define PTRACE_SETFPXREGS 19 >+ >+#define PTRACE_SETOPTIONS 21 >+ >+/* >+ * Linux keeps debug registers at the following >+ * offset in the user struct >+ */ >+#define LINUX_DBREG_OFFSET 252 >+#define LINUX_DBREG_SIZE 8*sizeof(l_int) >+ >+static __inline__ int >+map_signum(int signum) >+{ >+#ifndef __alpha__ >+ if (signum > 0 && signum <= LINUX_SIGTBLSZ) >+ signum = linux_to_bsd_signal[_SIG_IDX(signum)]; >+#endif >+ return ((signum == SIGSTOP)? 0 : signum); >+} >+ >+struct linux_pt_reg { >+ l_long ebx; >+ l_long ecx; >+ l_long edx; >+ l_long esi; >+ l_long edi; >+ l_long ebp; >+ l_long eax; >+ l_int xds; >+ l_int xes; >+ l_int xfs; >+ l_int xgs; >+ l_long orig_eax; >+ l_long eip; >+ l_int xcs; >+ l_long eflags; >+ l_long esp; >+ l_int xss; >+}; >+ >+ >+/* >+ * Translate i386 ptrace registers between Linux and FreeBSD formats. >+ * The translation is pretty straighforward, for all registers, but >+ * orig_eax in Linux side and r_trapno and r_err in FreeBSD >+ */ >+static int >+map_regs_to_linux(struct reg *bsd_r, struct linux_pt_reg *linux_r) >+{ >+ linux_r->ebx = bsd_r->r_ebx; >+ linux_r->ecx = bsd_r->r_ecx; >+ linux_r->edx = bsd_r->r_edx; >+ linux_r->esi = bsd_r->r_esi; >+ linux_r->edi = bsd_r->r_edi; >+ linux_r->ebp = bsd_r->r_ebp; >+ linux_r->eax = bsd_r->r_eax; >+ linux_r->xds = bsd_r->r_ds; >+ linux_r->xes = bsd_r->r_es; >+ linux_r->xfs = bsd_r->r_fs; >+ linux_r->xgs = bsd_r->r_gs; >+ linux_r->orig_eax = bsd_r->r_eax; >+ linux_r->eip = bsd_r->r_eip; >+ linux_r->xcs = bsd_r->r_cs; >+ linux_r->eflags = bsd_r->r_eflags; >+ linux_r->esp = bsd_r->r_esp; >+ linux_r->xss = bsd_r->r_ss; >+ return (0); >+} >+ >+static int >+map_regs_from_linux(struct reg *bsd_r, struct linux_pt_reg *linux_r) >+{ >+ bsd_r->r_ebx = linux_r->ebx; >+ bsd_r->r_ecx = linux_r->ecx; >+ bsd_r->r_edx = linux_r->edx; >+ bsd_r->r_esi = linux_r->esi; >+ bsd_r->r_edi = linux_r->edi; >+ bsd_r->r_ebp = linux_r->ebp; >+ bsd_r->r_eax = linux_r->eax; >+ bsd_r->r_ds = linux_r->xds; >+ bsd_r->r_es = linux_r->xes; >+ bsd_r->r_fs = linux_r->xfs; >+ bsd_r->r_gs = linux_r->xgs; >+ bsd_r->r_eip = linux_r->eip; >+ bsd_r->r_cs = linux_r->xcs; >+ bsd_r->r_eflags = linux_r->eflags; >+ bsd_r->r_esp = linux_r->esp; >+ bsd_r->r_ss = linux_r->xss; >+ return (0); >+} >+ >+struct linux_pt_fpreg { >+ l_long cwd; >+ l_long swd; >+ l_long twd; >+ l_long fip; >+ l_long fcs; >+ l_long foo; >+ l_long fos; >+ l_long st_space[2*10]; >+}; >+ >+static int >+map_fpregs_to_linux(struct fpreg *bsd_r, struct linux_pt_fpreg *linux_r) >+{ >+ linux_r->cwd = bsd_r->fpr_env[0]; >+ linux_r->swd = bsd_r->fpr_env[1]; >+ linux_r->twd = bsd_r->fpr_env[2]; >+ linux_r->fip = bsd_r->fpr_env[3]; >+ linux_r->fcs = bsd_r->fpr_env[4]; >+ linux_r->foo = bsd_r->fpr_env[5]; >+ linux_r->fos = bsd_r->fpr_env[6]; >+ bcopy(bsd_r->fpr_acc, linux_r->st_space, sizeof(linux_r->st_space)); >+ return (0); >+} >+ >+static int >+map_fpregs_from_linux(struct fpreg *bsd_r, struct linux_pt_fpreg *linux_r) >+{ >+ bsd_r->fpr_env[0] = linux_r->cwd; >+ bsd_r->fpr_env[1] = linux_r->swd; >+ bsd_r->fpr_env[2] = linux_r->twd; >+ bsd_r->fpr_env[3] = linux_r->fip; >+ bsd_r->fpr_env[4] = linux_r->fcs; >+ bsd_r->fpr_env[5] = linux_r->foo; >+ bsd_r->fpr_env[6] = linux_r->fos; >+ bcopy(bsd_r->fpr_acc, linux_r->st_space, sizeof(bsd_r->fpr_acc)); >+ return (0); >+} >+ >+struct linux_pt_fpxreg { >+ l_ushort cwd; >+ l_ushort swd; >+ l_ushort twd; >+ l_ushort fop; >+ l_long fip; >+ l_long fcs; >+ l_long foo; >+ l_long fos; >+ l_long mxcsr; >+ l_long reserved; >+ l_long st_space[32]; >+ l_long xmm_space[32]; >+ l_long padding[56]; >+}; >+ >+static int >+linux_proc_read_fpxregs(struct proc *p, struct linux_pt_fpxreg *fpxregs) >+{ >+#ifdef CPU_ENABLE_SSE >+ if (sizeof(*fpxregs) != sizeof(p->p_addr->u_pcb.pcb_save.sv_xmm)) { >+ printf("linux: savexmm != linux_pt_fpxreg\n"); >+ return (EIO); >+ } >+ if (cpu_fxsr == 0) >+#endif >+ return (EIO); >+ bcopy(&p->p_addr->u_pcb.pcb_save.sv_xmm, fpxregs, sizeof *fpxregs); >+ return (0); >+} >+ >+static int >+linux_proc_write_fpxregs(struct proc *p, struct linux_pt_fpxreg *fpxregs) >+{ >+#ifdef CPU_ENABLE_SSE >+ if (sizeof(*fpxregs) != sizeof(p->p_addr->u_pcb.pcb_save.sv_xmm)) { >+ printf("linux: savexmm != linux_pt_fpxreg\n"); >+ return (EIO); >+ } >+ if (cpu_fxsr == 0) >+#endif >+ return (EIO); >+ bcopy(fpxregs, &p->p_addr->u_pcb.pcb_save.sv_xmm, sizeof *fpxregs); >+ return (0); >+} >+ >+ >+int >+linux_ptrace(struct proc *p, struct linux_ptrace_args *uap) >+{ >+ struct ptrace_args bsd_args; >+ int error; >+ caddr_t sg; >+ union { >+ struct linux_pt_reg reg; >+ struct linux_pt_fpreg fpreg; >+ struct linux_pt_fpxreg fpxreg; >+ } r; >+ >+ sg = stackgap_init(); >+ >+ error = 0; >+ >+ /* by default, just copy data intact */ >+ bsd_args.req = uap->req; >+ bsd_args.pid = (pid_t)uap->pid; >+ bsd_args.addr = (caddr_t)uap->addr; >+ bsd_args.data = uap->data; >+ >+ switch (uap->req) { >+ case PTRACE_TRACEME: >+ case PTRACE_POKETEXT: >+ case PTRACE_POKEDATA: >+ case PTRACE_KILL: >+ error = ptrace(p, &bsd_args); >+ break; >+ case PTRACE_PEEKTEXT: >+ case PTRACE_PEEKDATA: { >+ /* need to preserve return value */ >+ int rval = p->p_retval[0]; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ if (error == 0) >+ error = copyout(p->p_retval, >+ (caddr_t)uap->data, sizeof(l_int)); >+ p->p_retval[0] = rval; >+ break; >+ } >+ case PTRACE_DETACH: >+ bsd_args.req = PT_DETACH; >+ /* fall through */ >+ case PTRACE_SINGLESTEP: >+ case PTRACE_CONT: >+ bsd_args.data = map_signum(uap->data); >+ bsd_args.addr = (caddr_t)1; >+ error = ptrace(p, &bsd_args); >+ break; >+ case PTRACE_ATTACH: >+ bsd_args.req = PT_ATTACH; >+ error = ptrace(p, &bsd_args); >+ break; >+ case PTRACE_GETREGS: { >+ struct reg *bsd_r = (struct reg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ /* Linux is using data where FreeBSD is using addr */ >+ bsd_args.req = PT_GETREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ if (error == 0) >+ error = map_regs_to_linux(bsd_r, &r.reg); >+ if (error == 0) >+ error = copyout(&r.reg, (caddr_t)uap->data, sizeof r.reg); >+ break; >+ } >+ case PTRACE_SETREGS: { >+ struct reg *bsd_r = (struct reg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ /* Linux is using data where FreeBSD is using addr */ >+ bsd_args.req = PT_SETREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = copyin((caddr_t)uap->data, &r.reg, sizeof r.reg); >+ if (error == 0) >+ error = map_regs_from_linux(bsd_r, &r.reg); >+ if (error == 0) >+ error = ptrace(p, &bsd_args); >+ break; >+ } >+ case PTRACE_GETFPREGS: { >+ struct fpreg *bsd_r = (struct fpreg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ /* Linux is using data where FreeBSD is using addr */ >+ bsd_args.req = PT_GETFPREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ if (error == 0) >+ error = map_fpregs_to_linux(bsd_r, &r.fpreg); >+ if (error == 0) >+ error = copyout(&r.fpreg, (caddr_t)uap->data, sizeof r.fpreg); >+ break; >+ } >+ case PTRACE_SETFPREGS: { >+ struct fpreg *bsd_r = (struct fpreg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ /* Linux is using data where FreeBSD is using addr */ >+ bsd_args.req = PT_SETFPREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = copyin((caddr_t)uap->data, &r.fpreg, sizeof r.fpreg); >+ if (error == 0) >+ error = map_fpregs_from_linux(bsd_r, &r.fpreg); >+ if (error == 0) >+ error = ptrace(p, &bsd_args); >+ break; >+ } >+ case PTRACE_SETFPXREGS: >+ case PTRACE_GETFPXREGS: { >+ struct proc *pchild; >+ struct fpreg *bsd_r; >+ >+ /* >+ * Use FreeBSD PT_GETFPREGS for permisson testing. >+ */ >+ bsd_r = (struct fpreg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ bsd_args.req = PT_GETFPREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ /* >+ * If this fails, PTRACE_GETFPXREGS should fail >+ * for exactly the same reason. >+ */ >+ if (error != 0) >+ break; >+ >+ if ((pchild = pfind(uap->pid)) == NULL) { >+ error = ESRCH; >+ break; >+ } >+ if (uap->req == PTRACE_GETFPXREGS) { >+ PHOLD(pchild); >+ error = linux_proc_read_fpxregs( >+ pchild, &r.fpxreg); >+ PRELE(pchild); >+ if (error == 0) >+ error = copyout(&r.fpxreg, >+ (caddr_t)uap->data, sizeof r.fpxreg); >+ } else { >+ error = copyin((caddr_t)uap->data, >+ &r.fpxreg, sizeof r.fpxreg); >+ if (error == 0) { >+ /* clear dangerous bits exactly as Linux does*/ >+ r.fpxreg.mxcsr &= 0xffbf; >+ PHOLD(pchild); >+ error = linux_proc_write_fpxregs( >+ pchild, &r.fpxreg); >+ PRELE(pchild); >+ } >+ } >+ break; >+ } >+ case PTRACE_PEEKUSR: >+ case PTRACE_POKEUSR: { >+ error = EIO; >+ >+ /* check addr for alignment */ >+ if (uap->addr < 0 || uap->addr & (sizeof(l_int) - 1)) >+ break; >+ /* >+ * Allow linux programs to access register values in >+ * user struct. We simulate this through PT_GET/SETREGS >+ * as necessary. >+ */ >+ if (uap->addr < sizeof(struct linux_pt_reg)) { >+ struct reg *bsd_r; >+ >+ bsd_r = (struct reg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ bsd_args.req = PT_GETREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ >+ error = ptrace(p, &bsd_args); >+ if (error != 0) >+ break; >+ >+ error = map_regs_to_linux(bsd_r, &r.reg); >+ if (error != 0) >+ break; >+ >+ if (uap->req == PTRACE_PEEKUSR) { >+ error = copyout((char *)&r.reg + uap->addr, >+ (caddr_t)uap->data, sizeof(l_int)); >+ break; >+ } >+ >+ *(l_int *)((char *)&r.reg + uap->addr) = (l_int)uap->data; >+ >+ error = map_regs_from_linux(bsd_r, &r.reg); >+ if (error != 0) >+ break; >+ >+ bsd_args.req = PT_SETREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ } >+ >+ /* >+ * Simulate debug registers access >+ */ >+ if (uap->addr >= LINUX_DBREG_OFFSET && >+ uap->addr <= LINUX_DBREG_OFFSET + LINUX_DBREG_SIZE) { >+ struct dbreg *bsd_r; >+ >+ bsd_r = (struct dbreg*)stackgap_alloc(&sg, >+ sizeof(*bsd_r)); >+ bsd_args.req = PT_GETDBREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ if (error != 0) >+ break; >+ >+ uap->addr -= LINUX_DBREG_OFFSET; >+ if (uap->req == PTRACE_PEEKUSR) { >+ error = copyout((char *)bsd_r + uap->addr, >+ (caddr_t)uap->data, sizeof(l_int)); >+ break; >+ } >+ >+ *(l_int *)((char *)bsd_r + uap->addr) = uap->data; >+ bsd_args.req = PT_SETDBREGS; >+ bsd_args.addr = (caddr_t)bsd_r; >+ bsd_args.data = 0; >+ error = ptrace(p, &bsd_args); >+ } >+ >+ break; >+ } >+ case PTRACE_SYSCALL: >+ /* fall through */ >+ default: >+ error = EINVAL; >+ goto noimpl; >+ } >+ return (error); >+ >+ noimpl: >+ printf("linux: ptrace(%u, ...) not implemented\n", >+ (u_int32_t)uap->req); >+ return (error); >+} >Index: i386/linux/linux_dummy.c >=================================================================== >RCS file: /home/ncvs/src/sys/i386/linux/linux_dummy.c,v >retrieving revision 1.21.2.6 >diff -u -r1.21.2.6 linux_dummy.c >--- i386/linux/linux_dummy.c 5 Nov 2001 19:08:23 -0000 1.21.2.6 >+++ i386/linux/linux_dummy.c 26 Dec 2001 15:33:52 -0000 >@@ -39,7 +39,6 @@ > DUMMY(stat); > DUMMY(mount); > DUMMY(stime); >-DUMMY(ptrace); > DUMMY(fstat); > DUMMY(olduname); > DUMMY(syslog); >Index: i386/linux/syscalls.master >=================================================================== >RCS file: /home/ncvs/src/sys/i386/linux/syscalls.master,v >retrieving revision 1.30.2.7 >diff -u -r1.30.2.7 syscalls.master >--- i386/linux/syscalls.master 5 Nov 2001 19:08:23 -0000 1.30.2.7 >+++ i386/linux/syscalls.master 26 Dec 2001 15:33:52 -0000 >@@ -65,7 +65,8 @@ > 23 STD LINUX { int linux_setuid16(l_uid16_t uid); } > 24 STD LINUX { int linux_getuid16(void); } > 25 STD LINUX { int linux_stime(void); } >-26 STD LINUX { int linux_ptrace(void); } >+26 STD LINUX { int linux_ptrace(l_long req, l_long pid, l_long addr, \ >+ l_long data); } > 27 STD LINUX { int linux_alarm(l_uint secs); } > 28 STD LINUX { int linux_fstat(l_uint fd, struct ostat *up); } > 29 STD LINUX { int linux_pause(void); } >Index: modules/linux/Makefile >=================================================================== >RCS file: /home/ncvs/src/sys/modules/linux/Makefile,v >retrieving revision 1.34.2.9 >diff -u -r1.34.2.9 Makefile >--- modules/linux/Makefile 5 Nov 2001 19:08:24 -0000 1.34.2.9 >+++ modules/linux/Makefile 26 Dec 2001 15:32:06 -0000 >@@ -8,7 +8,9 @@ > SRCS= linux_dummy.c linux_file.c linux_getcwd.c linux_ioctl.c linux_ipc.c \ > linux_machdep.c linux_mib.c linux_misc.c linux_signal.c linux_socket.c \ > linux_stats.c linux_sysctl.c linux_sysent.c linux_sysvec.c \ >- linux_util.c opt_compat.h opt_linux.h opt_vmpage.h vnode_if.h >+ linux_util.c linux_ptrace.c opt_compat.h opt_linux.h opt_vmpage.h \ >+ vnode_if.h >+ > OBJS= linux_locore.o > MAN= linux.8
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 33300
: 18554