2007-11-25
デバッガのソースコード
|instruction.rb
# # instruction.rb - structured bytecode library # module VMLib class InstSeqTree Headers = %w(magic major_version minor_version format_type misc name filename type locals args exception_table) # misc name filename line type locals args exception_table) # call-seq: # VMLib::InstSeqTree.new(parent, iseq) # parent Partent of InstSeqTree # For example, when you will construct InstSeqTree of # the method body, you must 'parent' is InstSeqTree of definition # code of the method. # If parent is none, 'parent' is nil. # iseq Instruction Sequence, Normally the result of # VM::InstructionSequence.compile(...) or # VM::InstructionSequence.compile_file(...) def initialize(parent = nil, iseq = nil) @klasses = {} @methodes = {} @blockes = {} @line = {} @line[nil] = [] @line_list = [] @header = {} @body = nil @parent = parent @cur_send_no = 0 Headers.each do |name| @header[name] = nil end if iseq then init_from_ary(iseq.to_a) end end attr :header attr :klasses attr :methodes attr :blockes attr :line attr :line_list attr :body attr :parent def init_from_ary(ary) i = 0 Headers.each do |name| @header[name] = ary[i] i = i + 1 end @body = ary[i] curlinno = nil @body.each do |inst| if inst.is_a? Integer then # Line number curlinno = "#{inst}" i = 1 while @line_list.include?(curlinno) do curlinno = "#{inst}-#{i}" i = i + 1 end @line_list.push curlinno @line[curlinno] = [] elsif inst.is_a? Array case inst[0] when :defineclass if inst[2] then obj = InstSeqTree.new(self) obj.init_from_ary(inst[2]) @klasses[inst[1]] = obj end when :definemethod if inst[2] then obj = InstSeqTree.new(self) obj.init_from_ary(inst[2]) @methodes[inst[1]] = obj end # inst[3]にはブロック内のメソッドの通し番号が入る # この通し番号は例外処理でreturn pointの確定などに # 使われる。 when :send if inst[3] then obj = InstSeqTree.new(self) obj.init_from_ary(inst[3]) @blockes[@cur_send_no]= obj inst[3] = [inst[3], @cur_send_no] else inst[3] = [nil, @cur_send_no] end @cur_send_no += 1 when :invokesuper if inst[2] then obj = InstSeqTree.new(self) obj.init_from_ary(inst[2]) @blockes[@cur_send_no] = obj inst[2] = [inst[3], @cur_send_no] else inst[2] = [nil, @cur_send_no] end @cur_send_no += 1 end @line[curlinno].push inst elsif inst.is_a? Symbol # Label if !@line_list.include?(curlinno) then @line_list.push curlinno end @line[curlinno].push inst else raise inst end end end def to_a res = [] Headers.each do |name| res.push @header[name] end body = [] @line_list.each do |ln| body.push ln.to_i @line[ln].each do |inst| if inst.is_a? Array then cinst = inst.clone case inst[0] when :defineclass if inst[2] then cinst[2] = @klasses[inst[1]].to_a end when :definemethod if inst[2] then cinst[2] = @methodes[inst[1]].to_a end when :send if inst[3] and inst[3][0] then cinst[3] = @blockes[inst[3][1]].to_a else cinst[3] = nil end when :invokesuper if inst[2] and inst[2][0] then cinst[2] = @blockes[inst[2][1]].to_a else cinst[2] = nil end end body.push cinst else body.push inst end end end res.push body res end def add_code_all_before_block(&action) traverse_code([nil, nil, nil]) do |code, info| if code.line_list[0] != :before then code.line_list.unshift :before code.line[:before] = [] end curlno = 0 code.line_list.sort_by {|item| item.to_i}.each do |n| if n.to_i != 0 then curlno = n break end end line = code.line inscode = action.call(header['filename'], info, curlno) line[:before] = insert_code(inscode, line[:before], 0) end end # 命令の最後にコードを付加するのではなく、:leave命令の直前に # 命令を付加しているのに注意 def add_code_all_before_return(&action) traverse_code([nil, nil, nil]) do |code, info| line = code.line code.line.each do |no, cont| insact = action.call(header['filename'], info, no) line[no] = insert_code_before_return(insact, line[no], code) end end end def add_code_all_around_send(&action) traverse_code([nil, nil, nil]) do |code, info| line = code.line code.line.each do |no, cont| insact = action.call(header['filename'], info, no) line[no] = insert_code_around_send(insact, line[no], code) end end end def add_code_all_before_line(&action) traverse_code([nil, nil, nil]) do |code, info| line = code.line code.line.each do |no, cont| inscode = action.call(header['filename'], info, no) line[no] = insert_code(inscode, line[no], 0) end end end def traverse_code(info, &action) action.call(self, info) blockes.each do |sno, cont| cont.traverse_code([info[0], info[1], sno], &action) end klasses.each do |name, cont| cont.traverse_code([name, nil, nil], &action) end methodes.each do |name, cont| cont.traverse_code([info[0], name, nil], &action) end end private def join(*arg) res = [] arg.each do |n| if n then res += n end end res end # ラベルと:getinlinecache命令の間に命令を挿入するとコア吐くので # それを防ぐ # insposは挿入地点で0が先頭 def insert_code(code, line, inspos) if line[inspos].is_a? Array and line[inspos][0] == :getinlinecache then join(line[0, inspos + 1], code, line[inspos+1..-1]) else join(line[0, inspos], code, line[inspos..-1]) end end # sendの前後にコードを挿入する # スタックフレームの作成に使う # insact: 実行するとsendの前、後の2つのコードを返す def insert_code_around_send(insact, line, ctree) line.inject([]) do |res, inst| issend = false sendno = nil mname = nil if inst.is_a? Array then if inst[0] == :send then issend = true sendno = inst[3][1] mname = inst[1] elsif inst[0] == :sendsuper then issend = true sendno = inst[2][1] mname = inst[1] end end if issend then res = res + insact.call(mname, sendno, inst) else res.push inst end res end end TAG_RETURN = 1 TAG_BREAK = 2 def insert_code_before_return(insact, line, ctree) line.inject([]) do |res, inst| if inst.is_a? Array then if inst[0] == :leave then res = res + insact.call(1) elsif inst[0] == :throw then if inst[1] == TAG_RETURN then # return # この場合は一番外側のmethodまでたどってそこにreturn itree = ctree n = 1 while itree do if itree.header['type'] == :method then break end # p itree.header['type'] n += 1 itree = itree.parent end if itree then res = res + insact.call(n) end elsif inst[1] == TAG_BREAK then # break # この場合はブロックにreturnする res = res + insact.call(1) end end end res.push inst res end end end end
debug.rb
require 'instruction' require 'pp' module Depatch class SourceManager def initialize(fname) @source_line = File.readlines(fname) end def list(center, len = 10, cur = nil) ll = 0 if center < len / 2 then ll = 0 else ll = center - len / 2 - 1 end len.times do |n| if ll == cur then printf "=> %4d %s" % [ll + 1, @source_line[ll]] else printf " %4d %s" % [ll + 1, @source_line[ll]] end ll = ll + 1 end end end class DebugEnv def initialize(fname, line, bind) @fname = fname @line = line @bind = bind # ブレークポイントのハンドラ # デバッガのContinuationが入る @break_handler = nil end def set_line(lin) @line = lin end def set_fname(fname) @fname = fname end attr :fname attr :line attr :bind end class DebugContext def initialize @frame = [] @frame_sp = -1 @frame_curp = -1 @top_frame = [nil, 0, TOPLEVEL_BINDING] @context_table = [] @max_context_no = -1 @env = DebugEnv.new(*@top_frame) @debugger = nil @required_file = [] end attr :env def set_debugger(debugger) @debugger = debugger end def reset_env @frame_curp = @frame_sp + 1 set_env p @env end def set_env if @frame_curp <= @frame_sp then cnt = @frame[@frame_curp] cno = cnt[0] fi = @context_table[cno] @env = DebugEnv.new(fi[0], fi[2], cnt[1]) else @env = DebugEnv.new(*@top_frame) end end def run(fname) @env.set_fname(fname) @top_frame[0] = fname end def frame_up if @frame_curp < @frame_sp + 1 then @frame_curp += 1 set_env else print "Here is top frame\n" end end def frame_down if @frame_curp > 0 then @frame_curp -= 1 set_env else print "Here is bottom frame\n" end end def add_context(fname, info, no, sendno) @max_context_no += 1 @context_table[@max_context_no] = [fname, info, no, sendno] # print "#{@context_table[@max_context_no]} : #{@max_context_no} \n" @max_context_no end # ブロック内でのreturnで、catchポイントを検索する # sendnoにreturnを含むブロックを起動したメソッドのシーケンス番号 # が入っているので、@context_tableからそのシーケンス番号のメソッドを # 検索する。 def search_return_pos(fname, klass, method, sendno) @context_table.each_with_index {|cont, idx| info = cont[1] if fname == cont[0] and klass == info[0] and method == info[1] and info[2] == nil and sendno == cont[3] then # returnはmethodから抜けるのでmethodのトップレベルまでスタックを # 巻き戻す必要がある。 # methodのトップレベルのcontextでは現在のブロックを起動した # メソッドのシーケンス番号をあらわすinfo[2]はnilになっていなければ # ならない return idx end if fname == cont[0] and klass == info[0] and method == info[1] and sendno == cont[3] then # ブロックがレキシカルにネスとしてる場合の処理 # info[2]がnilでないときはmethodのトップレベルじゃない # でも、呼び出したメソッドなので、sendnoを書き換える sendno = info[2] retry end } # たぶん内部エラー、原因追及のため情報をたんまり表示する p @context_table raise "Can't return #{fname} #{klass} #{method} #{sendno}" end def require_load(fname, no, bind) rfname = fname if !FileTest.exist?(rfname) then rfname = fname + ".rb" if !FileTest.exist?(rfname) then require fname return false end end if !@required_file.include?(rfname) @debugger.load_file(rfname) @required_file.push rfname end return rfname end def require_run(fname, no, bind) if fname = require_load(fname, no, bind) then method_enter(no, bind) @debugger.run(fname) method_leave(no) end end def method_enter(no, bind) @frame_sp += 1 # print "enter #{@context_table[no]} #{no}\n" if @frame[@frame_sp] then @frame[@frame_sp][0] = no if bind then @frame[@frame_sp][1] = bind end else @frame[@frame_sp] = [no, bind] end # @frame[@frame_sp] = @context_table[no] nil end def exception_throw(no, catchno) lvl = 0 @frame_sp.downto(0) do |n| lvl += 1 if @frame[n][0] == catchno then @frame_sp -= lvl return end end end def method_leave(no) @frame_sp -= 1 end def set_break_handler(cont) @break_handler = cont end def backtrace @frame_sp.downto(0) do |n| yield @context_table[@frame[n][0]], @frame[n][1] end end def handle_break(fname, lno, klass, method, bind) @top_frame = [fname, lno, bind] @env = DebugEnv.new(*@top_frame) @frame_curp = @frame_sp + 1 print "\nbreak in #{lno} at #{fname} (#{klass}##{method})\n" print eval("local_variables", bind) if @break_handler then callcc {|c| @break_handler.call(c) } end end end class Debugger include VMLib def initialize @source = {} @file_iseqt = {} $__debug__ = DebugContext.new @curfname = nil # PP.pp @iseqt.to_a @debugee_cont = nil end def load_file(fname) if @source[fname] == nil then @source[fname] = SourceManager.new(fname) iseq = VM::InstructionSequence.compile_file(fname) # print VM::InstructionSequence.load(iseq.to_a).disasm @file_iseqt[fname] = InstSeqTree.new(nil, iseq) set_trace(@file_iseqt[fname]) end end def set_current_file(fname) @curfname = fname end def run(fname = @curfname) # PP.pp @file_iseqt[fname].to_a $__debug__.run(fname) VM::InstructionSequence.load(@file_iseqt[fname].to_a).eval end =begin begin require 'readline' def readline(prompt, hist) Readline::readline(prompt, hist) end rescue LoadError =end def readline(prompt, hist) STDOUT.print prompt STDOUT.flush line = STDIN.gets exit unless line line.chomp! line end # end def where $__debug__.backtrace {|posinfo, bind| # p posinfo if posinfo[1][0] and posinfo[1][1] then print "#{posinfo[1][0]}##{posinfo[1][1]} #{posinfo[0]}:#{posinfo[2]}\n" else print "#{posinfo[0]}:#{posinfo[2]}\n" end print "local variables\n" print eval("local_variables",bind) print "\n\n" } end def disasm(iseqt) print VM::InstructionSequence.load(iseqt.to_a).disasm end def up $__debug__.frame_up end def down $__debug__.frame_down end def command_rep while command = readline("(dep) ", true) case command when /^\s*b(?:reak)?\s+(?:(.+):)?([0-9]+)/ line = $2 if $1 then file = $1 else file = @curfname end set_break_line(@file_iseqt[file], file, line) when /^\s*b(?:reak)?\s+(?:(.+)[.#])?(\S+)/ if $1 then klassn = $1.to_sym else klassn = nil end methodn = $2 set_break_method(klassn, methodn.to_sym) print "Set break point #{klassn.to_s}##{methodn}\n" when /^\s*r(?:un)?/ @debugee_cont = callcc {|c| $__debug__.set_break_handler(c) begin run rescue StandardError, ScriptError p $! p $!.backtrace /^([^:]+):([0-9]+):/ =~ $!.backtrace[0] @top_frame = [$1, $2, nil] p @top_frame $__debug__.reset_env $__debug__.frame_down end nil } when /^\s*c(?:ont)?/ if @debugee_cont then @debugee_cont.call(nil) end when /^\s*w(?:here)?/ where when /^\s*l(?:ist)?/ p $__debug__.env.fname @source[$__debug__.env.fname].list($__debug__.env.line.to_i) when /^\s*dis(?:asm)?/ disasm(@file_iseqt[$__debug__.env.fname]) when /^\s*up$/ up @curfname = $__debug__.env.fname p @curfname @source[$__debug__.env.fname].list($__debug__.env.line.to_i, 1) when /^\s*down$/ down @curfname = $__debug__.env.fname @source[$__debug__.env.fname].list($__debug__.env.line.to_i, 1) else begin eval(command, $__debug__.env.bind) rescue StandardError, ScriptError print "Error: #{command}\n" STDOUT.flush end end STDOUT.flush end end def set_trace(iseqt) iseqt.add_code_all_around_send {|fname, info, no| lambda {|mname, sendno, inst| cno = $__debug__.add_context(fname, info, no, sendno) case mname when :require [ [:swap], [:pop], [:getglobal, :$__debug__], [:swap], [:putobject, cno], [:putnil], [:send, :binding, 0, nil, 24, nil], [:send, :require_run, 3, nil, 0, nil], ] else [ [:getglobal, :$__debug__], [:putobject, cno], [:putnil], [:send, :binding, 0, nil, 24, nil], [:send, :method_enter, 2, nil, 0, nil], [:pop], inst, [:getglobal, :$__debug__], [:putobject, cno], [:send, :method_leave, 1, nil, 0, nil], [:pop], ] end } } # before_returnといいながら、例外のときだけコードを挿入する # returnのスタック調整用 iseqt.add_code_all_before_return {|fname, info, no| lambda {|level| if level > 1 then cno = $__debug__.add_context(fname, info, no, nil) retno = $__debug__.search_return_pos(fname, info[0], info[1], info[2]) [ [:getglobal, :$__debug__], [:putobject, cno], [:putobject, retno], [:send, :exception_throw, 2, nil, 0, nil], [:pop], ] else [] end } } end def set_break_line(iseqt, fname, line) if iseqt.nil? then return nil end if fname == nil then fname = @curfname end line = line.to_s iseqt.add_code_all_before_line {|fn, info, no| if fname == fn and no == line then [ [:getglobal, :$__debug__], [:putobject, fn], [:putobject, no], [:putobject, info[0]], [:putobject, info[1]], [:putnil], [:send, :binding, 0, nil, 24, nil], [:send, :handle_break, 5, nil, 0, nil], [:pop] ] else [] end } true end def set_break_method(klass, method) @file_iseqt.each do |file, iseqt| iseqt.add_code_all_before_block {|fn, info, no| if info[0] == klass and info[1] == method and info[2] == nil then [ [:getglobal, :$__debug__], [:putobject, fn], [:putobject, no], [:putobject, info[0]], [:putobject, info[1]], [:putnil], [:send, :binding, 0, nil, 24, nil], [:send, :handle_break, 5, nil, 0, nil], [:pop] ] else [] end } end true end end end =begin VM::InstructionSequence.compile_option = { trace_instruction: false, peephole_optimization: false, inline_const_cache: false, specialized_instruction: false, operands_unification: false, instructions_unification: false, } =end debug = Depatch::Debugger.new $__debug__.set_debugger(debug) debug.load_file(ARGV[0]) debug.set_current_file(ARGV[0]) ARGV.shift debug.command_rep
- 1 http://blog-search.yahoo.co.jp/search?p=drb+ruby&ei=UTF-8&fr=moz2&rls=org.mozilla:ja-JP:official
- 1 http://d.hatena.ne.jp/diarylist?of=0&mode=rss&type=public
- 1 http://d.hatena.ne.jp/diarylist?of=100&mode=rss&type=public
- 1 http://d.hatena.ne.jp/keyword/Can
- 1 http://d.hatena.ne.jp/keyword/Ruby
- 1 http://d.hatena.ne.jp/keyworddiary/入地
- 1 http://d.hatena.ne.jp/keyworddiary/Ruby
- 1 http://d.hatena.ne.jp/miura1729
- 1 http://mixi.jp/view_diary.pl?id=634457015&owner_id=9799227
- 1 http://reader.livedoor.com/reader/