github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/sait/usr/local/go/src/runtime/runtime-gdb.py (about)

     1  # Copyright 2010 The Go Authors. All rights reserved.
     2  # Use of this source code is governed by a BSD-style
     3  # license that can be found in the LICENSE file.
     4  
     5  """GDB Pretty printers and convenience functions for Go's runtime structures.
     6  
     7  This script is loaded by GDB when it finds a .debug_gdb_scripts
     8  section in the compiled binary. The [68]l linkers emit this with a
     9  path to this file based on the path to the runtime package.
    10  """
    11  
    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  
    18  from __future__ import print_function
    19  import re
    20  import sys
    21  
    22  print("Loading Go Runtime support.", file=sys.stderr)
    23  #http://python3porting.com/differences.html
    24  if sys.version > '3':
    25  	xrange = range
    26  # allow to manually reload while developing
    27  goobjfile = gdb.current_objfile() or gdb.objfiles()[0]
    28  goobjfile.pretty_printers = []
    29  
    30  #
    31  #  Value wrappers
    32  #
    33  
    34  class SliceValue:
    35  	"Wrapper for slice values."
    36  
    37  	def __init__(self, val):
    38  		self.val = val
    39  
    40  	@property
    41  	def len(self):
    42  		return int(self.val['len'])
    43  
    44  	@property
    45  	def cap(self):
    46  		return int(self.val['cap'])
    47  
    48  	def __getitem__(self, i):
    49  		if i < 0 or i >= self.len:
    50  			raise IndexError(i)
    51  		ptr = self.val["array"]
    52  		return (ptr + i).dereference()
    53  
    54  
    55  #
    56  #  Pretty Printers
    57  #
    58  
    59  
    60  class StringTypePrinter:
    61  	"Pretty print Go strings."
    62  
    63  	pattern = re.compile(r'^struct string( \*)?$')
    64  
    65  	def __init__(self, val):
    66  		self.val = val
    67  
    68  	def display_hint(self):
    69  		return 'string'
    70  
    71  	def to_string(self):
    72  		l = int(self.val['len'])
    73  		return self.val['str'].string("utf-8", "ignore", l)
    74  
    75  
    76  class SliceTypePrinter:
    77  	"Pretty print slices."
    78  
    79  	pattern = re.compile(r'^struct \[\]')
    80  
    81  	def __init__(self, val):
    82  		self.val = val
    83  
    84  	def display_hint(self):
    85  		return 'array'
    86  
    87  	def to_string(self):
    88  		return str(self.val.type)[6:]  # skip 'struct '
    89  
    90  	def children(self):
    91  		sval = SliceValue(self.val)
    92  		if sval.len > sval.cap:
    93  			return
    94  		for idx, item in enumerate(sval):
    95  			yield ('[{0}]'.format(idx), item)
    96  
    97  
    98  class MapTypePrinter:
    99  	"""Pretty print map[K]V types.
   100  
   101  	Map-typed go variables are really pointers. dereference them in gdb
   102  	to inspect their contents with this pretty printer.
   103  	"""
   104  
   105  	pattern = re.compile(r'^map\[.*\].*$')
   106  
   107  	def __init__(self, val):
   108  		self.val = val
   109  
   110  	def display_hint(self):
   111  		return 'map'
   112  
   113  	def to_string(self):
   114  		return str(self.val.type)
   115  
   116  	def children(self):
   117  		B = self.val['B']
   118  		buckets = self.val['buckets']
   119  		oldbuckets = self.val['oldbuckets']
   120  		flags = self.val['flags']
   121  		inttype = self.val['hash0'].type
   122  		cnt = 0
   123  		for bucket in xrange(2 ** int(B)):
   124  			bp = buckets + bucket
   125  			if oldbuckets:
   126  				oldbucket = bucket & (2 ** (B - 1) - 1)
   127  				oldbp = oldbuckets + oldbucket
   128  				oldb = oldbp.dereference()
   129  				if (oldb['overflow'].cast(inttype) & 1) == 0:  # old bucket not evacuated yet
   130  					if bucket >= 2 ** (B - 1):
   131  						continue    # already did old bucket
   132  					bp = oldbp
   133  			while bp:
   134  				b = bp.dereference()
   135  				for i in xrange(8):
   136  					if b['tophash'][i] != 0:
   137  						k = b['keys'][i]
   138  						v = b['values'][i]
   139  						if flags & 1:
   140  							k = k.dereference()
   141  						if flags & 2:
   142  							v = v.dereference()
   143  						yield str(cnt), k
   144  						yield str(cnt + 1), v
   145  						cnt += 2
   146  				bp = b['overflow']
   147  
   148  
   149  class ChanTypePrinter:
   150  	"""Pretty print chan[T] types.
   151  
   152  	Chan-typed go variables are really pointers. dereference them in gdb
   153  	to inspect their contents with this pretty printer.
   154  	"""
   155  
   156  	pattern = re.compile(r'^struct hchan<.*>$')
   157  
   158  	def __init__(self, val):
   159  		self.val = val
   160  
   161  	def display_hint(self):
   162  		return 'array'
   163  
   164  	def to_string(self):
   165  		return str(self.val.type)
   166  
   167  	def children(self):
   168  		# see chan.c chanbuf(). et is the type stolen from hchan<T>::recvq->first->elem
   169  		et = [x.type for x in self.val['recvq']['first'].type.target().fields() if x.name == 'elem'][0]
   170  		ptr = (self.val.address + 1).cast(et.pointer())
   171  		for i in range(self.val["qcount"]):
   172  			j = (self.val["recvx"] + i) % self.val["dataqsiz"]
   173  			yield ('[{0}]'.format(i), (ptr + j).dereference())
   174  
   175  
   176  #
   177  #  Register all the *Printer classes above.
   178  #
   179  
   180  def makematcher(klass):
   181  	def matcher(val):
   182  		try:
   183  			if klass.pattern.match(str(val.type)):
   184  				return klass(val)
   185  		except Exception:
   186  			pass
   187  	return matcher
   188  
   189  goobjfile.pretty_printers.extend([makematcher(var) for var in vars().values() if hasattr(var, 'pattern')])
   190  
   191  #
   192  #  For reference, this is what we're trying to do:
   193  #  eface: p *(*(struct 'runtime.rtype'*)'main.e'->type_->data)->string
   194  #  iface: p *(*(struct 'runtime.rtype'*)'main.s'->tab->Type->data)->string
   195  #
   196  # interface types can't be recognized by their name, instead we check
   197  # if they have the expected fields.  Unfortunately the mapping of
   198  # fields to python attributes in gdb.py isn't complete: you can't test
   199  # for presence other than by trapping.
   200  
   201  
   202  def is_iface(val):
   203  	try:
   204  		return str(val['tab'].type) == "struct runtime.itab *" and str(val['data'].type) == "void *"
   205  	except gdb.error:
   206  		pass
   207  
   208  
   209  def is_eface(val):
   210  	try:
   211  		return str(val['_type'].type) == "struct runtime._type *" and str(val['data'].type) == "void *"
   212  	except gdb.error:
   213  		pass
   214  
   215  
   216  def lookup_type(name):
   217  	try:
   218  		return gdb.lookup_type(name)
   219  	except gdb.error:
   220  		pass
   221  	try:
   222  		return gdb.lookup_type('struct ' + name)
   223  	except gdb.error:
   224  		pass
   225  	try:
   226  		return gdb.lookup_type('struct ' + name[1:]).pointer()
   227  	except gdb.error:
   228  		pass
   229  
   230  
   231  def iface_commontype(obj):
   232  	if is_iface(obj):
   233  		go_type_ptr = obj['tab']['_type']
   234  	elif is_eface(obj):
   235  		go_type_ptr = obj['_type']
   236  	else:
   237  		return
   238  
   239  	return go_type_ptr.cast(gdb.lookup_type("struct reflect.rtype").pointer()).dereference()
   240  
   241  
   242  def iface_dtype(obj):
   243  	"Decode type of the data field of an eface or iface struct."
   244  	# known issue: dtype_name decoded from runtime.rtype is "nested.Foo"
   245  	# but the dwarf table lists it as "full/path/to/nested.Foo"
   246  
   247  	dynamic_go_type = iface_commontype(obj)
   248  	if dynamic_go_type is None:
   249  		return
   250  	dtype_name = dynamic_go_type['string'].dereference()['str'].string()
   251  
   252  	dynamic_gdb_type = lookup_type(dtype_name)
   253  	if dynamic_gdb_type is None:
   254  		return
   255  
   256  	type_size = int(dynamic_go_type['size'])
   257  	uintptr_size = int(dynamic_go_type['size'].type.sizeof)	 # size is itself an uintptr
   258  	if type_size > uintptr_size:
   259  			dynamic_gdb_type = dynamic_gdb_type.pointer()
   260  
   261  	return dynamic_gdb_type
   262  
   263  
   264  def iface_dtype_name(obj):
   265  	"Decode type name of the data field of an eface or iface struct."
   266  
   267  	dynamic_go_type = iface_commontype(obj)
   268  	if dynamic_go_type is None:
   269  		return
   270  	return dynamic_go_type['string'].dereference()['str'].string()
   271  
   272  
   273  class IfacePrinter:
   274  	"""Pretty print interface values
   275  
   276  	Casts the data field to the appropriate dynamic type."""
   277  
   278  	def __init__(self, val):
   279  		self.val = val
   280  
   281  	def display_hint(self):
   282  		return 'string'
   283  
   284  	def to_string(self):
   285  		if self.val['data'] == 0:
   286  			return 0x0
   287  		try:
   288  			dtype = iface_dtype(self.val)
   289  		except Exception:
   290  			return "<bad dynamic type>"
   291  
   292  		if dtype is None:  # trouble looking up, print something reasonable
   293  			return "({0}){0}".format(iface_dtype_name(self.val), self.val['data'])
   294  
   295  		try:
   296  			return self.val['data'].cast(dtype).dereference()
   297  		except Exception:
   298  			pass
   299  		return self.val['data'].cast(dtype)
   300  
   301  
   302  def ifacematcher(val):
   303  	if is_iface(val) or is_eface(val):
   304  		return IfacePrinter(val)
   305  
   306  goobjfile.pretty_printers.append(ifacematcher)
   307  
   308  #
   309  #  Convenience Functions
   310  #
   311  
   312  
   313  class GoLenFunc(gdb.Function):
   314  	"Length of strings, slices, maps or channels"
   315  
   316  	how = ((StringTypePrinter, 'len'), (SliceTypePrinter, 'len'), (MapTypePrinter, 'count'), (ChanTypePrinter, 'qcount'))
   317  
   318  	def __init__(self):
   319  		gdb.Function.__init__(self, "len")
   320  
   321  	def invoke(self, obj):
   322  		typename = str(obj.type)
   323  		for klass, fld in self.how:
   324  			if klass.pattern.match(typename):
   325  				return obj[fld]
   326  
   327  
   328  class GoCapFunc(gdb.Function):
   329  	"Capacity of slices or channels"
   330  
   331  	how = ((SliceTypePrinter, 'cap'), (ChanTypePrinter, 'dataqsiz'))
   332  
   333  	def __init__(self):
   334  		gdb.Function.__init__(self, "cap")
   335  
   336  	def invoke(self, obj):
   337  		typename = str(obj.type)
   338  		for klass, fld in self.how:
   339  			if klass.pattern.match(typename):
   340  				return obj[fld]
   341  
   342  
   343  class DTypeFunc(gdb.Function):
   344  	"""Cast Interface values to their dynamic type.
   345  
   346  	For non-interface types this behaves as the identity operation.
   347  	"""
   348  
   349  	def __init__(self):
   350  		gdb.Function.__init__(self, "dtype")
   351  
   352  	def invoke(self, obj):
   353  		try:
   354  			return obj['data'].cast(iface_dtype(obj))
   355  		except gdb.error:
   356  			pass
   357  		return obj
   358  
   359  #
   360  #  Commands
   361  #
   362  
   363  sts = ('idle', 'runnable', 'running', 'syscall', 'waiting', 'moribund', 'dead', 'recovery')
   364  
   365  
   366  def linked_list(ptr, linkfield):
   367  	while ptr:
   368  		yield ptr
   369  		ptr = ptr[linkfield]
   370  
   371  
   372  class GoroutinesCmd(gdb.Command):
   373  	"List all goroutines."
   374  
   375  	def __init__(self):
   376  		gdb.Command.__init__(self, "info goroutines", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
   377  
   378  	def invoke(self, _arg, _from_tty):
   379  		# args = gdb.string_to_argv(arg)
   380  		vp = gdb.lookup_type('void').pointer()
   381  		for ptr in SliceValue(gdb.parse_and_eval("'runtime.allgs'")):
   382  			if ptr['atomicstatus'] == 6:  # 'gdead'
   383  				continue
   384  			s = ' '
   385  			if ptr['m']:
   386  				s = '*'
   387  			pc = ptr['sched']['pc'].cast(vp)
   388  			# python2 will not cast pc (type void*) to an int cleanly
   389  			# instead python2 and python3 work with the hex string representation
   390  			# of the void pointer which we can parse back into an int.
   391  			# int(pc) will not work.
   392  			try:
   393  				#python3 / newer versions of gdb
   394  				pc = int(pc)
   395  			except gdb.error:
   396  				# str(pc) can return things like
   397  				# "0x429d6c <runtime.gopark+284>", so
   398  				# chop at first space.
   399  				pc = int(str(pc).split(None, 1)[0], 16)
   400  			blk = gdb.block_for_pc(pc)
   401  			print(s, ptr['goid'], "{0:8s}".format(sts[int(ptr['atomicstatus'])]), blk.function)
   402  
   403  
   404  def find_goroutine(goid):
   405  	"""
   406  	find_goroutine attempts to find the goroutine identified by goid
   407  	and returns a pointer to the goroutine info.
   408  
   409  	@param int goid
   410  
   411  	@return ptr
   412  	"""
   413  	vp = gdb.lookup_type('void').pointer()
   414  	for ptr in SliceValue(gdb.parse_and_eval("'runtime.allgs'")):
   415  		if ptr['atomicstatus'] == 6:  # 'gdead'
   416  			continue
   417  		if ptr['goid'] == goid:
   418  			return ptr
   419  	return None
   420  
   421  def goroutine_info(ptr):
   422  	'''
   423  	Given a pointer to goroutine info clean it up a bit
   424  	and return the interesting info in a dict.
   425  	'''
   426  	gorinfo = {}
   427  	gorinfo['goid'] = ptr['goid']
   428  	gorinfo['atomicstatus'] = sts[int(ptr['atomicstatus'])]
   429  	if gorinfo['atomicstatus'] == 'gdead':
   430  		return gorinfo
   431  
   432  	vp = gdb.lookup_type('void').pointer()
   433  	gorinfo['pc_as_str'] = str(ptr['sched']['pc'].cast(vp))
   434  	gorinfo['sp_as_str'] = str(ptr['sched']['sp'].cast(vp))
   435  
   436  	# str(pc) can return things like
   437  	# "0x429d6c <runtime.gopark+284>", so
   438  	# chop at first space.
   439  	gorinfo['pc_as_int'] = int(gorinfo['pc_as_str'].split(None, 1)[0], 16)
   440  	gorinfo['sp_as_int'] = int(gorinfo['sp_as_str'], 16)
   441  
   442  	return gorinfo
   443  
   444  class GoroutineCmd(gdb.Command):
   445  	"""Execute a gdb command in the context of goroutine <goid>.
   446  
   447  	Switch PC and SP to the ones in the goroutine's G structure,
   448  	execute an arbitrary gdb command, and restore PC and SP.
   449  
   450  	Usage: (gdb) goroutine <goid> <gdbcmd>
   451  
   452  	Use goid 0 to invoke the command on all go routines.
   453  
   454  	Note that it is ill-defined to modify state in the context of a goroutine.
   455  	Restrict yourself to inspecting values.
   456  	"""
   457  
   458  	def __init__(self):
   459  		gdb.Command.__init__(self, "goroutine", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
   460  
   461  	def invoke(self, arg, _from_tty):
   462  		goid, cmd = arg.split(None, 1)
   463  		goid = gdb.parse_and_eval(goid)
   464  
   465  		if goid == 0:
   466  			goptr_list = SliceValue(gdb.parse_and_eval("'runtime.allgs'"))
   467  		else:
   468  			ptr = find_goroutine(goid)
   469  			if ptr is None:
   470  				print("No such goroutine: ", goid)
   471  				return
   472  			goptr_list = [ ptr ]
   473  
   474  		for ptr in goptr_list:
   475  			gor = goroutine_info(ptr)
   476  			if gor['atomicstatus'] == 'gdead':
   477  				continue
   478  
   479  			print("\ngoroutine %d:" % (gor['goid']))
   480  			if gor['sp_as_int'] == 0:
   481  				print("#0  %s -- stack trace unavailable (goroutine status: %s)" %
   482  				      (gor['pc_as_str'], gor['atomicstatus']))
   483  				if gor['atomicstatus'] == 'running':
   484                                          print("Try checking per thread stacks, i.e. 'thread apply all backtrace'")
   485  				continue
   486  
   487  			save_frame = gdb.selected_frame()
   488  			gdb.parse_and_eval('$save_sp = $sp')
   489  			gdb.parse_and_eval('$save_pc = $pc')
   490  			gdb.parse_and_eval('$sp = {0}'.format(str(gor['sp_as_int'])))
   491  			gdb.parse_and_eval('$pc = {0}'.format(str(gor['pc_as_int'])))
   492  			try:
   493  				gdb.execute(cmd)
   494  			finally:
   495  				gdb.parse_and_eval('$sp = $save_sp')
   496  				gdb.parse_and_eval('$pc = $save_pc')
   497  				save_frame.select()
   498  
   499  
   500  class GoIfaceCmd(gdb.Command):
   501  	"Print Static and dynamic interface types"
   502  
   503  	def __init__(self):
   504  		gdb.Command.__init__(self, "iface", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
   505  
   506  	def invoke(self, arg, _from_tty):
   507  		for obj in gdb.string_to_argv(arg):
   508  			try:
   509  				#TODO fix quoting for qualified variable names
   510  				obj = gdb.parse_and_eval(str(obj))
   511  			except Exception as e:
   512  				print("Can't parse ", obj, ": ", e)
   513  				continue
   514  
   515  			if obj['data'] == 0:
   516  				dtype = "nil"
   517  			else:
   518  				dtype = iface_dtype(obj)
   519  
   520  			if dtype is None:
   521  				print("Not an interface: ", obj.type)
   522  				continue
   523  
   524  			print("{0}: {1}".format(obj.type, dtype))
   525  
   526  # TODO: print interface's methods and dynamic type's func pointers thereof.
   527  #rsc: "to find the number of entries in the itab's Fn field look at
   528  # itab.inter->numMethods
   529  # i am sure i have the names wrong but look at the interface type
   530  # and its method count"
   531  # so Itype will start with a commontype which has kind = interface
   532  
   533  #
   534  # Register all convenience functions and CLI commands
   535  #
   536  GoLenFunc()
   537  GoCapFunc()
   538  DTypeFunc()
   539  GoroutinesCmd()
   540  GoroutineCmd()
   541  GoIfaceCmd()