DWARF is a full-featured and complex debugging information
format [7]. Our example program, dwarfscheme
, is
an interface that allows the user to browse DWARF information in an
object file by providing stubs to the
libdwarf
[8] library.
Figure 2 shows a sample dwarfscheme
dialogue.
Figure 2: dwarfscheme dialogue
In this example the user invokes dwarfscheme
, opens the file
"a.out"
for DWARF reading, defines a function for printing out
debugging information entries (DIEs), and prints out the first DIE.
This example shows how dwarfscheme
would be used by an end
user. Next, we will examine the way that the dwarfscheme
executable was created using libscheme
and the libdwarf
libraries.
The program dwarfscheme
is an executable that was produced by
linking libscheme
with a set of DWARF manipulating primitives,
a read-eval-print loop that initializes the primitives, and the
libdwarf
library that is provided as a system library. The
main routine for dwarfscheme
appears in
figure 3.
Figure 3: dwarfscheme read-eval-print loop
This main routine is a boiler-plate routine that is used when the
application writer wants to make the application a Scheme
read-eval-print loop. The thing that differentiates the main routines
in different applications is the initializations that are done on the
environment. In this case, we create a basic environment containing
the standard libscheme
bindings, and then add the DWARF
specific bindings to it by calling init_dwarf()
. The rest of
the routine takes care of the business of establishing an error
handler, printing out a prompt, reading an expression, evaluating the
expression, and printing out the result.
The application writer can also embed libscheme
in an
application that is not structured as a read-eval-print loop. For
example, at program startup a windowing application might initialize a
libscheme
environment, read and evaluate Scheme expressions
representing configuration information from a user configuration file,
and then enter its event loop. The user might bring up a dialog box
in which she can evaluate Scheme expressions to further configure and
query the system's state.
The major part of the DWARF initialization routine,
init_dwarf()
appears in figure 4. It consists of
calls to scheme_make_type()
to establish new data types, and
then several calls to scheme_add_global()
to add new global
bindings to the environment provided as an argument. Each call to
scheme_add_global()
provides the Scheme name for the global,
the initial value for the variable (in this case a new primitive that
points to its C implementation), and an environment to which the
global should be added. All routines and variables that are part of
the dwarfscheme
interface begin with a dw
prefix, while
routines and variables from the system-supplied libdwarf
library begin with a dwarf
prefix.
Figure 4: The DWARF primitive initialization routine
This practice of calling an initialization routine with the
environment for each logical piece of code is only a convention, but
is a helpful way of organizing libscheme
code. The
libscheme
library itself is organized this way. Each file
contains an initialization function that establishes that file's
primitives.
A sample primitive is shown in figure 5. Each
libscheme
primitive accepts an argument count and a vector of
evaluated arguments. Each primitive procedure is responsible for
checking the number and type of its arguments. All Scheme objects are
represented by the C type Scheme_Object
(see
section 4.1). The types Dwarf_Debug
and
Dwarf_Die
are foreign to libscheme
and are provided by
the libdwarf
library.
Figure 5: A dwarfscheme primitive
The SCHEME_ASSERT()
macro asserts that a particular form
evaluates to true, and signals an error otherwise. The
dw_first_die()
routine first checks for the correct number
of arguments, then it checks that the first argument is an object with
type dw_debug_type
. Next, it extracts the pointer value
representing the DWARF information in the file from the first
argument, a Scheme_Object
. It then calls a libdwarf
function, dwarf_nextdie()
and returns an appropriate value-a
new dw_die_type
object if there is another DIE, the Scheme
false value otherwise. The dw_make_die()
routine accepts a
Dwarf_Die
as an argument and returns a libscheme
object
of type dw_die_type
that contains a pointer to the
Dwarf_Die
structure.
Now that we have a feel for the way that libscheme
is extended,
we will take a closer look at the design of libscheme
itself.