I'm doing low-level IO (for library bindings) in Haskell and am experiencing a segfault. I would like to use GHCi's :break to figure out what's going on, but here's what happens:
> import SDL
> :break SDL.setPaletteColors
cannot set breakpoint on setPaletteColors: module SDL.Video.Renderer is not interpreted
Since the offending code is not inside my own modules, but rather inside a module in an external package, it's loaded as compiled code and apparently I can't use :break on compiled modules.
GHCi manual confirms this and provides a hint:
There is one major restriction: breakpoints and single-stepping are only available in interpreted modules; compiled code is invisible to the debugger[5].
[5] Note that packages only contain compiled code, so debugging a package requires finding its source and loading that directly.
Let's try it directly:
> :load some_path/sdl2/src/SDL/Video/Renderer.hs
some_path/sdl2/src/SDL/Video/Renderer.hs:101:8:
Could not find module ‘Control.Monad.IO.Class’
It is a member of the hidden package ‘transformers-0.3.0.0’.
Perhaps you need to add ‘transformers’ to the build-depends in your .cabal file.
Use -v to see a list of the files searched for.
I can add the dependencies to my .cabal file, but this already feels wrong. Once I've done that:
> :load some_path/sdl2/src/SDL/Video/Renderer.hs
some_path/sdl2/src/SDL/Video/Renderer.hs:119:8:
Could not find module ‘SDL.Internal.Numbered’
it is a hidden module in the package ‘sdl2-2.0.0’
Use -v to see a list of the files searched for.
I could make those modules public (probably? by modifying the package .cabal?), but at this point it seems a really awkward way to do things and I didn't pursue it further.
EDIT:
I actually tried that and got the baffling result:
> :load some_path/sdl2/src/SDL/Video/Renderer.hs
[1 of 1] Compiling SDL.Video.Renderer ( some_path/sdl2/src/SDL/Video/Renderer.hs, interpreted )
Ok, modules loaded: SDL.Video.Renderer.
> :break SDL.setPaletteColors
cannot set breakpoint on SDL.setPaletteColors: module SDL.Video.Renderer is not interpreted
My (uneducated) guess: it's because the external module is still linked to my code as a binary, and loading it dynamically in interpreted mode doesn't change that.
So, to sum up the question: what is a good way to debug IO in an external package?
Additional notes:
I do have the source to the package I need to debug; in fact, it's been added to the project with cabal sandbox add-source
An alternative option to using GHCi would be to add traces to the package source, but this is an unfortunate option, since it involves recompilation of the package on each modification (whenever I need more information about the execution and modify the traces), and that takes a really long time. Interactive debugging with GHCi seems a better tool for this job, if only I knew how to use it.
Stack has some support for this. Running stack ghci --load-local-deps $TARGET
will load your project and any dependencies that are in the packages
field of stack.yaml
, including if they're marked as extra-dep
s. Breakpoints will work then. You can debug a dependency in GHCi by running stack unpack $PACKAGE
and adding it to packages
in stack.yaml
.
This is not a panacea however. If the packages have conflicting package-global language extensions (or other dynamic flags) or module name clashes it won't work. For example, if your top-level package has default-extensions: NoImplicitPrelude
and your dependencies don't, they won't have a prelude imported and will almost certainly not load. See this GHC bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With