| plan 9 kernel history: overview | file list | diff list |
1997/0520/pc/trap.c (diff list | history)
| pc/trap.c on 1991/0613 | ||
| 1991/0613 | #include "u.h" | |
| 1992/0321 | #include "../port/lib.h" | |
| 1991/0613 | #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" | |
| 1991/0703 | #include "ureg.h" | |
| 1992/0111 | #include "../port/error.h" | |
| 1991/0613 | ||
| 1991/0720 | void noted(Ureg*, ulong); | |
| 1997/0327 | static void debugbpt(Ureg*, void*); static void fault386(Ureg*, void*); static void syscall(Ureg*, void*); | |
| 1991/0731 | ||
| 1997/0327 | static Lock irqctllock; static Irqctl *irqctl[256]; | |
| 1991/0709 | ||
| 1991/0703 | void | |
| 1997/0327 | intrenable(int v, void (*f)(Ureg*, void*), void* a, int tbdf) | |
| 1991/0613 | { | |
| 1997/0327 | Irq * irq; Irqctl *ctl; | |
| 1991/0614 | ||
| 1997/0327 | lock(&irqctllock); if(irqctl[v] == 0){ ctl = xalloc(sizeof(Irqctl)); if(arch->intrenable(v, tbdf, ctl) == -1){ unlock(&irqctllock); /* print("intrenable: didn't find v %d, tbdf 0x%uX\n", v, tbdf); */ xfree(ctl); return; } irqctl[v] = ctl; | |
| 1991/0709 | } | |
| 1997/0327 | ctl = irqctl[v]; irq = xalloc(sizeof(Irq)); irq->f = f; irq->a = a; irq->next = ctl->irq; ctl->irq = irq; unlock(&irqctllock); | |
| 1991/0703 | } | |
| 1997/0405 | static void nmienable(void) { int x; /* * Hack: should be locked with NVRAM access. */ outb(0x70, 0x80); outb(0x70, 0); x = inb(0x61); outb(0x61, 0x08|x); outb(0x61, x & ~0x08); } | |
| 1991/1112 | void | |
| 1991/0703 | trapinit(void) { | |
| 1997/0327 | int v, pri; ulong vaddr; Segdesc *idt; | |
| 1991/0614 | ||
| 1997/0327 | idt = (Segdesc*)IDTADDR; vaddr = (ulong)vectortable; for(v = 0; v < 256; v++){ if(v == VectorBPT || v == VectorSYSCALL) pri = 3; else pri = 0; idt[v].d0 = (vaddr & 0xFFFF)|(KESEL<<16); idt[v].d1 = (vaddr & 0xFFFF0000)|SEGP|SEGPL(pri)|SEGIG; vaddr += 6; } | |
| 1991/0731 | ||
| 1997/0327 | intrenable(VectorBPT, debugbpt, 0, BUSUNKNOWN); intrenable(VectorPF, fault386, 0, BUSUNKNOWN); intrenable(VectorSYSCALL, syscall, 0, BUSUNKNOWN); | |
| 1997/0405 | nmienable(); | |
| 1991/0703 | } | |
| 1994/0722 | char *excname[] = { [0] "divide error", [1] "debug exception", | |
| 1997/0327 | [2] "nonmaskable interrupt", | |
| 1994/0722 | [3] "breakpoint", [4] "overflow", [5] "bounds check", [6] "invalid opcode", [7] "coprocessor not available", [8] "double fault", | |
| 1997/0327 | [9] "coprocessor segment overrun", | |
| 1994/0722 | [10] "invalid TSS", [11] "segment not present", [12] "stack exception", [13] "general protection violation", [14] "page fault", [15] "15 (reserved)", [16] "coprocessor error", | |
| 1994/1029 | [17] "alignment check", | |
| 1997/0327 | [18] "machine check", | |
| 1991/1113 | }; | |
| 1997/0327 | static int nspuriousintr; | |
| 1994/1029 | ||
| 1991/0614 | /* | |
| 1993/1124 | * All traps come here. It is slower to have all traps call trap() rather than | |
| 1993/1115 | * directly vectoring the handler. However, this avoids a lot of code duplication | |
| 1994/0715 | * and possible bugs. trap is called splhi(). | |
| 1991/0614 | */ | |
| 1991/0710 | void | |
| 1997/0327 | trap(Ureg* ureg) | |
| 1991/0614 | { | |
| 1991/1112 | int v, user; | |
| 1991/1113 | char buf[ERRLEN]; | |
| 1997/0327 | Irqctl *ctl; Irq *irq; | |
| 1991/0703 | ||
| 1997/0327 | user = (ureg->cs & 0xFFFF) == UESEL; | |
| 1991/1112 | if(user) | |
| 1997/0327 | up->dbgreg = ureg; | |
| 1991/1112 | ||
| 1997/0327 | v = ureg->trap; if(ctl = irqctl[v]){ if(ctl->isintr) m->intr++; if(ctl->isr) ctl->isr(v); | |
| 1991/1113 | ||
| 1997/0327 | for(irq = ctl->irq; irq; irq = irq->next) irq->f(ureg, irq->a); | |
| 1993/0224 | ||
| 1997/0327 | if(ctl->eoi) ctl->eoi(v); | |
| 1991/0731 | } | |
| 1997/0327 | else if(v <= 16 && user){ spllo(); sprint(buf, "sys: trap: %s", excname[v]); postnote(up, 1, buf, NDebug); } else if(v >= VectorPIC && v <= MaxVectorPIC){ /* * An unknown interrupt. * Check for a default IRQ7. This can happen when * the IRQ input goes away before the acknowledge. * In this case, a 'default IRQ7' is generated, but * the corresponding bit in the ISR isn't set. * In fact, just ignore all such interrupts. */ if(nspuriousintr < 2) print("spurious interrupt %d\n", v-VectorPIC); nspuriousintr++; return; } else{ | |
| 1997/0405 | if(v == 2){ if(m->machno != 0) for(;;); nmienable(); } | |
| 1997/0327 | dumpregs(ureg); if(v < nelem(excname)) panic("%s", excname[v]); panic("unknown trap/intr: %d\n", v); } | |
| 1991/0801 | ||
| 1994/0715 | /* * check user since syscall does its own notifying */ | |
| 1993/0915 | splhi(); | |
| 1997/0327 | if(v != VectorSYSCALL && user && (up->procctl || up->nnote)) notify(ureg); | |
| 1991/0806 | } /* | |
| 1991/0718 | * dump registers */ void | |
| 1997/0327 | dumpregs2(Ureg* ureg) | |
| 1991/0718 | { | |
| 1997/0327 | ureg->cs &= 0xFFFF; ureg->ds &= 0xFFFF; ureg->es &= 0xFFFF; ureg->fs &= 0xFFFF; ureg->gs &= 0xFFFF; | |
| 1993/1115 | ||
| 1993/0915 | if(up) | |
| 1997/0327 | print("cpu%d: registers for %s %d\n", m->machno, up->text, up->pid); | |
| 1991/0718 | else | |
| 1997/0327 | print("cpu%d: registers for kernel\n", m->machno); print("FLAGS=%luX TRAP=%luX ECODE=%luX PC=%luX", ureg->flags, ureg->trap, ureg->ecode, ureg->pc); print(" SS=%4.4luX USP=%luX\n", ureg->ss & 0xFFFF, ureg->usp); print(" AX %8.8luX BX %8.8luX CX %8.8luX DX %8.8luX\n", ureg->ax, ureg->bx, ureg->cx, ureg->dx); print(" SI %8.8luX DI %8.8luX BP %8.8luX\n", ureg->si, ureg->di, ureg->bp); print(" CS %4.4uX DS %4.4uX ES %4.4uX FS %4.4uX GS %4.4uX\n", ureg->cs, ureg->ds, ureg->es, ureg->fs, ureg->gs); | |
| 1991/0718 | } | |
| 1991/0720 | void | |
| 1997/0327 | dumpregs(Ureg* ureg) | |
| 1993/0915 | { | |
| 1994/0813 | extern ulong etext; | |
| 1997/0327 | ulong mca[2], mct[2]; | |
| 1993/1113 | ||
| 1997/0327 | dumpregs2(ureg); | |
| 1994/0813 | ||
| 1997/0327 | /* * Processor control registers. * If machine check exception, time stamp counter, page size extensions or * enhanced virtual 8086 mode extensions are supported, there is a CR4. * If there is a CR4 and machine check extensions, read the machine check * address and machine check type registers if RDMSR supported. */ print(" CR0 %8.8lux CR2 %8.8lux CR3 %8.8lux", getcr0(), getcr2(), getcr3()); if(m->cpuiddx & 0x9A){ print(" CR4 %8.8luX", getcr4()); if((m->cpuiddx & 0xA0) == 0xA0){ rdmsr(0x00, &mca[1], &mca[0]); rdmsr(0x01, &mct[1], &mct[0]); print("\n MCA %8.8luX:%8.8luX MCT %8.8luX", mca[1], mca[0], mct[0]); } } print("\n ur %luX up %luX\n", ureg, up); | |
| 1993/0915 | } void | |
| 1991/0720 | dumpstack(void) { | |
| 1992/0804 | ulong l, v, i; | |
| 1997/0327 | uchar *p; | |
| 1992/0804 | extern ulong etext; | |
| 1993/0915 | if(up == 0) | |
| 1992/0804 | return; i = 0; | |
| 1994/0816 | for(l=(ulong)&l; l<(ulong)(up->kstack+KSTACK); l+=4){ | |
| 1992/0804 | v = *(ulong*)l; if(KTZERO < v && v < (ulong)&etext){ | |
| 1997/0327 | p = (uchar*)v; if(*(p-5) == 0xE8){ print("%lux ", p-5); i++; } | |
| 1992/0804 | } | |
| 1993/1113 | if(i == 8){ | |
| 1992/0804 | i = 0; print("\n"); } } | |
| 1991/0720 | } | |
| 1997/0327 | static void debugbpt(Ureg* ureg, void*) { char buf[ERRLEN]; if(up == 0) panic("kernel bpt"); /* restore pc to instruction that caused the trap */ ureg->pc--; sprint(buf, "sys: breakpoint"); postnote(up, 1, buf, NDebug); } static void fault386(Ureg* ureg, void*) { ulong addr; int read, user, n, insyscall; char buf[ERRLEN]; insyscall = up->insyscall; up->insyscall = 1; addr = getcr2(); read = !(ureg->ecode & 2); user = (ureg->cs&0xffff) == UESEL; spllo(); n = fault(addr, read); if(n < 0){ if(user){ sprint(buf, "sys: trap: fault %s addr=0x%lux", read? "read" : "write", addr); postnote(up, 1, buf, NDebug); return; } dumpregs(ureg); panic("fault: 0x%lux\n", addr); } up->insyscall = insyscall; } | |
| 1991/0718 | /* | |
| 1991/0710 | * system calls */ | |
| 1991/0731 | #include "../port/systab.h" | |
| 1991/0720 | ||
| 1992/0103 | /* | |
| 1994/0715 | * syscall is called splhi() | |
| 1992/0103 | */ | |
| 1997/0327 | static void syscall(Ureg* ureg, void*) | |
| 1991/0710 | { | |
| 1991/0720 | ulong sp; long ret; int i; | |
| 1997/0327 | m->syscall++; | |
| 1993/0915 | up->insyscall = 1; | |
| 1997/0327 | up->pc = ureg->pc; up->dbgreg = ureg; | |
| 1993/1225 | ||
| 1997/0327 | if((ureg->cs)&0xffff == KESEL) | |
| 1991/0720 | panic("recursive system call"); | |
| 1991/0718 | ||
| 1997/0327 | up->scallnr = ureg->ax; | |
| 1993/0915 | if(up->scallnr == RFORK && up->fpstate == FPactive){ | |
| 1992/0805 | /* * so that the child starts out with the | |
| 1994/0715 | * same registers as the parent. * this must be atomic relative to this CPU, hence * the spl's. | |
| 1992/0805 | */ | |
| 1993/0915 | if(up->fpstate == FPactive){ fpsave(&up->fpsave); up->fpstate = FPinactive; | |
| 1992/0805 | } } | |
| 1994/0715 | spllo(); | |
| 1997/0327 | sp = ureg->usp; | |
| 1993/0915 | up->nerrlab = 0; | |
| 1991/0720 | ret = -1; if(!waserror()){ | |
| 1994/0407 | if(up->scallnr >= nsyscall){ | |
| 1997/0327 | pprint("bad sys call number %d pc %lux\n", up->scallnr, ureg->pc); | |
| 1993/1013 | postnote(up, 1, "sys: bad sys call", NDebug); | |
| 1991/0720 | error(Ebadarg); } | |
| 1992/0625 | ||
| 1991/0720 | if(sp<(USTKTOP-BY2PG) || sp>(USTKTOP-(1+MAXSYSARG)*BY2WD)) validaddr(sp, (1+MAXSYSARG)*BY2WD, 0); | |
| 1992/0625 | ||
| 1993/0915 | up->s = *((Sargs*)(sp+1*BY2WD)); up->psstate = sysctab[up->scallnr]; | |
| 1992/0625 | ||
| 1997/0327 | ret = systab[up->scallnr](up->s.args); | |
| 1991/0720 | poperror(); } | |
| 1993/0915 | if(up->nerrlab){ print("bad errstack [%d]: %d extra\n", up->scallnr, up->nerrlab); | |
| 1991/0720 | for(i = 0; i < NERR; i++) | |
| 1993/0915 | print("sp=%lux pc=%lux\n", up->errlab[i].sp, up->errlab[i].pc); | |
| 1991/0720 | panic("error stack"); } | |
| 1991/1114 | ||
| 1993/0915 | up->insyscall = 0; up->psstate = 0; | |
| 1992/0609 | /* * Put return value in frame. On the safari the syscall is * just another trap and the return value from syscall is * ignored. On other machines the return value is put into * the results register by caller of syscall. */ | |
| 1997/0327 | ureg->ax = ret; | |
| 1992/0609 | ||
| 1993/0915 | if(up->scallnr == NOTED) | |
| 1997/0327 | noted(ureg, *(ulong*)(sp+BY2WD)); | |
| 1995/0105 | splhi(); | |
| 1991/1114 | ||
| 1993/0915 | if(up->scallnr!=RFORK && (up->procctl || up->nnote)) | |
| 1997/0327 | notify(ureg); | |
| 1995/0115 | ||
| 1991/0710 | } | |
| 1991/0720 | /* * Call user, if necessary, with note. * Pass user the Ureg struct and the note on his stack. */ | |
| 1992/0108 | int | |
| 1997/0327 | notify(Ureg* ureg) | |
| 1991/0710 | { | |
| 1995/0202 | int l; | |
| 1991/1114 | ulong s, sp; | |
| 1991/1218 | Note *n; | |
| 1991/0720 | ||
| 1993/0915 | if(up->procctl) | |
| 1993/1013 | procctl(up); | |
| 1993/0915 | if(up->nnote == 0) | |
| 1992/0108 | return 0; | |
| 1991/1114 | s = spllo(); | |
| 1993/0915 | qlock(&up->debug); up->notepending = 0; n = &up->note[0]; | |
| 1991/1218 | if(strncmp(n->msg, "sys:", 4) == 0){ l = strlen(n->msg); if(l > ERRLEN-15) /* " pc=0x12345678\0" */ l = ERRLEN-15; | |
| 1997/0327 | sprint(n->msg+l, " pc=0x%.8lux", ureg->pc); | |
| 1991/1218 | } | |
| 1995/0202 | ||
| 1993/0915 | if(n->flag!=NUser && (up->notified || up->notify==0)){ | |
| 1992/0714 | if(n->flag == NDebug) | |
| 1991/1218 | pprint("suicide: %s\n", n->msg); | |
| 1996/0626 | qunlock(&up->debug); | |
| 1991/1218 | pexit(n->msg, n->flag!=NDebug); | |
| 1991/0720 | } | |
| 1995/0202 | if(up->notified) { qunlock(&up->debug); splhi(); return 0; | |
| 1991/0720 | } | |
| 1995/0202 | if(!up->notify){ qunlock(&up->debug); pexit(n->msg, n->flag!=NDebug); } | |
| 1997/0327 | sp = ureg->usp; | |
| 1995/0202 | sp -= sizeof(Ureg); if(!okaddr((ulong)up->notify, 1, 0) | |
| 1995/02021 | || !okaddr(sp-ERRLEN-4*BY2WD, sizeof(Ureg)+ERRLEN+4*BY2WD, 1)){ | |
| 1995/0202 | pprint("suicide: bad address in notify\n"); qunlock(&up->debug); pexit("Suicide", 0); } up->ureg = (void*)sp; | |
| 1997/0327 | memmove((Ureg*)sp, ureg, sizeof(Ureg)); | |
| 1995/0202 | *(Ureg**)(sp-BY2WD) = up->ureg; /* word under Ureg is old up->ureg */ up->ureg = (void*)sp; sp -= BY2WD+ERRLEN; memmove((char*)sp, up->note[0].msg, ERRLEN); sp -= 3*BY2WD; *(ulong*)(sp+2*BY2WD) = sp+3*BY2WD; /* arg 2 is string */ *(ulong*)(sp+1*BY2WD) = (ulong)up->ureg; /* arg 1 is ureg* */ *(ulong*)(sp+0*BY2WD) = 0; /* arg 0 is pc */ | |
| 1997/0327 | ureg->usp = sp; ureg->pc = (ulong)up->notify; | |
| 1995/0202 | up->notified = 1; up->nnote--; memmove(&up->lastnote, &up->note[0], sizeof(Note)); memmove(&up->note[0], &up->note[1], up->nnote*sizeof(Note)); | |
| 1993/0915 | qunlock(&up->debug); | |
| 1991/1114 | splx(s); | |
| 1995/0202 | return 1; | |
| 1991/0710 | } | |
| 1991/0720 | /* * Return user to state before notify() */ | |
| 1991/0710 | void | |
| 1997/0327 | noted(Ureg* ureg, ulong arg0) | |
| 1991/0710 | { | |
| 1997/0327 | Ureg *nureg; | |
| 1995/0202 | ulong oureg, sp; | |
| 1991/0720 | ||
| 1993/0915 | qlock(&up->debug); | |
| 1995/0202 | if(arg0!=NRSTR && !up->notified) { | |
| 1993/0915 | qunlock(&up->debug); | |
| 1994/0513 | pprint("call to noted() when not notified\n"); | |
| 1995/0105 | pexit("Suicide", 0); | |
| 1991/0720 | } | |
| 1993/0915 | up->notified = 0; | |
| 1994/0513 | ||
| 1997/0327 | nureg = up->ureg; /* pointer to user returned Ureg struct */ | |
| 1995/0202 | /* sanity clause */ | |
| 1997/0327 | oureg = (ulong)nureg; | |
| 1995/02021 | if(!okaddr((ulong)oureg-BY2WD, BY2WD+sizeof(Ureg), 0)){ | |
| 1997/0520 | pprint("bad ureg in noted or call to noted when not notified\n"); | |
| 1994/0513 | qunlock(&up->debug); | |
| 1995/0202 | pexit("Suicide", 0); | |
| 1994/0513 | } | |
| 1997/0520 | /* * Check the segment selectors are all valid, otherwise * a fault will be taken on attempting to return to the * user process. */ if(nureg->cs != UESEL || nureg->ss != UDSEL || nureg->ds != UDSEL || nureg->es != UDSEL || nureg->fs != UDSEL || nureg->gs != UDSEL){ pprint("bad segement selector in noted\n"); qunlock(&up->debug); pexit("Suicide", 0); } | |
| 1995/0202 | /* don't let user change system flags */ | |
| 1997/0327 | nureg->flags = (ureg->flags & ~0xCD5) | (nureg->flags & 0xCD5); | |
| 1995/0202 | ||
| 1997/0327 | memmove(ureg, nureg, sizeof(Ureg)); | |
| 1995/0105 | ||
| 1991/0720 | switch(arg0){ case NCONT: | |
| 1995/0202 | case NRSTR: | |
| 1997/0327 | if(!okaddr(nureg->pc, 1, 0) || !okaddr(nureg->usp, BY2WD, 0)){ | |
| 1991/0814 | pprint("suicide: trap in noted\n"); | |
| 1995/0202 | qunlock(&up->debug); | |
| 1994/0513 | pexit("Suicide", 0); | |
| 1991/0814 | } | |
| 1995/0202 | up->ureg = (Ureg*)(*(ulong*)(oureg-BY2WD)); qunlock(&up->debug); | |
| 1995/0105 | break; | |
| 1991/0720 | ||
| 1995/0202 | case NSAVE: | |
| 1997/0327 | if(!okaddr(nureg->pc, BY2WD, 0) || !okaddr(nureg->usp, BY2WD, 0)){ | |
| 1995/0202 | pprint("suicide: trap in noted\n"); qunlock(&up->debug); pexit("Suicide", 0); } qunlock(&up->debug); sp = oureg-4*BY2WD-ERRLEN; splhi(); | |
| 1997/0327 | ureg->sp = sp; | |
| 1995/0202 | ((ulong*)sp)[1] = oureg; /* arg 1 0(FP) is ureg* */ ((ulong*)sp)[0] = 0; /* arg 0 is pc */ break; | |
| 1991/0720 | default: pprint("unknown noted arg 0x%lux\n", arg0); | |
| 1993/0915 | up->lastnote.flag = NDebug; | |
| 1991/0720 | /* fall through */ case NDFLT: | |
| 1993/0915 | if(up->lastnote.flag == NDebug) pprint("suicide: %s\n", up->lastnote.msg); | |
| 1995/0202 | qunlock(&up->debug); | |
| 1993/0915 | pexit(up->lastnote.msg, up->lastnote.flag!=NDebug); | |
| 1991/0720 | } | |
| 1991/1112 | } | |
| 1993/1113 | long execregs(ulong entry, ulong ssize, ulong nargs) { ulong *sp; | |
| 1997/0327 | Ureg *ureg; | |
| 1993/1113 | sp = (ulong*)(USTKTOP - ssize); *--sp = nargs; | |
| 1997/0327 | ureg = up->dbgreg; ureg->usp = (ulong)sp; ureg->pc = entry; | |
| 1994/0513 | return USTKTOP-BY2WD; /* address of user-level clock */ | |
| 1993/1113 | } ulong userpc(void) { | |
| 1997/0327 | Ureg *ureg; | |
| 1993/1113 | ||
| 1997/0327 | ureg = (Ureg*)up->dbgreg; return ureg->pc; | |
| 1993/1113 | } | |
| 1991/1112 | /* This routine must save the values of registers the user is not permitted to write | |
| 1997/0327 | * from devproc and then restore the saved values before returning | |
| 1991/1112 | */ void | |
| 1997/0327 | setregisters(Ureg* ureg, char* pureg, char* uva, int n) | |
| 1991/1112 | { ulong flags; ulong cs; ulong ss; | |
| 1997/0327 | flags = ureg->flags; cs = ureg->cs; ss = ureg->ss; | |
| 1991/1112 | memmove(pureg, uva, n); | |
| 1997/0327 | ureg->flags = (ureg->flags & 0x00FF) | (flags & 0xFF00); ureg->cs = cs; ureg->ss = ss; | |
| 1993/1113 | } static void linkproc(void) { spllo(); | |
| 1997/0327 | up->kpfun(up->kparg); | |
| 1993/1113 | } void | |
| 1997/0327 | kprocchild(Proc* p, void (*func)(void*), void* arg) | |
| 1993/1113 | { p->sched.pc = (ulong)linkproc; p->sched.sp = (ulong)p->kstack+KSTACK; p->kpfun = func; p->kparg = arg; } void | |
| 1997/0327 | forkchild(Proc *p, Ureg *ureg) | |
| 1993/1113 | { | |
| 1997/0327 | Ureg *cureg; | |
| 1993/1113 | /* | |
| 1997/0327 | * Add 2*BY2WD to the stack to account for | |
| 1993/1113 | * - the return PC * - trap's argument (ur) */ p->sched.sp = (ulong)p->kstack+KSTACK-(sizeof(Ureg)+2*BY2WD); p->sched.pc = (ulong)forkret; | |
| 1997/0327 | cureg = (Ureg*)(p->sched.sp+2*BY2WD); memmove(cureg, ureg, sizeof(Ureg)); cureg->ax = 0; /* return value of syscall in child */ | |
| 1993/1113 | ||
| 1997/0327 | /* Things from bottom of syscall which were never executed */ | |
| 1993/1113 | p->psstate = 0; p->insyscall = 0; | |
| 1991/0614 | } | |
| 1993/1022 | /* Give enough context in the ureg to produce a kernel stack for * a sleeping process */ void | |
| 1997/0327 | setkernur(Ureg* ureg, Proc* p) | |
| 1993/1022 | { | |
| 1997/0327 | ureg->pc = p->sched.pc; ureg->sp = p->sched.sp+4; | |
| 1993/1022 | } | |
| 1995/1024 | ulong dbgpc(Proc *p) { | |
| 1997/0327 | Ureg *ureg; | |
| 1995/1024 | ||
| 1997/0327 | ureg = p->dbgreg; if(ureg == 0) | |
| 1995/1024 | return 0; | |
| 1997/0327 | return ureg->pc; | |
| 1995/1024 | } | |