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, ®s->al); \
if (szo == 2) name ## _w(port, ®s->ax); \
if (szo == 4) name ## _l(port, ®s->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, ®s);
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
if (emulate(pid, ®s)) 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, ®s);
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