]> granicus.if.org Git - strace/commitdiff
[X86] Use ptrace(PTRACE_GETREGSET, NT_PRSTATUS) to get registers.
authorDenys Vlasenko <vda.linux@googlemail.com>
Thu, 14 Feb 2013 02:29:48 +0000 (03:29 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Thu, 14 Feb 2013 02:29:48 +0000 (03:29 +0100)
Unlike PTRACE_GETREGS, this new method detects 32-bit processes
reliably, without checking segment register values which
are undocumented and aren't part of any sort of API.
While at it, also fixed x32 detection to use __X32_SYSCALL_BIT,
as it should have been from the beginning.

* defs.h: Declare os_release and KERNEL_VERSION.
* strace.c: Make os_release non-static, remove KERNEL_VERSION define.
* syscall.c: New struct i386_user_regs_struct,
static union x86_regs_union and struct iovec x86_io.
(printcall): Use i386_regs or x86_64_regs depending on x86_io.iov_len.
(get_regs): On x86 and kernels 2.6.30+, use PTRACE_GETREGSET,
on earlier kernels fall back to old method.
(get_scno): [X86] Determine personality based on regset size
on scno & __X32_SYSCALL_BIT.
(syscall_fixup_on_sysenter): Use i386_regs or x86_64_regs depending
on x86_io.iov_len.
(get_syscall_args): Likewise.
(get_error): Likewise.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
defs.h
strace.c
syscall.c

diff --git a/defs.h b/defs.h
index ab474cbc97644ef9d00e5c23e53acfa5e59338d2..8ea1aaa36a11b944ee90ac52e4196976ceca7cfd 100644 (file)
--- a/defs.h
+++ b/defs.h
@@ -518,10 +518,13 @@ extern bool not_failing_only;
 extern bool show_fd_path;
 extern bool tracing_paths;
 extern bool need_fork_exec_workarounds;
-extern unsigned int xflag;
-extern unsigned int followfork;
-extern unsigned int ptrace_setoptions;
-extern unsigned int max_strlen;
+extern unsigned xflag;
+extern unsigned followfork;
+extern unsigned ptrace_setoptions;
+extern unsigned max_strlen;
+extern unsigned os_release;
+#undef KERNEL_VERSION
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
 
 enum bitness_t { BITNESS_CURRENT = 0, BITNESS_32 };
 
index eac6ba59ba07540f96840428496ecc4ee9f7db3a..bc98d365e9d276ff9c1f7e972bab755b2e135c51 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -58,9 +58,6 @@ extern char *optarg;
 # define my_tkill(tid, sig) kill((tid), (sig))
 #endif
 
-#undef KERNEL_VERSION
-#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
-
 cflag_t cflag = CFLAG_NONE;
 unsigned int followfork = 0;
 unsigned int ptrace_setoptions = 0;
@@ -146,7 +143,7 @@ static struct tcb **tcbtab;
 static unsigned int nprocs, tcbtabsize;
 static const char *progname;
 
-static unsigned os_release; /* generated from uname()'s u.release */
+unsigned os_release; /* generated from uname()'s u.release */
 
 static int detach(struct tcb *tcp);
 static int trace(void);
index ec4856c1dcf39117bdbc155e14a43f8bdcc4e8d0..b0104d09266a7ea1f808bb07819a5ccfb184911e 100644 (file)
--- a/syscall.c
+++ b/syscall.c
 # include <asm/rse.h>
 #endif
 
+#if defined(X86_64) || defined(X32)
+# include <linux/ptrace.h>
+# include <asm/ptrace.h>
+# include <sys/uio.h>
+# include <elf.h>
+#endif
+
 #if defined(AARCH64)
 # include <asm/ptrace.h>
 # include <sys/uio.h>
@@ -657,12 +664,39 @@ is_restart_error(struct tcb *tcp)
 struct pt_regs i386_regs;
 #elif defined(X86_64) || defined(X32)
 /*
- * On 32 bits, pt_regs and user_regs_struct are the same,
- * but on 64 bits, user_regs_struct has six more fields:
+ * On i386, pt_regs and user_regs_struct are the same,
+ * but on 64 bit x86, user_regs_struct has six more fields:
  * fs_base, gs_base, ds, es, fs, gs.
  * PTRACE_GETREGS fills them too, so struct pt_regs would overflow.
  */
-static struct user_regs_struct x86_64_regs;
+struct i386_user_regs_struct {
+       uint32_t ebx;
+       uint32_t ecx;
+       uint32_t edx;
+       uint32_t esi;
+       uint32_t edi;
+       uint32_t ebp;
+       uint32_t eax;
+       uint32_t xds;
+       uint32_t xes;
+       uint32_t xfs;
+       uint32_t xgs;
+       uint32_t orig_eax;
+       uint32_t eip;
+       uint32_t xcs;
+       uint32_t eflags;
+       uint32_t esp;
+       uint32_t xss;
+};
+static union {
+       struct user_regs_struct      x86_64_r;
+       struct i386_user_regs_struct i386_r;
+} x86_regs_union;
+# define x86_64_regs x86_regs_union.x86_64_r
+# define i386_regs   x86_regs_union.i386_r
+static struct iovec x86_io = {
+       .iov_base = &x86_regs_union
+};
 #elif defined(IA64)
 long ia32 = 0; /* not static */
 static long ia64_r8, ia64_r10;
@@ -738,7 +772,16 @@ printcall(struct tcb *tcp)
        tprintf("[%016lx] ", psw);
 # endif
 #elif defined(X86_64) || defined(X32)
-       tprintf("[%016lx] ", (unsigned long) x86_64_regs.rip);
+       if (x86_io.iov_len == sizeof(i386_regs)) {
+               tprintf("[%08x] ", (unsigned) i386_regs.eip);
+       } else {
+# if defined(X86_64)
+               tprintf("[%016lx] ", (unsigned long) x86_64_regs.rip);
+# elif defined(X32)
+               /* Note: this truncates 64-bit rip to 32 bits */
+               tprintf("[%08lx] ", (unsigned long) x86_64_regs.rip);
+# endif
+       }
 #elif defined(IA64)
        long ip;
 
@@ -859,7 +902,40 @@ void get_regs(pid_t pid)
 # elif defined(I386)
        get_regs_error = ptrace(PTRACE_GETREGS, pid, NULL, (long) &i386_regs);
 # elif defined(X86_64) || defined(X32)
-       get_regs_error = ptrace(PTRACE_GETREGS, pid, NULL, (long) &x86_64_regs);
+       /* PTRACE_GETREGSET was introduced around 2.6.25 */
+       if (os_release >= KERNEL_VERSION(2,6,30)) {
+               /*x86_io.iov_base = &x86_regs_union; - already is */
+               x86_io.iov_len = sizeof(x86_regs_union);
+               get_regs_error = ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, (long) &x86_io);
+       } else {
+               /* Use old method, with heuristical detection of 32-bitness */
+               x86_io.iov_len = sizeof(x86_64_regs);
+               get_regs_error = ptrace(PTRACE_GETREGS, pid, NULL, (long) &x86_64_regs);
+               if (!get_regs_error && x86_64_regs.cs == 0x23) {
+                       x86_io.iov_len = sizeof(i386_regs);
+                       /*
+                        * The order is important: i386_regs and x86_64_regs
+                        * are overlaid in memory!
+                        */
+                       i386_regs.ebx = x86_64_regs.rbx;
+                       i386_regs.ecx = x86_64_regs.rcx;
+                       i386_regs.edx = x86_64_regs.rdx;
+                       i386_regs.esi = x86_64_regs.rsi;
+                       i386_regs.edi = x86_64_regs.rdi;
+                       i386_regs.ebp = x86_64_regs.rbp;
+                       i386_regs.eax = x86_64_regs.rax;
+                       /*i386_regs.xds = x86_64_regs.ds; unused by strace */
+                       /*i386_regs.xes = x86_64_regs.es; ditto... */
+                       /*i386_regs.xfs = x86_64_regs.fs;*/
+                       /*i386_regs.xgs = x86_64_regs.gs;*/
+                       i386_regs.orig_eax = x86_64_regs.orig_rax;
+                       i386_regs.eip = x86_64_regs.rip;
+                       /*i386_regs.xcs = x86_64_regs.cs;*/
+                       /*i386_regs.eflags = x86_64_regs.eflags;*/
+                       i386_regs.esp = x86_64_regs.rsp;
+                       /*i386_regs.xss = x86_64_regs.ss;*/
+               }
+       }
 # elif defined(ARM)
        get_regs_error = ptrace(PTRACE_GETREGS, pid, NULL, (void *)&arm_regs);
 # elif defined(AARCH64)
@@ -1015,14 +1091,33 @@ get_scno(struct tcb *tcp)
 #  define __X32_SYSCALL_BIT    0x40000000
 # endif
        int currpers;
-       scno = x86_64_regs.orig_rax;
-
-       /* Check CS register value. On x86-64 linux it is:
-        *      0x33    for long mode (64 bit)
-        *      0x23    for compatibility mode (32 bit)
-        * Check DS register value. On x86-64 linux it is:
-        *      0x2b    for x32 mode (x86-64 in 32 bit)
+# if 1
+       /* GETREGSET of NT_PRSTATUS tells us regset size,
+        * which unambiguously detects i386.
+        *
+        * Linux kernel distinguishes x86-64 and x32 processes
+        * solely by looking at __X32_SYSCALL_BIT:
+        * arch/x86/include/asm/compat.h::is_x32_task():
+        * if (task_pt_regs(current)->orig_ax & __X32_SYSCALL_BIT)
+        *         return true;
+        */
+       if (x86_io.iov_len == sizeof(i386_regs)) {
+               scno = i386_regs.orig_eax;
+               currpers = 1;
+       } else {
+               scno = x86_64_regs.orig_rax;
+               currpers = 0;
+               if (scno & __X32_SYSCALL_BIT) {
+                       scno -= __X32_SYSCALL_BIT;
+                       currpers = 2;
+               }
+       }
+# elif 0
+       /* cs = 0x33 for long mode (native 64 bit and x32)
+        * cs = 0x23 for compatibility mode (32 bit)
+        * ds = 0x2b for x32 mode (x86-64 in 32 bit)
         */
+       scno = x86_64_regs.orig_rax;
        switch (x86_64_regs.cs) {
                case 0x23: currpers = 1; break;
                case 0x33:
@@ -1039,19 +1134,15 @@ get_scno(struct tcb *tcp)
                        currpers = current_personality;
                        break;
        }
-# if 0
+# elif 0
        /* This version analyzes the opcode of a syscall instruction.
         * (int 0x80 on i386 vs. syscall on x86-64)
-        * It works, but is too complicated.
+        * It works, but is too complicated, and strictly speaking, unreliable.
         */
-       unsigned long val, rip, i;
-
-       rip = x86_64_regs.rip;
-
+       unsigned long call, rip = x86_64_regs.rip;
        /* sizeof(syscall) == sizeof(int 0x80) == 2 */
        rip -= 2;
        errno = 0;
-
        call = ptrace(PTRACE_PEEKTEXT, tcp->pid, (char *)rip, (char *)0);
        if (errno)
                fprintf(stderr, "ptrace_peektext failed: %s\n",
@@ -1353,9 +1444,14 @@ syscall_fixup_on_sysenter(struct tcb *tcp)
        }
 #elif defined(X86_64) || defined(X32)
        {
-               long rax = x86_64_regs.rax;
-               if (current_personality == 1)
-                       rax = (int)rax; /* sign extend from 32 bits */
+               long rax;
+               if (x86_io.iov_len == sizeof(i386_regs)) {
+                       /* Sign extend from 32 bits */
+                       rax = (int32_t)i386_regs.eax;
+               } else {
+                       /* Note: in X32 build, this truncates 64 to 32 bits */
+                       rax = x86_64_regs.rax;
+               }
                if (rax != -ENOSYS) {
                        if (debug_flag)
                                fprintf(stderr, "not a syscall entry (rax = %ld)\n", rax);
@@ -1649,7 +1745,8 @@ get_syscall_args(struct tcb *tcp)
 #elif defined(X86_64) || defined(X32)
        (void)i;
        (void)nargs;
-       if (current_personality != 1) { /* x86-64 or x32 ABI */
+       if (x86_io.iov_len != sizeof(i386_regs)) {
+               /* x86-64 or x32 ABI */
                tcp->u_arg[0] = x86_64_regs.rdi;
                tcp->u_arg[1] = x86_64_regs.rsi;
                tcp->u_arg[2] = x86_64_regs.rdx;
@@ -1664,14 +1761,15 @@ get_syscall_args(struct tcb *tcp)
                tcp->ext_arg[4] = x86_64_regs.r8;
                tcp->ext_arg[5] = x86_64_regs.r9;
 #  endif
-       } else { /* i386 ABI */
-               /* Sign-extend lower 32 bits */
-               tcp->u_arg[0] = (long)(int)x86_64_regs.rbx;
-               tcp->u_arg[1] = (long)(int)x86_64_regs.rcx;
-               tcp->u_arg[2] = (long)(int)x86_64_regs.rdx;
-               tcp->u_arg[3] = (long)(int)x86_64_regs.rsi;
-               tcp->u_arg[4] = (long)(int)x86_64_regs.rdi;
-               tcp->u_arg[5] = (long)(int)x86_64_regs.rbp;
+       } else {
+               /* i386 ABI */
+               /* Sign-extend from 32 bits */
+               tcp->u_arg[0] = (long)(int32_t)i386_regs.ebx;
+               tcp->u_arg[1] = (long)(int32_t)i386_regs.ecx;
+               tcp->u_arg[2] = (long)(int32_t)i386_regs.edx;
+               tcp->u_arg[3] = (long)(int32_t)i386_regs.esi;
+               tcp->u_arg[4] = (long)(int32_t)i386_regs.edi;
+               tcp->u_arg[5] = (long)(int32_t)i386_regs.ebp;
        }
 #elif defined(MICROBLAZE)
        for (i = 0; i < nargs; ++i)
@@ -1985,22 +2083,39 @@ get_error(struct tcb *tcp)
                tcp->u_rval = i386_regs.eax;
        }
 #elif defined(X86_64)
-       if (check_errno && is_negated_errno(x86_64_regs.rax)) {
+       long rax;
+       if (x86_io.iov_len == sizeof(i386_regs)) {
+               /* Sign extend from 32 bits */
+               rax = (int32_t)i386_regs.eax;
+       } else {
+               rax = x86_64_regs.rax;
+       }
+       if (check_errno && is_negated_errno(rax)) {
                tcp->u_rval = -1;
-               u_error = -x86_64_regs.rax;
+               u_error = -rax;
        }
        else {
-               tcp->u_rval = x86_64_regs.rax;
+               tcp->u_rval = rax;
        }
 #elif defined(X32)
+       /* In X32, return value is 64-bit (llseek uses one).
+        * Using merely "long rax" would not work.
+        */
+       long long rax;
+       if (x86_io.iov_len == sizeof(i386_regs)) {
+               /* Sign extend from 32 bits */
+               rax = (int32_t)i386_regs.eax;
+       } else {
+               rax = x86_64_regs.rax;
+       }
        /* Careful: is_negated_errno() works only on longs */
-       if (check_errno && is_negated_errno_x32(x86_64_regs.rax)) {
+       if (check_errno && is_negated_errno_x32(rax)) {
                tcp->u_rval = -1;
-               u_error = -x86_64_regs.rax;
+               u_error = -rax;
        }
        else {
-               tcp->u_rval = x86_64_regs.rax; /* truncating */
-               tcp->u_lrval = x86_64_regs.rax;
+               tcp->u_rval = rax; /* truncating */
+               tcp->u_lrval = rax;
        }
 #elif defined(IA64)
        if (ia32) {