class GDBRuby

Constants

MAX_FRAMES

Public Class Methods

new(config) click to toggle source
# File bin/gdbruby.rb, line 356
def initialize(config)
  @config = config
end

Public Instance Methods

check_ruby_version() click to toggle source
# File bin/gdbruby.rb, line 469
def check_ruby_version
  @ruby_version = @gdb.cmd_get_value('p ruby_version')
  case @ruby_version.intern
  when :'2.0.0'
  end
  raise "unknown ruby version" unless @ruby_version
end
get_map_of_ruby_thread_pointer_to_gdb_thread_id() click to toggle source
# File bin/gdbruby.rb, line 384
def get_map_of_ruby_thread_pointer_to_gdb_thread_id
  # After 'set pagination off' is executed,
  # thread info is one line per thread.
  map = {}
  @gdb.cmd_exec('info threads').each_line do |line|
    if line =~ /\A[\s\*]+(\d+)/
      gdb_thread_id = $1
      @gdb.cmd_exec("thread #{gdb_thread_id}")
      @gdb.cmd_exec("backtrace").each_line do |bt_line|
        if bt_line =~ /\(th=(0x[0-9a-f]+)/
          ruby_thread_pointer = $1
          map[ruby_thread_pointer] = gdb_thread_id
          break
        end
      end
    end
  end
  map
end
get_ruby_frame(frame_count, thread) click to toggle source
# File bin/gdbruby.rb, line 404
def get_ruby_frame(frame_count, thread)
  # Accessor
  cfp = "(#{thread}->cfp + #{frame_count})"
  iseq = "#{cfp}->iseq"

  # Check iseq
  iseq_ptr = @gdb.cmd_get_pointer("p #{iseq}", 'rb_iseq_t')
  if iseq_ptr.hex != 0
    # TODO: check cfp->pc is null or not

    iseq_type = @gdb.cmd_get_value("p #{iseq}->type").intern

    case iseq_type
    when :ISEQ_TYPE_TOP
      return
    end

    # Ruby function
    @prev_location = {
      :cfp => cfp,
      :iseq => iseq,
    }
    file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.absolute_path"))
    if file_path.empty?
      file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.path"))
    end
    label = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.label"))
    base_label = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.base_label"))
    line_no = @ri.rb_vm_get_sourceline(cfp, iseq)

    self_value = @gdb.cmd_get_value("p #{cfp}->self")
    self_type = @ri.rb_type(self_value)
    self_name = self_type == 'RUBY_T_CLASS' ? @gdb.cmd_get_value("p rb_class2name(#{cfp}->self)") : ''

    func_prefix = "#{self_name}#" unless self_name.empty?

    {
      :callee => label.empty? ? '(unknown)' : "#{func_prefix}#{label}",
      :args => '', # TODO: implement
      :source_path_line => "#{file_path}:#{line_no}",
    }
  elsif @ri.rubyvm_cfunc_frame_p(cfp)
    # C function

    mid = @gdb.cmd_get_value("p #{cfp}->me->def ? #{cfp}->me->def->original_id : #{cfp}->me->called_id")
    label = @ri.rb_id2str(mid)
    if @prev_location
      cfp = @prev_location[:cfp]
      iseq = @prev_location[:iseq]

      file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.absolute_path"))
      if file_path.empty?
        file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.path"))
      end
      line_no = @ri.rb_vm_get_sourceline(cfp, iseq)
    end

    {
      :callee => label,
      :args => '', # TODO: implement
      :source_path_line => "#{file_path}:#{line_no}",
    }
  end
end
show_backtrace() click to toggle source
# File bin/gdbruby.rb, line 484
def show_backtrace
  # TODO: List threads with ruby_current_vm->living_threads and dump all threads.
  #       Now, we dump only ruby_current_thread which is equivalent to ruby_current_vm->running_thread.

  thread_map = get_map_of_ruby_thread_pointer_to_gdb_thread_id

  dump_results = thread_map.map do |ruby_pointer, gdb_thread|
    output_lines = []

    ruby_thread = "((rb_thread_t *) #{ruby_pointer})"
    @gdb.cmd_exec("thread #{gdb_thread}")

    output_lines << "thread: #{gdb_thread}"
    output_lines << ''

    # Show C backtrace
    if @config['c_trace', true]
      c_bt = @gdb.cmd_exec('bt')
      output_lines << 'c_backtrace:'
      c_bt.each_line do |line|
        break if line == '(gdb) '
        output_lines << line
      end
      output_lines << ''
    end

    # Show Ruby backtrace
    output_lines << "ruby_backtrace:"
    cfp_count = @gdb.cmd_get_value("p (rb_control_frame_t *)(#{ruby_thread}->stack + #{ruby_thread}->stack_size) - #{ruby_thread}->cfp").to_i

    frame_infos = []
    @prev_location = nil
    # NOTE: @prev_location may not be set properly when limited by MAX_FRAMES
    ([MAX_FRAMES, cfp_count].min - 1).downto(0).each do |count|
      frame_info = get_ruby_frame(count, ruby_thread)
      frame_infos << frame_info if frame_info
    end
    frame_infos.reverse.each_with_index do |fi, i|
      output_lines << "[#{frame_infos.length - i}] #{fi[:callee]}(#{fi[:args]}) <- #{fi[:source_path_line]}"
    end
    output_lines.join("\n")
  end

  puts dump_results.join("\n")
end
show_environ() click to toggle source
# File bin/gdbruby.rb, line 372
def show_environ
  i = 0
  puts "environ:"
  while true
    response = @gdb.cmd_get_value("p ((char **)environ)[#{i}]")
    break if response.empty? or response == '0x0'
    puts response
    i += 1
  end
  puts ''
end
show_ruby_version() click to toggle source
# File bin/gdbruby.rb, line 477
def show_ruby_version
  check_ruby_version
  puts 'ruby_version:'
  puts @ruby_version
  puts ''
end
trace() click to toggle source
# File bin/gdbruby.rb, line 360
def trace
  @gdb = GDB.new(@config)
  @ri = RubyInternal.new(@gdb)
  puts "command:\n#{@gdb.exec_options.join(' ')}"
  puts ''
  @gdb.run do
    show_environ if @config['env', true]
    show_ruby_version
    show_backtrace
  end
end