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