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