/* *********************************************************** * * * Copyright, (C) Honeywell Information Systems Inc., 1982 * * * *********************************************************** */ %; /* ****************************************************** * * * * * Copyright (c) 1972 by Massachusetts Institute of * * Technology and Honeywell Information Systems, Inc. * * * * * ****************************************************** */ debug: db: procedure; /* This is the main procedure of the debug package. It has two entries: * * 1. The CALLED entry "debug" or "db" is entered when debug is called by * a user. * * 2. "mme2_fault" is entered to handle a mme2 fault, i.e., a break. */ /* PARAMETER DATA */ dcl arg_mcp ptr; /* Pointer to the machine conditions that is * passed to the mme2 fault entry. This pointer * is generated by the "signaller" and is * handed down to "debug". */ /* AUTOMATIC DATA */ /* Below is the automatic area reserved for common data. Also declared is the area * where the SNT table is built. */ dcl common_auto_area (88) fixed bin aligned, snt_area (70) fixed bin aligned; dcl 1 save aligned like db_ext_stat_$db_ext_stat_; dcl break_num fixed bin, /* Number of the break that caused the * mme2 fault. */ break_ptr ptr, /* Pointer to where the break occurred. */ temp_break_ptr ptr; /* Pointer to where a temporary break will * be set. */ dcl comd_len fixed bin, /* The length of the command line in a break. * 0 => no command line. */ comd_ptr ptr, /* Pointer to the command line IN THE BREAK. */ cond_flag fixed bin; /* A flag indicating whether a given break * should be skipped due to an unsatisfied * condition. Its values are: * 0 => NO - No condition or condition met. * 1 => YES - Skip break, condition not met. */ dcl i fixed bin; /* Work variable. */ dcl input_buffer char (132) aligned, /* Area where the user commands are read. */ input_buffer_ptr ptr, /* Pointer to the beginning of this area. */ input_line_len21 fixed bin (21), /* For use with iox_ */ input_line_len fixed bin; /* Actual length of user's input command. */ dcl printer_on char (1) init (""); /* Turn printer on "006" */ dcl line_num fixed bin, /* Source line number associated with an * offset in an object segment. */ line_1st_inst_off fixed bin, /* The offset of the FIRST instruction of * a given source line. */ line_num_inst fixed bin; /* Number of instructions used to implement * a given source line. */ dcl line_info char (14) aligned; /* Line number for printing. */ dcl last_sp ptr; /* Pointer to the last stack frame in the * stack history which we will use as * part of our trace. */ dcl cleanup condition; dcl command_abort_ condition; dcl code fixed bin (35); dcl new_line char (1) init (" "); /* new line character */ /* The label used by the condition handling procedures. */ dcl read_line_label label; /* INTERNAL STATIC DATA */ /* Below is the static area reserved for common data. */ dcl common_static_area (1063) fixed bin internal static aligned; dcl static_init_count fixed bin internal static init (0); dcl initial_flag bit (1) int static init ("0"b); /* EXTERNAL DATA */ %include db_ext_stat_; dcl 1 d like db_ext_stat_$db_ext_stat_ based (addr (db_ext_stat_$db_ext_stat_)); dcl condition_ ext entry (char (*), entry), cu_$stack_frame_ptr ext entry returns (ptr), db_break$check_break ext entry (ptr, fixed bin, ptr, fixed bin, fixed bin, fixed bin, ptr, fixed bin), db_break$restart ext entry (ptr, fixed bin, fixed bin, ptr, fixed bin), db_break$set_break ext entry (ptr, fixed bin, ptr, fixed bin), db_fill_snt ext entry (ptr, ptr), db_find_mc ext entry (ptr, bit (1) aligned, ptr), db_parse ext entry (ptr, fixed bin, ptr, ptr), debug$mme2_fault ext entry (ptr), hcs_$high_low_seg_count ext entry (fixed bin, fixed bin), ioa_$ioa_stream ext entry options (variable), ioa_$rsnnl ext entry options (variable), legal_f_ ext entry (ptr) returns (fixed bin), db_line_no ext entry (ptr, fixed bin (18), fixed bin, fixed bin, fixed bin); dcl iox_$control ext entry (ptr, char (*), ptr, fixed bin (35)); dcl iox_$user_output ptr ext; dcl iox_$user_input ptr ext; dcl iox_$get_line ext entry (ptr, ptr, fixed bin (21), fixed bin (21), fixed bin (35)); dcl iox_$close ext entry (ptr, fixed bin (35)); dcl iox_$detach_iocb ext entry (ptr, fixed bin (35)); dcl com_err_ ext entry options (variable); dcl error_table_$long_record ext fixed bin (35); dcl error_table_$not_attached ext fixed bin (35); dcl error_table_$not_open ext fixed bin (35); dcl (addr, addrel, baseptr, fixed, null, ptr) builtin; /* */ %include db_common_auto; %include db_common_static; /* */ %include db_inst; /* */ %include db_snt; /* */ %include stack_header; %include stack_frame; %include mc; /* */ /* CALLED ENTRY - This is where "debug" is entered when the user calls "debug". */ if static_init_count = 0 then call set_internal_stat; call set_ext_stat; d.static_handler_call = "0"b; com_stat_ptr = addr (common_static_area); com_auto_ptr = addr (common_auto_area); sntp = addr (snt_area); call condition_ ("mme2", debug$mme2_fault); break_num = 0; /* debug was called so there is no break number */ temp_break_mode = 0; /* Not in temporary break mode */ num_skips = 0; /* No skip count since no break. */ /* Now get a pointer to the last sp we will use in the stack history trace. It must be the frame before the "debug" frame. */ last_sp = cu_$stack_frame_ptr () -> /* Pointer to our frame. */ stack_frame.prev_sp; /* ptr to the frame of the procedure that called debug */ /* Now get a pointer to the machine conditions of the last fault that was taken before "debug" was called. The stack frame of the procedure that took the fault must still be in the stack history if we are to get a pointer to the machine conditions. If there is no fault frame in the stack history it is OK. The user just won't have any registers to play with. If he tries to reference a machine register "db_regs" will tell him. */ call db_find_mc (last_sp, "0"b, db_mc_ptr); /* Now call an internal procedure that will perform the rest of the initialization. Once this is done we can start reading in user commands. */ call common_init; goto read_line; /* Go get user commands. */ /* */ /* This entry is entered via a mme2 fault that occurred at a break point. The input argument points to the machine conditions associated with this fault. There are two cases in which the break will be skipped. In both cases debug will not print any message a) The break has a non zero skip count. b) The break contains a condition but the condition has not been met. */ mme2_fault: entry (arg_mcp); if static_init_count = 0 then do; /* in case debug is entered via static handler */ call set_internal_stat; call set_ext_stat; d.static_handler_call = "1"b; end; com_stat_ptr = addr (common_static_area); com_auto_ptr = addr (common_auto_area); sntp = addr (snt_area); db_mc_ptr = arg_mcp; /* The machine conditions associated with * this fault serve as our registers. */ /* From the SCU data we get the address of where the mme2 fault occurred - in effect the PPR. The The address field of the mme2 instruction contains the break number. */ scup = addr (db_mc_ptr -> mc.scu); break_ptr = ptr (baseptr (fixed (scup -> scu.ppr.psr)), scup -> scu.ilc); break_num = fixed (break_ptr -> instr.offset); last_sp = db_mc_ptr -> mc.prs (spx); /* stack frame of the procedure that took the fault. */ call db_fill_snt (last_sp, sntp); call db_break$check_break (break_ptr, break_num, sntp, cond_flag, num_skips, comd_len, comd_ptr, line_num); /* If the break could not be handled (it was not enabled, it was a version 1 break), the user will enter command mode for debug. */ if break_ptr = null then do; call common_init; goto read_line_label; end; if (cond_flag = 1) | (num_skips > 0) then goto restart_break; call common_init; if line_num > 0 then call ioa_$rsnnl ("at line ^d", line_info, i, line_num); else line_info = ""; if print_mode = 0 then call ioa_$ioa_stream (d.debug_output, "Break ^d ^a of ^a", break_num, line_info, snt.ent_name); else call ioa_$ioa_stream (d.debug_output, "^RBreak ^d ^a of ^a - at ^p^B", break_num, line_info, snt.ent_name, break_ptr); if temp_comd_len ^= 0 /* temporary global command */ then call db_parse (addr (temp_comd_line), temp_comd_len, com_auto_ptr, com_stat_ptr); if comd_len ^= 0 /* conditional break */ then call db_parse (comd_ptr, comd_len, com_auto_ptr, com_stat_ptr); goto db_action_label (db_action_code); /* */ /* These condition handlers return to "read_line". The stack frame that * "debug" will be running on will be the last "debug" stack frame regardless of * how "debug" was entered. */ conversion_handler: procedure; call ioa_$ioa_stream (d.debug_output, "Conversion error"); goto read_line_label; end conversion_handler; prog_interrupt_handler: procedure; goto read_line_label; end prog_interrupt_handler; /* The any_other_handler prints the name of the condition and goes to read the next request from the user. The condition will be passed on if the user was not in debug when the condition occurred or the condition is not in the conditions table. The user is not in debug in the following cases: * db_parse (..) when a procedure is executed * (:=) when a subroutine call is made * debug (.c) when the user continues execution after a break */ any_other_handler: proc (mcptr, name, wcptr, info_ptr, cont); dcl mcptr ptr, name char (*), wcptr ptr, info_ptr ptr, cont bit (1); dcl conditions char (106) init ("conversion,fixedoverflow,out_of_bounds,overflow,underflow,zerodivide,stringrange,stringsize,subscriptrange"); if d.in_debug then do; /* only handle debug conditions */ if name = "db_conversion" then name = "conversion"; if index (conditions, name) > 0 then do; call ioa_$ioa_stream (d.debug_output, "db: ^a", name); go to read_line_label; end; end; cont = "1"b; return; end any_other_handler; /* */ /* This routine will read a user command and then call the parsing procedure to * process the command line. What we do when the parsing procedure returns depends * upon the action code which the parsing procedure sets. Thus the contents of the * command line determines what we do next. */ read_line: db_action_label (0): call iox_$get_line (d.debug_io_ptr (1), input_buffer_ptr, 132, input_line_len21, code); input_line_len = input_line_len21; if code ^= 0 then do; call com_err_ (code, "debug"); if code = error_table_$long_record then go to read_line; else go to quit; end; if input_line_len = 1 then goto read_line; /* Is it a blank line? */ db_action_code = 0; /* in case different value was returned before */ call db_parse (input_buffer_ptr, input_line_len, com_auto_ptr, com_stat_ptr); goto db_action_label (db_action_code); /* resetread: Flush read ahead because last request was incorrect */ db_action_label (1): call iox_$control (d.debug_io_ptr (1), "resetread", null, code); if code ^= 0 then call com_err_ (code, "debug"); goto read_line; /* Quit debug */ db_action_label (2): quit: if break_num = 0 then do; /* debug was CALLED */ call restore; return; end; /* If this invocation was through a mme2 fault, return is made to the stack frame that had the call to debug. If debug was originally invoked via a static handler and return is not possible. */ if ^d.flags.static_handler_call then goto d.return_label; signal command_abort_; goto read_line; return_from_debug: /* This is where the previous goto will transfer to. However, we will now be in a different stack frame. */ call restore; /* Restore i/o attachments & external data */ return; /* Return to CALLER. */ /* This routine is called when the user wants to restart a break. Note that all of * the data needed to restart the break has been set up by "debug". Only the num_skips * field could have been modified by a user if the parse procedure was called to * process a command line. If there is no break number then "debug" was entered * via a CALL. Thus there is no break to restart. We will just go and read in * another command line. */ restart_break: db_action_label (3): if break_num = 0 | break_ptr = null then do; call ioa_$ioa_stream (d.debug_output, "No break fault, cannot restart break."); goto read_line; end; /* There was a break fault so we can restart this break. First we will check to * see if we are in temporary break mode. If we are we will set a temporary break. * The location of the temporary break will be at the beginning of the next line if * there are line numbers available. Otherwise it will be at the next instruction. */ if temp_break_mode ^= 0 /* Are we in temporary break mode? */ then do; /* YES */ call db_fill_snt (last_sp, sntp); call get_line_num; /* Get temporary break pointer. */ call db_break$set_break (temp_break_ptr, 1, sntp, print_mode); end; /* Now we will call db_break to restart the break. It will fiddle with our SCU * data so that when we say "return" the instruction that was replaced by the * mme2 will eventually be executed. We return to the procedure that called "debug" * at the mme2 fault entry. Eventually a return is made to the signaller who does * an "RCU" instruction from our SCU data. This will restart the procedure which * will execute as if the break never happened. */ d.in_debug = "0"b; /* restart break means leaving debug */ call db_break$restart (break_ptr, break_num, num_skips, scup, print_mode); return; /* This will begin the process which will * restart the procedure. */ /* */ common_init: procedure; /* This procedure is called to perform initialization that is common the both the * CALLED and the mme2_fault entries. It will not be called at all if the mme2 fault * entry immediately restarts the break. */ /* First initialize the rest of the common automatic variables. Also set up * the pointer to the input buffer. */ first_call_flag, db_action_code = 0; input_buffer_ptr = addr (input_buffer); /* Establish condition handlers for illegal debug conversions and for program * interrupts. Both of these condition handlers will go to "read_line" to get * another input line. At that time "debug" will be executing out of the last * "debug" stack frame regardless of how it was entered. */ read_line_label = read_line; call condition_ ("db_conversion", conversion_handler); call condition_ ("program_interrupt", prog_interrupt_handler); call condition_ ("any_other", any_other_handler); d.in_debug = "1"b; /* Now trace the stack history. We will start at the beginning of the stack and * trace until we reach the frame we have designated as the last frame. The pointer * to this frame is in "last_sp". The index of the stack_ptr_array entry for * this last frame will be saved in the common variable "max_sp_x". */ sp = ptr (last_sp, 0) -> stack_header.stack_begin_ptr; /* Get a pointer to the first * frame in the stack. Note, * it is a dummy and will * be skipped. */ do i = 0 to 511; if legal_f_ (sp) ^= 0 /* Is it a legal frame? */ then do; /* NO. */ max_sp_x = i - 1; /* Previous frame is the last one * we can use in the stack history. */ call ioa_$ioa_stream (d.debug_output, "Cannot trace stack past depth ^d", i-1); goto get_snt_data; /* End stack trace. */ end; stack_ptr_array (i) = sp; /* Stack is legal. Save its pointer * in the stack array. */ if sp = last_sp /* Is this the last stack frame we * want to trace? */ then do; /* YES. This is the end of the trace. */ max_sp_x = i; /* Save number of valid stack frames * in the trace. */ goto get_snt_data; end; sp = sp -> stack_frame.next_sp; /* Get a pointer to the next frame. */ end; /* If we get here we have overflowed the stack array area. Tell the user. */ call ioa_$ioa_stream (d.debug_output, "Stack array overflow has occurred."); max_sp_x = i - 1; /* Now that the stack trace has finished we will fill in the SNT table from the data * in the last stack frame in the trace. */ get_snt_data: call db_fill_snt (stack_ptr_array (max_sp_x), sntp); snt_ptr = sntp; end common_init; /* */ get_line_num: procedure; /* This internal procedure is called to get the line number of the instruction at * the current break point. It will also return a pointer to where the next temporary * break point should be set. If we can't get the line number then a line number * value of (-1) will be returned. This procedure assumes that "break_ptr" points to * the break point and that "sntp" points to valid SNT data of the fault frame. */ /* Get the line number. */ call db_line_no (sntp, fixed (rel (break_ptr), 18), line_1st_inst_off, line_num_inst, line_num); if line_num > -1 /* Did we get a line number. */ then do; /* YES, temporary pointer is beginning of * next line. */ temp_break_ptr = ptr (break_ptr, line_1st_inst_off + line_num_inst); return; end; /* Either we couldn't get a symbol pointer or we couldn't get a line number. In * any case we will return a line number of (-1) and the temporary break pointer * will be equal to the instruction after the break point. */ line_num = -1; temp_break_ptr = addrel (break_ptr, 1); end get_line_num; restore: proc; /* This procedure is called when the user quits debug or when the cleanup condition * is signaled. * 1. Any i/o attachments that were made by debug are detached. * 2. The external data is restore to its initial value when debug was called. */ do i = 1 to 2; if d.debug_io_open (i) then do; call iox_$close (d.debug_io_ptr (i), code); if code ^= 0 then if code ^= error_table_$not_open then call com_err_ (code, "debug"); end; if d.debug_io_attach (i) then do; call iox_$detach_iocb (d.debug_io_ptr (i), code); if code ^= 0 then if code ^= error_table_$not_attached then call com_err_ (code, "debug"); end; end; d = save; /* restore external static data */ static_init_count = static_init_count - 1; return; end restore; /* */ /* The external static is setup to work like controlled storage. When debug is called, the external static is copied into automatic storage. Before the user returns from debug, the external static is restored using the values saved in automatic storage. This is required for the return_label and is also convienent for cleanup for users who change the io switches. */ set_ext_stat: proc; save = d; /* save ext static data */ d.debug_input = "user_input"; d.debug_output = "user_output"; d.debug_io_open (1), d.debug_io_open (2), d.debug_io_attach (1), d.debug_io_attach (2) = "0"b; d.debug_io_ptr (1) = iox_$user_input; d.debug_io_ptr (2) = iox_$user_output; static_init_count = static_init_count + 1; /* When the user issues a debug quit command we want to return to the procedure which called "debug". Thus we must be using the stack frame of "debug" when it was entered via a call. In order to quit out of debug when it was entered via a fault we must do a non local goto back to the stack frame of "debug" when it was entered via a call. */ d.return_label = return_from_debug; on cleanup call restore; end set_ext_stat; set_internal_stat: proc; com_stat_ptr = addr (common_static_area); /* common static data */ if initial_flag then return; call hcs_$high_low_seg_count (i, hcs_count); sb = ptr (cu_$stack_frame_ptr (), 0); /* ptr to base of stack */ lotp = sb -> stack_header.lot_ptr; /* ptr to base of the linkage */ print_mode = 1; /* long message mode */ temp_comd_len = 0; /* no temporary global break command line */ initial_flag = "1"b; end set_internal_stat; end debug;