a lightweight interactive debugger for haskell simon marlow josé iborra bernard pope andy gill

27
A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Upload: ashlynn-heath

Post on 01-Jan-2016

218 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

A Lightweight Interactive Debugger for Haskell

Simon MarlowJosé Iborra

Bernard PopeAndy Gill

Page 2: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

We don’t need no debugger!

• We haveA type

system!

QuickCheck and HUnit!

GHCi and Hugs!

NO BUGS!

Page 3: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

but…

• A debugger was still the #1 requested feature in the GHC survey (2005)

• New users often want a way to visualise execution to understand what’s happening

• When you missed out a test, diagnosing the failure is hard: deep bugs

• head []

Page 4: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

But we have debuggers!

• There exist debugging tools for Haskell, but for various reasons they are often too hard to use:– limited language support– require recompiling libraries– time/space overhead– etc. (see paper for comparison)

Page 5: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Goals

• We want a debugger that:– Is always available– works with everything that GHC can compile– doesn’t add significant overheads

• We might sacrifice some functionality to achieve accessibility. Aim for a high power-to-weight ratio.

• This won’t necessarily supercede other Haskell debuggers: the goal is to provide some debugging functionality that everyone can use easily.

• Secondary goals:– can be re-used in IDE frontends

Page 6: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Real programmers want to set breakpoints

• Our debugger is “online”: you debug the running program, as opposed to “post-mortem” debugging (eg. Hat).

• The basic model is “execute, stop, inspect, continue”.

• Advantages:– we can let you evaluate arbitrary expressions involving

runtime values (e.g. look up a key in a finite map).– no limit on the runtime of the program

• Disadvantages:– we can’t easily go back in time– evaluation order is exposed (possibly an advantage?)

• Our debugger is integrated into GHCi

Page 7: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

GHCi, version 6.8.0.20070919: http://www.haskell.org/ghc/ :? for helpLoading package base ... linking ... done.Prelude> :load foldl[1 of 1] Compiling Test ( foldl.hs, interpreted )Ok, modules loaded: Test.*Test> :list foldl10 11 foldl f c xs = go c xs12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 *Test> :break foldlBreakpoint 1 activated at foldl.hs:(11,0)-(13,34)*Test> :show breaks[0] Test foldl.hs:(11,0)-(13,34)*Test> foldl (+) 0 [1..5]Stopped at foldl.hs:(11,0)-(13,34)_result :: t1 = _[foldl.hs:(11,0)-(13,34)] *Test>[foldl.hs:(11,0)-(13,34)] *Test> :list 10 11 foldl f c xs = go c xs12 where go c [] = c13 go c (x:xs) = go (f c x) xs14

breakpoints can be set in any interpreted module, on any

function definition or expression.

Page 8: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

[foldl.hs:(11,0)-(13,34)] *Test> :set stop :list

[foldl.hs:(11,0)-(13,34)] *Test> :stepStopped at foldl.hs:11:15-21_result :: a = _c :: a = _go :: a -> [b] -> a = _xs :: [b] = _10 11 foldl f c xs = go c xs12 where go c [] = c

[foldl.hs:11:15-21] *Test> length xs5

we can take a single step, which executes until the nextbreakpoint location - next expression or function call.

this is the expression to be evaluated next

new bindings for variables in scope at the breakpoint. Underscore means “unevaluated”

we can evaluate expressions involving the local variables

Page 9: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

[foldl.hs:11:15-21] *Test> :type cc :: a

[foldl.hs:11:15-21] *Test> c

<interactive>:1:0: Ambiguous type variable `a' in the constraint: `Show a' arising from a use of `print' at <interactive>:1:0 Cannot resolve unknown runtime types: a Use :print or :force to determine these types

some variables have polymorphic types. What does that mean? At runtime, c is bound to a value with a monomorphic type.

The compiler doesn’t know the runtime type of c, so it doesn’t know which Show instance to use.

‘a’ is a runtime type variable

Defaulting does not apply to these type variables.

Note that ‘length xs’ worked, however.

Page 10: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

[foldl.hs:11:15-21] *Test> :print cc = (_t1::a)

[foldl.hs:11:15-21] *Test> :force cc = 0

[foldl.hs:11:15-21] *Test> :type cc :: Integer

[foldl.hs:11:15-21] *Test> :show bindings_result :: Integer_t1 :: Integerc :: Integergo :: Integer -> [Integer] -> Integerit :: Intxs :: [Integer]

:print displays a value without knowing its type, and without forcing evaluation.

c is completely unevaluated, so we don't seeany new information.

:force is like :print, but forces evaluation.the type of ‘c’ is now known!

and the type information has been propagated to the other bindings (type variable ‘a’ is now bound to ‘Integer’)

‘b’ was resolved to Integer earlier by ‘length xs’.

GHCi automatically propagates type information as it becomes available.

Page 11: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

[foldl.hs:11:15-21] *Test> :stepStopped at foldl.hs:(12,8)-(13,34)_result :: a = _f :: a -> b -> a = _11 foldl f c xs = go c xs12 where go c [] = c13 go c (x:xs) = go (f c x) xs14

[foldl.hs:(12,8)-(13,34)] *Test> :stepStopped at foldl.hs:13:22-34_result :: Integer = _c :: Integer = 0f :: Integer -> Integer -> Integer = _x :: Integer = 1xs :: [Integer] = [2,3,4,5]12 where go c [] = c13 go c (x:xs) = go (f c x) xs14

most of the types are concrete, becauseGHCi has inspected the values of the free variables to determine their types.

Page 12: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough[foldl.hs:13:22-34] *Test> :stepStopped at foldl.hs:(12,8)-(13,34)_result :: a = _f :: a -> b -> a = _11 foldl f c xs = go c xs12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 [foldl.hs:(12,8)-(13,34)] *Test> :stepStopped at foldl.hs:13:22-34_result :: a = _c :: a = _f :: a -> Integer -> a = _x :: Integer = 2xs :: [Integer] = [3,4,5]12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 [foldl.hs:13:22-34] *Test> :stepStopped at foldl.hs:(12,8)-(13,34)_result :: a = _f :: a -> b -> a = _11 foldl f c xs = go c xs12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 [foldl.hs:(12,8)-(13,34)] *Test> :stepStopped at foldl.hs:13:22-34_result :: a = _c :: a = _f :: a -> Integer -> a = _x :: Integer = 3xs :: [Integer] = [4,5]12 where go c [] = c13 go c (x:xs) = go (f c x) xs14

note that ‘c’ is unevaluated now. foldl is building up a chain of thunks.

Page 13: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

[foldl.hs:13:22-34] *Test> :step c

<interactive>:1:0: Ambiguous type variable `a' in the constraint: `Show a' arising from a use of `print' at <interactive>:1:0 Cannot resolve unknown runtime types: a Use :print or :force to determine these types

[foldl.hs:13:22-34] *Test> :step c `seq` ()Stopped at foldl.hs:13:26-30_result :: a = _c :: a = _f :: a -> Integer -> a = _x :: Integer = 212 where go c [] = c13 go c (x:xs) = go (f c x) xs14 ... [foldl.hs:13:26-30] *Test>

... [foldl.hs:13:26-30] *Test> :show context--> foldl (+) 0 [1..5] Stopped at foldl.hs:13:22-34--> c `seq` () Stopped at foldl.hs:13:26-30

We can single-step the evaluation of ‘c’ separately, :step takes an expression to evaluate as an argument

oops, not like that!

but this works

“…” in the prompt indicates that we are now in a nested debugging session.

lists the current debugging sessions

Page 14: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough... [foldl.hs:13:26-30] *Test> :stepStopped at foldl.hs:13:26-30_result :: Integer = _c :: Integer = 0f :: Integer -> Integer -> Integer = _x :: Integer = 112 where go c [] = c13 go c (x:xs) = go (f c x) xs14 ... [foldl.hs:13:26-30] *Test> :step()

[foldl.hs:13:22-34] *Test> :continue15*Test>

continue the evaluation of c

result of c `seq` () is ()

continue the original evaluation, result is 15

Page 15: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

*Test> :delete *

*Test> foldl (+) 0 [1..20000]*** Exception: stack overflow

let’s try something else… first delete all breakpoints

foldl has a commonly-encountered problem with stack overflow

(I’m using a reduced stack limit here).

Page 16: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough

*Test> :set -fbreak-on-error

*Test> :trace foldl (+) 0 [1..20000]Stopped at <exception thrown>_exception :: e = stack overflow

[<exception thrown>] *Test> :history-1 : foldl (foldl.hs:13:26-30)-2 : foldl (foldl.hs:13:26-30)-3 : foldl (foldl.hs:13:26-30)-4 : foldl (foldl.hs:13:26-30)-5 : foldl (foldl.hs:13:26-30)-6 : foldl (foldl.hs:13:26-30)-7 : foldl (foldl.hs:13:26-30)-8 : foldl (foldl.hs:13:26-30)-9 : foldl (foldl.hs:13:26-30)-10 : foldl (foldl.hs:13:26-30)-11 : foldl (foldl.hs:13:26-30)-12 : foldl (foldl.hs:13:26-30)-13 : foldl (foldl.hs:13:26-30)-14 : foldl (foldl.hs:13:26-30)-15 : foldl (foldl.hs:13:26-30)-16 : foldl (foldl.hs:13:26-30)-17 : foldl (foldl.hs:13:26-30)-18 : foldl (foldl.hs:13:26-30)-19 : foldl (foldl.hs:13:26-30)-20 : foldl (foldl.hs:13:26-30)...

Uncaught exceptions now trigger a

breakpoint

:trace evaluates as normal, but

remembers the 50 most recent steps

:history shows the history of evaluation steps when stopped

Page 17: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Walkthrough[<exception thrown>] *Test> :backLogged breakpoint at foldl.hs:13:26-30_result :: ac :: af :: a -> Integer -> ax :: Integer12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 [-1: foldl.hs:13:26-30] *Test> :backLogged breakpoint at foldl.hs:13:26-30_result :: ac :: af :: a -> Integer -> ax :: Integer12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 [-2: foldl.hs:13:26-30] *Test> :backLogged breakpoint at foldl.hs:13:26-30_result :: ac :: af :: a -> Integer -> ax :: Integer12 where go c [] = c13 go c (x:xs) = go (f c x) xs14 [-3: foldl.hs:13:26-30] *Test>

every step in the history is evaluating ‘f

c x’

Page 18: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

:trace/:history

• The :trace/:history functionality is a poor substitute for a proper lexical call stack.

• Maintaining a proper lexical call stack is hard, but :trace/:history were easy to implement.

• Still, it is useful for finding the source of ‘head []’, pattern-match failure, stack overflow, and infinite loops.

• For infinite loops, hit Control-C which generates an exception, then use :history.

Page 19: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Implementation

• A problem with implementing a debugger is during execution answering the question “where in the source code am I right now?”.

• Approach 1: annotate each expression in the object code with the source expression it orignated from.– Problem: with extensive optimisation and

transformation, it is hard to maintain this information accurately, and it requires careful attention to all the transformations.

– cf. debugging optimised C with gdb.

Page 20: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Annotating compiled code

• Approach 2: use a systematic source-to-source transformation to yield a program with the same semantics, but with additional side-effects, e.g. reporting the current source location– Used by several existing Haskell debuggers, also the

HPC coverage tool.– Two challenges:

• the annotations are side-effects, we don’t want those side-effects to be lost or moved by transformations in the compiler

• do this without impacting performance too much – we want the debugger to be “always on”

Page 21: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Ticks

• Has the same semantics as E• tick<module,n> is a side-effect:

– in coverage it records that this expression was evaluated

– in the debugger it checks whether this breakpoint is enabled

• GHC’s middle-stage optimisations are already side-effect-aware; they won’t– eliminate the side-effect, or– perform it speculatively

• but otherwise transformations are unaffected.

case tick<module,n> of DEFAULT -> E

Page 22: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Compared to coverage analysis…

• The debugger puts ticks in different places– not on variables (too many ticks)– in let-expressions, coverage puts it here:

– the debugger puts it here:

– so that x is in scope at the breakpoint

case tick<module,n> of DEFAULT -> let x = E in E’

let x = E incase tick<module,n> of DEFAULT -> E’

Page 23: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Compiling ticks

• Ideally we want ticks to imply no extra allocation. e.g. in

• f gives rise to a single byte-code-object (BCO), the first instruction of which implements the tick.

• Sadly, not all ticks are quite so easy to implement. e.g.

• Every tick must be at a safe-point, i.e. at the beginning of a BCO, because execution may be suspended (just like a GC). We need to manufacture an extra safe point:

let f = \x -> case tick<module,n> of DEFAULT -> Ein …

let x = E incase tick<m,n> of DEFAULT -> E’

let x = E inlet z = case tick<m,n> of DEFAULT -> E’in z

Page 24: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Performance

• Interpreted code already runs 15-20x slower than optimised compiled code (but compiles 2-3x quicker)

• Performance of interpreted code is affected:– at compile-time, we need to insert ticks (+15%)– at runtime, check breakpoint status at each breakpoint, and extra safe-

points (+45%)• But safe points do unnecessary updates, we expect to be able to reduce this

– debugger cannot be disabled, but individual modules can be compiled rather than interpreted.

Page 25: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

The GHC API

• The debugger is implemented mostly in Haskell; breakpoints are implemented using threads.

• Debugging functionality is exposed via the GHC API.

• GHCi is just a textual front-end built on top of this API, IDEs could also talk directly to the GHC API to provide interactive debugging.

Page 26: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Future Work

• High priority: a real lexical call stack• Get feedback from GHC 6.8.1 users – is

exposing the evaluation order confusing or helpful?

• Performance can be improved– breakpoints in compiled code should be

possible

• API interface means it can be built into an IDE

• Concurrency debugging

Page 27: A Lightweight Interactive Debugger for Haskell Simon Marlow José Iborra Bernard Pope Andy Gill

Conclusion

• We have an instant always-on debugger that works with everything that GHCi can compile

• Functionality is limited compared to other debuggers – no backtrace, no algorithmic debugging etc.

• Online debugging has some benefits over offline debugging: – evaluate arbitrary expressions involving runtime

values– no need to store the entire trace of the program– no need to switch tools if you’re already using GHCi