HP OpenVMS Calling Standard
8.8.1 Handler Invocation During a GOTO Unwind
When an unwind operation takes place, all frame-based exception
handlers are invoked that were established by any procedure invocation
being terminated. In addition, the handler for the target procedure
invocation is called if the PDSC$V_TARGET_INVO flag is set in the
corresponding procedure descriptor or unwind information (see Sections
3.4.2, 3.4.5 and A.4.3.) These handlers are
invoked in the reverse order from which they were established.
Because primary, last-chance handlers, and the system catchall handler
are not associated with a normal procedure invocation, these handlers
are never invoked during an unwind (but they are invoked if an
exception is raised during the unwind operation).
For a GOTO unwind procedure, each handler that is invoked is called
with two arguments as follows:
(* handler) (signal_args, mechanism_args)
|
Argument |
OpenVMS Usage |
Type |
Access |
Mechanism |
signal_args
|
signal vector
|
structure
|
modify
|
by reference
|
mechanism_args
|
mechanism vector
|
structure
|
modify
|
by reference
|
Arguments:
|
signal_args
Argument count of 2, followed by a condition value of SS$_UNWIND,
followed by:
- SS$_GOTO_UNWIND when a target invocation is specified but not for
that target invocation
- SS$_TARGET_GOTO_UNWIND when a target invocation is specified and
the handler for that target invocation is called
|
|
mechanism_args
Mechanism argument corresponding to the frame being unwound, as
defined in Section 8.5.1.2.
|
For information about signal argument and mechanism argument vectors,
see Sections 8.5.1.1 and 8.5.1.2.
8.8.2 Unwind Completion
When an unwind completes, the following conditions are true:
- The target procedure invocation is the most current invocation in
the procedure invocation chain.
- The environment of the target invocation is restored to the state
when that invocation was last current, except for the contents of all
scratch registers.
- RET0 and RET1 contain the respective values (if any) that were
passed by the routine that invoked the unwind.
- Execution continues at the target location.
8.9 Multiple Active Signals
A signal is said to be active until the signaler gets control again or
is unwound. A signal can occur while a condition handler or a procedure
it has called is executing in response to a previous signal. For
example, procedures A, B, and C establish condition handlers Ah, Bh,
and Ch. If A calls B and B calls C, which signals S, and Ch resignals,
then Bh gets control.
If Bh calls procedure X, and X calls procedure Y, and Y signals T, the
stack is as follows:
The handlers are searched for in the following order: Yh, Xh, Bhh, Ah.
Bh is not called again because it is not appropriate to assume that a
routine is able to be its own handler. However, Bh can establish itself
or another procedure as its handler (Bhh).
On VAX systems, Ch is not checked or called because it is a structural
descendant of B.
On Alpha or I64 systems, the search does check handlers Ch
and Bh between calling Bhh and Ah. These handlers will be reinvoked
only if enabled by the HANDLER_REINVOCABLE flag of the establisher's
procedure descriptor (see Sections 3.4.1 and 3.4.4) or
unwind information (see Section A.4.3).
For all systems, the following algorithm is used on the second and
subsequent signals that occur before the handler for the original
signal returns to the Condition Handling Facility. The primary and
secondary exception vectors are checked. However, the search backward
in the process stack is then modified. On a VAX processor, the stack
frames traversed in the first search are skipped, in effect, during the
second search, while on an Alpha or I64 system, the stack
frames are skipped unless they explicitly enable handler reinvocation.
Therefore, the stack frame preceding the first condition handler, up to
and including the frame of the procedure that has established the
handler, is skipped. In the VAX environment, frames that are skipped
are not counted in the depth. In the Alpha or I64
environment, all frames are counted in the depth.
For example, the stack frames traversed in the first and second
searches are skipped in a third search. Note that if a condition
handler signals, it is not automatically invoked recursively. However,
if a handler itself establishes a handler, the second handler is
invoked. Therefore, a recursive condition handler should start by
establishing itself. Any procedures invoked by the handler are treated
in the normal way; that is, exception signaling follows the stack up to
the condition handler.
If an unwind operation is requested while multiple signals are active,
all the intermediate handlers are called for the operation. For
example, in the preceding diagram, if Ah specifies unwinding to A, the
following handlers are called for the unwind: Yh, Xh, Bhh, Ch, and Bh.
For proper hierarchical operation, an exception that occurs during
execution of a condition handler established in an exception vector
should be handled by that handler rather than propagating up the
activation stack. To prevent such propagation, the vectored condition
handler should establish a handler in its stack frame to handle all
exceptions.
8.10 Multiple Active Unwind Operations
During an unwind operation (resulting from a call of
SYS$GOTO_UNWIND_64, SYS$GOTO_UNWIND, or SYS$UNWIND), another unwind
operation can be initiated (using SYS$GOTO_UNWIND_64, SYS$GOTO_UNWIND,
or SYS$UNWIND). This can occur, for example, if a handler that is
invoked for the original unwind initiates another unwind, or if an
exception is raised in the context of such a handler and a handler
invoked for that exception initiates another unwind operation. However,
SYS$UNWIND cannot be called from a handler that is invoked as part of
an unwind (see Section 8.7), but it can be called from a handler for
a nested exception.
An unwind that is initiated while a previous unwind is active is either
a nested unwind or an overlapping unwind.
A nested unwind is an unwind that is initiated while a
previous unwind is active and whose target invocation in the procedure
invocation chain is not a predecessor of the most current active unwind
handler. A nested unwind does not terminate any procedure invocation
that would have been terminated by the previously active unwind.
When a nested unwind is initiated, no special rules apply. The nested
unwind operation proceeds as a normal unwind operation, and when
execution resumes at the target location of the nested unwind, the
nested unwind is complete and the previous unwind is once again the
most current unwind operation.
An overlapping unwind is an unwind that is initiated
while a previous unwind is active and whose target invocation in the
procedure invocation chain is a predecessor of the most current active
unwind handler. An overlapping unwind terminates one or more procedure
invocations that would have been terminated by the previously active
unwind.
An overlapping unwind is detected when the most current active unwind
handler is terminated. This detection of an overlapping unwind is
termed an unwind collision.
When a GOTO unwind collides with a GOTO unwind, the later unwind
supersedes the earlier unwind, which is abandoned. The later unwind
then continues from the point of the collision.
The result of any other collision is undefined.
Appendix A Stack Unwinding and Exception Handling on OpenVMS I64
Stack unwinding is the process of tracing backwards
through the stack of invocation contexts of a thread. Every active
procedure has one invocation context. An invocation context has memory
(a frame) on the register stack, the memory stack, or
both. To trace backwards through the stack of invocation contexts, it
must be possible to identify each invocation context and its associated
frames. Exception handling often requires the ability to trace
backwards through a number of invocation contexts and then to transfer
control to an exception handling routine.
For the register stack, the state of the current register stack frame
together with the AR.PFS register provides sufficient information to
identify the previous frame. However, this works for only one level of
nesting, because there is no hardware stack of AR.PFS registers. To
make it possible to unwind the register stack, this calling standard
defines a convention for saving and recovering the AR.PFS register in
each frame.
For the memory stack, it is expected that most procedures will allocate
a frame that does not change in size while the procedure is active. For
these procedures, the fixed frame size is recorded in a static unwind
table, and the instruction pointer (PC) is used as a key into this
table.
To make it possible to unwind frames that vary in size, this calling
standard defines a convention for saving and recovering the SP value
for the previous frame on the stack.
As the register and memory stacks are unwound, it is also necessary to
recover the values of preserved registers that were saved by each
procedure for the following uses:
- So that debuggers have access to correct values of local variables
- So that exception handlers can operate correctly
- To provide values needed for further unwinding
This calling standard defines a convention for saving and recovering
the values of these preserved registers. This convention uses the PC as
a key for locating a static unwind table entry that contains everything
necessary for locating the following values:
- The previous register stack frame
- The memory stack frames
- The previous PC
Unwinding the stack is done using system routines (see Section 4.8.3)
that can be called from the thread itself, from a debugger, or for
exception handling. Stack unwinding operates on context records; the
primary routine reconstructs the context for a previous frame given the
context for its descendent frame.
This appendix describes the following topics:
- The framework for unwinding the stack and for processing exceptions
- The format of the static unwind tables
- The code generation conventions required to perform the above tasks
A.1 Unwinding the Stack
The process of unwinding the stack begins with an initial context
record that describes the process state in the most recent procedure
invocation at the point of interruption. From this initial state, the
stack is unwound one invocation context at a time, using static
information generated by the compilers about each procedure to
reconstruct a context record that describes the previous procedure
(which is suspended at a point just after the procedure call or an
asynchronous interruption).
A.1.1 Initial Context
There is only one way to get an initial context: call
LIB$I64_GET_CURR_INVO_CONTEXT (see Section 4.8.3.7).
A.1.2 Step to Previous Frame
The unwind routines build a context record that corresponds to the next
older frame on the stack. This context record can then be used to
unwind to the previous frame on the stack. The following steps
reconstruct the context for the previous frame using information in the
unwind tables for the current frame:
- Find the return link in the current context, and set PC in the
previous context to that address.
- Find the previous frame marker in the current context (for example,
in the AR.PFS register), and copy it to the current frame marker (CFM)
in the previous context.
- Determine the value of GP for the new PC, and set GP in the
previous context to that value.
- Set SP in the previous context to SP from current context plus the
current size of the memory frame.
- Set AR.BSP in the previous context to AR.BSP from the current
context minus the size of the input/local region of the frame (taking
into account NaT collections that may have been saved to the backing
store). The frame size can be calculated from the frame marker.
- Find the saved copies of the preserved registers in the current
context, and copy them to the previous context.
- Find any VMS-specific Caller Spill Register information (see
Section A.4.3.2) in the unwind information associated with the PC that
was determined in Step 1 and restore any applicable registers saved in
the previous frame.
The bottom of the call stack is identified by a BOTTOM_OF_STACK flag in
the context block.
The information needed to execute these steps correctly is recorded in
static unwind information that is associated with each code segment of
the program itself. The structure of this information is described in
Section A.4. Each code segment has an associated table of static
unwind information, and the operating system provides an API for
finding the unwind table, given a known PC (see Section A.6).
When a thread receives an asynchronous interruption, the thread context
is saved so that the thread can continue executing correctly once the
interruption has been handled. This context is saved on the memory
stack, and a new procedure frame is constructed for the interruption
handler. The first procedure frame in the interruption handler is
marked in such a way that the unwind routine can recognize that
unwinding past the point of interruption requires a restoration of the
full context.
A.2 Exception Handling Framework
The exception handling model for OpenVMS is partitioned into a
language-independent component and a language-dependent component. The
language-independent component is responsible for fielding an
exception, searching for and dispatching to a condition handler and
unwinding the stack. The run-time library of each source language that
supports exception handling must provide a condition
handler that implements the language-dependent component of
this model.
Note
For compatibility with the OpenVMS VAX and Alpha calling standards,
this document uses the term condition handler, rather
than the term personality routine.
|
The exception handling model is oriented around procedure invocation
contexts. Each invocation context corresponds to an activation of a
procedure, which may or may not have associated exception handling
requirements. A language typically uses a single condition handler for
all procedures, but this is not a requirement.
Exceptions are signalled by invoking a routine in the
language-independent component called the exception
dispatcher, which initiates the process of handling the
exception. Synchronous exceptions can be signalled directly by the
application through a language-specific construct; asynchronous
exceptions can be signalled in response to hardware-detected traps or
faults.
The exception dispatcher walks the stack of invocation contexts
non-destructively beginning with the most recent invocation, searching
for the first invocation context with a condition handler. When a
condition handler is found, the exception dispatcher invokes the
condition handler.
A condition handler may perform the following actions:
- Ignore the condition.
- Take some special action and continue from the point at which the
condition occurred.
- End the operation and branch from the sequential flow of control.
- Treat the condition as an unrecoverable error.
- Resignal the exception to the next condition handler.
- Invoke a user-written condition handler.
- Perform language-specific exception handling actions (for example,
C++ try region processing).
If the condition handling facility finds a handler for the exception
that requests an unwind, it invokes the dispatcher to walk the stack a
second time. During the second walk, the dispatcher invokes the
condition handler for each frame again to execute cleanup actions as
necessary. When the dispatcher reaches the frame that contains the
condition handler, control is transferred to the condition handler.
For more details about OpenVMS condition handling, see Chapter 8.
A.3 Coding Conventions for Reliable Unwinding
This section describes the coding conventions that must be observed to
guarantee that the stacks can be unwound from every point in the
program. For the purposes of unwinding, this calling standard divides
every procedure into one or more regions, which are classified as
either prologue or body regions.
A prologue region is one where the register stack and
memory stack frames are established and where key registers are saved.
To unwind correctly when the PC is one of these regions, the unwinder
must have a detailed description of the order of operations within the
region, so that it knows what state has changed, and which registers
have been saved at any given point in that region.
A body region is one for which the register stack and
the memory stack are fully formed and initialized. Although a body
region can change the state of the stack frame and save and restore
preserved registers (for example, to shrink-wrap the
save and restore of a register), the unwind data structures are tuned
for body regions that have few such operations.
A.3.1 Requirements for Unwinding the Stack
Certain constraints must be met in order to unwind the stack
successfully at any time, both by standard procedure calls as described
in Chapter 4, and by special-purpose calling conventions.
Appendix B describes the format of the unwind data structures. To
meet the needs of the stack unwind mechanism, the following rules must
be followed at all times:
- The previous function state register (AR.PFS) must be preserved
prior to any call. The compiler must record, in the unwind data
structures, where this register is stored, and over what range of code
the saved value is valid.
- For special calls using a return branch register other than B0, the
compiler must record the branch register number used for the return
link.
- The return branch register must be preserved prior to any call
involving the same branch register. The compiler must record where the
return branch register is stored and over what range of code the saved
value is valid.
- If a preserved register is saved, the compiler must record where
the preserved register is stored and over what range of code the saved
value is valid.
- If a procedure has a memory stack frame, the compiler must record
either: (1) how large the frame is, or (2) that a previous frame
pointer is stored on the stack or in a general register.
- The return branch register must contain an address that can be used
to determine the unwind state of the calling procedure. For example, a
compiler may choose to optimize calls to procedures that do not return.
If it does so, however, it must ensure that the unwind information for
the procedure properly describes the unwind state at the return point,
even though the return pointer will never be used. This may require the
insertion of an otherwise unnecessary NOP or BREAK instruction.
The following sections provide detailed conventions for satisfying
these requirements.
A.3.2 Conventions for Prologue Regions
A typical prologue region performs some or all of the following steps:
- Allocate a new register stack frame. The order of this step is not
important to the unwind process (although it must precede any other
operations in the prologue that require the use of local stack
registers).
- Allocate a new memory stack frame. For fixed-size frames, the
stack pointer (SP) must be modified in a single instruction (either
with a single add immediate, or by performing intermediate calculations
in a scratch register before modifying SP). The location of this
instruction and the size of the fixed-frame must be recorded in the
unwind descriptor (see Section A.4.1.1).
For variable-size frames,
the stack pointer must be saved in a general register that is kept
valid throughout the remainder of the prologue region and the following
body regions. This copy of the previous stack pointer is called PSP.
The location of the copy instruction and the general register number
must be recorded in the unwind descriptor.
- Save the previous function state (AR.PFS), either in a general
register or on the memory stack. The location of this instruction and
the general register number (or stack offset) must be recorded in the
unwind descriptor. Normally, the previous function state is copied to a
general register by the ALLOC instruction that allocates a new register
stack frame. However, if the previous function state is to be stored in
the memory stack, the location of the instruction that stores the
general register to the memory stack must be recorded, and the original
PFS must not be modified until after the store.
- Save the return pointer (RP), either in a general register or on
the memory stack. The location of this instruction and the general
register number (or stack offset) must be recorded in the unwind
descriptor. Saving RP to the memory stack requires the following steps:
- Copy it to a general register.
- Store it (the location of this store is the one to record). The
original RP must not be modified before the store.
- Save the preserved registers, either on the memory stack or in
local registers in the current register stack frame. In general, the
location of each instruction used to save a preserved register and the
general register number (or stack offset) must be recorded. There are
five groups of registers:
- General registers
- Floating-point registers
- Branch registers
- Predicate registers
- Application registers
The predicate registers must be copied as a whole to a general
register with a single Move from Predicates instruction; if they are to
be stored on the memory stack, the Store instruction is the one to
record. Any arbitrary subset of preserved general registers,
floating-point registers, and branch registers can be saved in a
prologue, but they must be saved in ascending order by register number
within each group (saves from different register groups may be
interleaved). Saving a branch register to memory (other than RP)
requires the following steps:
- Move to general register.
- Store it (the location of this store is the one to record). The
value of the branch register must not be modified until the store is
completed.
The unwinder must also know where preserved registers are saved in the
memory stack frame, because it must reconstruct the values of these
registers as it unwinds the stack. The conventions for the spill area
are discussed in Section A.3.5.
A prologue region can contain code that is irrelevant to the unwind
process. However, for efficiency during the unwind process, observe the
following guidelines:
- Keep the size of the prologue region as small as possible.
- End the prologue immediately after allocating stack frames and
saving registers.
When OpenVMS semantics apply (see Section A.4.1), a condition handler
will not be called for an exception that occurs in a prologue or
epilogue because the procedure is not current (see Section 4.8.1), but
a condition handler of the caller will be considered. Therefore, a
prologue region can not occur in the interior of a procedure, except
for a zero-length prologue that describes the initial state for
noncontiguous code segments. General unwind descriptors must be used in
the interior of a procedure instead of prologue descriptors (see
Section A.4.1.3) to describe needed changes in unwind state.
For a routine that has no condition handler, there is no restriction on
the use of prologue descriptors, even interior to the body.
|