Table of Contents
The Debugger B.I.O.
B.I.O. means “Believe In Oracles”, which is a pun regarding the underlying technique. This technique is described in the paper soon to be uploaded here.
Usage in KiCS
Getting the Sources
Up to now the sources are only available from the master branch of my git repository
~bbr/kics
. The debugger is in the alpha test phase only.
Installation
After following the instructions about installing KiCS, there is one more thing to do:
make bio
will create three binaries in your kics/bin
directory (and a few other files somewhere else):
prophecy
transforms flat curry files, adding side effects to retrieve oracle information during executionstricths
transforms the same flat curry files into strict haskell programs, consuming oracle informationbiotope
is a viewer for the I/O environment of debugged programs. Here, you can see the read/written files and the console output.- finally the
Prelude
is transformed usingstricths
andprophecy
such that you can directly start debugging.
After these three files have been built you are ready to go! (And don't worry, all other modules are transformed on demand.)
One final thing: You should defenitely include the …kics/bin path in your $PATH variable, which is useful anyway.
Starting the Debugger in the Interactive Environment
When starting kicsi
you have access to debugging in a straight forward way.
Just type :set +d
(or the long version :set ++debug
)
and the environment is in debugging mode.
The settings overview (you get by :set
) will look something like this:
options | |
---|---|
search mode: | interactive depth first |
timing: | off |
debug: | on |
evaluation mode: | interpreted (+e) |
verbosity level: | 1 |
recompilation: | only if older (-f) |
As long as the environment is in debug mode, all the expressions you type will be fed to the debugger.
And if you are finished debbugging you type :set -d
or :set –debug
.
Debugging in Step Mode
B.I.O. supports different modes of debugging. The simplest is a step debugger, showing the evaluation as if Curry was a strict language (which it isn't ).
The basic commands in step mode are:
s
to skip the sub computation evaluating the current expression<SPACE>
to step into the sub computation, i.e., do one step according to leftmost innermost (strict) evaluation.r
let's you see what the value of the given expression is. This is what really sets even this mode apart from standard step debuggers. with this information you can decide whether the sub computation might be interesting to look at or not.i
switches the debbugging mod to declarative debugging (see below).?
gives you ahelp menu such that yopu do not have to look everything up here.q
quits the debbugging session
Here is an example debugging session.
After typing :set +d
we want to find out about boolean expressions. We type
((True && False) || (or [False,False,False])
Note: Now that the debugger supports trusted functions, you won't see anything when repeating this debugging session unless you define your own (&&), (||) and or.
There is much compiling going on (maybe we could decrease the verbosity some time.) Without any notice, the expression is evaluated to retrieve the oracle and after some more compilation b.i.o. is started. It looks something like this:
– such a nice banner – but this wiki is not so good for ascii art
main
Here we type some spaces (exactly twice), making single steps. We get
main
True && False
True && False ~> False
or
We see that the result of (&& True False)
has been
fully evaluated (to False
). This is why we now
see the next sub expression. What is that or
thing there?
Better have a look at the Prelude
, maybe
soon in b.i.o. with Parisas' nice source code viewer.
By now we rather type another space.
or
or ~> foldr (||) False
apply (foldr (||) False) [False,False,False]
Funky, at least if you like higher order. How about typing
s
now?
apply (foldr (||) False) [False,False,False] ~> False
False || False
The whole (apply …)
has been reduced to False
,
is what the new line tells you. Thus, without further notice,
we are back to the main expression. Another space will reveal this.
False || False
False || False ~> False
main ~> False
end reached. press 'q' to abort or any other key to restart.
And that's it. You either do your session all other or you are satisfied.
There is more interesting features in b.i.o., though, in the
other debugging mode. You can turn it on at any time
by typing i
.
Debugging in Inspection Mode
Whenever you type 'i' during debugging you switch between “Step Mode” as described above and “Inspection Mode”. Inspection mode is an instance of the so called “Declarative Debugging”. This means that it presents to you the results of all occurring function applications. You have then the opportunity to type
- c to mark the given sub-computation as correct or
- w to mark the given sub-computation as wrong
The debugger will then proceed to ask you other sub-computations in the neighborhood. As if by accident, the order to ask questions is identical to the left-most innermost evaluation order.
After providing the debugger with some information about (in)correctness, it will come up with a message to locate the bug in your program. For example, we consider the following program:
delete :: a → [a] → [a]
delete _ [] = []
delete x (y:ys) | x==y = ys
| otherwise = x : delete y ys
The bug may be quite obvious, but we use b.i.o. anyway:
delete 'a' “baernd”
main
first, we press 'i' to switch to inspection mode:
main ~> “abaern”
This line means the original expression
was evaluated to “abaern”
.
This is definitely wrong in whatever
presentation, so we type 'w':
delete 'a' “baernd” ~> “abaern”
wrong again.
'a' == 'b' ~> False
correct.
otherwise ~> True
Now that is a tricky one … correct.
delete 'b' “aernd” ~> “baern”
wrong.
'b' == 'a' ~> False
correct.
otherwise ~> True
what, again?? … correct.
delete 'a' “ernd” ~> “aern”
wrong
'a' == 'e' ~> False
otherwise ~> True
yaaaawwwwn
delete 'e' “rnd” ~> “ern”
'e' == 'r' ~> False
otherwise ~> True
delete 'r' “nd” ~> “rn”
'r' == 'n' ~> False
otherwise ~> True
delete 'n' “d” ~> “n”
'n' == 'd' ~> False
otherwise ~> True
even bigger yawn.
delete 'd' [] ~> []
This is finally correct. Now we get the message:
found bug in rule:
lhs = delete 'n' “d”
rhs = “n”
Well, we might have found this bug much earlier ourselves and were bored to a coma by silly questions, but who said that debugging should be fun?
Moving back
In whatever mode, b.i.o. lets you take back the last command by hitting 'b' for back. This back history is comprehensive. If you want. you can go back all the way to the start even if you have gone through the inspector loop multiple times.
Debugging IO-Actions
The B.I.O. debugger features virtual I/O. As soon as the first I/O action with a visible effect is executed, the B.I.O.tope will open up. The B.I.O.tope is a tcl/tk gui that let's you see what has been done so far,e.g., what has been printed or which files have been read.
Try it out.
Options
The usage menu (type 'h' or '?') describes the possible options:
usage: | |
---|---|
r | inspect result |
i | toggle inspect mode |
b | back |
s | skip |
<SPACE> | step into |
v | toggle verbosity |
q | quit |
d | set max depth |
The commands (“inspect result” tc.) have already been explained above. Up to date there are two options influencing the appearance of b.i.o.: verbosity and max depth. Verbosity is more for the developers debugging-of-the-debugger interest. But “max depth” is often useful.
After hitting 'd' you can enter a number which is the depth up to which terms are printed in b.i.o. Without depth restriction, things can get really confusing. Just hitting <ENTER> instead of entering a number will remove any depth restriction.
Trusted Functions
A trusted function will not appear during debugging. All the functions in standard modules are trusted, for example.
For each module M you can add a file M.trust in the same directory. A .trust file contains the names of functions, possibly lead by a bang.
Let, for example, M contain functions f1, f2, f3. If you write in M.trust
!f1
f2
Then f1 is not trusted, while f2 is trusted.
The default for all functions is defined by the first entry in the trust file. If the first entry is lead by a bang then the default is that functions are trusted. If the first entry has no bang then the default is “not trusted”. In the above example f3 is, therefore, trusted (and the second line superfluous). The reasoning is that you'd rather like to write the exceptions to the file than to repeat the usual case all over. Along the same line, an empty .trust file simply means: trust the whole module.
And sometimes one of the arguments of a function is just not interesting. Imagine a dictionary carried around at all times.
You can omit such arguments in a trust file like this
!mysuperfun _ x _ _ y
now from the function “mysuperfun” you will only see the second and fifth argument. Actually, you will also see any remaining arguments if the function has more than five. Thus, you could also have written
!mysuperfun _ x _ _
for the same effect.
Don't worry about what to call “x” or “y”. Every string but “_” will be interpreted as “ I want to see this”. Thus, its still the same to write:
!mysuperfun _ showmethismostimportantargumentatallcalls _ _
During debugging, trusted arguments are shown as green underscores (so that you won't confuse it with an unneeded argument).
State of Implementation (in KiCS)
The debugger now works stably with an external C oracle. The current state is that it can correctly work with any pure module (i.e. moduls without external functions).
Fully covered moduls with external functions:
- Prelude
- Time
- System
- Directory
- Distribution
- Float
- Global (with restrictions) known issues:
- global states may not contain higher order values
- global states may not be initialized with unevaluated expressions
Modules to be implemented:
- Unsafe
- Meta
- IO (only partial)
- IOExts
- ReadShowTerm
- Socket
- Generic (there is a principal problem here)