Skip to content

Commit 7a4ce23

Browse files
author
Luuk van Dijk
committed
[68]l and runtime: GDB support for interfaces and goroutines.
R=rsc CC=golang-dev https://golang.org/cl/3477041
1 parent d96685e commit 7a4ce23

File tree

3 files changed

+280
-34
lines changed

3 files changed

+280
-34
lines changed

src/cmd/ld/dwarf.c

+28-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// ptype struct '[]uint8' and qualifiers need to be quoted away
1212
// - lexical scoping is lost, so gdb gets confused as to which 'main.i' you mean.
1313
// - file:line info for variables
14+
// - make strings a typedef so prettyprinters can see the underlying string type
1415
//
1516
#include "l.h"
1617
#include "lib.h"
@@ -280,8 +281,9 @@ static struct DWAbbrev {
280281

281282
/* IFACETYPE */
282283
{
283-
DW_TAG_interface_type, DW_CHILDREN_no,
284+
DW_TAG_typedef, DW_CHILDREN_yes,
284285
DW_AT_name, DW_FORM_string,
286+
DW_AT_type, DW_FORM_ref_addr,
285287
0, 0
286288
},
287289

@@ -957,6 +959,14 @@ decodetype_structfieldoffs(Sym *s, int i)
957959
return decode_inuxi(s->p + 10*PtrSize + 0x10 + i*5*PtrSize, 4); // 0x38 / 0x60
958960
}
959961

962+
// InterfaceTYpe.methods.len
963+
static vlong
964+
decodetype_ifacemethodcount(Sym *s)
965+
{
966+
return decode_inuxi(s->p + 6*PtrSize + 8, 4);
967+
}
968+
969+
960970
// Fake attributes for slices, maps and channel
961971
enum {
962972
DW_AT_internal_elem_type = 250, // channels and slices
@@ -1095,6 +1105,12 @@ defgotype(Sym *gotype)
10951105
case KindInterface:
10961106
die = newdie(&dwtypes, DW_ABRV_IFACETYPE, name);
10971107
newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, bytesize, 0);
1108+
nfields = decodetype_ifacemethodcount(gotype);
1109+
if (nfields == 0)
1110+
s = lookup("type.runtime.eface", 0);
1111+
else
1112+
s = lookup("type.runtime.iface", 0);
1113+
newrefattr(die, DW_AT_type, defgotype(s));
10981114
break;
10991115

11001116
case KindMap:
@@ -1425,8 +1441,13 @@ defdwsymb(Sym* sym, char *s, int t, vlong v, vlong size, int ver, Sym *gotype)
14251441
return;
14261442
if (strncmp(s, "string.", 7) == 0)
14271443
return;
1428-
if (strncmp(s, "type.", 5) == 0)
1444+
if (strncmp(s, "type._.", 7) == 0)
1445+
return;
1446+
1447+
if (strncmp(s, "type.", 5) == 0) {
1448+
defgotype(sym);
14291449
return;
1450+
}
14301451

14311452
dv = nil;
14321453

@@ -2291,6 +2312,11 @@ dwarfemitdebugsections(void)
22912312
newattr(die, DW_AT_encoding, DW_CLS_CONSTANT, DW_ATE_unsigned, 0);
22922313
newattr(die, DW_AT_byte_size, DW_CLS_CONSTANT, PtrSize, 0);
22932314

2315+
// Needed by the prettyprinter code for interface inspection.
2316+
defgotype(lookup("type.runtime.commonType",0));
2317+
defgotype(lookup("type.runtime.InterfaceType",0));
2318+
defgotype(lookup("type.runtime.itab",0));
2319+
22942320
genasmsym(defdwsymb);
22952321

22962322
writeabbrev();

src/pkg/runtime/runtime-gdb.py

+237-13
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,20 @@
99
path to this file based on the path to the runtime package.
1010
"""
1111

12+
# Known issues:
13+
# - pretty printing only works for the 'native' strings. E.g. 'type
14+
# foo string' will make foo a plain struct in the eyes of gdb,
15+
# circumventing the pretty print triggering.
16+
# -
17+
1218
import sys, re
1319

1420
print >>sys.stderr, "Loading Go Runtime support."
1521

22+
# allow to manually reload while developing
23+
goobjfile = gdb.current_objfile() or gdb.objfiles()[0]
24+
goobjfile.pretty_printers = []
25+
1626
#
1727
# Pretty Printers
1828
#
@@ -95,7 +105,7 @@ def traverse_hash(self, stab):
95105
class ChanTypePrinter:
96106
"""Pretty print chan[T] types.
97107
98-
Map-typed go variables are really pointers. dereference them in gdb
108+
Chan-typed go variables are really pointers. dereference them in gdb
99109
to inspect their contents with this pretty printer.
100110
"""
101111

@@ -117,18 +127,109 @@ def children(self):
117127
ptr = ptr['link']
118128

119129
#
120-
# Register all the *Printer classes
130+
# Register all the *Printer classes above.
121131
#
122132

123133
def makematcher(klass):
124134
def matcher(val):
125135
try:
126-
if klass.pattern.match(str(val.type)): return klass(val)
127-
except: pass
136+
if klass.pattern.match(str(val.type)):
137+
return klass(val)
138+
except:
139+
pass
128140
return matcher
129141

130-
gdb.current_objfile().pretty_printers.extend([makematcher(k) for k in vars().values() if hasattr(k, 'pattern')])
142+
goobjfile.pretty_printers.extend([makematcher(k) for k in vars().values() if hasattr(k, 'pattern')])
131143

144+
#
145+
# For reference, this is what we're trying to do:
146+
# eface: p *(*(struct 'runtime.commonType'*)'main.e'->type_->data)->string
147+
# iface: p *(*(struct 'runtime.commonType'*)'main.s'->tab->Type->data)->string
148+
#
149+
# interface types can't be recognized by their name, instead we check
150+
# if they have the expected fields. Unfortunately the mapping of
151+
# fields to python attributes in gdb.py isn't complete: you can't test
152+
# for presence other than by trapping.
153+
154+
155+
def is_iface(val):
156+
try:
157+
return str(val['tab'].type) == "struct runtime.itab *" \
158+
and str(val['data'].type) == "void *"
159+
except:
160+
pass
161+
162+
def is_eface(val):
163+
try:
164+
return str(val['type_'].type) == "runtime.Type *" \
165+
and str(val['data'].type) == "void *"
166+
except:
167+
pass
168+
169+
def lookup_type(name):
170+
try:
171+
return gdb.lookup_type(name)
172+
except:
173+
pass
174+
try:
175+
return gdb.lookup_type('struct ' + name)
176+
except:
177+
pass
178+
try:
179+
return gdb.lookup_type('struct ' + name[1:]).pointer()
180+
except:
181+
pass
182+
183+
184+
def iface_dtype(obj):
185+
"Decode type of the data field of an eface or iface struct."
186+
187+
if is_iface(obj):
188+
go_type_ptr = obj['tab']['Type']
189+
elif is_eface(obj):
190+
go_type_ptr = obj['type_']
191+
else:
192+
return
193+
194+
ct = gdb.lookup_type("struct runtime.commonType").pointer()
195+
dynamic_go_type = go_type_ptr['data'].cast(ct).dereference()
196+
dtype_name = dynamic_go_type['string'].dereference()['str'].string()
197+
type_size = int(dynamic_go_type['size'])
198+
uintptr_size = int(dynamic_go_type['size'].type.sizeof) # size is itself an uintptr
199+
dynamic_gdb_type = lookup_type(dtype_name)
200+
if type_size > uintptr_size:
201+
dynamic_gdb_type = dynamic_gdb_type.pointer()
202+
return dynamic_gdb_type
203+
204+
205+
class IfacePrinter:
206+
"""Pretty print interface values
207+
208+
Casts the data field to the appropriate dynamic type."""
209+
210+
def __init__(self, val):
211+
self.val = val
212+
213+
def display_hint(self):
214+
return 'string'
215+
216+
def to_string(self):
217+
try:
218+
dtype = iface_dtype(self.val)
219+
except:
220+
return "<bad dynamic type>"
221+
try:
222+
return self.val['data'].cast(dtype).dereference()
223+
except:
224+
pass
225+
return self.val['data'].cast(dtype)
226+
227+
228+
def ifacematcher(val):
229+
if is_iface(val) or is_eface(val):
230+
return IfacePrinter(val)
231+
232+
goobjfile.pretty_printers.append(ifacematcher)
132233

133234
#
134235
# Convenience Functions
@@ -137,35 +238,158 @@ def matcher(val):
137238
class GoLenFunc(gdb.Function):
138239
"Length of strings, slices, maps or channels"
139240

140-
how = ((StringTypePrinter, 'len' ),
141-
(SliceTypePrinter, 'len'),
142-
(MapTypePrinter, 'count'),
143-
(ChanTypePrinter, 'qcount'))
241+
how = ((StringTypePrinter, 'len' ),
242+
(SliceTypePrinter, 'len'),
243+
(MapTypePrinter, 'count'),
244+
(ChanTypePrinter, 'qcount'))
144245

145246
def __init__(self):
146247
super(GoLenFunc, self).__init__("len")
147248

148249
def invoke(self, obj):
149250
typename = str(obj.type)
150-
for klass, fld in self.how:
251+
for klass, fld in self.how:
151252
if klass.pattern.match(typename):
152253
return obj[fld]
153254

154255
class GoCapFunc(gdb.Function):
155256
"Capacity of slices or channels"
156257

157-
how = ((SliceTypePrinter, 'cap'),
158-
(ChanTypePrinter, 'dataqsiz'))
258+
how = ((SliceTypePrinter, 'cap'),
259+
(ChanTypePrinter, 'dataqsiz'))
159260

160261
def __init__(self):
161262
super(GoCapFunc, self).__init__("cap")
162263

163264
def invoke(self, obj):
164265
typename = str(obj.type)
165-
for klass, fld in self.how:
266+
for klass, fld in self.how:
166267
if klass.pattern.match(typename):
167268
return obj[fld]
168269

270+
class DTypeFunc(gdb.Function):
271+
"""Cast Interface values to their dynamic type.
272+
273+
For non-interface types this behaves as the identity operation.
274+
"""
275+
276+
def __init__(self):
277+
super(DTypeFunc, self).__init__("dtype")
278+
279+
def invoke(self, obj):
280+
try:
281+
return obj['data'].cast(iface_dtype(obj))
282+
except:
283+
pass
284+
return obj
285+
286+
#
287+
# Commands
288+
#
289+
290+
sts = ( 'idle', 'runnable', 'running', 'syscall', 'waiting', 'moribund', 'dead', 'recovery')
291+
292+
def linked_list(ptr, linkfield):
293+
while ptr:
294+
yield ptr
295+
ptr = ptr[linkfield]
296+
297+
298+
class GoroutinesCmd(gdb.Command):
299+
"List all goroutines."
300+
301+
def __init__(self):
302+
super(GoroutinesCmd, self).__init__("info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
303+
304+
def invoke(self, arg, from_tty):
305+
# args = gdb.string_to_argv(arg)
306+
vp = gdb.lookup_type('void').pointer()
307+
for ptr in linked_list(gdb.parse_and_eval("'runtime.allg'"), 'alllink'):
308+
if ptr['status'] == 6: # 'gdead'
309+
continue
310+
m = ptr['m']
311+
s = ' '
312+
if m:
313+
pc = m['sched']['pc'].cast(vp)
314+
sp = m['sched']['sp'].cast(vp)
315+
s = '*'
316+
else:
317+
pc = ptr['sched']['pc'].cast(vp)
318+
sp = ptr['sched']['sp'].cast(vp)
319+
blk = gdb.block_for_pc(long((pc)))
320+
print s, ptr['goid'], "%8s" % sts[long((ptr['status']))], blk.function
321+
322+
def find_goroutine(goid):
323+
vp = gdb.lookup_type('void').pointer()
324+
for ptr in linked_list(gdb.parse_and_eval("'runtime.allg'"), 'alllink'):
325+
if ptr['status'] == 6: # 'gdead'
326+
continue
327+
if ptr['goid'] == goid:
328+
return [(ptr['m'] or ptr)['sched'][x].cast(vp) for x in 'pc', 'sp']
329+
return None, None
330+
331+
332+
class GoroutineCmd(gdb.Command):
333+
"""Execute gdb command in the context of goroutine <goid>.
334+
335+
Switch PC and SP to the ones in the goroutine's G structure,
336+
execute an arbitrary gdb command, and restore PC and SP.
337+
338+
Usage: (gdb) goroutine <goid> <gdbcmd>
339+
340+
Note that it is ill-defined to modify state in the context of a goroutine.
341+
Restrict yourself to inspecting values.
342+
"""
343+
344+
def __init__(self):
345+
super(GoroutineCmd, self).__init__("goroutine", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
346+
347+
def invoke(self, arg, from_tty):
348+
goid, cmd = arg.split(None, 1)
349+
pc, sp = find_goroutine(int(goid))
350+
if not pc:
351+
print "No such goroutine: ", goid
352+
return
353+
save_frame = gdb.selected_frame()
354+
gdb.parse_and_eval('$save_pc = $pc')
355+
gdb.parse_and_eval('$save_sp = $sp')
356+
gdb.parse_and_eval('$pc = 0x%x' % long(pc))
357+
gdb.parse_and_eval('$sp = 0x%x' % long(sp))
358+
try:
359+
gdb.execute(cmd)
360+
finally:
361+
gdb.parse_and_eval('$pc = $save_pc')
362+
gdb.parse_and_eval('$sp = $save_sp')
363+
save_frame.select()
364+
365+
366+
class GoIfaceCmd(gdb.Command):
367+
"Print Static and dynamic interface types"
368+
369+
def __init__(self):
370+
super(GoIfaceCmd, self).__init__("iface", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
371+
372+
def invoke(self, arg, from_tty):
373+
for obj in gdb.string_to_argv(arg):
374+
try:
375+
#TODO fix quoting for qualified variable names
376+
obj = gdb.parse_and_eval("%s" % obj)
377+
except Exception, e:
378+
print "Can't parse ", obj, ": ", e
379+
continue
380+
381+
dtype = iface_dtype(obj)
382+
if not dtype:
383+
print "Not an interface: ", obj.type
384+
continue
385+
386+
print "%s: %s" % (obj.type, dtype)
387+
388+
# TODO: print interface's methods and dynamic type's func pointers thereof.
389+
#rsc: "to find the number of entries in the itab's Fn field look at itab.inter->numMethods
390+
#i am sure i have the names wrong but look at the interface type and its method count"
391+
# so Itype will start with a commontype which has kind = interface
392+
169393
#
170394
# Register all convience functions and CLI commands
171395
#

0 commit comments

Comments
 (0)