# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. # Copyright (C) 2000 Information-technology Promotion Agency, Japan # Copyright (C) 2000-2003 NAKAMURA, Hiroshi # Changes for the FreeRIDE IDE by Laurent JULLIARD. FreeRIDE uses # Distributed ruby (DRuby) to communicate with the debugger back # end. However, this can't interoperate with C++ in KDevelop and so # a Unix domain socket connection is used instead. # Adapted for KDevelop debugging # ------------------------------ # begin : Mon Nov 1 2004 # copyright : (C) 2004 by Richard Dale # email : Richard_Dale@tipitina.demon.co.uk if $SAFE > 0 STDERR.print "-r debug.rb is not available in safe mode\n" exit 1 end require 'tracer' require 'pp' require 'rbconfig' class Tracer def Tracer.trace_func(*vars) Single.trace_func(*vars) end end # FreeRIDE/KDevelop must always intercept exits hence the exit! redefinition # at_exit calls the quit method to cleanly disconnect from the # FreeRIDE/KDevelop debugger client module Kernel alias_method :exit!, :exit end BEGIN { at_exit do set_trace_func nil DEBUGGER__.quit end } SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ class DEBUGGER__ class Mutex def initialize @locker = nil @waiting = [] @locked = false; end def locked? @locked end def lock return if Thread.critical return if @locker == Thread.current while (Thread.critical = true; @locked) @waiting.push Thread.current Thread.stop end @locked = true @locker = Thread.current Thread.critical = false self end def unlock return if Thread.critical return unless @locked unless @locker == Thread.current raise RuntimeError, "unlocked by other" end Thread.critical = true t = @waiting.shift @locked = false @locker = nil Thread.critical = false t.run if t self end end MUTEX = Mutex.new class Context DEBUG_LAST_CMD = [] def readline(prompt_cmd, hist) DEBUGGER__.client.readline(prompt_cmd) end def initialize if Thread.current == Thread.main @stop_next = 1 else @stop_next = 0 end @last_file = nil @file = nil @line = nil @no_step = nil @frames = [] @finish_pos = 0 @trace = false @trace_ruby = false @catch = "StandardError" @suspend_next = false end def stop_next(n=1) @stop_next = n end def set_suspend @suspend_next = true end def clear_suspend @suspend_next = false end def suspend_all DEBUGGER__.suspend end def resume_all DEBUGGER__.resume end def check_suspend return if Thread.critical while (Thread.critical = true; @suspend_next) DEBUGGER__.waiting.push Thread.current @suspend_next = false Thread.stop end Thread.critical = false end def trace? @trace end def set_trace(arg) @trace = arg end def trace_ruby? @trace_ruby end def set_trace_ruby(arg) @trace_ruby = arg end def stdout DEBUGGER__.stdout end def break_points DEBUGGER__.break_points end def display DEBUGGER__.display end def context(th) DEBUGGER__.context(th) end def set_trace_all(arg) DEBUGGER__.set_trace(arg) end def set_last_thread(th) DEBUGGER__.set_last_thread(th) end def debug_eval(str, binding) begin val = eval(str, binding) rescue StandardError, ScriptError => e at = eval("caller(1)", binding) stdout.printf "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '') for i in at stdout.printf "\tfrom %s\n", i end throw :debug_error end end def debug_silent_eval(str, binding) begin eval(str, binding) rescue StandardError, ScriptError nil end end # Temporarily change the pretty_print methods to not expand arrays # and hashes, just give the length def customize_debug_pp Array.module_eval %q{ def pretty_print(pp) pp.pp "Array (%d element(s))" % length end } Hash.module_eval %q{ def pretty_print(pp) pp.pp "Hash (%d element(s))" % length end } end # Restore the original pretty_print methods for arrays and hashes def restore_debug_pp Array.module_eval %q{ def pretty_print(q) q.group(1, '[', ']') { self.each {|v| q.comma_breakable unless q.first? q.pp v } } end } Hash.module_eval %q{ def pretty_print(q) q.pp_hash self end } end # Prevent the 'var *' commands from expanding Arrays and Hashes # This could be done by redefining inspect, but that would affect # everywhere not just here and in the pp command. def debug_inspect(obj) if obj.kind_of? Array "Array (%d element(s))" % obj.length elsif obj.kind_of? Hash "Hash (%d element(s))" % obj.length elsif obj.kind_of? String str = obj.inspect if str.length > 255 "String (length %d)" % obj.length else str end else obj.inspect end end def var_list(ary, binding) ary.sort! for v in ary stdout.printf " %s => %s\n", v, debug_inspect(eval(v, binding)) end end def const_list(ary, obj) ary.sort! for c in ary str = debug_inspect(obj.module_eval(c)) if c.to_s != str && str !~ /^Qt::|^KDE::/ && c.to_s !~ /@@classes$|@@cpp_names$|@@idclass$|@@debug_level$/ && c.to_s !~ /^DCOPMeta$|^Meta$|SCRIPT_LINES__|TRUE|FALSE|NIL|MatchingData/ && c.to_s !~ /^PLATFORM$|^RELEASE_DATE$|^VERSION$|SilentClient|SilentObject/ && c.to_s !~ /^Client$|^Context$|^DEBUG_LAST_CMD$|^MUTEX$|^Mutex$|^SimpleDelegater$|^Delegater$/ && c.to_s !~ /IPsocket|IPserver|UDPsocket|UDPserver|TCPserver|TCPsocket|UNIXserver|UNIXsocket/ if c.to_s == "ENV" stdout.printf " %s => Hash (%d element(s))\n", c, obj.module_eval(c).length else stdout.printf " %s => %s\n", c, str end end end end def debug_variable_info(input, binding) case input when /^\s*g(?:lobal)?$/ var_list(global_variables, binding) when /^\s*l(?:ocal)?$/ var_list(eval("local_variables", binding) << "self", binding) when /^\s*i(?:nstance)?\s+/ obj = debug_eval($', binding) var_list(obj.instance_variables, obj.instance_eval{binding()}) when /^\s*cl(?:ass)?\s+/ obj = debug_eval($', binding) unless obj.kind_of? Module stdout.print "Should be Class/Module: ", $', "\n" else const_list(obj.class_variables, obj) end when /^\s*c(?:onst(?:ant)?)?\s+/ obj = debug_eval($', binding) unless obj.kind_of? Module stdout.print "Should be Class/Module: ", $', "\n" else const_list(obj.constants, obj) end end end def debug_method_info(input, binding) case input when /^i(:?nstance)?\s+/ obj = debug_eval($', binding) len = 0 for v in obj.methods.sort len += v.size + 1 if len > 70 len = v.size + 1 stdout.print "\n" end stdout.print v, " " end stdout.print "\n" else obj = debug_eval(input, binding) unless obj.kind_of? Module stdout.print "Should be Class/Module: ", input, "\n" else len = 0 for v in obj.instance_methods(false).sort len += v.size + 1 if len > 70 len = v.size + 1 stdout.print "\n" end stdout.print v, " " end stdout.print "\n" end end end def thnum num = DEBUGGER__.instance_eval{@thread_list[Thread.current]} unless num DEBUGGER__.make_thread_list num = DEBUGGER__.instance_eval{@thread_list[Thread.current]} end num end def debug_command(file, line, id, binding) MUTEX.lock set_last_thread(Thread.current) frame_pos = 0 binding_file = file binding_line = line previous_line = nil if ENV['EMACS'] stdout.printf "\032\032%s:%d:\n", binding_file, binding_line else stdout.printf "%s:%d:%s", binding_file, binding_line, line_at(binding_file, binding_line) end @frames[0] = [binding, file, line, id] display_expressions(binding) prompt = true while prompt and input = readline("(rdb:%d) "%thnum(), true) catch(:debug_error) do if input == "" next unless DEBUG_LAST_CMD[0] input = DEBUG_LAST_CMD[0] stdout.print input, "\n" else DEBUG_LAST_CMD[0] = input end case input when /^\s*trace_ruby(?:\s+(on|off))?$/ if defined?( $1 ) if $1 == 'on' set_trace_ruby true else set_trace_ruby false end end when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/ if defined?( $2 ) if $1 == 'on' set_trace_all true else set_trace_all false end elsif defined?( $1 ) if $1 == 'on' set_trace true else set_trace false end end if trace? stdout.print "Trace on.\n" else stdout.print "Trace off.\n" end when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/ pos = $2 if $1 klass = debug_silent_eval($1, binding) # file = $1 file = File.expand_path($1) end if pos =~ /^\d+$/ pname = pos pos = pos.to_i else pname = pos = pos.intern.id2name end break_points.push [true, 0, klass || file, pos] stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, klass || file, pname when /^\s*b(?:reak)?\s+(.+)[#.]([^.:]+)$/ pos = $2.intern.id2name klass = debug_eval($1, binding) break_points.push [true, 0, klass, pos] stdout.printf "Set breakpoint %d at %s.%s\n", break_points.size, klass, pos when /^\s*wat(?:ch)?\s+(.+)$/ exp = $1 break_points.push [true, 1, exp] stdout.printf "Set watchpoint %d\n", break_points.size, exp when /^\s*b(?:reak)?$/ if break_points.find{|b| b[1] == 0} n = 1 stdout.print "Breakpoints:\n" for b in break_points if b[0] and b[1] == 0 stdout.printf " %d %s:%s\n", n, b[2], b[3] end n += 1 end end if break_points.find{|b| b[1] == 1} n = 1 stdout.print "\n" stdout.print "Watchpoints:\n" for b in break_points if b[0] and b[1] == 1 stdout.printf " %d %s\n", n, b[2] end n += 1 end end if break_points.size == 0 stdout.print "No breakpoints\n" else stdout.print "\n" end when /^\s*del(?:ete)?(?:\s+(\d+))?$/ pos = $1 unless pos # input = readline("Clear all breakpoints? (y/n) ", false) # if input == "y" for b in break_points b[0] = false end # end else pos = pos.to_i if break_points[pos-1] break_points[pos-1][0] = false else stdout.printf "Breakpoint %d is not defined\n", pos end end when /^\s*disp(?:lay)?\s+(.+)$/ exp = $1 display.push [true, exp] stdout.printf "%d: ", display.size display_expression(exp, binding) when /^\s*disp(?:lay)?$/ display_expressions(binding) when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/ pos = $1 unless pos # input = readline("Clear all expressions? (y/n) ", false) # if input == "y" for d in display d[0] = false end # end else pos = pos.to_i if display[pos-1] display[pos-1][0] = false else stdout.printf "Display expression %d is not defined\n", pos end end when /^\s*c(?:ont)?$/ prompt = false when /^\s*s(?:tep)?(?:\s+(\d+))?$/ if $1 lev = $1.to_i else lev = 1 end @stop_next = lev prompt = false when /^\s*n(?:ext)?(?:\s+(\d+))?$/ if $1 lev = $1.to_i else lev = 1 end @stop_next = lev @no_step = @frames.size - frame_pos prompt = false when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/ display_frames(frame_pos) when /^\s*l(?:ist)?(?:\s+(.+))?$/ if not $1 b = previous_line ? previous_line + 10 : binding_line - 5 e = b + 9 elsif $1 == '-' b = previous_line ? previous_line - 10 : binding_line - 5 e = b + 9 else b, e = $1.split(/[-,]/) if e b = b.to_i e = e.to_i else b = b.to_i - 5 e = b + 9 end end previous_line = b display_list(b, e, binding_file, binding_line) when /^\s*up(?:\s+(\d+))?$/ previous_line = nil if $1 lev = $1.to_i else lev = 1 end frame_pos += lev if frame_pos >= @frames.size frame_pos = @frames.size - 1 stdout.print "At toplevel\n" end binding, binding_file, binding_line = @frames[frame_pos] stdout.print format_frame(frame_pos) when /^\s*down(?:\s+(\d+))?$/ previous_line = nil if $1 lev = $1.to_i else lev = 1 end frame_pos -= lev if frame_pos < 0 frame_pos = 0 stdout.print "At stack bottom\n" end binding, binding_file, binding_line = @frames[frame_pos] stdout.print format_frame(frame_pos) when /^\s*fin(?:ish)?$/ if frame_pos == @frames.size stdout.print "\"finish\" not meaningful in the outermost frame.\n" else @finish_pos = @frames.size - frame_pos frame_pos = 0 prompt = false end when /^\s*cat(?:ch)?(?:\s+(.+))?$/ if $1 excn = $1 if excn == 'off' @catch = nil stdout.print "Clear catchpoint.\n" else @catch = excn stdout.printf "Set catchpoint %s.\n", @catch end else if @catch stdout.printf "Catchpoint %s.\n", @catch else stdout.print "No catchpoint.\n" end end when /^\s*q(?:uit)?$/ # input = readline("Really quit? (y/n) ", false) # if input == "y" exit! # exit -> exit!: No graceful way to stop threads... # end when /^\s*v(?:ar)?\s+/ debug_variable_info($', binding) when /^\s*m(?:ethod)?\s+/ debug_method_info($', binding) when /^\s*th(?:read)?\s+/ if DEBUGGER__.debug_thread_info($', binding) == :cont prompt = false end when /^\s*pp\s+/ obj_name = $' obj = debug_eval($', binding) customize_debug_pp if obj.kind_of? Array obj.each_index { |i| stdout.printf "[%d]=%s\n", i.to_s, debug_inspect(obj[i]) } elsif obj.kind_of? Hash or obj_name =~ /^ENV$/ # Special case ENV to print like a hash obj.each { |key, value| stdout.printf "[%s]=%s\n", key.inspect, debug_inspect(value) } elsif obj.kind_of?(String) && obj.inspect.length > 255 # Assume long strings contain packed data and show them as a # sequence of 12 byte slices in hex i = 0 while i < obj.length j = (i + 12 < obj.length ? i + 12 : obj.length) - 1 stdout.printf "[%d..%d]=0x", i, j for k in i..j stdout.printf "%2.2x", obj[k] end stdout.printf " %s\n", obj[i..j].dump i += 12 end else PP.pp(obj, stdout) end restore_debug_pp when /^\s*p\s+/ stdout.printf "%s\n", debug_eval($', binding).inspect when /^\s*h(?:elp)?$/ debug_print_help() else v = debug_eval(input, binding) stdout.printf "%s\n", v.inspect end end end MUTEX.unlock resume_all end def debug_print_help stdout.print < b[reak] [class.] set breakpoint to some position wat[ch] set watchpoint to some expression cat[ch] set catchpoint to an exception b[reak] list breakpoints cat[ch] show catchpoint del[ete][ nnn] delete some or all breakpoints disp[lay] add expression into display expression list undisp[lay][ nnn] delete one particular or all display expressions c[ont] run until program ends or hit breakpoint s[tep][ nnn] step (into methods) one line or till line nnn n[ext][ nnn] go over one line or till line nnn w[here] display frames f[rame] alias for where l[ist][ (-|nn-mm)] list program, - lists backwards nn-mm lists given lines up[ nn] move to higher frame down[ nn] move to lower frame fin[ish] return to outer frame tr[ace] (on|off) set trace mode of current thread tr[ace] (on|off) all set trace mode of all threads q[uit] exit from debugger v[ar] g[lobal] show global variables v[ar] l[ocal] show local variables v[ar] i[nstance] show instance variables of object v[ar] cl[ass] show class variables of object v[ar] c[onst] show constants of object m[ethod] i[nstance] show methods of object m[ethod] show instance methods of class or module th[read] l[ist] list all threads th[read] c[ur[rent]] show current thread th[read] [sw[itch]] switch thread context to nnn th[read] stop stop thread nnn th[read] resume resume thread nnn p expression evaluate expression and print its value h[elp] print this help evaluate EOHELP end def display_expressions(binding) n = 1 for d in display if d[0] stdout.printf "%d: ", n display_expression(d[1], binding) end n += 1 end end def display_expression(exp, binding) stdout.printf "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s end def frame_set_pos(file, line) if @frames[0] @frames[0][1] = file @frames[0][2] = line end end def display_frames(pos) 0.upto(@frames.size - 1) do |n| if n == pos stdout.print "--> " else stdout.print " " end stdout.print format_frame(n) end end def format_frame(pos) bind, file, line, id = @frames[pos] sprintf "#%d %s:%s%s\n", pos + 1, file, line, (id ? ":in `#{id.id2name}'" : "") end def display_list(b, e, file, line) stdout.printf "[%d, %d] in %s\n", b, e, file if lines = SCRIPT_LINES__[file] and lines != true n = 0 b.upto(e) do |n| if n > 0 && lines[n-1] if n == line stdout.printf "=> %d %s\n", n, lines[n-1].chomp else stdout.printf " %d %s\n", n, lines[n-1].chomp end end end else stdout.printf "No sourcefile available for %s\n", file end end def line_at(file, line) lines = SCRIPT_LINES__[file] if lines return "\n" if lines == true line = lines[line-1] return "\n" unless line return line end return "\n" end def debug_funcname(id) if id.nil? "toplevel" else id.id2name end end def check_break_points(file, klass, pos, binding, id) return false if break_points.empty? n = 1 for b in break_points if b[0] # valid if b[1] == 0 # breakpoint if (b[2] == file and b[3] == pos) or (klass and b[2] == klass and b[3] == pos) stdout.printf "Breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos return true end elsif b[1] == 1 # watchpoint if debug_silent_eval(b[2], binding) stdout.printf "Watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos return true end end end n += 1 end return false end def excn_handle(file, line, id, binding) if $!.class <= SystemExit set_trace_func nil exit end if @catch and ($!.class.ancestors.find { |e| e.to_s == @catch }) stdout.printf "%s:%d: `%s' (%s)\n", file, line, $!, $!.class fs = @frames.size tb = caller(0)[-fs..-1] if tb for i in tb stdout.printf "\tfrom %s\n", i end end suspend_all debug_command(file, line, id, binding) end end def trace_func(event, file, line, id, binding, klass) Tracer.trace_func(event, file, line, id, binding, klass) if trace? context(Thread.current).check_suspend if not trace_ruby? and ( file =~ /#{Config::CONFIG['sitelibdir']}/ or file =~ /#{Config::CONFIG['rubylibdir']}/ or file =~ %r{/debuggee.rb} ) case event when 'line' frame_set_pos(file, line) when 'call' @frames.unshift [binding, file, line, id] when 'c-call' frame_set_pos(file, line) when 'class' @frames.unshift [binding, file, line, id] when 'return', 'end' @frames.shift when 'end' @frames.shift when 'raise' excn_handle(file, line, id, binding) end return end @file = file @line = line case event when 'line' frame_set_pos(file, line) if !@no_step or @frames.size == @no_step @stop_next -= 1 @stop_next = -1 if @stop_next < 0 elsif @frames.size < @no_step @stop_next = 0 # break here before leaving... else # nothing to do. skipped. end #LJ reverse the test here because we always want the breakpoint reached # message to be display. if stop_next is null *AND* there is also a break point # the message will never display. if check_break_points(file, nil, line, binding, id) or @stop_next == 0 # LJ this test doesn't make sense and cause troubles when # on a line with a recursive call and a breakpoint on it (e.g factorial) # or when in a while loop with one line only inside the loop # # RJD: reinstated the test with a check on whether '@frames.size' # has changed to catch the recursive factorial case LJ describes # above. The while loop problem still exists though if [file, line, @frames.size] == @last @stop_next = 1 else @no_step = nil suspend_all debug_command(file, line, id, binding) @last = [file, line, @frames.size] end end when 'call' @frames.unshift [binding, file, line, id] if check_break_points(file, klass, id.id2name, binding, id) suspend_all debug_command(file, line, id, binding) end when 'c-call' frame_set_pos(file, line) when 'class' @frames.unshift [binding, file, line, id] when 'return', 'end' if @frames.size == @finish_pos @stop_next = 1 @finish_pos = 0 end @frames.shift when 'end' @frames.shift when 'raise' excn_handle(file, line, id, binding) end @last_file = file end end trap("INT") { DEBUGGER__.interrupt } @last_thread = Thread::main @max_thread = 1 @thread_list = {Thread::main => 1} @break_points = [] @display = [] @waiting = [] @stdout = STDOUT class SilentObject def method_missing( msg_id, *a, &b ); end end SilentClient = SilentObject.new() @client = SilentClient @attached = false class << DEBUGGER__ def stdout @stdout end def stdout=(s) @stdout = s end def display @display end def break_points @break_points end def client @client end def set_client( client ) @client = client DEBUGGER__.stdout = Tracer.stdout = @client end def quit #LJ flush STDOUT and ERR # @stdout.print "Quitting debugger" STDERR.flush; STDOUT.flush $stderr.flush; $stdout.flush # STDERR.close; STDOUT.close detach #DebugSvr.stop_service end def detach @attached = false @client.detach set_client( SilentClient ) end def waiting @waiting end def set_trace( arg ) saved_crit = Thread.critical Thread.critical = true make_thread_list for th, in @thread_list context(th).set_trace arg end Thread.critical = saved_crit arg end def set_last_thread(th) @last_thread = th end def suspend saved_crit = Thread.critical Thread.critical = true make_thread_list for th, in @thread_list next if th == Thread.current context(th).set_suspend end Thread.critical = saved_crit # Schedule other threads to suspend as soon as possible. Thread.pass unless Thread.critical end def resume saved_crit = Thread.critical Thread.critical = true make_thread_list for th, in @thread_list next if th == Thread.current context(th).clear_suspend end waiting.each do |th| th.run end waiting.clear Thread.critical = saved_crit # Schedule other threads to restart as soon as possible. Thread.pass end def context(thread=Thread.current) c = thread[:__debugger_data__] unless c thread[:__debugger_data__] = c = Context.new end c end def interrupt context(@last_thread).stop_next end def get_thread(num) th = @thread_list.index(num) unless th @stdout.print "No thread ##{num}\n" throw :debug_error end th end def thread_list(num) th = get_thread(num) if th == Thread.current @stdout.print "+" else @stdout.print " " end @stdout.printf "%d ", num @stdout.print th.inspect, "\t" file = context(th).instance_eval{@file} if file @stdout.print file,":",context(th).instance_eval{@line} end @stdout.print "\n" end def thread_list_all for th in @thread_list.values.sort thread_list(th) end end def make_thread_list hash = {} for th in Thread::list if @thread_list.key? th hash[th] = @thread_list[th] else @max_thread += 1 hash[th] = @max_thread end end @thread_list = hash end def debug_thread_info(input, binding) case input when /^l(?:ist)?/ make_thread_list thread_list_all when /^c(?:ur(?:rent)?)?$/ make_thread_list thread_list(@thread_list[Thread.current]) when /^(?:sw(?:itch)?\s+)?(\d+)/ make_thread_list th = get_thread($1.to_i) if th == Thread.current @stdout.print "It's the current thread.\n" else thread_list(@thread_list[th]) context(th).stop_next th.run return :cont end when /^stop\s+(\d+)/ make_thread_list th = get_thread($1.to_i) if th == Thread.current @stdout.print "It's the current thread.\n" elsif th.stop? @stdout.print "Already stopped.\n" else thread_list(@thread_list[th]) context(th).suspend end when /^resume\s+(\d+)/ make_thread_list th = get_thread($1.to_i) if th == Thread.current @stdout.print "It's the current thread.\n" elsif !th.stop? @stdout.print "Already running." else thread_list(@thread_list[th]) th.run end end end end require 'socket' ## # DEBUGGEE -> socket -> KDevelop # The Client class holds all the methods invoked from the debuggee that send and # receive data from KDevelop via the Unix domain socket. # class Client def initialize(path) @debugger = UNIXSocket.open(path) @debugger.sync=true end def detach # @debugger.close end def printf( *args ) str = sprintf(*args) @debugger.send(str, 0) end def print( *args ) str = args.to_s @debugger.send(str, 0) end def <<( arg ) @debugger.send(arg, 0) end # Return next command def readline(prompt_cmd) @debugger.send(prompt_cmd, 0) msg = @debugger.recvfrom(2048) return msg[0] end end #stdout.printf "Debug.rb\n" #stdout.printf "Emacs support available.\n\n" STDERR.sync=true STDOUT.sync=true path = $stdin.gets.chomp DEBUGGER__.set_client( Client.new(path) ) set_trace_func proc { |event, file, line, id, binding, klass, *rest| # LJ make sure the file path is always absolute. It is needed by # the Debugger plugin in KDevelop and can only be determined here # in the context of the debugged process file = File.expand_path(file) DEBUGGER__.context.trace_func event, file, line, id, binding, klass } end