github.com/aloncn/graphics-go@v0.0.1/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 It returns a touple of gdv.Value's representing the stack pointer 408 and program counter pointer for the goroutine. 409 410 @param int goid 411 412 @return tuple (gdb.Value, gdb.Value) 413 """ 414 vp = gdb.lookup_type('void').pointer() 415 for ptr in SliceValue(gdb.parse_and_eval("'runtime.allgs'")): 416 if ptr['atomicstatus'] == 6: # 'gdead' 417 continue 418 if ptr['goid'] == goid: 419 return (ptr['sched'][x].cast(vp) for x in ('pc', 'sp')) 420 return None, None 421 422 423 class GoroutineCmd(gdb.Command): 424 """Execute gdb command in the context of goroutine <goid>. 425 426 Switch PC and SP to the ones in the goroutine's G structure, 427 execute an arbitrary gdb command, and restore PC and SP. 428 429 Usage: (gdb) goroutine <goid> <gdbcmd> 430 431 Note that it is ill-defined to modify state in the context of a goroutine. 432 Restrict yourself to inspecting values. 433 """ 434 435 def __init__(self): 436 gdb.Command.__init__(self, "goroutine", gdb.COMMAND_STACK, gdb.COMPLETE_NONE) 437 438 def invoke(self, arg, _from_tty): 439 goid, cmd = arg.split(None, 1) 440 goid = gdb.parse_and_eval(goid) 441 pc, sp = find_goroutine(int(goid)) 442 if not pc: 443 print("No such goroutine: ", goid) 444 return 445 try: 446 #python3 / newer versions of gdb 447 pc = int(pc) 448 except gdb.error: 449 pc = int(str(pc).split(None, 1)[0], 16) 450 save_frame = gdb.selected_frame() 451 gdb.parse_and_eval('$save_pc = $pc') 452 gdb.parse_and_eval('$save_sp = $sp') 453 gdb.parse_and_eval('$pc = {0}'.format(str(pc))) 454 gdb.parse_and_eval('$sp = {0}'.format(str(sp))) 455 try: 456 gdb.execute(cmd) 457 finally: 458 gdb.parse_and_eval('$pc = $save_pc') 459 gdb.parse_and_eval('$sp = $save_sp') 460 save_frame.select() 461 462 463 class GoIfaceCmd(gdb.Command): 464 "Print Static and dynamic interface types" 465 466 def __init__(self): 467 gdb.Command.__init__(self, "iface", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL) 468 469 def invoke(self, arg, _from_tty): 470 for obj in gdb.string_to_argv(arg): 471 try: 472 #TODO fix quoting for qualified variable names 473 obj = gdb.parse_and_eval(str(obj)) 474 except Exception as e: 475 print("Can't parse ", obj, ": ", e) 476 continue 477 478 if obj['data'] == 0: 479 dtype = "nil" 480 else: 481 dtype = iface_dtype(obj) 482 483 if dtype is None: 484 print("Not an interface: ", obj.type) 485 continue 486 487 print("{0}: {1}".format(obj.type, dtype)) 488 489 # TODO: print interface's methods and dynamic type's func pointers thereof. 490 #rsc: "to find the number of entries in the itab's Fn field look at 491 # itab.inter->numMethods 492 # i am sure i have the names wrong but look at the interface type 493 # and its method count" 494 # so Itype will start with a commontype which has kind = interface 495 496 # 497 # Register all convenience functions and CLI commands 498 # 499 GoLenFunc() 500 GoCapFunc() 501 DTypeFunc() 502 GoroutinesCmd() 503 GoroutineCmd() 504 GoIfaceCmd()