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