Files
build-web-application-with-…/en/eBook/11.2.md

10 KiB

11.2 Debugging with GDB

During the development process of any application, developers will always need to perform some kind of code debugging. PHP, Python, and most of the other dynamic languages, are able to be modified at runtime, as long as the modifications do not explicitly need to be compiled. We can easily print data in dynamic operating environments, outputting our changes and printing variable information directly. In Go, you can of course speckle your code with Printlns before-hand to display variable information for debugging purposes, but any changes to your code need to be recompiled every time. This can quickly become cumbersome. If you've programmed in Python or Javascript, you'll know that the former provides tools such as pdb and ipdb for debugging, and the latter has similar tools that are able to dynamically display variable information and facilitate single-step debugging. Fortunately, Go has native support for a similar tool which provides such debugging features: GDB. This section serves as a brief introduction into debugging Go applications using GDB.

GDB debugging Profile

GDB is a powerful debugging tool targeting UNIX-like systems, released by the FSF (Free Software Foundation). GDB allows us to do the following things:

  1. Initial settings can be customize according to the specific requirements of your application.
  2. Can be set so that the program being debugged in the developer's console stops at the prescribed breakpoints (breakpoints can be conditional expressions).
  3. When the program has been stopped, you can check its current state to see what happened.
  4. Dynamically change the current program's execution environment.

To debug your Go applications using GDB, the version of GDB you use must be greater than 7.1.

When compiling Go programs, the following points require particular attention:

  1. Using -ldflags "-s" will prevent the standard debugging information from being printed
  2. Using -gcflags "-N-l" will prevent Go from performing some of its automated optimizations -optimizations of aggregate variables, functions, etc. These optimizations can make it very difficult for GDB to do its job, so it's best to disable them at compile time using these flags.

Some of GDB's most commonly used commands are as follows:

  • list

Also used in its abbreviated form l, list is used to display the source code. By default, it displays ten lines of code and you can specify the line you wish to display. For example, the command list 15 displays ten lines of code centered around line 15, as shown below.

10	        time.Sleep(2 * time.Second)
11	        c <- i
12	    }
13	    close(c)
14	}
15	
16	func main() {
17	    msg := "Starting main"
18	    fmt.Println(msg)
19	    bus := make(chan int)
  • break

Also used in its abbreviated form b, break is used to set breakpoints, and takes as an argument that defines which point to set the breakpoint at. For example, b 10 sets a break point at the tenth row.

  • delete Also used in its abbreviated form d, delete is used to delete break points. the break point is set followed by the serial number, the serial number can be obtained through the info breakpoints break point set corresponding serial number is displayed as follows to set a break point number.

    Num Type Disp Enb Address What 2 breakpoint keep y 0x0000000000400dc3 in main.main at /home/xiemengjun/gdb.go:23 breakpoint already hit 1 time

  • backtrace

Abbreviated command bt, the process used to print the execution of the code, as follows:

#0  main.main () at /home/xiemengjun/gdb.go:23
#1  0x000000000040d61e in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244
#2  0x000000000040d6c1 in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267
#3  0x0000000000000000 in ?? ()
  • info

info command to display information, followed by several parameters, we used the following categories:

  • info locals

Displays the currently executing program variable values

  • info breakpoints

Display a list of currently set breakpoints

  • info goroutines

Goroutine displays the current list of execution, as shown in the code, with* indicates the current execution

* 1 running runtime.gosched
* 2 syscall runtime.entersyscall
3 waiting runtime.gosched
4 runnable runtime.gosched
  • print

Abbreviated command p, or other information used to print variable, followed by the variable name to be printed, of course, there are some very useful function $len() and $cap(), is used to return the current string, slices or maps the length and capacity.

  • whatis

Used to display the current variable type, followed by the variable name, for example, whatis msg, is shown below :

type = struct string

  • next

Abbreviated command n, for single-step debugging, skip the next step, when there is a break point, you can enter n jump to the next step to continue

  • continue

Abbreviated command c, to jump out of the current break point can be followed by parameter N, the number of times the break point skipped

  • set variable

This command is used to change the value of a variable during operation, formats such as : set variable <var> = <value>

Debugging process

We use the following code to demonstrate how this GDB to debug Go program, the following is the code that will be demonstrated :

package main

import (
	"fmt"
	"time"
)

func counting(c chan<- int) {
	for i := 0; i < 10; i++ {
		time.Sleep(2 * time.Second)
		c <- i
	}
	close(c)
}

func main() {
	msg := "Starting main"
	fmt.Println(msg)
	bus := make(chan int)
	msg = "starting a gofunc"
	go counting(bus)
	for count := range bus {
		fmt.Println("count:", count)
	}
}

Compiled file, an executable file gdbfile:

go build -gcflags "-N -l" gdbfile.go

By GDB command to start debugging :

gdb gdbfile

After the first start is not possible to look at this program up and running, just enter the run command carriage return after the program starts to run, the program correctly, then you can see the program output is as follows, and we execute the program directly from the command line output is the same:

(gdb) run
Starting program: /home/xiemengjun/gdbfile 
Starting main
count: 0
count: 1
count: 2
count: 3
count: 4
count: 5
count: 6
count: 7
count: 8
count: 9
[LWP 2771 exited]
[Inferior 1 (process 2771) exited normally]	

Well, now we know how to make the program run up, then began to give the code to set a break point :

(gdb) b 23
Breakpoint 1 at 0x400d8d: file /home/xiemengjun/gdbfile.go, line 23.
(gdb) run
Starting program: /home/xiemengjun/gdbfile 
Starting main
[New LWP 3284]
[Switching to LWP 3284]

Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23	        fmt.Println("count:", count)

The above example shows the b 23 set a break point on line 23, then enter run start the program running. The program now in place to set a break point in front stopped, we need to look at the context of the break point corresponding source code, enter list you can see the display from the current source line before stopping five starts :

(gdb) list
18	    fmt.Println(msg)
19	    bus := make(chan int)
20	    msg = "starting a gofunc"
21	    go counting(bus)
22	    for count := range bus {
23	        fmt.Println("count:", count)
24	    }
25	}

GDB now running the current program environment has retained some useful debugging information, we just print out the corresponding variables, see the corresponding variable types and values:

(gdb) info locals
count = 0
bus = 0xf840001a50
(gdb) p count
$1 = 0
(gdb) p bus
$2 = (chan int) 0xf840001a50
(gdb) whatis bus
type = chan int

Then let the program continue down the execution, please read the following command:

(gdb) c
Continuing.
count: 0
[New LWP 3303]
[Switching to LWP 3303]

Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)
(gdb) c
Continuing.
count: 1
[Switching to LWP 3302]

Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)

After each entry c code will be executed once, and jump to the next for loop, continue to print out the appropriate information.

Assume that current need to change the context variables, skipping the process and continue to the next step, the modified desired results obtained :

(gdb) info locals
count = 2
bus = 0xf840001a50
(gdb) set variable count=9
(gdb) info locals
count = 9
bus = 0xf840001a50
(gdb) c
Continuing.
count: 9
[Switching to LWP 3302]

Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23
23 fmt.Println("count:", count)		

Finally a little thought, in front of the entire program is running in the process in the end created a number goroutine, each goroutine are doing :

(gdb) info goroutines
* 1 running runtime.gosched
* 2 syscall runtime.entersyscall 
3 waiting runtime.gosched 
4 runnable runtime.gosched
(gdb) goroutine 1 bt
#0 0x000000000040e33b in runtime.gosched () at /home/xiemengjun/go/src/pkg/runtime/proc.c:927
#1 0x0000000000403091 in runtime.chanrecv (c=void, ep=void, selected=void, received=void)
at /home/xiemengjun/go/src/pkg/runtime/chan.c:327
#2 0x000000000040316f in runtime.chanrecv2 (t=void, c=void)
at /home/xiemengjun/go/src/pkg/runtime/chan.c:420
#3 0x0000000000400d6f in main.main () at /home/xiemengjun/gdbfile.go:22
#4 0x000000000040d0c7 in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244
#5 0x000000000040d16a in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267
#6 0x0000000000000000 in ?? ()

Commands by viewing goroutines we can clearly understand goruntine performed internally how each function call sequence has plainly shown.

Summary

In this section, we introduced some basic commands from the GDB debugger that you can use to debug your Go applications. These included the run, print, info, set variable, continue, list and break commands, among others. From the brief examples above, I hope that you will have a better understanding of how the debugging process works in Go using the GDB debugger. If you want to get more debugging tips, please refer to the GDB manual on its official website.