openjdk-lts/debian/dbg.py

1233 lines
47 KiB
Python
Raw Normal View History

# Copyright 2016, Red Hat and individual contributors
# by the @authors tag.
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this software; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA, or see the FSF site: http://www.fsf.org.
#
# @authors Andrew Dinn
import gdb
import re
from gdb.FrameDecorator import FrameDecorator
# we rely on the unwinder API provided by gdb.7.10
_dump_frame = False
_have_unwinder = True
try:
from gdb.unwinder import Unwinder
gdb.write("Installing openjdk unwinder\n")
except ImportError:
_have_unwinder = False
# We need something here; it doesn't matter what as no unwinder
# will ever be instantiated.
Unwinder = object
def debug_write(msg):
gdb.write(msg)
# pass
def t(msg):
gdb.write("%s\n" % msg)
# pass
# debug_write("dbg.py\n")
# class providing various type conversions for gdb Value instances
class Types(object):
# cache some basic primitive and pointer types
byte_t = gdb.lookup_type('unsigned char')
char_t = gdb.lookup_type('char')
int_t = gdb.lookup_type('int')
long_t = gdb.lookup_type('long')
void_t = gdb.lookup_type('void')
bytep_t = byte_t.pointer()
charp_t = char_t.pointer()
intp_t = int_t.pointer()
longp_t = long_t.pointer()
voidp_t = void_t.pointer()
codeblobp_t = gdb.lookup_type('CodeBlob').pointer()
cmethodp_t = gdb.lookup_type('CompiledMethod').pointer()
nmethodp_t = gdb.lookup_type('nmethod').pointer()
ptrp_t = voidp_t.pointer()
# convert Values to primitive Values
@classmethod
def cast_byte(cls, value):
return value.cast(cls.byte_t)
@classmethod
def cast_int(cls, value):
return value.cast(cls.int_t)
@classmethod
def cast_long(cls, value):
return value.cast(cls.long_t)
# convert Values to pointer Values
@classmethod
def cast_bytep(cls, value):
return value.cast(cls.bytep_t)
@classmethod
def cast_intp(cls, value):
return value.cast(cls.intp_t)
@classmethod
def cast_longp(cls, value):
return value.cast(cls.longp_t)
@classmethod
def cast_voidp(cls, value):
return value.cast(cls.voidp_t)
@classmethod
def cast_ptrp(cls, value):
return value.cast(cls.ptrp_t)
# cast Value to pointer type then load and return contents as Value
@classmethod
def load_ptr(cls, value):
return cls.cast_ptrp(cls.cast_ptrp(value).dereference())
@classmethod
def load_byte(cls, value):
return cls.cast_bytep(value).dereference()
@classmethod
def load_int(cls, value):
return cls.cast_intp(value).dereference()
@classmethod
def load_long(cls, value):
return cls.cast_longp(value).dereference()
# cast Value to int and return as python integer
@classmethod
def as_int(cls, value):
return int(cls.cast_int(value))
# cast Value to long and return as python integer
@classmethod
def as_long(cls, value):
return int(cls.cast_long(value))
# construct Value from integer x and cast to type t
@classmethod
def to_type(cls, x, t):
return gdb.Value(x).cast(t)
# construct voidp Value from integer x
@classmethod
def to_voidp(cls, x):
return cls.to_type(x, cls.voidp_t)
# construct void ** Value from integer x
@classmethod
def to_ptrp(cls, x):
return cls.to_type(x, cls.ptrp_t)
# OpenJDK specific classes which understand the layout of the code
# heap and know how to translate a PC to an associated code blob and,
# from there to a method object. n.b. in some cases the latter step
# requires a frame base pointer bu tthat can be calculated using the
# code blob and stack pointer
# class encapsulating details of a specific heap
class CodeHeap:
# track whether we have static initialized yet
class_inited = False
heap_block_type = None
code_blob_type = None
@classmethod
def class_init(cls):
# t("CodeHeap.class_init")
if cls.class_inited:
return
# we can only proceed if we have the necessary heap symbols
# if this excepts then we don't care
cls.heap_block_type = gdb.lookup_type("HeapBlock").pointer()
cls.code_blob_type = gdb.lookup_type("CodeBlob").pointer()
cls.class_inited = True
def __init__(self, heap):
# t("CodeHeap.__init__")
# make sure we have static inited successfuly
self.class_init()
# if we got here we are ok to create a new instance
self.heap = heap
self.name = str(heap['_name'])
self.lo = Types.as_long(heap['_memory']['_low_boundary'])
self.hi = Types.as_long(heap['_memory']['_high_boundary'])
self.segmap = heap['_segmap']
self.segmap_lo = self.segmap['_low']
self.segment_size = int(heap['_segment_size'])
self.log2_segment_size = int(heap['_log2_segment_size'])
# debug_write("@@ heap.name = %s\n" % self.name)
# debug_write("@@ heap.lo = 0x%x\n" % self.lo)
# debug_write("@@ heap.hi = 0x%x\n" % self.hi)
def inrange(self, x):
# t("CodeHeap.inrange")
return self.lo <= x and self.hi > x
def findblob(self, pc):
# t("CodeHeap.findblob")
x = Types.as_long(pc)
# debug_write("@@ findblob(%s, 0x%x)\n" % (self.name, pc))
# debug_write("@@ pc (%s) = 0x%x \n" % (str(pc.type), pc))
# debug_write("@@ self.lo = 0x%x\n" % self.lo)
# debug_write("@@ self.hi = 0x%x\n" % self.hi)
# check pc is in this heap's range
# t("if not self.inrange(x):")
if not self.inrange(x):
return None
# debug_write("@@ pc in range\n")
# t("segments = 0")
segments = 0
# debug_write("@@ segmap_lo (%s) = 0x%x\n" % (str(self.segmap_lo.type), self.segmap_lo))
# debug_write("@@ self.lo = 0x%x \n" % self.lo)
# debug_write("@@ self.log2_segment_size = 0x%x \n" % self.log2_segment_size)
# t("offset = Types.as_long(pc - self.lo)")
offset = Types.as_long(pc - self.lo)
# debug_write("@@ offset = 0x%x\n" % offset)
# t("shift = self.log2_segment_size")
shift = self.log2_segment_size
# debug_write("@@ shift = 0x%x\n" % shift)
# t("segment = offset >> shift")
segment = offset >> shift
# segment = (offset >> self.log2_segment_size)
#segment = offset >> (self.log2_segment_size & 0x31)
# debug_write("@@ segment = 0x%x\n" % segment)
# t("tag = (self.segmap_lo + segment).dereference() & 0xff")
tag = (self.segmap_lo + segment).dereference() & 0xff
# tag = Types.load_byte(self.segmap_lo + segment) & 0xff
# debug_write("@@ tag (%s) = 0x%x\n" % (str(tag.type), tag))
# t("while tag > 0 and segments < 64:")
while tag > 0 and segments < 64:
# t("segment = segment - tag")
segment = segment - tag
# debug_write("@@ segment = 0x%x\n" % segment)
# t("tag = (self.segmap_lo + segment).dereference() & 0xff")
tag = (self.segmap_lo + segment).dereference() & 0xff
# debug_write("@@ tag (%s) = 0x%x\n" % (str(tag.type), tag))
# t("segments += 1")
segments += 1
# t("if tag != 0:")
if tag != 0:
# t("return None")
return None
# debug_write("@@ lo = 0x%x\n" % self.lo)
# debug_write("@@ segment << self.log2_segment_size = 0x%x\n" % (segment << self.log2_segment_size))
# t("block_addr = self.lo + (segment << self.log2_segment_size)")
block_addr = self.lo + (segment << self.log2_segment_size)
# debug_write("@@ block_addr (%s) = 0x%x\n" % (str(block_addr.type), block_addr))
# t("heap_block = gdb.Value(block_addr).cast(CodeHeap.heap_block_type)")
heap_block = gdb.Value(block_addr).cast(CodeHeap.heap_block_type)
# debug_write("@@ heap_block (%s) = 0x%x\n" % (str(heap_block.type), heap_block))
# t("if heap_block['_header']['_used'] != 1:")
if heap_block['_header']['_used'] != 1:
# hmm, this is not meant to happen
# t("return None")
return None
# t("blob = (heap_block + 1).cast(CodeHeap.code_blob_type)")
blob = (heap_block + 1).cast(CodeHeap.code_blob_type)
return blob
# class encapsulating access to code cache memory
# this is essentially all static as per the JVM class
class CodeCache:
# track whether we have successfully intiialized
class_inited = False
# static heap start, lo and hi bounds for code addresses
lo = 0
hi = 0
heap_count = 0
heap_list = []
@classmethod
def class_init(cls):
# t("CodeCache.class_init")
if cls.class_inited:
return
# t("if cls.lo == 0 or cls.hi == 0:")
if cls.lo == 0 or cls.hi == 0:
try:
# t("cls.lo = gdb.parse_and_eval(\"CodeCache::_low_bound\")")
lo = gdb.parse_and_eval("CodeCache::_low_bound")
cls.lo = Types.as_long(lo)
# debug_write("@@ CodeCache::_low_bound = 0x%x\n" % cls.lo)
if cls.lo == 0:
return
# t("cls.hi = gdb.parse_and_eval(\"CodeCache::_high_bound\")")
hi = gdb.parse_and_eval("CodeCache::_high_bound")
cls.hi = Types.as_long(hi)
# debug_write("@@ CodeCache::_high_bound = 0x%x\n" % cls.hi)
if cls.hi == 0:
return
except Exception as arg:
# debug_write("@@ %s\n" % arg)
cls.lo = 0
cls.hi = 0
cls.class_inited = False
raise
# t("f cls.heap_list == []:")
if cls.heap_list == []:
try:
# t("heaps = gdb.parse_and_eval(\"CodeCache::_heaps\")")
heaps = gdb.parse_and_eval("CodeCache::_heaps")
# debug_write("@@ CodeCache::_heaps (%s) = 0x%x\n" % (heaps.type, heaps))
# t("len = int(heaps['_len'])")
len = int(heaps['_len'])
# debug_write("@@ CodeCache::_heaps->_len = %d\n" % len)
# t("data = heaps['_data']")
data = heaps['_data']
# debug_write("@@ CodeCache::_heaps->_data = 0x%x\n" % data)
# t("for i in range(0, len):")
for i in range(0, len):
# t("heap = CodeHeap((data + i).dereference())")
heap = CodeHeap((data + i).dereference())
# t("cls.heap_list.append(heap)")
cls.heap_list.append(heap)
# t("cls.heap_count += 1")
cls.heap_count += 1
except Exception as arg:
# debug_write("@@ %s\n" % arg)
cls.heap_list = []
cls.heap_count = 0
cls.class_inited = False
raise
cls.class_inited = True
@classmethod
def inrange(cls, pc):
# t("CodeCache.inrange")
# make sure we are initialized
cls.class_init()
# if we got here we can use the heaps
x = Types.as_long(pc)
# t("return cls.lo <= x and cls.hi > x")
return cls.lo <= x and cls.hi > x
@classmethod
def makestr(cls, charcnt, charptr):
# t("CodeCache.makestr")
#res = ""
#for i in range(0, charcnt):
# c = (charptr + i).dereference()
# res = ("%s%c" % (res, c))
#return res
# debug_write("charcnt = %d charptr = %s\n" % (charcnt, str(charptr)))
return charptr.string("ascii", "ignore", charcnt)
# given a PC find the associated OpenJDK code blob instance
@classmethod
def findblob(cls, pc):
# t("CodeCache.findblob")
# make sure we are initialized
cls.class_init()
# if we got here we can use the heaps
if not cls.inrange(pc):
raise gdb.GdbError("dbg.findblob : address 0x%x is not in range!" % pc)
for heap in cls.heap_list:
try:
# t("blob = heap.findblob(pc)")
blob = heap.findblob(pc)
except Exception as arg:
# debug_write("@@ findblob excepted %s\n" % str(arg))
# t("blob = None")
blob = None
# t("if blob != None:")
if blob != None:
# t("name=str(blob['_name'])")
name=str(blob['_name'])
# debug_write("@@ blob(0x%x) -> %s\n" % (pc, name))
# t("return blob")
return blob
# t("raise gdb.GdbError")
raise gdb.GdbError("dbg.findblob : no blob for inrange address 0x%x!" % pc)
# abstract over some constants for stack frame layout
class FrameConstants(object):
class_inited = False
_sender_sp_offset = 0
_interpreter_frame_sender_sp_offset = 0
_interpreter_frame_method_offset = 0
_interpreter_frame_bcp_offset = 0
@classmethod
def class_init(cls):
if cls.class_inited:
return
cls._interpreter_frame_sender_sp_offset = int(gdb.parse_and_eval("frame::interpreter_frame_sender_sp_offset"))
cls._sender_sp_offset = int(gdb.parse_and_eval("frame::sender_sp_offset"))
cls._interpreter_frame_method_offset = int(gdb.parse_and_eval("frame::interpreter_frame_method_offset"))
cls._interpreter_frame_bcp_offset = int(gdb.parse_and_eval("frame::interpreter_frame_bcp_offset"))
# only set if we got here with no errors
cls.class_inited = True
@classmethod
def sender_sp_offset(cls):
cls.class_init()
return cls._sender_sp_offset
@classmethod
def interpreter_frame_sender_sp_offset(cls):
cls.class_init()
return cls._interpreter_frame_sender_sp_offset
@classmethod
def interpreter_frame_method_offset(cls):
cls.class_init()
return cls._interpreter_frame_method_offset
@classmethod
def interpreter_frame_bcp_offset(cls):
cls.class_init()
return cls._interpreter_frame_bcp_offset
# class which knows how to read a compressed stream of bytes n.b. at
# the moment we only need to know how to read bytes and ints
class CompressedStream:
# important constants for compressed stream read
BitsPerByte = 8
lg_H = 6
H = 1 << lg_H
L = (1 << BitsPerByte) - H
MAX_i = 4
def __init__(self, data):
# data is a gdb.Value of type 'byte *'
self.data = data
self.pos = 0
# retrieve the byte at offset pos
def at(self, pos):
return int(Types.load_byte(self.data + pos))
# read and return the next byte
def read(self):
# t("CompressedStream.read()")
pos = self.pos
b = self.at(pos)
self.pos = pos+1
return b
def read_int(self):
# t("CompressedStream.read_int()")
b0 = self.read()
# debug_write("b0 = 0x%x\n" % b0)
if b0 < CompressedStream.L:
return b0
return self.read_int_mb(b0)
def read_signed_int(self):
# t("CompressedStream.read_signed_int()")
return self.decode_sign(self.read_int())
def decode_sign(self, x):
# t("CompressedStream.decode_sign()")
return (x >> 1) ^ (0 - (x & 1))
def read_int_mb(self, b):
t# ("CompressedStream.read_int_mb()")
sum = b
pos = self.pos
# debug_write("pos = %d\n" % pos)
# debug_write("sum = 0x%x\n" % sum)
lg_H_i = CompressedStream.lg_H
i = 0
while (True):
b_i = self.at(pos + i)
# debug_write("b_%d = %d\n" % (i, b_i))
sum += (b_i << lg_H_i)
# debug_write("sum = 0x%x\n" % sum)
i += 1
if b_i < CompressedStream.L or i == CompressedStream.MAX_i:
self.pos = pos + i
# debug_write("self.pos = %d\n" % self.pos)
return sum
lg_H_i += CompressedStream.lg_H
# class which knows how to find method and bytecode
# index pairs from a pc associated with a given compiled
# method. n.b. there may be more than one such pair
# because of inlining.
class MethodBCIReader:
pcdescstar_t = None
class_inited = False
@classmethod
def class_init(cls):
# t("MethodBCIReader.class_init")
if cls.class_inited:
return
# cache some useful types
cls.pcdesc_p = gdb.lookup_type("PcDesc").pointer()
cls.metadata_p = gdb.lookup_type("Metadata").pointer()
cls.metadata_pp = cls.metadata_p.pointer()
cls.method_p = gdb.lookup_type("Method").pointer()
cls.class_inited = True
@classmethod
def as_pcdesc_p(cls, val):
return Types.to_type(val, cls.pcdesc_p)
def __init__(self, nmethod, method):
# t("MethodBCIReader.__init__")
# ensure we have cached the necessary types
self.class_init()
# need to unpack pc scopes
self.nmethod = nmethod
self.method = method
# debug_write("nmethod (%s) = 0x%x\n" % (str(nmethod.type), Types.as_long(nmethod)))
blob = Types.to_type(nmethod, Types.codeblobp_t);
self.code_begin = Types.as_long(blob['_code_begin'])
self.code_end = Types.as_long(blob['_code_end'])
scopes_pcs_begin_offset = Types.as_int(nmethod['_scopes_pcs_offset'])
# debug_write("scopes_pcs_begin_offset = 0x%x\n" % scopes_pcs_begin_offset)
scopes_pcs_end_offset = Types.as_int(nmethod['_dependencies_offset'])
# debug_write("scopes_pcs_end_offset = 0x%x\n" % scopes_pcs_end_offset)
header_begin = Types.cast_bytep(nmethod)
self.scopes_pcs_begin = self.as_pcdesc_p(header_begin + scopes_pcs_begin_offset)
# debug_write("scopes_pcs_begin (%s) = 0x%x\n" % (str(self.scopes_pcs_begin.type), Types.as_long(self.scopes_pcs_begin)))
self.scopes_pcs_end = self.as_pcdesc_p(header_begin + scopes_pcs_end_offset)
# debug_write("scopes_pcs_end (%s) = 0x%x\n" % (str(self.scopes_pcs_end.type), Types.as_long(self.scopes_pcs_end)))
def find_pc_desc(self, pc_off):
lower = self.scopes_pcs_begin
upper = self.scopes_pcs_end - 1
if lower == upper:
return None
# non-empty table always starts with lower as a sentinel with
# offset -1 and will have at least one real offset beyond that
next = lower + 1
while next < upper and next['_pc_offset'] <= pc_off:
next = next + 1
# use the last known bci below this pc
return next - 1
def pc_desc_to_method_bci_stack(self, pc_desc):
scope_decode_offset = Types.as_int(pc_desc['_scope_decode_offset'])
if scope_decode_offset == 0:
return [ { 'method': self.method, 'bci': 0 } ]
nmethod = self.nmethod
# debug_write("nmethod = 0x%x\n" % nmethod)
# debug_write("pc_desc = 0x%x\n" % Types.as_long(pc_desc))
base = Types.cast_bytep(nmethod)
# scopes_data_offset = Types.as_int(nmethod['_scopes_data_offset'])
# scopes_base = base + scopes_data_offset
scopes_base = nmethod['_scopes_data_begin']
# debug_write("scopes_base = 0x%x\n" % Types.as_long(scopes_base))
metadata_offset = Types.as_int(nmethod['_metadata_offset'])
metadata_base = Types.to_type(base + metadata_offset, self.metadata_pp)
# debug_write("metadata_base = 0x%x\n" % Types.as_long(metadata_base))
scope = scopes_base + scope_decode_offset
# debug_write("scope = 0x%x\n" % Types.as_long(scope))
stream = CompressedStream(scope)
# debug_write("stream = %s\n" % stream)
sender = stream.read_int()
# debug_write("sender = %s\n" % sender)
# method name is actually in metadata
method_idx = stream.read_int()
method_md = (metadata_base + (method_idx - 1)).dereference()
methodptr = Types.to_type(method_md, self.method_p)
method = Method(methodptr)
# bci is offset by -1 to allow range [-1, ..., MAX_UINT)
bci = stream.read_int() - 1
# debug_write("method,bci = %s,0x%x\n" % (method.get_name(), bci))
result = [ { 'method': method, 'bci': bci } ]
while sender > 0:
# debug_write("\nsender = 0x%x\n" % sender)
stream = CompressedStream(scopes_base + sender)
sender = stream.read_int()
method_idx = stream.read_int()
method_md = (metadata_base + (method_idx - 1)).dereference()
methodptr = Types.to_type(method_md, self.method_p)
method = Method(methodptr)
# bci is offset by -1 to allow range [-1, ..., MAX_UINT)
bci = stream.read_int() - 1
# debug_write("method,bci = %s,0x%x\n" % (method.get_name(), bci))
result.append( { 'method': method, 'bci': bci } )
return result
def pc_to_method_bci_stack(self, pc):
# need to search unpacked pc scopes
if pc < self.code_begin or pc >= self.code_end:
return None
pc_off = pc - self.code_begin
# debug_write("\npc_off = 0x%x\n" % pc_off)
pc_desc = self.find_pc_desc(pc_off)
if pc_desc is None:
return None
return self.pc_desc_to_method_bci_stack(pc_desc)
# class which knows how to read a method's line
# number table, translating bytecode indices to line numbers
class LineReader:
# table is a gdb.Value of type 'byte *' (strictly 'u_char *' in JVM code)
def __init__(self, table):
# t("LineReader.init")
self.table = table
self.translations = {}
def bc_to_line(self, bci):
# t("LineReader.bc_to_line()")
try:
return self.translations[bci]
except Exception as arg:
line = self.compute_line(bci)
if line >= 0:
self.translations[bci] = line
return line
def compute_line(self, bci):
# t("LineReader.compute_line()")
# debug_write("table = 0x%x\n" % self.table)
bestline = -1
self.stream = CompressedStream(self.table)
self._bci = 0
self._line = 0
while self.read_pair():
nextbci = self._bci
nextline = self._line
if nextbci >= bci:
return nextline
else:
bestline = nextline
return bestline
def read_pair(self):
# t("LineReader.read_pair()")
next = self.stream.read()
# debug_write("next = 0x%x\n" % next)
if next == 0:
return False
if next == 0xff:
self._bci = self._bci + self.stream.read_signed_int()
self._line = self._line + self.stream.read_signed_int()
else:
self._bci = self._bci + (next >> 3)
self._line = self._line + (next & 0x7)
# debug_write("_bci = %d\n" % self._bci)
# debug_write("_line = %d\n" % self._line)
return True
# class to provide access to data relating to a Method object
class Method(object):
def __init__(self, methodptr):
self.methodptr = methodptr;
self.name = None
const_method = self.methodptr['_constMethod']
bcbase = Types.cast_bytep(const_method + 1)
bytecode_size = Types.as_int(const_method['_code_size'])
lnbase = Types.cast_bytep(bcbase + bytecode_size)
self.line_number_reader = LineReader(lnbase)
def get_name(self):
if self.name == None:
self.make_name(self.methodptr)
return self.name
def get_klass_path(self):
if self.name == None:
self.make_name(self.methodptr)
return self.klass_path
def get_line(self, bci):
if bci < 0:
bci = 0
return self.line_number_reader.bc_to_line(bci)
def make_name(self, methodptr):
const_method = methodptr['_constMethod']
constant_pool = const_method['_constants']
constant_pool_base = Types.cast_voidp((constant_pool + 1))
klass_sym = constant_pool['_pool_holder']['_name']
klass_name = klass_sym['_body'].cast(gdb.lookup_type("char").pointer())
klass_name_length = int(klass_sym['_length'])
klass_path = CodeCache.makestr(klass_name_length, klass_name)
self.klass_str = klass_path.replace("/", ".")
dollaridx = klass_path.find('$')
if dollaridx >= 0:
klass_path = klass_path[0:dollaridx]
self.klass_path = klass_path + ".java"
method_idx = const_method['_name_index']
method_sym = (constant_pool_base + (8 * method_idx)).cast(gdb.lookup_type("Symbol").pointer().pointer()).dereference()
method_name = method_sym['_body'].cast(gdb.lookup_type("char").pointer())
method_name_length = int(method_sym['_length'])
self.method_str = CodeCache.makestr(method_name_length, method_name)
sig_idx = const_method['_signature_index']
sig_sym = (constant_pool_base + (8 * sig_idx)).cast(gdb.lookup_type("Symbol").pointer().pointer()).dereference()
sig_name = sig_sym['_body'].cast(gdb.lookup_type("char").pointer())
sig_name_length = int(sig_sym['_length'])
sig_str = CodeCache.makestr(sig_name_length, sig_name)
self.sig_str = self.make_sig_str(sig_str)
self.name = self.klass_str + "." + self.method_str + self.sig_str
def make_sig_str(self, sig):
in_sym_name = False
sig_str = ""
prefix=""
for i, c in enumerate(sig):
if c == "(":
sig_str = sig_str + c
elif c == ")":
# ignore return type
return sig_str + c
elif in_sym_name == True:
if c == ";":
in_sym_name = False
elif c == "/":
sig_str = sig_str + "."
else:
sig_str = sig_str + c
elif c == "L":
sig_str = sig_str + prefix
prefix = ","
in_sym_name = True
elif c == "B":
sig_str = sig_str + prefix + "byte"
prefix = ","
elif c == "S":
sig_str = sig_str + prefix + "short"
prefix = ","
elif c == "C":
sig_str = sig_str + prefix + "char"
prefix = ","
elif c == "I":
sig_str = sig_str + prefix + "int"
prefix = ","
elif c == "J":
sig_str = sig_str + prefix + "long"
prefix = ","
elif c == "F":
sig_str = sig_str + prefix + "float"
prefix = ","
elif c == "D":
sig_str = sig_str + prefix + "double"
prefix = ","
elif c == "Z":
sig_str = sig_str + prefix + "boolean"
prefix = ","
return sig_str
# This represents a method in a JIT frame for the purposes of
# display. There may be more than one method associated with
# a (compiled) frame because of inlining. The MethodInfo object
# associated with a JIT frame creates a list of decorators.
# Interpreted and stub MethodInfo objects insert only one
# decorator. Compiled MethodInfo objects insert one for the
# base method and one for each inlined method found at the
# frame PC address.
class OpenJDKFrameDecorator(FrameDecorator):
def __init__(self, base, methodname, filename, line):
super(FrameDecorator, self).__init__()
self._base = base
self._methodname = methodname
self._filename = filename
self._line = line
def function(self):
try:
# t("OpenJDKFrameDecorator.function")
return self._methodname
except Exception as arg:
gdb.write("!!! function oops !!! %s\n" % arg)
return None
def method_name(self):
return _methodname
def filename(self):
try:
return self._filename
except Exception as arg:
gdb.write("!!! filename oops !!! %s\n" % arg)
return None
def line(self):
try:
return self._line
except Exception as arg:
gdb.write("!!! line oops !!! %s\n" % arg)
return None
# A frame filter for OpenJDK.
class OpenJDKFrameFilter(object):
def __init__(self, unwinder):
self.name="OpenJDK"
self.enabled = True
self.priority = 100
self.unwinder = unwinder
def maybe_wrap_frame(self, frame):
# t("OpenJDKFrameFilter.maybe_wrap_frame")
if self.unwinder is None:
return [ frame ]
# t("unwindercache = self.unwinder.unwindercache")
unwindercache = self.unwinder.unwindercache
if unwindercache is None:
return [ frame ]
# t("base = frame.inferior_frame()")
base = frame.inferior_frame()
# t("sp = Types.as_long(base.read_register('rsp'))")
sp = base.read_register('rsp')
x = Types.as_long(sp)
# debug_write("@@ get info at unwindercache[0x%x]\n" % x)
try:
cache_entry = unwindercache[x]
except Exception as arg:
# n.b. no such entry throws an exception
# just ignore and use existing frame
return [ frame ]
try:
if cache_entry is None:
# debug_write("@@ lookup found no cache_entry\n")
return [ frame ]
elif cache_entry.codetype == "unknown":
# debug_write("@@ lookup found unknown cache_entry\n")
return [ frame ]
else:
# debug_write("@@ got cache_entry for blob 0x%x at unwindercache[0x%x]\n" % (cache_entry.blob, x))
method_info = cache_entry.method_info
if method_info == None:
return [ frame ]
else:
return method_info.decorate(frame)
except Exception as arg:
gdb.write("!!! maybe_wrap_frame oops !!! %s\n" % arg)
return [ frame ]
def flatten(self, list_of_lists):
return [x for y in list_of_lists for x in y ]
def filter(self, frame_iter):
# return map(self.maybe_wrap_frame, frame_iter)
return self.flatten( map(self.maybe_wrap_frame, frame_iter) )
# A frame id class, as specified by the gdb unwinder API.
class OpenJDKFrameId(object):
def __init__(self, sp, pc):
# t("OpenJDKFrameId.__init__")
self.sp = sp
self.pc = pc
# class hierarchy to record details of different code blobs
# the class implements functionality required by the frame decorator
class MethodInfo(object):
def __init__(self, entry):
self.blob = entry.blob
self.pc = Types.as_long(entry.pc)
self.sp = Types.as_long(entry.sp)
if entry.bp is None:
self.bp = 0
else:
self.bp = Types.as_long(entry.bp)
def decorate(self, frame):
return [ frame ]
# info for stub frame
class StubMethodInfo(MethodInfo):
def __init__(self, entry, name):
super(StubMethodInfo, self).__init__(entry)
self.name = name
def decorate(self, frame):
return [ OpenJDKFrameDecorator(frame, self.name, None, None) ]
# common info for compiled, native or interpreted frame
class JavaMethodInfo(MethodInfo):
def __init__(self, entry):
super(JavaMethodInfo, self).__init__(entry)
def cache_method_info(self):
methodptr = self.get_methodptr()
self.method = Method(methodptr)
def get_method(self):
return self.method
def method_name(self):
return self.get_method().get_name()
def filename(self):
return self.get_method().get_klass_path()
def decorate(self, frame):
return [ OpenJDKFrameDecorator(frame, self.method_name(), self.filename(), None) ]
# info for compiled frame
class CompiledMethodInfo(JavaMethodInfo):
def __init__(self, entry):
# t("CompiledMethodInfo.__init__")
super(CompiledMethodInfo,self).__init__(entry)
blob = self.blob
cmethod = Types.to_type(blob, Types.cmethodp_t)
nmethod = Types.to_type(blob, Types.nmethodp_t)
self.methodptr = cmethod['_method']
const_method = self.methodptr['_constMethod']
bcbase = Types.cast_bytep(const_method + 1)
self.code_begin = Types.as_long(blob['_code_begin'])
# get bc and line number info from method
self.cache_method_info()
# get PC to BCI translator from the nmethod
self.bytecode_index_reader = MethodBCIReader(nmethod, self.method)
# t("self.method_bci_stack = self.bytecode_index_reader.pc_to_method_bci_stack(self.pc)")
self.method_bci_stack = self.bytecode_index_reader.pc_to_method_bci_stack(self.pc)
# subclasses need to compute their method pointer
def get_methodptr(self):
return self.methodptr
def format_method_name(self, method, is_outer):
name = method.get_name()
if is_outer:
return ("[compiled offset=0x%x] %s" % (self.pc - self.code_begin, name))
else:
return ("[inlined] %s" % name)
def make_decorator(self, frame, pair, is_outer):
# t("make_decorator")
method = pair['method']
bci = pair['bci']
methodname = self.format_method_name(method, is_outer)
filename = method.get_klass_path()
line = method.get_line(bci)
if line < 0:
line = None
decorator = OpenJDKFrameDecorator(frame, methodname, filename, line)
return decorator
def decorate(self, frame):
if self.method_bci_stack == None:
return [ frame ]
else:
try:
decorators = []
pairs = self.method_bci_stack
# debug_write("converting method_bci_stack = %s\n" % self.method_bci_stack)
l = len(pairs)
for i in range(l):
pair = pairs[i]
# debug_write("decorating pair %s\n" % pair)
decorator = self.make_decorator(frame, pair, i == (l - 1))
decorators.append(decorator)
return decorators
except Exception as arg:
gdb.write("!!! decorate oops %s !!!\n" % arg)
return [ frame ]
# info for native frame
class NativeMethodInfo(JavaMethodInfo):
def __init__(self, entry):
# t("NativeMethodInfo.__init__")
super(NativeMethodInfo,self).__init__(entry)
blob = self.blob
cmethod = Types.to_type(blob, Types.cmethodp_t)
nmethod = Types.to_type(blob, Types.nmethodp_t)
self.methodptr = cmethod['_method']
const_method = self.methodptr['_constMethod']
bcbase = Types.cast_bytep(const_method + 1)
self.code_begin = Types.as_long(blob['_code_begin'])
# get bc and line number info from method
self.cache_method_info()
# subclasses need to compute their method pointer
def get_methodptr(self):
return self.methodptr
def format_method_name(self, method):
name = method.get_name()
return ("[native offset=0x%x] %s" % (self.pc - self.code_begin, name))
def method_name(self):
return self.format_method_name(self.method)
# info for interpreted frame
class InterpretedMethodInfo(JavaMethodInfo):
def __init__(self, entry, bcp):
super(InterpretedMethodInfo,self).__init__(entry)
# interpreter frames store methodptr in slot 3
methodptr_offset = FrameConstants.interpreter_frame_method_offset() * 8
methodptr_addr = gdb.Value((self.bp + methodptr_offset) & 0xffffffffffffffff)
methodptr_slot = methodptr_addr.cast(gdb.lookup_type("Method").pointer().pointer())
self.methodptr = methodptr_slot.dereference()
# bytecode immediately follows const method
const_method = self.methodptr['_constMethod']
bcbase = Types.cast_bytep(const_method + 1)
# debug_write("@@ bcbase = 0x%x\n" % Types.as_long(bcbase))
bcp_offset = FrameConstants.interpreter_frame_bcp_offset() * 8
if bcp is None:
# interpreter frames store bytecodeptr in slot 8
bcp_addr = gdb.Value((self.bp + bcp_offset) & 0xffffffffffffffff)
bcp_val = Types.cast_bytep(Types.load_ptr(bcp_addr))
else:
bcp_val = Types.cast_bytep(bcp)
# sanity check the register value -- it is sometimes off
# when returning from a call out
bcoff = Types.as_long(bcp_val - bcbase)
if bcoff < 0 or bcoff >= 0x10000:
# use the value in the frame slot
bcp_addr = gdb.Value((self.bp + bcp_offset) & 0xffffffffffffffff)
bcp_val = Types.cast_bytep(Types.load_ptr(bcp_addr))
self.bcoff = Types.as_long(bcp_val - bcbase)
# debug_write("@@ bcoff = 0x%x\n" % self.bcoff)
# line number table immediately following bytecode
bytecode_size = Types.as_int(const_method['_code_size'])
self.is_native = (bytecode_size == 0)
# n.b. data in compressed line_number_table block is u_char
# debug_write("bytecode_size = 0x%x\n" % bytecode_size)
lnbase = Types.cast_bytep(bcbase + bytecode_size)
# debug_write("lnbase = 0x%x\n" % Types.as_long(lnbase))
self.line_number_reader = LineReader(lnbase)
self.cache_method_info()
def get_methodptr(self):
return self.methodptr
def format_method_name(self, method):
name = method.get_name()
if self.is_native:
return "[native] " + name
else:
return ("[interpreted: bc = %d] " % self.bcoff) + name
def line(self):
line = self.line_number_reader.bc_to_line(self.bcoff)
# debug_write("bc_to_line(%d) = %d\n" % (self.bcoff, line))
if line < 0:
line = None
return line
def decorate(self, frame):
method = self.get_method()
return [ OpenJDKFrameDecorator(frame, self.format_method_name(method), method.get_klass_path(), self.line()) ]
class OpenJDKUnwinderCacheEntry(object):
def __init__(self, blob, sp, pc, bp, bcp, name, codetype):
# t("OpenJDKUnwinderCacheEntry.__init__")
self.blob = blob
self.sp = sp
self.pc = pc
self.bp = bp
self.codetype = codetype
try:
if codetype == "compiled":
self.method_info = CompiledMethodInfo(self)
elif codetype == "native":
self.method_info = NativeMethodInfo(self)
elif codetype == "interpreted":
self.method_info = InterpretedMethodInfo(self, bcp)
elif codetype == "stub":
self.method_info = StubMethodInfo(self, name)
else:
self.method_info = None
except Exception as arg:
gdb.write("!!! failed to cache info for %s frame [pc: 0x%x sp:0x%x bp 0x%x] !!!\n!!! %s !!!\n" % (codetype, pc, sp, bp, arg))
self.method_info = None
# an unwinder class, an instance of which can be registered with gdb
# to handle unwinding of frames.
class OpenJDKUnwinder(Unwinder):
def __init__(self):
# t("OpenJDKUnwinder.__init__")
super(OpenJDKUnwinder, self).__init__("OpenJDKUnwinder")
# blob name will be in format '0xHexDigits "AlphaNumSpaces"'
self.matcher=re.compile('^0x[a-fA-F0-9]+ "(.*)"$')
self.unwindercache = {}
self.invocations = {}
# the method that gets called by the pyuw_sniffer
def __call__(self, pending_frame):
# sometimes when we call into python gdb routines
# the call tries to re-establish the frame and ends
# up calling the frame sniffer recursively
#
# so use a list keyed by thread to avoid recursive calls
# t("OpenJDKUnwinder.__call__")
thread = gdb.selected_thread()
if self.invocations.get(thread) != None:
# debug_write("!!! blocked %s !!!\n" % str(thread))
return None
try:
# debug_write("!!! blocking %s !!!\n" % str(thread))
self.invocations[thread] = thread
result = self.call_sub(pending_frame)
# debug_write("!!! unblocking %s !!!\n" % str(thread))
self.invocations[thread] = None
return result
except Exception as arg:
gdb.write("!!! __call__ oops %s !!!\n" % arg)
# debug_write("!!! unblocking %s !!!\n" % str(thread))
self.invocations[thread] = None
return None
def call_sub(self, pending_frame):
# t("OpenJDKUnwinder.__call_sub__")
# debug_write("@@ reading pending frame registers\n")
pc = pending_frame.read_register('rip')
# debug_write("@@ pc = 0x%x\n" % Types.as_long(pc))
sp = pending_frame.read_register('rsp')
# debug_write("@@ sp = 0x%x\n" % Types.as_long(sp))
bp = pending_frame.read_register('rbp')
# debug_write("@@ bp = 0x%x\n" % Types.as_long(bp))
try:
if not CodeCache.inrange(pc):
# t("not CodeCache.inrange(0x%x)\n" % pc)
return None
except Exception as arg:
# debug_write("@@ %s\n" % arg)
return None
if _dump_frame:
debug_write(" pc = 0x%x\n" % Types.as_long(pc))
debug_write(" sp = 0x%x\n" % Types.as_long(sp))
debug_write(" bp = 0x%x\n" % Types.as_long(bp))
# this is a Java frame so we need to return unwind info
#
# the interpreter keeps the bytecode pointer (bcp) in a
# register and saves it to the stack when it makes a call to
# another Java method or to C++. if the current frame has a
# bcp register value then we pass it in case the pending frame
# is an intrepreter frame. that ensures we use the most up to
# date bcp when the inferior has stopped at the top level in a
# Java interpreted frame. it also saves us doing a redundant
# stack read when the pending frame sits below a non-JITted
# (C++) frame. n.b. if the current frame is a JITted frame
# (i.e. one that we have already unwound) then rbp will not be
# present. that's ok because the frame decorator can still
# find the latest bcp value on the stack.
bcp = pending_frame.read_register('r13')
try:
# convert returned value to a python int to force a check that
# the register is defined. if not this will except
bcp = gdb.Value(int(bcp))
# debug_write("@@ bcp = 0x%x\n" % Types.as_long(bcp))
except Exception as arg:
# debug_write("@@ !!! call_sub oops %s !!! \n" % arg)
bcp = None
# debug_write("@@ bcp = None\n")
# t("blob = CodeCache.findblob(pc)")
blob = CodeCache.findblob(pc)
# t("if blob is None:")
if blob is None:
# t("return None")
return None
# if the blob is an nmethod then we use the frame
# size to identify the frame base otherwise we
# use the value in rbp
# t("name = str(blob['_name'])")
name = str(blob['_name'])
# blob name will be in format '0xHexDigits "AlphaNumSpaces"'
# and we just want the bit between the quotes
m = self.matcher.match(name)
if not m is None:
# debug_write("@@ m.group(1) == %s\n" % m.group(1))
name = m.group(1)
if name == "nmethod":
# debug_write("@@ compiled %s\n" % name)
codetype = 'compiled'
# TODO -- need to check if frame is complete
# i.e. if ((char *)pc - (char *)blob) > blob['_code_begin'] + blob['_frame_complete_offset']
# if not then we have not pushed a frame.
# what do we do then? use SP as BP???
frame_size = blob['_frame_size']
# debug_write("@@ frame_size = 0x%x\n" % int(frame_size))
# n.b. frame_size includes stacked rbp and rip hence the -2
bp = sp + ((frame_size - 2) * 8)
# debug_write("@@ revised bp = 0x%x\n" % Types.as_long(bp))
elif name == "native nmethod":
# debug_write("@@ native %s \n" % name)
codetype = "native"
elif name == "Interpreter":
# debug_write("@@ interpreted %s\n" %name)
codetype = "interpreted"
elif name[:4] == "Stub":
# debug_write("@@ stub %s\n" % name)
codetype = "stub"
else:
# debug_write("@@ unknown %s\n" % name)
codetype = "unknown"
# cache details of the current frame
x = Types.as_long(sp)
# debug_write("@@ add %s cache entry for blob 0x%x at unwindercache[0x%x]\n" % (codetype, blob, x))
self.unwindercache[x] = OpenJDKUnwinderCacheEntry(blob, sp, pc, bp, bcp, name, codetype)
# t("next_bp = Types.load_long(bp)")
next_bp = Types.load_long(bp)
# t("next_pc = Types.load_long(bp + 8)")
next_pc = Types.load_long(bp + 8)
# next_sp is normally just 2 words below current bp
# but for interpreted frames we need to skip locals
# so we pull caller_sp from the frame
if codetype == "interpreted":
interpreter_frame_sender_sp_offset = FrameConstants.interpreter_frame_sender_sp_offset() * 8
# interpreter frames store sender sp in slot 1
next_sp = Types.load_long(bp + interpreter_frame_sender_sp_offset)
else:
sender_sp_offset = FrameConstants.sender_sp_offset() * 8
next_sp = bp + sender_sp_offset
# create unwind info for this frame
# t("frameid = OpenJDKFrameId(...)")
frameid = OpenJDKFrameId(Types.to_voidp(next_sp),
Types.to_type(next_pc, pc.type))
# debug_write("@@ created frame id\n")
# t("unwind_info = pending_frame.create_unwind_info(frameid)")
unwind_info = pending_frame.create_unwind_info(frameid)
# debug_write("@@ created unwind info\n")
# debug_write("@@ next_bp = 0x%x\n" % next_bp)
# debug_write("@@ next_pc = 0x%x\n" % next_pc)
# debug_write("@@ next_sp = 0x%x\n" % next_sp)
# we must calculate pc, sp and bp.
#
# for now we only add the minimum of registers that we know
# are valid.
unwind_info.add_saved_register('rip', next_pc)
unwind_info.add_saved_register('rsp', next_sp)
unwind_info.add_saved_register('rbp', next_bp)
if _dump_frame:
debug_write("next pc = 0x%x\n" % Types.as_long(next_pc))
debug_write("next sp = 0x%x\n" % Types.as_long(next_sp))
debug_write("next bp = 0x%x\n" % Types.as_long(next_bp))
# t("return unwind_info")
return unwind_info
# register the unwinder globally [probably really needs to be
# registered solely with libjvm.so]
def register_unwinder():
unwinder = None
if _have_unwinder:
unwinder = OpenJDKUnwinder()
gdb.unwinder.register_unwinder(None, unwinder, replace=True)
filt = OpenJDKFrameFilter(unwinder)
gdb.frame_filters[filt.name] = filt
register_unwinder()