From 8a06af5f88e07bf3723f06611d69466944079439 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 14 Oct 2020 16:41:07 -0700 Subject: Mostly recover a Ruby stack trace from a core file Update the lldb script so it can mostly recover a Ruby stack trace from a core file. It's still missing line numbers and dealing with CFUNCs, but you use it like this: ``` (lldb) rbbt ec rb_control_frame_t TYPE 0x7f6fd6555fa0 EVAL ./bootstraptest/runner.rb error!! 0x7f6fd6555f68 METHOD ./bootstraptest/runner.rb main 0x7f6fd6555f30 METHOD ./bootstraptest/runner.rb in_temporary_working_directory 0x7f6fd6555ef8 METHOD /home/aaron/git/ruby/lib/tmpdir.rb mktmpdir 0x7f6fd6555ec0 BLOCK ./bootstraptest/runner.rb block in in_temporary_working_directory 0x7f6fd6555e88 CFUNC 0x7f6fd6555e50 BLOCK ./bootstraptest/runner.rb block (2 levels) in in_temporary_working_directory 0x7f6fd6555e18 BLOCK ./bootstraptest/runner.rb block in main 0x7f6fd6555de0 METHOD ./bootstraptest/runner.rb exec_test 0x7f6fd6555da8 CFUNC 0x7f6fd6555d70 BLOCK ./bootstraptest/runner.rb block in exec_test 0x7f6fd6555d38 CFUNC 0x7f6fd6555d00 TOP /home/aaron/git/ruby/bootstraptest/test_insns.rb error!! 0x7f6fd6555cc8 CFUNC 0x7f6fd6555c90 BLOCK /home/aaron/git/ruby/bootstraptest/test_insns.rb block in 0x7f6fd6555c58 METHOD ./bootstraptest/runner.rb assert_equal 0x7f6fd6555c20 METHOD ./bootstraptest/runner.rb assert_check 0x7f6fd6555be8 METHOD ./bootstraptest/runner.rb show_progress 0x7f6fd6555bb0 METHOD ./bootstraptest/runner.rb with_stderr 0x7f6fd6555b78 BLOCK ./bootstraptest/runner.rb block in show_progress 0x7f6fd6555b40 BLOCK ./bootstraptest/runner.rb block in assert_check 0x7f6fd6555b08 METHOD ./bootstraptest/runner.rb get_result_string 0x7f6fd6555ad0 METHOD ./bootstraptest/runner.rb make_srcfile 0x7f6fd6555a98 CFUNC 0x7f6fd6555a60 BLOCK ./bootstraptest/runner.rb block in make_srcfile ``` Getting the main execution context is difficult (it is stored in a thread local) so for now you must supply an ec and this will make a backtrace --- misc/lldb_cruby.py | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) (limited to 'misc') diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py index ff66fe476c..075612735c 100755 --- a/misc/lldb_cruby.py +++ b/misc/lldb_cruby.py @@ -14,6 +14,149 @@ import shlex HEAP_PAGE_ALIGN_LOG = 14 HEAP_PAGE_ALIGN_MASK = (~(~0 << HEAP_PAGE_ALIGN_LOG)) +class BackTrace: + VM_FRAME_MAGIC_METHOD = 0x11110001 + VM_FRAME_MAGIC_BLOCK = 0x22220001 + VM_FRAME_MAGIC_CLASS = 0x33330001 + VM_FRAME_MAGIC_TOP = 0x44440001 + VM_FRAME_MAGIC_CFUNC = 0x55550001 + VM_FRAME_MAGIC_IFUNC = 0x66660001 + VM_FRAME_MAGIC_EVAL = 0x77770001 + VM_FRAME_MAGIC_RESCUE = 0x78880001 + VM_FRAME_MAGIC_DUMMY = 0x79990001 + + VM_FRAME_MAGIC_MASK = 0x7fff0001 + + VM_FRAME_MAGIC_NAME = { + VM_FRAME_MAGIC_TOP: "TOP", + VM_FRAME_MAGIC_METHOD: "METHOD", + VM_FRAME_MAGIC_CLASS: "CLASS", + VM_FRAME_MAGIC_BLOCK: "BLOCK", + VM_FRAME_MAGIC_CFUNC: "CFUNC", + VM_FRAME_MAGIC_IFUNC: "IFUNC", + VM_FRAME_MAGIC_EVAL: "EVAL", + VM_FRAME_MAGIC_RESCUE: "RESCUE", + 0: "-----" + } + + def __init__(self, debugger, command, result, internal_dict): + self.debugger = debugger + self.command = command + self.result = result + + self.target = debugger.GetSelectedTarget() + self.process = self.target.GetProcess() + self.thread = self.process.GetSelectedThread() + self.frame = self.thread.GetSelectedFrame() + self.tRString = self.target.FindFirstType("struct RString").GetPointerType() + self.tRArray = self.target.FindFirstType("struct RArray").GetPointerType() + + rb_cft_len = len("rb_control_frame_t") + method_type_length = sorted(map(len, self.VM_FRAME_MAGIC_NAME.values()), reverse=True)[0] + # cfp address, method type, function name + self.fmt = "%%-%ds %%-%ds %%s" % (rb_cft_len, method_type_length) + + def vm_frame_magic(self, cfp): + ep = cfp.GetValueForExpressionPath("->ep") + frame_type = ep.GetChildAtIndex(0).GetValueAsUnsigned() & self.VM_FRAME_MAGIC_MASK + return self.VM_FRAME_MAGIC_NAME.get(frame_type, "(none)") + + def rb_iseq_path_str(self, iseq): + tRBasic = self.target.FindFirstType("struct RBasic").GetPointerType() + + pathobj = iseq.GetValueForExpressionPath("->body->location.pathobj") + pathobj = pathobj.Cast(tRBasic) + flags = pathobj.GetValueForExpressionPath("->flags").GetValueAsUnsigned() + flType = flags & RUBY_T_MASK + + if flType == RUBY_T_ARRAY: + pathobj = pathobj.Cast(self.tRArray) + + if flags & RUBY_FL_USER1: + len = ((flags & (RUBY_FL_USER3|RUBY_FL_USER4)) >> (RUBY_FL_USHIFT+3)) + ptr = pathobj.GetValueForExpressionPath("->as.ary") + else: + len = pathobj.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned() + ptr = pathobj.GetValueForExpressionPath("->as.heap.ptr") + + pathobj = ptr.GetChildAtIndex(0) + + pathobj = pathobj.Cast(self.tRString) + ptr, len = string2cstr(pathobj) + err = lldb.SBError() + path = self.target.process.ReadMemory(ptr, len, err) + if err.Success(): + return path.decode("utf-8") + else: + return "unknown" + + def dump_iseq_frame(self, cfp, iseq): + m = self.vm_frame_magic(cfp) + + if iseq.GetValueAsUnsigned(): + iseq_label = iseq.GetValueForExpressionPath("->body->location.label") + path = self.rb_iseq_path_str(iseq) + ptr, len = string2cstr(iseq_label.Cast(self.tRString)) + + err = lldb.SBError() + iseq_name = self.target.process.ReadMemory(ptr, len, err) + if err.Success(): + iseq_name = iseq_name.decode("utf-8") + else: + iseq_name = "error!!" + + else: + print("No iseq", file=self.result) + + print(self.fmt % (("%0#12x" % cfp.GetAddress().GetLoadAddress(self.target)), m, "%s %s" % (path, iseq_name)), file=self.result) + + def dump_cfunc_frame(self, cfp): + print(self.fmt % ("%0#12x" % (cfp.GetAddress().GetLoadAddress(self.target)), "CFUNC", ""), file=self.result) + + def print_bt(self, ec): + tRbExecutionContext_t = self.target.FindFirstType("rb_execution_context_t") + ec = ec.Cast(tRbExecutionContext_t.GetPointerType()) + vm_stack = ec.GetValueForExpressionPath("->vm_stack") + vm_stack_size = ec.GetValueForExpressionPath("->vm_stack_size") + + last_cfp_frame = ec.GetValueForExpressionPath("->cfp") + cfp_type_p = last_cfp_frame.GetType() + + stack_top = vm_stack.GetValueAsUnsigned() + ( + vm_stack_size.GetValueAsUnsigned() * vm_stack.GetType().GetByteSize()) + + cfp_frame_size = cfp_type_p.GetPointeeType().GetByteSize() + + start_cfp = stack_top + # Skip dummy frames + start_cfp -= cfp_frame_size + start_cfp -= cfp_frame_size + + last_cfp = last_cfp_frame.GetValueAsUnsigned() + + size = ((start_cfp - last_cfp) / cfp_frame_size) + 1 + + print(self.fmt % ("rb_control_frame_t", "TYPE", ""), file=self.result) + + curr_addr = start_cfp + + while curr_addr >= last_cfp: + cfp = self.target.CreateValueFromAddress("cfp", lldb.SBAddress(curr_addr, self.target), cfp_type_p.GetPointeeType()) + ep = cfp.GetValueForExpressionPath("->ep") + iseq = cfp.GetValueForExpressionPath("->iseq") + + frame_type = ep.GetChildAtIndex(0).GetValueAsUnsigned() & self.VM_FRAME_MAGIC_MASK + + if iseq.GetValueAsUnsigned(): + pc = cfp.GetValueForExpressionPath("->pc") + if pc.GetValueAsUnsigned(): + self.dump_iseq_frame(cfp, iseq) + else: + if frame_type == self.VM_FRAME_MAGIC_CFUNC: + self.dump_cfunc_frame(cfp) + + curr_addr -= cfp_frame_size + def lldb_init(debugger): target = debugger.GetSelectedTarget() global SIZEOF_VALUE @@ -360,6 +503,25 @@ def dump_node(debugger, command, ctx, result, internal_dict): dump = ctx.frame.EvaluateExpression("(struct RString*)rb_parser_dump_tree((NODE*)(%s), 0)" % node) output_string(ctx, result, dump) +def rb_backtrace(debugger, command, result, internal_dict): + bt = BackTrace(debugger, command, result, internal_dict) + frame = bt.frame + + if command: + if frame.IsValid(): + val = frame.EvaluateExpression(command) + else: + val = target.EvaluateExpression(command) + + error = val.GetError() + if error.Fail(): + print >> result, error + return + else: + print("Need an EC for now") + + bt.print_bt(val) + def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand("command script add -f lldb_cruby.lldb_rp rp") debugger.HandleCommand("command script add -f lldb_cruby.count_objects rb_count_objects") @@ -367,5 +529,6 @@ def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand("command script add -f lldb_cruby.dump_node dump_node") debugger.HandleCommand("command script add -f lldb_cruby.heap_page heap_page") debugger.HandleCommand("command script add -f lldb_cruby.heap_page_body heap_page_body") + debugger.HandleCommand("command script add -f lldb_cruby.rb_backtrace rbbt") lldb_init(debugger) print("lldb scripts for ruby has been installed.") -- cgit v1.2.3