1 /**************************************************************************** 2 3 THIS SOFTWARE IS NOT COPYRIGHTED 4 5 HP offers the following for use in the public domain. HP makes no 6 warranty with regard to the software or it's performance and the 7 user accepts the software "AS IS" with all faults. 8 9 HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD 10 TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES 11 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 12 13 ****************************************************************************/ 14 15 /**************************************************************************** 16 * Header: remcom.c,v 1.34 91/03/09 12:29:49 glenne Exp $ 17 * 18 * Module name: remcom.c $ 19 * Revision: 1.34 $ 20 * Date: 91/03/09 12:29:49 $ 21 * Contributor: Lake Stevens Instrument Division$ 22 * 23 * Description: low level support for gdb debugger. $ 24 * 25 * Considerations: only works on target hardware $ 26 * 27 * Written by: Glenn Engel $ 28 * ModuleState: Experimental $ 29 * 30 * NOTES: See Below $ 31 * 32 * Modified for SPARC by Stu Grossman, Cygnus Support. 33 * 34 * This code has been extensively tested on the Fujitsu SPARClite demo board. 35 * 36 * To enable debugger support, two things need to happen. One, a 37 * call to set_debug_traps() is necessary in order to allow any breakpoints 38 * or error conditions to be properly intercepted and reported to gdb. 39 * Two, a breakpoint needs to be generated to begin communication. This 40 * is most easily accomplished by a call to breakpoint(). Breakpoint() 41 * simulates a breakpoint by executing a trap #1. 42 * 43 ************* 44 * 45 * The following gdb commands are supported: 46 * 47 * command function Return value 48 * 49 * g return the value of the CPU registers hex data or ENN 50 * G set the value of the CPU registers OK or ENN 51 * 52 * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN 53 * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN 54 * 55 * c Resume at current address SNN ( signal NN) 56 * cAA..AA Continue at address AA..AA SNN 57 * 58 * s Step one instruction SNN 59 * sAA..AA Step one instruction from AA..AA SNN 60 * 61 * k kill 62 * 63 * ? What was the last sigval ? SNN (signal NN) 64 * 65 * All commands and responses are sent with a packet which includes a 66 * checksum. A packet consists of 67 * 68 * $<packet info>#<checksum>. 69 * 70 * where 71 * <packet info> :: <characters representing the command or response> 72 * <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>> 73 * 74 * When a packet is received, it is first acknowledged with either '+' or '-'. 75 * '+' indicates a successful transfer. '-' indicates a failed transfer. 76 * 77 * Example: 78 * 79 * Host: Reply: 80 * $m0,10#2a +$00010203040506070809101112131415#42 81 * 82 ****************************************************************************/ 83 84 #include <string.h> 85 #include <signal.h> 86 87 /************************************************************************ 88 * 89 * external low-level support routines 90 */ 91 92 extern void putDebugChar(); /* write a single character */ 93 extern int getDebugChar(); /* read and return a single char */ 94 95 /************************************************************************/ 96 /* BUFMAX defines the maximum number of characters in inbound/outbound buffers*/ 97 /* at least NUMREGBYTES*2 are needed for register packets */ 98 #define BUFMAX 2048 99 100 static int initialized = 0; /* !0 means we've been initialized */ 101 102 static void set_mem_fault_trap(); 103 104 static const char hexchars[]="0123456789abcdef"; 105 106 #define NUMREGS 72 107 108 /* Number of bytes of registers. */ 109 #define NUMREGBYTES (NUMREGS * 4) 110 enum regnames {G0, G1, G2, G3, G4, G5, G6, G7, 111 O0, O1, O2, O3, O4, O5, SP, O7, 112 L0, L1, L2, L3, L4, L5, L6, L7, 113 I0, I1, I2, I3, I4, I5, FP, I7, 114 115 F0, F1, F2, F3, F4, F5, F6, F7, 116 F8, F9, F10, F11, F12, F13, F14, F15, 117 F16, F17, F18, F19, F20, F21, F22, F23, 118 F24, F25, F26, F27, F28, F29, F30, F31, 119 Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR }; 120 121 /*************************** ASSEMBLY CODE MACROS *************************/ 122 /* */ 123 124 extern void trap_low(); 125 126 asm(" 127 .reserve trapstack, 1000 * 4, \"bss\", 8 128 129 .data 130 .align 4 131 132 in_trap_handler: 133 .word 0 134 135 .text 136 .align 4 137 138 ! This function is called when any SPARC trap (except window overflow or 139 ! underflow) occurs. It makes sure that the invalid register window is still 140 ! available before jumping into C code. It will also restore the world if you 141 ! return from handle_exception. 142 143 .globl _trap_low 144 _trap_low: 145 mov %psr, %l0 146 mov %wim, %l3 147 148 srl %l3, %l0, %l4 ! wim >> cwp 149 cmp %l4, 1 150 bne window_fine ! Branch if not in the invalid window 151 nop 152 153 ! Handle window overflow 154 155 mov %g1, %l4 ! Save g1, we use it to hold the wim 156 srl %l3, 1, %g1 ! Rotate wim right 157 tst %g1 158 bg good_wim ! Branch if new wim is non-zero 159 nop 160 161 ! At this point, we need to bring a 1 into the high order bit of the wim. 162 ! Since we don't want to make any assumptions about the number of register 163 ! windows, we figure it out dynamically so as to setup the wim correctly. 164 165 not %g1 ! Fill g1 with ones 166 mov %g1, %wim ! Fill the wim with ones 167 nop 168 nop 169 nop 170 mov %wim, %g1 ! Read back the wim 171 inc %g1 ! Now g1 has 1 just to left of wim 172 srl %g1, 1, %g1 ! Now put 1 at top of wim 173 mov %g0, %wim ! Clear wim so that subsequent save 174 nop ! won't trap 175 nop 176 nop 177 178 good_wim: 179 save %g0, %g0, %g0 ! Slip into next window 180 mov %g1, %wim ! Install the new wim 181 182 std %l0, [%sp + 0 * 4] ! save L & I registers 183 std %l2, [%sp + 2 * 4] 184 std %l4, [%sp + 4 * 4] 185 std %l6, [%sp + 6 * 4] 186 187 std %i0, [%sp + 8 * 4] 188 std %i2, [%sp + 10 * 4] 189 std %i4, [%sp + 12 * 4] 190 std %i6, [%sp + 14 * 4] 191 192 restore ! Go back to trap window. 193 mov %l4, %g1 ! Restore %g1 194 195 window_fine: 196 sethi %hi(in_trap_handler), %l4 197 ld [%lo(in_trap_handler) + %l4], %l5 198 tst %l5 199 bg recursive_trap 200 inc %l5 201 202 set trapstack+1000*4, %sp ! Switch to trap stack 203 204 recursive_trap: 205 st %l5, [%lo(in_trap_handler) + %l4] 206 sub %sp,(16+1+6+1+72)*4,%sp ! Make room for input & locals 207 ! + hidden arg + arg spill 208 ! + doubleword alignment 209 ! + registers[72] local var 210 211 std %g0, [%sp + (24 + 0) * 4] ! registers[Gx] 212 std %g2, [%sp + (24 + 2) * 4] 213 std %g4, [%sp + (24 + 4) * 4] 214 std %g6, [%sp + (24 + 6) * 4] 215 216 std %i0, [%sp + (24 + 8) * 4] ! registers[Ox] 217 std %i2, [%sp + (24 + 10) * 4] 218 std %i4, [%sp + (24 + 12) * 4] 219 std %i6, [%sp + (24 + 14) * 4] 220 ! F0->F31 not implemented 221 mov %y, %l4 222 mov %tbr, %l5 223 st %l4, [%sp + (24 + 64) * 4] ! Y 224 st %l0, [%sp + (24 + 65) * 4] ! PSR 225 st %l3, [%sp + (24 + 66) * 4] ! WIM 226 st %l5, [%sp + (24 + 67) * 4] ! TBR 227 st %l1, [%sp + (24 + 68) * 4] ! PC 228 st %l2, [%sp + (24 + 69) * 4] ! NPC 229 230 ! CPSR and FPSR not impl 231 232 or %l0, 0xf20, %l4 233 mov %l4, %psr ! Turn on traps, disable interrupts 234 235 call _handle_exception 236 add %sp, 24 * 4, %o0 ! Pass address of registers 237 238 ! Reload all of the registers that aren't on the stack 239 240 ld [%sp + (24 + 1) * 4], %g1 ! registers[Gx] 241 ldd [%sp + (24 + 2) * 4], %g2 242 ldd [%sp + (24 + 4) * 4], %g4 243 ldd [%sp + (24 + 6) * 4], %g6 244 245 ldd [%sp + (24 + 8) * 4], %i0 ! registers[Ox] 246 ldd [%sp + (24 + 10) * 4], %i2 247 ldd [%sp + (24 + 12) * 4], %i4 248 ldd [%sp + (24 + 14) * 4], %i6 249 250 ldd [%sp + (24 + 64) * 4], %l0 ! Y & PSR 251 ldd [%sp + (24 + 68) * 4], %l2 ! PC & NPC 252 253 restore ! Ensure that previous window is valid 254 save %g0, %g0, %g0 ! by causing a window_underflow trap 255 256 mov %l0, %y 257 mov %l1, %psr ! Make sure that traps are disabled 258 ! for rett 259 260 sethi %hi(in_trap_handler), %l4 261 ld [%lo(in_trap_handler) + %l4], %l5 262 dec %l5 263 st %l5, [%lo(in_trap_handler) + %l4] 264 265 jmpl %l2, %g0 ! Restore old PC 266 rett %l3 ! Restore old nPC 267 "); 268 269 /* Convert ch from a hex digit to an int */ 270 271 static int 272 hex (unsigned char ch) 273 { 274 if (ch >= 'a' && ch <= 'f') 275 return ch-'a'+10; 276 if (ch >= '0' && ch <= '9') 277 return ch-'0'; 278 if (ch >= 'A' && ch <= 'F') 279 return ch-'A'+10; 280 return -1; 281 } 282 283 static char remcomInBuffer[BUFMAX]; 284 static char remcomOutBuffer[BUFMAX]; 285 286 /* scan for the sequence $<data>#<checksum> */ 287 288 unsigned char * 289 getpacket (void) 290 { 291 unsigned char *buffer = &remcomInBuffer[0]; 292 unsigned char checksum; 293 unsigned char xmitcsum; 294 int count; 295 char ch; 296 297 while (1) 298 { 299 /* wait around for the start character, ignore all other characters */ 300 while ((ch = getDebugChar ()) != '$') 301 ; 302 303 retry: 304 checksum = 0; 305 xmitcsum = -1; 306 count = 0; 307 308 /* now, read until a # or end of buffer is found */ 309 while (count < BUFMAX) 310 { 311 ch = getDebugChar (); 312 if (ch == '$') 313 goto retry; 314 if (ch == '#') 315 break; 316 checksum = checksum + ch; 317 buffer[count] = ch; 318 count = count + 1; 319 } 320 buffer[count] = 0; 321 322 if (ch == '#') 323 { 324 ch = getDebugChar (); 325 xmitcsum = hex (ch) << 4; 326 ch = getDebugChar (); 327 xmitcsum += hex (ch); 328 329 if (checksum != xmitcsum) 330 { 331 putDebugChar ('-'); /* failed checksum */ 332 } 333 else 334 { 335 putDebugChar ('+'); /* successful transfer */ 336 337 /* if a sequence char is present, reply the sequence ID */ 338 if (buffer[2] == ':') 339 { 340 putDebugChar (buffer[0]); 341 putDebugChar (buffer[1]); 342 343 return &buffer[3]; 344 } 345 346 return &buffer[0]; 347 } 348 } 349 } 350 } 351 352 /* send the packet in buffer. */ 353 354 static void 355 putpacket (unsigned char *buffer) 356 { 357 unsigned char checksum; 358 int count; 359 unsigned char ch; 360 361 /* $<packet info>#<checksum>. */ 362 do 363 { 364 putDebugChar('$'); 365 checksum = 0; 366 count = 0; 367 368 while (ch = buffer[count]) 369 { 370 putDebugChar(ch); 371 checksum += ch; 372 count += 1; 373 } 374 375 putDebugChar('#'); 376 putDebugChar(hexchars[checksum >> 4]); 377 putDebugChar(hexchars[checksum & 0xf]); 378 379 } 380 while (getDebugChar() != '+'); 381 } 382 383 /* Indicate to caller of mem2hex or hex2mem that there has been an 384 error. */ 385 static volatile int mem_err = 0; 386 387 /* Convert the memory pointed to by mem into hex, placing result in buf. 388 * Return a pointer to the last char put in buf (null), in case of mem fault, 389 * return 0. 390 * If MAY_FAULT is non-zero, then we will handle memory faults by returning 391 * a 0, else treat a fault like any other fault in the stub. 392 */ 393 394 static unsigned char * 395 mem2hex (unsigned char *mem, unsigned char *buf, int count, int may_fault) 396 { 397 unsigned char ch; 398 399 set_mem_fault_trap(may_fault); 400 401 while (count-- > 0) 402 { 403 ch = *mem++; 404 if (mem_err) 405 return 0; 406 *buf++ = hexchars[ch >> 4]; 407 *buf++ = hexchars[ch & 0xf]; 408 } 409 410 *buf = 0; 411 412 set_mem_fault_trap(0); 413 414 return buf; 415 } 416 417 /* convert the hex array pointed to by buf into binary to be placed in mem 418 * return a pointer to the character AFTER the last byte written */ 419 420 static char * 421 hex2mem (unsigned char *buf, unsigned char *mem, int count, int may_fault) 422 { 423 int i; 424 unsigned char ch; 425 426 set_mem_fault_trap(may_fault); 427 428 for (i=0; i<count; i++) 429 { 430 ch = hex(*buf++) << 4; 431 ch |= hex(*buf++); 432 *mem++ = ch; 433 if (mem_err) 434 return 0; 435 } 436 437 set_mem_fault_trap(0); 438 439 return mem; 440 } 441 442 /* This table contains the mapping between SPARC hardware trap types, and 443 signals, which are primarily what GDB understands. It also indicates 444 which hardware traps we need to commandeer when initializing the stub. */ 445 446 static struct hard_trap_info 447 { 448 unsigned char tt; /* Trap type code for SPARClite */ 449 unsigned char signo; /* Signal that we map this trap into */ 450 } hard_trap_info[] = { 451 {1, SIGSEGV}, /* instruction access error */ 452 {2, SIGILL}, /* privileged instruction */ 453 {3, SIGILL}, /* illegal instruction */ 454 {4, SIGEMT}, /* fp disabled */ 455 {36, SIGEMT}, /* cp disabled */ 456 {7, SIGBUS}, /* mem address not aligned */ 457 {9, SIGSEGV}, /* data access exception */ 458 {10, SIGEMT}, /* tag overflow */ 459 {128+1, SIGTRAP}, /* ta 1 - normal breakpoint instruction */ 460 {0, 0} /* Must be last */ 461 }; 462 463 /* Set up exception handlers for tracing and breakpoints */ 464 465 void 466 set_debug_traps (void) 467 { 468 struct hard_trap_info *ht; 469 470 for (ht = hard_trap_info; ht->tt && ht->signo; ht++) 471 exceptionHandler(ht->tt, trap_low); 472 473 initialized = 1; 474 } 475 476 asm (" 477 ! Trap handler for memory errors. This just sets mem_err to be non-zero. It 478 ! assumes that %l1 is non-zero. This should be safe, as it is doubtful that 479 ! 0 would ever contain code that could mem fault. This routine will skip 480 ! past the faulting instruction after setting mem_err. 481 482 .text 483 .align 4 484 485 _fltr_set_mem_err: 486 sethi %hi(_mem_err), %l0 487 st %l1, [%l0 + %lo(_mem_err)] 488 jmpl %l2, %g0 489 rett %l2+4 490 "); 491 492 static void 493 set_mem_fault_trap (int enable) 494 { 495 extern void fltr_set_mem_err(); 496 mem_err = 0; 497 498 if (enable) 499 exceptionHandler(9, fltr_set_mem_err); 500 else 501 exceptionHandler(9, trap_low); 502 } 503 504 /* Convert the SPARC hardware trap type code to a unix signal number. */ 505 506 static int 507 computeSignal (int tt) 508 { 509 struct hard_trap_info *ht; 510 511 for (ht = hard_trap_info; ht->tt && ht->signo; ht++) 512 if (ht->tt == tt) 513 return ht->signo; 514 515 return SIGHUP; /* default for things we don't know about */ 516 } 517 518 /* 519 * While we find nice hex chars, build an int. 520 * Return number of chars processed. 521 */ 522 523 static int 524 hexToInt(char **ptr, int *intValue) 525 { 526 int numChars = 0; 527 int hexValue; 528 529 *intValue = 0; 530 531 while (**ptr) 532 { 533 hexValue = hex(**ptr); 534 if (hexValue < 0) 535 break; 536 537 *intValue = (*intValue << 4) | hexValue; 538 numChars ++; 539 540 (*ptr)++; 541 } 542 543 return (numChars); 544 } 545 546 /* 547 * This function does all command procesing for interfacing to gdb. It 548 * returns 1 if you should skip the instruction at the trap address, 0 549 * otherwise. 550 */ 551 552 extern void breakinst(); 553 554 static void 555 handle_exception (unsigned long *registers) 556 { 557 int tt; /* Trap type */ 558 int sigval; 559 int addr; 560 int length; 561 char *ptr; 562 unsigned long *sp; 563 564 /* First, we must force all of the windows to be spilled out */ 565 566 asm(" save %sp, -64, %sp 567 save %sp, -64, %sp 568 save %sp, -64, %sp 569 save %sp, -64, %sp 570 save %sp, -64, %sp 571 save %sp, -64, %sp 572 save %sp, -64, %sp 573 save %sp, -64, %sp 574 restore 575 restore 576 restore 577 restore 578 restore 579 restore 580 restore 581 restore 582 "); 583 584 if (registers[PC] == (unsigned long)breakinst) 585 { 586 registers[PC] = registers[NPC]; 587 registers[NPC] += 4; 588 } 589 590 sp = (unsigned long *)registers[SP]; 591 592 tt = (registers[TBR] >> 4) & 0xff; 593 594 /* reply to host that an exception has occurred */ 595 sigval = computeSignal(tt); 596 ptr = remcomOutBuffer; 597 598 *ptr++ = 'T'; 599 *ptr++ = hexchars[sigval >> 4]; 600 *ptr++ = hexchars[sigval & 0xf]; 601 602 *ptr++ = hexchars[PC >> 4]; 603 *ptr++ = hexchars[PC & 0xf]; 604 *ptr++ = ':'; 605 ptr = mem2hex((char *)®isters[PC], ptr, 4, 0); 606 *ptr++ = ';'; 607 608 *ptr++ = hexchars[FP >> 4]; 609 *ptr++ = hexchars[FP & 0xf]; 610 *ptr++ = ':'; 611 ptr = mem2hex(sp + 8 + 6, ptr, 4, 0); /* FP */ 612 *ptr++ = ';'; 613 614 *ptr++ = hexchars[SP >> 4]; 615 *ptr++ = hexchars[SP & 0xf]; 616 *ptr++ = ':'; 617 ptr = mem2hex((char *)&sp, ptr, 4, 0); 618 *ptr++ = ';'; 619 620 *ptr++ = hexchars[NPC >> 4]; 621 *ptr++ = hexchars[NPC & 0xf]; 622 *ptr++ = ':'; 623 ptr = mem2hex((char *)®isters[NPC], ptr, 4, 0); 624 *ptr++ = ';'; 625 626 *ptr++ = hexchars[O7 >> 4]; 627 *ptr++ = hexchars[O7 & 0xf]; 628 *ptr++ = ':'; 629 ptr = mem2hex((char *)®isters[O7], ptr, 4, 0); 630 *ptr++ = ';'; 631 632 *ptr++ = 0; 633 634 putpacket(remcomOutBuffer); 635 636 while (1) 637 { 638 remcomOutBuffer[0] = 0; 639 640 ptr = getpacket(); 641 switch (*ptr++) 642 { 643 case '?': 644 remcomOutBuffer[0] = 'S'; 645 remcomOutBuffer[1] = hexchars[sigval >> 4]; 646 remcomOutBuffer[2] = hexchars[sigval & 0xf]; 647 remcomOutBuffer[3] = 0; 648 break; 649 650 case 'd': /* toggle debug flag */ 651 break; 652 653 case 'g': /* return the value of the CPU registers */ 654 { 655 ptr = remcomOutBuffer; 656 ptr = mem2hex((char *)registers, ptr, 16 * 4, 0); /* G & O regs */ 657 ptr = mem2hex(sp + 0, ptr, 16 * 4, 0); /* L & I regs */ 658 memset(ptr, '0', 32 * 8); /* Floating point */ 659 mem2hex((char *)®isters[Y], 660 ptr + 32 * 4 * 2, 661 8 * 4, 662 0); /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ 663 } 664 break; 665 666 case 'G': /* set the value of the CPU registers - return OK */ 667 { 668 unsigned long *newsp, psr; 669 670 psr = registers[PSR]; 671 672 hex2mem(ptr, (char *)registers, 16 * 4, 0); /* G & O regs */ 673 hex2mem(ptr + 16 * 4 * 2, sp + 0, 16 * 4, 0); /* L & I regs */ 674 hex2mem(ptr + 64 * 4 * 2, (char *)®isters[Y], 675 8 * 4, 0); /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ 676 677 /* See if the stack pointer has moved. If so, then copy the saved 678 locals and ins to the new location. This keeps the window 679 overflow and underflow routines happy. */ 680 681 newsp = (unsigned long *)registers[SP]; 682 if (sp != newsp) 683 sp = memcpy(newsp, sp, 16 * 4); 684 685 /* Don't allow CWP to be modified. */ 686 687 if (psr != registers[PSR]) 688 registers[PSR] = (psr & 0x1f) | (registers[PSR] & ~0x1f); 689 690 strcpy(remcomOutBuffer,"OK"); 691 } 692 break; 693 694 case 'm': /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ 695 /* Try to read %x,%x. */ 696 697 if (hexToInt(&ptr, &addr) 698 && *ptr++ == ',' 699 && hexToInt(&ptr, &length)) 700 { 701 if (mem2hex((char *)addr, remcomOutBuffer, length, 1)) 702 break; 703 704 strcpy (remcomOutBuffer, "E03"); 705 } 706 else 707 strcpy(remcomOutBuffer,"E01"); 708 break; 709 710 case 'M': /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ 711 /* Try to read '%x,%x:'. */ 712 713 if (hexToInt(&ptr, &addr) 714 && *ptr++ == ',' 715 && hexToInt(&ptr, &length) 716 && *ptr++ == ':') 717 { 718 if (hex2mem(ptr, (char *)addr, length, 1)) 719 strcpy(remcomOutBuffer, "OK"); 720 else 721 strcpy(remcomOutBuffer, "E03"); 722 } 723 else 724 strcpy(remcomOutBuffer, "E02"); 725 break; 726 727 case 'c': /* cAA..AA Continue at address AA..AA(optional) */ 728 /* try to read optional parameter, pc unchanged if no parm */ 729 730 if (hexToInt(&ptr, &addr)) 731 { 732 registers[PC] = addr; 733 registers[NPC] = addr + 4; 734 } 735 736 /* Need to flush the instruction cache here, as we may have deposited a 737 breakpoint, and the icache probably has no way of knowing that a data ref to 738 some location may have changed something that is in the instruction cache. 739 */ 740 741 flush_i_cache(); 742 return; 743 744 /* kill the program */ 745 case 'k' : /* do nothing */ 746 break; 747 #if 0 748 case 't': /* Test feature */ 749 asm (" std %f30,[%sp]"); 750 break; 751 #endif 752 case 'r': /* Reset */ 753 asm ("call 0 754 nop "); 755 break; 756 } /* switch */ 757 758 /* reply to the request */ 759 putpacket(remcomOutBuffer); 760 } 761 } 762 763 /* This function will generate a breakpoint exception. It is used at the 764 beginning of a program to sync up with a debugger and can be used 765 otherwise as a quick means to stop program execution and "break" into 766 the debugger. */ 767 768 void 769 breakpoint (void) 770 { 771 if (!initialized) 772 return; 773 774 asm(" .globl _breakinst 775 776 _breakinst: ta 1 777 "); 778 } 779