Quick notes on debugging a Go program with lldb - limetext/lime GitHub Wiki
Prerequisites
General
We'll be using the termbox frontend to debug issue #515 in this example so make sure you've got the termbox frontend built first.
Once that's done, run this in a terminal:
cd $GOPATH/src/github.com/limetext/lime/frontend/termbox
git checkout 90e5cc5c54a6cb1c964fb45bf40b672c04e20ba9 # Just so that we check out the code in a state where the issue hasn't been fixed yet
go build -ldflags="-linkmode=internal" -o lime
./lime -console main.go
The internal linkmode is due to Go#8973.
As issue #515 is specifically with the console not showing anything, we'll want the bottom couple of lines to be empty to be able to figure out why that happens as shown in the following screenshot:
If the console is showing, press ctrl+q
to quit and launch again (you might have to try several times).
On OSX, update to a recent lldb
At the time of writing this, installing lldb via brew results in lldb complaining about not finding "debugserver", so a manual lldb compilation is needed.
curl -O http://llvm.org/releases/3.6.0/llvm-3.6.0.src.tar.xz
curl -O http://llvm.org/releases/3.6.0/lldb-3.6.0.src.tar.xz
curl -O http://llvm.org/releases/3.6.0/cfe-3.6.0.src.tar.xz
tar xvfz llvm-3.6.0.src.tar.xz
cd llvm-3.6.0.src/tools/
tar xvfz ../../lldb-3.6.0.src.tar.xz
mv lldb-3.6.0.src/ lldb
tar xvfz ../../cfe-3.6.0.src.tar.xz
mv cfe-3.6.0.src/ clang
cd ..
mkdir build
cd build
cmake -DLLVM_TARGETS_TO_BUILD=host -DCMAKE_BUILD_TYPE=Release ..
make -j8 lldb debugserver
export PATH=$PWD/bin:$PATH
When "lldb" is referenced in the text below, make sure to use your newly compiled $PWD/bin/lldb
binary.
Launching lldb
OK, time to attach lldb to the running process. Run this in your second terminal tab/window:
lldb -p `ps | grep "\d\s./lime" | grep -o -E "^\d+"`
The -p
parameter tells lldb that it should attach to an already running process, and the "magic" is just to figure out what the process id of lime that was launched earlier.
If lldb complains about unable to locate debugserver
try launching it manually:
debugserver localhost:12345 --attach `ps | grep "\d\s./lime" | grep -o -E "^\d+"`
and in a third terminal:
./lldb -o "process connect connect://localhost:12345"
Since something is broken with the render of the console (which is a "View"), putting a breakpoint there seems like a good idea. The following is a copy and paste of the session I had:
13:17 ~/code/3rdparty/llvm-3.6.0.src/build/bin $ ./lldb -o "process connect connect://localhost:12345"
(lldb) process connect connect://localhost:12345
Process 76098 stopped
* thread #1: tid = 0x1b49df, 0x000000000005ef83 lime`runtime.mach_semaphore_timedwait + 19 at sys_darwin_amd64.s:436, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x000000000005ef83 lime`runtime.mach_semaphore_timedwait + 19 at sys_darwin_amd64.s:436
433 MOVL nsec+8(FP), DX
434 MOVL $(0x1000000+38), AX // semaphore_timedwait_trap
435 SYSCALL
-> 436 MOVL AX, ret+16(FP)
437 RET
438
439 // uint32 mach_semaphore_signal(uint32)
(lldb) b main.(*tbfe).renderView
Breakpoint 1: where = lime`main.(*tbfe).renderView + 37 at main.go:179, address = 0x0000000000002555
(lldb) c
Process 76098 resuming
Process 76098 stopped
* thread #1: tid = 0x1b49df, 0x0000000000002555 lime`main.(t=0x000000c208090360, v=0x000000c208092200, lay=main.layout at 0x000000c20888bde0).renderView + 37 at main.go:179, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000000002555 lime`main.(t=0x000000c208090360, v=0x000000c208092200, lay=main.layout at 0x000000c20888bde0).renderView + 37 at main.go:179
176 }
177
178 func (t *tbfe) renderView(v *backend.View, lay layout) {
-> 179 p := util.Prof.Enter("render")
180 defer p.Exit()
181
182 sx, sy, w, h := lay.x, lay.y, lay.width, lay.height
(lldb) p v->cursyntax
(string) $0 = (str = 0x0000000000000000, len = 0)
(lldb) p lay
(main.layout) $1 = {
x = 0
y = 0
width = 0
height = 0
visible = (A = 4411, B = 4411)
lastUpdate = 0
}
AHA, the layout is bogus. Lets try modifying it and see if the issue goes away. But first, lets figure out the parameters of the other view:
(lldb) c
Process 76098 resuming
Process 76098 stopped
* thread #13: tid = 0x1b49eb, 0x0000000000002555 lime`main.(t=0x000000c208090360, v=0x000000c2080e5500, lay=main.layout at 0x000000c20888bde0).renderView + 37 at main.go:179, stop reason = breakpoint 1.1
frame #0: 0x0000000000002555 lime`main.(t=0x000000c208090360, v=0x000000c2080e5500, lay=main.layout at 0x000000c20888bde0).renderView + 37 at main.go:179
176 }
177
178 func (t *tbfe) renderView(v *backend.View, lay layout) {
-> 179 p := util.Prof.Enter("render")
180 defer p.Exit()
181
182 sx, sy, w, h := lay.x, lay.y, lay.width, lay.height
(lldb) p lay
(main.layout) $2 = {
x = 0
y = 0
width = 204
height = 45
visible = (A = 0, B = 1669)
lastUpdate = 0
}
(lldb) c
Process 76098 resuming
Process 76098 stopped
* thread #13: tid = 0x1b49eb, 0x0000000000002555 lime`main.(t=0x000000c208090360, v=0x000000c208092200, lay=main.layout at 0x000000c20888bde0).renderView + 37 at main.go:179, stop reason = breakpoint 1.1
frame #0: 0x0000000000002555 lime`main.(t=0x000000c208090360, v=0x000000c208092200, lay=main.layout at 0x000000c20888bde0).renderView + 37 at main.go:179
176 }
177
178 func (t *tbfe) renderView(v *backend.View, lay layout) {
-> 179 p := util.Prof.Enter("render")
180 defer p.Exit()
181
182 sx, sy, w, h := lay.x, lay.y, lay.width, lay.height
(lldb) p lay
(main.layout) $3 = {
x = 0
y = 0
width = 0
height = 0
visible = (A = 4411, B = 4411)
lastUpdate = 0
}
Ok, now we are ready. The e
command will evaluate an expression, like this:
(lldb) e lay.width=204, lay.y=46, lay.visible.A=4000, lay.height=5
(long) $1 = 5
(lldb) p lay
(main.layout) $20 = {
x = 0
y = 46
width = 204
height = 5
visible = (A = 4000, B = 4411)
lastUpdate = 0
}
Lets add a breakpoint for termbox.Flush, which is the function that will actually output the changes we've made to the terminal, and disable breakpoint 1.
(lldb) b github.com/limetext/termbox-go.Flush
Breakpoint 2: where = lime`github.com/limetext/termbox-go.Flush + 38 at api.go:247, address = 0x00000000000e47e6
(lldb) break disable 1
1 breakpoints disabled.
(lldb) c
Process 76278 resuming
Process 76278 stopped
* thread #4: tid = 0x1b5e8e, 0x00000000000e47e6 lime`github.com/limetext/termbox-go.Flush(~r0=error at 0x000000c20888fdd0) + 38 at api.go:247, stop reason = breakpoint 2.1
frame #0: 0x00000000000e47e6 lime`github.com/limetext/termbox-go.Flush(~r0=error at 0x000000c20888fdd0) + 38 at api.go:247
244 // Synchronizes the internal back buffer with the terminal.
245 func Flush() error {
246 // invalidate cursor position
-> 247 lastx = coord_invalid
248 lasty = coord_invalid
249
250 update_size_maybe()
Ok, so we are in termbox.Flush
, but it has not executed yet. What we want is to execute a step-out
step, which executes the current function and breaks as soon as execution has resumed at the call site.
(lldb) thread step-out
Process 76278 stopped
* thread #4: tid = 0x1b5e8e, 0x000000000000a4c0 lime`main.(*tbfe).renderthread.func1 + 1152 at main.go:513, stop reason = step out
frame #0: 0x000000000000a4c0 lime`main.(*tbfe).renderthread.func1 + 1152 at main.go:513
510 }
511
512 termbox.Flush()
-> 513 }
514
515 for range t.dorender {
516 dorender()
(lldb)
Switch to the terminal that is running lime and we'll see that it has now rendered a bit of the console!