my experience with ghci debugger extensions
Peter Hercek
phercek at gmail.com
Thu Feb 5 04:05:39 EST 2009
Hi users of ghci debugger,
This post is going to be a bit longer. Here are few cookies to motivate
you to go on:
* you will probably like it more than printf debugging for a lot of cases
* it can provide a way of implementing Claus Reinke's idea of
breakpoints with a list of identifiers which should be available at
breakpoint location and doing it without recompilation; here is the link
to Claus' message:
http://permalink.gmane.org/gmane.comp.lang.haskell.glasgow.user/15900
* it gives some idea to ghc team about importance of ghci debugger
related tickets (and whether to implement them just the way they were
proposed)
A note to ghc developers: Stepping program in ghci debugger sometimes
purges top level bindings and sometimes not. Not sure this is a bug or
feature (whether there is some logic in it). I do not have a simple
example when it purges the bindings. But I did not really look for one.
I would probably post later, but some ghci bugs and missing features are
badly limiting my progress. There is not much more I can think of to
investigate. Maybe somebody will have better ideas how to solve the
obstacles I'm hitting. I'm also posting with hope that people will find
this interesting and ghc team will fix some of the critical bugs and
adds the most critical features, especially if somebody will not have
better debugging tips.
You can get my extensions here:
http://www.hck.sk/users/peter/pub/ghciext-0.1.tar.gz
The extensions are not in a single .ghci file now. The new .ghci file
needs to install a library. The reason is that without the library it is
not manageable any more.
And here are arch linux packaging instructions for my custom ghc (if you
are an arch linux user just download and run makepkg :) ):
http://www.hck.sk/users/peter/pub/ghc-custom-6.10.1.tar.gz
The custom ghc is just the same one as ghc 6.10.1 with two more patches:
t2740.patch and loglocal.patch. The first one fixes ticket 2740 and you
will get it in 6.10.2. The second one adds :loglocal command to ghci.
You can extract the patches from the tar file.
If you already read ghci scripting tutorial from Claus Reinke then you
will know how to customize ghciext (that is if you would feel like doing
so). The tutorial is here:
http://www.haskell.org/pipermail/haskell-cafe/2007-September/032260.html
Here is the list of custom commands in ghciext package:
:defs -- list user-defined commands
:. <file> -- source commands from <file>
:redir <var> <cmd>... -- execute <cmd> redirecting stdout to <var>
:grep <pat> <cmd>... -- filter lines matching <pat> from <cmd> output
:* <count> <cmd>... -- run <cmd> <count> times
:x <cmd>... -- run <cmd> with stdout suppressed
:bp <bpArgs> -- put breakpoint at location <bpArgs> (adds
hit count)
:inject <cc> <c> <sc> <b> -- at location <b> execute <c> if <cc>, and
stop if <sc>
:monitor ["<c>"] <vs> <b> -- show comma separated variables at location
<b> if <c>
:watch <cond> <bpArgs> -- break at location <bpArgs> when <cond> is True
:count (_|<N>) [<bpArgs>] -- count/query/stop execution at location <bpArgs>
:find <var> <cmd>... -- step with <cmd> until <var> is found
:findex <str> <cmd>... -- step with <cmd> until <str> is found
:next [lazy] -- step over; lazy version forces only the top
constructor
:xout -- redirect ghci stdout to /dev/null
:out -- redirect ghci stdout back to console
:defs, :., :redir, :grep, are the same as the commands in Claus'
tutorial. The only differences I recall now are:
* grep pattern can be in quotation marks (easier search for spaces)
* grep merges :browse output more nicely
* redir can accept :step, :steplocal etc; i.e. also the commands which
sometimes remove top level bindings
* the commands do not pollute top level bindings so much
The rest will describe my custom commands and now they relate to tickets
in ghci track. If you want to check the tickets mentioned here then the
most easy way is probably selecting them from this list:
http://hackage.haskell.org/trac/ghc/search?q=phercek&noquickjump=1&ticket=on
The initial big problem was how to limit the amount of information ghci
debugger spits at you when breakpoints with custom scripts are used.
This is also mentioned in forth point of the "unknown" section of ticket
#1377:
/We can disable a breakpoint with ":set stop N :continue", but this
still prints out the breakpoint info when we stop. Should we print the
info only if there were no commands?/
So I say: yes do it! Just disable any ghci output related to a
breakpoint when the breakpoint has a custom code attached. We can
disable the output ourselves, but then we disable all the output
(including the output of the debugged program). People who are debugging
console applications are busted (especially if the applications are
interactive). This is not an issue for me since my application is not
using console almost at all. I'm solving the problem by prefixing
commands like /:main/ and /:continue /with my command /:x/. This makes
output by default disabled and ghciExt enables it just before a
breakpoint code is run. If the breakpoint continues execution it
disables it again. If the debugged function finishes the output is
enabled by /:x/ itself. A small problem happens when you forget to use
/:x/ e.g. with your /:main/. Then you do not get a prompt when the
program finishes. It's time to notice it and use /:out/ to switch it on.
This is the only minor disadvantage for a gui application debugging.
/:xout /in not that useful (it is mostly used internally by /:x/). It
could be a hidden command (i.e. not registered in defs).
Now when we have ghci "muted" we can get to real goodies: /:monitor/,
/:watch/, and /:count/.
If you like printf debugging, then /:monitor/ can help you. It can
monitor only free variables in the selected expression (on which a
breakpoint is hit) but that was always enough for me during debug
sessions I had. Write a script file (lets say it is named *ghciInit*
(I'll also call it this way later on in this text)) which is like this
example:
/:monitor var1,var2 ModuleName 23
:monitor var3 ModuleName 40
:x :main mainArgument/
Then open ghci with your program and run *:. ghciInit*
And you will get nice log like this:
/(0): var1 = <value01>
var2 = <value02>
(1): var3 = <value03>
(0): var1 = <value11>
var2 = <value12>
(1): var3 = <value13>
... etc/
Moreover /:monitor/ allows condition in quotation marks to be specified
as the first argument. If you do it then the variable values will be
printed only when the condition is True. Of course the condition can
contain only free variables in the selected expression. Mostly it is not
a problem.
/:watch/ is a conditional breakpoint which stops when the specified
condition is True.
/:count/ has 3 forms:
/:count _ ModuleName 23/
This never breaks just counts number of times the execution reached
position ModuleName 23
/:count 5 ModuleNmae 23/
This breaks when we reach position ModuleName 23 fifth time
/:count 0/
This tells how many times breakpoint number 0 was hit (it can report
the number of hits for any breakpoint created with /:bp/, /:inject/,
/:monitor/, /:watch/, and /:count/).
The first form of /:count/ is interesting when you want to stop earlier
than something bad happens so that you can see why the bad think
happened. Put the first form of /:count/ at the start of the function
with the bug and then put a break in the function which is hit when the
bad think happens maybe with /:watch/. When you stop at the bug place,
check hit count at the start of the function. Add the hit count in your
/:count/ breakpoint in your *ghciInt* file restart and you can use
/:steplocal/ or /:loglocal/ to find out what went wrong.
/:loglocal/ is implemented directly in ghc source code. It does exactly
the same as /:steplocal/ but makes sure that the trace history does not
contain anything outside of the function we step with /:steplocal/. The
problem is that /:steplocal/ works like the code was traced while it is
executing. Mostly the result is that your trace history is loaded with
crap outside of the scope you are interested in. I'll return to
/:loglocal/ later again.
/:inject/ is there when you need something special (/:monitor/,
/:watch/, and /:count/ are implemented with something very like
/:inject/). E.g. when you want to do monitoring of a value but do not
want the associated breakpoint number printed.
/:find/ and /:findex/ are there primary to search trace history.
/:find var1 :back/
will find the variable var1 in your trace history by back stepping it
/:find var1 :step/
will single step forward till variable var1 is in the list of free
variables
/:findex BL/Logic.hs:23 :loglocal/
will fill in your trace history will all the local breakpoints till
location BL/Logic.hs:23 is hit. Having the trace history filled in with
the right stuff is useful for checking out why you got bad results later.
Now lets return to Claus' idea of breakpoints with a list of identifiers
which should be available at the breakpoint location. You can make sure
the identifiers are available with the first form of /:count/. It never
stops but it puts records to the trace history. So the trace history
will contain free variables at the locations where you put /:count/.
This will be even more useful when automatic search of trace history is
built in (see ticket #2737). So a weaker form of Claus' idea can be
implemented by carefully selecting what should bet to the trace history.
Why a weaker form only? Well, in some cases, the variable instances in
trace log may not be the expected ones (they may be from a different
lexical scope). Experience with my code indicates this should be rare.
/:next/ is an idea how to implement a kind of step over. That is if by
step over you mean something else than steplocal. The non-lazy form of
/:next/ forces _result and does a /:step/. The lazy form forces only the
top level constructor of _result before the step. Hey, I even had a case
when it worked just like I expected. But typically it does not work
because of bug #1531. _result is not correctly bound to the result of
selected expression in most of the practical cases. This bug is also
critical for all the forms of conditional breakpoints. It would be cool
if we could specify the condition based on _result or some part of it.
The implementation of ghciExt conditional breakpoints would need to be
extended to support conditions on _result (in particular the breakpoint
would need to be disabled during the condition execution) but that is
easy to do. Even more worrying thing about bug #1531 is that it has the
milestone set to _|_.
It is easy to add /:enable/ and /:disable/ to support enabling and
disabling breakpoints. I just did not need it yet. Here is how a GhciExt
breakpoint looks like:
*Main> :show breaks
[0] Main a.hs:4:2-8 ":cmd return$GhciExt.getStopCode 0 (True) "putStr
\"(0): \"\n:force x" "False""
*Main>
Just replace getStopCode with getDisabledStopCode and you have it
disabled. Return back to enable. Yeah, and implement getDisabledStopCode
which will just continue.
I added :/loglocal/ mostly to simulate how /:tracelocal/ in ticket #2737
would help. I was also trying how full tracing is helping. In both cases
the answer is: full tracing almost never helps. /:tracelocal/ from
ticket #2737 as proposed originally would rarely help. The problem is
that trace log gets overwhelmed with crap when we cannot control what
can be saved in it and what cannot be saved. My idea is that user should
be able to specify what can go in it and also what should not go in it.
Here is an alternative solution to the ones I proposed in tickets #2737
and #2946. I think this one would be best. The command to control the
tracing should look like:
-- should everything be traced?
:set trace (True|False)
-- scopes which should be traced (or should not be traced when ! is present)
:set trace ( (!)? scopeid )*
-- add/remove individual scopeids to/from the trace specification
:set trace (+|-) (!)? scopeid
where scopeid = ( conid . )* ( varid . )* varid
Notice how scopeid looks. It can have a sequence of varids at the end.
The reason is so that user can leave out a scope of a function which is
defined in a where clause. The scope specification is similar to the
proposal in ticket #3000. E.g. for this code:
fn s = 'a' : add s
where add = (++"z")
it could look like
:set trace Main.fn !Main.fn.add
meaning trace whole scope of *fn* but not the stuff in the scope of *add.
*Order should not be important, requests for not tracing should have
precedence before requests to trace.
The scopes which we want typically exclude are the ones which contain
loops. The loop content often fills in the trace log forcing the
interesting stuff out of it. It is better to investigate functions with
loops separately in nested context.
Notice that there is a bit difference between this proposal of
controlling trace content and the one in ticket #2737. #2737 assumes
usage of breakpoint arguments to specify a scope. The breakpoint
arguments give an ability to define scopes at finer level but there is
no option to define exclude scopes which I find important now.
The summary is: Trace log is as useful as much you can control what can
get in it. The :trace command looks to me like an error. It is better to
control it by allowing/disallowing scopes.
I also changed my opinion a bit about ticket #2945. :mergetrace would be
better than global trace history. Being able to investigate something
separately in a nested context is useful.
If I should order the ghci debugger related tickets then the order would
be like (more important first):
#1531 (_result can get bound to the wrong value in a breakpoint)
#2737 and #2946 (add :tracelocal to ghci debugger... and add command
:mergetrace...)
#3000 (:break command should recognize also nonexported top level
symbols...)
#2803 (bring full top level of a module in scope when a breakpoint is
hit in the ...)
#1377 (the task: "We should print breakpoint related info only if
breakpoint has no commands set") but people debugging interactive
console applications would like to have this one the very top; *IIRC*
this may be easy to do, looks like all the printing is done in one
function (something like afterCmd???); also #2950 looked like trivial to
do (like 15 mins without the compile time???)
And the last thing: my first time experiences hacking in the /:loglocal/
into ghc. I cannot tell much, I spend with it only one long Sunday
afternoon, but here are my two points:
* I needed to extend ghc interface. The type of function GHC.resume
changed from:
resume :: GhcMonad m => SingleStep -> m RunResult
to:
resume :: GhcMonad m => (SrcSpan->Bool) -> SingleStep -> m RunResult
... plus the corresponding implementation change. The added argument
is a filtering function to limit source spans which can recorded in the
trace history.
* It would be cool if ghci has its own dir in the souce tree where only
the ghci source files are. It would encourage people to hack it more
since it would be easier to maintain private patches and merging
upstream. It would be also easier to make sure one modifies only ghci
source code so that it works with other ghc releases.
Hopefully this helps somebody,
Peter.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/glasgow-haskell-users/attachments/20090205/123ca701/attachment-0001.htm
More information about the Glasgow-haskell-users
mailing list