21

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:

  1. 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

  2. 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.

Community
  • 1
  • 1
  • You've gotten further than I have. I don't even know how to use the debugging features of GHCi when they *are* available—I should really RTFM sometime. One suggestion for you: while segfaults can certainlt result from improper use of `unsafeCoerce` or `unsafePerformIO`, it seems more common to get them from bad unsafe array/vector references. Do the unsafe operation's you're using have safe equivalents, like `write` as opposed to `unsafeWrite`? If so, using the safe versions for your testing and then switching to unsafe ones for performance can make things *much* easier to deal with. – dfeuer Feb 17 '15 at 22:53
  • 1
    @dfeuer: In this case, as I discovered today, the segfault actually came from dereferencing a pointer ("Ptr a"), whose underlying memory was already freed by the C library (libSDL2) the package was using. That was my own fault entirely. But the question still stands, since I had to resort to Debug.Trace, and in a more complex case that might take a really long time. – Vladimir Semyonov Feb 18 '15 at 19:27
  • Just to make sure - did you configure with `cabal configure --ghc-option=-fPIC` or similar? The sdl2 README mentions it. – schellsan Dec 14 '16 at 22:22

1 Answers1

2

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-deps. 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.

Echo Nolan
  • 1,111
  • 1
  • 11
  • 23