Strace 4 Linux x86 - IO traceing

Maciej Zenczykowski maze at cela.pl
Tue Nov 11 13:40:13 UTC 2003


On Sun, 9 Nov 2003, Roland McGrath wrote:

> This is not something that is really apropos to strace, or at least not in
> the current circumstances.  strace uses the operating system's facilities
> for catching a process doing a system call.  You would need an equivalent
> facility in the operating system, i.e. the Linux kernel.  As far as I know,
> there is no aspect of the ptrace facility that lets you trap io instructions.
> >From my understanding of the x86 hardware facilities, it would have to work
> by disallowing the permissions that ioperm/iopl enable, catching the fault,
> and then handling it specially.  Not real hard to do, but it has to be done
> before strace can consider supporting it.

I've spent a good while thinking about what you've written - even so far 
as almost starting coding it within kernel space and I've come to the 
conclusion that this belongs in user space.  All the functionality 
necessary to implement this is present in the kernel.

I'm including a 'proof-of-concept' or whatever program.  8160 bytes and it 
pretty much does what I was looking for.  It's not 100% perfect.

  a) pushf/popf/iret/cli/sti can't be emulated period - processor 
     limitation - they'll be silently ignored (cli/sti) and the interrupt
     flag will be silently unchanged (popf/iret) and the interrupt flag
     and iopl levels will contain unemulated values (pushf)
- if something is critical enough to require this we can't really 
trace it like this anyway since our timing is way off... each io 
instruction causes at least two task switches...

  b) I'm not fully familiar with the ptrace/signal interaction and I'm 
     sure I'm missing something (iotrace mc with subshell hangs in 
     rt_sigsuspend for no apparent reason - I don't need such functionality
     so I'm not fixing it [don't really know how] - strace handles mc
     nicely, so it can be done, besides if this gets converted into a patch
     for strace then it'll fix itself... :) ) [actually it's already 
     fixed]

  c) I haven't implemented string/stream i/o (ie. insb/insw/insl/
     outsb/outsw/outsl and the same preceded by rep) because:
       1) I haven't seen it used that much
       2) we're not catching mmaped IO anyway
       3) it's not trivial to implement if DS/ES registers are set to 
          non-default values
- on the other hand if this was to go into strace as a patch the DS/ES 
== default value should probably be implemented for pseudo-completeness.
If someone tells me how to access our traced processes user memory at 
address seg:ofs (note that seg is not necessarily the normal user DS!) and 
figure out whether segment seg is 32bit operand/address sized default or
not - then I'll do it.
- currently string I/O instructions should cause a SIGSEGV (untested)
- undefined instructions should also cause a SIGSEGV (untested)
This is probably much easier to get right by patching strace then by 
writing it from scratch :) especially due to the lack of good ptrace 
documentation...

  d) at the moment we're assuming the code segment is fully 32 bit 
     (default 32bit operand and address sizes)
- as above this requires determining whether a segment is 32bit operand 
and/or 32bit address not.

Most nice one-threaded programs which don't screw around with signals 
should work with this version, most others will probably work as well :), 
although we don't follow any forks.
For example "iotrace SVGATextMode 80x25 2>log" works fine for me.
And "iotrace zgv 2>/dev/null" also works (didn't examine the actual log of 
this svgalib console graphics viewer - it's possible it forks...).

What do you people think?  Should this be patched into strace?  If so 
anybody willing to tell me where to cut&paste this into the strace source?
Anybody know how to get segment code16/32 data16/32 boolean values and 
segment base/limit for any given segment?  I expect GDB does this somehow...

Cheers,
MaZe.
-------------- next part --------------
/* GPLv2 (C) Copyright 2003, Maciej Zenczykowski */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <sys/ptrace.h>
#include <linux/ptrace.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/io.h>
/* #include <linux/user.h> */

#define eprintf(V...) fprintf(stderr, V)

typedef int bool;
#define false ((bool)0)
#define true  ((bool)1)

/* typedef unsigned uint; */
typedef   signed char   int8;
typedef   signed short  int16;
typedef   signed int    int32;
typedef unsigned char  uint8;
typedef unsigned short uint16;
typedef unsigned int   uint32;

struct user_regs {
  union {
    struct { uint32 ebx,              ecx,              edx;              };
    struct { uint16 bx,     xbx,      cx,     xcx,      dx,     xdx;      };
    struct { uint8  bl, bh, xbl, xbh, cl, ch, xcl, xch, dl, dh, xdl, xdh; };
  };
  union {
    struct { uint32 esi,              edi,              ebp;               };
    struct { uint16 si,     xsi,      di,     xdi,      bp,     xbp;       };
  };
  union {
    struct { uint32 eax;              };
    struct { uint16 ax,     xax;      };
    struct { uint8  al, ah, xal, xah; };
  };
  uint16 ds, xds, es, xes, fs, xfs, gs, xgs;
  union {
    struct { uint32 _eax,                 eip;     };
    struct { uint16 _ax,      _xax,       ip, xip; }; 
    struct { uint8  _al, _ah, _xal, _xah; };
  };
  uint16 cs, xcs;
  union {
    struct { uint32 eflags,        esp;     };
    struct { uint16 flags, xflags, sp, xsp; };
  };
  uint16 ss, xss;
};

static inline void out_b (uint16 port, uint8  * v) { asm volatile ("outb\t%b0, %w1" : : "a" (*v), "Nd" (port)); };
static inline void out_w (uint16 port, uint16 * v) { asm volatile ("outw\t%w0, %w1" : : "a" (*v), "Nd" (port)); };
static inline void out_l (uint16 port, uint32 * v) { asm volatile ("outl\t%0, %w1"  : : "a" (*v), "Nd" (port)); };

static inline void in_b  (uint16 port, uint8  * v) { asm volatile ("inb\t%w1, %b0"  : "=a" (*v) : "Nd" (port)); };
static inline void in_w  (uint16 port, uint16 * v) { asm volatile ("inw\t%w1, %w0"  : "=a" (*v) : "Nd" (port)); };
static inline void in_l  (uint16 port, uint32 * v) { asm volatile ("inl\t%w1, %0"   : "=a" (*v) : "Nd" (port)); };

int cap_rawio = 1;

int user_iopl = 0;
#define max_ioperm 1024
uint8 user_bitmap[max_ioperm / 8 + 1];

void init_ioperm (void) { memset(user_bitmap, -1, sizeof(user_bitmap)); };

int emulate_iopl (int level) {
  int res;
  eprintf("iopl(%d) = ", level);
  res = -EINVAL; if (level > 3) goto out;
  /* Trying to gain more privileges? */
  res = -EPERM; if (level > user_iopl) if (!cap_rawio) goto out;
  res = 0; user_iopl = level;
out:
  eprintf("%d\n", res);
  return res;
};

int emulate_ioperm(uint32 from, uint32 num, int turn_on) {
  int res, i;
  eprintf("ioperm(%u, %u, %d) = ", from, num, turn_on);
  res = -EINVAL; if ((from + num <= from) || (from + num > max_ioperm)) goto out;
  res = -EPERM; if (turn_on && !cap_rawio) goto out;

  res = 0; /* doesn't have to be fast */
  for (i = from; i < from + num; i++) {
    if (turn_on) user_bitmap[i >> 3] &= ~( 1 << (i & 7) );
    else         user_bitmap[i >> 3] |=  ( 1 << (i & 7) );
  };
out:
  eprintf("%d\n", res);
  return res;
};

bool valid_port (uint16 port, int mask) {
  if (user_iopl == 3) return true;
  if ( ((*(uint16*)&user_bitmap[port >> 3]) >> (port & 7)) & mask ) return false;
  return true;
};
#define valid8(P)  valid_port((P), 1)
#define valid16(P) valid_port((P), 3)
#define valid32(P) valid_port((P), 7)

#define CODE(P)   ptrace(PTRACE_PEEKTEXT, pid, (P), 0)
#define CODE8(P)  ((uint8)(CODE(P)))

uint32 mask[] = { 0, 0xFF, 0xFFFF, 0xFFFFFF, 0xFFFFFFFF };

int emulate (int pid, struct user_regs * regs) {
  int seg = 0;
  bool gr1 = 0, gr2 = 0, gr3 = 0, gr4 = 0;
  bool op32 = 1, ad32 = 1; /* !!! ASSUMES 32BIT CODE !!! */
  bool lock = 0, repne = 0, repe = 0;
  int pp = 0;
  uint8 insn, param;
 
loop:
  insn = CODE8(regs->eip + pp); ++pp;
  param = CODE8(regs->eip + pp);

  switch (insn) {
    case 0x26: eprintf("ES: ");   if (gr1++) goto error; seg = ES; goto loop;
    case 0x2E: eprintf("CS: ");   if (gr1++) goto error; seg = CS; goto loop;
    case 0x36: eprintf("SS: ");   if (gr1++) goto error; seg = SS; goto loop;
    case 0x3E: eprintf("DS: ");   if (gr1++) goto error; seg = DS; goto loop;
    case 0x64: eprintf("FS: ");   if (gr1++) goto error; seg = FS; goto loop;
    case 0x65: eprintf("GS: ");   if (gr1++) goto error; seg = GS; goto loop;
    case 0x66: eprintf("op32 " ); if (gr2++) goto error; op32 ^= 1; goto loop;
    case 0x67: eprintf("ad32 " ); if (gr3++) goto error; ad32 ^= 1; goto loop;
    case 0xF0: eprintf("lock " ); if (gr4++) goto error; lock  = 1; goto loop;
    case 0xF2: eprintf("repne "); if (gr4++) goto error; repne = 1; goto loop;
    case 0xF3: eprintf("repe " ); if (gr4++) goto error; repe  = 1; goto loop;

    /* DISABLE/ENABLE INTERRUPTS - IGNORE! */
    case 0xFA: eprintf("cli\n"); goto ok;
    case 0xFB: eprintf("sti\n"); goto ok;

    /* STRING I/O - UNIMPLEMENTED */
    case 0x6C: eprintf("insb\n"); goto error;
    case 0x6D: eprintf("insw/l\n"); goto error;
    case 0x6E: eprintf("outsb\n"); goto error;
    case 0x6F: eprintf("outsw/l\n"); goto error;

#define simulate(name, szo, sza, port, dir) \
eprintf("%s%c %0*X %s ", #name, "0bw3l"[szo], sza+sza, port, dir); \
if (!valid_port(port, (1 << szo) - 1 )) { eprintf("ERROR\n"); goto error; }; \
if (szo == 1) name ## _b(port, &regs->al); \
if (szo == 2) name ## _w(port, &regs->ax); \
if (szo == 4) name ## _l(port, &regs->eax); \
eprintf("%0*X\n", szo+szo, regs->eax & mask[szo]); \
goto ok;

#define simulate_in(szo, sza, port) simulate(in, szo, sza, port, "=>");
#define simulate_out(szo, sza, port) simulate(out, szo, sza, port, "<=");

    /* NORMAL I/O */
    case 0xE4: pp++; simulate_in (           1, 1, param);
    case 0xE5: pp++; simulate_in (op32 ? 4 : 2, 1, param);
    case 0xE6: pp++; simulate_out(           1, 1, param);
    case 0xE7: pp++; simulate_out(op32 ? 4 : 2, 1, param);
    case 0xEC:       simulate_in (           1, 2, regs->dx);
    case 0xED:       simulate_in (op32 ? 4 : 2, 2, regs->dx);
    case 0xEE:       simulate_out(           1, 2, regs->dx);
    case 0xEF:       simulate_out(op32 ? 4 : 2, 2, regs->dx);

#undef simulate_out
#undef simulate_in
#undef simulate

    /* ANYTHING ELSE IS BAD */
    default: goto error;
  };
ok:
  regs->eip += pp;
  return pp;
error:
  return 0;
};

int main (int argc, char * * argv) {
  int pid;

  if (argc < 2) {
    eprintf("Usage: %s program arguments...\n", argv[0]);
    return 127;
  }
  
  pid = fork();
  if (!pid) {
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    execvp(argv[1], &argv[1]);
    eprintf("Execution of %s failed.\n", argv[1]);
    return 126;
  };
  
  ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD);

  iopl(3);

  for (;;) {
    static struct user_regs regs;
    static bool in_sys = true; /* we begin in post-exec syscall */
    static int sc_retval = 0;
    int status, signal;

    while (pid != wait4(pid, &status, 0, 0));
    
    if (WIFEXITED(status)) goto exit; /* WEXITSTATUS */
    signal = WIFSIGNALED(status) ? WTERMSIG(status) : WIFSTOPPED(status) ? WSTOPSIG(status) : 0;
    
    ptrace(PTRACE_GETREGS, pid, 0, &regs);
    
    if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
      if (emulate(pid, &regs)) signal = 0;
    };
    if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
      signal = 0;
      int sc = regs._eax;
      eprintf("SYSCALL %d %s\n", sc, in_sys ? "LEAVE" : "ENTER");
#define EMU(NR, FUNCTION) \
if (sc==NR) if (!in_sys) { sc_retval = FUNCTION; regs._eax = 0; }; \
if (sc== 0) if (in_sys)  { regs.eax = sc_retval; };
      EMU(__NR_iopl,   emulate_iopl(regs.ebx) );
      EMU(__NR_ioperm, emulate_ioperm(regs.ebx, regs.ecx, regs.edx) );
      in_sys ^= 1;
    };
    
    ptrace(PTRACE_SETREGS, pid, 0, &regs);

    if (ptrace(PTRACE_SYSCALL, pid, 0, signal)) {
      perror("ptrace_syscall");
      return 1;
    };
  };

/* ptrace(PTRACE_CONT, pid, 0, 0); */
/* wait3(0, 0, 0); */
 exit:
  return 0;
};


More information about the Strace-devel mailing list