/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: gcore.c,v 1.10.2.1 92/06/24 13:43:16 marker Exp $"

/*
 * gcore - get core images of running processes
 *
 * Author: Girish Goyal
 *
 * This code uses /proc file system (or interface) to obtain information 
 * about running processes and generate a core dump.
 */

#include <stdio.h>
#include <nlist.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/pcb.h>
#include <sys/sbd.h>
#include <sys/immu.h>
#include <sys/region.h>
#include <sys/signal.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/fs/prfcntl.h>
#include <sys/procfs.h>
#include <sys/message.h>
#include <sys/dso_core.h>
#include <sys/reg.h>
#include <sys/regset.h>
#include <sys/fcntl.h>

/*
 * Create a core image on the file "core"
 */

#undef	USIZE
#define	SEGBUF	4096
long	psegs[SEGBUF];
int	npsegs;

/* the dso core file structures
 */
core_header_t	*core_header;		/* the core file header */
core_regts_t	*core_registers;	/* the core file registers */
core_regns_t	*core_regions;		/* region info header */

unsigned	page_size;
extern int	getpagesize();

union userdata {
	struct user user;
	char upages[8192];
} *userp;
#define u	userp->user
#define uarea	userp->upages


char	errmsg[256];
char	fname[256];
char	*coref = NULL;

int	errflg;
int	debug;
char	*corepath = "core";

main(argc, argv)
	int argc;
	char **argv;
{
	int	fd;			/* input (/proc file) descriptor */
	int	ofd;			/* output (core file) descriptor */
	struct proc p;
	register int i;
	int 	pid;

	extern int	optind;
	extern int getopt();
	extern char *optarg;
	int	c;




	/* Process all options */
	while ((c = getopt(argc, argv, "#p:")) != EOF) {
	    switch(c) {
		case 'p':   corepath = optarg;
			    break;

		case '#':   debug++;
			    break;

		default:    errflg++;
	    }
	}
	if (errflg || optind >= argc) {
		printf("Usage: gcore [-p path] process-id ...\n", argv[0]);
		exit(1);
	}

	page_size = getpagesize();

	/* allocate memory to form core file names */
	if ((coref = (char *)malloc(strlen(corepath)+64)) == NULL) {
		perror("Can't allocate memory for core file name.\n");
		exit(1);
	}

	for (i = optind; i < argc; i++) {
		printf("%s:\t", argv[i]);
		fflush(stdout);
		if ((pid = atoi(argv[i])) < 0) {
		    fprintf(stderr, "Invalid process-id.\n");
		    continue;
		}
		sprintf(fname, "/proc/%d", pid);
		if ((fd = open(fname,O_RDWR)) < 0) {
			perror(fname);
			continue;
		}
		if (ioctl(fd, PIOCGETPR, &p) == -1) {
		    perror("Can't read proc structure (PIOCGETPR)");
		    close(fd);
		    continue;
		}
		if (p.p_flag & SSYS) {
		    fprintf(stderr, "System process.\n");
		    close(fd);
		    continue;
		}
		if (p.p_stat == SZOMB) {
		    fprintf(stderr, "Zombie process.\n");
		    close(fd);
		    continue;
		}

		sprintf(coref, "%s.%d", corepath, pid);
		if ((ofd = creat(coref, 0644)) < 0) {
		    sprintf(errmsg,"Can't create %s ",coref);
		    perror(errmsg);
		    close(fd);
		    continue;
		}
		if (debug)
			printf("Dumping to %s\n", coref);
		if (mkcore(fd, ofd) >= 0) {
			printf("Dumped to %s\n", coref);
			fflush(stdout);
		}
		close(fd);
		close(ofd);
	}
	exit(0);
}


#undef	USIZE
#undef	ctob
#undef	btoc
#undef	poff

#define	btoc(x)		((x + page_size - 1)/page_size)
#define	ctob(x)		(x * page_size)
#define	poff(x)		((unsigned)x % page_size)

mkcore(fd, ofd)
int	fd;
int	ofd;
{
	struct pfcseg 	useg;
	int 		tot_reg_size = 0;
	int 		running_total;
	core_regns_t	*rp;
	int	i;
	prmap_t	*ppmap,*prmap = (prmap_t *)&psegs[0];
	gregset_t	cregs;
	prstatus_t	prstatus;
	prrun_t		prrun;

	bzero(&prrun,sizeof(prrun));

	/* stop the process to snapshot it */
	if (ioctl(fd, PIOCSTOP,&prstatus) == -1) {
		perror("Can't stop process (PIOCSTOP)");
		return (-1);
	}
	if (ioctl(fd, PIOCWSTOP,&prstatus) == -1) {
		perror("Can't stop process (PIOCWSTOP)");
		return (-1);
	}
	
	/* First of all, get the memory map info */
	if (ioctl(fd, PIOCNMAP, (int)&npsegs) == -1 || npsegs <=0 ) {
		perror("gcore: can't get memory map info (PIOCNMAP)");
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}

	/* check if we can read all the memory segments */
	if (npsegs >= (sizeof(psegs)/sizeof(prmap_t))) {
		fprintf(stderr,"gcore: too many memory maps\n");
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

	/* read memory map */
	if (ioctl(fd, PIOCMAP, prmap) == -1) {
		perror("gcore: can't get memory map (PIOCMAP)\n");
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

	/* Get uarea info */
	if (fcntl(fd, PFCGETUSEG, &useg) == -1) {
		perror("Can't get uarea info (FCGETUSEG)");
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}

	/* read registers */
	if (ioctl(fd, PIOCGREG, &cregs[0]) == -1) {
		perror("Can't read registers (PIOCGREG)");
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}

	if ((userp = (union userdata *)malloc(useg.seg_size)) == NULL) { 
		perror("Can't allocate #d bytes for uarea.\n",
			useg.seg_size);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}
	/* Read u-area first */
	if (lseek(fd, (long)useg.seg_vaddr, 0) == -1L 
	    || read(fd, uarea, useg.seg_size) != useg.seg_size) {
		sprintf(errmsg, "Can't lseek/read uarea ");
		perror(errmsg);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

        /* start to fill in the core header
         */
        if ((core_header = (core_header_t *)malloc(sizeof(core_header_t)))
		== NULL) {
		perror("Can't allocate %d bytes for core header.\n",
			sizeof(core_header_t));
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}
        if ((core_registers = (core_regts_t *)malloc(sizeof(core_regts_t)))
		== NULL) {
		perror("Can't allocate %d bytes for core registers.\n",
			sizeof(core_regts_t));
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN, &prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}
        strcpy( core_header->magic,"CORE" );
        core_header->version = 1;
        core_header->nbpp = getpagesize();
        core_header->register_offset = sizeof(struct core_header);
        core_header->region_offset = core_header->register_offset +
                                        sizeof(struct core_registers);
        strcpy( core_header->name,u.u_comm );
        core_header->signo = u.u_arg[0];
        core_header->usize = sizeof( struct user );
        core_header->uincluded = 1;

        /* the register struct gets the pcb from the uarea and
         * the exception frame from the kernel stack
         */
        core_registers->tid = -1;
        memcpy((caddr_t)&(core_registers->pcb),(caddr_t)&(u.u_pcb),
							sizeof(pcb_t));
	mapregs( core_registers->ef,cregs );

	/* calculate where the uarea will be
	 */
	ppmap = prmap;
	for (i=0; i < npsegs; i++,ppmap++ ) 
	    if ( (ppmap->pr_mflags & MA_WRITE) && 
				!(ppmap->pr_mflags & MA_SHARED) ) 
		tot_reg_size += ppmap->pr_size;

        core_header->num_regions = npsegs;
        core_header->uoffset = core_header->region_offset +
                                (npsegs * sizeof(struct core_region)) +
                                tot_reg_size;

        /* malloc the region info header */
        if ((core_regions = 
	    (core_regns_t *)malloc(npsegs * sizeof(core_regns_t))) == NULL){
		perror("Can't allocate %d bytes for core regions.\n",
			npsegs * sizeof(core_regns_t));
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN,&prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return (-1);
	}
        /* fill in the region info headers */
        running_total = sizeof(struct core_header) +
                                sizeof(struct core_registers) +
                                (npsegs * sizeof(struct core_region));
	rp = core_regions;
	ppmap = prmap;
	for(i = 0; i < npsegs; i++,ppmap++,rp++ ) {
	    if ( (ppmap->pr_mflags & MA_WRITE) && 
				!(ppmap->pr_mflags & MA_SHARED) ) {
		    rp->included = 1;
		    rp->offset = running_total;

		    rp->type = (ppmap->pr_mflags & MA_STACK) ? CORE_STACK
								: CORE_DATA;
		    rp->vaddr = ppmap->pr_vaddr;
		    rp->size = ppmap->pr_size;
		    running_total += rp->size;
	    }
	}

	u.u_error = 0;
	u.u_syscall = DUCOREDUMP;

	/* Write core header */
	if (debug)
	    printf("\twriting core header (0x%x bytes) to offset: 0\n",
		sizeof(core_header_t));
	if (write(ofd, core_header, sizeof(core_header_t)) != 
						sizeof(core_header_t)) {
		sprintf(errmsg, "Can't write core header ");
		perror(errmsg);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN,&prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

	/* Write core registers */
	if (debug)
	    printf("\twriting core registers (0x%x bytes) to offset: 0x%x\n",
		sizeof(core_regts_t),core_header->register_offset);
	if (write(ofd, core_registers, sizeof(core_regts_t)) != 
						sizeof(core_regts_t)) {
		sprintf(errmsg, "Can't write core registers ");
		perror(errmsg);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN,&prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

	/* Write the region info header */
	if (debug)
	    printf("\twriting core region info (0x%x bytes) to offset: 0x%x\n",
		(sizeof(core_regns_t) * npsegs),core_header->region_offset);
	if (write(ofd, core_regions, sizeof(core_regns_t) * npsegs) != 
					sizeof(core_regns_t) * npsegs) {
		sprintf(errmsg, "Can't write core region info ");
		perror(errmsg);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN,&prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

	/* Write the regions */
	for (rp = core_regions,i=0; i < npsegs; i++,++rp ) {
	    if ( rp->included ) {
		if (debug) {
		    printf("core %s: ",
			    (rp->type == CORE_STACK) ? "stack" : "data");
		}
		if (copydata(ofd, rp->offset, fd, rp->vaddr,rp->size) < 0) {
		    perror("Can't write user data ");
		    if (debug)
			printf(errmsg);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl( fd, PIOCRUN,&prrun ) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		    return(-1);
		}
	    }
	}

	/* Write the uarea */
	if (debug)
	    printf("\twriting user area (0x%x bytes) to offset: 0x%x\n",
		core_header->usize, core_header->uoffset);
	if (write(ofd, uarea, useg.seg_size) != useg.seg_size) {
		sprintf(errmsg, "Can't write user area ");
		perror(errmsg);
		prrun.pr_flags &= ~PRSTOP;
		if (ioctl(fd, PIOCRUN,&prrun) == -1) {
			perror("Can't restart process (PIOCRUN)");
		}
		return(-1);
	}

	/* restart the process */
	prrun.pr_flags &= ~PRSTOP;
	if (ioctl( fd,PIOCRUN,&prrun ) == -1) {
		perror("Can't restart process (PIOCRUN)");
		return (-1);
	}
	return(0);
}

/* local buffer to copy data */
char	mybuf[0x80000];

copydata(ofd, offset, fd, addr, size)
caddr_t	addr;
long	offset;
int	ofd, fd, size;
{
	register int	cnt;

	if (debug)
	    printf("\tcopying 0x%x bytes from addr 0x%x to offset: 0x%x\n",
		size, addr, offset);
	while (size > 0) {
	    cnt = (size > sizeof(mybuf)) ? sizeof(mybuf) : size;
	    if (lseek(fd,addr,0) == -1L || (cnt=read(fd,mybuf,cnt)) < 0) {
		sprintf(errmsg,"Can't read 0x%x bytes from addr 0x%x\n", 
			cnt, addr);
		return(-1);
	    }
	    if (lseek(ofd,offset,0) == -1L || write(ofd,mybuf,cnt) != cnt) {
		sprintf(errmsg, "Can't write 0x%x bytes to offset 0x%x\n", 
					    cnt, offset);
		return(-1);
	    }
	    offset += cnt;
	    addr += cnt;
	    size -= cnt;
	}
	return(0);
}

/* map the general register set to the exception frame that dbx expects */
mapregs( ef, rs )
int	*ef;
int	*rs;
{
	ef[EF_AT] = rs[CTX_AT];
	ef[EF_V0] = rs[CTX_V0];
	ef[EF_V1] = rs[CTX_V1];
	ef[EF_A0] = rs[CTX_A0];
	ef[EF_A1] = rs[CTX_A1];
	ef[EF_A2] = rs[CTX_A2];
	ef[EF_A3] = rs[CTX_A3];
	ef[EF_T0] = rs[CTX_T0];
	ef[EF_T1] = rs[CTX_T1];
	ef[EF_T2] = rs[CTX_T2];
	ef[EF_T3] = rs[CTX_T3];
	ef[EF_T4] = rs[CTX_T4];
	ef[EF_T5] = rs[CTX_T5];
	ef[EF_T6] = rs[CTX_T7];
	ef[EF_S0] = rs[CTX_S0];
	ef[EF_S1] = rs[CTX_S1];
	ef[EF_S2] = rs[CTX_S2];
	ef[EF_S3] = rs[CTX_S3];
	ef[EF_S4] = rs[CTX_S4];
	ef[EF_S5] = rs[CTX_S5];
	ef[EF_S6] = rs[CTX_S6];
	ef[EF_S7] = rs[CTX_S7];
	ef[EF_T8] = rs[CTX_T8];
	ef[EF_T9] = rs[CTX_T9];
	ef[EF_K0] = rs[CTX_K0];
	ef[EF_K1] = rs[CTX_K1];
	ef[EF_GP] = rs[CTX_GP];
	ef[EF_SP] = rs[CTX_SP];
	ef[EF_FP] = rs[CTX_S8];
	ef[EF_RA] = rs[CTX_RA];
	ef[EF_MDLO] = rs[CTX_MDLO];
	ef[EF_MDHI] = rs[CTX_MDHI];
	ef[EF_CAUSE] = rs[CTX_CAUSE];
	ef[EF_EPC] = rs[CTX_EPC];
}
