Core files ... it works

Larry McVoy lm at arizona.edu
Thu Jun 30 08:28:00 AEST 1988


In article <797 at scubed.UUCP> warner at scubed.UUCP (Ken Warner) writes:
>The next thing I want to do is save the stack and registers at a particular, 
>known state and restore the stack and registers on start-up.  I know this will
>take some start-up code.  
>
>My questions: 
>
>Where is the top of the stack, I know it starts at some high-memory location 
>and grows down.  How and where can a program find out the bounds of the stack.
>
>How can one get the contents of the registers, including the pc, fp and sp
>registers?  
>
>How can these be restored?

I had to do this for a thread library that I wrote for the Vax (Mt Xinu, 4.3).
I've attached the code below.  You should be able to see how to do what you want
from this code (on a vax).   If you do get a Sun version working I'd like a
copy...

Basically, you call the startup routine from main, Tfork() duplicates the
current stack (all the way back to the top return address which is exit(), I
believe), and Tswitch() saves state (regs), and starts you up on the new
stack.  This library has been used a little, I'd be happy to give the whole
thing to anyone that wants it (contact me at laidbak!lm at sun.com - this
account will be dead soon).

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# README Tasm.S Tfork.c Tstack.c Tstartup.c Tswitch.c

echo x - README
cat > "README" << '//E*O*F README//'
TODO:
    The memory management is screwed.  I can't use malloc because malloc
    imbeds info into free blocks and I can overrun a stack into the free
    stuff (causes confusing core dumps in malloc).  I'm currently statically
    allocating but that is gross because it uses tons of mem.  I really need
    kernel supported stacks (plural).

    Rework the run q?  The doubly linked list shit is error prone.

    Preemptive scheduling?  Could use itimer but user level shit uses that.

FILES
-----
tests.d
    a bunch of sample tests live here

T.h
    definitions of proc structs and globals
Tasm.S
    startup, switch, and fork live here
Texit.c
    code for doing an exit.  Surprisingly complex;  I needed to be able
    to tell which process runs next, hence Trunnext.  I needed to have
    a place to store the previous info (for switch) hence junk (I think).
    In short, exit has its grubby paws all over the place.
Tf77.c
    Stubs to make fortran work.  Not well tested.
Tfork.c
    Actually Tprefork.  This is the C code called from the asm version
    of fork.  It does all the non-assembler stuff, like mucking about
    with proc structs, etc.
Tgetpid.c
    thread library process id.
Tpanic.c
    error message and quit.
Tps.c
    debugging. Defines Tps() and Trunq().
Tspace.c
    definitions for globals.
Tstack.c
    rethreading of the copied stack.  called on each fork.  gross code,
    but it works.
Tstartup.c
    actually Tstartup2.  called from asm version to initialize the first
    proc structure.
Tswitch.c
    actually Tpreswitch.  called from asm version for C stuff.
Twait.c
    an attempt (pathetic one, I admit) at wait(2).  I need this since I can't
    let processes die until someone out there wants them to.  I really out
    to have a sigchild.
//E*O*F README//

echo x - Tasm.S
cat > "Tasm.S" << '//E*O*F Tasm.S//'
/*
 * copyright (c) 1988 by Larry McVoy
 */

/*
 * Tstartup 
 *	find the bottom of the stack, save it's location;
 *	the best way to find the location of the beginning of
 *	the stack is to
 *		get main's ap
 *		dereference to get # of args
 *		add that # + 1 to get top
 *	must be called from **main** before any Tfork calls.
 *	Calls Tinit() for process table setup, etc.
 */
	.data
	.align	1
	.comm	_Tchildid,4
	.text
	.globl	_Tstartup
	.globl	_Tnext
	.globl	_tnext_		/* for f77 */
	.globl	_Tfork
	.globl	_tfork_		/* for f77 */
	.globl	_Tswitch
	.globl	_tswitch_	/* for f77 */
	.align	1
_Tstartup:
	.word	0
	moval	_Tproc,r0		/* initialize state for proc 0 */
	movl	fp,(r0)+		/* save fp in Tproc.p_sp */
	movl	$0,(r0)			/* true switch, not a fork */
	movl	8(fp),r0		/* get main's ap */
	movl	$1,r1			/* 1 word for the N value */
	addl2	(r0),r1			/* get offset past args in r1 */
	mull2	$4,r1			/* get byte offset */
	addl2	r0,r1			/* r1 has address of beginning */
	pushl	12(fp)			/* save old fp */
	pushl	r1			/* and stack bottom */
	calls	$2,_Tinit		/* call Tinit(stkbottom, firstframe) */
	ret
/*
 * Tfork
 *     Save state in the parent's proc struct,
 *     pass info to Tstack so it can do its' thing,
 */
	.align	1
_tfork_:	/* for f77 */
_Tfork:
	.word	0xfc0
/*
 * set things up for the fork.  Tprefork will point Tprevproc at child.
 * Note that Tprefork is called from here because we don't want 
 * two call frames on the stack (which would happen if the fork
 * called us instead of this way).
 */
	calls	$0,_Tprefork
	movl	r0,_Tchildid
/*
 * allocate and rethread new stack
 */
	pushl	fp
	calls	$1,_Tstack
/*
 * save state for the child
 */
	movl	_Tprevproc,r1
	movl	r0,(r1)+		/* store fp returned by Tstack */
	movl	$0,(r1)			/* child is not forking */
	movl	_Tchildid,r0		/* child's Tid for the parent */
	ret
/*
 * Tswitch - the assembler stuff to make the switch work.
 *
 * save state and switch stacks.
 */
	.align	1
_Tnext:
_tnext_:	/* for f77 */
	.word	0xfc0
	pushl	$-1		/* T_RNDROBIN */
	jbr	callpre

_tswitch_:	/* for f77 */
	.word	0xfc0
	pushl	*4(ap)
	jbr	callpre

_Tswitch:
	.word	0xfc0
/*
 * set things up for the switch.
 * As above, we do most of the work in C, relying on Tpreswitch to
 * give us the current process in Tprevproc and the next one in
 * Tcurproc.  Scheduling and all associated muck is done in Tpreswitch.
 */
	pushl	4(ap)
callpre:
	calls	$1,_Tpreswitch
/*
 * save state; note that the .word 0xfc0 saved the registers used by C.
 */
	movl	_Tprevproc,r0
	movl	fp,(r0)+
	movl	$0,(r0)			/* true switch, not a fork */
/*
 * restore state.
 */
	movl	_Tcurproc,r0
	movl	(r0)+,fp		/* set up stack */
	movl	(r0),r0			/* return value; might be a pid */
	ret
//E*O*F Tasm.S//

echo x - Tfork.c
cat > "Tfork.c" << '//E*O*F Tfork.c//'
static char* copyright = "copyright (c) 1988 by Larry McVoy";
# include	"T.h"
/*
 * Tfork() - create a new thread of control
 *
 * part of the job is to add the new thread to the end of the runq.
 * the current method leaves the parent at the front; unfair.
 */
Tprefork()
{
    register i;
    static last = 0;
    register proc* p;
    register proc* lastp;

/*
 * search the whole process table for a slot.
 */
    for (i=0; i<T_MAXPROC; ++i) {
	last = (last + 1) % T_MAXPROC;
	if (Tproc[last].p_pid == T_UNUSED) {
	    p = &Tproc[last];
	    goto gotone;
	}
    }
    Tpanic("T: can't fork, out of process table space.");

gotone:
/* 
 * patch up the runq:
 *   before: 1->2->3->(back to 1)
 *   after:  1->2->3->4->(back to 1)
 * (imagine the backpointers, it's too hard to draw)
 */
    if (Trunning == 1) {
	Tcurproc->p_next = Tcurproc->p_prev = p;
	p->p_next = p->p_prev = Tcurproc;
    }
    else {
	p->p_next = Tcurproc;
	p->p_prev = lastp = Tcurproc->p_prev;
	lastp->p_next = Tcurproc->p_prev = p;
    }
    Tprevproc = p;
    p->p_pid = last;
    Trunning++;
    debug((stderr, "Tfork() threads=%u prev=%u cur=%u\n", 
	Trunning, Tprevproc->p_pid, Tcurproc->p_pid));
    debugTps();
    return last;
}
//E*O*F Tfork.c//

echo x - Tstack.c
cat > "Tstack.c" << '//E*O*F Tstack.c//'
static char* copyright = "copyright (c) 1988 by Larry McVoy";
# include	"T.h"
/*
 * Tstack - set up the new stack
 *
 * 	check for enough space after copying the current stack,
 *	copy the stack,
 *	rethread the frame and arg pointers,
 *	return the location of the frame pointer to the new stack.
 *
 *	Note: this code assumes that stacks grow down (also vaxdeps?)
 *
 *	This is worth a little doc.  The stack frame looks like
 *
 *	main	[ arg n		]
 *		[ ....		]
 *	ap->	[ # of args	]
 *		[ regs		]
 *		[ ....		]
 *		[ old pc	]
 *		[ old fp	]
 *		[ old ap	]
 *		[ junk		]
 *	fp->	[ 0		]
 *	sub1	[ arg n		]
 *		[ ....		]
 *
 *	I have the fp/ap pointing at main's; that's not true.  What I really
 *	have is p_stkbgn, which points just above main, and p_1stfrm, which
 *	points where I have fp drawn.
 *
 *	"mysize" is represents the stack size from "here" to the start of
 *	the stack.  The arg "fp" is the *current* frame pointer, so I can
 *	get mysize from p_stkbgn and fp.
 *
 *	Setting p_1stfrm is a little weird (it's done by hand for the first
 *	stack in Tinit()).  What I do is use the offset from the current
 *	versions to calculate the new versions (since p_stkbgn is easy).
 *
 *	In rethread, I work back up the stack until I hit p_1stfrm, which is 
 *	the only frame I *don't* have to rethread (it points to exit()).
 */
    int*
Tstack(fp)
    register char* fp;
{
    char* malloc();
    register mysize;
    register char* newstk;
    register char* newfp;
    register proc* kid = Tprevproc;
    register proc* cur = Tcurproc;

    mysize = (char*)cur->p_stkbgn - fp;
    newstk = Tstacks[kid->p_pid];
    newfp = newstk + T_STK_SIZE - mysize;

    debug((stderr, "Tstack mysize=%u fp=%#x newstk=%#x\n", 
	mysize, fp, newstk));

    kid->p_stack = newstk;
    kid->p_stkbgn = (int*)(newstk + T_STK_SIZE);
    kid->p_1stfrm = kid->p_stkbgn - (cur->p_stkbgn - cur->p_1stfrm);
    bcopy(T_SENTINEL, newstk, sizeof(T_SENTINEL));
    bcopy(fp, newfp, mysize);
    rethread(fp, newfp, cur->p_1stfrm);
    return (int*)newfp;
}

    static 
rethread(fp, newfp, firstframe)
    register int* fp;
    register int* newfp;
    register int* firstframe;
{
    register offset;

    debug((stderr, "rethread: fp=%#x nfp=%#x quit=%#x\n", 
	fp, newfp, firstframe));

    while (fp < firstframe) {
	fp += 3;
	newfp += 3;
	offset = (int*)*fp - fp;
  	debug((stderr, 
	    "rethread: fp=%#x oap=%#x shit=%#x ofp=%#x offset=%#x words\n", 
	    fp, *(newfp-1), *(newfp-2), *newfp, offset));
	*newfp = (int)(newfp + offset);
	--fp, --newfp;
	offset = (int*)*fp - fp;
	*newfp = (int)(newfp + offset);
	fp = (int*)*(fp+1);
	newfp = (int*)*(newfp+1);
    }
}
//E*O*F Tstack.c//

echo x - Tstartup.c
cat > "Tstartup.c" << '//E*O*F Tstartup.c//'
static char* copyright = "copyright (c) 1988 by Larry McVoy";
# include	"T.h"
/*
 * Tinit - fill out the first process table entry, 
 * general initialization,
 * called from Tstartup().
 */
Tinit(stkbottom, firstframe)
    int* stkbottom;
    int* firstframe;
{
    register i;
    register proc* p = Tproc;
    struct rlimit rl;

    debug((stderr, "Tinit()\n"));
# ifdef T_DEBUG
    setbuf(stdout, 0);
# endif

    for (i=1; i<T_MAXPROC; ++i)
	Tproc[i].p_pid = T_UNUSED;
    p->p_pid = 0;
    p->p_stack = 0;	/* special thread, needs no stack check */
    p->p_next = p->p_prev = p;
    p->p_ran = 1;
    p->p_stkbgn = stkbottom;
    p->p_1stfrm = firstframe;
    Trunning = 1;
    Tmax = T_MAXPROC;
    Tcurproc = p;
    debugTps();
}
//E*O*F Tstartup.c//

echo x - Tswitch.c
cat > "Tswitch.c" << '//E*O*F Tswitch.c//'
static char* copyright = "copyright (c) 1988 by Larry McVoy";
# include	"T.h"
/*
 * Tpreswitch - release control
 *
 * if entered with a valid thread id then run that thread
 */
Tpreswitch(Tid)
{
    register proc* p;
    static proc    junk;
    
    debug((stderr, "Tswitch(%d)\n", Tid));
/*
 * check the stack sentinal.  
 * The first process uses the unix stack; needs no check.
 */
    if (Tcurproc->p_stack) {
	if (bcmp(Tcurproc->p_stack, T_SENTINEL, sizeof(T_SENTINEL)))
	    Tpanic("stack sentinal overwritten");
    }
/*
 * Grab the previous proc while we know where it is.
 * This is a bit strange because we can be switching or 
 * returning control from an exiting thread.  If it's an 
 * exit then we don't want to save state. Tpid tells us which.
 */
    if (Tpid == Tcurproc->p_pid) 	/* true switch */
	Tprevproc = Tcurproc;
    else
	Tprevproc = &junk;		/* switching from exit() */
/*
 * figure out who's next.  
 */
    Trunnext = -1;
    if (Tid == T_RNDROBIN) 
rndrobin:
	Tcurproc = p = Tcurproc->p_next;
    else {
	if ((p = &Tproc[Tid])->p_pid == Tid) 
	    Tcurproc = p;
	else {
	    char buf[100];
	    sprintf(buf, "Tswitch(%d): thread not on run q", Tid);
	    Terror(buf);
	    goto rndrobin;
	}
    }
# ifdef T_DEBUG
    debug((stderr, "Tpreswitch: pid=%u next=%u fp=%#x prev=%u cur=%u\n", 
	Tpid, p->p_pid, p->p_sp, Tprevproc->p_pid, Tcurproc->p_pid));
    { register int* fp = p->p_sp;
      fprintf(stderr, "frame=%#x %#x %#x %#x %#x\n",
	*fp, *(fp+1), *(fp+2), *(fp+3), *(fp+4));
    }
# endif
    Tpid = p->p_pid;
    p->p_ran++;
    return;
}
//E*O*F Tswitch.c//

echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/shar$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
      51     277    1787 README
     112     497    2695 Tasm.S
      51     188    1213 Tfork.c
      95     434    2654 Tstack.c
      33     104     739 Tstartup.c
      60     230    1576 Tswitch.c
     402    1730   10664 total
!!!
wc  README Tasm.S Tfork.c Tstack.c Tstartup.c Tswitch.c | sed 's=[^ ]*/==' | diff -b $temp -
exit 0
-- 
Larry McVoy	laidbak!lm at sun.com	1-800-LAI-UNIX x286



More information about the Comp.unix.questions mailing list