[comp.os.vms] Callable Traceback Routine

mike%udel-oitvax.arpa@UDEL.EDU ("Michael J. Porter") (06/28/87)

I believe there was a request for calling the traceback facility recently.
The following routine will do this.  The only problems are:

1.  The interface to traceback is likely to change since it kinda strange.
2.  The traceback image can be mapped twice if a normal traceback is generated
    as a result of an exception.  There is no easy way to know where 
    SYS$IMAGSTA mapped TRACE since the symbols where it saves the address
    range are not global.
3.  You will have to define the symbol lib__trace in your favorite message
    library, or change the code to use something else.

The main entry point is lib_trace.  The ISI values are obtained from RMS via
the location RMS$W_ISI in the RAB.  I hope the code is documented well enough
to be of use.

*----- TRACE.MAR
	.TITLE	lib_trace
	.IDENT	/1.00/

;++
; Facility:	LIB -- General Programming Facility Library
;
; Abstract:
;
;		This module contains a routine that directly maps and calls
;		the traceback facility.  Normally, this facility is not
;		callable, it is mapped and called by the system when an
;		exception does not get handled.
;
;		The primary use of this routine is to generate tracebacks
;		from within a program, saving the trace in a file, without
;		generating an exception.  This is useful for tracing program
;		usage.  A general use of this routine is to allow users to make
;		comments in the program.  Later these comments can be
;		reviewed, and the location of the user in the program is
;		known symbolically.
;
;		In order to use this routine effectively, the image must
;		not be linked with /NOTRACEBACK.  This means that this
;		routine is of limited use for programs that are
;		installed.
;
;		Needless to say, this module is highly dependant on
;		undocumented interfaces to VMS.  Hopefully, at some
;		time in the future TRACEBACK will be a callable facility.
;
; Environment:	User-Mode, Non-AST Re-Entrant
;
; Author:	Michael Porter		7/23/85
;
; Modified By:
;		Original
;--


	.SBTTL	Global Declarations
	.DISABLE GLOBAL

;++
; Library Macro Calls:
;--

	.LIBRARY -
		/SYS$LIBRARY:LIB/		; System defs

	$IACDEF					; Image activator ctrl flags
	$IHDDEF					; Image header defintions
	$SSDEF					; System service status defs
	$SFDEF					; Stack frame definitions
	$CHFDEF					; Signal definitions
	$STSDEF					; Status code definitions
	$LNMDEF					; Logical definitions

;++
; External Declarations:
;--

	.EXTERNAL -
		lib__trace, -			; Default signal name

		LIB$SIG_TO_RET			; Change signal to return status

;++
; Macroes
;
; NONE.
;--

;++
; Equated Symbols:
;--

;+
;	Define offsets for the traceback vector argument.  The traceback
;	vector is formed as follows:
;
;	+--------------------------------------------------------+
;	|  PC when the pseudo fault occured                      |
;	+--------------------------------------------------------+
;	| Frame in which the fault occured.  This is the first   |
;	| frame that appears in the traceback                    |
;	+--------------------------------------------------------+
;	| First FP, this is the last frame listed in the         |
;	| traceback                                              |
;	+--------------------------------------------------------+
;	| Signal array for the pseudo exception                  |
;	+--------------------------------------------------------+
;	| Lower section address for the symbol table             |
;	+--------------------------------------------------------+
;	| Upper section address of the symbol table              |
;	+--------------------------------------------------------+
;-

	fault_pc		= 0		; PC at exception
	fault_fp		= 4		; FP of faulting frame
	first_fp		= 8		; FP at start of program
	signal_array		= 12		; Address of signal array
	sect_bounds		= 16		; Address range for DST
	tbvec_len		= 24		; Length of vector

;++
; PSECT Declarations:
;--

	.PSECT	_LIBSHRCODE_	NOWRT, EXE, CON, RD, SHR, PIC,-
				LCL, WORD

	.PSECT	_LIBSHRPLIT_	NOWRT, NOEXE, CON, RD, SHR, PIC,-
                                LCL, WORD

	.PSECT	$OWN$		WRT, NOEXE, CON, RD, NOSHR, NOPIC,-
                                LCL, WORD

;++
; OWN Storage:
;--

	.PSECT	$OWN$

;+
;	Define the location to save the pointer to trace when it is mapped.
;-

sect_addr: -
	.LONG	0

;+
;	End of own storage.
;-

;++
; Static Storage
;--

	.PSECT	_LIBSHRPLIT_

;+
;	Define the name of the trace image.
;-

image:	.ASCID	/SYS$LIBRARY:TRACE.EXE/

;+
;	Define the logical name and the table it will go into.
;-

log_name: -
	.ASCID	/SYS$PUTMSG/

table_name: -
	.ASCID	/LNM$PROCESS/

;+
;	End of static storage.
;-

	.PSECT	_LIBSHRCODE_

;++
; End of global Definitions.
;--

	.SBTTL Sub-Routine trans_log
trans_log:

;++
; Functional Description:
;
;	This sub-routine is used to translate a process logical name which
;	is used to communicate to $PUTMSG and TRACE what file they
;	should be outputting to.
;
;	SYS$PUTMSG creates a logical name with the following format when it
;	is called the first time:
;
;	|  2 bytes    |  2 bytes    |  2 bytes    |  2 bytes    |  2 bytes    |
;	|---------------------------------------------------------------------|
;	|  Error ISI  |  Output ISI |  Error ISI  |  Output ISI |    ^X11B    |
;
;	The ^X11B is sort of an ID code, the ISI values are the RAB$W_ISI
;	values found in an RAB.  Those values are all that is needed to
;	write to an already open file.  The values must be repeated for some
;	reason - it just doesn't work if you don't.
;
;	This routine translates such a logical name and returns the ISI
;	values in R5 and R6
;
; Calling Sequence:
;
;	BSBB	trans_log
;
; Formal Parameters:
;
;	NONE.
;
; Implicit Inputs:
;
;	log_name		-- Logical name string
;	table_name		-- Table name to enter the name in
;
; Implicit Outputs:
;
;	R5			-- Error ISI value, 0 if not found
;	R6			-- Output ISI value, 0 if not found
;
; Register Usage:
;
;	R2 - R4			-- Unavailable
;
; Completion Status:
;
;	SS$_NORMAL
;
; Side Effects:
;
;	NONE.
;--

	SUBL2	#16, SP				; Make room for the item desc.
	MOVL	SP, R0				; Save address of item
	SUBL2	#10, SP				; Make equivalence buffer
	MOVL	SP, R6				; Save address

	MOVW	#10, (R0)			; String length
	MOVW	#LNM$_STRING, 2(R0)		; Set the code
	MOVL	R6, 4(R0)			; Buffer address
	CLRL	8(R0)				; Return length address
	CLRL	12(R0)				; End of list

;+
;	Create the logical name
;-

	$TRNLNM_S -
		TABNAM = table_name, -		; Table name
		LOGNAM = log_name, -		; Logical name
		ITMLST = (R0)			; Item list address

	BLBS	R0, 10$				; Success

	CMPL	R0, #SS$_NOLOGNAM		; Failed to locate it?
	BNEQU	1$				; No
	CLRQ	R5				; Yes
	MOVL	#SS$_NORMAL, R0			; Set success
	BRB	100$				; Exit

1$:	CMPL	R0, #SS$_BUFFEROVF		; Simple buffer overflow?
	BEQLU	10$				; Yes
	BRB	100$				; General error

10$:	CMPW	#^X11B, (R6)			; Check for code
	BEQLU	11$				; Matched
	CLRQ	R5				; No string
	BRB	100$				; Exit

11$:	MOVZWL	4(R6), R5			; Get the error ISI
	MOVZWL	2(R6), R6			; Get the output ISI

100$:	ADDL2	#26, SP				; Return stack space
	RSB

;++
;	End of routine.
;--

	.SBTTL Sub-Routine set_log
set_log:

;++
; Functional Description:
;
;	This sub-routine is used to define a process logical name which
;	is used to communicate to $PUTMSG and TRACE what file they
;	should be outputting to.
;
;	SYS$PUTMSG creates a logical name with the following format when it
;	is called the first time:
;
;	|  2 bytes    |  2 bytes    |  2 bytes    |  2 bytes    |  2 bytes    |
;	|---------------------------------------------------------------------|
;	|  Error ISI  |  Output ISI |  Error ISI  |  Output ISI |    ^X11B    |
;
;	The ^X11B is sort of an ID code, the ISI values are the RAB$W_ISI
;	values found in an RAB.  Those values are all that is needed to
;	write to an already open file.  The values must be repeated, it
;	just doesn't work if you don't.
;
;	This routine defines such a logical name,  using R7 and R8 for
;	the ISI values.
;
;	Always delete the logical name first.  If the input ISI values
;	are zero, then exit at that point.
;
; Calling Sequence:
;
;	BSBB	set_log
;
; Formal Parameters:
;
;	R7			-- Error ISI
;	R8			-- Output ISI
;
; Implicit Inputs:
;
;	log_name		-- Logical name string
;	table_name		-- Table name to enter the name in
;
; Implicit Outputs:
;
;	Logical name created or deleted.
;
; Register Usage:
;
;	R2 - R6			-- Unavailable
;
; Completion Status:
;
;	SS$_NORMAL
;
; Side Effects:
;
;	NONE.
;--

;+
;	See if the values are zero or not.  If they are, just delete
;	the logical name.
;-

	CMPL	#0, R7				; Check the error ISI
	BNEQU	1$				; Not zero
	CMPL	#0, R8				; Check the output ISI
	BNEQU	1$				; Not zero

;+
;	Delete the logical name.
;-

	$DELLNM_S -
		TABNAM = table_name, -
		LOGNAM = log_name
	RSB

;+
;	The ISIs are not zero, so create the logical name.
;-

1$:	SUBL2	#16, SP				; Make room for the item desc.
	MOVL	SP, R0				; Save address of item
	SUBL2	#10, SP				; Make equivalence buffer
	MOVL	SP, R1				; Save address

	MOVW	#10, (R0)			; String length
	MOVW	#LNM$_STRING, 2(R0)		; Set the code
	MOVL	R1, 4(R0)			; Buffer address
	CLRL	8(R0)				; Return length address
	CLRL	12(R0)				; End of list

	MOVW	#^X11B, (R1)			; Set code
	MOVW	R8, 2(R1)			; Set output ISI
	MOVW	R7, 4(R1)			; Set error ISI
	MOVW	R8, 6(R1)			; Set output ISI
	MOVW	R7, 8(R1)			; Set error ISI

;+
;	Create the logical name
;-

	$CRELNM_S -
		TABNAM = table_name, -		; Table name
		LOGNAM = log_name, -		; Logical name
		ITMLST = (R0)			; Item list address

100$:	ADDL2	#26, SP				; Return stack space
	RSB

;++
;	End of routine.
;--

	.SBTTL Routine handler
	.ENTRY	handler, 0

;++
; Functional Description:
;
;	This routine acts as a condition handler when the traceback
;	facility is called.  It will delete the logical name SYS$PUTMSG
;	and change the signal to a return status.  This will cause the
;	lib_trace routine to exit because we call LIB$SIG_TO_RET.
;
; Calling Sequence:
;
;	handler( sigargs.ra.r, mechargs.ra.r )
;
; Formal Parameters:
;
;	sigargs		-- Signal arguments
;	mechargs	-- Mechanism arguments
;
; Implicit Inputs:
;
;	table_name	-- Name the logical appears in
;	log_name	-- Logical name string
;
; Implicit Outputs:
;
;	NONE.
;
; Completion Status:
;
;	SS$_NORMAL		Normal, successful completion
;
; Side Effects:
;
;	NONE.
;--

	$DELLNM_S -				; Delete the logical name
		TABNAM = table_name, -
		LOGNAM = log_name

	CALLG	(AP), G^LIB$SIG_TO_RET		; Change to return status

	MOVL	#SS$_CONTINUE, R0
	RET

;++
;	End of routine.
;--

	.SBTTL Global Routine lib_trace
	.ENTRY	lib_trace, ^M< R2, R3, R4, R5, R6, R7, R8, R9, R10, R11 >

;++
; Functional Description:
;
;	This routine is used to generate a traceback that will be written
;	to the file specified using the RAB RAB$W_ISI values.  The traceback
;	is generated by actually calling the traceback facility, an error
;	is not signalled, so there will be no confusion with other exception
;	handlers.  This routine can be called from an exception handler.
;	If it called from an exception handler and the exception is not
;	handled, two tracebacks will be generated.  The system generated
;	traceback will occur on SYS$OUTPUT and SYS$ERROR, the traceback
;	from this routine will go to the file specified or default to
;	SYS$OUTPUT and SYS$ERROR.
;
; Calling Sequence:
;
;	ret_status = lib_trace( [,error_isi.rw.v, output_isi.rl.v]
;	                        [frame.rl.v] [,signal.ra.r] )
;
; Formal Parameters:
;
;	error_isi	-- RAB RAB$W_ISI value of the file you want the output
;	output_isi	   to appear in.  If output_isi and error_isi are
;			   the same, the output will only occur once.  If
;			   neither is specified, SYS$OUTPUT and SYS$ERROR will
;			   be used.  you must specify both or neither.
;
;	frame		-- # of frames from end of call stack to skip
;			   This argument is used if this routine is called
;			   from a general location, and the frames for the
;			   general location are not wanted in the traceback.
;			   Do not include internal frames in this routine
;			   in the count
;
;	signal		-- Signal array used in traceback message.  The default
;			   is a signal named lib__trace.
;
; Implicit Inputs:
;
;	sect_addr	-- Pointer to location where TRACE was mapped
;	image		-- String descriptor giving the name of the image
;			   to map
;
; Implicit Outputs:
;
;	sect_addr	-- Pointer to location where TRACE was mapped
;
; Register Usage:
;
;	R2		-- Top FP (top of stack, usually SYS$IMGSTA)
;	R3		-- Top AP (AP pointing to args at image startup)
;	R4		-- Fault FP (FP of frame pseudo fault occured in)
;	R5		-- PC at Pseudo fault (The saved PC in the frame
;			   after the faulting frame)
;	R6		-- Signal name
;
; Completion Status:
;
;	SS$_NORMAL		Normal, successful completion
;
; Side Effects:
;
;	The TRACE image will be mapped.  If the system also generates
;	trace messages, TRACE will be mapped twice, but this will not
;	cause a problem since it is relocatable.
;--

;+
;	Set the condition handler
;-

	MOVAL	handler, (FP)

;+
;	Find the top stack frame by scanning the stack backwards.  Also,
;	find the AP that was used to pass the original arguments to the
;	image.  This is not necessarily the AP for the top frame.  Only
;	use the value if the # of args is none zero.
;
;	While we are winding back frames, move the fault FP and PC if the
;	number of frames to not trace is non-zero.
;
;	R2		-- Top FP
;	R3		-- Top AP
;	R4		-- Fault FP
;	R5		-- Fault PC
;
;	After we find the top frame, use the second from the top frame since
;	the top frame is SYS$IMAGSTA.  Even if $IMAGSTA is not present, then
;	it does not matter much since the traceback will not have symbols
;	anyway.  R8 is the trailer.
;-

0$:	MOVL	SF$L_SAVE_FP( FP ), R2		; Skip this frame
	MOVL	FP, R8				; Previous frame
	MOVL	SF$L_SAVE_AP( FP ), R3		; Set the top AP value.
	MOVL	R2, R4				; Fault FP
	MOVL	SF$L_SAVE_PC( FP ), R5		; Faulting PC
	CLRL	R6				; Counter
	MOVZBL	(AP), R7			; Argument count
	CMPL	R7, #3				; 3 args passed?
	BLSSU	1$				; No
	MOVL	12(AP), R7			; # of frames to skip
	BRB	2$				; Start unwinding frames
1$:	CLRL	R7				; No frames to skip

2$:	MOVL	SF$L_SAVE_FP( R2 ), R1		; Get the previous frame
	BEQLU	5$				; No previous frame
	MOVL	SF$L_SAVE_AP( R2 ), R0		; Get saved AP
	CMPB	#0, ( R0 )			; Args passed to frame?
	BEQLU	3$				; No
	MOVL	R0, R3				; Yes, save AP
3$:	CMPL	R6, R7				; How many frames back?
	BGEQU	4$				; Enough frames
	MOVL	R1, R4				; Move back fault FP
	MOVL	SF$L_SAVE_PC( R2 ), R5		; Move back fault PC
	INCL	R6				; Increment the counter
4$:	MOVL	R2, R8				; Save current FP
	MOVL	R1, R2				; Save the previous frame
	BRB	2$				; Get next frame
5$:	MOVL	R8, R2				; Use next to last frame
						; Skips SYS$IMAGSTA

;+
;	Build the traceback vector.  This vector's address will be placed
;	in the first argument slot pointed to by the AP used when the image was
;	originally run.
;
;	R2	-- First FP, Signal name
;	R3	-- First AP
;	R4	-- Fault FP, address to jump to if mapped
;	R5	-- Fault PC
;	R6	-- Temp
;
;	At the end of this code, only R2, R3 and R4 are alive.  R4 is only
;	live if trace is mapped.
;-

;+
;	Don't build a signal vector if one was provided.
;-

10$:	CMPB	(AP), #4			; 4 args passed?
	BLSSU	11$				; No
	MOVL	16(AP), R6			; Yes
	BNEQU	12$				; Address not NULL

11$:	MOVPSL	-(SP)				; Create signal vec, push PSL
	PUSHL	R5				; Fault PC
	PUSHL	#lib__trace			; Push default signal name
	PUSHL	#3				; Push length
	MOVL	SP, R6				; Save addr of signal array

12$:	SUBL2	#tbvec_len, SP			; Make space for trace vector
	MOVL	R2, first_fp( SP )		; Set FP for first frame
	MOVL	R4, fault_fp( SP )		; FP for fault
	MOVL	R5, fault_pc( SP )		; Save faulting PC
	MOVL	R6, signal_array( SP )		; Address of signal array
	CLRQ	sect_bounds( SP )		; DST address
	MOVL	SP, 4( R3 )			; Set addr. of tb vector
	MOVL	CHF$L_SIG_NAME(R6), R2		; Save signal name
	MOVL	sect_addr, R4			; Get addr of trace
	BNEQU	100$				; Already mapped

;+
;	Map in the traceback image.  Do not do all the work the system
;	does (translating LIB$TRACE etc), just map it.
;
;	R2		-- Signal name
;	R3		-- Image arguments
;-

	SUBL2	#IHD$S_IHDDEF, SP		; Create image header buffer
	MOVL	SP, R1				; Save address of header
	CLRQ	-(SP)				; Create return address array
	MOVL	SP, R4				; Save address of return array
	PUSHL	#1@30-1				; 2nd lw for input addr. array
	PUSHL	#^X200				; 1st lw for input addr. array
	MOVL	SP, R0				; Input address array

;+
;	Activate the image.
;-

	$IMGACT_S -
		NAME   = image, -		; Name of file
		HDRBUF = (R1), -		; Address of image header
		IMGCTL = #<IAC$M_MERGE!IAC$M_EXPREG>, -
		INADR  = (R0), -		; Range to map into
		RETADR = (R4)			; Range mapped into
	BLBS	R0, 15$				; Error?
	RET					; Yes

15$:	$IMGFIX_S				; Fix up image vectors
	BLBS	R0, 20$				; Error?
	RET					; Yes
20$:						; No

;+
;	Get the transfer address from the image.  The address of the array
;	that holds the range that was mapped is in R4.  The third longword
;	in the mapped section contains the transfer address for trace.
;-

	MOVL	(R4), R4			; Get mapped address
	ADDL3	8( R4 ), R4, R4			; Compute start address
	MOVL	R4, sect_addr			; Save the start address
		
;+
;	See if the error and output ISI values are specified or if we
;	defaulting to SYS$OUTPUT.
;
;	R2		-- Signal name
;	R3		-- Argument list
;	R4		-- Address to jump to
;	R7		-- Error ISI
;	R8		-- Error ISI
;	R5		-- Old error ISI
;	R6		-- Old output ISI
;-

100$:	CLRQ	R5				; Clear OLD ISI values
	CMPB	(AP), #2			; Enough args passed?
	BLSSU	105$				; Nope, default
	MOVL	4(AP), R7			; Get the error ISI
	BEQLU	105$				; Ignore if zero
	MOVL	8(AP), R8			; Get the output ISI
	BEQLU	105$				; Ignore if zero
	BSBW	trans_log			; Translate the logical
	BLBC	R0, 115$			; Error?
	BSBW	set_log				; Set the new logical
	BLBC	R0, 115$			; Error?

105$:	CALLG	(R3), 120$			; Call trace
	MOVL	R0, R3				; Save the return status

;+
;	Remove the logical name or re-define it to what it was.
;-

	MOVQ	R5, R7				; Yes
	BSBW	set_log				; Redefine the logical name
	BLBC	R0, 115$			; Error?

;+
;	If the signal name is still the same, then exit with SS$_NORMAL,
;	otherwise make sure the inhibit flag is clear and return the
;	code.
;-

110$:	BICL2	#STS$M_INHIB_MSG, R2		; Clear inhib on signal name
	BICL2	#STS$M_INHIB_MSG, R3		; Clear inhib in ret status
	CMPL	R2, R3				; Are codes the same
	BNEQU	116$				; No
	MOVL	#SS$_NORMAL, R0			; Set success	
115$:	RET					; Return
116$:	MOVL	R3, R0				; No, return TRACE error
	RET					; Return

;+
;	This frame will be removed by the RET in TRACE.  A RET is not needed.
;
;	R2		-- Signal name
;	R4		-- Address to jump to (not saved)
;	R5		-- Old error ISI
;	R6		-- Old output ISI
;-

120$:	.WORD	^M< R2, R5, R6 >
	JMP	( R4 )

;++
;	End of module.
;--

	.END
------