import gdb
import gdb.types
from gdb.FrameDecorator import FrameDecorator
import re
import platform
from mozilla.ExecutableAllocator import jsjitExecutableAllocatorCache, jsjitExecutableAllocator
try:
long
except NameError:
long = int
try:
from itertools import imap
except ImportError:
imap = map
_have_unwinder = True
try:
from gdb.unwinder import Unwinder
except ImportError:
_have_unwinder = False
Unwinder = object
def debug(something):
pass
SizeOfFramePrefix = {
'FrameType::IonJS': 'ExitFrameLayout',
'FrameType::BaselineJS': 'JitFrameLayout',
'FrameType::BaselineStub': 'BaselineStubFrameLayout',
'FrameType::IonStub': 'JitStubFrameLayout',
'FrameType::CppToJSJit': 'JitFrameLayout',
'FrameType::WasmToJSJit': 'JitFrameLayout',
'FrameType::JSJitToWasm': 'JitFrameLayout',
'FrameType::Rectifier': 'RectifierFrameLayout',
'FrameType::IonAccessorIC': 'IonAccessorICFrameLayout',
'FrameType::IonICCall': 'IonICCallFrameLayout',
'FrameType::Exit': 'ExitFrameLayout',
'FrameType::Bailout': 'JitFrameLayout',
}
class UnwinderTypeCache(object):
def __init__(self):
self.d = None
self.frame_enum_names = {}
self.frame_class_types = {}
def __getattr__(self, name):
if self.d is None:
self.initialize()
return self.d[name]
def value(self, name):
return long(gdb.lookup_symbol(name)[0].value())
def jit_value(self, name):
return self.value('js::jit::' + name)
def initialize(self):
self.d = {}
self.d['FRAMETYPE_MASK'] = (1 << self.jit_value('FRAMETYPE_BITS')) - 1
self.d['FRAMESIZE_SHIFT'] = self.jit_value('FRAMESIZE_SHIFT')
self.d['FRAME_HEADER_SIZE_SHIFT'] = self.jit_value('FRAME_HEADER_SIZE_SHIFT')
self.d['FRAME_HEADER_SIZE_MASK'] = self.jit_value('FRAME_HEADER_SIZE_MASK')
self.compute_frame_info()
commonFrameLayout = gdb.lookup_type('js::jit::CommonFrameLayout')
self.d['typeCommonFrameLayout'] = commonFrameLayout
self.d['typeCommonFrameLayoutPointer'] = commonFrameLayout.pointer()
self.d['per_tls_context'] = gdb.lookup_global_symbol('js::TlsContext')
self.d['void_starstar'] = gdb.lookup_type('void').pointer().pointer()
self.d['mod_ExecutableAllocator'] = jsjitExecutableAllocatorCache()
jitframe = gdb.lookup_type("js::jit::JitFrameLayout")
self.d['jitFrameLayoutPointer'] = jitframe.pointer()
self.d['CalleeToken_Function'] = self.jit_value("CalleeToken_Function")
self.d['CalleeToken_FunctionConstructing'] = self.jit_value(
"CalleeToken_FunctionConstructing")
self.d['CalleeToken_Script'] = self.jit_value("CalleeToken_Script")
self.d['JSFunction'] = gdb.lookup_type("JSFunction").pointer()
self.d['JSScript'] = gdb.lookup_type("JSScript").pointer()
self.d['Value'] = gdb.lookup_type("JS::Value")
self.d['SOURCE_SLOT'] = self.value('js::ScriptSourceObject::SOURCE_SLOT')
self.d['NativeObject'] = gdb.lookup_type("js::NativeObject").pointer()
self.d['HeapSlot'] = gdb.lookup_type("js::HeapSlot").pointer()
self.d['ScriptSource'] = gdb.lookup_type("js::ScriptSource").pointer()
def compute_frame_info(self):
t = gdb.lookup_type('enum js::jit::FrameType')
for field in t.fields():
name = field.name[9:]
enumval = long(field.enumval)
self.d[name] = enumval
self.frame_enum_names[enumval] = name
class_type = gdb.lookup_type('js::jit::' + SizeOfFramePrefix[name])
self.frame_class_types[enumval] = class_type.pointer()
def parse_proc_maps():
mapfile = '/proc/' + str(gdb.selected_inferior().pid) + '/maps'
matcher = re.compile("^([a-fA-F0-9]+)-([a-fA-F0-9]+)\s+..x.\s+\S+\s+\S+\s+\S*(.*)$")
mappings = []
with open(mapfile, "r") as inp:
for line in inp:
match = matcher.match(line)
if not match:
continue
start = match.group(1)
end = match.group(2)
name = match.group(3).strip()
if name is '' or (name.startswith('[') and name is not '[vdso]'):
continue
mappings.append((long(start, 16), long(end, 16)))
return mappings
class FrameSymbol(object):
"A symbol/value pair as expected from gdb frame decorators."
def __init__(self, sym, val):
self.sym = sym
self.val = val
def symbol(self):
return self.sym
def value(self):
return self.val
class JitFrameDecorator(FrameDecorator):
def __init__(self, base, info, cache):
super(JitFrameDecorator, self).__init__(base)
self.info = info
self.cache = cache
def _decode_jitframe(self, this_frame):
calleetoken = long(this_frame['calleeToken_'])
tag = calleetoken & 3
calleetoken = calleetoken ^ tag
function = None
script = None
if (tag == self.cache.CalleeToken_Function or
tag == self.cache.CalleeToken_FunctionConstructing):
fptr = gdb.Value(calleetoken).cast(self.cache.JSFunction)
try:
atom = fptr['atom_']
if atom:
function = str(atom)
except gdb.MemoryError:
function = "(could not read function name)"
script = fptr['u']['scripted']['s']['script_']
elif tag == self.cache.CalleeToken_Script:
script = gdb.Value(calleetoken).cast(self.cache.JSScript)
return {"function": function, "script": script}
def function(self):
if self.info["name"] is None:
return FrameDecorator.function(self)
name = self.info["name"]
result = "<<" + name
this_frame = self.info["this_frame"]
if this_frame is not None:
if gdb.types.has_field(this_frame.type.target(), "calleeToken_"):
function = self._decode_jitframe(this_frame)["function"]
if function is not None:
result = result + " " + function
return result + ">>"
def filename(self):
this_frame = self.info["this_frame"]
if this_frame is not None:
if gdb.types.has_field(this_frame.type.target(), "calleeToken_"):
script = self._decode_jitframe(this_frame)["script"]
if script is not None:
obj = script['sourceObject_']['value']
nativeobj = obj.cast(self.cache.NativeObject)
class_name = nativeobj['group_']['value']['clasp_']['name'].string(
"ISO-8859-1")
if class_name != "ScriptSource":
return FrameDecorator.filename(self)
scriptsourceobj = (
nativeobj + 1).cast(self.cache.HeapSlot)[self.cache.SOURCE_SLOT]
scriptsource = scriptsourceobj['value']['asBits_'] << 1
scriptsource = scriptsource.cast(self.cache.ScriptSource)
return scriptsource['filename_']['mTuple']['mFirstA'].string()
return FrameDecorator.filename(self)
def frame_args(self):
this_frame = self.info["this_frame"]
if this_frame is None:
return FrameDecorator.frame_args(self)
if not gdb.types.has_field(this_frame.type.target(), "numActualArgs_"):
return FrameDecorator.frame_args(self)
if self._decode_jitframe(this_frame)["function"] is None:
return FrameDecorator.frame_args(self)
result = []
num_args = long(this_frame["numActualArgs_"])
if num_args > 10:
num_args = 10
args_ptr = (this_frame + 1).cast(self.cache.Value.pointer())
for i in range(num_args + 1):
if i == 0:
name = 'this'
else:
name = 'arg%d' % i
result.append(FrameSymbol(name, args_ptr[i]))
return result
class SpiderMonkeyFrameFilter(object):
"A frame filter for SpiderMonkey."
def __init__(self, cache, state_holder):
self.name = "SpiderMonkey"
self.enabled = True
self.priority = 100
self.state_holder = state_holder
self.cache = cache
def maybe_wrap_frame(self, frame):
if self.state_holder is None or self.state_holder.unwinder_state is None:
return frame
base = frame.inferior_frame()
info = self.state_holder.unwinder_state.get_frame(base)
if info is None:
return frame
return JitFrameDecorator(frame, info, self.cache)
def filter(self, frame_iter):
return imap(self.maybe_wrap_frame, frame_iter)
class SpiderMonkeyFrameId(object):
"A frame id class, as specified by the gdb unwinder API."
def __init__(self, sp, pc):
self.sp = sp
self.pc = pc
class UnwinderState(object):
def __init__(self, typecache):
self.next_sp = None
self.next_type = None
self.activation = None
self.thread = gdb.selected_thread()
self.frame_map = {}
self.proc_mappings = None
try:
self.proc_mappings = parse_proc_maps()
except IOError:
pass
self.typecache = typecache
def get_frame(self, frame):
sp = long(frame.read_register(self.SP_REGISTER))
if sp in self.frame_map:
return self.frame_map[sp]
return None
def add_frame(self, sp, name=None, this_frame=None):
self.frame_map[long(sp)] = {"name": name, "this_frame": this_frame}
def text_address_claimed(self, pc):
for (start, end) in self.proc_mappings:
if (pc >= start and pc <= end):
return True
return False
def is_jit_address(self, pc):
if self.proc_mappings is not None:
return not self.text_address_claimed(pc)
cx = self.get_tls_context()
runtime = cx['runtime_']['value']
if long(runtime.address) == 0:
return False
jitRuntime = runtime['jitRuntime_']
if long(jitRuntime.address) == 0:
return False
execAllocators = [jitRuntime['execAlloc_'], jitRuntime['backedgeExecAlloc_']]
for execAlloc in execAllocators:
for pool in jsjitExecutableAllocator(execAlloc, self.typecache):
pages = pool['m_allocation']['pages']
size = pool['m_allocation']['size']
if pages <= pc and pc < pages + size:
return True
return False
def check(self):
return gdb.selected_thread() is self.thread
def get_tls_context(self):
return self.typecache.per_tls_context.value()['mValue']
def unpack_descriptor(self, common):
value = long(common['descriptor_'])
local_size = value >> self.typecache.FRAMESIZE_SHIFT
header_size = ((value >> self.typecache.FRAME_HEADER_SIZE_SHIFT) &
self.typecache.FRAME_HEADER_SIZE_MASK)
header_size = header_size * self.typecache.void_starstar.sizeof
frame_type = long(value & self.typecache.FRAMETYPE_MASK)
if frame_type == self.typecache.CppToJSJit:
header_size = self.typecache.typeCommonFrameLayout.sizeof
return (local_size, header_size, frame_type)
def create_frame(self, pc, sp, frame, frame_type, pending_frame):
frame_id = SpiderMonkeyFrameId(frame, pc)
common = frame.cast(self.typecache.typeCommonFrameLayoutPointer)
next_pc = common['returnAddress_']
(local_size, header_size, next_type) = self.unpack_descriptor(common)
next_sp = frame + header_size + local_size
this_class_type = self.typecache.frame_class_types[frame_type]
this_frame = frame.cast(this_class_type)
frame_name = self.typecache.frame_enum_names[frame_type]
self.add_frame(sp, name=frame_name, this_frame=this_frame)
self.next_sp = next_sp
self.next_type = next_type
unwind_info = pending_frame.create_unwind_info(frame_id)
unwind_info.add_saved_register(self.PC_REGISTER, next_pc)
unwind_info.add_saved_register(self.SP_REGISTER, next_sp)
return unwind_info
def unwind_ordinary(self, pc, pending_frame):
return self.create_frame(pc, self.next_sp, self.next_sp,
self.next_type, pending_frame)
def unwind_exit_frame(self, pc, pending_frame):
if self.activation == 0:
return None
elif self.activation is None:
cx = self.get_tls_context()
self.activation = cx['jitActivation']['value']
else:
self.activation = self.activation['prevJitActivation_']
packedExitFP = self.activation['packedExitFP_']
if packedExitFP == 0:
return None
exit_sp = pending_frame.read_register(self.SP_REGISTER)
frame_type = self.typecache.Exit
return self.create_frame(pc, exit_sp, packedExitFP, frame_type, pending_frame)
def unwind_entry_frame(self, pc, pending_frame):
sp = self.next_sp
self.add_frame(sp, name='FrameType::CppToJSJit')
frame_id = SpiderMonkeyFrameId(sp, pc)
unwind_info = pending_frame.create_unwind_info(frame_id)
self.unwind_entry_frame_registers(sp, unwind_info)
self.next_sp = None
self.next_type = None
return unwind_info
def unwind(self, pending_frame):
pc = pending_frame.read_register(self.PC_REGISTER)
if not self.is_jit_address(long(pc)):
return None
if self.next_sp is not None:
if self.next_type == self.typecache.CppToJSJit:
return self.unwind_entry_frame(pc, pending_frame)
return self.unwind_ordinary(pc, pending_frame)
return self.unwind_exit_frame(pc, pending_frame)
class x64UnwinderState(UnwinderState):
"The UnwinderState subclass for x86-64."
SP_REGISTER = 'rsp'
PC_REGISTER = 'rip'
SENTINEL_REGISTER = 'rip'
PUSHED_REGS = ["r15", "r14", "r13", "r12", "rbx", "rbp", "rip"]
def unwind_entry_frame_registers(self, sp, unwind_info):
sp = sp.cast(self.typecache.void_starstar)
sp = sp + 1
for reg in self.PUSHED_REGS:
data = sp.dereference()
sp = sp + 1
unwind_info.add_saved_register(reg, data)
if reg is "rbp":
unwind_info.add_saved_register(self.SP_REGISTER, sp)
class SpiderMonkeyUnwinder(Unwinder):
UNWINDERS = [x64UnwinderState]
def __init__(self, typecache):
super(SpiderMonkeyUnwinder, self).__init__("SpiderMonkey")
self.typecache = typecache
self.unwinder_state = None
self.enabled = False
gdb.write("SpiderMonkey unwinder is disabled by default, to enable it type:\n" +
"\tenable unwinder .* SpiderMonkey\n")
if not hasattr(gdb.events, "breakpoint_created"):
gdb.write("\tflushregs\n")
gdb.events.cont.connect(self.invalidate_unwinder_state)
assert self.test_sentinels()
def test_sentinels(self):
regs = {}
for unwinder in self.UNWINDERS:
if unwinder.SENTINEL_REGISTER in regs:
return False
regs[unwinder.SENTINEL_REGISTER] = 1
return True
def make_unwinder(self, pending_frame):
for unwinder in self.UNWINDERS:
try:
pending_frame.read_register(unwinder.SENTINEL_REGISTER)
except Exception:
continue
return unwinder(self.typecache)
return None
def __call__(self, pending_frame):
if self.unwinder_state is None or not self.unwinder_state.check():
self.unwinder_state = self.make_unwinder(pending_frame)
if not self.unwinder_state:
return None
return self.unwinder_state.unwind(pending_frame)
def invalidate_unwinder_state(self, *args, **kwargs):
self.unwinder_state = None
def register_unwinder(objfile):
type_cache = UnwinderTypeCache()
unwinder = None
if _have_unwinder and platform.system() == "Linux":
unwinder = SpiderMonkeyUnwinder(type_cache)
gdb.unwinder.register_unwinder(objfile, unwinder, replace=True)
filt = SpiderMonkeyFrameFilter(type_cache, unwinder)
if objfile is None:
objfile = gdb
objfile.frame_filters[filt.name] = filt