summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-rw-r--r--misc/.vscode/launch.json13
-rw-r--r--misc/.vscode/settings.json8
-rw-r--r--misc/.vscode/tasks.json14
-rw-r--r--misc/README1
-rw-r--r--misc/call_fuzzer.rb372
-rwxr-xr-xmisc/call_fuzzer.sh13
-rw-r--r--misc/gdb.py181
-rwxr-xr-xmisc/lldb_cruby.py124
-rw-r--r--misc/lldb_disasm.py13
-rw-r--r--misc/lldb_rb/commands/command_template.py30
-rw-r--r--misc/lldb_rb/commands/heap_page_command.py26
-rw-r--r--misc/lldb_rb/commands/print_flags_command.py31
-rw-r--r--misc/lldb_rb/commands/rb_id2str_command.py49
-rw-r--r--misc/lldb_rb/commands/rclass_ext_command.py14
-rw-r--r--misc/lldb_rb/commands/rp_command.py15
-rw-r--r--misc/lldb_rb/constants.py6
-rw-r--r--misc/lldb_rb/lldb_interface.py7
-rw-r--r--misc/lldb_rb/rb_base_command.py57
-rw-r--r--misc/lldb_rb/rb_heap_structs.py143
-rw-r--r--misc/lldb_rb/utils.py494
-rw-r--r--misc/lldb_yjit.py47
-rwxr-xr-xmisc/yjit_perf.py116
22 files changed, 1659 insertions, 115 deletions
diff --git a/misc/.vscode/launch.json b/misc/.vscode/launch.json
new file mode 100644
index 0000000000..51bfef09d7
--- /dev/null
+++ b/misc/.vscode/launch.json
@@ -0,0 +1,13 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "name": "Run ruby",
+ "request": "launch",
+ "program": "${workspaceFolder}/ruby",
+ "args": ["test.rb"],
+ "preLaunchTask": "${defaultBuildTask}"
+ }
+ ]
+}
diff --git a/misc/.vscode/settings.json b/misc/.vscode/settings.json
new file mode 100644
index 0000000000..7b1a38c536
--- /dev/null
+++ b/misc/.vscode/settings.json
@@ -0,0 +1,8 @@
+{
+ "rust-analyzer.cargo.features": [
+ "disasm",
+ ],
+ "rust-analyzer.cargo.unsetTest": [
+ "yjit",
+ ],
+}
diff --git a/misc/.vscode/tasks.json b/misc/.vscode/tasks.json
new file mode 100644
index 0000000000..045fe7e5c0
--- /dev/null
+++ b/misc/.vscode/tasks.json
@@ -0,0 +1,14 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "type": "shell",
+ "command": "make -j",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+}
diff --git a/misc/README b/misc/README
index 1728b42700..86b680e724 100644
--- a/misc/README
+++ b/misc/README
@@ -4,3 +4,4 @@ rb_optparse.zsh zsh completion script
ruby-style.el Ruby's C/C++ mode style for emacs
lldb_cruby.py LLDB port of debug utility
test_lldb_cruby.rb test file for LLDB port
+.vscode example VSCode config to debug Ruby
diff --git a/misc/call_fuzzer.rb b/misc/call_fuzzer.rb
new file mode 100644
index 0000000000..c3f9f90490
--- /dev/null
+++ b/misc/call_fuzzer.rb
@@ -0,0 +1,372 @@
+require 'optparse'
+require 'set'
+
+# Number of iterations to test
+num_iters = 10_000
+
+# Parse the command-line options
+OptionParser.new do |opts|
+ opts.on("--num-iters=N") do |n|
+ num_iters = n.to_i
+ end
+end.parse!
+
+# Format large numbers with comma separators for readability
+def format_number(pad, number)
+ s = number.to_s
+ i = s.index('.') || s.size
+ s.insert(i -= 3, ',') while i > 3
+ s.rjust(pad, ' ')
+end
+
+# Wrap an integer to pass as argument
+# We use this so we can have some object arguments
+class IntWrapper
+ def initialize(v)
+ # Force the object to have a random shape
+ if rand() < 50
+ @v0 = 1
+ end
+ if rand() < 50
+ @v1 = 1
+ end
+ if rand() < 50
+ @v2 = 1
+ end
+ if rand() < 50
+ @v3 = 1
+ end
+ if rand() < 50
+ @v4 = 1
+ end
+ if rand() < 50
+ @v5 = 1
+ end
+ if rand() < 50
+ @v6 = 1
+ end
+
+ @value = v
+ end
+
+ attr_reader :value
+end
+
+# Generate a random argument value, integer or string or object
+def sample_arg()
+ c = ['int', 'string', 'object'].sample()
+
+ if c == 'int'
+ return rand(0...100)
+ end
+
+ if c == 'string'
+ return 'f' * rand(0...100)
+ end
+
+ if c == 'object'
+ return IntWrapper.new(rand(0...100))
+ end
+
+ raise "should not get here"
+end
+
+# Evaluate the value of an argument with respect to the checksum
+def arg_val(arg)
+ if arg.kind_of? Integer
+ return arg
+ end
+
+ if arg.kind_of? String
+ return arg.length
+ end
+
+ if arg.kind_of? Object
+ return arg.value
+ end
+
+ raise "unknown arg type"
+end
+
+# List of parameters/arguments for a method
+class ParamList
+ def initialize()
+ self.sample_params()
+ self.sample_args()
+ end
+
+ # Sample/generate a random set of parameters for a method
+ def sample_params()
+ # Choose how many positional arguments to use, and how many are optional
+ num_pargs = rand(10)
+ @opt_parg_idx = rand(num_pargs)
+ @num_opt_pargs = rand(num_pargs + 1 - @opt_parg_idx)
+ @num_pargs_req = num_pargs - @num_opt_pargs
+ @pargs = (0...num_pargs).map do |i|
+ {
+ :name => "p#{i}",
+ :optional => (i >= @opt_parg_idx && i < @opt_parg_idx + @num_opt_pargs)
+ }
+ end
+
+ # Choose how many kwargs to use, and how many are optional
+ num_kwargs = rand(10)
+ @kwargs = (0...num_kwargs).map do |i|
+ {
+ :name => "k#{i}",
+ :optional => rand() < 0.5
+ }
+ end
+
+ # Choose whether to have rest parameters or not
+ @has_rest = @num_opt_pargs == 0 && rand() < 0.5
+ @has_kwrest = rand() < 0.25
+
+ # Choose whether to have a named block parameter or not
+ @has_block_param = rand() < 0.25
+ end
+
+ # Sample/generate a random set of arguments corresponding to the parameters
+ def sample_args()
+ # Choose how many positional args to pass
+ num_pargs_passed = rand(@num_pargs_req..@pargs.size)
+
+ # How many optional arguments will be filled
+ opt_pargs_filled = num_pargs_passed - @num_pargs_req
+
+ @pargs.each_with_index do |parg, i|
+ if parg[:optional]
+ parg[:default] = rand(100)
+ end
+
+ if !parg[:optional] || i < @opt_parg_idx + opt_pargs_filled
+ parg[:argval] = rand(100)
+ end
+ end
+
+ @kwargs.each_with_index do |kwarg, i|
+ if kwarg[:optional]
+ kwarg[:default] = rand(100)
+ end
+
+ if !kwarg[:optional] || rand() < 0.5
+ kwarg[:argval] = rand(100)
+ end
+ end
+
+ # Randomly pass a block or not
+ @block_arg = nil
+ if rand() < 0.5
+ @block_arg = rand(100)
+ end
+ end
+
+ # Compute the expected checksum of arguments ahead of time
+ def compute_checksum()
+ checksum = 0
+
+ @pargs.each_with_index do |arg, i|
+ value = (arg.key? :argval)? arg[:argval]:arg[:default]
+ checksum += (i+1) * arg_val(value)
+ end
+
+ @kwargs.each_with_index do |arg, i|
+ value = (arg.key? :argval)? arg[:argval]:arg[:default]
+ checksum += (i+1) * arg_val(value)
+ end
+
+ if @block_arg
+ if @has_block_param
+ checksum += arg_val(@block_arg)
+ end
+
+ checksum += arg_val(@block_arg)
+ end
+
+ checksum
+ end
+
+ # Generate code for the method signature and method body
+ def gen_method_str()
+ m_str = "def m("
+
+ @pargs.each do |arg|
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+
+ m_str += arg[:name]
+
+ # If this has a default value
+ if arg[:optional]
+ m_str += " = #{arg[:default]}"
+ end
+ end
+
+ if @has_rest
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+ m_str += "*rest"
+ end
+
+ @kwargs.each do |arg|
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+
+ m_str += "#{arg[:name]}:"
+
+ # If this has a default value
+ if arg[:optional]
+ m_str += " #{arg[:default]}"
+ end
+ end
+
+ if @has_kwrest
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+ m_str += "**kwrest"
+ end
+
+ if @has_block_param
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+
+ m_str += "&block"
+ end
+
+ m_str += ")\n"
+
+ # Add some useless locals
+ rand(0...16).times do |i|
+ m_str += "local#{i} = #{i}\n"
+ end
+
+ # Add some useless if statements
+ @pargs.each_with_index do |arg, i|
+ if rand() < 50
+ m_str += "if #{arg[:name]} > 4; end\n"
+ end
+ end
+
+ m_str += "checksum = 0\n"
+
+ @pargs.each_with_index do |arg, i|
+ m_str += "checksum += #{i+1} * arg_val(#{arg[:name]})\n"
+ end
+
+ @kwargs.each_with_index do |arg, i|
+ m_str += "checksum += #{i+1} * arg_val(#{arg[:name]})\n"
+ end
+
+ if @has_block_param
+ m_str += "if block; r = block.call; checksum += arg_val(r); end\n"
+ end
+
+ m_str += "if block_given?; r = yield; checksum += arg_val(r); end\n"
+
+ if @has_rest
+ m_str += "raise 'rest is not array' unless rest.kind_of?(Array)\n"
+ m_str += "raise 'rest size not integer' unless rest.size.kind_of?(Integer)\n"
+ end
+
+ if @has_kwrest
+ m_str += "raise 'kwrest is not a hash' unless kwrest.kind_of?(Hash)\n"
+ m_str += "raise 'kwrest size not integer' unless kwrest.size.kind_of?(Integer)\n"
+ end
+
+ m_str += "checksum\n"
+ m_str += "end"
+
+ m_str
+ end
+
+ # Generate code to call into the method and pass the arguments
+ def gen_call_str()
+ c_str = "m("
+
+ @pargs.each_with_index do |arg, i|
+ if !arg.key? :argval
+ next
+ end
+
+ if !c_str.end_with?("(")
+ c_str += ", "
+ end
+
+ c_str += "#{arg[:argval]}"
+ end
+
+ @kwargs.each_with_index do |arg, i|
+ if !arg.key? :argval
+ next
+ end
+
+ if !c_str.end_with?("(")
+ c_str += ", "
+ end
+
+ c_str += "#{arg[:name]}: #{arg[:argval]}"
+ end
+
+ c_str += ")"
+
+ # Randomly pass a block or not
+ if @block_arg
+ c_str += " { #{@block_arg} }"
+ end
+
+ c_str
+ end
+end
+
+iseqs_compiled_start = RubyVM::YJIT.runtime_stats[:compiled_iseq_entry]
+start_time = Time.now.to_f
+
+num_iters.times do |i|
+ puts "Iteration #{i}"
+
+ lst = ParamList.new()
+ m_str = lst.gen_method_str()
+ c_str = lst.gen_call_str()
+ checksum = lst.compute_checksum()
+
+ f = Object.new
+
+ # Define the method on f
+ puts "Defining"
+ p m_str
+ f.instance_eval(m_str)
+ #puts RubyVM::InstructionSequence.disasm(f.method(:m))
+ #exit 0
+
+ puts "Calling"
+ c_str = "f.#{c_str}"
+ p c_str
+ r = eval(c_str)
+ puts "checksum=#{r}"
+
+ if r != checksum
+ raise "return value #{r} doesn't match checksum #{checksum}"
+ end
+
+ puts ""
+end
+
+# Make sure that YJIT actually compiled the tests we ran
+# Should be run with --yjit-call-threshold=1
+iseqs_compiled_end = RubyVM::YJIT.runtime_stats[:compiled_iseq_entry]
+if iseqs_compiled_end - iseqs_compiled_start < num_iters
+ raise "YJIT did not compile enough ISEQs"
+end
+
+puts "Code region size: #{ format_number(0, RubyVM::YJIT.runtime_stats[:code_region_size]) }"
+
+end_time = Time.now.to_f
+itrs_per_sec = num_iters / (end_time - start_time)
+itrs_per_hour = 3600 * itrs_per_sec
+puts "#{'%.1f' % itrs_per_sec} iterations/s"
+puts "#{format_number(0, itrs_per_hour.round)} iterations/hour"
diff --git a/misc/call_fuzzer.sh b/misc/call_fuzzer.sh
new file mode 100755
index 0000000000..cf4ec76fe8
--- /dev/null
+++ b/misc/call_fuzzer.sh
@@ -0,0 +1,13 @@
+# Stop at first error
+set -e
+
+# TODO
+# TODO: boost --num-iters to 1M+ for actual test
+# TODO
+export NUM_ITERS=25000
+
+# Enable code GC so we don't stop compiling when we hit the code size limit
+ruby --yjit-call-threshold=1 --yjit-code-gc misc/call_fuzzer.rb --num-iters=$NUM_ITERS
+
+# Do another pass with --verify-ctx
+ruby --yjit-call-threshold=1 --yjit-code-gc --yjit-verify-ctx misc/call_fuzzer.rb --num-iters=$NUM_ITERS
diff --git a/misc/gdb.py b/misc/gdb.py
new file mode 100644
index 0000000000..6034a389bb
--- /dev/null
+++ b/misc/gdb.py
@@ -0,0 +1,181 @@
+import argparse
+import textwrap
+
+# usage: [-h] [-a | --all | --no-all] [-s STACK_SIZE] [uplevel]
+#
+# Dump a control frame
+#
+# positional arguments:
+# uplevel CFP offset from the stack top
+#
+# options:
+# -h, --help show this help message and exit
+# -a, --all, --no-all dump all frames
+# -s STACK_SIZE, --stack-size STACK_SIZE
+# override stack_size (useful for JIT frames)
+class CFP(gdb.Command):
+ FRAME_MAGICS = [
+ # frame types
+ 'VM_FRAME_MAGIC_METHOD',
+ 'VM_FRAME_MAGIC_BLOCK',
+ 'VM_FRAME_MAGIC_CLASS',
+ 'VM_FRAME_MAGIC_TOP',
+ 'VM_FRAME_MAGIC_CFUNC',
+ 'VM_FRAME_MAGIC_IFUNC',
+ 'VM_FRAME_MAGIC_EVAL',
+ 'VM_FRAME_MAGIC_RESCUE',
+ 'VM_FRAME_MAGIC_DUMMY',
+ ]
+ FRAME_FLAGS = [
+ # frame flag
+ 'VM_FRAME_FLAG_FINISH',
+ 'VM_FRAME_FLAG_BMETHOD',
+ 'VM_FRAME_FLAG_CFRAME',
+ 'VM_FRAME_FLAG_LAMBDA',
+ 'VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM',
+ 'VM_FRAME_FLAG_CFRAME_KW',
+ 'VM_FRAME_FLAG_PASSED',
+ # env flag
+ 'VM_ENV_FLAG_LOCAL',
+ 'VM_ENV_FLAG_ESCAPED',
+ 'VM_ENV_FLAG_WB_REQUIRED',
+ 'VM_ENV_FLAG_ISOLATED',
+ ]
+
+ def __init__(self):
+ super(CFP, self).__init__('cfp', gdb.COMMAND_USER)
+
+ self.parser = argparse.ArgumentParser(description='Dump a control frame')
+ self.parser.add_argument('uplevel', type=int, nargs='?', default=0, help='CFP offset from the stack top')
+ self.parser.add_argument('-a', '--all', action=argparse.BooleanOptionalAction, help='dump all frames')
+ self.parser.add_argument('-s', '--stack-size', type=int, help='override stack_size (useful for JIT frames)')
+
+ def invoke(self, args, from_tty):
+ try:
+ args = self.parser.parse_args(args.split())
+ except SystemExit:
+ return
+ cfp = f'(ruby_current_ec->cfp + ({args.uplevel}))'
+ end_cfp = self.get_int('ruby_current_ec->vm_stack + ruby_current_ec->vm_stack_size')
+ cfp_index = int((end_cfp - self.get_int(cfp) - 1) / self.get_int('sizeof(rb_control_frame_t)'))
+
+ if args.all:
+ cfp_count = int((end_cfp - self.get_int('ruby_current_ec->cfp')) / self.get_int('sizeof(rb_control_frame_t)')) - 1 # exclude dummy CFP
+ for i in range(cfp_count):
+ print('-' * 80)
+ self.invoke(str(cfp_count - i - 1), from_tty)
+ return
+
+ print('CFP (addr=0x{:x}, index={}):'.format(self.get_int(cfp), cfp_index))
+ gdb.execute(f'p *({cfp})')
+ print()
+
+ if self.get_int(f'{cfp}->iseq'):
+ local_size = self.get_int(f'{cfp}->iseq->body->local_table_size - {cfp}->iseq->body->param.size')
+ param_size = self.get_int(f'{cfp}->iseq->body->param.size')
+
+ if local_size:
+ print(f'Params (size={param_size}):')
+ for i in range(-3 - local_size - param_size, -3 - local_size):
+ self.print_stack(cfp, i, self.rp(cfp, i))
+ print()
+
+ if param_size:
+ print(f'Locals (size={local_size}):')
+ for i in range(-3 - local_size, -3):
+ self.print_stack(cfp, i, self.rp(cfp, i))
+ print()
+
+ print('Env:')
+ self.print_env(cfp, -3, self.rp_env(cfp, -3))
+ self.print_env(cfp, -2, self.specval(cfp, -2))
+ self.print_env(cfp, -1, self.frame_types(cfp, -1))
+ print()
+
+ # We can't calculate BP for the first frame.
+ # vm_base_ptr doesn't work for C frames either.
+ if cfp_index > 0 and self.get_int(f'{cfp}->iseq'):
+ if args.stack_size is not None:
+ stack_size = args.stack_size
+ else:
+ stack_size = int((self.get_int(f'{cfp}->sp') - self.get_int(f'vm_base_ptr({cfp})')) / 8)
+ print(f'Stack (size={stack_size}):')
+ for i in range(0, stack_size):
+ self.print_stack(cfp, i, self.rp(cfp, i))
+ print(self.regs(cfp, stack_size))
+
+ def print_env(self, cfp, bp_index, content):
+ ep_index = bp_index + 1
+ address = self.get_int(f'((rb_control_frame_t *){cfp})->ep + {ep_index}')
+ value = self.get_env(cfp, bp_index)
+ regs = self.regs(cfp, bp_index)
+ if content:
+ content = textwrap.indent(content, ' ' * 3).lstrip() # Leave the regs column empty
+ content = f'{content} '
+ print('{:2} 0x{:x} [{}] {}(0x{:x})'.format(regs, address, bp_index, content, value))
+
+ def print_stack(self, cfp, bp_index, content):
+ address = self.get_int(f'vm_base_ptr({cfp}) + {bp_index}')
+ value = self.get_value(cfp, bp_index)
+ regs = self.regs(cfp, bp_index)
+ if content:
+ content = textwrap.indent(content, ' ' * 3).lstrip() # Leave the regs column empty
+ content = f'{content} '
+ print('{:2} 0x{:x} [{}] {}(0x{:x})'.format(regs, address, bp_index, content, value))
+
+ def regs(self, cfp, bp_index):
+ address = self.get_int(f'vm_base_ptr({cfp}) + {bp_index}')
+ regs = []
+ for reg, field in { 'EP': 'ep', 'SP': 'sp' }.items():
+ if address == self.get_int(f'{cfp}->{field}'):
+ regs.append(reg)
+ return ' '.join(regs)
+
+ def rp(self, cfp, bp_index):
+ value = self.get_value(cfp, bp_index)
+ return self.get_string(f'rp {value}').rstrip()
+
+ def rp_env(self, cfp, bp_index):
+ value = self.get_env(cfp, bp_index)
+ return self.get_string(f'rp {value}').rstrip()
+
+ # specval: block_handler or previous EP
+ def specval(self, cfp, bp_index):
+ value = self.get_env(cfp, bp_index)
+ if value == 0:
+ return 'VM_BLOCK_HANDLER_NONE'
+ if value == self.get_int('rb_block_param_proxy'):
+ return 'rb_block_param_proxy'
+ return ''
+
+ def frame_types(self, cfp, bp_index):
+ types = []
+ value = self.get_env(cfp, bp_index)
+
+ magic_mask = self.get_int('VM_FRAME_MAGIC_MASK')
+ for magic in self.FRAME_MAGICS:
+ magic_value = self.get_int(magic)
+ if value & magic_mask == magic_value:
+ types.append(magic)
+
+ for flag in self.FRAME_FLAGS:
+ flag_value = self.get_int(flag)
+ if value & flag_value:
+ types.append(flag)
+
+ return ' | '.join(types)
+
+ def get_env(self, cfp, bp_index):
+ ep_index = bp_index + 1
+ return self.get_int(f'((rb_control_frame_t *){cfp})->ep[{ep_index}]')
+
+ def get_value(self, cfp, bp_index):
+ return self.get_int(f'vm_base_ptr({cfp})[{bp_index}]')
+
+ def get_int(self, expr):
+ return int(self.get_string(f'printf "%ld", ({expr})'))
+
+ def get_string(self, expr):
+ return gdb.execute(expr, to_string=True)
+
+CFP()
diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py
index b146361a0f..400ccb45b9 100755
--- a/misc/lldb_cruby.py
+++ b/misc/lldb_cruby.py
@@ -9,15 +9,16 @@
from __future__ import print_function
import lldb
import os
+import inspect
+import sys
import shlex
import platform
+import glob
-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
+from lldb_rb.constants import *
+# BEGIN FUNCTION STYLE DECLS
+# This will be refactored to use class style decls in the misc/commands dir
class BackTrace:
VM_FRAME_MAGIC_METHOD = 0x11110001
VM_FRAME_MAGIC_BLOCK = 0x22220001
@@ -170,8 +171,10 @@ def lldb_init(debugger):
g = globals()
imemo_types = target.FindFirstType('enum imemo_type')
+ enum_members = imemo_types.GetEnumMembers()
- for member in imemo_types.GetEnumMembers():
+ for i in range(enum_members.GetSize()):
+ member = enum_members.GetTypeEnumMemberAtIndex(i)
g[member.GetName()] = member.GetValueAsUnsigned()
for enum in target.FindFirstGlobalVariable('ruby_dummy_gdb_enums'):
@@ -194,18 +197,16 @@ def string2cstr(rstring):
flags = rstring.GetValueForExpressionPath(".basic->flags").unsigned
if flags & RUBY_T_MASK != RUBY_T_STRING:
raise TypeError("not a string")
+ clen = int(rstring.GetValueForExpressionPath(".len").value, 0)
if flags & 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(debugger, result, rstring):
cptr, clen = string2cstr(rstring)
- expr = "print *(const char (*)[%d])%0#x" % (clen, cptr)
- append_command_output(debugger, expr, result)
+ append_expression(debugger, "*(const char (*)[%d])%0#x" % (clen, cptr), result)
def fixnum_p(x):
return x & RUBY_FIXNUM_FLAG != 0
@@ -224,6 +225,9 @@ def append_command_output(debugger, command, result):
result.write(output1)
result.write(output2)
+def append_expression(debugger, expression, result):
+ append_command_output(debugger, "expression " + expression, result)
+
def lldb_rp(debugger, command, result, internal_dict):
if not ('RUBY_Qfalse' in globals()):
lldb_init(debugger)
@@ -255,13 +259,13 @@ def lldb_inspect(debugger, target, result, val):
elif fixnum_p(num):
print(num >> 1, file=result)
elif flonum_p(num):
- append_command_output(debugger, "print rb_float_value(%0#x)" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "rb_float_value(%0#x)" % val.GetValueAsUnsigned(), result)
elif static_sym_p(num):
if num < 128:
print("T_SYMBOL: %c" % num, file=result)
else:
print("T_SYMBOL: (%x)" % num, file=result)
- append_command_output(debugger, "p rb_id2name(%0#x)" % (num >> 8), result)
+ append_expression(debugger, "rb_id2name(%0#x)" % (num >> 8), result)
elif num & RUBY_IMMEDIATE_MASK:
print('immediate(%x)' % num, file=result)
else:
@@ -289,13 +293,13 @@ def lldb_inspect(debugger, target, result, val):
print('T_NIL: %s%s' % (flaginfo, val.Dereference()), file=result)
elif flType == RUBY_T_OBJECT:
result.write('T_OBJECT: %s' % flaginfo)
- append_command_output(debugger, "print *(struct RObject*)%0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RObject*)%0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_CLASS or flType == RUBY_T_MODULE or flType == RUBY_T_ICLASS:
result.write('T_%s: %s' % ('CLASS' if flType == RUBY_T_CLASS else 'MODULE' if flType == RUBY_T_MODULE else 'ICLASS', flaginfo))
- append_command_output(debugger, "print *(struct RClass*)%0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RClass*)%0#x" % val.GetValueAsUnsigned(), result)
tRClass = target.FindFirstType("struct RClass")
if not val.Cast(tRClass).GetChildMemberWithName("ptr").IsValid():
- append_command_output(debugger, "print *(struct rb_classext_struct*)%0#x" % (val.GetValueAsUnsigned() + tRClass.GetByteSize()), result)
+ append_expression(debugger, "*(struct rb_classext_struct*)%0#x" % (val.GetValueAsUnsigned() + tRClass.GetByteSize()), result)
elif flType == RUBY_T_STRING:
result.write('T_STRING: %s' % flaginfo)
encidx = ((flags & RUBY_ENCODING_MASK)>>RUBY_ENCODING_SHIFT)
@@ -309,12 +313,12 @@ def lldb_inspect(debugger, target, result, val):
if len == 0:
result.write("(empty)\n")
else:
- append_command_output(debugger, "print *(const char (*)[%d])%0#x" % (len, ptr), result)
+ append_expression(debugger, "*(const char (*)[%d])%0#x" % (len, ptr), result)
elif flType == RUBY_T_SYMBOL:
result.write('T_SYMBOL: %s' % flaginfo)
tRSymbol = target.FindFirstType("struct RSymbol").GetPointerType()
val = val.Cast(tRSymbol)
- append_command_output(debugger, 'print (ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned(), result)
+ append_expression(debugger, '(ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned(), result)
tRString = target.FindFirstType("struct RString").GetPointerType()
output_string(debugger, result, val.GetValueForExpressionPath("->fstr").Cast(tRString))
elif flType == RUBY_T_ARRAY:
@@ -340,12 +344,12 @@ def lldb_inspect(debugger, target, result, val):
else:
result.write("\n")
if ptr.GetValueAsSigned() == 0:
- append_command_output(debugger, "expression -fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "-fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned(), result)
else:
- append_command_output(debugger, "expression -Z %d -fx -- (const VALUE*)%0#x" % (len, ptr.GetValueAsUnsigned()), result)
+ append_expression(debugger, "-Z %d -fx -- (const VALUE*)%0#x" % (len, ptr.GetValueAsUnsigned()), result)
elif flType == RUBY_T_HASH:
result.write("T_HASH: %s" % flaginfo)
- append_command_output(debugger, "p *(struct RHash *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RHash *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_BIGNUM:
tRBignum = target.FindFirstType("struct RBignum").GetPointerType()
val = val.Cast(tRBignum)
@@ -353,15 +357,15 @@ def lldb_inspect(debugger, target, result, val):
if flags & RUBY_FL_USER2:
len = ((flags & (RUBY_FL_USER3|RUBY_FL_USER4|RUBY_FL_USER5)) >> (RUBY_FL_USHIFT+3))
print("T_BIGNUM: sign=%s len=%d (embed)" % (sign, len), file=result)
- append_command_output(debugger, "print ((struct RBignum *) %0#x)->as.ary" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "((struct RBignum *) %0#x)->as.ary" % val.GetValueAsUnsigned(), result)
else:
len = val.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned()
print("T_BIGNUM: sign=%s len=%d" % (sign, len), file=result)
print(val.Dereference(), file=result)
- append_command_output(debugger, "expression -Z %x -fx -- (const BDIGIT*)((struct RBignum*)%d)->as.heap.digits" % (len, val.GetValueAsUnsigned()), result)
- # append_command_output(debugger, "x ((struct RBignum *) %0#x)->as.heap.digits / %d" % (val.GetValueAsUnsigned(), len), result)
+ append_expression(debugger, "-Z %x -fx -- (const BDIGIT*)((struct RBignum*)%d)->as.heap.digits" % (len, val.GetValueAsUnsigned()), result)
+ # append_expression(debugger, "((struct RBignum *) %0#x)->as.heap.digits / %d" % (val.GetValueAsUnsigned(), len), result)
elif flType == RUBY_T_FLOAT:
- append_command_output(debugger, "print ((struct RFloat *)%d)->float_value" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "((struct RFloat *)%d)->float_value" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_RATIONAL:
tRRational = target.FindFirstType("struct RRational").GetPointerType()
val = val.Cast(tRRational)
@@ -394,38 +398,39 @@ def lldb_inspect(debugger, target, result, val):
flag = val.GetValueForExpressionPath("->typed_flag")
if flag.GetValueAsUnsigned() == 1:
print("T_DATA: %s" % val.GetValueForExpressionPath("->type->wrap_struct_name"), file=result)
- append_command_output(debugger, "p *(struct RTypedData *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RTypedData *) %0#x" % val.GetValueAsUnsigned(), result)
else:
print("T_DATA:", file=result)
- append_command_output(debugger, "p *(struct RData *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RData *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_NODE:
tRTypedData = target.FindFirstType("struct RNode").GetPointerType()
nd_type = (flags & RUBY_NODE_TYPEMASK) >> RUBY_NODE_TYPESHIFT
- append_command_output(debugger, "p (node_type) %d" % nd_type, result)
+ append_expression(debugger, "(node_type) %d" % nd_type, result)
val = val.Cast(tRTypedData)
- append_command_output(debugger, "p *(struct RNode *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RNode *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_MOVED:
tRTypedData = target.FindFirstType("struct RMoved").GetPointerType()
val = val.Cast(tRTypedData)
- append_command_output(debugger, "p *(struct RMoved *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RMoved *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_MATCH:
tRTypedData = target.FindFirstType("struct RMatch").GetPointerType()
val = val.Cast(tRTypedData)
- append_command_output(debugger, "p *(struct RMatch *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RMatch *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_IMEMO:
# I'm not sure how to get IMEMO_MASK out of lldb. It's not in globals()
imemo_type = (flags >> RUBY_FL_USHIFT) & 0x0F # IMEMO_MASK
+
print("T_IMEMO: ", file=result)
- append_command_output(debugger, "p (enum imemo_type) %d" % imemo_type, result)
- append_command_output(debugger, "p *(struct MEMO *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "(enum imemo_type) %d" % imemo_type, result)
+ append_expression(debugger, "*(struct MEMO *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_STRUCT:
tRTypedData = target.FindFirstType("struct RStruct").GetPointerType()
val = val.Cast(tRTypedData)
- append_command_output(debugger, "p *(struct RStruct *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RStruct *) %0#x" % val.GetValueAsUnsigned(), result)
elif flType == RUBY_T_ZOMBIE:
tRZombie = target.FindFirstType("struct RZombie").GetPointerType()
val = val.Cast(tRZombie)
- append_command_output(debugger, "p *(struct RZombie *) %0#x" % val.GetValueAsUnsigned(), result)
+ append_expression(debugger, "*(struct RZombie *) %0#x" % val.GetValueAsUnsigned(), result)
else:
print("Not-handled type %0#x" % flType, file=result)
print(val, file=result)
@@ -466,19 +471,6 @@ def check_bits(page, bitmap_name, bitmap_index, bitmap_bit, v):
else:
return ' '
-def heap_page(debugger, command, ctx, result, internal_dict):
- target = debugger.GetSelectedTarget()
- process = target.GetProcess()
- thread = process.GetSelectedThread()
- frame = thread.GetSelectedFrame()
-
- val = frame.EvaluateExpression(command)
- page = get_page(lldb, target, val)
- page_type = target.FindFirstType("struct heap_page").GetPointerType()
- page.Cast(page_type)
- append_command_output(debugger, "p (struct heap_page *) %0#x" % page.GetValueAsUnsigned(), result)
- append_command_output(debugger, "p *(struct heap_page *) %0#x" % page.GetValueAsUnsigned(), result)
-
def heap_page_body(debugger, command, ctx, result, internal_dict):
target = debugger.GetSelectedTarget()
process = target.GetProcess()
@@ -721,37 +713,37 @@ def rb_id2str(debugger, command, result, internal_dict):
pos = (num % ID_ENTRY_UNIT) * ID_ENTRY_SIZE
id_str = rb_ary_entry(target, ary, pos, result)
lldb_inspect(debugger, target, result, id_str)
+# END FUNCTION STYLE DECLS
-def rb_rclass_ext(debugger, command, result, internal_dict):
- if not ('RUBY_Qfalse' in globals()):
- lldb_init(debugger)
-
- target = debugger.GetSelectedTarget()
- process = target.GetProcess()
- thread = process.GetSelectedThread()
- frame = thread.GetSelectedFrame()
- uintptr_t = target.FindFirstType("uintptr_t")
- rclass_t = target.FindFirstType("struct RClass")
- rclass_ext_t = target.FindFirstType("rb_classext_t")
+load_dir, _ = os.path.split(os.path.realpath(__file__))
- rclass_addr = target.EvaluateExpression(command).Cast(uintptr_t)
- rclass_ext_addr = (rclass_addr.GetValueAsUnsigned() + rclass_t.GetByteSize())
- debugger.HandleCommand("p *(rb_classext_t *)%0#x" % rclass_ext_addr)
+for fname in glob.glob(f"{load_dir}/lldb_rb/commands/*_command.py"):
+ _, basename = os.path.split(fname)
+ mname, _ = os.path.splitext(basename)
+ exec(f"import lldb_rb.commands.{mname}")
def __lldb_init_module(debugger, internal_dict):
- debugger.HandleCommand("command script add -f lldb_cruby.lldb_rp rp")
+ # Register all classes that subclass RbBaseCommand
+
+ for memname, mem in inspect.getmembers(sys.modules["lldb_rb.rb_base_command"]):
+ 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 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")
- 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")
debugger.HandleCommand("command script add -f lldb_cruby.dump_page dump_page")
debugger.HandleCommand("command script add -f lldb_cruby.dump_page_rvalue dump_page_rvalue")
- debugger.HandleCommand("command script add -f lldb_cruby.rb_id2str rb_id2str")
- debugger.HandleCommand("command script add -f lldb_cruby.rb_rclass_ext rclass_ext")
+ debugger.HandleCommand("command script add -f lldb_cruby.rb_id2str old_rb_id2str")
+
+ lldb_rb.rb_base_command.RbBaseCommand.lldb_init(debugger)
- lldb_init(debugger)
print("lldb scripts for ruby has been installed.")
diff --git a/misc/lldb_disasm.py b/misc/lldb_disasm.py
index 713a935285..ab759f009a 100644
--- a/misc/lldb_disasm.py
+++ b/misc/lldb_disasm.py
@@ -63,6 +63,7 @@ class IseqDisassembler:
self.internal_dict = internal_dict
self.target = debugger.GetSelectedTarget()
+ self.insns_address_table = self.__get_insns_address_table()
self.process = self.target.GetProcess()
self.thread = self.process.GetSelectedThread()
self.frame = self.thread.GetSelectedFrame()
@@ -88,7 +89,7 @@ class IseqDisassembler:
tIntPtr = target.FindFirstType("intptr_t")
size = target.EvaluateExpression('ruby_vminsn_type::VM_INSTRUCTION_SIZE').unsigned
sizeOfIntPtr = tIntPtr.GetByteSize()
- addr_of_table = target.FindSymbols("vm_exec_core.insns_address_table")[0].GetSymbol().GetStartAddress().GetLoadAddress(target)
+ addr_of_table = self.insns_address_table.GetStartAddress().GetLoadAddress(target)
my_dict = {}
@@ -220,6 +221,15 @@ class IseqDisassembler:
else:
print('error getting insn name', error)
+ def __get_insns_address_table(self):
+ module = self.target.FindSymbols("vm_exec_core")[0].GetModule()
+
+ for symbol in module:
+ if "insns_address_table" in symbol.name and symbol.GetType() == lldb.eSymbolTypeData:
+ print(f"found symbol {symbol.name}")
+ return symbol
+
+
def disasm(debugger, command, result, internal_dict):
disassembler = IseqDisassembler(debugger, command, result, internal_dict)
frame = disassembler.frame
@@ -235,7 +245,6 @@ def disasm(debugger, command, result, internal_dict):
disassembler.disasm(val);
-
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand("command script add -f lldb_disasm.disasm rbdisasm")
print("lldb Ruby disasm installed.")
diff --git a/misc/lldb_rb/commands/command_template.py b/misc/lldb_rb/commands/command_template.py
new file mode 100644
index 0000000000..21014a993e
--- /dev/null
+++ b/misc/lldb_rb/commands/command_template.py
@@ -0,0 +1,30 @@
+# This is a command template for implementing a helper function inside LLDB. To
+# use this file
+# 1. Copy it and rename the copy so it ends with `_command.py`.
+# 2. Rename the class to something descriptive that ends with Command.
+# 3. Change the program variable to be a descriptive command name
+# 4. Ensure you are inheriting from RbBaseCommand or another command that
+# implements the same interface
+
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+# This test command inherits from RbBaseCommand which provides access to Ruby
+# globals and utility helpers
+class TestCommand(RbBaseCommand):
+ # program is the keyword the user will type in lldb to execute this command
+ program = "test"
+
+ # help_string will be displayed in lldb when the user uses the help functions
+ help_string = "This is a test command to show how to implement lldb commands"
+
+ # call is where our command logic will be implemented
+ def call(self, debugger, command, exe_ctx, result):
+ # This method will be called once the LLDB environment has been setup.
+ # You will have access to self.target, self.process, self.frame, and
+ # self.thread
+ #
+ # This is where we should implement our command logic
+ pass
diff --git a/misc/lldb_rb/commands/heap_page_command.py b/misc/lldb_rb/commands/heap_page_command.py
new file mode 100644
index 0000000000..b56a3eae4e
--- /dev/null
+++ b/misc/lldb_rb/commands/heap_page_command.py
@@ -0,0 +1,26 @@
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class HeapPageCommand(RbBaseCommand):
+ program = "heap_page"
+ help_string = "prints out 'struct heap_page' for a VALUE pointer in the page"
+
+ def call(self, debugger, command, exe_ctx, result):
+ self.t_heap_page_body = self.target.FindFirstType("struct heap_page_body")
+ self.t_heap_page_ptr = self.target.FindFirstType("struct heap_page").GetPointerType()
+
+ page = self._get_page(self.frame.EvaluateExpression(command))
+ page.Cast(self.t_heap_page_ptr)
+
+ self._append_expression(debugger, "(struct heap_page *) %0#x" % page.GetValueAsUnsigned(), result)
+ self._append_expression(debugger, "*(struct heap_page *) %0#x" % page.GetValueAsUnsigned(), result)
+
+ def _get_page(self, val):
+ addr = val.GetValueAsUnsigned()
+ page_addr = addr & ~(HEAP_PAGE_ALIGN_MASK)
+ address = lldb.SBAddress(page_addr, self.target)
+ body = self.target.CreateValueFromAddress("page", address, self.t_heap_page_body)
+
+ return body.GetValueForExpressionPath("->header.page")
diff --git a/misc/lldb_rb/commands/print_flags_command.py b/misc/lldb_rb/commands/print_flags_command.py
new file mode 100644
index 0000000000..00da4834bf
--- /dev/null
+++ b/misc/lldb_rb/commands/print_flags_command.py
@@ -0,0 +1,31 @@
+import lldb
+import re
+
+from lldb_rb.constants import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class PrintFlagsCommand(RbBaseCommand):
+ program = "print_flags"
+
+ help_string = "Print out the individial flags of an RVALUE object in human readable format"
+
+ # call is where our command logic will be implemented
+ def call(self, debugger, command, exe_ctx, result):
+ rclass_t = self.target.FindFirstType("struct RBasic")
+ rcass_ptr = self.target.EvaluateExpression(command).Cast(rclass_t.GetPointerType())
+ obj_flags = rcass_ptr.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+
+ flags = [
+ "RUBY_FL_WB_PROTECTED", "RUBY_FL_PROMOTED0", "RUBY_FL_PROMOTED1", "RUBY_FL_FINALIZE",
+ "RUBY_FL_SHAREABLE", "RUBY_FL_EXIVAR", "RUBY_FL_FREEZE",
+ "RUBY_FL_USER0", "RUBY_FL_USER1", "RUBY_FL_USER2", "RUBY_FL_USER3", "RUBY_FL_USER4",
+ "RUBY_FL_USER5", "RUBY_FL_USER6", "RUBY_FL_USER7", "RUBY_FL_USER8", "RUBY_FL_USER9",
+ "RUBY_FL_USER10", "RUBY_FL_USER11", "RUBY_FL_USER12", "RUBY_FL_USER13", "RUBY_FL_USER14",
+ "RUBY_FL_USER15", "RUBY_FL_USER16", "RUBY_FL_USER17", "RUBY_FL_USER18"
+ ]
+
+ types_index = {v: k for k, v in self.ruby_globals.items() if re.match(r'RUBY_T_', k)}
+ print("TYPE: {}".format(types_index[obj_flags & self.ruby_globals["RUBY_T_MASK"]]))
+ for flag in flags:
+ output = "{} : {}".format(flag, "1" if (obj_flags & self.ruby_globals[flag]) else "0")
+ print(output, file=result)
diff --git a/misc/lldb_rb/commands/rb_id2str_command.py b/misc/lldb_rb/commands/rb_id2str_command.py
new file mode 100644
index 0000000000..6ee859ebf6
--- /dev/null
+++ b/misc/lldb_rb/commands/rb_id2str_command.py
@@ -0,0 +1,49 @@
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.utils import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class RbID2StrCommand(RbBaseCommand):
+ program = "rb_id2str"
+
+ 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):
+ global_symbols = self.target.FindFirstGlobalVariable("ruby_global_symbols")
+
+ id_val = self.frame.EvaluateExpression(command).GetValueAsUnsigned()
+ num = self.rb_id_to_serial(id_val)
+
+ last_id = global_symbols.GetChildMemberWithName("last_id").GetValueAsUnsigned()
+ ID_ENTRY_SIZE = 2
+ ID_ENTRY_UNIT = int(self.target.FindFirstGlobalVariable("ID_ENTRY_UNIT").GetValue())
+
+ ids = global_symbols.GetChildMemberWithName("ids")
+
+ if num <= last_id:
+ idx = num // ID_ENTRY_UNIT
+ ary = self.rb_ary_entry(ids, idx, result)
+ pos = (num % ID_ENTRY_UNIT) * ID_ENTRY_SIZE
+ id_str = self.rb_ary_entry(ary, pos, result)
+
+ RbInspector(debugger, result, self.ruby_globals).inspect(id_str)
+
+ def rb_id_to_serial(self, id_val):
+ if id_val > self.ruby_globals["tLAST_OP_ID"]:
+ return id_val >> self.ruby_globals["RUBY_ID_SCOPE_SHIFT"]
+ else:
+ return id_val
+
+ def rb_ary_entry(self, ary, idx, result):
+ tRArray = self.target.FindFirstType("struct RArray").GetPointerType()
+ ary = ary.Cast(tRArray)
+ flags = ary.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+
+ if flags & self.ruby_globals["RUBY_FL_USER1"]:
+ ptr = ary.GetValueForExpressionPath("->as.ary")
+ else:
+ ptr = ary.GetValueForExpressionPath("->as.heap.ptr")
+
+ ptr_addr = ptr.GetValueAsUnsigned() + (idx * ptr.GetType().GetByteSize())
+ return self.target.CreateValueFromAddress("ary_entry[%d]" % idx, lldb.SBAddress(ptr_addr, self.target), ptr.GetType().GetPointeeType())
diff --git a/misc/lldb_rb/commands/rclass_ext_command.py b/misc/lldb_rb/commands/rclass_ext_command.py
new file mode 100644
index 0000000000..8bae911457
--- /dev/null
+++ b/misc/lldb_rb/commands/rclass_ext_command.py
@@ -0,0 +1,14 @@
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class RclassExtCommand(RbBaseCommand):
+ program = "rclass_ext"
+ help_string = "retrieves and prints the rb_classext_struct for the VALUE pointer passed in"
+
+ def call(self, debugger, command, exe_ctx, result):
+ uintptr_t = self.target.FindFirstType("uintptr_t")
+ rclass_t = self.target.FindFirstType("struct RClass")
+ rclass_ext_t = self.target.FindFirstType("rb_classext_t")
+
+ rclass_addr = self.target.EvaluateExpression(command).Cast(uintptr_t)
+ rclass_ext_addr = (rclass_addr.GetValueAsUnsigned() + rclass_t.GetByteSize())
+ debugger.HandleCommand("p *(rb_classext_t *)%0#x" % rclass_ext_addr)
diff --git a/misc/lldb_rb/commands/rp_command.py b/misc/lldb_rb/commands/rp_command.py
new file mode 100644
index 0000000000..06b2516d50
--- /dev/null
+++ b/misc/lldb_rb/commands/rp_command.py
@@ -0,0 +1,15 @@
+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)
diff --git a/misc/lldb_rb/constants.py b/misc/lldb_rb/constants.py
new file mode 100644
index 0000000000..9cd56eccb0
--- /dev/null
+++ b/misc/lldb_rb/constants.py
@@ -0,0 +1,6 @@
+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
diff --git a/misc/lldb_rb/lldb_interface.py b/misc/lldb_rb/lldb_interface.py
new file mode 100644
index 0000000000..785a54b3e3
--- /dev/null
+++ b/misc/lldb_rb/lldb_interface.py
@@ -0,0 +1,7 @@
+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()
diff --git a/misc/lldb_rb/rb_base_command.py b/misc/lldb_rb/rb_base_command.py
new file mode 100644
index 0000000000..70a5addd6d
--- /dev/null
+++ b/misc/lldb_rb/rb_base_command.py
@@ -0,0 +1,57 @@
+import lldb
+from pydoc import locate
+from lldb_rb.constants import *
+from lldb_rb.utils import *
+
+class RbBaseCommand(LLDBInterface):
+ @classmethod
+ def register_lldb_command(cls, debugger, module_name):
+ # Add any commands contained in this module to LLDB
+ command = f"command script add -c {module_name}.{cls.__name__} {cls.program}"
+ debugger.HandleCommand(command)
+
+ @classmethod
+ def lldb_init(cls, debugger):
+ target = debugger.GetSelectedTarget()
+ global SIZEOF_VALUE
+ SIZEOF_VALUE = target.FindFirstType("VALUE").GetByteSize()
+
+ value_types = []
+ g = globals()
+
+ imemo_types = target.FindFirstType("enum imemo_type")
+
+ #for member in imemo_types.GetEnumMembers():
+ # g[member.GetName()] = member.GetValueAsUnsigned()
+
+ for enum in target.FindFirstGlobalVariable("ruby_dummy_gdb_enums"):
+ enum = enum.GetType()
+ members = enum.GetEnumMembers()
+ for i in range(0, members.GetSize()):
+ member = members.GetTypeEnumMemberAtIndex(i)
+ name = member.GetName()
+ value = member.GetValueAsUnsigned()
+ g[name] = value
+
+ if name.startswith("RUBY_T_"):
+ value_types.append(name)
+ g["value_types"] = value_types
+ return g
+
+ def __init__(self, debugger, _internal_dict):
+ self.ruby_globals = RbBaseCommand.lldb_init(debugger)
+ self.internal_dict = _internal_dict
+
+ def __call__(self, debugger, command, exe_ctx, result):
+ self.ruby_globals = RbBaseCommand.lldb_init(debugger)
+ self.build_environment(debugger)
+ self.call(debugger, command, exe_ctx, result)
+
+ def call(self, debugger, command, exe_ctx, result):
+ raise NotImplementedError("subclasses must implement call")
+
+ def get_short_help(self):
+ return self.__class__.help_string
+
+ def get_long_help(self):
+ return self.__class__.help_string
diff --git a/misc/lldb_rb/rb_heap_structs.py b/misc/lldb_rb/rb_heap_structs.py
new file mode 100644
index 0000000000..86b38dbbbd
--- /dev/null
+++ b/misc/lldb_rb/rb_heap_structs.py
@@ -0,0 +1,143 @@
+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 as_type(self, type_name):
+ return self.val.Cast(self.tRValue.GetPointerType()).GetValueForExpressionPath("->as."+type_name)
+
+ def ary_ptr(self):
+ rval = self.as_type("array")
+ if self.flags & self.ruby_globals["RUBY_FL_USER1"]:
+ ptr = rval.GetValueForExpressionPath("->as.ary")
+ else:
+ ptr = rval.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:
+ rval = self.as_type("array")
+ len = rval.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned()
+
+ return len
+
+ def bignum_len(self):
+ if self.flags & self.flUser2:
+ len = ((self.flags &
+ (self.flUser3 | self.flUser4 | self.flUser5)
+ ) >> (self.flUshift + 3))
+ else:
+ len = (self.as_type("bignum").GetValueForExpressionPath("->as.heap.len").
+ GetValueAsUnsigned())
+
+ return len
diff --git a/misc/lldb_rb/utils.py b/misc/lldb_rb/utils.py
new file mode 100644
index 0000000000..86b5bdda2d
--- /dev/null
+++ b/misc/lldb_rb/utils.py
@@ -0,0 +1,494 @@
+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 _append_expression(self, expression):
+ self._append_command_output("expression " + expression)
+
+ 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
+ clen = int(rstring.GetValueForExpressionPath(".len").value, 0)
+ if flags & self.ruby_globals["RUBY_FL_USER1"]:
+ cptr = int(rstring.GetValueForExpressionPath(".as.heap.ptr").value, 0)
+ else:
+ cptr = int(rstring.GetValueForExpressionPath(".as.embed.ary").location, 0)
+
+ return cptr, clen
+
+ def output_string(self, rstring):
+ cptr, clen = self.string2cstr(rstring)
+ self._append_expression("*(const char (*)[%d])%0#x" % (clen, cptr))
+
+ 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_expression("*(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_expression("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_expression("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_expression("*(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_expression("*(struct RClass*)%0#x" % val.GetValueAsUnsigned())
+ if not val.Cast(tRClass).GetChildMemberWithName("ptr").IsValid():
+ self._append_expression(
+ "*(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()
+
+ chilled = self.ruby_globals["RUBY_FL_USER3"]
+ if (rval.flags & chilled) != 0:
+ self.result.write("[CHILLED] ")
+
+ 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_expression("*(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_expression('(ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned())
+ self.output_string(val.GetValueForExpressionPath("->fstr").Cast(tRString))
+
+ elif rval.is_type("RUBY_T_ARRAY"):
+ 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_globals["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_expression("-fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned())
+ else:
+ self._append_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)
+ ptr = val.GetValueAsUnsigned()
+ self._append_expression("*(struct RHash *) %0#x" % ptr)
+ if rval.flags & self.ruby_globals["RUBY_FL_USER3"]:
+ self._append_expression("*(struct st_table *) (%0#x + sizeof(struct RHash))" % ptr)
+ else:
+ self._append_expression("*(struct ar_table *) (%0#x + sizeof(struct RHash))" % ptr)
+
+ elif rval.is_type("RUBY_T_BIGNUM"):
+ 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_expression("((struct RBignum *) %0#x)->as.ary"
+ % val.GetValueAsUnsigned())
+ else:
+ print("T_BIGNUM: sign=%s len=%d" % (sign, len), file=self.result)
+ print(rval.as_type("bignum"), file=self.result)
+ self._append_expression("-Z %d -fx -- ((struct RBignum*)%d)->as.heap.digits" %
+ (len, val.GetValueAsUnsigned()))
+
+ elif rval.is_type("RUBY_T_FLOAT"):
+ self._append_expression("((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_expression("*(struct RTypedData *) %0#x" % val.GetValueAsUnsigned())
+ else:
+ print("T_DATA:", file=self.result)
+ self._append_expression("*(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 = (rval.flags & rbNodeTypeMask) >> rbNodeTypeShift
+ val = val.Cast(tRNode)
+
+ self._append_expression("(node_type) %d" % nd_type)
+
+ if nd_type == self.ruby_globals["NODE_SCOPE"]:
+ self._append_expression("*(struct RNode_SCOPE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BLOCK"]:
+ self._append_expression("*(struct RNode_BLOCK *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_IF"]:
+ self._append_expression("*(struct RNode_IF *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_UNLESS"]:
+ self._append_expression("*(struct RNode_UNLESS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CASE"]:
+ self._append_expression("*(struct RNode_CASE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CASE2"]:
+ self._append_expression("*(struct RNode_CASE2 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CASE3"]:
+ self._append_expression("*(struct RNode_CASE3 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_WHEN"]:
+ self._append_expression("*(struct RNode_WHEN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_IN"]:
+ self._append_expression("*(struct RNode_IN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_WHILE"]:
+ self._append_expression("*(struct RNode_WHILE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_UNTIL"]:
+ self._append_expression("*(struct RNode_UNTIL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ITER"]:
+ self._append_expression("*(struct RNode_ITER *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FOR"]:
+ self._append_expression("*(struct RNode_FOR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FOR_MASGN"]:
+ self._append_expression("*(struct RNode_FOR_MASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BREAK"]:
+ self._append_expression("*(struct RNode_BREAK *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_NEXT"]:
+ self._append_expression("*(struct RNode_NEXT *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_REDO"]:
+ self._append_expression("*(struct RNode_REDO *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RETRY"]:
+ self._append_expression("*(struct RNode_RETRY *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BEGIN"]:
+ self._append_expression("*(struct RNode_BEGIN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RESCUE"]:
+ self._append_expression("*(struct RNode_RESCUE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RESBODY"]:
+ self._append_expression("*(struct RNode_RESBODY *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ENSURE"]:
+ self._append_expression("*(struct RNode_ENSURE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_AND"]:
+ self._append_expression("*(struct RNode_AND *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OR"]:
+ self._append_expression("*(struct RNode_OR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MASGN"]:
+ self._append_expression("*(struct RNode_MASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LASGN"]:
+ self._append_expression("*(struct RNode_LASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DASGN"]:
+ self._append_expression("*(struct RNode_DASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_GASGN"]:
+ self._append_expression("*(struct RNode_GASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_IASGN"]:
+ self._append_expression("*(struct RNode_IASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CDECL"]:
+ self._append_expression("*(struct RNode_CDECL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CVASGN"]:
+ self._append_expression("*(struct RNode_CVASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN1"]:
+ self._append_expression("*(struct RNode_OP_ASGN1 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN2"]:
+ self._append_expression("*(struct RNode_OP_ASGN2 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN_AND"]:
+ self._append_expression("*(struct RNode_OP_ASGN_AND *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN_OR"]:
+ self._append_expression("*(struct RNode_OP_ASGN_OR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_CDECL"]:
+ self._append_expression("*(struct RNode_OP_CDECL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CALL"]:
+ self._append_expression("*(struct RNode_CALL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OPCALL"]:
+ self._append_expression("*(struct RNode_OPCALL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FCALL"]:
+ self._append_expression("*(struct RNode_FCALL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_VCALL"]:
+ self._append_expression("*(struct RNode_VCALL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_QCALL"]:
+ self._append_expression("*(struct RNode_QCALL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SUPER"]:
+ self._append_expression("*(struct RNode_SUPER *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ZSUPER"]:
+ self._append_expression("*(struct RNode_ZSUPER *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LIST"]:
+ self._append_expression("*(struct RNode_LIST *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ZLIST"]:
+ self._append_expression("*(struct RNode_ZLIST *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_HASH"]:
+ self._append_expression("*(struct RNode_HASH *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RETURN"]:
+ self._append_expression("*(struct RNode_RETURN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_YIELD"]:
+ self._append_expression("*(struct RNode_YIELD *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LVAR"]:
+ self._append_expression("*(struct RNode_LVAR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DVAR"]:
+ self._append_expression("*(struct RNode_DVAR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_GVAR"]:
+ self._append_expression("*(struct RNode_GVAR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CONST"]:
+ self._append_expression("*(struct RNode_CONST *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CVAR"]:
+ self._append_expression("*(struct RNode_CVAR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_NTH_REF"]:
+ self._append_expression("*(struct RNode_NTH_REF *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BACK_REF"]:
+ self._append_expression("*(struct RNode_BACK_REF *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MATCH"]:
+ self._append_expression("*(struct RNode_MATCH *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MATCH2"]:
+ self._append_expression("*(struct RNode_MATCH2 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MATCH3"]:
+ self._append_expression("*(struct RNode_MATCH3 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_STR"]:
+ self._append_expression("*(struct RNode_STR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DSTR"]:
+ self._append_expression("*(struct RNode_DSTR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_XSTR"]:
+ self._append_expression("*(struct RNode_XSTR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DXSTR"]:
+ self._append_expression("*(struct RNode_DXSTR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_EVSTR"]:
+ self._append_expression("*(struct RNode_EVSTR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_REGX"]:
+ self._append_expression("*(struct RNode_REGX *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DREGX"]:
+ self._append_expression("*(struct RNode_DREGX *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ONCE"]:
+ self._append_expression("*(struct RNode_ONCE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGS"]:
+ self._append_expression("*(struct RNode_ARGS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGS_AUX"]:
+ self._append_expression("*(struct RNode_ARGS_AUX *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OPT_ARG"]:
+ self._append_expression("*(struct RNode_OPT_ARG *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_KW_ARG"]:
+ self._append_expression("*(struct RNode_KW_ARG *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_POSTARG"]:
+ self._append_expression("*(struct RNode_POSTARG *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGSCAT"]:
+ self._append_expression("*(struct RNode_ARGSCAT *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGSPUSH"]:
+ self._append_expression("*(struct RNode_ARGSPUSH *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SPLAT"]:
+ self._append_expression("*(struct RNode_SPLAT *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DEFN"]:
+ self._append_expression("*(struct RNode_DEFN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DEFS"]:
+ self._append_expression("*(struct RNode_DEFS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ALIAS"]:
+ self._append_expression("*(struct RNode_ALIAS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_VALIAS"]:
+ self._append_expression("*(struct RNode_VALIAS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_UNDEF"]:
+ self._append_expression("*(struct RNode_UNDEF *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CLASS"]:
+ self._append_expression("*(struct RNode_CLASS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MODULE"]:
+ self._append_expression("*(struct RNode_MODULE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SCLASS"]:
+ self._append_expression("*(struct RNode_SCLASS *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_COLON2"]:
+ self._append_expression("*(struct RNode_COLON2 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_COLON3"]:
+ self._append_expression("*(struct RNode_COLON3 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DOT2"]:
+ self._append_expression("*(struct RNode_DOT2 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DOT3"]:
+ self._append_expression("*(struct RNode_DOT3 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FLIP2"]:
+ self._append_expression("*(struct RNode_FLIP2 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FLIP3"]:
+ self._append_expression("*(struct RNode_FLIP3 *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SELF"]:
+ self._append_expression("*(struct RNode_SELF *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_NIL"]:
+ self._append_expression("*(struct RNode_NIL *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_TRUE"]:
+ self._append_expression("*(struct RNode_TRUE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FALSE"]:
+ self._append_expression("*(struct RNode_FALSE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ERRINFO"]:
+ self._append_expression("*(struct RNode_ERRINFO *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DEFINED"]:
+ self._append_expression("*(struct RNode_DEFINED *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_POSTEXE"]:
+ self._append_expression("*(struct RNode_POSTEXE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DSYM"]:
+ self._append_expression("*(struct RNode_DSYM *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ATTRASGN"]:
+ self._append_expression("*(struct RNode_ATTRASGN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LAMBDA"]:
+ self._append_expression("*(struct RNode_LAMBDA *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARYPTN"]:
+ self._append_expression("*(struct RNode_ARYPTN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_HSHPTN"]:
+ self._append_expression("*(struct RNode_HSHPTN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FNDPTN"]:
+ self._append_expression("*(struct RNode_FNDPTN *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ERROR"]:
+ self._append_expression("*(struct RNode_ERROR *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LINE"]:
+ self._append_expression("*(struct RNode_LINE *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FILE"]:
+ self._append_expression("*(struct RNode_FILE *) %0#x" % val.GetValueAsUnsigned())
+ else:
+ self._append_expression("*(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_expression("(enum imemo_type) %d" % imemo_type)
+ self._append_expression("*(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)
diff --git a/misc/lldb_yjit.py b/misc/lldb_yjit.py
deleted file mode 100644
index cc37b990ea..0000000000
--- a/misc/lldb_yjit.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python
-#coding: utf-8
-#
-# Usage: run `command script import -r misc/lldb_yjit.py` on LLDB
-#
-
-from __future__ import print_function
-import lldb
-import os
-import shlex
-
-def list_comments(debugger, command, result, internal_dict):
- target = debugger.GetSelectedTarget()
- process = target.GetProcess()
- thread = process.GetSelectedThread()
- frame = thread.GetSelectedFrame()
-
- # Get the different types we need
- rb_darray_meta_t = target.FindFirstType("rb_darray_meta_t")
- codeblock_t = target.FindFirstType("codeblock_t")
- yjit_comment = target.FindFirstType("yjit_comment")
-
- # Get the global variables we need
- comments = target.FindFirstGlobalVariable("yjit_code_comments")
- cb = target.FindFirstGlobalVariable("cb").Cast(codeblock_t.GetPointerType())
-
- # Get the address of the memory block we're using
- mem_addr = cb.GetChildMemberWithName("mem_block").GetValueAsUnsigned()
-
- # Find the size of the darray comment list
- meta = comments.Cast(rb_darray_meta_t.GetPointerType())
- size = meta.GetChildMemberWithName("size").GetValueAsUnsigned()
-
- # Get the address of the block following the metadata header
- t_offset = comments.GetValueAsUnsigned() + rb_darray_meta_t.GetByteSize()
-
- # Loop through each comment and print
- for t in range(0, size):
- addr = lldb.SBAddress(t_offset + (t * yjit_comment.GetByteSize()), target)
- comment = target.CreateValueFromAddress("yjit_comment", addr, yjit_comment)
- string = comment.GetChildMemberWithName("comment")
- comment_offset = mem_addr + comment.GetChildMemberWithName("offset").GetValueAsUnsigned()
- print("%0#x %s" % (comment_offset, string.GetSummary()), file = result)
-
-
-def __lldb_init_module(debugger, internal_dict):
- debugger.HandleCommand("command script add -f lldb_yjit.list_comments lc")
diff --git a/misc/yjit_perf.py b/misc/yjit_perf.py
new file mode 100755
index 0000000000..61434e5eb4
--- /dev/null
+++ b/misc/yjit_perf.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+import os
+import sys
+from collections import Counter, defaultdict
+import os.path
+
+# Aggregating cycles per symbol and dso
+total_cycles = 0
+category_cycles = Counter()
+detailed_category_cycles = defaultdict(Counter)
+categories = set()
+
+def truncate_symbol(symbol, max_length=50):
+ """ Truncate the symbol name to a maximum length """
+ return symbol if len(symbol) <= max_length else symbol[:max_length-3] + '...'
+
+def categorize_symbol(dso, symbol):
+ """ Categorize the symbol based on the defined criteria """
+ if dso == 'sqlite3_native.so':
+ return '[sqlite3]'
+ elif 'SHA256' in symbol:
+ return '[sha256]'
+ elif symbol.startswith('[JIT] gen_send'):
+ return '[JIT send]'
+ elif symbol.startswith('[JIT]'):
+ return '[JIT code]'
+ elif '::' in symbol or symbol.startswith('yjit::') or symbol.startswith('_ZN4yjit'):
+ return '[YJIT compile]'
+ elif symbol.startswith('rb_vm_') or symbol.startswith('vm_') or symbol in {
+ "rb_call0", "callable_method_entry_or_negative", "invoke_block_from_c_bh",
+ "rb_funcallv_scope", "setup_parameters_complex", "rb_yield"}:
+ return '[interpreter]'
+ elif symbol.startswith('rb_hash_') or symbol.startswith('hash_'):
+ return '[rb_hash_*]'
+ elif symbol.startswith('rb_ary_') or symbol.startswith('ary_'):
+ return '[rb_ary_*]'
+ elif symbol.startswith('rb_str_') or symbol.startswith('str_'):
+ return '[rb_str_*]'
+ elif symbol.startswith('rb_sym') or symbol.startswith('sym_'):
+ return '[rb_sym_*]'
+ elif symbol.startswith('rb_st_') or symbol.startswith('st_'):
+ return '[rb_st_*]'
+ elif symbol.startswith('rb_ivar_') or 'shape' in symbol:
+ return '[ivars]'
+ elif 'match' in symbol or symbol.startswith('rb_reg') or symbol.startswith('onig'):
+ return '[regexp]'
+ elif 'alloc' in symbol or 'free' in symbol or 'gc' in symbol:
+ return '[GC]'
+ elif 'pthread' in symbol and 'lock' in symbol:
+ return '[pthread lock]'
+ else:
+ return symbol # Return the symbol itself for uncategorized symbols
+
+def process_event(event):
+ global total_cycles, category_cycles, detailed_category_cycles, categories
+
+ full_dso = event.get("dso", "Unknown_dso")
+ dso = os.path.basename(full_dso)
+ symbol = event.get("symbol", "[unknown]")
+ cycles = event["sample"]["period"]
+ total_cycles += cycles
+
+ category = categorize_symbol(dso, symbol)
+ category_cycles[category] += cycles
+ detailed_category_cycles[category][(dso, symbol)] += cycles
+
+ if category.startswith('[') and category.endswith(']'):
+ categories.add(category)
+
+def trace_end():
+ if total_cycles == 0:
+ return
+
+ print("Aggregated Event Data:")
+ print("{:<20} {:<50} {:>20} {:>15}".format("[dso]", "[symbol or category]", "[top-most cycle ratio]", "[num cycles]"))
+
+ for category, cycles in category_cycles.most_common():
+ ratio = (cycles / total_cycles) * 100
+ dsos = {dso for dso, _ in detailed_category_cycles[category]}
+ dso_display = next(iter(dsos)) if len(dsos) == 1 else "Multiple DSOs"
+ print("{:<20} {:<50} {:>20.2f}% {:>15}".format(dso_display, truncate_symbol(category), ratio, cycles))
+
+ # Category breakdown
+ for category in categories:
+ symbols = detailed_category_cycles[category]
+ category_total = sum(symbols.values())
+ category_ratio = (category_total / total_cycles) * 100
+ print(f"\nCategory: {category} ({category_ratio:.2f}%)")
+ print("{:<20} {:<50} {:>20} {:>15}".format("[dso]", "[symbol]", "[top-most cycle ratio]", "[num cycles]"))
+ for (dso, symbol), cycles in symbols.most_common():
+ symbol_ratio = (cycles / category_total) * 100
+ print("{:<20} {:<50} {:>20.2f}% {:>15}".format(dso, truncate_symbol(symbol), symbol_ratio, cycles))
+
+# There are two ways to use this script:
+# 1) perf script -s misc/yjit_perf.py -- native interface
+# 2) perf script > perf.txt && misc/yjit_perf.py perf.txt -- hack, which doesn't require perf with Python support
+#
+# In both cases, __name__ is "__main__". The following code implements (2) when sys.argv is 2.
+if __name__ == "__main__" and len(sys.argv) == 2:
+ if len(sys.argv) != 2:
+ print("Usage: yjit_perf.py <filename>")
+ sys.exit(1)
+
+ with open(sys.argv[1], "r") as file:
+ for line in file:
+ # [Example]
+ # ruby 78207 3482.848465: 1212775 cpu_core/cycles:P/: 5c0333f682e1 [JIT] getlocal_WC_0+0x0 (/tmp/perf-78207.map)
+ row = line.split(maxsplit=6)
+
+ period = row[3] # "1212775"
+ symbol, dso = row[6].split(" (") # "[JIT] getlocal_WC_0+0x0", "/tmp/perf-78207.map)\n"
+ symbol = symbol.split("+")[0] # "[JIT] getlocal_WC_0"
+ dso = dso.split(")")[0] # "/tmp/perf-78207.map"
+
+ process_event({"dso": dso, "symbol": symbol, "sample": {"period": int(period)}})
+ trace_end()