Quick notes on debugging a Go program with lldb - adamallaf/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!