1% Copyright (C) 2012-2017,2018 John E. Davis 2% 3% This file is part of the S-Lang Library and may be distributed under the 4% terms of the GNU General Public License. See the file COPYING for 5% more information. 6%--------------------------------------------------------------------------- 7% 8% This file implements the core a simple debugger. It needs to be wrapped 9% by routines that implement the Debugger_Methods. 10% 11% Public functions: 12% sldb_methods() 13% sldb_stop (); 14% sldb_start (); 15% sldb_set_breakpoint (); 16% 17% 18% Notes: 19% 20% If a file was not compiled with bos/eos hooks, then debugging of 21% it may be limited due to the lack of line number information. 22% 23% 24require ("print"); 25 26private variable Debugger_Methods = struct 27{ 28 list, % list (file, linemin, linemax) 29 vmessage, % vmessage (fmt, args...) 30 read_input, % input = read_input (prompt, default) 31 pprint, % pprint(obj) % pprint the value of an object 32 quit, % quit (and kill) the program 33 exit % exit the debugger but not the program 34}; 35 36private define output () 37{ 38 variable args = __pop_args (_NARGS); 39 (@Debugger_Methods.vmessage)(__push_args(args)); 40} 41 42private define quit_method () 43{ 44 output ("Program exiting\n"); 45 exit (0); 46} 47Debugger_Methods.quit = &quit_method; 48 49private define exit_method () 50{ 51 output ("Leaving the debugger\n"); 52} 53Debugger_Methods.exit = &exit_method; 54 55define sldb_methods () 56{ 57 return Debugger_Methods; 58} 59define sldb_initialize (); % This should be overridden 60 61define sldb_stop(); 62 63private variable Depth = 0; 64private variable Stop_Depth = 0; 65private variable Debugger_Step = 0; 66private variable STEP_NEXT = 1; 67private variable STEP_STEP = 2; 68private variable STEP_FINISH = 3; 69private variable STEP_EXIT = 4; 70private variable Breakpoints = NULL; 71private variable Breakpoint_Number = 1; 72private variable Current_Frame; 73private variable Max_Current_Frame; 74private variable Last_List_Line = 0; 75private variable Last_Cmd_Line = NULL; 76private variable Last_Cmd = NULL; 77private variable Prompt = "(SLdb) "; 78private variable Startup_PID = NULL; 79 80private define new_breakpoints () 81{ 82 Breakpoints = Assoc_Type[Int_Type, 0]; 83 Breakpoint_Number = 1; 84} 85 86private define check_breakpoints () 87{ 88 if (Breakpoints == NULL) 89 new_breakpoints (); 90} 91 92define sldb_set_breakpoint (pos) 93{ 94 variable bp; 95 96 check_breakpoints (); 97 bp = Breakpoint_Number; 98 Breakpoints[pos] = bp; 99 Breakpoint_Number++; 100 101 output ("breakpoint #%d set at %s\n", bp, pos); 102 return bp; 103} 104 105private define make_breakpoint_name (file, line) 106{ 107 return sprintf ("%S:%d", file, line); 108} 109 110private define eval_in_frame (frame, expr, num_on_stack, print_fun) 111{ 112 variable boseos = _boseos_info; 113 variable bofeof = _bofeof_info; 114 expr = sprintf ("_boseos_info=0; _bofeof_info=0; _use_frame_namespace(%d); %s; _bofeof_info=%d; _boseos_info=%d;", 115 frame, expr, bofeof, boseos); 116 variable depth = _stkdepth () - num_on_stack; 117 eval (expr); 118 119 variable n = _stkdepth () - depth; 120 if (print_fun == NULL) 121 return n; 122 123 loop (n) 124 { 125 variable val = (); 126 (@print_fun) (val); 127 } 128 return n; 129} 130 131private define break_cmd (cmd, args, file, line) 132{ 133 variable bp; 134 if (strlen (args) == 0) 135 bp = make_breakpoint_name (file, line); 136 else if (_slang_guess_type (args) == Int_Type) 137 bp = make_breakpoint_name (file, integer (args)); 138 else 139 { 140 bp = args; 141 if (0 == is_substr (args, ":")) 142 { 143 } 144 } 145 146 () = sldb_set_breakpoint (bp); 147 return 0; 148} 149 150private define display_file_and_line (file, linemin, linemax) 151{ 152 if (file == "***string***") 153 return; 154 155 if (linemin < 1) 156 linemin = 1; 157 if (linemax < linemin) 158 linemax = linemin; 159 160 (@Debugger_Methods.list)(file, linemin, linemax); 161} 162 163private define finish_cmd (cmd, args, file, line) 164{ 165 %variable fun = _get_frame_info (Max_Current_Frame).function; 166 variable fun = _get_frame_info (Current_Frame).function; 167 if (fun == NULL) fun = "<top-level>"; 168 output ("Run until exit from %s\n", fun); 169 Debugger_Step = STEP_FINISH; 170 Stop_Depth = Depth-1; 171 return 1; 172} 173 174private define next_cmd (cmd, args, file, line) 175{ 176 Debugger_Step = STEP_NEXT; 177 Stop_Depth = Depth; 178 return 1; 179} 180 181private define step_cmd (cmd, args, file, line) 182{ 183 Debugger_Step = STEP_STEP; 184 Stop_Depth = Depth + 1; 185 return 1; 186} 187 188private define delete_cmd (cmd, args, file, line) 189{ 190 variable bp = make_breakpoint_name (file, line); 191 variable n = Breakpoints[bp]; 192 if (n) 193 { 194 Breakpoints[bp] = 0; 195 output ("Deleted breakpoint #%d\n", n); 196 return 0; 197 } 198 if (args == "") 199 { 200 new_breakpoints (); 201 output ("Deleted all breakpoints\n"); 202 return 0; 203 } 204 205 variable keys = assoc_get_keys (Breakpoints); 206 variable vals = assoc_get_values (Breakpoints); 207 208 foreach (eval (sprintf ("[%s]", args))) 209 { 210 bp = (); 211 variable i = wherefirst (vals == bp); 212 if (i == NULL) 213 continue; 214 assoc_delete_key (Breakpoints, keys[i]); 215 output ("Deleted breakpoint %d\n", bp); 216 } 217 return 0; 218} 219 220private define continue_cmd (cmd, args, file, line) 221{ 222 Debugger_Step = 0; 223 return 1; 224} 225 226private define watch_cmd (cmd, args, file, line) 227{ 228 output ("%s is not implemented\n", cmd); 229 return 0; 230} 231 232private define exit_cmd (cmd, args, file, line) 233{ 234 sldb_stop (); 235 (@Debugger_Methods.exit) (); 236 return 1; 237} 238 239private define quit_cmd (cmd, args, file, line) 240{ 241 variable prompt = "Are you sure you want to quit (and kill) the program? (y/n) "; 242 variable y = (@Debugger_Methods.read_input)(prompt, NULL); 243 y = strup (y); 244 !if (strlen (y)) 245 return 0; 246 if (y[0] != 'Y') 247 { 248 output ("Try using 'exit' to leave the debugger"); 249 return 0; 250 } 251 sldb_stop (); 252 (@Debugger_Methods.quit)(); 253 return 1; 254} 255 256private define simple_print (v) 257{ 258 if (length (v) <= 1) 259 print (v, &v); 260 output ("%S\n", v); 261} 262 263private define pretty_print (v) 264{ 265 variable p = Debugger_Methods.pprint; 266 if (p == NULL) 267 { 268 simple_print (v); 269 return; 270 } 271 (@p)(v); 272} 273 274private define print_expr (print_fun, expr) 275{ 276 variable info = _get_frame_info (Current_Frame); 277 variable localvars = info.locals; 278 279 if (localvars == NULL) 280 { 281 () = eval_in_frame (Current_Frame, expr, 0, print_fun); 282 return; 283 } 284 285 % Create a dummy function and call it with the values of the local-vars 286 % The idea is that variables that are initialized will be arguments, and 287 % others will just be locals 288 variable a = Assoc_Type[]; 289 foreach (localvars) 290 { 291 variable lvar = (); 292 try 293 { 294 a[lvar] = _get_frame_variable (Current_Frame, lvar); 295 } 296 catch VariableUninitializedError; 297 } 298 variable inited_vars = assoc_get_keys (a); 299 variable uninited_vars = String_Type[0]; 300 foreach (localvars) 301 { 302 lvar = (); 303 if (assoc_key_exists (a, lvar)) 304 continue; 305 uninited_vars = [uninited_vars, lvar]; 306 } 307 if (length (uninited_vars)) 308 uninited_vars = strcat ("variable ", strjoin (uninited_vars, ","), ";"); 309 else 310 uninited_vars = ""; 311 312 variable fmt = "private define %s (%s) { %s %s; }"; 313 variable dummy = "__debugger_print_function"; 314 variable fun = sprintf (fmt, dummy, strjoin (inited_vars, ","), 315 uninited_vars, expr); 316 () = eval_in_frame (Current_Frame, fun, 0, print_fun); 317 318 % push values to the stack and call the dummy function 319 foreach lvar (inited_vars) 320 { 321 a[lvar]; 322 } 323 () = eval_in_frame (Current_Frame, dummy, length (inited_vars), print_fun); 324} 325 326private define print_cmd (cmd, args, file, line) 327{ 328 print_expr (&simple_print, args); 329 return 0; 330} 331 332private define pprint_cmd (cmd, args, file, line) 333{ 334 print_expr (&pretty_print, args); 335 return 0; 336} 337 338private define list_cmd (cmd, args, file, line) 339{ 340 variable dline = 5; 341 line = int (line); 342 343 if (Last_Cmd == cmd) 344 line = Last_List_Line + 1 + dline; 345 346 variable line_min = line - dline; 347 variable line_max = line + dline; 348 349 if (strlen (args)) 350 { 351 line_min = integer (args); 352 line_max = line_min + 10; 353 } 354 355 display_file_and_line (file, line_min, line_max); 356 Last_List_Line = line_max; 357 return 0; 358} 359 360private define print_frame_info (f, print_line) 361{ 362 variable info = _get_frame_info (f); 363 variable file = info.file; 364 variable function = info.function; 365 variable line = info.line; 366 367 if (function == NULL) 368 function = "<top-level frame>"; 369 370 output("#%d %S:%d:%s\n", Max_Current_Frame-f, file, line, function); 371 if (print_line) 372 display_file_and_line (file, line, line); 373} 374 375private define up_cmd (cmd, args, file, line) 376{ 377 if (Current_Frame == 1) 378 { 379 output ("Can't go up\n"); 380 return 0; 381 } 382 Current_Frame--; 383 print_frame_info (Current_Frame, 1); 384 return 0; 385} 386 387private define down_cmd (cmd, args, file, line) 388{ 389 if (Current_Frame == Max_Current_Frame) 390 { 391 output ("At inner-most frame\n"); 392 return 0; 393 } 394 Current_Frame++; 395 print_frame_info (Current_Frame, 1); 396 return 0; 397} 398 399private define where_cmd (cmd, args, file, line) 400{ 401 variable i = Current_Frame; 402 while (i > 0) 403 { 404 print_frame_info (i, 0); 405 i--; 406 } 407 return 0; 408} 409 410#ifexists fpu_clear_except_bits 411private variable WatchFPU_Flags = 0; 412#endif 413 414private define watchfpu_cmd (cmd, args, file, line) 415{ 416#ifexists fpu_clear_except_bits 417 fpu_clear_except_bits (); 418 if (args == "") 419 { 420 WatchFPU_Flags = FE_ALL_EXCEPT; 421 output ("Watching all FPU exceptions:\n"); 422 output (" FE_DIVBYZERO | FE_INEXACT | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW\n"); 423 return 0; 424 } 425 WatchFPU_Flags = eval (args); 426 if (WatchFPU_Flags == 0) 427 { 428 output ("Watching FPU exceptions disabled\n"); 429 } 430 return 0; 431#else 432 output ("watchfpu is not supported on this OS\n"); 433 return 0; 434#endif 435} 436 437private variable Cmd_Table = Assoc_Type [Ref_Type]; 438Cmd_Table["finish"] = &finish_cmd; 439Cmd_Table["next"] = &next_cmd; 440Cmd_Table["step"] = &step_cmd; 441Cmd_Table["break"] = &break_cmd; 442Cmd_Table["delete"] = &delete_cmd; 443Cmd_Table["cont"] = &continue_cmd; 444Cmd_Table["continue"] = &continue_cmd; 445Cmd_Table["watch"] = &watch_cmd; 446Cmd_Table["list"] = &list_cmd; 447Cmd_Table["pprint"] = &pprint_cmd; 448Cmd_Table["print"] = &print_cmd; 449Cmd_Table["exit"] = &exit_cmd; 450Cmd_Table["quit"] = &quit_cmd; 451Cmd_Table["up"] = &up_cmd; 452Cmd_Table["down"] = &down_cmd; 453Cmd_Table["where"] = &where_cmd; 454Cmd_Table["watchfpu"] = &watchfpu_cmd; 455 456% Aliases 457define sldb_add_alias (alias, cmd) 458{ 459 if (0 == assoc_key_exists (Cmd_Table, cmd)) 460 return; 461 Cmd_Table[alias] = Cmd_Table[cmd]; 462} 463sldb_add_alias ("b", "break"); 464sldb_add_alias ("c", "continue"); 465sldb_add_alias ("d", "delete"); 466sldb_add_alias ("h", "help"); 467sldb_add_alias ("l", "list"); 468sldb_add_alias ("n", "next"); 469sldb_add_alias ("p", "print"); 470sldb_add_alias ("pp", "pprint"); 471sldb_add_alias ("q", "quit"); 472sldb_add_alias ("s", "step"); 473 474private define help_cmd (cmd, args, file, line) 475{ 476 output ("Commands:\n"); 477 variable cmds = assoc_get_keys (Cmd_Table); 478 cmds = cmds[array_sort(cmds)]; 479 foreach cmd (cmds) 480 output (" %s\n", cmd); 481 return 0; 482} 483Cmd_Table["help"] = &help_cmd; 484 485private define sigint_handler (sig) 486{ 487 Debugger_Step = STEP_STEP; 488 Stop_Depth = INT_MAX; 489 if (Depth == 0) 490 throw UserBreakError; 491} 492 493private variable Old_Sigint_Handler; 494private define deinit_sigint_handler () 495{ 496#ifexists SIGINT 497 signal (SIGINT, Old_Sigint_Handler); 498#endif 499} 500 501private define init_sigint_handler () 502{ 503#ifexists SIGINT 504 variable old; 505 signal (SIGINT, &sigint_handler, &old); 506 ifnot (_eqs(old, &sigint_handler)) 507 Old_Sigint_Handler = old; 508#endif 509} 510 511private variable Last_Frame = -1; 512private variable Last_Function = NULL; 513 514private define debugger_input_loop () 515{ 516 variable max_frame = Max_Current_Frame; 517 %Last_Cmd_Line = NULL; 518 %Last_Cmd = NULL; 519 forever 520 { 521 variable e; 522 try (e) 523 { 524 %deinit_sigint_handler (); 525 init_sigint_handler (); 526 Debugger_Step = 0; 527 528 if (Current_Frame > max_frame) 529 { 530 Max_Current_Frame = max_frame; 531 Current_Frame = max_frame; 532 } 533 variable info = _get_frame_info (Current_Frame); 534 variable file = info.file; 535 variable line = info.line; 536 537 variable cmdline, cmd, cmd_parm; 538 forever 539 { 540 variable prompt = Prompt; 541#iffalse 542 prompt = "Depth=${Depth},Stop_Depth=${Stop_Depth} $prompt"$; 543#endif 544 cmdline = (@Debugger_Methods.read_input)(prompt, Last_Cmd_Line); 545 if (cmdline == NULL) 546 throw ReadError, "NULL input returned"; 547 548 cmdline = strtrim (cmdline); 549 variable tokens = strtok (cmdline, " \t"); 550 if (length (tokens)) 551 { 552 cmd = tokens[0]; 553 break; 554 } 555 } 556 cmd_parm = substr (cmdline, 1+strlen(cmd), -1); 557 cmd_parm = strtrim (cmd_parm, "\t "); 558 559 if (0 == assoc_key_exists (Cmd_Table, cmd)) 560 { 561 output("%s is unknown. Try help.\n", cmd); 562 Last_Cmd_Line = NULL; 563 Last_Cmd = NULL; 564 continue; 565 } 566 variable ret = (@Cmd_Table[cmd])(cmd, cmd_parm, file, line); 567 Last_Cmd_Line = cmdline; 568 Last_Cmd = cmd; 569 if (ret) return; 570 } 571 catch IOError: 572 { 573 sldb_stop (); 574 vmessage ("Caught IOError exception -- stopping the debugger: %S",e.message); 575 return; 576 } 577 catch AnyError: 578 { 579 output("Caught exception:%S:%S:%S:%S\n", e.file, e.line, e.function, e.message); 580 } 581 } 582} 583 584private define do_debug (file, line, bp_num) 585{ 586 %output ("do_debug: file=%S, line=%S, fun=%S\n", file, line, bp_num); 587 Current_Frame = _get_frame_depth ()-2; 588 Max_Current_Frame = Current_Frame; 589 %vmessage ("Current_Frame=%d\n", Current_Frame); 590 % We do not want the debug_hook catching errors here 591 variable debug_hook = _set_debug_hook (NULL); 592 EXIT_BLOCK 593 { 594 if (Debugger_Step != STEP_EXIT) 595 { 596 () = _set_debug_hook (debug_hook); 597 init_sigint_handler (); 598 } 599 } 600 601 variable info = _get_frame_info (Current_Frame); 602 if (file == NULL) 603 { 604 file = info.file; 605 if (file == NULL) 606 file = "???"; 607 } 608 if (line == NULL) 609 line = info.line; 610 611 variable fun = info.function; 612 if (fun == NULL) fun = "<top-level>"; 613 614 if ((file == "<stdin>"))% or (file == "***string***")) 615 { 616 Last_Frame = Current_Frame; 617 Last_Function = fun; 618 Debugger_Step = STEP_NEXT; 619 Stop_Depth = Depth-1; 620 return; 621 } 622 if (bp_num) 623 { 624 output ("Breakpoint %d, %s\n at %s:%d\n", abs(bp_num), fun, file, line); 625 } 626 else if ((Last_Frame != Current_Frame) or (Last_Function != fun)) 627 { 628 output ("%s at %s:%d\n", fun, file, line); 629 } 630 display_file_and_line (file, line, line); 631 Last_Frame = Current_Frame; 632 Last_Function = fun; 633 634 debugger_input_loop (); 635} 636 637private define bos_handler (file, line) 638{ 639 %output ("bos: depth=%d, stop_depth=%d, fun=%S\n", Depth,Stop_Depth,_get_frame_info(-1).function); 640 variable pos = make_breakpoint_name (file, line); 641 variable bp = Breakpoints[pos]; 642 643 if (bp) 644 { 645 if (bp < 0) Breakpoints[pos] = 0; % clear temporary breakpoint 646 do_debug (file, line, bp); 647 return; 648 } 649 650 if (Depth > Stop_Depth) 651 return; 652 653 if (Debugger_Step == 0) 654 return; 655 656#iffalse 657 if (Debugger_Step == STEP_FINISH) 658 return; 659 660 if (Debugger_Step == STEP_NEXT) 661 { 662 if (Depth > Stop_Depth) 663 return; 664 } 665#endif 666 do_debug (file, line, bp); 667} 668 669% end of statement handler: tracks the recursion depth, 670% to be able to step over function calls (using 'Next' Command) 671private define eos_handler() 672{ 673#ifexists fpu_clear_except_bits 674 if (WatchFPU_Flags) 675 { 676 variable bits = fpu_test_except_bits (WatchFPU_Flags); 677 if (bits) 678 { 679 variable info = _get_frame_info (-1); 680 variable str = String_Type[0]; 681 if (bits & FE_DIVBYZERO) str = [str,"FE_DIVBYZERO"]; 682 if (bits & FE_INEXACT) str = [str,"FE_INEXACT"]; 683 if (bits & FE_INVALID) str = [str,"FE_INVALID"]; 684 if (bits & FE_OVERFLOW) str = [str,"FE_OVERFLOW"]; 685 if (bits & FE_UNDERFLOW) str = [str,"FE_UNDERFLOW"]; 686 output ("*** FPU exception bits set: %s\n", strjoin(str, ",")); 687 output ("Entering the debugger.\n"); 688 fpu_clear_except_bits (); 689 do_debug (info.file, info.line, 0); 690 } 691 } 692#endif 693 %output ("eos: depth=%d\n", Depth); 694} 695 696private define bof_handler (fun, file) 697{ 698 %output ("Entering BOF: %S, %S, %S", fun, file, line); 699 Depth++; 700 701 variable bp = Breakpoints[fun]; 702 if (bp) 703 { 704 output ("Breakpoint %d, %s\n", abs(bp), fun); 705 if (bp < 0) Breakpoints[fun] = 0; % clear temporary breakpoint 706 Debugger_Step = STEP_NEXT; 707 Stop_Depth = Depth; 708 } 709} 710 711private define eof_handler () 712{ 713 %output ("Leaving EOF"); 714 Depth--; 715 if (Debugger_Step) 716 { 717 if (Debugger_Step == STEP_FINISH) 718 { 719 if (Depth == Stop_Depth) 720 { 721 Debugger_Step = 0; 722 %variable info = _get_frame_info (_get_frame_depth ()-2); 723 %do_debug (info.file, info.line, 0); 724 do_debug (NULL, NULL, 0); 725 } 726 } 727 if ((Debugger_Step == STEP_NEXT) and (Stop_Depth > Depth)) 728 Stop_Depth = Depth; 729 } 730} 731 732private define debug_hook (file, line) 733{ 734 if (Startup_PID != getpid()) 735 return; 736 737 %variable file = e.file, line = e.line; 738 variable e = __get_exception_info (); 739 output ("Received %s error. Entering the debugger\n", e.descr); 740 check_breakpoints (); 741 do_debug (file, line, 0); 742} 743 744define sldb_enable () 745{ 746 ()=_set_bos_handler (&bos_handler); 747 ()=_set_eos_handler (&eos_handler); 748 ()=_set_bof_handler (&bof_handler); 749 ()=_set_eof_handler (&eof_handler); 750 ()=_set_debug_hook (&debug_hook); 751 752 check_breakpoints (); 753 Depth = 0; 754 Debugger_Step = STEP_STEP; 755 init_sigint_handler (); 756 _traceback = 1; 757 _bofeof_info = 1; 758 _boseos_info = 3; 759} 760 761% Usage Forms: 762% sldb (); 763% sldb (file); 764% sldb (file, ns); 765% The namespace semantics are the same as that of require. 766define sldb () 767{ 768 Startup_PID = getpid (); 769 sldb_initialize (); 770 771 sldb_enable (); 772 if (_NARGS == 0) 773 { 774 Current_Frame = _get_frame_depth ()-1; 775 Max_Current_Frame = Current_Frame; 776 debugger_input_loop (); 777 return; 778 } 779 variable args = __pop_args (_NARGS); 780 require (__push_args (args)); 781#iffalse 782 variable ns = current_namespace (); 783 if (_NARGS == 2) 784 785 ns = (); 786 variable file = (); 787 788 if (ns == NULL) 789 () = evalfile (file); 790 else 791 () = evalfile (file, ns); 792#endif 793} 794 795% remove bos and eos handlers. 796define sldb_stop () 797{ 798 ()=_set_bos_handler (NULL); 799 ()=_set_eos_handler (NULL); 800 ()=_set_bof_handler (NULL); 801 ()=_set_eof_handler (NULL); 802 ()=_set_debug_hook (NULL); 803 deinit_sigint_handler (); 804 _bofeof_info = 0; 805 _boseos_info = 0; 806 Debugger_Step = STEP_EXIT; 807} 808 809provide ("sldbcore"); 810