FreeBSD/Linux Kernel Cross Reference
sys/mtx/trap.c
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "ureg.h"
7 #include "io.h"
8 #include "tos.h"
9 #include "../port/error.h"
10
11 static Lock vctllock;
12 static Vctl *vctl[256];
13
14 void
15 hwintrinit(void)
16 {
17 i8259init();
18 mpicenable(0, nil); /* 8259 interrupts are routed through MPIC intr 0 */
19 }
20
21 static int
22 hwintrenable(Vctl *v)
23 {
24 int vec, irq;
25
26 irq = v->irq;
27 if(BUSTYPE(v->tbdf) == BusPCI) { /* MPIC? */
28 if(irq > 15) {
29 print("intrenable: pci irq %d out of range\n", v->irq);
30 return -1;
31 }
32 vec = irq;
33 mpicenable(vec, v);
34 }
35 else {
36 if(irq > MaxIrqPIC) {
37 print("intrenable: irq %d out of range\n", v->irq);
38 return -1;
39 }
40 vec = irq+VectorPIC;
41 if(i8259enable(v) == -1)
42 return -1;
43 }
44 return vec;
45 }
46
47 static int
48 hwintrdisable(Vctl *v)
49 {
50 int vec, irq;
51
52 irq = v->irq;
53 if(BUSTYPE(v->tbdf) == BusPCI) { /* MPIC? */
54 if(irq > 15) {
55 print("intrdisable: pci irq %d out of range\n", v->irq);
56 return -1;
57 }
58 vec = irq;
59 mpicdisable(vec);
60 }
61 else {
62 if(irq > MaxIrqPIC) {
63 print("intrdisable: irq %d out of range\n", v->irq);
64 return -1;
65 }
66 vec = irq+VectorPIC;
67 if(i8259disable(irq) == -1)
68 return -1;
69 }
70 return vec;
71 }
72
73 static int
74 hwvecno(int irq, int tbdf)
75 {
76 if(BUSTYPE(tbdf) == BusPCI) /* MPIC? */
77 return irq;
78 else
79 return irq+VectorPIC;
80 }
81
82 void
83 intrenable(int irq, void (*f)(Ureg*, void*), void* a, int tbdf, char *name)
84 {
85 int vno;
86 Vctl *v;
87
88 if(f == nil){
89 print("intrenable: nil handler for %d, tbdf 0x%uX for %s\n",
90 irq, tbdf, name);
91 return;
92 }
93
94 v = xalloc(sizeof(Vctl));
95 v->isintr = 1;
96 v->irq = irq;
97 v->tbdf = tbdf;
98 v->f = f;
99 v->a = a;
100 strncpy(v->name, name, KNAMELEN-1);
101 v->name[KNAMELEN-1] = 0;
102
103 ilock(&vctllock);
104 vno = hwintrenable(v);
105 if(vno == -1){
106 iunlock(&vctllock);
107 print("intrenable: couldn't enable irq %d, tbdf 0x%uX for %s\n",
108 irq, tbdf, v->name);
109 xfree(v);
110 return;
111 }
112 if(vctl[vno]){
113 if(vctl[vno]->isr != v->isr || vctl[vno]->eoi != v->eoi)
114 panic("intrenable: handler: %s %s %#p %#p %#p %#p",
115 vctl[vno]->name, v->name,
116 vctl[vno]->isr, v->isr, vctl[vno]->eoi, v->eoi);
117 v->next = vctl[vno];
118 }
119 vctl[vno] = v;
120 iunlock(&vctllock);
121 }
122
123 void
124 intrdisable(int irq, void (*f)(Ureg *, void *), void *a, int tbdf, char *name)
125 {
126 Vctl **pv, *v;
127 int vno;
128
129 vno = hwvecno(irq, tbdf);
130 ilock(&vctllock);
131 pv = &vctl[vno];
132 while (*pv &&
133 ((*pv)->irq != irq || (*pv)->tbdf != tbdf || (*pv)->f != f || (*pv)->a != a ||
134 strcmp((*pv)->name, name)))
135 pv = &((*pv)->next);
136 assert(*pv);
137
138 v = *pv;
139 *pv = (*pv)->next; /* Link out the entry */
140
141 if(vctl[vno] == nil)
142 hwintrdisable(v);
143 iunlock(&vctllock);
144 xfree(v);
145 }
146
147 void syscall(Ureg*);
148 void noted(Ureg*, ulong);
149 static void _dumpstack(Ureg*);
150
151 char *excname[] =
152 {
153 "reserved 0",
154 "system reset",
155 "machine check",
156 "data access",
157 "instruction access",
158 "external interrupt",
159 "alignment",
160 "program exception",
161 "floating-point unavailable",
162 "decrementer",
163 "reserved A",
164 "reserved B",
165 "system call",
166 "trace trap",
167 "floating point assist",
168 "reserved F",
169 "reserved 10",
170 "reserved 11",
171 "reserved 12",
172 "instruction address breakpoint",
173 "system management interrupt",
174 };
175
176 char *fpcause[] =
177 {
178 "inexact operation",
179 "division by zero",
180 "underflow",
181 "overflow",
182 "invalid operation",
183 };
184 char *fpexcname(Ureg*, ulong, char*);
185 #define FPEXPMASK 0xfff80300 /* Floating exception bits in fpscr */
186
187
188 char *regname[]={
189 "CAUSE", "SRR1",
190 "PC", "GOK",
191 "LR", "CR",
192 "XER", "CTR",
193 "R0", "R1",
194 "R2", "R3",
195 "R4", "R5",
196 "R6", "R7",
197 "R8", "R9",
198 "R10", "R11",
199 "R12", "R13",
200 "R14", "R15",
201 "R16", "R17",
202 "R18", "R19",
203 "R20", "R21",
204 "R22", "R23",
205 "R24", "R25",
206 "R26", "R27",
207 "R28", "R29",
208 "R30", "R31",
209 };
210
211 void
212 trap(Ureg *ureg)
213 {
214 ulong dsisr;
215 int ecode, user;
216 char buf[ERRMAX], *s;
217
218 ecode = (ureg->cause >> 8) & 0xff;
219 user = (ureg->srr1 & MSR_PR) != 0;
220 if(user)
221 up->dbgreg = ureg;
222
223 if(ureg->status & MSR_RI == 0)
224 print("double fault?: ecode = %d\n", ecode);
225
226 switch(ecode) {
227 case CEI:
228 intr(ureg);
229 break;
230 case CDEC:
231 clockintr(ureg);
232 break;
233 case CSYSCALL:
234 if(!user)
235 panic("syscall in kernel: srr1 0x%4.4luX", ureg->srr1);
236 syscall(ureg);
237 return; /* syscall() calls notify itself, don't do it again */
238 case CFPU:
239 if(!user || up == nil) {
240 dumpregs(ureg);
241 panic("floating point in kernel");
242 }
243 switch(up->fpstate){
244 case FPinit:
245 fprestore(&initfp);
246 up->fpstate = FPactive;
247 break;
248 case FPinactive:
249 fprestore(&up->fpsave);
250 up->fpstate = FPactive;
251 break;
252 default:
253 panic("fpstate");
254 }
255 ureg->srr1 |= MSR_FP;
256 break;
257 case CISI:
258 faultpower(ureg, ureg->pc, 1);
259 break;
260 case CDSI:
261 dsisr = getdsisr();
262 if(dsisr & BIT(6))
263 faultpower(ureg, getdar(), 0);
264 else
265 faultpower(ureg, getdar(), 1);
266 break;
267 case CPROG:
268 if(ureg->status & (1<<19))
269 s = "floating point exception";
270 else if(ureg->status & (1<<18))
271 s = "illegal instruction";
272 else if(ureg->status & (1<<17))
273 s = "privileged instruction";
274 else
275 s = "undefined program exception";
276 if(user){
277 spllo();
278 sprint(buf, "sys: trap: %s", s);
279 postnote(up, 1, buf, NDebug);
280 break;
281 }
282 dumpregs(ureg);
283 panic(s);
284 break;
285 default:
286 if(ecode < nelem(excname) && user){
287 spllo();
288 sprint(buf, "sys: trap: %s", excname[ecode]);
289 postnote(up, 1, buf, NDebug);
290 break;
291 }
292 dumpregs(ureg);
293 if(ecode < nelem(excname))
294 panic("%s", excname[ecode]);
295 panic("unknown trap/intr: %d", ecode);
296 }
297
298 /* restoreureg must execute at high IPL */
299 splhi();
300
301 /* delaysched set because we held a lock or because our quantum ended */
302 if(up && up->delaysched && ecode == CDEC){
303 sched();
304 splhi();
305 }
306
307 if(user) {
308 notify(ureg);
309 if(up->fpstate == FPinactive)
310 ureg->srr1 &= ~MSR_FP;
311 kexit(ureg);
312 }
313 }
314
315 void
316 faultpower(Ureg *ureg, ulong addr, int read)
317 {
318 int user, insyscall, n;
319 char buf[ERRMAX];
320
321 user = (ureg->srr1 & MSR_PR) != 0;
322 insyscall = up->insyscall;
323 up->insyscall = 1;
324 n = fault(addr, read);
325 if(n < 0){
326 if(!user){
327 dumpregs(ureg);
328 panic("fault: 0x%lux", addr);
329 }
330 sprint(buf, "sys: trap: fault %s addr=0x%lux", read? "read" : "write", addr);
331 postnote(up, 1, buf, NDebug);
332 }
333 up->insyscall = insyscall;
334 }
335
336 void
337 sethvec(int v, void (*r)(void))
338 {
339 ulong *vp, pa, o;
340
341 vp = KADDR(v);
342 vp[0] = 0x7c1043a6; /* MOVW R0, SPR(SPRG0) */
343 vp[1] = 0x7c0802a6; /* MOVW LR, R0 */
344 vp[2] = 0x7c1243a6; /* MOVW R0, SPR(SPRG2) */
345 pa = PADDR(r);
346 o = pa >> 25;
347 if(o != 0 && o != 0x7F){
348 /* a branch too far */
349 vp[3] = (15<<26)|(pa>>16); /* MOVW $r&~0xFFFF, R0 */
350 vp[4] = (24<<26)|(pa&0xFFFF); /* OR $r&0xFFFF, R0 */
351 vp[5] = 0x7c0803a6; /* MOVW R0, LR */
352 vp[6] = 0x4e800021; /* BL (LR) */
353 }else
354 vp[3] = (18<<26)|(pa&0x3FFFFFC)|3; /* bla */
355 dcflush(vp, 8*sizeof(ulong));
356 }
357
358 void
359 trapinit(void)
360 {
361 int i;
362
363 /*
364 * set all exceptions to trap
365 */
366 for(i = 0; i < 0x2000; i += 0x100)
367 sethvec(i, trapvec);
368
369 putmsr(getmsr() & ~MSR_IP);
370 }
371
372 void
373 intr(Ureg *ureg)
374 {
375 int vno;
376 Vctl *ctl, *v;
377
378 vno = mpicintack();
379 if(vno == 0) { /* 8259, wired through MPIC vec 0 */
380 vno = i8259intack();
381 mpiceoi(0);
382 }
383
384 if(vno > nelem(vctl) || (ctl = vctl[vno]) == 0) {
385 panic("spurious intr %d", vno);
386 return;
387 }
388
389 if(ctl->isr)
390 ctl->isr(vno);
391 for(v = ctl; v != nil; v = v->next){
392 if(v->f)
393 v->f(ureg, v->a);
394 }
395 if(ctl->eoi)
396 ctl->eoi(vno);
397
398 if(up)
399 preempted();
400 }
401
402 char*
403 fpexcname(Ureg *ur, ulong fpscr, char *buf)
404 {
405 int i;
406 char *s;
407 ulong fppc;
408
409 fppc = ur->pc;
410 s = 0;
411 fpscr >>= 3; /* trap enable bits */
412 fpscr &= (fpscr>>22); /* anded with exceptions */
413 for(i=0; i<5; i++)
414 if(fpscr & (1<<i))
415 s = fpcause[i];
416 if(s == 0)
417 return "no floating point exception";
418 sprint(buf, "%s fppc=0x%lux", s, fppc);
419 return buf;
420 }
421
422 /*
423 * Fill in enough of Ureg to get a stack trace, and call a function.
424 * Used by debugging interface rdb.
425 */
426
427 static void
428 getpcsp(ulong *pc, ulong *sp)
429 {
430 *pc = getcallerpc(&pc);
431 *sp = (ulong)&pc-4;
432 }
433
434 void
435 callwithureg(void (*fn)(Ureg*))
436 {
437 Ureg ureg;
438
439 getpcsp((ulong*)&ureg.pc, (ulong*)&ureg.sp);
440 ureg.lr = getcallerpc(&fn);
441 fn(&ureg);
442 }
443
444 static void
445 _dumpstack(Ureg *ureg)
446 {
447 ulong l, sl, el, v;
448 int i;
449
450 l = (ulong)&l;
451 if(up == 0){
452 el = (ulong)m+BY2PG;
453 sl = el-KSTACK;
454 }
455 else{
456 sl = (ulong)up->kstack;
457 el = sl + KSTACK;
458 }
459 if(l > el || l < sl){
460 el = (ulong)m+BY2PG;
461 sl = el-KSTACK;
462 }
463 if(l > el || l < sl)
464 return;
465 print("ktrace /kernel/path %.8lux %.8lux %.8lux\n", ureg->pc, ureg->sp, ureg->lr);
466 i = 0;
467 for(; l < el; l += 4){
468 v = *(ulong*)l;
469 if(KTZERO < v && v < (ulong)etext){
470 print("%.8lux=%.8lux ", l, v);
471 if(i++ == 4){
472 print("\n");
473 i = 0;
474 }
475 }
476 }
477 }
478
479 void
480 dumpstack(void)
481 {
482 callwithureg(_dumpstack);
483 }
484
485 void
486 dumpregs(Ureg *ur)
487 {
488 int i;
489 ulong *l;
490
491 if(up) {
492 print("registers for %s %ld\n", up->text, up->pid);
493 if(ur->srr1 & MSR_PR == 0)
494 if(ur->usp < (ulong)up->kstack || ur->usp > (ulong)up->kstack+KSTACK)
495 print("invalid stack ptr\n");
496 }
497 else
498 print("registers for kernel\n");
499
500 print("dsisr\t%.8lux\tdar\t%.8lux\n", getdsisr(), getdar());
501 l = &ur->cause;
502 for(i=0; i<sizeof regname/sizeof(char*); i+=2, l+=2)
503 print("%s\t%.8lux\t%s\t%.8lux\n", regname[i], l[0], regname[i+1], l[1]);
504 }
505
506 static void
507 linkproc(void)
508 {
509 spllo();
510 (*up->kpfun)(up->kparg);
511 pexit("", 0);
512 }
513
514 void
515 kprocchild(Proc *p, void (*func)(void*), void *arg)
516 {
517 p->sched.pc = (ulong)linkproc;
518 p->sched.sp = (ulong)p->kstack+KSTACK;
519
520 p->kpfun = func;
521 p->kparg = arg;
522 }
523
524 /*
525 * called in sysfile.c
526 */
527 void
528 evenaddr(ulong addr)
529 {
530 if(addr & 3){
531 postnote(up, 1, "sys: odd address", NDebug);
532 error(Ebadarg);
533 }
534 }
535
536 long
537 execregs(ulong entry, ulong ssize, ulong nargs)
538 {
539 ulong *sp;
540 Ureg *ureg;
541
542 sp = (ulong*)(USTKTOP - ssize);
543 *--sp = nargs;
544
545 ureg = up->dbgreg;
546 ureg->usp = (ulong)sp;
547 ureg->pc = entry;
548 ureg->srr1 &= ~MSR_FP;
549 return USTKTOP-sizeof(Tos); /* address of kernel/user shared data */
550 }
551
552 void
553 forkchild(Proc *p, Ureg *ur)
554 {
555 Ureg *cur;
556
557 p->sched.sp = (ulong)p->kstack+KSTACK-UREGSIZE;
558 p->sched.pc = (ulong)forkret;
559
560 cur = (Ureg*)(p->sched.sp+2*BY2WD);
561 memmove(cur, ur, sizeof(Ureg));
562 cur->r3 = 0;
563
564 /* Things from bottom of syscall we never got to execute */
565 p->psstate = 0;
566 p->insyscall = 0;
567 }
568
569 ulong
570 userpc(void)
571 {
572 Ureg *ureg;
573
574 ureg = (Ureg*)up->dbgreg;
575 return ureg->pc;
576 }
577
578
579 /* This routine must save the values of registers the user is not
580 * permitted to write from devproc and then restore the saved values
581 * before returning
582 */
583 void
584 setregisters(Ureg *xp, char *pureg, char *uva, int n)
585 {
586 ulong status;
587
588 status = xp->status;
589 memmove(pureg, uva, n);
590 xp->status = status;
591 }
592
593 /* Give enough context in the ureg to produce a kernel stack for
594 * a sleeping process
595 */
596 void
597 setkernur(Ureg* ureg, Proc* p)
598 {
599 ureg->pc = p->sched.pc;
600 ureg->sp = p->sched.sp+4;
601 }
602
603 ulong
604 dbgpc(Proc *p)
605 {
606 Ureg *ureg;
607
608 ureg = p->dbgreg;
609 if(ureg == 0)
610 return 0;
611
612 return ureg->pc;
613 }
614
615 /*
616 * system calls
617 */
618 #include "../port/systab.h"
619
620 /* TODO: make this trap a separate asm entry point, like on other RISC architectures */
621 void
622 syscall(Ureg* ureg)
623 {
624 int i;
625 char *e;
626 long ret;
627 ulong sp, scallnr;
628
629 m->syscall++;
630 up->insyscall = 1;
631 up->pc = ureg->pc;
632 up->dbgreg = ureg;
633
634 scallnr = ureg->r3;
635 up->scallnr = ureg->r3;
636 spllo();
637
638 sp = ureg->usp;
639 up->nerrlab = 0;
640 ret = -1;
641 if(!waserror()){
642 if(scallnr >= nsyscall || systab[scallnr] == nil){
643 pprint("bad sys call number %d pc %lux\n", scallnr, ureg->pc);
644 postnote(up, 1, "sys: bad sys call", NDebug);
645 error(Ebadarg);
646 }
647
648 if(sp<(USTKTOP-BY2PG) || sp>(USTKTOP-sizeof(Sargs)-BY2WD))
649 validaddr(sp, sizeof(Sargs)+BY2WD, 0);
650
651 up->s = *((Sargs*)(sp+BY2WD));
652 up->psstate = sysctab[scallnr];
653
654 ret = systab[scallnr](up->s.args);
655 poperror();
656 }else{
657 /* failure: save the error buffer for errstr */
658 e = up->syserrstr;
659 up->syserrstr = up->errstr;
660 up->errstr = e;
661 }
662 if(up->nerrlab){
663 print("bad errstack [%uld]: %d extra\n", scallnr, up->nerrlab);
664 print("scall %s lr =%lux\n", sysctab[scallnr], ureg->lr);
665 for(i = 0; i < NERR; i++)
666 print("sp=%lux pc=%lux\n", up->errlab[i].sp, up->errlab[i].pc);
667 panic("error stack");
668 }
669
670 up->insyscall = 0;
671 up->psstate = 0;
672
673 /*
674 * Put return value in frame. On the x86 the syscall is
675 * just another trap and the return value from syscall is
676 * ignored. On other machines the return value is put into
677 * the results register by caller of syscall.
678 */
679 ureg->r3 = ret;
680
681 if(scallnr == NOTED)
682 noted(ureg, *(ulong*)(sp+BY2WD));
683
684 /* restoreureg must execute at high IPL */
685 splhi();
686 if(scallnr!=RFORK)
687 notify(ureg);
688 if(up->fpstate == FPinactive)
689 ureg->srr1 &= ~MSR_FP;
690 }
691
692 /*
693 * Call user, if necessary, with note.
694 * Pass user the Ureg struct and the note on his stack.
695 */
696 int
697 notify(Ureg* ur)
698 {
699 int l;
700 ulong s, sp;
701 Note *n;
702
703 if(up->procctl)
704 procctl(up);
705 if(up->nnote == 0)
706 return 0;
707
708 s = spllo();
709 qlock(&up->debug);
710 up->notepending = 0;
711 n = &up->note[0];
712 if(strncmp(n->msg, "sys:", 4) == 0){
713 l = strlen(n->msg);
714 if(l > ERRMAX-15) /* " pc=0x12345678\0" */
715 l = ERRMAX-15;
716 sprint(n->msg+l, " pc=0x%.8lux", ur->pc);
717 }
718
719 if(n->flag!=NUser && (up->notified || up->notify==0)){
720 if(n->flag == NDebug)
721 pprint("suicide: %s\n", n->msg);
722 qunlock(&up->debug);
723 pexit(n->msg, n->flag!=NDebug);
724 }
725
726 if(up->notified) {
727 qunlock(&up->debug);
728 splhi();
729 return 0;
730 }
731
732 if(!up->notify) {
733 qunlock(&up->debug);
734 pexit(n->msg, n->flag!=NDebug);
735 }
736 sp = ur->usp & ~(BY2V-1);
737 sp -= sizeof(Ureg);
738
739 if(!okaddr((ulong)up->notify, BY2WD, 0) ||
740 !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)) {
741 pprint("suicide: bad address or sp in notify\n");
742 qunlock(&up->debug);
743 pexit("Suicide", 0);
744 }
745
746 memmove((Ureg*)sp, ur, sizeof(Ureg));
747 *(Ureg**)(sp-BY2WD) = up->ureg; /* word under Ureg is old up->ureg */
748 up->ureg = (void*)sp;
749 sp -= BY2WD+ERRMAX;
750 memmove((char*)sp, up->note[0].msg, ERRMAX);
751 sp -= 3*BY2WD;
752 *(ulong*)(sp+2*BY2WD) = sp+3*BY2WD; /* arg 2 is string */
753 ur->r1 = (long)up->ureg; /* arg 1 is ureg* */
754 ((ulong*)sp)[1] = (ulong)up->ureg; /* arg 1 0(FP) is ureg* */
755 ((ulong*)sp)[0] = 0; /* arg 0 is pc */
756 ur->usp = sp;
757 ur->pc = (ulong)up->notify;
758 up->notified = 1;
759 up->nnote--;
760 memmove(&up->lastnote, &up->note[0], sizeof(Note));
761 memmove(&up->note[0], &up->note[1], up->nnote*sizeof(Note));
762
763 qunlock(&up->debug);
764 splx(s);
765 return 1;
766 }
767
768
769 /*
770 * Return user to state before notify()
771 */
772 void
773 noted(Ureg* ureg, ulong arg0)
774 {
775 Ureg *nureg;
776 ulong oureg, sp;
777
778 qlock(&up->debug);
779 if(arg0!=NRSTR && !up->notified) {
780 qunlock(&up->debug);
781 pprint("call to noted() when not notified\n");
782 pexit("Suicide", 0);
783 }
784 up->notified = 0;
785
786 nureg = up->ureg; /* pointer to user returned Ureg struct */
787
788 /* sanity clause */
789 oureg = (ulong)nureg;
790 if(!okaddr((ulong)oureg-BY2WD, BY2WD+sizeof(Ureg), 0)){
791 pprint("bad ureg in noted or call to noted when not notified\n");
792 qunlock(&up->debug);
793 pexit("Suicide", 0);
794 }
795
796 memmove(ureg, nureg, sizeof(Ureg));
797
798 switch(arg0){
799 case NCONT:
800 case NRSTR:
801 if(!okaddr(nureg->pc, 1, 0) || !okaddr(nureg->usp, BY2WD, 0)){
802 pprint("suicide: trap in noted\n");
803 qunlock(&up->debug);
804 pexit("Suicide", 0);
805 }
806 up->ureg = (Ureg*)(*(ulong*)(oureg-BY2WD));
807 qunlock(&up->debug);
808 break;
809
810 case NSAVE:
811 if(!okaddr(nureg->pc, BY2WD, 0)
812 || !okaddr(nureg->usp, BY2WD, 0)){
813 pprint("suicide: trap in noted\n");
814 qunlock(&up->debug);
815 pexit("Suicide", 0);
816 }
817 qunlock(&up->debug);
818 sp = oureg-4*BY2WD-ERRMAX;
819 splhi();
820 ureg->sp = sp;
821 ((ulong*)sp)[1] = oureg; /* arg 1 0(FP) is ureg* */
822 ((ulong*)sp)[0] = 0; /* arg 0 is pc */
823 break;
824
825 default:
826 pprint("unknown noted arg 0x%lux\n", arg0);
827 up->lastnote.flag = NDebug;
828 /* fall through */
829
830 case NDFLT:
831 if(up->lastnote.flag == NDebug)
832 pprint("suicide: %s\n", up->lastnote.msg);
833 qunlock(&up->debug);
834 pexit(up->lastnote.msg, up->lastnote.flag!=NDebug);
835 }
836 }
Cache object: fb2c04e644b5ef3d7fddf6d147b93392
|