
A cross-platform debugger for Go - jschlatter
http://blog.mailgun.com/introducing-a-new-cross-platform-debugger-for-go/
======
troym
There's a more, er... traditional, debugger for Golang in the works:
[https://github.com/derekparker/delve](https://github.com/derekparker/delve)

~~~
tyho
Comparing the Linux and OSX instructions is fun:

[https://github.com/derekparker/delve#building](https://github.com/derekparker/delve#building)

~~~
sunnyps
If I remember correctly I had to follow the same instructions for installing
gdb too :/

~~~
autoreleasepool
Really? I just did brew install gdb

~~~
aleksi
[https://sourceware.org/gdb/wiki/BuildingOnDarwin](https://sourceware.org/gdb/wiki/BuildingOnDarwin)

~~~
autoreleasepool
Duly noted. Thanks.

------
dvirsky
This looks really great. When I saw how Go does coverage testing using the
same approach I felt that the code generation approach would work for a
debugger. I'm glad someone did it - and from the few moments of playing with
it it seems to be working great. I'm adding this to my toolkit for sure.

------
e79
This demonstrates the power of Go generators. We're seeing what are typically
language features such as generics and debugging being implemented _before_
runtime using code generation. The result is more functionality without an
overall increase in runtime size or decrease in runtime performance.

It's unusual and it's opinionated, but it works.

~~~
pcwalton
> This demonstrates the power of Go generators. We're seeing what are
> typically language features such as generics and debugging being implemented
> before runtime using code generation. The result is more functionality
> without an increase in runtime size or a decrease in performance.

Monomorphization is a bog-standard implementation technique for generics. It's
not at all unusual.

~~~
kzhahou
source?

~~~
pcwalton
Just off the top of my head, C++, .NET (via a JIT), Rust, D, MLton, and (I
think) Swift all do it.

~~~
kzhahou
I was hoping for some article or wikipedia entry that would tie your comment
with the parent's comment, and with the original article. Monomorphization is,
to my understanding, taking a polymorphic function and instantiating it to the
specific type requested. This is done at compile time. Now, the tool in this
article interleaves debugging code with original source code. So the
connection is... both monomorphization of generics/templates and this tool
operate by emitting source code prior to compilation?

~~~
Jweb_Guru
The original comment was about how both debugging and generics are being
implemented with code generation before runtime in Go and described this as
"unusual." The response was that this precise technique is how generics are
implemented in many languages already; in fact, CFront (the original C++
compiler) was entirely implemented as code generation on top of C. The
response specifically discussed generics, not debugging, so I'm not sure why
you thought it was talking about debugging.

------
eosrei
Jeremy Schlatter, godebug creator, did an interesting talk introducing the
project March 18th at GoSF. The slides are available here:
[https://github.com/jeremyschlatter/godebug-
talk](https://github.com/jeremyschlatter/godebug-talk)

------
housel
Norman Ramsey and David R. Hanson implemented this sort of compiler-assisted
debugging for C back in 1992, in a debugger called ldb:
[http://www.cs.tufts.edu/~nr/pubs/retargetable-
abstract.html](http://www.cs.tufts.edu/~nr/pubs/retargetable-abstract.html)

------
DannyBee
What do you do for inlining + code movement, where functions become
interleaved (as do lines)?

In particular, after inlining, how are you guaranteeing statements from a
given function don't get moved before the inlined enterfunction call (and
similarly with lines)

Or do you not expect it to ever be able to report right answers for optimized
programs? (which is a valid way to live life, of course, but ...)

~~~
skybrian
Since it's modifying the source before compiling it, I expect that the
compiler will conclude that most optimizations can't be applied when they
cross breakpoint boundaries.

So, when debugging is turned on, the code would return the right answers, but
it wouldn't have the same performance.

~~~
DannyBee
"Since it's modifying the source before compiling it, I expect that the
compiler will conclude that most optimizations can't be applied when they
cross breakpoint boundaries."

While true, this depends on the compiler knowing this is a magical breakpoint
barrier it can't move things across. The compiler has no idea this is a
magical barrier unless something has told it it's a magical barrier. Looking
at godebug library, i don't see this being the case, it looks like it
translates into an atomic store and an atomic load to some variables, and then
a function call, which the compiler is definitely not going to see as a
"nothing can move across" barrier.

(Also, having the debugging library alter the semantics of the program is 100%
guaranteed to lead to bugs that are not visible when using the library, etc)

~~~
jschlatter
"Also, having the debugging library alter the semantics of the program is 100%
guaranteed to lead to bugs that are not visible when using the library, etc"

Can you give an example of the kind of bug you expect to see?

~~~
chrisseaton
say you have:

    
    
        x = 1
        x = 2
        bar()
    

You set a breakpoint on the call to bar and examine the value of x. You would
expect it to be 2, but what if the compiler had decided to move the allocation
of x = 2 to after the call to bar? There's no reason why it shouldn't. You'd
then see x = 1, which would confuse you.

~~~
jschlatter
Thanks for the concrete example. The transformed source code that would get
compiled for this example looks like:

    
    
      scope.Declare("x", &x)
      godebug.Line(ctx, scope, 3)
      x = 1
      godebug.Line(ctx, scope, 4)
      x = 2
      godebug.Line(ctx, scope, 5)
      bar()
    

The value of x is visible to all of the godebug.Line calls, so the compiler
should know that it can't move x = 2 to after the call to bar.

~~~
DannyBee
Right, but now you have the opposite problem. Let's say that before the
compiler _could_ move x=2 after bar (let's assume bar does not touch x).

Now, in your world, it can't.

So before, you would have seen x=1 in the call to bar, and now when you use
the debug library, you will see x=2.

~~~
ghodss
But it's not really material, because bar doesn't touch x (and if it did, this
problem wouldn't exist to begin with).

In other words, it might be a bit strange and cause a slight detour in your
quest to discover the cause of a bug, but it wouldn't actually change any
behavior or cause any issues.

~~~
DannyBee
Your argument is essentially: The barriers you insert, even if they block
optimization, will never block optimization in a way that changes behavior.
This is demonstrably false, since you are, among other things, taking the
address of a variable, which means escape analysis won't do things to it, etc.

You can argue "The behavior it changes doesn't matter". As i've shown, 1\. it
does in a threaded environment (like, you know, go) 2\. It depends on whether
your code is buggy or not.

IT's certainly true that it never, on it's own, causes bugs. But as i've
shown, it can make bugs appear to come or go.

If you don't think that will ever happen, i don't know what to tell you, other
than "It has happened in literally every compiler that has ever had barriers
like this".

Without any evidence why Go should be different here, i don't see why go will
be different here.

------
sippndipp
What an awesome idea to use gopherjs. Just added it to
[http://golangtoolbox.com/](http://golangtoolbox.com/) \- if you have more
tools that are great please add them here.

~~~
jschlatter
Thanks! gopherjs is a really great project. This was my first time using it
and I was impressed.

I've considered putting it up as a permanent playground like
[http://play.golang.org](http://play.golang.org), where you can debug little
Go snippets on the web. Is that something that you would find useful?

~~~
polymathist
I agree with you about gopherjs being an awesome project. And I would love to
see a playground-like debugger that you could run directly in your browser for
quick debugging!

------
lighthawk
While I totally applaud this effort, how does it not lie? It inserts code into
the actual source claiming it is on a line in the original file, but it is in
a different line in the compiled version. That seems like it could be a
problem at some point, and despite the serious utility in this, I'm not 100%
convinced it is the right tool for the job.

~~~
jschlatter
Author here. I agree that this could cause surprises. Are there specific cases
you have in mind?

Two cases I have thought of are stack traces and logging that inspects the
stack. In the former case, if you get a stack trace while debugging it will
not mean much because the lines do not match those of your code. In the latter
case, logging statements may print the wrong things if they depend on being
called a certain number of stack frames below user code. glog does this, for
example[1]. I don't have solutions to either case yet, but I have some ideas
of how to start. I think the utility the tool provides is well worth those two
issues, and I'm hopeful that both can be fixed.

[1]
[https://github.com/golang/glog/blob/master/glog.go#L536](https://github.com/golang/glog/blob/master/glog.go#L536)

~~~
skybrian
A neat trick in some languages is to modify the source so that the line
numbers don't change, for example by using semicolons instead of newlines and
making sure a newline never appears in inserted instrumentation code. But I'm
not sure if that's possible in Go.

------
shurcooL
I love the in-browser demo of this Go program via GopherJS. So easy to try and
become familiar. Well done!

------
tlrobinson
I wrote a similar instrumenting debugger for JavaScript awhile back, but
haven't maintained it:
[https://github.com/tlrobinson/xebug](https://github.com/tlrobinson/xebug)

At one point even I implemented the V8 debugger protocol so you could use the
Web Inspector, and an HTTP proxy that would automatically instrument
JavaScript files (using synchronous XMLHttpRequests to block execution...),
but I don't think I ever posted that code :(

------
djhworld
This is really cool, thanks.

------
andrewchambers
How does this handle go routines? Can only one be paused at a time?

~~~
jschlatter
Author here. Yes, only one goroutine is paused at a time. I'm planning to add
commands to show and jump between goroutines. Still sketching those out,
though. Do you think it would be helpful to have multiple goroutines paused by
the debugger at once?

~~~
dvirsky
I'd like to be able to completely pause all goroutines and slowly analyze the
state of each one. You can probably just add some mutex or waitgroup to your
line() method and just lock it when a goroutine pauses, no?

~~~
jschlatter
Yeah, that should be relatively easy to do. We would just skip the "am I the
goroutine under the debugger" check here [1] and add some kind of channel
communication here [2]. The hardest thing is probably just the UI. How should
that functionality work? I'm imagining something like this:

    
    
      >>> pause all
      All goroutines paused
      >>> show goroutines
      1: foo.go:16
      2: foo.go:24
      3. bar.go:10 [current]
      >>> next
      -> // some code from goroutine 3
      >>> goroutine 2
      Now tracing goroutine 2. Current location:
      /*
         Code listing from goroutine 2's current location
      */
      >>> next
      -> // some code from goroutine 2
    

Is this the kind of interface you were imagining?

[1]
[https://github.com/mailgun/godebug/blob/5c173f56b398bc13fd41...](https://github.com/mailgun/godebug/blob/5c173f56b398bc13fd4175bc828cb1b6bfa9e47e/lib/debug.go#L293)
[2]
[https://github.com/mailgun/godebug/blob/5c173f56b398bc13fd41...](https://github.com/mailgun/godebug/blob/5c173f56b398bc13fd4175bc828cb1b6bfa9e47e/lib/debug.go#L300)

EDIT: formatting

~~~
dvirsky
Yep, something like gdb's threads interface.

------
caioariede
It would be nice to have something like this for Rust.

------
Rapzid
Curious.. I used debugging in LiteIDE a while back and it seemed to work well.
Anyone else using it successfully?

~~~
jschlatter
LiteIDE was probably using gdb as a backend. gdb is not a very reliable
debugger as of the last few Go releases. Here's a demo of a common failure
mode of gdb on Go: occasionally running the entire program when you try to
take a single step
[https://youtu.be/qo4RAPxCTa0?t=351](https://youtu.be/qo4RAPxCTa0?t=351)

This failure mode is a large part of why I decided to write godebug.

