/* PL/I symbolic debugger module handling breakpoint-related commands */ gocmd: procedure; %include 'knldef.pli'; %include 'scandef.pli'; %include 'workdef.pli'; %include 'initdef.pli'; %include 'putdef.pli'; %include 'segdef.pli'; %include 'moddef.pli'; %include 'brksymdef.pli'; %include 'breakdef.pli'; %include 'statement.inc'; %include 'overlaym.inc'; %include 'tiqio.inc'; %include 'plioctal.inc'; %include 'findbpt.inc'; /* misc external debugger routines: */ dcl userqio entry(fixed bin(15), char(*), fixed bin(15), fixed bin(15)); /* subroutines from PLIUTL & PLIMMR: */ dcl ior entry(fixed bin(15), fixed bin(15)) returns(fixed bin(15)); /* code packet pointers defined in debasm: */ dcl ( dogo0, dogo1, dogo2, dobrek, dostrt, doovly ) pointer external; /* offsets (subscripts) in packet, defined in debasm: */ dcl ( odsw, ogo0ps, ogo0pc, ogo0ds, ogo0fp, ogo1ps, ogo1pc, ogo1in, ogo1ds, ogo1fp, ogo2pc, ogo2ds, obrkpc, obrkps, obrksp, obrkfp, obrkr5, ovlysg ) fixed bin(15) external; /* misc variables defined in debasm: */ dcl ( bpt_instruction, psTbit ) fixed bin(15) external; dcl ( io_att, io_det, /** cut pasall stuff (see explanation below): sf_gmc, sf_smc, set_passall, set_nopassall,**/ ie_dna) fixed bin(15) external; dcl ibpt fixed bin(15), /* variables are made static only because static references often take less code */ (i, j, x) fixed bin(15) static; /*scratch variables*/ /**************************************************************************************************************/ /************************************* * End (of do-group or procedure) * *************************************/ dcl were_in_do_group fixed bin(1); if nxtokenis('END') then do; if ^nxtokenis(';') ! (nextp <= length(commandline)) then do; nextp = ignore_rest_of_line; /*force rest of line to be ignored*/ call warning('no command may follow END on the same line'); end; if (scan_state_nesting_level > 0) & (debprocedure_define_level = 0) then do; were_in_do_group = in_do_group; call pop_scan_state; if were_in_do_group & (scan_state_nesting_level = 0) then goto break_message; /*end of do-group*/ else goto command_done; /*continue in calling debprocedure/do_group*/ end; if debprocedure_define_level <= 0 then call error('END without prior DO or PROCEDURE'); debprocedure_define_level = debprocedure_define_level - 1; if debprocedure_define_level = 0 then call workwrite(endmark); goto command_done; end; /**************** * Go / Go To * ****************/ /* the code relating to set passall that has been commented out closes the window between the detach of TI by the user task and the attach by the debug task, during which unsolicited input goes to the CLI (if the window is left open). it has been commented out for two reasons. the minor reason is that it slows things down. the major reason is that if something bad happens and the debugger loses control while TI is in passall mode, it will be left in passall mode. the user will then be unable to use control-c to abort things, and will have no clue as to why. the belief is that it is unacceptable to risk having a situation where by using the debugger the user gets into a much bigger pickle than he's already in by having his program not work, even if it is rather unlikely and the result is a relatively likely inconvenience (having the DCL prompt pop up and having to hit control-U & return to get out of it). */ dcl 1 save_cursor_attr static, 2 a fixed bin(7) initial('033'b3), 2 b char(1) initial('7'); dcl 1 restore_cursor_attr static, 2 a fixed bin(7) initial('033'b3), 2 b char(1) initial('8'); dcl 1 turn_off_attr static, 2 a fixed bin(7) initial('033'b3), 2 b char(3) initial('[0m'); dcl dostop pointer external, (ostpds, ostpfp, ostppc, ostpad) fixed bin(15) external; /*defined in debasm*/ dcl loaded_overlay fixed bin(15), (stopping, go_to) fixed bin(1), err fixed bin(15); if false then put skip;/*temp for pli bug-must implicitly declare sysprint before the wrtpas*/ go_to = false; if nxtokenis('GOTO') then goto dogoto; if nxtokenis(' GO G ') then do; statement_address = userpc; /*GO - address is current location*/ if nxtokenis('TO') then do; dogoto: call statement; /*GO TO - get address*/ go_to = true; end; call endscan; userpc = statement_address; stopping = false; if initializing then call fillpacket(dostrt); /*initialization sequence is special (see debmain & debasm)*/ else do; go_or_stop: /* the following loop causes go/goto to terminate a do group & all do's, procedures, & includes nested from it. if this was not done, execution paths could get rather tangled, and in particular a do-group with a GOTO, at a breakpoint in a loop, would pile up an indefinite number of nesting levels (do-groups are otherwise exited only via their END command, which would be left dangling by each instance of GO/GOTO). */ do while(in_do_group); if in_include then close file(includefile); call pop_scan_state; end; if window_top ^= 0 then do; call canwindow; call wrtpas(sysprint,string(restore_cursor_attr)); end; keep_going: if user_attached then do; /**** cut passall stuff: (note tiqio now has five arguments) call tiqio(sf_smc, 'set passall', set_passall); ************/ call detach_ti; /*detach for debug task*/ call userqio(io_att, 'attach', i, j); /*attach by user task **later - restore attach options...*/ /**** cut passall stuff: if user_passall = set_nopassall then call userqio(sf_smc, 'set nopassall', set_nopassall, i); ***********/ end; else do; /***** cut passall stuff: (note tiqio now has five arguments) if user_passall = set_passall then call tiqio(sf_smc, 'set passall', set_passall); *********/ call detach_ti; end; if stopping then do; call fillpacket(dostop); packet(ostpds) = userdsw; packet(ostpfp) = userfp; packet(ostppc) = userpc; packet(ostpad) = stop_address; end; else do; if go_to then ibpt = bpt_not_found; /*GO TO - if there's a breakpoint set there, don't run through it*/ else ibpt = find_breakpoint(userpc, userbreakoverlay); /*GO - see if current breakpoint still set*/ if ibpt = bpt_not_found then do; /*no - just go*/ call fillpacket(dogo0); packet(ogo0ds) = userdsw; packet(ogo0fp) = userfp; packet(ogo0ps) = userps; packet(ogo0pc) = userpc; end; else do; /*yes - must restore instruction, execute it, & restore breakpoint*/ call fillpacket(dogo1); packet(ogo1in) = breakpoint(ibpt).instruction; packet(ogo1ds) = userdsw; packet(ogo1fp) = userfp; packet(ogo1ps) = ior(userps, psTbit); /*set single instruct trap*/ packet(ogo1pc) = userpc; call sendknl; call rcvknl; userdsw = packet(odsw); /*the single instruction could have changed it*/ call fillpacket(dogo2); packet(ogo2ds) = userdsw; packet(ogo2pc) = userpc; /*location of instruction we executed - must set back to bpt*/ end; end; end; call sendknl; /*send the user on his way*/ call rcvknl; /*wait for a breakpoint*/ /*********************** * handle breakpoint * ***********************/ stopping = false; userdsw = packet(odsw); call fillpacket(dobrek); /*get user state*/ call sendknl; call rcvknl; userpc = packet(obrkpc)-2; userps = packet(obrkps); usersp = packet(obrksp); userfp = packet(obrkfp); userr5 = packet(obrkr5); /* the detach-reattach code could be skipped for overlay loader breakpoints and /AFTER breakpoints for improved performance, but then if an error occurs (for example on a send or receive to the kernal), the debugger would hang trying to display the error message. it may be do-able by putting the detach code in an external procedure and having put_skip_list call it if the terminal has not been detached (make sure that all error messages go through put_skip_list). */ /**** cut passall stuff: user_passall = set_nopassall; /*format for get is same as set*/ /** call userqio(sf_gmc, 'get passall status', user_passall, i); call userqio(sf_smc, 'set passall', set_passall, i); *************/ call userqio(io_det, 'detach', i, j); /*make user task detach TI:*/ user_attached = (j ^= ie_dna); /*remember whether user actually had it attached*/ call attach_ti; /*attach for debug task*/ /**** cut passall stuff : (note tiqio now has five arguments) call tiqio(sf_smc, 'set nopassall', set_nopassall); **************/ /* if this is the special breakpoint in the overlay loader, then perform any deferred set/cancel breakpoints, and update the overlay unmapped flags */ if userpc = breakpoint(overlay_loader).location /*if this is the breakpoint in the overlay loader*/ then do; userbreakoverlay = root_segment_descriptor; /*it's in the root (must set for resume logic to work)*/ /***temp kludge logic for skipping the breakpoint if it's a cluster call*******************************/ dcl unsigned entry(fixed bin(15)) returns(fixed bin(31)); if unsigned(getword(usersp+'12'b3)) < unsigned('1000'b3) then goto skip_overlay_stuff; /************************************************************/ call fillpacket(doovly); /*find the overlay segment descriptor address for the overlay that was loaded*/ call sendknl; call rcvknl; loaded_overlay = packet(ovlysg); /***temp skip if clustered call*******************************/ if unsigned(loaded_overlay) < unsigned(root_segment_descriptor) then goto skip_overlay_stuff; /************************************************************/ /* loop through breakpoint table entries applying to this overlay, cancelling breakpoints (in case memory mapped) and setting breakpoints (in case disk resident). note that proper maintenance of the overlay_unmapped flags is contingent on us calling set_a_break here. */ do ibpt = lbound(breakpoint,1) to hbound(breakpoint,1); if breakpoint(ibpt).overlay = loaded_overlay then do; if breakpoint(ibpt).to_be_cancelled then call cancel_a_break(ibpt); else do; /*set it*/ if breakpoint(ibpt).location = to_be_determined /**temp until addr can be determined at SET BREAK time*/ then do; dcl stmtaddr entry(fixed bin(15), fixed bin(15)) returns(fixed bin(15)); /**must split or get sym oflo*/ x = stmtaddr(breakpoint(ibpt).prolog, breakpoint(ibpt).statementn); if x = -1 then do; call workgrab(breakpoint(ibpt).module, scratch_workp); call put_skip_list('***statement ', scratch_workp->workbuf.name,'\',breakpoint(ibpt).statementn, ' not found - breakpoint can''t be set***'); goto overlay_next; end; breakpoint(ibpt).location = x; end; call set_a_break(ibpt); end; end; overlay_next: end; /*of loop through breakpoint table*/ skip_overlay_stuff: /***temp***/ if breakpoint(overlay_loader).to_be_cancelled /*if this is ordinary implicit overlay loader breakpoint */ then goto keep_going; /* then resume execution*/ end; /*else user set explicit breakpoint on overlay loader by octal address - fall through to regular break*/ /* determine which breakpoint it is. although there may be several breakpoint table entries with the same address (being in different overlays), only one will also not be flagged as unmapped, due to the code in set_a_break being called from the overlay loader breakpoint loop above. */ do ibpt = lbound(breakpoint,1) to hbound(breakpoint,1); if (userpc = breakpoint(ibpt).location) & ^ breakpoint(ibpt).overlay_unmapped then do; if breakpoint(ibpt).aftercount > 1 then do; breakpoint(ibpt).aftercount = breakpoint(ibpt).aftercount - 1; goto keep_going; end; if window_top ^= 0 then do; call wrtpas(sysprint,string(save_cursor_attr)); call wrtpas(sysprint,string(turn_off_attr)); call setwindow; end; userbreakstatement = breakpoint(ibpt).statementn; userbreakoverlay = breakpoint(ibpt).overlay; default_module = breakpoint(ibpt).module; default_block = null_fileaddr; if breakpoint(ibpt).do_fileaddr ^= null_fileaddr then do; call push_scan_state; call workpos(breakpoint(ibpt).do_fileaddr,debprocedure_workp); in_do_group = true; nextp = -1; /*negative to force a read*/ end; else do; break_message: if ^ initializing then do; call workgrab(default_module, scratch_workp); call put_skip_list('break at statement',userbreakstatement,' in module ',scratch_workp->workbuf.name); end; in_do_group = false; end; goto command_done; end; end; /*of do loop determining which breakpoint it is*/ call put_skip_list('unexpected breakpoint at ', pli_octal(userpc)); userbreakstatement = by_octal_address; userbreakoverlay = 0; userbreaklabel = 0; goto command_done; end /*GO/GOTO*/; /* STOP */ if nxtokenis('STOP') then do; stopping = true; goto go_or_stop; end; return; command_done: signal condition(endcmd); end; /* temporary procedure to find addr of statement, given statement number & prolog addr the code here is copied from statement (statement.pli); this is a temporary kludge due to symbol table overflow on compiling breakcmd, and due to the statement number table not being incorporated in the .SYM file yet, meaning the statement address cant be found at SET BREAK time (via statement procedure) if it is in an unloaded overlay, necessitating all the deferred set break logic. when the .SYM file has a statement number table, the deferred set logic disappears and this procedure disappears, leaving statement as the only place in the debugger that looks up statement addresses... returns the address in user task of the statement, or -1 if no such statement found in table */ stmtaddr: procedure(stmt_prolog, stmt_number) returns(fixed bin(15)); dcl stmt_prolog fixed bin(15) /*addr in user task of prolog of desired module (must be in memory)*/, stmt_number fixed bin(15) /*statement number*/; %include 'knldef.pli'; %include 'rundef.pli'; /**************************************************************************************************************/ dcl ( dostma pointer, (ostmat, ostmao, ostman) fixed bin(15) ) external /*defined in debasm*/; dcl x fixed bin(15); x = getword(stmt_prolog+prolog_static) + getword(stmt_prolog+prolog_stmtoffset); /*must get before fill - getword uses packet*/ call fillpacket(dostma); packet(ostmat) = x; packet(ostman) = stmt_number; call sendknl; call rcvknl; x = packet(ostmao); if x ^= -1 then x = stmt_prolog + x; return(x); end;