A cross-platform debugger for Go
blog.mailgun.com
blog.mailgun.com
It seems like he tried to use delve and it wasn't portable at the time. Sure he could have worked on making delve portable instead, but he probably learned a lot more poking around the internals himself.
It's unusual and it's opinionated, but it works.
I'm doing some research that shows this isn't the case. You can have a system where enabling the debugger has zero impact on runtime performance, via dynamic deoptimization.
http://www.lifl.fr/dyla14/papers/dyla14-3-Debugging_at_Full_...
Monomorphization is a bog-standard implementation technique for generics. It's not at all unusual.
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 ...)
So, when debugging is turned on, the code would return the right answers, but it wouldn't have the same performance.
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)
Can you give an example of the kind of bug you expect to see?
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. 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.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.
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.
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.
If you've inserted compiler barriers it can't move code across, the compiler will no longer perform the same optimizations with and without your debug library.
Those optimizations often make bugs visible, because variables no longer have the value you expect them to at the time you expect it, etc.
Add in threads, and the problem gets worse:
http://preshing.com/20120515/memory-reordering-caught-in-the...
Look at the behavior this has with and without the barrier. It's buggy without it. With it, it's fine.
This is exactly equivalent to:
Without the debugging library, the code is clearly buggy and doesn't work in user-visible ways.
With the debugging library, if i insert a breakpoint where the current asm barrier is, it now behaves correctly 100% of the time, even though it's broken.
On the other hand, godebug generates straightforward single-threaded code that creates pointers to locals in a shadow data structure and accesses them later. There's no reason it shouldn't work if you're not using goroutines.
In particular, a previous call to godebug.Declare("x", &x) will add a pointer to what was previously a local variable to a data structure. This effectively moves all locals to a heap representation of the goroutine's stack, to be accessed later. It's going to kill performance, but it's legal to do.
Sure, but it's going to cause the optimizer to do different things than it would have to that variable. As I said, this essentially changes what the compiler is allowed to do, and will expose or hide bugs (usually hide if it hurts the optimizer) :)
One only has to look at the bugzilla's of gcc and llvm to discover all sorts of fun things that barriers hide/expose.
I've considered putting it up as a permanent playground like http://play.golang.org, where you can debug little Go snippets on the web. Is that something that you would find useful?
"template: views/layout:views/categories/show:1:301: executing "views/categories/show::main" at <.CurrentUser.Id>: nil pointer evaluating interface {}.Id"
Happens when viewing any category. Looks like a useful site though.
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.
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 :(
>>> 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... [2] https://github.com/mailgun/godebug/blob/5c173f56b398bc13fd41...
EDIT: formatting
I think one is fine for now.
This failure mode is a large part of why I decided to write godebug.