1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this file, 3# You can obtain one at http://mozilla.org/MPL/2.0/. 4 5# mozilla/unwind.py --- unwinder and frame filter for SpiderMonkey 6 7import gdb 8import gdb.types 9from gdb.FrameDecorator import FrameDecorator 10import platform 11 12# For ease of use in Python 2, we use "long" instead of "int" 13# everywhere. 14try: 15 long 16except NameError: 17 long = int 18 19# The Python 3 |map| built-in works lazily, but in Python 2 we need 20# itertools.imap to get this. 21try: 22 from itertools import imap 23except ImportError: 24 imap = map 25 26_have_unwinder = True 27try: 28 from gdb.unwinder import Unwinder 29except ImportError: 30 _have_unwinder = False 31 # We need something here; it doesn't matter what as no unwinder 32 # will ever be instantiated. 33 Unwinder = object 34 35 36def debug(something): 37 # print("@@ " + something) 38 pass 39 40 41# Maps frametype enum base names to corresponding class. 42SizeOfFramePrefix = { 43 'FrameType::IonJS': 'ExitFrameLayout', 44 'FrameType::BaselineJS': 'JitFrameLayout', 45 'FrameType::BaselineStub': 'BaselineStubFrameLayout', 46 'FrameType::IonStub': 'JitStubFrameLayout', 47 'FrameType::CppToJSJit': 'JitFrameLayout', 48 'FrameType::WasmToJSJit': 'JitFrameLayout', 49 'FrameType::JSJitToWasm': 'JitFrameLayout', 50 'FrameType::Rectifier': 'RectifierFrameLayout', 51 'FrameType::IonAccessorIC': 'IonAccessorICFrameLayout', 52 'FrameType::IonICCall': 'IonICCallFrameLayout', 53 'FrameType::Exit': 'ExitFrameLayout', 54 'FrameType::Bailout': 'JitFrameLayout', 55} 56 57 58# We cannot have semi-colon as identifier names, so use a colon instead, 59# and forward the name resolution to the type cache class. 60class UnwinderTypeCacheFrameType(object): 61 def __init__(self, tc): 62 self.tc = tc 63 64 def __getattr__(self, name): 65 return self.tc.__getattr__("FrameType::" + name) 66 67 68class UnwinderTypeCache(object): 69 # All types and symbols that we need are attached to an object that we 70 # can dispose of as needed. 71 72 def __init__(self): 73 self.d = None 74 self.frame_enum_names = {} 75 self.frame_class_types = {} 76 77 # We take this bizarre approach to defer trying to look up any 78 # symbols until absolutely needed. Without this, the loading 79 # approach taken by the gdb-tests would cause spurious exceptions. 80 def __getattr__(self, name): 81 if self.d is None: 82 self.initialize() 83 if name == "frame_type": 84 return UnwinderTypeCacheFrameType(self) 85 return self.d[name] 86 87 def value(self, name): 88 return long(gdb.lookup_symbol(name)[0].value()) 89 90 def jit_value(self, name): 91 return self.value('js::jit::' + name) 92 93 def initialize(self): 94 self.d = {} 95 self.d['FRAMETYPE_MASK'] = (1 << self.jit_value('FRAMETYPE_BITS')) - 1 96 self.d['FRAMESIZE_SHIFT'] = self.jit_value('FRAMESIZE_SHIFT') 97 self.d['FRAME_HEADER_SIZE_SHIFT'] = self.jit_value('FRAME_HEADER_SIZE_SHIFT') 98 self.d['FRAME_HEADER_SIZE_MASK'] = self.jit_value('FRAME_HEADER_SIZE_MASK') 99 100 self.compute_frame_info() 101 commonFrameLayout = gdb.lookup_type('js::jit::CommonFrameLayout') 102 self.d['typeCommonFrameLayout'] = commonFrameLayout 103 self.d['typeCommonFrameLayoutPointer'] = commonFrameLayout.pointer() 104 self.d['per_tls_context'] = gdb.lookup_global_symbol('js::TlsContext') 105 106 self.d['void_starstar'] = gdb.lookup_type('void').pointer().pointer() 107 108 jitframe = gdb.lookup_type("js::jit::JitFrameLayout") 109 self.d['jitFrameLayoutPointer'] = jitframe.pointer() 110 111 self.d['CalleeToken_Function'] = self.jit_value("CalleeToken_Function") 112 self.d['CalleeToken_FunctionConstructing'] = self.jit_value( 113 "CalleeToken_FunctionConstructing") 114 self.d['CalleeToken_Script'] = self.jit_value("CalleeToken_Script") 115 self.d['JSFunction'] = gdb.lookup_type("JSFunction").pointer() 116 self.d['JSScript'] = gdb.lookup_type("JSScript").pointer() 117 self.d['Value'] = gdb.lookup_type("JS::Value") 118 119 self.d['SOURCE_SLOT'] = self.value('js::ScriptSourceObject::SOURCE_SLOT') 120 self.d['NativeObject'] = gdb.lookup_type("js::NativeObject").pointer() 121 self.d['HeapSlot'] = gdb.lookup_type("js::HeapSlot").pointer() 122 self.d['ScriptSource'] = gdb.lookup_type("js::ScriptSource").pointer() 123 124 # ProcessExecutableMemory, used to identify if a pc is in the section 125 # pre-allocated by the JIT. 126 self.d['MaxCodeBytesPerProcess'] = self.jit_value('MaxCodeBytesPerProcess') 127 self.d['execMemory'] = gdb.lookup_symbol('::execMemory')[0].value() 128 129 # Compute maps related to jit frames. 130 def compute_frame_info(self): 131 t = gdb.lookup_type('enum js::jit::FrameType') 132 for field in t.fields(): 133 # Strip off "js::jit::", remains: "FrameType::*". 134 name = field.name[9:] 135 enumval = long(field.enumval) 136 self.d[name] = enumval 137 self.frame_enum_names[enumval] = name 138 class_type = gdb.lookup_type('js::jit::' + SizeOfFramePrefix[name]) 139 self.frame_class_types[enumval] = class_type.pointer() 140 141 142class FrameSymbol(object): 143 "A symbol/value pair as expected from gdb frame decorators." 144 145 def __init__(self, sym, val): 146 self.sym = sym 147 self.val = val 148 149 def symbol(self): 150 return self.sym 151 152 def value(self): 153 return self.val 154 155 156class JitFrameDecorator(FrameDecorator): 157 """This represents a single JIT frame for the purposes of display. 158 That is, the frame filter creates instances of this when it sees a 159 JIT frame in the stack.""" 160 161 def __init__(self, base, info, cache): 162 super(JitFrameDecorator, self).__init__(base) 163 self.info = info 164 self.cache = cache 165 166 def _decode_jitframe(self, this_frame): 167 calleetoken = long(this_frame['calleeToken_']) 168 tag = calleetoken & 3 169 calleetoken = calleetoken ^ tag 170 function = None 171 script = None 172 if (tag == self.cache.CalleeToken_Function or 173 tag == self.cache.CalleeToken_FunctionConstructing): 174 fptr = gdb.Value(calleetoken).cast(self.cache.JSFunction) 175 try: 176 atom = fptr['atom_'] 177 if atom: 178 function = str(atom) 179 except gdb.MemoryError: 180 function = "(could not read function name)" 181 script = fptr['u']['scripted']['s']['script_'] 182 elif tag == self.cache.CalleeToken_Script: 183 script = gdb.Value(calleetoken).cast(self.cache.JSScript) 184 return {"function": function, "script": script} 185 186 def function(self): 187 if self.info["name"] is None: 188 return FrameDecorator.function(self) 189 name = self.info["name"] 190 result = "<<" + name 191 # If we have a frame, we can extract the callee information 192 # from it for display here. 193 this_frame = self.info["this_frame"] 194 if this_frame is not None: 195 if gdb.types.has_field(this_frame.type.target(), "calleeToken_"): 196 function = self._decode_jitframe(this_frame)["function"] 197 if function is not None: 198 result = result + " " + function 199 return result + ">>" 200 201 def filename(self): 202 this_frame = self.info["this_frame"] 203 if this_frame is not None: 204 if gdb.types.has_field(this_frame.type.target(), "calleeToken_"): 205 script = self._decode_jitframe(this_frame)["script"] 206 if script is not None: 207 obj = script['sourceObject_']['value'] 208 # Verify that this is a ScriptSource object. 209 # FIXME should also deal with wrappers here. 210 nativeobj = obj.cast(self.cache.NativeObject) 211 # See bug 987069 and despair. At least this 212 # approach won't give exceptions. 213 class_name = nativeobj['group_']['value']['clasp_']['name'].string( 214 "ISO-8859-1") 215 if class_name != "ScriptSource": 216 return FrameDecorator.filename(self) 217 scriptsourceobj = ( 218 nativeobj + 1).cast(self.cache.HeapSlot)[self.cache.SOURCE_SLOT] 219 scriptsource = scriptsourceobj['value']['asBits_'] << 1 220 scriptsource = scriptsource.cast(self.cache.ScriptSource) 221 return scriptsource['filename_']['mTuple']['mFirstA'].string() 222 return FrameDecorator.filename(self) 223 224 def frame_args(self): 225 this_frame = self.info["this_frame"] 226 if this_frame is None: 227 return FrameDecorator.frame_args(self) 228 if not gdb.types.has_field(this_frame.type.target(), "numActualArgs_"): 229 return FrameDecorator.frame_args(self) 230 # See if this is a function call. 231 if self._decode_jitframe(this_frame)["function"] is None: 232 return FrameDecorator.frame_args(self) 233 # Construct and return an iterable of all the arguments. 234 result = [] 235 num_args = long(this_frame["numActualArgs_"]) 236 # Sometimes we see very large values here, so truncate it to 237 # bypass the damage. 238 if num_args > 10: 239 num_args = 10 240 args_ptr = (this_frame + 1).cast(self.cache.Value.pointer()) 241 for i in range(num_args + 1): 242 # Synthesize names, since there doesn't seem to be 243 # anything better to do. 244 if i == 0: 245 name = 'this' 246 else: 247 name = 'arg%d' % i 248 result.append(FrameSymbol(name, args_ptr[i])) 249 return result 250 251 252class SpiderMonkeyFrameFilter(object): 253 "A frame filter for SpiderMonkey." 254 255 # |state_holder| is either None, or an instance of 256 # SpiderMonkeyUnwinder. If the latter, then this class will 257 # reference the |unwinder_state| attribute to find the current 258 # unwinder state. 259 def __init__(self, cache, state_holder): 260 self.name = "SpiderMonkey" 261 self.enabled = True 262 self.priority = 100 263 self.state_holder = state_holder 264 self.cache = cache 265 266 def maybe_wrap_frame(self, frame): 267 if self.state_holder is None or self.state_holder.unwinder_state is None: 268 return frame 269 base = frame.inferior_frame() 270 info = self.state_holder.unwinder_state.get_frame(base) 271 if info is None: 272 return frame 273 return JitFrameDecorator(frame, info, self.cache) 274 275 def filter(self, frame_iter): 276 return imap(self.maybe_wrap_frame, frame_iter) 277 278 279class SpiderMonkeyFrameId(object): 280 "A frame id class, as specified by the gdb unwinder API." 281 282 def __init__(self, sp, pc): 283 self.sp = sp 284 self.pc = pc 285 286 287class UnwinderState(object): 288 """This holds all the state needed during a given unwind. Each time a 289 new unwind is done, a new instance of this class is created. It 290 keeps track of all the state needed to unwind JIT frames. Note that 291 this class is not directly instantiated. 292 293 This is a base class, and must be specialized for each target 294 architecture, both because we need to use arch-specific register 295 names, and because entry frame unwinding is arch-specific. 296 See https://sourceware.org/bugzilla/show_bug.cgi?id=19286 for info 297 about the register name issue. 298 299 Each subclass must define SP_REGISTER, PC_REGISTER, and 300 SENTINEL_REGISTER (see x64UnwinderState for info); and implement 301 unwind_entry_frame_registers.""" 302 303 def __init__(self, typecache): 304 self.next_sp = None 305 self.next_type = None 306 self.activation = None 307 # An unwinder instance is specific to a thread. Record the 308 # selected thread for later verification. 309 self.thread = gdb.selected_thread() 310 self.frame_map = {} 311 self.typecache = typecache 312 313 # If the given gdb.Frame was created by this unwinder, return the 314 # corresponding informational dictionary for the frame. 315 # Otherwise, return None. This is used by the frame filter to 316 # display extra information about the frame. 317 def get_frame(self, frame): 318 sp = long(frame.read_register(self.SP_REGISTER)) 319 if sp in self.frame_map: 320 return self.frame_map[sp] 321 return None 322 323 # Add information about a frame to the frame map. This map is 324 # queried by |self.get_frame|. |sp| is the frame's stack pointer, 325 # and |name| the frame's type as a string, e.g. "FrameType::Exit". 326 def add_frame(self, sp, name=None, this_frame=None): 327 self.frame_map[long(sp)] = {"name": name, "this_frame": this_frame} 328 329 # See whether |pc| is claimed by the Jit. 330 def is_jit_address(self, pc): 331 execMem = self.typecache.execMemory 332 base = long(execMem['base_']) 333 length = self.typecache.MaxCodeBytesPerProcess 334 335 # If the base pointer is null, then no memory got allocated yet. 336 if long(base) == 0: 337 return False 338 339 # If allocated, then we allocated MaxCodeBytesPerProcess. 340 return base <= pc and pc < base + length 341 342 # Check whether |self| is valid for the selected thread. 343 def check(self): 344 return gdb.selected_thread() is self.thread 345 346 # Essentially js::TlsContext.get(). 347 def get_tls_context(self): 348 return self.typecache.per_tls_context.value()['mValue'] 349 350 # |common| is a pointer to a CommonFrameLayout object. Return a 351 # tuple (local_size, header_size, frame_type), where |size| is the 352 # integer size of the previous frame's locals; |header_size| is 353 # the size of this frame's header; and |frame_type| is an integer 354 # representing the previous frame's type. 355 def unpack_descriptor(self, common): 356 value = long(common['descriptor_']) 357 local_size = value >> self.typecache.FRAMESIZE_SHIFT 358 header_size = ((value >> self.typecache.FRAME_HEADER_SIZE_SHIFT) & 359 self.typecache.FRAME_HEADER_SIZE_MASK) 360 header_size = header_size * self.typecache.void_starstar.sizeof 361 frame_type = long(value & self.typecache.FRAMETYPE_MASK) 362 if frame_type == self.typecache.frame_type.CppToJSJit: 363 # Trampoline-x64.cpp pushes a JitFrameLayout object, but 364 # the stack pointer is actually adjusted as if a 365 # CommonFrameLayout object was pushed. 366 header_size = self.typecache.typeCommonFrameLayout.sizeof 367 return (local_size, header_size, frame_type) 368 369 # Create a new frame for gdb. This makes a new unwind info object 370 # and fills it in, then returns it. It also registers any 371 # pertinent information with the frame filter for later display. 372 # 373 # |pc| is the PC from the pending frame 374 # |sp| is the stack pointer to use 375 # |frame| points to the CommonFrameLayout object 376 # |frame_type| is a integer, one of the |enum FrameType| values, 377 # describing the current frame. 378 # |pending_frame| is the pending frame (see the gdb unwinder 379 # documentation). 380 def create_frame(self, pc, sp, frame, frame_type, pending_frame): 381 # Make a frame_id that claims that |frame| is sort of like a 382 # frame pointer for this frame. 383 frame_id = SpiderMonkeyFrameId(frame, pc) 384 385 # Read the frame layout object to find the next such object. 386 # This lets us unwind the necessary registers for the next 387 # frame, and also update our internal state to match. 388 common = frame.cast(self.typecache.typeCommonFrameLayoutPointer) 389 next_pc = common['returnAddress_'] 390 (local_size, header_size, next_type) = self.unpack_descriptor(common) 391 next_sp = frame + header_size + local_size 392 393 # Compute the type of the next oldest frame's descriptor. 394 this_class_type = self.typecache.frame_class_types[frame_type] 395 this_frame = frame.cast(this_class_type) 396 397 # Register this frame so the frame filter can find it. This 398 # is registered using SP because we don't have any other good 399 # approach -- you can't get the frame id from a gdb.Frame. 400 # https://sourceware.org/bugzilla/show_bug.cgi?id=19800 401 frame_name = self.typecache.frame_enum_names[frame_type] 402 self.add_frame(sp, name=frame_name, this_frame=this_frame) 403 404 # Update internal state for the next unwind. 405 self.next_sp = next_sp 406 self.next_type = next_type 407 408 unwind_info = pending_frame.create_unwind_info(frame_id) 409 unwind_info.add_saved_register(self.PC_REGISTER, next_pc) 410 unwind_info.add_saved_register(self.SP_REGISTER, next_sp) 411 # FIXME it would be great to unwind any other registers here. 412 return unwind_info 413 414 # Unwind an "ordinary" JIT frame. This is used for JIT frames 415 # other than enter and exit frames. Returns the newly-created 416 # unwind info for gdb. 417 def unwind_ordinary(self, pc, pending_frame): 418 return self.create_frame(pc, self.next_sp, self.next_sp, 419 self.next_type, pending_frame) 420 421 # Unwind an exit frame. Returns None if this cannot be done; 422 # otherwise returns the newly-created unwind info for gdb. 423 def unwind_exit_frame(self, pc, pending_frame): 424 if self.activation == 0: 425 # Reached the end of the list. 426 return None 427 elif self.activation is None: 428 cx = self.get_tls_context() 429 self.activation = cx['jitActivation']['value'] 430 else: 431 self.activation = self.activation['prevJitActivation_'] 432 433 packedExitFP = self.activation['packedExitFP_'] 434 if packedExitFP == 0: 435 return None 436 437 exit_sp = pending_frame.read_register(self.SP_REGISTER) 438 frame_type = self.typecache.frame_type.Exit 439 return self.create_frame(pc, exit_sp, packedExitFP, frame_type, pending_frame) 440 441 # A wrapper for unwind_entry_frame_registers that handles 442 # architecture-independent boilerplate. 443 def unwind_entry_frame(self, pc, pending_frame): 444 sp = self.next_sp 445 # Notify the frame filter. 446 self.add_frame(sp, name='FrameType::CppToJSJit') 447 # Make an unwind_info for the per-architecture code to fill in. 448 frame_id = SpiderMonkeyFrameId(sp, pc) 449 unwind_info = pending_frame.create_unwind_info(frame_id) 450 self.unwind_entry_frame_registers(sp, unwind_info) 451 self.next_sp = None 452 self.next_type = None 453 return unwind_info 454 455 # The main entry point that is called to try to unwind a JIT frame 456 # of any type. Returns None if this cannot be done; otherwise 457 # returns the newly-created unwind info for gdb. 458 def unwind(self, pending_frame): 459 pc = pending_frame.read_register(self.PC_REGISTER) 460 461 # If the jit does not claim this address, bail. GDB defers to our 462 # unwinder by default, but we don't really want that kind of power. 463 if not self.is_jit_address(long(pc)): 464 return None 465 466 if self.next_sp is not None: 467 if self.next_type == self.typecache.frame_type.CppToJSJit: 468 return self.unwind_entry_frame(pc, pending_frame) 469 return self.unwind_ordinary(pc, pending_frame) 470 # Maybe we've found an exit frame. FIXME I currently don't 471 # know how to identify these precisely, so we'll just hope for 472 # the time being. 473 return self.unwind_exit_frame(pc, pending_frame) 474 475 476class x64UnwinderState(UnwinderState): 477 "The UnwinderState subclass for x86-64." 478 479 SP_REGISTER = 'rsp' 480 PC_REGISTER = 'rip' 481 482 # A register unique to this architecture, that is also likely to 483 # have been saved in any frame. The best thing to use here is 484 # some arch-specific name for PC or SP. 485 SENTINEL_REGISTER = 'rip' 486 487 # Must be in sync with Trampoline-x64.cpp:generateEnterJIT. Note 488 # that rip isn't pushed there explicitly, but rather by the 489 # previous function's call. 490 PUSHED_REGS = ["r15", "r14", "r13", "r12", "rbx", "rbp", "rip"] 491 492 # Fill in the unwound registers for an entry frame. 493 def unwind_entry_frame_registers(self, sp, unwind_info): 494 sp = sp.cast(self.typecache.void_starstar) 495 # Skip the "result" push. 496 sp = sp + 1 497 for reg in self.PUSHED_REGS: 498 data = sp.dereference() 499 sp = sp + 1 500 unwind_info.add_saved_register(reg, data) 501 if reg == "rbp": 502 unwind_info.add_saved_register(self.SP_REGISTER, sp) 503 504 505class SpiderMonkeyUnwinder(Unwinder): 506 """The unwinder object. This provides the "user interface" to the JIT 507 unwinder, and also handles constructing or destroying UnwinderState 508 objects as needed.""" 509 510 # A list of all the possible unwinders. See |self.make_unwinder|. 511 UNWINDERS = [x64UnwinderState] 512 513 def __init__(self, typecache): 514 super(SpiderMonkeyUnwinder, self).__init__("SpiderMonkey") 515 self.typecache = typecache 516 self.unwinder_state = None 517 518 # Disabled by default until we figure out issues in gdb. 519 self.enabled = False 520 gdb.write("SpiderMonkey unwinder is disabled by default, to enable it type:\n" + 521 "\tenable unwinder .* SpiderMonkey\n") 522 # Some versions of gdb did not flush the internal frame cache 523 # when enabling or disabling an unwinder. This was fixed in 524 # the same release of gdb that added the breakpoint_created 525 # event. 526 if not hasattr(gdb.events, "breakpoint_created"): 527 gdb.write("\tflushregs\n") 528 529 # We need to invalidate the unwinder state whenever the 530 # inferior starts executing. This avoids having a stale 531 # cache. 532 gdb.events.cont.connect(self.invalidate_unwinder_state) 533 assert self.test_sentinels() 534 535 def test_sentinels(self): 536 # Self-check. 537 regs = {} 538 for unwinder in self.UNWINDERS: 539 if unwinder.SENTINEL_REGISTER in regs: 540 return False 541 regs[unwinder.SENTINEL_REGISTER] = 1 542 return True 543 544 def make_unwinder(self, pending_frame): 545 # gdb doesn't provide a good way to find the architecture. 546 # See https://sourceware.org/bugzilla/show_bug.cgi?id=19399 547 # So, we look at each known architecture and see if the 548 # corresponding "unique register" is known. 549 for unwinder in self.UNWINDERS: 550 try: 551 pending_frame.read_register(unwinder.SENTINEL_REGISTER) 552 except Exception: 553 # Failed to read the register, so let's keep going. 554 # This is more fragile than it might seem, because it 555 # fails if the sentinel register wasn't saved in the 556 # previous frame. 557 continue 558 return unwinder(self.typecache) 559 return None 560 561 def __call__(self, pending_frame): 562 if self.unwinder_state is None or not self.unwinder_state.check(): 563 self.unwinder_state = self.make_unwinder(pending_frame) 564 if not self.unwinder_state: 565 return None 566 return self.unwinder_state.unwind(pending_frame) 567 568 def invalidate_unwinder_state(self, *args, **kwargs): 569 self.unwinder_state = None 570 571 572def register_unwinder(objfile): 573 """Register the unwinder and frame filter with |objfile|. If |objfile| 574 is None, register them globally.""" 575 576 type_cache = UnwinderTypeCache() 577 unwinder = None 578 # This currently only works on Linux, due to parse_proc_maps. 579 if _have_unwinder and platform.system() == "Linux": 580 unwinder = SpiderMonkeyUnwinder(type_cache) 581 gdb.unwinder.register_unwinder(objfile, unwinder, replace=True) 582 # We unconditionally register the frame filter, because at some 583 # point we'll add interpreter frame filtering. 584 filt = SpiderMonkeyFrameFilter(type_cache, unwinder) 585 if objfile is None: 586 objfile = gdb 587 objfile.frame_filters[filt.name] = filt 588