home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frozen Fish 1: Amiga
/
FrozenFish-Apr94.iso
/
bbs
/
alib
/
d1xx
/
d179
/
excption.lha
/
Excption
/
excption.man
< prev
next >
Wrap
Text File
|
1989-02-25
|
26KB
|
924 lines
EXCEPTION HANDLING ROUTINES
_______ _ _ Version 0.6
July 6th, 1988
Gerald T HEWES
46 Blvd Inkermann
92220 NEUILLY S/S
FRANCE
I INTRODUCTION
The purpose of this module is to provide a programmer with a set of
easy to use routines to handle error conditions. The principle of
exception handling is inspired from Ada(r) exception routines.
Exception handling helps to solve difficult error handling conditions
such as No more memory, file not open, read/write error.... Usually
because of the difficulty of handling these errors, not much action is
done to correct them. How many programmers faithfully test all their
fread or fwrite results for possible errors?
Exception may be used by any program either as a flow control or
either to capture software errors i.e 68000 exceptions and Traps.
Exception makes the usage of traps very easy from C, since no assembly
code is required.
Experienced programmers might not find anything really new about
these routines and may have already adopted ways to deal with error
handling but I feel it might help recent Amiga programmers and anybody
who is not interested in wrinting for the (n+1)th time similar
procedures.
These routines are very different from the GOMF software. You may
have as many "protected code" zones in your program as you want, and
you dispose of a mean to propagate easily errors upward if you cannot
fix the problem at a low level (usually the case for fwrites's). In
any case, no actions are taken whatsoever, control is simply passed to
your local (context wise) handling routine. Your task is not
suspended. All your handling routines are in user mode with
multitasking enabled.
- 0 -
1.2 Contents
I INTRODUCTION
II EXCEPTION HANDLING
2.1 Introduction to Exception Handling
2.1.1 Top Level
2.1.2 Resource Protection
2.2 How to Use Excption
2.2.1 ExcpGlobal
2.2.2 MAIN
2.2.3 ExcpDeclare
2.2.4 BEGIN/EXCEPTION/END
2.2.5 Variables
2.2.6 Excption Class Numbers
2.3 Important Data
III ORGANISATION
3.1 Files
3.2 Macros
IV IMPROVEMENTS
V CONCLUSION
VI BIBLIOGRAPHY
- 1 -
II EXCEPTION HANDLING
2.1 Introduction to Exception Handling
Error Management and complexe human interfaces are always a
difficult problem in a program, and no perfect solution exists.
Exception handling is one way to alleviate the problem, but does not
solve it. These techniques were originally applied on a sun
workstation for an interactive program. Port was then done on the
Amiga, with a better interface. Originally, the ideas commes from
Ada's(r) exception handling but has been slightly modified to take
into account C's differences. A first difference is the use of
Macros, these do not have the power of defined keywords in Ada. The
second major difference stems from the fact that Ada(r) protects you
from every error condition possible, while this library will only
recuperate errors which normally give birth to
"SoftWare Error/Task Held". Thus you still enjoy fireworks for fatal
errors destroying the operation of the system.
What is exactly exception programing? In gross terms, algorithms
usually describe the normal flow of operations. Unfortunately,
exceptional activities can happen and have to be treated. If nothing
is provided by the language, the programmer has to introduce many
control statements to take into account these abnormal conditions.
The programer usually ends up with a code twice the size of the
original algorithm. This has two inconvenients: the code is no longer
clear because of the overhead, and it is hard to follow the flow of a
normal program. Exception programing tries to solve this problem.
The principle is clear, write the normal code, and add at the end of
this code the code describing those exceptional events. Each part
being separate, programs get much clearer.
Lets consider a simple problem. You want to translate from
cartesian coordinates to polar coordinates. The method in the first
quadrant is theta = atan (y/x). This formula is true for x <> 0, if x
= 0, then theta = pi/2. With no exceptions this gives:
- 2 -
if (x != 0)
{
z = y/x;
theta = atan(z);
}
else
theta = pi/2;
The normal flow is lost in the if condition. Exception programing
would prefer:
ADA:
begin
z := y/x;
theta := atan(z);
exception
when NUMERIC_ERROR =>
theta = pi/2;
when others =>
PUT("UNKNOWN ERROR");
raise ERROR;
end;
This is simply translated into:
C:
BEGIN
{
z = y/x;
theta = atan(z);
}
EXCEPTION
{
switch(Eclass)
{
case ZERO_DIVIDE : theta = pi/2; break;
default : puts("UNKNOWN ERROR"); RAISE(ERROR);
}
}
END;
This may not seem remarkable on a simple example, but it can become
very handy in real life problems which usually are not coded in 6
lines.
The idea is simple. In a protected region, you describe the simple
algorithm. Automatic errors, or software detected ones can provoke an
"Exception" which will cause the program to abandon the protected
region to execute the handler region instead. Either this handler is
qualified to handle the exception and thus controls resumes after the
protected/handler block, or the handler can decide to propagate the
exception to the next handler.
This is the second advantage of Exception programing. Protected
blocks can be inscribed inside each other like russian dolls. An
exception can thus be handled, at the most convenient level, and
necessary action while "poping" out can be performed, like liberating
- 3 -
some resources allocated in the normal flow.
This is why Exception handling is very practical in riche
environments like the amiga. For example, you can isolate all the
function calls in your main Wait loop with a protection zone. If
anything happens, including an abort request, control resumes back to
your main loop where you can decide what to do. Thus your main Wait
loop can be seen as a "top level" for your program. Deep in your
code, if you get stuck, a raise to the "top level" is always
available.
Here are a few possible scenarios:
2.1.1 Top Level
forever
{
Wait();
if (EXIT) break;
BEGIN
{
switch() { Some Dangerous Functions }
}
EXCEPTION
{
switch(Eclass) { .... }
}
END
}
2.1.2 Resource Protection
In this construction, you cannot leave the area without liberating
your resource, even if the error will be handled at higher level.
Allocate-Resource
BEGIN
{
Use-Resource (Dangerous Area)
Liberate-Resource
}
EXCEPTION
{
Yell
Liberate-Resource
Propagate-Error-If-Necessary
}
END
2.2 How to Use Excption
In order to use all the exception handling routines you need to
include the following file:
#include "local:excption.h"
For examples of the use of the exception handler consult the two
- 4 -
supplied examples "essai.c" and "fact.c".
2.2.1 ExcpGlobal
This static definition is needed once in your code. It reserves a
structure needed by the handling code to operate. ExcpGlobal is
implemented as a macro.
2.2.2 MAIN
The first protected block, called throughout this document "top
level block" is of a particular form. This block does not need a
declaration statement of the form ExcpDeclare, all relevant data is
already declared in the ExcpGlobal structure. The format of the first
protected is MAIN/EXCEPTION/OUT. The form BEGIN/EXCEPTION/END cannot
be used. This is very important because the MAIN and OUT macro
perform the necessary initialization and conclusion functions.
Here is an example:
main()
{
other declarations ...
some code ...
MAIN
{
protected code ...
}
EXCEPTION
{
Exception handling code ...
}
OUT
yet more code ...
}
Note: the toplevel does not need to be in the main function.
2.2.3 ExcpDeclare
Declare an exception handling routine in the current function.
This is a macro which hides the necessary declaration of hidden
variables. As such it must be used before any program statement.
Currently only one protected zone may exist in one routine.
2.2.4 BEGIN / EXCEPTION / END
Exception handling is based on the notion of protected blocks of
statements. In the current implementation, only one block per
function is allowed. This in practice has not been an handicap and
may be changed in future releases. This limitation only stems from
the Macros used, not from any limitation by the exception handling
routines. The syntax is:
- 5 -
toto()
{
ExcpDeclare;
other declarations ...
some code ...
BEGIN
{
protected code ...
}
EXCEPTION
{
Exception handling code ...
}
END
yet more code ...
}
In the some code block, nothing is changed, so put your faithful code
in this area. No niceties are provided.
In the protected code block, you have acces to the following
functions:
RAISE(ExcpClass) : raise an error.
BLOW(ExcpClass) : raise/blow to top level handler.
Control will be transferred to the exception handling block. Your
exception will then be handled by this code. Either the exception is
propagated with a RAISE statement to upper levels, or it is handled
locally. If it is handled locally, execution will resume in the yet
more code area. If no exception occur in the protected code block,
control continues normally in the yet more code area.
These two function may also be called from any subroutines called
in the protected code block. They may not be called from another task
if a task is created in the protected code block.
A return statement is not allowed in the protected code area, and
the exit function is not recommended. Long Jumps are not recommended:
Exception handling is long jumping, stay consistent. Goto's are not
allowed to span in or out of the protected code block. All those
limitations come from the fact that you have to execute the END code
before leaving the function. The END code, disables the current
context and restores the previous (upper) one. If you want to
LongJmp, you can modify your code to use instead the propagation
mechanism.
In the handling code block you should test the exceptions and either
handle them locally, or propagate them upward. You have access to the
same functions:
- 6 -
RAISE(ExcpClass) : raise an error to the next level
BLOW(ExcpClass) : raise/blow to top level handler.
RAISE has the same operation as when employed in the protected code
block, but will transfer control to the next (upper)level handling
code, raising the error. Once raised, there is no way to resume
processing in the yet more code block.
The same restrictions apply to the handling code block as to the
protected block concerning: goto,return,exit,_exit,longjmp,...
2.2.5 Variables
The following variables are defined in the Handler routines:
Eclass : Exception class of the exception
In the exception handling zone, Eclass contains the exception
number being treated. Be sure to check this number because the system
may generate numbers that you have not planned for. For example the
system traps all 68000 errors, Eclass is then the Trap number as
defined by Motorola. This is very useful for program debugging since
the code traps bus errors which usually indicate pointer troubles.
If you do not handle the error locally, the call RAISE(Eclass) is a
must. By doing this you can correct the exception at a higher level.
2.2.6 Excption Class Numbers
All exceptions have an exception class number which serves as an
identification. Numbers from 1-65535 are reserved values which may
not be used. Under the current version words 1-1023 are reserved for
68000 related problems. Check the values defined in excption.h for
more information. This range covers 68000 exception, 68000 traps and
could cover 68000 interrupts. Range 1024-2047 are global types of
error which are also declared in exception.h. Most of these
declarations are inspired from Ada.
Range 2048-65535 are reserved for libraries. These values cannot
be used for an application.
Application must use numbers over 65536.
The programmer must take great care to avoid using the same number
for two different exception. Unfortunately, this has to be done by
hand. This is a major difference with Ada. Two, incompatible ways can
be used for allocating these exception class numbers.
The first method is to #define all your exception class numbers, in
a fashion similar to the handler itself. This is the most simple way
but has the inconvenience of being dangerous. Two exceptions might
use the same number. Coherence has to be maintained by the
programmer. This method is not recommended for large programs.
The second method is to use the allocating function
AllocException(). AllocException returns a unique exception number
starting from 65536. This automatically insures the coherence of all
part of a large program. Its inconvenient is that it requires more
code from the programmer point of view.
The current algorithm for allocation is rudimentary. You can only
- 7 -
call AllocException with an argument of -1. This means, as for many
Amiga allocating function, that you have no preference for the number
allocated. Allocation starts from 65536, and increments by one at
every call. FreeAllocation is for decoration since it does nothing.
I might consider providing better allocation mechanism in the future,
but for the moment I see no use for it.
2.3 Important Data
I add this paragraph since I do not have the appropriate
documentation. If you catch an error report it.
When a 68000 exception occurs, the Amiga OS detects it and treats
it. The Excption 68000 number is calculated and pushed down on the
SSP stack after the usual exception frame. Control is then given
back, in superuser mode, to the code pointed by task->tc_TrapCode.
When using the exception handling routines, this points to my
assembler EIExcpHandler routine.
This routine tests if the code is running on a 68000. If yes, the
SSP stack is corrected by 6+4 bytes for simple exception, and 14+4
bytes for bus or address exceptions. The +4 comes from the Long Word
pushed by the Amiga Os.
If the code is running on a 68010 or +, operations are very
different. The handler, fetches the format number at SSP+8 and
corrects the SSP by the following values:
Values in 16-bit words
Format
0 1 2 3 4 5 6 7 8 9 a b c d e f
-------------------------------------------------------------
4 4 6 0 0 0 0 0 29 10 16 46 0 0 0 0
As usual, 4 bytes have to be added to these numbers. These table
may be inaccurate since I do not have a good documentation on these
values.
After correcting the SSP stack, the handler passes in User Mode,
and calls the EIEndHandler (a C function) with the exception number
(provided by Amiga OS) as sole argument.
- 8 -
III ORGANISATION
3.1 Files
To compile programs you need to assign local: to contain
excption.lib, excption.h and boolean.h. You must have directory named
private with the excppriv.h file in it. Your programs need to include
the definition file "local:excption.h".
As of version 0.4, all routines are compiled seperately. For
Lattice users, these object modules are all joined in the excption.lib
lattice library. To compile your code all you need to do id to add
LIB local:excption.lib
to your blink command. Currently the files are organised as follows:
-excption.h : file which your code needs to include to use the
exception routines.
-boolean.h : file included by excption.h
-excption.lib : library file to use with blink.
-excption.man : this file
-excption.doc : documentation on defined functions
-EI*.c : source for defined functions
-excphand.a : assembler handling routine
-essai.* : A good example of use of exceptions
-fact : A bad example, for different reasons but instructive.
-README : text file describing the contents and the version
differences.
3.2 Macros
This is a resume of the available Macros, Variables and Functions:
BEGIN : M start of a protected block
BLOW : M give control to outmost handler
Eclass : V exception class
END : M end of handler block
ExcpAlloc : F allocate an unique exception class number
ExcpDeclare : M declare an excption block in a routine
EXCPDISABLE : M disable processor error handling
EXCPENABLE : M enable back processor handling (Default Mode)
ExcpFree : F free an allocated exception class number
ExcpGlobal : M declare a global structure needed by exception
EXCEPTION : M end of protected block, beginning of handler
MAIN : M start of outmost protected block (replaces BEGIN)
OUT : M end of outmost protected block (replaces END)
- 9 -
RAISE : M Propagate exception to inmost handler
- 10 -
IV IMPROVEMENTS
The following ideas will be investigated in further releases:
-Signaling to the User that an 68000 exception has occured. Useful
for debugging.
-Final Catch of 68000 exceptions and user notice at the top level.
-Provide a mean for the program to call a supplied function before
giving control to the local exception handling code
-Run time library version
-Stack checking before saving context
-Tracing of entrance into protected blocks in a debug version.
Discussing with some Ada compilers makers was instructive. Their
point of view was that they do not want exception handling to lose
time in normal operations. On the other hand they are willing to
consecrate a big amount of time to solve where the control has to
resume when an exception is raised. This is radically different from
my implementation with longjmps. Each BEGIN statement needs to save
the environment which is slow. When an exception is raised, restoring
the environment is not costly. Those Ada compiler makers have thus
used a completely different approach. At compile time they note where
each handler is active. When an exception occurs, the program tests
where the exception occured, and thus where to branch. It is not
necessary to save the environment at each begin. To achieve this you
need to have exception handling defined in the language, not out of
it. Which is best?
Performances:
Version 0.5 seemed to have no bugs. There are two things to
watch. Bad bugs will crash the system, because they destroy the
environment. Nothing can be done by software. The exception handler
is fairly safe since its always checks its data coherence, if an error
occurs, an exit(200) or exit(201) is performed. These two exits have
always been due to unmatched BEGIN/EXCEPTION/END statements, even in
spots far from where the program stopped. Check those if this happens
to you.
- 11 -
V CONCLUSION
Exception programming is a very powerful tool, but should be
carefully used. 68000 Exceptions should stay exceptional. Keep in
mind that an exception declaration in a routine consumes about 40
bytes in the stack. Recursive functions have to be carefully
examined. A good knowledge of LongJumping although not necessary can
help resolve all kinds of conflicts you may run into. This code can
be particularly helpful in the debugging period, but is unfortunately
incompatible with Fred Fish DBUG macros. In general, this software
does not like longjmps.
Because of its similarity with Ada(r) Exception handling,
consulting a manual on Ada, and mostly on how to use its exception
mechanism is a recommended idea.
- 12 -
VI BIBLIOGRAPHY
* [ADA 83]
REFERENCE MANUAL FOR THE ADA(r) PROGRAMMING LANGUAGE ---
ANSI/MIL-STD 1815 A
* [BEHM 86]
NOTES SUR LE TRAITEMENT DES EXCEPTIONS --- Patrick BEHM, BULL,
Louveciennes
(r) ADA is a registered trademark of the U.S. Government. Ada Joint
Program Office.
- 13 -