469 lines
14 KiB
C
469 lines
14 KiB
C
/*
|
|
* Main program for ioctl tracer.
|
|
*
|
|
* Copyright (c) 2007 by Jefferson Ogata
|
|
*/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; see the file COPYING. If not, write to the Free Software
|
|
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
/********************************************************************
|
|
|
|
megatrace
|
|
|
|
Program to ptrace another program and inspect megaraid-related ioctl
|
|
syscalls it makes.
|
|
|
|
Author: Jefferson Ogata (JO317) <ogata@antibozo.net>
|
|
Date: 2006/01/23
|
|
|
|
TODO:
|
|
|
|
Fixes for 64-bit systems.
|
|
|
|
********************************************************************/
|
|
|
|
|
|
#include "mega.h"
|
|
#include "megaioctl.h"
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/wait.h>
|
|
#include <asm/user.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <scsi/scsi.h>
|
|
|
|
|
|
#include "callinfo.h"
|
|
#include "dumpbytes.h"
|
|
|
|
enum state { UNTRACED, INBOUND, OUTBOUND };
|
|
|
|
|
|
void copyout (void *buf, size_t len, pid_t pid, uint32_t addr)
|
|
{
|
|
off_t k;
|
|
uint32_t *z = (uint32_t *) buf;
|
|
uint32_t sd;
|
|
|
|
for (k = 0; k < (len + 3) / 4; ++k)
|
|
{
|
|
sd = ptrace (PTRACE_PEEKDATA, pid, (void *) (addr + 4 * k), 0);
|
|
z[k] = sd;
|
|
}
|
|
}
|
|
|
|
|
|
void copyin (void *buf, size_t len, pid_t pid, uint32_t addr)
|
|
{
|
|
off_t k;
|
|
uint32_t *z = (uint32_t *) buf;
|
|
|
|
for (k = 0; k < (len + 3) / 4; ++k)
|
|
ptrace (PTRACE_POKEDATA, pid, (void *) (addr + 4 * k), z[k]);
|
|
}
|
|
|
|
|
|
int main (int argc, char **argv, char **environ)
|
|
{
|
|
pid_t pid;
|
|
int state = UNTRACED;
|
|
struct timeval starttv;
|
|
int printproc = 0;
|
|
|
|
if ((pid = fork ()) == 0)
|
|
{
|
|
ptrace (PTRACE_TRACEME);
|
|
++argv;
|
|
execvp (argv[0], argv);
|
|
fprintf (stderr, "execve: %s: %s\n", argv[0], strerror (errno));
|
|
_exit (1);
|
|
}
|
|
|
|
if (pid < 0)
|
|
{
|
|
fprintf (stderr, "fork: %s\n", strerror (errno));
|
|
exit (1);
|
|
}
|
|
|
|
gettimeofday (&starttv, NULL);
|
|
|
|
/*fprintf (stderr, "%d\n", sizeof (mega_host_config));*/
|
|
|
|
while (1)
|
|
{
|
|
pid_t w;
|
|
int st;
|
|
|
|
w = waitpid (pid, &st, /*WNOHANG*/ 0);
|
|
|
|
if (w == 0)
|
|
{
|
|
fprintf (stderr, "wait: no child\n");
|
|
usleep (10);
|
|
continue;
|
|
}
|
|
|
|
if (w < 0)
|
|
{
|
|
fprintf (stderr, "wait: %s\n", strerror (errno));
|
|
exit (1);
|
|
}
|
|
|
|
if (WIFEXITED (st))
|
|
{
|
|
if (printproc)
|
|
fprintf (stderr, "child exited with status %d\n", WEXITSTATUS (st));
|
|
break;
|
|
}
|
|
|
|
if (WIFSTOPPED (st))
|
|
{
|
|
if (WSTOPSIG (st) == SIGTRAP)
|
|
{
|
|
struct timeval tv;
|
|
long secs;
|
|
long usecs;
|
|
char tbuf[1024];
|
|
int printcalls = (getenv ("LOG_CALLS") != NULL);
|
|
int printthis = 0;
|
|
int printregs = (getenv ("LOG_REGS") != NULL);
|
|
|
|
struct user_regs_struct r;
|
|
u32 call;
|
|
|
|
gettimeofday (&tv, NULL);
|
|
secs = tv.tv_sec - starttv.tv_sec;
|
|
usecs = tv.tv_usec - starttv.tv_usec;
|
|
if (usecs < 0)
|
|
{
|
|
usecs += 1000000;
|
|
secs -= 1;
|
|
}
|
|
snprintf (tbuf, sizeof tbuf, "%ld.%06ld", secs, usecs);
|
|
|
|
if (ptrace (PTRACE_GETREGS, pid, 0, &r) < 0)
|
|
{
|
|
fprintf (stderr, "ptrace:getregs: %s\n", strerror (errno));
|
|
exit (1);
|
|
}
|
|
call = r.orig_eax;
|
|
/*printthis = call == SYS_ioctl;*/
|
|
|
|
if (state == INBOUND)
|
|
{
|
|
if (printcalls || printthis)
|
|
{
|
|
if ((call >= 0) && (call < callmax) && (callinfo[call].name != NULL))
|
|
fprintf (stderr, "%s()", callinfo[call].name);
|
|
else
|
|
fprintf (stderr, "syscall(%u)", call);
|
|
}
|
|
}
|
|
|
|
if (state == OUTBOUND)
|
|
{
|
|
if ((call < 0) || (call > callmax) || (callinfo[call].name == NULL))
|
|
{
|
|
fprintf (stderr, "= 0x%08lx\n", (unsigned long) r.eax);
|
|
}
|
|
else
|
|
{
|
|
if (callinfo[call].ptrval)
|
|
{
|
|
if (printcalls || printthis)
|
|
fprintf (stderr, " = 0x%08lx\n", r.eax);
|
|
}
|
|
else
|
|
{
|
|
long rv = r.eax;
|
|
if (rv < 0)
|
|
{
|
|
if (printcalls || printthis)
|
|
fprintf (stderr, " = -1 (%s)\n", strerror (-rv));
|
|
}
|
|
else
|
|
{
|
|
if (printcalls || printthis)
|
|
fprintf (stderr, " = %lu\n", rv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((call == SYS_ioctl) && ((state == OUTBOUND) || getenv ("LOG_INBOUND")))
|
|
{
|
|
unsigned int len = 16;
|
|
unsigned char buf[65536];
|
|
|
|
unsigned long fd = r.ebx;
|
|
|
|
unsigned long ioc = r.ecx;
|
|
unsigned int iocdir = _IOC_DIR(ioc);
|
|
unsigned char ioctype = _IOC_TYPE(ioc);
|
|
unsigned int iocnr = _IOC_NR(ioc);
|
|
unsigned int iocsize = _IOC_SIZE(ioc);
|
|
char *iocdirname;
|
|
|
|
unsigned long arg = r.edx;
|
|
|
|
switch (iocdir)
|
|
{
|
|
case _IOC_READ: iocdirname = "r"; break;
|
|
case _IOC_WRITE: iocdirname = "w"; break;
|
|
case _IOC_READ|_IOC_WRITE: iocdirname = "rw"; break;
|
|
default: iocdirname = "none"; break;
|
|
}
|
|
|
|
fprintf (stderr, "%s: ioctl(%ld, _IOC(\"%s\",'%c',0x%02x,0x%02x), 0x%08lx)", tbuf, fd, iocdirname, ioctype, iocnr, iocsize, arg);
|
|
if (state == OUTBOUND)
|
|
fprintf (stderr, " = %ld\n", r.eax);
|
|
if (getenv ("LOG_INBOUND"))
|
|
fprintf (stderr, "\n");
|
|
|
|
if (_IOC_SIZE(ioc) > len)
|
|
len = _IOC_SIZE(ioc);
|
|
if (len > sizeof buf)
|
|
len = sizeof buf;
|
|
|
|
if (printregs)
|
|
fprintf (stderr, " ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx ebp=%08lx eax=%08lx ds=%04x __ds=%04x es=%04x __es=%04x fs=%04x __fs=%04x gs=%04x __gs=%04x orig_eax=%08lx eip=%08lx cs=%04x __cs=%04x eflags=%08lx esp=%08lx ss=%04x __ss=%04x\n", r.ebx, r.ecx, r.edx, r.esi, r.edi, r.ebp, r.eax, r.ds, r.__ds, r.es, r.__es, r.fs, r.__fs, r.gs, r.__gs, r.orig_eax, r.eip, r.cs, r.__cs, r.eflags, r.esp, r.ss, r.__ss);
|
|
|
|
copyout (buf, len, pid, r.edx);
|
|
|
|
if ((ioctype == 'm') && (iocnr == 0) && (iocsize == sizeof (struct uioctl_t)))
|
|
{
|
|
/* megaraid_mbox-style ioctl */
|
|
struct uioctl_t *uio = (struct uioctl_t *) buf;
|
|
mraid_passthru_t p = uio->pthru;
|
|
unsigned long datalen;
|
|
int_mbox_t *m = (int_mbox_t *) uio->mbox;
|
|
|
|
fprintf (stderr, " uio:\tinlen %u, outlen %u, { op %02x, subop %02x, adap %04x, buf %08lx, len %u }, data %08lx\n", uio->inlen, uio->outlen, uio->ui.fcs.opcode, uio->ui.fcs.subopcode, uio->ui.fcs.adapno, (unsigned long) uio->ui.fcs.buffer, uio->ui.fcs.length, (unsigned long) uio->data);
|
|
dumpbytes (stderr, uio, sizeof (*uio), uio, "uioc");
|
|
fprintf (stderr, " mbox:\tcmd %02x, cmdid %02x, op %02x, subop %02x, lba %08x, xfer %08x, ldrv %02x, numsge %u, busy %u, nstatus %u, status %u\n", m->cmd, m->cmdid, m->opcode, m->subopcode, m->lba, m->xferaddr, m->logdrv, m->rsvd[0], m->rsvd[2], m->numstatus, m->status);
|
|
dumpbytes (stderr, uio->mbox, sizeof (uio->mbox), uio->mbox, "mbox");
|
|
fprintf (stderr, " pass:\ttimeout %u, ars %u, isldrv %u, ldrv %u, chan %u, targ %02x, qtag %u, qact %u, numsge %u, scsistat %u\n", p.timeout, p.ars, p.islogical, p.logdrv, p.channel, p.target, p.queuetag, p.queueaction, p.numsge, p.scsistatus);
|
|
dumpbytes (stderr, &p.cdb, p.cdblen, &p.cdb, "cdb");
|
|
dumpbytes (stderr, &p.reqsensearea, p.reqsenselen, &p.reqsensearea, "rqsense");
|
|
dumpbytes (stderr, &uio->pthru, sizeof (uio->pthru), &uio->pthru, "pass");
|
|
if (p.dataxferaddr && p.dataxferlen)
|
|
{
|
|
unsigned char data[4096];
|
|
unsigned long len = p.dataxferlen;
|
|
if (len > sizeof data)
|
|
len = sizeof data;
|
|
copyout (data, len, pid, p.dataxferaddr);
|
|
dumpbytes (stderr, data, len, (void *) p.dataxferaddr, "parm");
|
|
}
|
|
|
|
datalen = uio->ui.fcs.length;
|
|
if (datalen < uio->outlen)
|
|
datalen = uio->outlen;
|
|
if (datalen > sizeof buf)
|
|
datalen = sizeof buf;
|
|
if (datalen < 16)
|
|
datalen = 16;
|
|
if (datalen && uio->data)
|
|
{
|
|
// if ((state == INBOUND) && (datalen > uio->inlen))
|
|
// datalen = uio->inlen;
|
|
// else if ((state == OUTBOUND) && (datalen > uio->outlen))
|
|
// datalen = uio->outlen;
|
|
// if (datalen)
|
|
// {
|
|
copyout (buf, datalen, pid, (unsigned long) uio->data);
|
|
dumpbytes (stderr, buf, datalen, uio->data, "data");
|
|
// }
|
|
}
|
|
}
|
|
else if ((ioctype == 'M') && (iocnr == 1) && (iocsize == sizeof (struct megasas_iocpacket)))
|
|
{
|
|
/* megaraid_sas-style ioctl */
|
|
struct megasas_iocpacket *iocp = (struct megasas_iocpacket *) buf;
|
|
struct megasas_header *mh = &(iocp->frame.hdr);
|
|
int k;
|
|
u8 cmd;
|
|
struct megasas_sge32 *sge = (struct megasas_sge32 *) ((u32) (&iocp->frame) + iocp->sgl_off);
|
|
int log = 1;
|
|
|
|
cmd = mh->cmd;
|
|
if (cmd == MFI_CMD_DCMD)
|
|
{
|
|
struct megasas_dcmd_frame *f = (struct megasas_dcmd_frame *) mh;
|
|
|
|
if ((getenv ("LOG_OPCODE") != 0) && (strtoul (getenv ("LOG_OPCODE"), NULL, 0) != f->opcode))
|
|
{
|
|
log = 0;
|
|
}
|
|
|
|
/* Lie like a rug. */
|
|
if ((f->opcode == 0x02020000) && (state == OUTBOUND))
|
|
{
|
|
u32 sgbase = sge[0].phys_addr;
|
|
size_t sglen = sge[0].length;
|
|
struct mega_physical_disk_info_sas disk;
|
|
|
|
if (sglen > sizeof disk)
|
|
sglen = sizeof disk;
|
|
|
|
copyout (&disk, sglen, pid, sgbase);
|
|
|
|
//if (!((disk.device_id == 16) || (disk.device_id == 15)))
|
|
// log = 0;
|
|
|
|
copyin (&disk, sglen, pid, sgbase);
|
|
}
|
|
|
|
if ((f->opcode == 0x04010000) && (state == OUTBOUND))
|
|
{
|
|
u32 sgbase = sge[0].phys_addr;
|
|
size_t sglen = sge[0].length;
|
|
u8 config[4096];
|
|
|
|
if (sglen > sizeof config)
|
|
sglen = sizeof config;
|
|
|
|
copyout (&config, sglen, pid, sgbase);
|
|
|
|
{
|
|
struct mega_array_header_sas *header = (struct mega_array_header_sas *) config;
|
|
struct mega_array_span_def_sas *span = (struct mega_array_span_def_sas *) (header + 1);
|
|
struct mega_array_disk_def_sas *disk = (struct mega_array_disk_def_sas *) (span + header->num_span_defs);
|
|
struct mega_array_hotspare_def_sas *hotspare = (struct mega_array_hotspare_def_sas *) (disk + header->num_disk_defs);
|
|
int k;
|
|
|
|
for (k = 0; k < header->num_disk_defs; ++k)
|
|
{
|
|
// disk[k].state = 0xffff;
|
|
}
|
|
}
|
|
|
|
copyin (&config, sglen, pid, sgbase);
|
|
}
|
|
|
|
if ((f->opcode == 0x05010000) && (state == OUTBOUND))
|
|
{
|
|
u32 sgbase = sge[0].phys_addr;
|
|
size_t sglen = sge[0].length;
|
|
struct mega_battery_state_sas bs;
|
|
|
|
if (sglen > sizeof bs)
|
|
sglen = sizeof bs;
|
|
|
|
copyout (&bs, sglen, pid, sgbase);
|
|
|
|
if (getenv ("BATTERY_X"))
|
|
bs.type = strtoul (getenv ("BATTERY_X"), NULL, 0);
|
|
if (getenv ("BATTERY_Y"))
|
|
bs.foo = strtoul (getenv ("BATTERY_Y"), NULL, 0);
|
|
bs.over_charged = 1;
|
|
|
|
copyin (&bs, sglen, pid, sgbase);
|
|
}
|
|
|
|
if (log)
|
|
{
|
|
fprintf (stderr, " host %d, off 0x%04x, count %d, sense_off 0x%08x, sense_len 0x%08x\n", iocp->host_no, iocp->sgl_off, iocp->sge_count, iocp->sense_off, iocp->sense_len);
|
|
fprintf (stderr, " DCMD: cmd_status %d, sge_count %d, context 0x%08x, flags 0x%04x, timeout %d, data_xfer_len 0x%x, opcode 0x%08x\n", f->cmd_status, f->sge_count, f->context, f->flags, f->timeout, f->data_xfer_len, f->opcode);
|
|
dumpbytes (stderr, &f->mbox, sizeof (f->mbox), &f->mbox, "mbox");
|
|
}
|
|
}
|
|
else if (cmd == MFI_CMD_PD_SCSI_IO)
|
|
{
|
|
struct megasas_pthru_frame *f = (struct megasas_pthru_frame *) mh;
|
|
|
|
fprintf (stderr, " host %d, off 0x%04x, count %d, sense_off 0x%08x, sense_len 0x%08x\n", iocp->host_no, iocp->sgl_off, iocp->sge_count, iocp->sense_off, iocp->sense_len);
|
|
fprintf (stderr, " SCSI: cmd_status %d, sge_count %d, context 0x%08x, flags 0x%04x, timeout %d, data_xfer_len 0x%x\n", f->cmd_status, f->sge_count, f->context, f->flags, f->timeout, f->data_xfer_len);
|
|
fprintf (stderr, " : scsi_status %d, target_id 0x%02x, lun %d, sense_len %d, sense_lo 0x%08x, sense_hi 0x%08x\n", f->scsi_status, f->target_id, f->lun, f->sense_len, f->sense_buf_phys_addr_lo, f->sense_buf_phys_addr_hi);
|
|
|
|
dumpbytes (stderr, &f->cdb, f->cdb_len, &f->cdb, "cdb");
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, " host %d, off 0x%04x, count %d, sense_off 0x%08x, sense_len 0x%08x\n", iocp->host_no, iocp->sgl_off, iocp->sge_count, iocp->sense_off, iocp->sense_len);
|
|
dumpbytes (stderr, buf, len, (void *) r.edx, NULL);
|
|
}
|
|
if (log)
|
|
{
|
|
for (k = 0; k < iocp->sge_count; ++k)
|
|
{
|
|
u32 sgbase = sge[k].phys_addr;
|
|
size_t sglen = sge[k].length;
|
|
char sgname[16];
|
|
char sgdata[4096];
|
|
|
|
snprintf (sgname, sizeof sgname, "sg%d", k);
|
|
fprintf (stderr, " %s at 0x%08x [0x%x]:\n", sgname, sgbase, sglen);
|
|
if (sglen > sizeof sgdata)
|
|
sglen = sizeof sgdata;
|
|
copyout (sgdata, sglen, pid, sgbase);
|
|
dumpbytes (stderr, sgdata, sglen, (void *) sgbase, sgname);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dumpbytes (stderr, buf, len, (void *) r.edx, NULL);
|
|
}
|
|
fprintf (stderr, "\n");
|
|
}
|
|
|
|
switch (state)
|
|
{
|
|
static u32 lasteip = 0;
|
|
|
|
case UNTRACED:
|
|
/* We don't know whether we were inbound or outbound on the first signal; this
|
|
appears to differ between kernels. So we defer until we see the same eip in
|
|
two successive traps, at which point we know we were outbound, so the next
|
|
trap is inbound. */
|
|
if (lasteip == r.eip)
|
|
state = INBOUND;
|
|
lasteip = r.eip;
|
|
break;
|
|
case INBOUND: state = OUTBOUND; break;
|
|
case OUTBOUND: state = INBOUND; break;
|
|
}
|
|
|
|
if (ptrace (PTRACE_SYSCALL, pid, 0, 0) < 0)
|
|
{
|
|
fprintf (stderr, "ptrace:syscall: %s\n", strerror (errno));
|
|
exit (1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "running\n");
|
|
}
|
|
}
|
|
|
|
exit (0);
|
|
}
|
|
|
|
|