Debugging GO program with GDB #1

Golang provides a python script to extend gdb to show goroutines and the callstacks here.

However, it doesn’t work as written.
This patch fixed the first problem, but it still fails (at least on OS X and Windows).

I found this article which I think worked in older GO versions, but it fails in go 1.4.2 (latest as of 2015.3).

I changed Allg class’s offset and init function to show a list of goroutines as below.

(gdb) info goroutines
  1 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc208000120 waitreason="sleep"
  2 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc208000480 waitreason="force gc (idle)"
  3 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc2080005a0 waitreason="GC sweep wait"
  4 waiting  fname=runtime.gopark faddr=0x13585 &g=0xc2080006c0 waitreason="finalizer wait"
  5 syscall  fname=runtime.switchtoM faddr=0x36830 &g=0xc208000a20

I’ll need more work to fix ‘go $goroutineid bt’.

(gdb) goroutine 1 bt
Python Exception <class 'gdb.error'> There is no member named status.:
Error occurred in Python command: There is no member named status.

Today’s change.

runtime/runtime-gdb.py:
class Allg:
    __allglen = -1
    __position = 0
    __allg = 0

    __offsets = {
            'status': 120,
            'waitreason': 144,
            'goid': 128,
            'm': 200,
            'sched': 48,
            'sched.pc': 56,
            'sched.sp': 48,
            'stackguard': 16,
            'stackbase': 0,
        }

    def __init__(self):
        # first, fetch the number of active goroutines
        self.__allglen = int(str(gdb.parse_and_eval("&{uint64}'runtime.allglen'")), 16)
        # print("found allglen = {0}".format(self.__allglen))

        # get the next address in the array
        s = "&*{uint64}(&'runtime.allg')"
        self.__allg = int(gdb.parse_and_eval(s))
        # print("found allg = {0}".format(hex(self.__allg)))

    def fetch(self):
        if self.__position >= self.__allglen:
            return None

        s = "&*{uint64}(" + "{0}+{1})".format(self.__allg, self.__position*8)
        p = int(gdb.parse_and_eval(s))
        self.__position += 1
        return p

    def Status(self, a):
        s = "&*{int16}(" + "{0}+{1})".format(a, self.__offsets['status'])
        return int(gdb.parse_and_eval(s))

    def WaitReason(self, a):
        s = "&*{int64}(" + "{0}+{1})".format(a, self.__offsets['waitreason'])
        x = int(gdb.parse_and_eval(s))
        s = "&{int8}" + "{0}".format(x)
        return str(gdb.parse_and_eval(s))

    def Goid(self, a):
        s = "&*{int64}(" + "{0}+{1})".format(a, self.__offsets['goid'])
        return int(gdb.parse_and_eval(s))

    def M(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['m'])
        return int(gdb.parse_and_eval(s))

    def Pc(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['sched.pc'])
        return int(gdb.parse_and_eval(s))

    def Sp(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['sched.sp'])
        return int(gdb.parse_and_eval(s))

    def Stackguard(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['stackguard'])
        return int(gdb.parse_and_eval(s))

    def Stackbase(self, a):
        s = "&*{uint64}(" + "{0}+{1})".format(a, self.__offsets['stackbase'])
        return int(gdb.parse_and_eval(s))


class GoroutinesCmd(gdb.Command):
	"List all goroutines."
	__allg = None

	def __init__(self):
		gdb.Command.__init__(self, "info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)

	def invoke(self, _arg, _from_tty):
		self.__allg = Allg()
		while True:
			ptr = self.__allg.fetch()
			# print("fetched ptr = {0}".format(hex(ptr)))
			if not ptr:
				break

			st = self.__allg.Status(ptr)
			# print("status is {0}".format(st))
			w = self.__allg.WaitReason(ptr)
			# print("waitreason is {0}".format(w))
			#if st == 6:  # 'gdead'
			    #print("skipping over dead goroutine")
			    #continue

			s = ' '
			m = self.__allg.M(ptr)
			if m:
				s = '*'

			# if the status isn't "waiting" then the waitreason doesn' tmatter
			if st != 4:
				w = ''
			w2 = w.split('"')
			if len(w2) > 1:
				w = """waitreason="{0}\"""".format(w2[len(w2) - 2])

			pc = self.__allg.Pc(ptr)
			# print("pc is {0}".format(pc))
                        blk = gdb.block_for_pc(pc)
                        # print("blk is {0}".format(blk))
                        goid = self.__allg.Goid(ptr)
                        a = "fname={0} faddr={1}".format(blk.function, hex(pc))
                        print(s, goid, "{0:8s}".format(sts[st]), a, "&g={0}".format(hex(ptr)), w)

The __offsets in Allg was calculated from go’s runtime zruntime_defs_darwin_amd64.go.

 123 type g struct {
 124 ▸ stack        stack          // 0, +16
 125 ▸ stackguard0  uintptr        // 16
 126 ▸ stackguard1  uintptr        // 24
 127 ▸ _panic       *_panic        // 32
 128 ▸ _defer       *_defer        // 40
 129 ▸ sched        gobuf          // 48, +48
 130 ▸ syscallsp    uintptr        // 96
 131 ▸ syscallpc    uintptr        // 104
 132 ▸ param        unsafe.Pointer // 112
 133 ▸ atomicstatus uint32         // 120, padding +4
 134 ▸ goid         int64          // 128
 135 ▸ waitsince    int64          // 136
 136 ▸ waitreason   string         // 144, +16
 137 ▸ schedlink    *g             // 160
 138 ▸ issystem     bool           // 168
 139 ▸ preempt      bool           // 172
 140 ▸ paniconfault bool           //176
 141 ▸ preemptscan  bool           //180
 142 ▸ gcworkdone   bool           //184
 143 ▸ throwsplit   bool           //188
 144 ▸ raceignore   int8           //192, padding +7
 145 ▸ m            *m             //200

Leave a Reply

Your email address will not be published. Required fields are marked *