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