[ci skip] Move rp helper to new LLDB format

For now, the old function still exists as `old_rp`, in order to debug
issues with this command.
This commit is contained in:
Matt Valentine-House 2023-03-16 09:54:36 +00:00
parent cc68d692f8
commit c7862c68eb
Notes: git 2023-03-17 20:05:21 +00:00
7 changed files with 455 additions and 16 deletions

View File

@ -727,13 +727,13 @@ def __lldb_init_module(debugger, internal_dict):
# Register all classes that subclass RbBaseCommand
for memname, mem in inspect.getmembers(sys.modules["lldb_rb.rb_base_command"]):
if inspect.isclass(mem):
if memname == "RbBaseCommand":
for sclass in mem.__subclasses__():
sclass.register_lldb_command(debugger, f"{__name__}.{sclass.__module__}")
## FUNCTION INITS - These should be removed when converted to class commands
debugger.HandleCommand("command script add -f lldb_cruby.lldb_rp rp")
debugger.HandleCommand("command script add -f lldb_cruby.lldb_rp old_rp")
debugger.HandleCommand("command script add -f lldb_cruby.count_objects rb_count_objects")
debugger.HandleCommand("command script add -f lldb_cruby.stack_dump_raw SDR")
debugger.HandleCommand("command script add -f lldb_cruby.dump_node dump_node")

View File

@ -0,0 +1,16 @@
import lldb
from lldb_rb.constants import *
from lldb_rb.utils import *
from lldb_rb.rb_base_command import RbBaseCommand
class RbID2StrCommand(RbBaseCommand):
program = "rp"
help_string = "convert and print a Ruby ID to a C string and print it to the LLDB console"
def call(self, debugger, command, exe_ctx, result):
val = self.frame.EvaluateExpression(command)
inspector = RbInspector(debugger, result, self.ruby_globals)
inspector.inspect(val)

View File

@ -2,3 +2,5 @@ HEAP_PAGE_ALIGN_LOG = 16
HEAP_PAGE_ALIGN_MASK = (~(~0 << HEAP_PAGE_ALIGN_LOG))
HEAP_PAGE_ALIGN = (1 << HEAP_PAGE_ALIGN_LOG)
HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN
IMEMO_MASK = 0x0F

View File

@ -0,0 +1,8 @@
class LLDBInterface:
def build_environment(self, debugger):
self.debugger = debugger
self.target = debugger.GetSelectedTarget()
self.process = self.target.GetProcess()
self.thread = self.process.GetSelectedThread()
self.frame = self.thread.GetSelectedFrame()

View File

@ -1,7 +1,9 @@
import lldb
from pydoc import locate
from lldb_rb.constants import *
from lldb_rb.utils import *
class RbBaseCommand:
class RbBaseCommand(LLDBInterface):
@classmethod
def register_lldb_command(cls, debugger, module_name):
# Add any commands contained in this module to LLDB
@ -53,16 +55,3 @@ class RbBaseCommand:
def get_long_help(self):
return self.__class__.help_string
def build_environment(self, debugger):
self.target = debugger.GetSelectedTarget()
self.process = self.target.GetProcess()
self.thread = self.process.GetSelectedThread()
self.frame = self.thread.GetSelectedFrame()
def _append_command_output(self, debugger, command, result):
output1 = result.GetOutput()
debugger.GetCommandInterpreter().HandleCommand(command, result)
output2 = result.GetOutput()
result.Clear()
result.write(output1)
result.write(output2)

View File

@ -0,0 +1,140 @@
import lldb
from lldb_rb.lldb_interface import LLDBInterface
from lldb_rb.constants import *
class HeapPage(LLDBInterface):
def __init__(self, debugger, val):
self.build_environment(debugger)
self.page_type = self.target.FindFirstType("struct heap_page").GetPointerType()
self.val = val
def heap_page_body(self, command, ctx, result, internal_dict):
process = self.target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
val = frame.EvaluateExpression(command)
page = self.get_page_body(val)
print("Page body address: ", page.GetAddress(), file=result)
print(page, file=result)
def get_page_body(self, val):
tHeapPageBody = self.target.FindFirstType("struct heap_page_body")
addr = val.GetValueAsUnsigned()
page_addr = addr & ~(HEAP_PAGE_ALIGN_MASK)
address = lldb.SBAddress(page_addr, self.target)
return self.target.CreateValueFromAddress("page", address, tHeapPageBody)
def get_page_raw(self, val):
body = self.get_page_body(val)
return body.GetValueForExpressionPath("->header.page")
def to_heap_page_struct(self):
pagePtr = self.get_page_raw(self.val)
return pagePtr.Cast(self.page_type)
class RbObject(LLDBInterface):
def __init__(self, ptr, debugger, ruby_globals):
self.build_environment(debugger)
self.ruby_globals = ruby_globals
self.flUser1 = self.ruby_globals["RUBY_FL_USER1"]
self.flUser2 = self.ruby_globals["RUBY_FL_USER2"]
self.flUser3 = self.ruby_globals["RUBY_FL_USER3"]
self.flUser4 = self.ruby_globals["RUBY_FL_USER4"]
self.flUser5 = self.ruby_globals["RUBY_FL_USER5"]
self.flUser6 = self.ruby_globals["RUBY_FL_USER6"]
self.flUser7 = self.ruby_globals["RUBY_FL_USER7"]
self.flUser8 = self.ruby_globals["RUBY_FL_USER8"]
self.flUser9 = self.ruby_globals["RUBY_FL_USER9"]
self.flUshift = self.ruby_globals["RUBY_FL_USHIFT"]
self.tRBasic = self.target.FindFirstType("struct RBasic").GetPointerType()
self.tRValue = self.target.FindFirstType("struct RVALUE")
self.val = ptr.Cast(self.tRBasic)
self.page = HeapPage(self.debugger, self.val)
self.flags = self.val.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
self.type = None
self.type_name = ""
def check_bits(self, bitmap_name, bitmap_index, bitmap_bit, v):
page = self.page.to_heap_page_struct()
bits = page.GetChildMemberWithName(bitmap_name)
plane = bits.GetChildAtIndex(bitmap_index).GetValueAsUnsigned()
if (plane & bitmap_bit) != 0:
return v
else:
return ' '
def dump_bits(self, result, end = "\n"):
tRValue = self.target.FindFirstType("struct RVALUE")
tUintPtr = self.target.FindFirstType("uintptr_t") # bits_t
num_in_page = (self.val.GetValueAsUnsigned() & HEAP_PAGE_ALIGN_MASK) // tRValue.GetByteSize();
bits_bitlength = tUintPtr.GetByteSize() * 8
bitmap_index = num_in_page // bits_bitlength
bitmap_offset = num_in_page & (bits_bitlength - 1)
bitmap_bit = 1 << bitmap_offset
page = self.page.to_heap_page_struct()
print("bits: [%s%s%s%s%s]" % (
self.check_bits("uncollectible_bits", bitmap_index, bitmap_bit, "L"),
self.check_bits("mark_bits", bitmap_index, bitmap_bit, "M"),
self.check_bits("pinned_bits", bitmap_index, bitmap_bit, "P"),
self.check_bits("marking_bits", bitmap_index, bitmap_bit, "R"),
self.check_bits("wb_unprotected_bits", bitmap_index, bitmap_bit, "U"),
), end=end, file=result)
def promoted_p(self):
rbFlPromoted = self.ruby_globals["RUBY_FL_PROMOTED"]
return (self.flags & rbFlPromoted) == rbFlPromoted
def frozen_p(self):
rbFlFreeze = self.ruby_globals["RUBY_FL_FREEZE"]
return (self.flags & rbFlFreeze) == rbFlFreeze
def is_type(self, type_name):
if self.type is None:
flTMask = self.ruby_globals["RUBY_T_MASK"]
flType = self.flags & flTMask
self.type = flType
if self.type == self.ruby_globals[type_name]:
self.type_name = type_name
return True
else:
return False
def ary_ptr(self):
if self.flags & self.ruby_globals["RUBY_FL_USER1"]:
ptr = self.val.GetValueForExpressionPath("->as.ary")
else:
ptr = self.val.GetValueForExpressionPath("->as.heap.ptr")
return ptr
def ary_len(self):
if self.flags & self.flUser1:
len = ((self.flags &
(self.flUser3 | self.flUser4 | self.flUser5 | self.flUser6 |
self.flUser7 | self.flUser8 | self.flUser9)
) >> (self.flUshift + 3))
else:
len = self.val.GetValueForExpressionPath("->as.heap.len")
return len
def bignum_len(self):
if self.flags & flUser2:
len = ((self.flags &
(self.flUser3 | self.flUser4 | self.flUser5)
) >> (self.flUshift + 3))
else:
len = self.val.GetValueForExpressionPath("->as.heap.len")
return len

284
misc/lldb_rb/utils.py Normal file
View File

@ -0,0 +1,284 @@
from lldb_rb.lldb_interface import LLDBInterface
from lldb_rb.rb_heap_structs import HeapPage, RbObject
from lldb_rb.constants import *
class RbInspector(LLDBInterface):
def __init__(self, debugger, result, ruby_globals):
self.build_environment(debugger)
self.result = result
self.ruby_globals = ruby_globals
def _append_command_output(self, command):
output1 = self.result.GetOutput()
self.debugger.GetCommandInterpreter().HandleCommand(command, self.result)
output2 = self.result.GetOutput()
self.result.Clear()
self.result.write(output1)
self.result.write(output2)
def string2cstr(self, rstring):
"""Returns the pointer to the C-string in the given String object"""
if rstring.TypeIsPointerType():
rstring = rstring.Dereference()
flags = rstring.GetValueForExpressionPath(".basic->flags").unsigned
if flags & self.ruby_globals["RUBY_FL_USER1"]:
cptr = int(rstring.GetValueForExpressionPath(".as.heap.ptr").value, 0)
clen = int(rstring.GetValueForExpressionPath(".as.heap.len").value, 0)
else:
cptr = int(rstring.GetValueForExpressionPath(".as.embed.ary").location, 0)
clen = int(rstring.GetValueForExpressionPath(".as.embed.len").value, 0)
return cptr, clen
def output_string(self, rstring):
cptr, clen = self.string2cstr(rstring)
expr = "print *(const char (*)[%d])%0#x" % (clen, cptr)
self._append_command_output(expr)
def fixnum_p(self, x):
return x & self.ruby_globals["RUBY_FIXNUM_FLAG"] != 0
def flonum_p(self, x):
return (x & self.ruby_globals["RUBY_FLONUM_MASK"]) == self.ruby_globals["RUBY_FLONUM_FLAG"]
def static_sym_p(self, x):
special_shift = self.ruby_globals["RUBY_SPECIAL_SHIFT"]
symbol_flag = self.ruby_globals["RUBY_SYMBOL_FLAG"]
return (x & ~(~0 << special_shift)) == symbol_flag
def generic_inspect(self, val, rtype):
tRType = self.target.FindFirstType("struct %s" % rtype).GetPointerType()
val = val.Cast(tRType)
self._append_command_output("p *(struct %s *) %0#x" % (rtype, val.GetValueAsUnsigned()))
def inspect(self, val):
rbTrue = self.ruby_globals["RUBY_Qtrue"]
rbFalse = self.ruby_globals["RUBY_Qfalse"]
rbNil = self.ruby_globals["RUBY_Qnil"]
rbUndef = self.ruby_globals["RUBY_Qundef"]
rbImmediateMask = self.ruby_globals["RUBY_IMMEDIATE_MASK"]
num = val.GetValueAsSigned()
if num == rbFalse:
print('false', file=self.result)
elif num == rbTrue:
print('true', file=self.result)
elif num == rbNil:
print('nil', file=self.result)
elif num == rbUndef:
print('undef', file=self.result)
elif self.fixnum_p(num):
print(num >> 1, file=self.result)
elif self.flonum_p(num):
self._append_command_output("print rb_float_value(%0#x)" % val.GetValueAsUnsigned())
elif self.static_sym_p(num):
if num < 128:
print("T_SYMBOL: %c" % num, file=self.result)
else:
print("T_SYMBOL: (%x)" % num, file=self.result)
self._append_command_output("p rb_id2name(%0#x)" % (num >> 8))
elif num & rbImmediateMask:
print('immediate(%x)' % num, file=self.result)
else:
rval = RbObject(val, self.debugger, self.ruby_globals)
rval.dump_bits(self.result)
flaginfo = ""
if rval.promoted_p():
flaginfo += "[PROMOTED] "
if rval.frozen_p():
flaginfo += "[FROZEN] "
if rval.is_type("RUBY_T_NONE"):
print('T_NONE: %s%s' % (flaginfo, val.Dereference()), file=self.result)
elif rval.is_type("RUBY_T_NIL"):
print('T_NIL: %s%s' % (flaginfo, val.Dereference()), file=self.result)
elif rval.is_type("RUBY_T_OBJECT"):
self.result.write('T_OBJECT: %s' % flaginfo)
self._append_command_output("print *(struct RObject*)%0#x" % val.GetValueAsUnsigned())
elif (rval.is_type("RUBY_T_CLASS") or
rval.is_type("RUBY_T_MODULE") or
rval.is_type("RUBY_T_ICLASS")):
self.result.write('T_%s: %s' % (rval.type_name.split('_')[-1], flaginfo))
tRClass = self.target.FindFirstType("struct RClass")
self._append_command_output("print *(struct RClass*)%0#x" % val.GetValueAsUnsigned())
if not val.Cast(tRClass).GetChildMemberWithName("ptr").IsValid():
self._append_command_output(
"print *(struct rb_classext_struct*)%0#x" %
(val.GetValueAsUnsigned() + tRClass.GetByteSize())
)
elif rval.is_type("RUBY_T_STRING"):
self.result.write('T_STRING: %s' % flaginfo)
tRString = self.target.FindFirstType("struct RString").GetPointerType()
rb_enc_mask = self.ruby_globals["RUBY_ENCODING_MASK"]
rb_enc_shift = self.ruby_globals["RUBY_ENCODING_SHIFT"]
encidx = ((rval.flags & rb_enc_mask) >> rb_enc_shift)
encname = self.target.FindFirstType("enum ruby_preserved_encindex") \
.GetEnumMembers().GetTypeEnumMemberAtIndex(encidx) \
.GetName()
if encname is not None:
self.result.write('[%s] ' % encname[14:])
else:
self.result.write('[enc=%d] ' % encidx)
ptr, len = self.string2cstr(val.Cast(tRString))
if len == 0:
self.result.write("(empty)\n")
else:
self._append_command_output("print *(const char (*)[%d])%0#x" % (len, ptr))
elif rval.is_type("RUBY_T_SYMBOL"):
self.result.write('T_SYMBOL: %s' % flaginfo)
tRSymbol = self.target.FindFirstType("struct RSymbol").GetPointerType()
tRString = self.target.FindFirstType("struct RString").GetPointerType()
val = val.Cast(tRSymbol)
self._append_command_output(
'print (ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned())
self.output_string(val.GetValueForExpressionPath("->fstr").Cast(tRString))
elif rval.is_type("RUBY_T_ARRAY"):
tRArray = self.target.FindFirstType("struct RArray").GetPointerType()
len = rval.ary_len()
ptr = rval.ary_ptr()
self.result.write("T_ARRAY: %slen=%d" % (flaginfo, len))
if rval.flags & self.ruby_globals["RUBY_FL_USER1"]:
self.result.write(" (embed)")
elif rval.flags & self.ruby_globasl["RUBY_FL_USER2"]:
shared = val.GetValueForExpressionPath("->as.heap.aux.shared").GetValueAsUnsigned()
self.result.write(" (shared) shared=%016x" % shared)
else:
capa = val.GetValueForExpressionPath("->as.heap.aux.capa").GetValueAsSigned()
self.result.write(" (ownership) capa=%d" % capa)
if len == 0:
self.result.write(" {(empty)}\n")
else:
self.result.write("\n")
if ptr.GetValueAsSigned() == 0:
self._append_command_output(
"expression -fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned())
else:
self._append_command_output(
"expression -Z %d -fx -- (const VALUE*)%0#x" % (len, ptr.GetValueAsUnsigned()))
elif rval.is_type("RUBY_T_HASH"):
self.result.write("T_HASH: %s" % flaginfo)
self._append_command_output("p *(struct RHash *) %0#x" % val.GetValueAsUnsigned())
elif rval.is_type("RUBY_T_BIGNUM"):
tRBignum = self.target.FindFirstType("struct RBignum").GetPointerType()
sign = '-'
if (rval.flags & self.ruby_globals["RUBY_FL_USER1"]) != 0:
sign = '+'
len = rval.bignum_len()
if rval.flags & self.ruby_globals["RUBY_FL_USER2"]:
print("T_BIGNUM: sign=%s len=%d (embed)" % (sign, len), file=self.result)
self._append_command_output("print ((struct RBignum *) %0#x)->as.ary"
% val.GetValueAsUnsigned())
else:
print("T_BIGNUM: sign=%s len=%d" % (sign, len), file=self.result)
print(val.Dereference(), file=self.result)
self._append_command_output(
"expression -Z %x -fx -- (const BDIGIT*)((struct RBignum*)%d)->as.heap.digits" %
(len, val.GetValueAsUnsigned()))
elif rval.is_type("RUBY_T_FLOAT"):
self._append_command_output("print ((struct RFloat *)%d)->float_value"
% val.GetValueAsUnsigned())
elif rval.is_type("RUBY_T_RATIONAL"):
tRRational = self.target.FindFirstType("struct RRational").GetPointerType()
val = val.Cast(tRRational)
self.inspect(val.GetValueForExpressionPath("->num"))
output = self.result.GetOutput()
self.result.Clear()
self.result.write("(Rational) " + output.rstrip() + " / ")
self.inspect(val.GetValueForExpressionPath("->den"))
elif rval.is_type("RUBY_T_COMPLEX"):
tRComplex = self.target.FindFirstType("struct RComplex").GetPointerType()
val = val.Cast(tRComplex)
self.inspect(val.GetValueForExpressionPath("->real"))
real = self.result.GetOutput().rstrip()
self.result.Clear()
self.inspect(val.GetValueForExpressionPath("->imag"))
imag = self.result.GetOutput().rstrip()
self.result.Clear()
if not imag.startswith("-"):
imag = "+" + imag
print("(Complex) " + real + imag + "i", file=self.result)
elif rval.is_type("RUBY_T_REGEXP"):
tRRegex = self.target.FindFirstType("struct RRegexp").GetPointerType()
val = val.Cast(tRRegex)
print("(Regex) ->src {", file=self.result)
self.inspect(val.GetValueForExpressionPath("->src"))
print("}", file=self.result)
elif rval.is_type("RUBY_T_DATA"):
tRTypedData = self.target.FindFirstType("struct RTypedData").GetPointerType()
val = val.Cast(tRTypedData)
flag = val.GetValueForExpressionPath("->typed_flag")
if flag.GetValueAsUnsigned() == 1:
print("T_DATA: %s" %
val.GetValueForExpressionPath("->type->wrap_struct_name"),
file=self.result)
self._append_command_output(
"p *(struct RTypedData *) %0#x" % val.GetValueAsUnsigned())
else:
print("T_DATA:", file=self.result)
self._append_command_output(
"p *(struct RData *) %0#x" % val.GetValueAsUnsigned())
elif rval.is_type("RUBY_T_NODE"):
tRNode = self.target.FindFirstType("struct RNode").GetPointerType()
rbNodeTypeMask = self.ruby_globals["RUBY_NODE_TYPEMASK"]
rbNodeTypeShift = self.ruby_globals["RUBY_NODE_TYPESHIFT"]
nd_type = (flags & rbNodeTypeMask) >> rbNodeTypeShift
val = val.Cast(tRNode)
self._append_command_output("p (node_type) %d" % nd_type)
self._append_command_output("p *(struct RNode *) %0#x" % val.GetValueAsUnsigned())
elif rval.is_type("RUBY_T_IMEMO"):
imemo_type = ((rval.flags >> self.ruby_globals["RUBY_FL_USHIFT"])
& IMEMO_MASK)
print("T_IMEMO: ", file=self.result)
self._append_command_output("p (enum imemo_type) %d" % imemo_type)
self._append_command_output("p *(struct MEMO *) %0#x" % val.GetValueAsUnsigned())
elif rval.is_type("RUBY_T_FILE"):
self.generic_inspect(val, "RFile")
elif rval.is_type("RUBY_T_MOVED"):
self.generic_inspect(val, "RMoved")
elif rval.is_type("RUBY_T_MATCH"):
self.generic_inspect(val, "RMatch")
elif rval.is_type("RUBY_T_STRUCT"):
self.generic_inspect(val, "RStruct")
elif rval.is_type("RUBY_T_ZOMBIE"):
self.generic_inspect(val, "RZombie")
else:
print("Not-handled type %0#x" % rval.type, file=self.result)
print(val, file=self.result)