*** empty log message ***

git-svn-id: https://swig.svn.sourceforge.net/svnroot/swig/trunk/SWIG@1072 626c5289-ae23-0410-ae9c-e8d60b6d4f22
This commit is contained in:
Dave Beazley 2001-04-01 04:53:32 +00:00
commit 55b1139976

View file

@ -3,7 +3,7 @@
%use at your own risk. Complaints to /dev/null.
%make two column with no page numbering, default is 10 point
%\documentstyle{article}
\documentstyle[twocolumn]{article}
\documentstyle[twocolumn,times]{article}
%\pagestyle{empty}
%set dimensions of columns, gap between columns, and space between paragraphs
@ -127,14 +127,16 @@ scripting while retaining the best features of compiled code such as high
performance \cite{ouster1}.
A critical aspect of scripting-compiled code integration is the way in
which it departs from traditional C/C++ development. Rather than
building large monolithic stand-alone applications, scripting
languages strongly encourage the creation of modular software
components. Because of this, scripted software tends to be constructed as
a mix of dynamically loadable libraries, scripts, and third-party
extension modules. In this sense, one might argue that the benefits of
scripting are achieved at the expense of creating a somewhat more
complicated development environment.
which it departs from traditional C/C++ development and shell
scripting. Rather than building stand-alone applications that run as
separate processes, extension programming encourages a style of
programming in which components are more tightly integrated within the
process of an interpreter that is responsible for high-level control.
Because of this, scripted software tends to rely heavily
upon shared libraries, dynamic loading, scripts, and
third-party extensions. In this sense, one might argue that the
benefits of scripting are achieved at the expense of creating a
more complicated and decentralized development environment.
A consequence of this complexity is an increased degree of difficulty
associated with debugging programs that utilize multiple languages,
@ -180,35 +182,37 @@ produce the following result:
Segmentation Fault (core dumped)
\end{verbatim}
In this case, the user has no idea of what has happened other
than it appears to be ``very bad.'' To make matters worse, script-level
debuggers are unable to identify the problem since they also crash
when the error occurs (they usually run in the same process as
the interpreter). A user might be able to narrow the source of the
problem through trial-and-error techniques such as inserting print
statements or commenting out sections of script code. However,
neither of these techniques are very attractive for obvious reasons.
In this case, the user has no idea of what has happened other than it
appears to be ``very bad.'' Furthermore, script-level debuggers are
unable to identify the problem since they also crash when the error
occurs (they run in the same process as the interpreter). This means
that the only way for a user to narrow the source of the problem is
through trial-and-error techniques such as inserting print statements,
commenting out sections of scripts, or having a deep intuition of the
underlying implementation. Obviously, none of these techniques are
entirely satisfactory.
Alternatively, a user could run the application under the control of a
traditional debugger such as gdb \cite{gdb}. Although this certainly provides
some information about the error, the debugger mostly provides information about the
internal implementation of the scripting language interpreter.
Needless to say, this isn't very useful nor does it provide much insight as to
where the error might have occurred within a script. A related problem is that
An alternative approach is to run the application under the control of
a traditional debugger such as gdb \cite{gdb}. Although this provides
some information about the error, the debugger mostly provides
detailed information about the internal implementation of the
scripting language interpreter instead of the script-level code that
was running at the time of the error. Needless to say, this information
isn't particularly useful for most programmers.
A related problem is that
the structure of a scripted application tends to be much more complex
than a traditional stand-alone program. As a result, a user may not
have a good sense of how to actually attach a C/C++ debugger to their
have a good sense of how to actually attach an external debugger to their
script. In addition, execution may occur within a
complex run-time environment involving events, threads, and network
connections. Because of this, it can be difficult to reproduce
and identify certain types of catastrophic errors (especially if they
depend on timing or peculiar sequences of events). Finally, this approach
assumes that a programmer has a C/C++ development environment installed on
their machine and that they know how to use a low-level C source
and identify certain types of catastrophic errors if they depend on
timing or unusual event sequences. Finally, this approach
assumes that a programmer has a C development environment installed on
their machine and that they know how to use a low-level source
debugger. Unfortunately, neither of these assumptions may hold in practice.
This is because scripting languages are often used to provide programmability to
applications in which end-users might write scripts, yet would not be expected
to write low-level C code.
applications where end-users write scripts, but do not write low-level C code.
Even if a traditional debugger such as gdb were modified to provide
better integration with scripting languages, it is not clear that this
@ -239,9 +243,9 @@ Traceback (most recent call last):
SegFault: [ C stack trace ]
#2 0x00027774 in call_builtin(func=0x1c74f0,arg=0x1a1ccc,kw=0x0) in 'ceval.c', line 2650
#1 0xff083544 in _wrap_doh(self=0x0,args=0x1a1ccc) in 'foo_wrap.c', line 745
#0 0xfe7e0568 in doh(a=0x3,b=0x4,c=0x0) in 'foo.c', line 28
#2 0x00027774 in call_builtin(func=0x1c74f0,arg=0x1a1ccc,kw=0x0) in 'ceval.c',line 2650
#1 0xff083544 in _wrap_doh(self=0x0,args=0x1a1ccc) in 'foo_wrap.c',line 745
#0 0xfe7e0568 in doh(a=3,b=4,c=0x0) in 'foo.c',line 28
/u0/beazley/Projects/WAD/Python/foo.c, line 28
@ -254,20 +258,22 @@ SegFault: [ C stack trace ]
\caption{Cross language traceback generated for a segmentation fault in a Python extension}
\end{figure*}
The current solution to the debugging problem is to take a proactive approach and simply add as
much error checking as possible to extension code. Although this is never
a bad thing to do, it's usually not enough to completely eliminate the problem.
For one, scripting languages are sometimes used to control hundreds
of thousands to millions of lines of compiled code. In this case, it is simply improbable
that a programmer will be able to foresee every conceivable error.
In addition, scripting languages are often used to put new user interfaces on legacy software. In this
case, scripting may introduce new modes of execution that cause a formerly ``bug-free''
application to fail in an unexpected manner. Finally, certain types
of errors such as floating-point exceptions can be particularly
difficult to eliminate because they might be generated algorithmically (e.g.,
as the result of instability in a numerical method). Therefore, even when a programmer has worked hard to eliminate
crashes, there is always a small probability that a complex application
will fail.
The current state of the art in extension debugging is to simply add
as much error checking as possible to extension modules. This is never
a bad thing to do, but in practice it's usually not enough to
eliminate every possible problem. For one, scripting languages are
sometimes used to control hundreds of thousands to millions of lines
of compiled code. In this case, it is improbable that a programmer
foresee every conceivable error. In addition, scripting languages are
often used to put new user interfaces on legacy software. In this
case, scripting may introduce new modes of execution that cause a
formerly ``bug-free'' application to fail in an unexpected manner.
Finally, certain types of errors such as floating-point exceptions can
be particularly difficult to eliminate because they might be generated
algorithmically (e.g., as the result of instability in a numerical
method). Therefore, even if a programmer has worked hard to eliminate
crashes, there is always a small probability that a complex
application will fail.
\section{Embedded Error Reporting}
@ -417,23 +423,26 @@ either ignore the problem or label it as an ``limitation.''
\section{Overview of WAD}
WAD installs a reliable signal handler for
SIGSEGV, SIGBUS, SIGABRT, SIGILL, and SIGFPE using {\tt sigaction}
\cite{stevens}. Since none of these signals are normally used in the implementation
of the scripting interpreter or by any user scripts, this typically does not override any previous
signal handling. Afterwards, when one of these signals occurs, a two-phase
recovery process executes. First,
information is collected about the execution context including a
full stack-trace, symbol table entries, and debugging information.
Second, the current stream of execution is aborted and an error is
returned to the interpreter. This process is illustrated in Figure~3.
WAD installs a signal handler for SIGSEGV, SIGBUS, SIGABRT, SIGILL,
and SIGFPE using the {\tt sigaction} function
\cite{stevens}. Furthermore, it uses a special option (SA\_SIGINFO) of
signal handling that passes process context information to the signal
handler when a signal occurs. Since none of these signals are normally used in the
implementation of the scripting interpreter or by any user scripts,
this typically does not override any previous signal handling.
Afterwards, when one of these signals occurs, a two-phase recovery
process executes. First, information is collected about the execution
context including a full stack-trace, symbol table entries, and
debugging information. Second, the current stream of execution is
aborted and an error is returned to the interpreter. This process is
illustrated in Figure~3.
The collection of context and debugging information is a relatively
straightforward process involving the following steps:
\begin{itemize}
\item The program counter and stack pointer are obtained from
context information passed to the WAD signal handler.
context information passed to the signal handler.
\item The virtual memory map of the process is obtained from /proc
and used to associate virtual memory addresses with executable files,
@ -443,19 +452,19 @@ shared libraries, and dynamically loaded extension modules \cite{proc}.
each step of the stack traceback, symbol table and debugging
information is gathered and stored in a generic data structure for later use
in the recovery process. This data is obtained by memory-mapping
the ELF format object files associated with the process and extracting
symbol table and stabs debugging information \cite{elf,stabs}.
the object files associated with the process and extracting
symbol table and debugging information.
\end{itemize}
Once debugging information has been collected, the signal handler
enters an error-recovery phase that
attempts to raise an exception and return to a suitable location in the
attempts to raise a scripting exception and return to a suitable location in the
interpreter. To do this, the following steps are performed:
\begin{itemize}
\item The stack trace is examined to see if there are any locations to which
control can be returned.
\item The stack trace is examined to see if there are any locations in the interpreter
to which control can be returned.
\item If a suitable return location is found, the CPU context is modified in
a manner that makes the signal handler return to the interpreter
@ -465,18 +474,21 @@ return to the interpreter after the signal handler returns.
\end{itemize}
\noindent
Of the two phases, the return to the interpreter is of greater interest. Therefore, it
is now described in greater detail.
Of the two phases, the first is the most straightforward to implement
because it involves standard Unix API functions and common file formats such
as ELF and stabs \cite{elf,stabs}. On the other hand, the recovery phase in
which control is returned to the interpreter is of greater interest. Therefore,
it is now described in greater detail.
\begin{figure*}[t]
\begin{picture}(480,340)(5,60)
\put(50,330){\framebox(200,70){}}
\put(60,388){\tt >>> {\bf foo()}}
\put(60,376){\tt Traceback (most recent call last):}
\put(70,364){\tt File "<stdin>", line 1, in ?}
\put(60,352){\tt SegFault: [ C stack trace ]}
\put(60,340){\tt ...}
\put(60,388){\small \tt >>> {\bf foo()}}
\put(60,376){\small \tt Traceback (most recent call last):}
\put(70,364){\small \tt File "<stdin>", line 1, in ?}
\put(60,352){\small \tt SegFault: [ C stack trace ]}
\put(60,340){\small \tt ...}
\put(55,392){\line(-1,0){25}}
\put(30,392){\line(0,-1){80}}
@ -532,29 +544,29 @@ is now described in greater detail.
\section{Returning to the Interpreter}
To return to the interpreter, WAD maintains a table of symbolic names
and return values that correspond to locations within the interpreter responsible for invoking
wrapper functions and object/type methods. For example, Table 1 shows a partial list of
return locations used in the Python implementation. When an error
occurs, the call stack is scanned for the first occurrence of any
symbol in this table. If a match is found, control is returned to that location
by emulating the return of a wrapper function with the error code from the table. If
no match is found, the error handler simply prints a stack trace to
and return values that correspond to locations within the interpreter
responsible for invoking wrapper functions and object/type methods.
For example, Table 1 shows a partial list of return locations used in
the Python implementation. When an error occurs, the call stack is
scanned for the first occurrence of any symbol in this table. If a
match is found, control is returned to that location by emulating the
return of a wrapper function with the error code from the table. If no
match is found, the error handler simply prints a stack trace to
standard output and aborts.
When a symbolic match is found, WAD invokes a special user-defined
handler function that is written for a specific scripting language.
The primary role of this handler is to take debugging information
gathered from the call stack and generate an appropriate scripting language error.
One peculiar problem of this step is that the generation
of an error may require the use of parameters passed to a
gathered from the call stack and generate an appropriate scripting
language error. One peculiar problem of this step is that the
generation of an error may require the use of parameters passed to a
wrapper function. For example, in the Tcl wrapper shown earlier, one
of the arguments was an object of type ``{\tt Tcl\_Interp *}''.
This object contains information specific to the state of the
interpreter (and multiple interpreter objects may exist in a single
application). Unfortunately, no reference to the interpreter object is
available in the signal handler. Furthermore, the interpreter
object may not be available in the context of a function that generated the error.
of the arguments was an object of type ``{\tt Tcl\_Interp *}''. This
object contains information specific to the state of the interpreter
(and multiple interpreter objects may exist in a single application).
Unfortunately, no reference to the interpreter object is available in the
signal handler nor is a reference to interpreter guaranteed to exist in
the context of a function that generated the error.
\begin{table}[t]
\begin{center}
@ -577,7 +589,8 @@ PyObject\_GetAttrString & NULL \\
To work around this problem, WAD implements a feature
known as argument stealing. When examining the call-stack, the signal
handler has full access to all function arguments and local variables.
handler has full access to all function arguments and local variables of each function
on the stack.
Therefore, if the handler knows that an error was generated while
calling a wrapper function (as determined by looking at the symbol names),
it can grab the interpreter object from the stack frame of the wrapper and
@ -591,14 +604,17 @@ code similar to the following is written:
Tcl_Interp *interp;
int err;
interp = (Tcl_Interp *) wad_steal_outarg(
interp = (Tcl_Interp *)
wad_steal_outarg(
stack,
"TclExecuteByteCode",
1,
&err);
&err
);
...
if (!err) {
Tcl_SetResult(interp,errtype,TCL_STATIC);
Tcl_AddErrorInfo(interp,errdetails);
Tcl_SetResult(interp,errtype,...);
Tcl_AddErrorInfo(interp,errdetails);
}
\end{verbatim}
@ -609,6 +625,11 @@ At this time, argument stealing is only applicable to simple types
such as integers and pointers. However, this is adequate for generating
scripting language errors.
The symbolic matching approach is particularly attractive because it
does not require an extensive amount of detail about the
implementation of the interpreter or the way in which it has been
linked.
\section{Register Management}
A final issue concerning the return mechanism has to do with the
@ -618,27 +639,29 @@ library call. However, this is done without the use of a matching
{\tt setjmp} in the interpreter.
The primary problem with aborting execution and returning to the
interpreter in this manner is that most compilers use a register management technique
known as callee-save \cite{prag}. In this case, it is the responsibility of
the called function to save the state of the registers and to restore
them before returning to the caller. By making a non-local jump,
registers may be left in an inconsistent state due to the fact that
they are not restored to their original values. The {\tt longjmp} function
in the C library avoids this problem by relying upon {\tt setjmp} to save
the registers. Unfortunately, WAD does not have this
luxury. As a result, a return from the signal handler may produce a
corrupted set of registers at the point of return in the interpreter.
interpreter in this manner is that most compilers use a register
management technique known as callee-save \cite{prag}. In this case,
it is the responsibility of the called function to save the state of
the registers and to restore them before returning to the caller. By
making a non-local jump, registers may be left in an inconsistent
state due to the fact that they are not restored to their original
values. The {\tt longjmp} function in the C library avoids this
problem by relying upon {\tt setjmp} to save the registers. Unfortunately,
WAD does not have this luxury. As a result, a return from the signal
handler may produce a corrupted set of registers at the point of return
in the interpreter.
The severity of this problem depends greatly on the architecture and
compiler. For example, on the SPARC, register windows effectively
solve the callee-save problem \cite{sparc}. In this case, each stack frame has its own
register window and the windows are flushed to the stack whenever a
signal occurs. Therefore, the recovery mechanism can simply examine the stack and
arrange to restore the registers to their proper values when control
is returned. Furthermore, certain conventions of the SPARC ABI resolve several related
issues. For example, floating point registers are caller-saved
and the contents of the SPARC global registers are not guaranteed to be preserved
across procedure calls (in fact, they are not even saved by {\tt setjmp}).
solve the callee-save problem \cite{sparc}. In this case, each stack
frame has its own register window and the windows are flushed to the
stack whenever a signal occurs. Therefore, the recovery mechanism can
simply examine the stack and arrange to restore the registers to their
proper values when control is returned. Furthermore, certain
conventions of the SPARC ABI resolve several related issues. For
example, floating point registers are caller-saved and the contents of
the SPARC global registers are not guaranteed to be preserved across
procedure calls (in fact, they are not even saved by {\tt setjmp}).
On other platforms, the problem of register management becomes much
more interesting. In this case, a heuristic approach that examines
@ -647,12 +670,24 @@ determine where the registers might have been saved. This approach is
used by gdb and other debuggers when they allow users to inspect
register values within arbitrary stack frames \cite{gdb}. Even though
this sounds complicated to implement, the algorithm is greatly
simplified by the fact that compilers usually generate code to store
simplified by the fact that compilers typically generate code to store
the callee-save registers immediately upon the entry to each function.
In addition, this code is highly regular and easy to examine. For instance, on
i386-Linux, the callee-save registers can be fully restored by simply
examining the first 12 bytes of the machine code for each function on
the stack.
In addition, this code is highly regular and easy to examine. For
instance, on i386-Linux, the callee-save registers can be restored by
simply examining the first few bytes of the machine code for each
function on the call stack to figure out where values have been saved.
For example, the following code shows a typical sequence of machine instructions
used to store callee-save registers on the i386:
\begin{verbatim}
foo:
55 pushl %ebp
89 e5 mov %esp, %ebp
83 a0 subl $0xa0,%esp
56 pushl %esi
57 pushl %edi
...
\end{verbatim}
%
% Include an example
@ -676,42 +711,207 @@ the stack.
% not yet been implemented in WAD due to its obvious implementation difficulty and the
% fact that the WAD prototype has primarily been developed for the SPARC.
As a fall-back, WAD can be configured to return control to a location
As a fall-back, WAD could be configured to return control to a location
previously specified with {\tt setjmp}. Unfortunately, this either
requires modifications to the interpreter or its extension modules.
Although this kind of instrumentation can be facilitated by automatic
Although this kind of instrumentation could be facilitated by automatic
wrapper code generators, it is not a preferred solution and is
not discussed further.
\section{Making WAD Easy to Use}
\section{Initialization}
To make the debugging of extension modules as simple as possible, it
is desirable to make the use of WAD as transparent as possible.
Currently, there are two ways in which the system is used. First, WAD
may be explicitly loaded as a scripting language extension module.
For instance, in Python, a user can include the statement {\tt import
libwadpy} in a script to load the debugger. Alternatively, WAD can be
implicitly enabled by simply linking it to an extension module as a shared
library. For instance:
\begin{verbatim}
% ld -shared $(OBJS) -lwadpy
\end{verbatim}
In this case, the debugger automatically initializes itself when the
extension module is loaded. The same shared library can be used for
both situations by making sure two types of initialization techniques
are used. First, an empty initialization function is written to make
WAD appear like a proper scripting language extension module (although
it adds no functions to the interpreter). Second, the real
initialization of the system is placed into the initialization section
of the WAD shared library. This code always executes when a library
is first loaded by the runtime loader. A fairly portable way to force
code into the initialization section is to use a C++ statically
constructed object like this:
\begin{verbatim}
class InitWad {
public:
InitWad() { wad_init(); }
};
/* This forces InitWad() to execute
on loading. */
static InitWad init;
\end{verbatim}
The nice thing about this trick is that WAD can be enabled by the
linker without having to recompile any extension code or having to
patch existing script code. The downside to this approach is that WAD
can not be linked directly to an interpreter (since its initialization
would occur before any code in the interpreter began to execute).
\section{Exception Objects}
Before WAD returns control to the interpreter, it collects all of the
stack-trace and debugging information it was able to obtain into a
special exception object. This object represents the state of the call
stack and includes things like symbolic names for each stack frame,
the names, types, and values of function parameters and local
variables, as well as a complete copy of data on the stack. This
information is represented in a relatively generic manner that hides
platform specific details related to the CPU, object file formats,
debugging tables, and so forth.
Minimally, the exception data is used to print a stack trace as shown
in Figure 1. However, if the interpreter is successfully able to
regain control, the contents of the exception object can be
freely examined by the user after an error has occurred. For example:
\begin{verbatim}
try:
# Some buggy code
...
except SegFault,e:
print 'Whoa!'
# Get WAD exception object
t = e.args[0]
# Print location info
print t.__FILE__
print t.__LINE__
print t.__NAME__
print t.__SOURCE__
...
\end{verbatim}
The exception object also makes it possible to write post mortem
debuggers that merge the call stacks of the two languages together and
provide cross language diagnostics. For instance, Figure 4 shows an
example of a simple mixed language debugging session using the WAD
post-mortem debugger (wpm) after an extension error has occurred in a
Python program. In the figure, the user is first presented with a
multi-language stack trace. The information in this trace is obtained
both from the WAD exception object and from the Python traceback
generated when the exception was raised. Next, we see the user walking
up the call stack (the 'u' command of the debugger). As this
proceeds, there is a seamless transition from C to Python where the
trace crosses between the two languages. An optional feature of the
debugger (not shown) allows the debugger to walk up the entire C
call-stack (in this case, the trace shows information about the
implementation of the Python interpreter). More advanced features of
the debugger also allow the user to query values of function
parameters, local variables, and stack frames (although some of this
information may not be obtainable due to compiler optimizations and the
difficulties of accurately recovering register values).
\begin{figure*}[t]
{\small
\begin{verbatim}
[ Error occurred ]
>>> from wpm import *
*** WAD Debugger ***
#5 [ Python ] in self.widget._report_exception() in ...
#4 [ Python ] in Button(self,text="Die", command=lambda x=self: ...
#3 [ Python ] in death_by_segmentation() in death.py, line 22
#2 [ Python ] in debug.seg_crash() in death.py, line 5
#1 0xfeee2780 in _wrap_seg_crash(self=0x0,args=0x18f114) in 'pydebug.c', line 512
#0 0xfeee1320 in seg_crash() in 'debug.c', line 20
int *a = 0;
=> *a = 3;
return 1;
>>> u
#1 0xfeee2780 in _wrap_seg_crash(self=0x0,args=0x18f114) in 'pydebug.c', line 512
if(!PyArg_ParseTuple(args,":seg_crash")) return NULL;
=> result = (int )seg_crash();
resultobj = PyInt_FromLong((long)result);
>>> u
#2 [ Python ] in debug.seg_crash() in death.py, line 5
def death_by_segmentation():
=> debug.seg_crash()
>>> u
#3 [ Python ] in death_by_segmentation() in death.py, line 22
if ty == 1:
=> death_by_segmentation()
elif ty == 2:
>>>
\end{verbatim}
}
\caption{Cross-language debugging session in Python where user is walking up the call stack.}
\end{figure*}
\section{Design and Portability Concerns}
\section{Implementation Details}
Currently, WAD is implemented in ANSI C and small amount of assembly
code to assist in the return to the interpreter. The current
implementation supports Python, Tcl, and Perl extensions on SPARC Solaris. An
i386-Linux port has also been developed. The entire implementation contains
approximately 1500 semicolons and most of this code is related to the gathering of debugging
information. Furthermore, due to the hostile environment in which the
recovery process must run, the implementation takes great care not to utilize the
process heap. This allows the signal handler to collect information in situations
where the heap allocator has been corrupted or destroyed in some manner.
implementation supports Python and Tcl extensions on SPARC Solaris and
i386-Linux. The entire implementation contains approximately 2000
semicolons. Most of this code is related to the gathering of
debugging information from object files. Only a small part of the
code is specific to a particular scripting language (170 semicolons for Python
and 50 semicolons for Tcl). Furthermore, due to the
hostile environment in which the recovery process must run, the
implementation takes great care not to use heap allocated memory or
library functions that might require memory allocation. This
conservative approach allows the signal handler to collect information
in situations where the heap allocator has been corrupted or destroyed
in some manner.
Although there are libraries such as the GNU Binary File Descriptor
(BFD) library that can assist with the manipulation of object files
these are not used in the implementation \cite{bfd}. First, these
these are not used in the implementation \cite{bfd}. These
libraries tend to be quite large and are oriented more towards
stand-alone tools such as debuggers, linkers, and loaders. Second,
stand-alone tools such as debuggers, linkers, and loaders. In addition,
the behavior of these libraries with respect to memory management
would need to be carefully studied before they could be safely used in
an embedded environment. Finally, given the small size of the
implementation, it didn't seem necessary to rely upon such a
an embedded environment. Finally, given the small size of the prototype
implementation, it didn't seem necessary to rely upon such a
heavyweight solution.
A surprising feature of the implementation is that a significant
amount of the code is language independent. Language
independence is achieved by placing all of the process introspection,
data collection, and platform specific code within a centralized core.
To provide a specific scripting language interface, a developer
only needs to supply two things; a table containing symbolic function
names where control can be returned (Table 1), and a
handler function in the form of a callback. As input, this handler
receives a generic exception object that represents traceback data
in a platform neutral representation. This information can then be used to raise
an appropriate scripting language exception. It turns out that the core
can also be used without any scripting language interface at all. In this case,
an application linked with WAD will simply print a stack trace and exit when
an error occurs.
Significant portions of the core are also platform independent. For
instance, code to read ELF object files and stabs debugging data is
essentially identical for Linux and Solaris. In addition, the
high-level control logic is unchanged between platforms. Platform
specific differences arise in the obvious places including the
examination of CPU registers, manipulation of the process context in
the signal handler, reading the virtual memory map from /proc, and so
forth. To extent that it is possible, platform differences
can be hidden by abstraction mechanisms (although the initial
implementation of WAD is weak in this regard and would benefit from
techniques used in more advanced debuggers such as gdb).
\section{Discussion}
The primary goal of embedded error recovery is to provide an
@ -794,6 +994,8 @@ destruction of the heap or stack.
\section{Related Work}
(add Java, PyDebug)
A huge body of literature is devoted to the topic of exception
handling in various languages and systems. Furthermore, the topic
remains one of active interest in the software community. For