git-svn-id: https://swig.svn.sourceforge.net/svnroot/swig/trunk/SWIG@1053 626c5289-ae23-0410-ae9c-e8d60b6d4f22
993 lines
45 KiB
TeX
993 lines
45 KiB
TeX
%template for producing IEEE-format articles using LaTeX.
|
|
%written by Matthew Ward, CS Department, Worcester Polytechnic Institute.
|
|
%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}
|
|
%\pagestyle{empty}
|
|
|
|
%set dimensions of columns, gap between columns, and space between paragraphs
|
|
%\setlength{\textheight}{8.75in}
|
|
\setlength{\textheight}{9.0in}
|
|
\setlength{\columnsep}{0.25in}
|
|
\setlength{\textwidth}{6.45in}
|
|
\setlength{\footheight}{0.0in}
|
|
\setlength{\topmargin}{0.0in}
|
|
\setlength{\headheight}{0.0in}
|
|
\setlength{\headsep}{0.0in}
|
|
\setlength{\oddsidemargin}{0in}
|
|
%\setlength{\oddsidemargin}{-.065in}
|
|
%\setlength{\oddsidemargin}{-.17in}
|
|
%\setlength{\parindent}{0pc}
|
|
|
|
%I copied stuff out of art10.sty and modified them to conform to IEEE format
|
|
|
|
\makeatletter
|
|
%as Latex considers descenders in its calculation of interline spacing,
|
|
%to get 12 point spacing for normalsize text, must set it to 10 points
|
|
\def\@normalsize{\@setsize\normalsize{12pt}\xpt\@xpt
|
|
\abovedisplayskip 10pt plus2pt minus5pt\belowdisplayskip \abovedisplayskip
|
|
\abovedisplayshortskip \z@ plus3pt\belowdisplayshortskip 6pt plus3pt
|
|
minus3pt\let\@listi\@listI}
|
|
|
|
%need an 11 pt font size for subsection and abstract headings
|
|
\def\subsize{\@setsize\subsize{12pt}\xipt\@xipt}
|
|
|
|
%make section titles bold and 12 point, 2 blank lines before, 1 after
|
|
\def\section{\@startsection {section}{1}{\z@}{24pt plus 2pt minus 2pt}
|
|
{12pt plus 2pt minus 2pt}{\large\bf}}
|
|
|
|
%make subsection titles bold and 11 point, 1 blank line before, 1 after
|
|
\def\subsection{\@startsection {subsection}{2}{\z@}{12pt plus 2pt minus 2pt}
|
|
{12pt plus 2pt minus 2pt}{\subsize\bf}}
|
|
\makeatother
|
|
|
|
\newcommand{\ignore}[1]{}
|
|
%\renewcommand{\thesubsection}{\arabic{subsection}.}
|
|
|
|
\begin{document}
|
|
|
|
%don't want date printed
|
|
\date{}
|
|
|
|
%make title bold and 14 pt font (Latex default is non-bold, 16 pt)
|
|
\title{\Large \bf An Embedded Error Recovery and Debugging Mechanism for Scripting Language Extensions}
|
|
|
|
%for single author (just remove % characters)
|
|
\author{{David M.\ Beazley} \\
|
|
{\em Department of Computer Science} \\
|
|
{\em University of Chicago }\\
|
|
{\em Chicago, Illinois 60637 }\\
|
|
{\em beazley@cs.uchicago.edu }}
|
|
|
|
% My Department \\
|
|
% My Institute \\
|
|
% My City, ST, zip}
|
|
|
|
%for two authors (this is what is printed)
|
|
%\author{\begin{tabular}[t]{c@{\extracolsep{8em}}c}
|
|
% Roscoe Giles & Pablo Tamayo \\
|
|
% \\
|
|
% Department of Electrical, Computer, & Thinking Machines Corp. \\
|
|
% and Systems Engineering & Cambridge, MA~~02142. \\
|
|
% and & \\
|
|
% Center for Computational Science & \\
|
|
% Boston University, Boston, MA~~02215. &
|
|
%\end{tabular}}
|
|
|
|
\maketitle
|
|
|
|
%I don't know why I have to reset thispagesyle, but otherwise get page numbers
|
|
\thispagestyle{empty}
|
|
|
|
|
|
\subsection*{Abstract}
|
|
{\em
|
|
In recent years, scripting languages such as Perl, Python, and Tcl
|
|
have become popular development tools for the creation of
|
|
sophisticated application software. One of the most useful features
|
|
of these languages is their ability to easily interact with compiled
|
|
languages such as C and C++. Although this mixed language approach
|
|
has many benefits, one of the greatest drawbacks is the complexity of
|
|
debugging that results from using interpreted and compiled code in
|
|
the same application. In part, this is due to the fact that scripting
|
|
language interpreters are unable to recover from catastrophic errors in
|
|
compiled extension code. Furthermore, traditional C/C++ debuggers do
|
|
not provide a satisfactory degree of integration with interpreted
|
|
languages. This paper describes an experimental system in which fatal
|
|
extension errors such as segmentation faults, bus errors, and failed
|
|
assertions are handled as scripting language exceptions. This system,
|
|
which has been implemented as a general purpose shared library,
|
|
requires no modifications to the target scripting language, introduces
|
|
no performance overhead, and simplifies the debugging of mixed
|
|
interpreted-compiled application software.
|
|
}
|
|
|
|
\section{Introduction}
|
|
|
|
Slightly more than ten years have passed since John Ousterhout
|
|
introduced the Tcl scripting language at the 1990 USENIX technical
|
|
conference \cite{ousterhout}. Since then, scripting languages have
|
|
been gaining in popularity as evidenced by the wide-spread use of
|
|
systems such as Tcl, Perl, Python, Guile, PHP, and Ruby
|
|
\cite{ousterhout,perl,python,guile,php,ruby}.
|
|
|
|
In part, the success of modern scripting languages is due to their
|
|
ability to be easily integrated with software written in compiled
|
|
languages such as C, C++, and Fortran. In addition, a wide variety of wrapper
|
|
generation tools can be used
|
|
to automatically produce bindings between existing code and a
|
|
variety of scripting language environments
|
|
\cite{swig,sip,pyfort,f2py,advperl,heidrich,vtk,gwrap,wrappy}. As a result, a large number of
|
|
programmers are using scripting languages to control
|
|
complex C/C++ programs or as a tool for re-engineering legacy
|
|
software. This approach is attractive because it allows programmers
|
|
to benefit from the flexibility and rapid development of
|
|
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. As a result, 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.
|
|
|
|
A consequence of this complexity is an increased degree of difficulty
|
|
associated with debugging programs that utilize multiple languages,
|
|
dynamically loadable modules, and a sophisticated runtime environment.
|
|
To address this problem, this paper describes an experimental system
|
|
known as WAD (Wrapped Application Debugger) in which an embedded error
|
|
recovery and debugging mechanism is added to common scripting
|
|
languages. This system converts catastrophic signals such as
|
|
segmentation faults and failed assertions to exceptions that can be
|
|
handled by the scripting language interpreter. In doing so, it
|
|
provides more seamless integration between error handling in
|
|
scripting language interpreters and compiled extensions.
|
|
|
|
\section{The Debugging Problem}
|
|
|
|
Normally, a programming error in a scripted application
|
|
results in an exception that describes the problem and the context in
|
|
which it occurred. For example, an error in a Python script might
|
|
produce a traceback similar to the following:
|
|
|
|
\begin{verbatim}
|
|
% python foo.py
|
|
Traceback (innermost last):
|
|
File "foo.py", line 11, in ?
|
|
foo()
|
|
File "foo.py", line 8, in foo
|
|
bar()
|
|
File "foo.py", line 5, in bar
|
|
spam()
|
|
File "foo.py", line 2, in spam
|
|
doh()
|
|
NameError: doh
|
|
\end{verbatim}
|
|
|
|
In this case, a programmer might be able to apply a fix simply based
|
|
on information in the traceback. Alternatively, if the problem is
|
|
more complicated, a script-level debugger can be used to provide more information. In contrast,
|
|
a failure in compiled extension code might produce the following result:
|
|
|
|
\begin{verbatim}
|
|
% python foo.py
|
|
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.'' Furthermore, 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. Unfortunately,
|
|
neither of these techniques are very attractive for obvious reasons.
|
|
|
|
Alternatively, a user could run the application under the control of a
|
|
traditional debugger such as gdb \cite{gdb}. Unfortunately, this also has
|
|
drawbacks. First, even though the debugger provides 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. Second,
|
|
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
|
|
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
|
|
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.
|
|
|
|
Even if a traditional debugger such as gdb were modified to
|
|
provide better integration with scripting languages, it is not clear
|
|
that this would be the most natural solution to the problem.
|
|
For one, the whole notion of having to run a separate debugging process to debug
|
|
extension code is unnatural when no such requirement exists for
|
|
a script. Furthermore, even if such a debugger existed, an inexperienced user may not
|
|
have the expertise or inclination to use it. Finally,
|
|
obscure fatal errors may occur long after an application has been deployed.
|
|
Unless the debugger is distributed along with the application in some manner, it will be
|
|
extraordinary difficult to obtain useful diagnostics when such errors occur.
|
|
|
|
\begin{figure*}[t]
|
|
{\small
|
|
\begin{verbatim}
|
|
% python foo.py
|
|
Traceback (most recent call last):
|
|
File "<stdin>", line 1, in ?
|
|
File "foo.py", line 16, in ?
|
|
foo()
|
|
File "foo.py", line 13, in foo
|
|
bar()
|
|
File "foo.py", line 10, in bar
|
|
spam()
|
|
File "foo.py", line 7, in spam
|
|
doh.doh(a,b,c)
|
|
|
|
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
|
|
|
|
/u0/beazley/Projects/WAD/Python/foo.c, line 28
|
|
|
|
int doh(int a, int b, int *c) {
|
|
=> *c = a + b;
|
|
return *c;
|
|
}
|
|
\end{verbatim}
|
|
}
|
|
\caption{Cross language traceback generated for a segmentation fault in a Python extension}
|
|
\end{figure*}
|
|
|
|
The easiest solution to the debugging problem is
|
|
to simply add as much error checking as possible. 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 improbable
|
|
that a programmer will be able to foresee every conceivable error.
|
|
Second, 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 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.
|
|
|
|
\section{Embedded Error Recovery}
|
|
|
|
Rather than modifying an existing debugger to support scripting
|
|
languages, an alternative approach is to add a more powerful error
|
|
handling and recovery mechanism to the scripting language interpreter.
|
|
This approach has been implemented in the form of an
|
|
experimental system known as WAD. WAD
|
|
is packaged as dynamically loadable shared library that can either be
|
|
loaded as a scripting language extension or linked to existing
|
|
extension modules as a library. The core of the system is generic and
|
|
requires no modifications to the scripting interpreter or existing
|
|
extension modules. Furthermore, the system does not introduce a performance penalty as it
|
|
does not rely upon program instrumentation or tracing.
|
|
|
|
WAD works by converting fatal signals such as SIGSEGV,
|
|
SIGBUS, SIGFPE, and SIGABRT into scripting language exceptions that contain
|
|
debugging information collected from the call-stack of compiled
|
|
extension code. By handling errors in this manner, the scripting
|
|
language interpreter is able to produce a cross-language stack trace that
|
|
contains information from both the script code and extension code as
|
|
shown for Python and Tcl/Tk in Figures 1 and 2. In this case, the user
|
|
is given a very clear idea of what has happened without having
|
|
to launch a separate debugger.
|
|
|
|
The advantage to this approach is that it provides
|
|
more seamless integration between error handling
|
|
in scripts and error handling in extensions. In addition, it eliminates
|
|
the most common debugging step that a developer is likely to perform
|
|
in the event of a fatal error--running a separate debugger on a core
|
|
file and typing 'where' to get a stack trace. Finally, this allows
|
|
end-users to provide extension writers with useful debugging
|
|
information since they can supply a stack trace as opposed to a vague
|
|
complaint that the program ``crashed.''
|
|
|
|
\begin{figure*}[t]
|
|
\begin{picture}(400,250)(0,0)
|
|
\put(50,-110){\special{psfile = tcl.ps hscale = 60 vscale = 60}}
|
|
\end{picture}
|
|
\caption{Dialogue box with traceback information for a failed assertion in a Tcl/Tk extension}
|
|
\end{figure*}
|
|
|
|
\section{Scripting Language Internals}
|
|
|
|
In order to provide embedded error recovery, it is critical to understand how
|
|
scripting language interpreters interface with extension code. Despite the wide variety
|
|
of scripting languages, essentially every implementation uses a similar
|
|
technique for accessing foreign code.
|
|
|
|
The most widely used extension mechanism is a foreign function
|
|
interface in which compiled procedures can be called from the scripting language
|
|
interpreter. This is accomplished by writing a collection of wrapper functions that conform
|
|
to a specified calling convention. The primary purpose of the wrappers are to
|
|
marshal arguments and return values between the two languages and to handle errors.
|
|
For example, in Tcl, every wrapper
|
|
function must conform to the following prototype:
|
|
|
|
\begin{verbatim}
|
|
int
|
|
wrap_foo(ClientData clientData,
|
|
Tcl_Interp *interp,
|
|
int objc,
|
|
Tcl_Obj *CONST objv[])
|
|
{
|
|
/* Convert arguments */
|
|
...
|
|
/* Call a function */
|
|
|
|
result = foo(args);
|
|
|
|
/* Set result */
|
|
...
|
|
if (success) {
|
|
return TCL_OK;
|
|
} else {
|
|
return TCL_ERROR;
|
|
}
|
|
}
|
|
\end{verbatim}
|
|
|
|
The other extension mechanism is an object/type interface that allows programmers to create new
|
|
kinds of fundamental types or attach special properties to objects in
|
|
the interpreter. This usually involves setting up tables of function
|
|
pointers that define various properties of an object. For example, if
|
|
you wanted to add complex numbers to an interpreter, you might fill in a special
|
|
data structure with pointers to various methods like this:
|
|
|
|
\begin{verbatim}
|
|
NumberMethods ComplexMethods {
|
|
complex_add,
|
|
complex_sub,
|
|
complex_mul,
|
|
complex_div,
|
|
...
|
|
};
|
|
\end{verbatim}
|
|
|
|
\noindent
|
|
Once registered with the interpreter, the methods in this structure
|
|
would be invoked by various interpreter operators such as $+$,
|
|
$-$, $*$, and $/$.
|
|
|
|
Most interpreters handle errors as a two-step process in which
|
|
detailed error information is first registered with the interpreter
|
|
and then a special error code is returned. For example, in Tcl, errors
|
|
are handled by setting error information in the interpreter and
|
|
returning a value of TCL\_ERROR. Similarly in Python, errors are
|
|
handled by raising an exception and returning NULL. In both cases,
|
|
this triggers the interpreter's error handler---possibly resulting in
|
|
a stack trace of the running script. In some cases, an interpreter
|
|
might handle errors using a form of the C {\tt longjmp} function.
|
|
For example, Perl provides a special function {\tt die} that jumps back
|
|
to the interpreter with a fatal error \cite{advperl}.
|
|
|
|
The precise implementation details of these mechanisms aren't so
|
|
important for our discussion. The critical point is that scripting
|
|
languages always access extension code though a well-defined interface
|
|
that precisely defines how arguments are to be passed, values are to be
|
|
returned, and errors are to be handled.
|
|
|
|
\section{Scripting Languages and Signals}
|
|
|
|
Under normal circumstances, errors in extension code are handled
|
|
through the error-handling API provided by the scripting language
|
|
interpreter. For example, if an invalid function parameter is passed,
|
|
a program can simply set an error message and return to the
|
|
interpreter. Similarly, automatic wrapper generators such as SWIG can produce
|
|
code to convert C++ exceptions and other C-related error handling
|
|
schemes to scripting language errors \cite{swigexcept}. On the other
|
|
hand, segmentation faults, failed assertions, and similar problems
|
|
produce signals that cause the interpreter to crash.
|
|
|
|
Most scripting languages provide limited support for Unix signal
|
|
handling \cite{stevens}. However, this support is not sufficiently advanced to
|
|
recover from fatal signals produced by extension code.
|
|
First, unlike signals generated for asynchronous events such as I/O,
|
|
execution can {\em not} be resumed at the point of a fatal signal.
|
|
Therefore, even if such a signal could be caught and handled by a script,
|
|
there isn't much that it can do except to print a diagnostic
|
|
message and abort before the signal handler returns. Second,
|
|
some interpreters block signal delivery while executing
|
|
extension code--opting to handle signals at a time when it is more convenient.
|
|
In this case, a signal such as SIGSEGV would simply cause the whole application
|
|
to freeze since there is no way for execution to continue to a point where
|
|
the signal could be delivered. Because of these issues, scripting languages
|
|
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.
|
|
|
|
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.
|
|
|
|
\item The virtual memory map of the process is obtained from /proc
|
|
and used to associate virtual memory addresses with executable files,
|
|
shared libraries, and dynamically loaded extension modules \cite{proc}.
|
|
|
|
\item The call stack is unwound to collect traceback information.
|
|
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}.
|
|
\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
|
|
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 If a suitable return location is found, the CPU context is modified in
|
|
a manner that makes the signal handler return to the interpreter
|
|
with an error. This return process is assisted by a small
|
|
trampoline function (partially written in assembly language) that arranges a proper
|
|
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.
|
|
|
|
\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(55,392){\line(-1,0){25}}
|
|
\put(30,392){\line(0,-1){80}}
|
|
\put(30,312){\line(1,0){95}}
|
|
\put(125,312){\vector(0,-1){10}}
|
|
\put(175,302){\line(0,1){10}}
|
|
\put(175,312){\line(1,0){95}}
|
|
\put(270,312){\line(0,1){65}}
|
|
\put(270,377){\vector(-1,0){30}}
|
|
|
|
\put(50,285){\framebox(200,15)[c]{[Python internals]}}
|
|
\put(125,285){\vector(0,-1){10}}
|
|
\put(175,275){\vector(0,1){10}}
|
|
\put(50,260){\framebox(200,15)[c]{call\_builtin()}}
|
|
\put(125,260){\vector(0,-1){10}}
|
|
%\put(175,250){\vector(0,1){10}}
|
|
\put(50,235){\framebox(200,15)[c]{wrap\_foo()}}
|
|
\put(125,235){\vector(0,-1){10}}
|
|
\put(50,210){\framebox(200,15)[c]{foo()}}
|
|
\put(125,210){\vector(0,-1){10}}
|
|
\put(50,185){\framebox(200,15)[c]{doh()}}
|
|
\put(125,185){\vector(0,-1){20}}
|
|
\put(110,148){SIGSEGV}
|
|
\put(160,152){\vector(1,0){100}}
|
|
\put(260,70){\framebox(200,100){}}
|
|
\put(310,155){WAD signal handler}
|
|
\put(265,140){1. Unwind C stack}
|
|
\put(265,125){2. Gather symbols and debugging info}
|
|
\put(265,110){3. Find safe return location}
|
|
\put(265,95){4. Raise Python exception}
|
|
\put(265,80){5. Modify CPU context and return}
|
|
|
|
\put(260,185){\framebox(200,15)[c]{return assist}}
|
|
\put(365,174){Return from signal}
|
|
\put(360,170){\vector(0,1){15}}
|
|
\put(360,200){\line(0,1){65}}
|
|
|
|
%\put(360,70){\line(0,-1){10}}
|
|
%\put(360,60){\line(1,0){110}}
|
|
%\put(470,60){\line(0,1){130}}
|
|
%\put(470,190){\vector(-1,0){10}}
|
|
|
|
\put(360,265){\vector(-1,0){105}}
|
|
\put(255,250){NULL}
|
|
\put(255,270){Return to interpreter}
|
|
|
|
\end{picture}
|
|
|
|
\caption{Control Flow of the Error Recovery Mechanism for Python}
|
|
\label{wad}
|
|
\end{figure*}
|
|
|
|
\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
|
|
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
|
|
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.
|
|
|
|
|
|
\begin{table}[t]
|
|
\begin{center}
|
|
\begin{tabular}{ll}
|
|
Python symbol & Return value \\ \hline
|
|
call\_builtin & NULL \\
|
|
PyObject\_Print & -1 \\
|
|
PyObject\_CallFunction & NULL \\
|
|
PyObject\_CallMethod & NULL \\
|
|
PyObject\_CallObject & NULL \\
|
|
PyObject\_Cmp & -1 \\
|
|
PyObject\_DelAttrString & -1 \\
|
|
PyObject\_DelItem & -1 \\
|
|
PyObject\_GetAttrString & NULL \\
|
|
\end{tabular}
|
|
\end{center}
|
|
\label{returnpoints}
|
|
\caption{A partial list of symbolic return locations in the Python interpreter}
|
|
\end{table}
|
|
|
|
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.
|
|
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
|
|
use it to set an appropriate error code before returning to the interpreter.
|
|
Currently, this is managed by allowing the signal handler to steal
|
|
arguments from the caller using positional information.
|
|
For example, to grab the {\tt Tcl\_Interp *} object from a Tcl wrapper function,
|
|
code similar to the following is written:
|
|
|
|
\begin{verbatim}
|
|
Tcl_Interp *interp;
|
|
int err;
|
|
|
|
interp = (Tcl_Interp *) wad_steal_outarg(
|
|
stack,
|
|
"TclExecuteByteCode",
|
|
1,
|
|
&err);
|
|
if (!err) {
|
|
Tcl_SetResult(interp,errtype,TCL_STATIC);
|
|
Tcl_AddErrorInfo(interp,errdetails);
|
|
}
|
|
\end{verbatim}
|
|
|
|
In this case, the 2nd argument passed to a wrapper function
|
|
is stolen and used to generate an error. Also, the name {\tt TclExecuteByteCode}
|
|
refers to the calling function, not the wrapper function itself.
|
|
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.
|
|
|
|
\section{Register Management}
|
|
|
|
A final issue concerning the return mechanism has to do with the
|
|
precise behavior of the non-local return to the interpreter. Roughly
|
|
speaking, this emulates the behavior of the C {\tt longjmp}
|
|
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.
|
|
|
|
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 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. One approach is to simply ignore the problem
|
|
altogether and return to the interpreter with the registers in an
|
|
essentially random state. Surprisingly, this approach actually seems to work (although a considerable degree of
|
|
caution might be in order).
|
|
This is because the return of an error code tends to trigger
|
|
a cascade of procedure returns within the implementation of the interpreter.
|
|
As a result, the values of the registers are simply discarded and
|
|
overwritten with restored values as the interpreter unwinds itself and prepares to handle an
|
|
exception. A better solution to this problem is to modify the recovery mechanism to discover and
|
|
restore saved registers from the stack. Unfortunately, there is
|
|
no standardized way to know exactly where the registers might have been saved.
|
|
Therefore, a heuristic scheme that examines the machine code for each procedure would
|
|
have to be used to try and identify stack locations. This approach is used by gdb
|
|
and other debuggers when they allow users to inspect register values
|
|
within arbitrary stack frames \cite{gdb}. However, this technique has
|
|
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
|
|
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
|
|
wrapper code generators, it is not a preferred solution and is
|
|
not discussed further.
|
|
|
|
\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.
|
|
|
|
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
|
|
libraries tend to be quite large and are oriented more towards
|
|
stand-alone tools such as debuggers, linkers, and loaders. Second,
|
|
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
|
|
heavyweight solution.
|
|
|
|
\section{Discussion}
|
|
|
|
The primary goal of embedded error recovery is to provide an
|
|
alternative approach for debugging scripting language extensions.
|
|
Although this approach has many benefits, there are a number
|
|
drawbacks and issues that must be discussed.
|
|
|
|
First, like the C {\tt longjmp} function, the error recovery mechanism
|
|
does not cleanly unwind the call stack. For C++, this means that
|
|
objects allocated on stack will not be finalized (destructors will not
|
|
be invoked) and that memory allocated on the heap may be
|
|
leaked. Similarly, this could result in open files, sockets, and other
|
|
system resources. Furthermore, in a multi-threaded environment,
|
|
deadlock may occur if a procedure holds a lock when an error occurs.
|
|
|
|
Second, the use of signals may interact adversely with both scripting
|
|
language signal handling and signal handling in thread libraries.
|
|
Since scripting languages ordinarily do not catch signals such as
|
|
SIGSEGV, SIGBUS, and SIGABRT, the use of WAD is unlikely to conflict
|
|
with any existing signal handling. However, this does not prevent a
|
|
module from overriding the error recovery mechanism with its own
|
|
signal handler. Threads present a different sort of signal handling problem
|
|
due to the fact that thread libraries tend to override default signal handling \cite{thread}.
|
|
In this case, the thread library directs fatal signals to the thread in which the problem occurred.
|
|
However, first-hand experience has shown that certain implementations
|
|
of user threads do not reliably pass signal context information nor do
|
|
they universally support advanced signal operations such as {\tt
|
|
sigaltstack}. Because of this, the WAD recovery mechanism may not be
|
|
compatible with a crippled implementation of user threads on certain
|
|
platforms. To further complicate matters, the recovery process itself is
|
|
not thread-safe (i.e., it is not possible to concurrently handle fatal errors
|
|
occurring different threads).
|
|
|
|
Third, certain types of errors may result in an unrecoverable crash.
|
|
For example, if an application overwrites the heap, it may destroy
|
|
critical data structures within the interpreter.
|
|
Similarly,
|
|
destruction of the call stack (via buffer overflow) makes it
|
|
impossible for the recovery mechanism to create a stack-trace and
|
|
return to the interpreter. Although it might be possible to add a heuristic scheme for
|
|
recovering a partial stack trace such as backward stack tracing, no such feature has been implemented
|
|
\cite{debug}. Finally, memory management problems such as
|
|
double-freeing of heap allocated memory can cause a system to fail in
|
|
a way that bears little resemblance to the actual source of the
|
|
problem.
|
|
|
|
Finally, there are a number of issues that pertain
|
|
to the interaction of the recovery mechanism with the interpreter.
|
|
First, the recovery scheme is unable to return to procedures
|
|
that might invoke wrapper functions with conflicting return codes.
|
|
This problem manifests itself when the interpreter's virtual
|
|
machine is built around a large {\tt switch} statement from which different
|
|
types of wrapper functions are called. For example, in Python, certain
|
|
internal procedures call a mix of functions where both NULL and -1 are
|
|
returned to indicate errors (depending on the function). In this case, there
|
|
is no way for WAD to easily determine which return value to use. Second,
|
|
the recovery process is extremely inefficient. This is because the
|
|
data collection process relies heavily upon {\tt mmap}, file I/O, and linear search
|
|
algorithms for finding symbols and debugging information. Therefore, it would
|
|
probably not be suitable as a general purpose exception handling mechanism.
|
|
Finally, even when an error is successfully returned to the interpreter
|
|
and presented to the user, it may not be possible to resume execution of
|
|
the application (e.g., even though the interpreter is operational, the extension
|
|
module may be corrupted in some manner).
|
|
|
|
Despite these limitations, embedded error recovery is applicable to a
|
|
wide range of extension-related errors. This is because errors such as
|
|
failed assertions, bus errors, and floating point exceptions rarely
|
|
result in a situation where the recovery process would be unable to run or the
|
|
interpreter would crash. Furthermore, more serious errors such as segmentation faults are more
|
|
likely to caused by an uninitialized pointer than a blatant
|
|
destruction of the heap or stack.
|
|
|
|
\section{Related Work}
|
|
|
|
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
|
|
instance, IEEE Transactions on Software Engineering recently devoted
|
|
two entire issues to current trends in exception handling
|
|
\cite{except1,except2}. Unfortunately, very little of this work seems
|
|
to be directly related to mixed compiled-interpreted exception
|
|
handling, recovery from fatal signals, and problems pertaining to
|
|
mixed-language debugging.
|
|
|
|
Perhaps the most directly relevant work is that of advanced programming
|
|
environments for Common Lisp \cite{lisp}. Not only does CL have a foreign function interface,
|
|
debuggers such as gdb have previously been modified to walk the Lisp stack
|
|
\cite{ffi,wcl}. Furthermore, certain Lisp development environments have
|
|
provided a high degree of integration between compiled code and
|
|
the Lisp interpreter\footnote{Note to program committee: I
|
|
have been unable to find a suitable reference describing this capability. However,
|
|
discussions with Richard Gabriel and other people in the Lisp community seem to indicate that
|
|
such work has been done. Please advise.}
|
|
|
|
In certain cases, a scripting language module has been used to provide
|
|
partial information for fatal signals. For example, the Perl {\tt
|
|
sigtrap} module can be used to produce a Perl stack trace when a
|
|
problem occurs \cite{perl}. Unfortunately, this module does not
|
|
provide any information from the C stack. Similarly, advanced software development
|
|
environments such as Microsoft's Visual Studio can automatically launch a C/C++
|
|
debugger when an error occurs. Unfortunately, this doesn't provide any information
|
|
about the script that was running.
|
|
|
|
In the area of programming languages, a number of efforts have been made to
|
|
map signals to exceptions in the form of asynchronous exception handling
|
|
\cite{buhr,ml,haskell}. Unfortunately, this work tends to
|
|
concentrate on the problem of handling asynchronous signals related to I/O as opposed
|
|
to synchronously generated signals caused by software faults.
|
|
|
|
With respect to debugging, little work appears to have been done in the area of
|
|
mixed compiled-interpreted debugging. Although modern debuggers
|
|
certainly try to provide advanced capabilities for debugging within a
|
|
single language, they tend to ignore the boundary between languages.
|
|
As previously mentioned, debuggers have occasionally been modified to
|
|
support other languages such as Common Lisp \cite{wcl}. However, no such work appears
|
|
to have been done in the context of modern scripting languages. One system of possible interest
|
|
in the context of mixed compiled-interpreted debugging is the R$^{n}$
|
|
system developed at Rice University in the mid-1980's \cite{carle}. This
|
|
system, primarily developed for scientific computing, allowed control
|
|
to transparently pass between compiled code and an interpreter.
|
|
Furthermore, the system allowed dynamic patching of an executable in
|
|
which compiled procedures could be replaced by an interpreted
|
|
replacement. Although this system does not directly pertain to the problem of
|
|
debugging of scripting language extensions, it is one of the few
|
|
examples of a system in which compiled and interpreted code have been
|
|
tightly integrated within a debugger.
|
|
|
|
\section{Future Directions}
|
|
|
|
As of this writing, WAD is only an experimental prototype. Because of
|
|
this, there are certainly a wide variety of incremental improvements
|
|
that could be made to support additional platforms and scripting
|
|
languages. In addition, there are a variety of improvements that could be made
|
|
to provide better integration with threads and C++.
|
|
|
|
A more interesting extension of this work would be to expose a broader
|
|
range of debugging capabilities to the scripting interpreter. For example,
|
|
rather than simply raising an exception with limited diagnostic
|
|
information, the recovery mechanism might be able to provide the
|
|
interpreter with a detailed snapshot of the entire call stack
|
|
including symbolic debugging information. Using this information, it
|
|
might be possible to implement an interactive post-mortem debugger
|
|
that allows a programmer to inspect the values of local
|
|
variables and other aspects of the application without leaving the
|
|
interpreter. Alternatively, it may be possible to integrate this information
|
|
into an existing script-level debugger.
|
|
|
|
\section{Conclusions and Availability}
|
|
|
|
This paper has presented a mechanism by which fatal errors such as
|
|
segmentation faults and failed assertions can be handled as scripting
|
|
language exceptions. This approach, which relies upon advanced
|
|
features of Unix signal handling, allows fatal signals to be caught
|
|
and transformed into errors from which interpreters can produce an
|
|
informative cross-language stack trace. In doing so, it provides more
|
|
seamless integration between scripting languages and compiled
|
|
extensions. Furthermore, this has the potential to greatly simplify the
|
|
frustrating task of debugging complicated mixed scripted-compiled
|
|
software.
|
|
|
|
The prototype implementation of this system is available at :
|
|
|
|
\begin{center}
|
|
{\tt http://systems.cs.uchicago.edu/wad}.
|
|
\end{center}
|
|
|
|
\noindent
|
|
Currently, WAD supports Python,
|
|
Tcl, and Perl on SPARC Solaris and i386-Linux systems. Work to
|
|
support additional scripting languages and platforms is ongoing.
|
|
|
|
\section{Acknowledgments}
|
|
|
|
Richard Gabriel and Harlan Sexton provided interesting insights concerning similar capabilities
|
|
in Common Lisp.
|
|
|
|
\begin{thebibliography}{99}
|
|
|
|
|
|
\bibitem{ousterhout} J. K. Ousterhout, {\em Tcl: An Embedable Command Language},
|
|
Proceedings of the USENIX Association Winter Conference, 1990.
|
|
|
|
\bibitem{ouster1} J. K. Ousterhout, {\em Scripting: Higher-Level Programming for the 21st Century},
|
|
IEEE Computer, Vol 31, No. 3, p. 23-30, 1998.
|
|
|
|
\bibitem{perl} L. Wall, T. Christiansen, and R. Schwartz, {\em Programming Perl}, 2nd. Ed.
|
|
O'Reilly \& Associates, 1996.
|
|
|
|
\bibitem{python} M. Lutz, {\em Programming Python}, O'Reilly \& Associates, 1996.
|
|
|
|
\bibitem{guile} Thomas Lord, {\em An Anatomy of Guile, The Interface to
|
|
Tcl/Tk}, USENIX 3rd Annual Tcl/Tk Workshop 1995.
|
|
|
|
\bibitem{php} T. Ratschiller and T. Gerken, {\em Web Application Development with PHP 4.0},
|
|
New Riders, 2000.
|
|
|
|
\bibitem{ruby} D. Thomas, A. Hunt, {\em Programming Ruby}, Addison-Wesley, 2001.
|
|
|
|
\bibitem{swig} D.M. Beazley, {\em SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++}, Proceedings of the 4th USENIX Tcl/Tk Workshop, p. 129-139, July 1996.
|
|
|
|
\bibitem{sip} P. Thompson, {\em SIP},\\
|
|
{\tt http://www.thekompany.com/projects/pykde}.
|
|
|
|
\bibitem{pyfort} P.~F.~Dubois, {\em Climate Data Analysis Software}, 8th International Python Conference,
|
|
Arlington, VA., 2000.
|
|
|
|
\bibitem{f2py} P. Peterson, J. Martins, and J. Alonso,
|
|
{\em Fortran to Python Interface Generator with an application to Aerospace
|
|
Engineering}, 9th International Python Conference, submitted, 2000.
|
|
|
|
\bibitem{advperl} S. Srinivasan, {\em Advanced Perl Programming}, O'Reilly \& Associates, 1997.
|
|
|
|
\bibitem{heidrich} Wolfgang Heidrich and Philipp Slusallek, {\em Automatic Generation of Tcl Bindings for C and C++ Libraries.},
|
|
USENIX 3rd Tcl/Tk Workshop, 1995.
|
|
|
|
\bibitem{vtk} K. Martin, {\em Automated Wrapping of a C++ Class Library into Tcl},
|
|
USENIX 4th Tcl/Tk Workshop, p. 141-148, 1996.
|
|
|
|
\bibitem{gwrap} C. Lee, {\em G-Wrap: A tool for exporting C libraries into Scheme Interpreters},\\
|
|
{\tt http://www.cs.cmu.edu/\~{ }chrislee/
|
|
Software/g-wrap}.
|
|
|
|
\bibitem{wrappy} G. Couch, C. Huang, and T. Ferrin, {\em Wrappy :A Python Wrapper
|
|
Generator for C++ Classes}, O'Reilly Open Source Software Convention, 1999.
|
|
|
|
\bibitem{gdb} R. Stallman and R. Pesch, {\em Using GDB: A Guide to the GNU Source-Level Debugger}.
|
|
Free Software Foundation and Cygnus Support, Cambridge, MA, 1991.
|
|
|
|
\bibitem{swigexcept} D.M. Beazley and P.S. Lomdahl, {\em Feeding a
|
|
Large-scale Physics Application to Python}, 6th International Python
|
|
Conference, co-sponsored by USENIX, p. 21-28, 1997.
|
|
|
|
\bibitem{stevens} W. Richard Stevens, {\em UNIX Network Programming: Interprocess Communication, Volume 2}. PTR
|
|
Prentice-Hall, 1998.
|
|
|
|
\bibitem{proc} R. Faulkner and R. Gomes, {\em The Process File System and Process Model in UNIX System V}, USENIX Conference Proceedings,
|
|
January 1991.
|
|
|
|
\bibitem{elf} J.~R.~Levine, {\em Linkers \& Loaders.} Morgan Kaufmann Publishers, 2000.
|
|
|
|
\bibitem{stabs} Free Software Foundation, {\em The "stabs" debugging format}. GNU info document.
|
|
|
|
\bibitem{prag} M.L. Scott. {\em Programming Language Pragmatics}, Morgan Kaufmann Publishers, 2000.
|
|
|
|
\bibitem{sparc} D. Weaver and T. Germond, {\em SPARC Architecture Manual Version 9},
|
|
Prentice-Hall, 1993.
|
|
|
|
\bibitem{bfd} S. Chamberlain. {\em libbfd: The Binary File Descriptor Library}. Cygnus Support, bfd version 3.0 edition, April 1991.
|
|
|
|
\bibitem{thread} F. Mueller, {\em A Library Implementation of POSIX Threads Under Unix},
|
|
USENIX Winter Technical Conference, San Diego, CA., p. 29-42, 1993.
|
|
|
|
\bibitem{debug} J. B. Rosenberg, {\em How Debuggers Work: Algorithms, Data Structures, and
|
|
Architecture}, John Wiley \& Sons, 1996.
|
|
|
|
\bibitem{except1} D.E. Perry, A. Romanovsky, and A. Tripathi, {\em
|
|
Current Trends in Exception Handling-Part I},
|
|
IEEE Transactions on Software Engineering, Vol 26, No. 9, p. 817-819, 2000.
|
|
|
|
\bibitem{except2} D.E. Perry, A. Romanovsky, and A. Tripathi, {\em
|
|
Current Trends in Exception Handling-Part II},
|
|
IEEE Transactions on Software Engineering, Vol 26, No. 10, p. 921-922, 2000.
|
|
|
|
|
|
\bibitem{lisp} G.L. Steele Jr., {\em Common Lisp: The Language, Second Edition}, Digital Press,
|
|
Bedford, MA. 1990.
|
|
|
|
\bibitem{ffi} H. Sexton, {\em Foreign Functions and Common Lisp}, in Lisp Pointers, Vol 1, No. 5, 1988.
|
|
|
|
\bibitem{wcl} W. Henessey, {\em WCL: Delivering Efficient Common Lisp Applications Under Unix},
|
|
ACM Conference on Lisp and Functional Languages, p. 260-269, 1992.
|
|
|
|
\bibitem{buhr} P.A. Buhr and W.Y.R. Mok, {\em Advanced Exception Handling Mechanisms}, IEEE Transactions on Software Engineering,
|
|
Vol. 26, No. 9, p. 820-836, 2000.
|
|
|
|
\bibitem{haskell} S. Marlow, S. P. Jones, and A. Moran. {\em
|
|
Asynchronous Exceptions in Haskell.} In 4th International Workshop on
|
|
High-Level Concurrent Languages, September 2000.
|
|
|
|
\bibitem{ml} J. H. Reppy, {\em Asynchronous Signals in Standard ML}. Technical Report TR90-1144,
|
|
Cornell University, Computer Science Department, 1990.
|
|
|
|
\bibitem{carle} A. Carle, D. Cooper, R. Hood, K. Kennedy, L. Torczon, S. Warren,
|
|
{\em A Practical Environment for Scientific Programming.}
|
|
IEEE Computer, Vol 20, No. 11, p. 75-89, 1987.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\end{thebibliography}
|
|
|
|
\end{document}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|