1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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 <top (required)>
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
This commit is contained in:
Aaron Patterson 2020-10-14 16:41:07 -07:00
parent 2e8b5968e1
commit 8a06af5f88
No known key found for this signature in database
GPG key ID: 953170BCB4FFAFC6

View file

@ -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.")